Add X11 forwarding for interactive jobs (xcecli -I -X, ssh -X style)
Render -X jobs' windows on the user's own X server with zero client install, mirroring the ssh -X experience: the job command starts with DISPLAY pointed at a per-job TCP listener on the worker, and the user's xcecli relays that listener's byte stream to their local X server. - xcecli: new -X/--x11 flag (interactive=2 + exdata "x11":1). After submit it fetches the per-job MIT-MAGIC-COOKIE-1, registers it in the user's Xauthority, then bridges one xattach websocket to the local X server address parsed from $DISPLAY (unix socket or TCP). The cookie entry is removed when the session ends. Pre-flight checks report missing DISPLAY/xauth before submit. - apiserver: GET /xce/v1/jobs/:id/xattach relays websocket binary frames to the worker's forwarding port (raw TCP, no ttyd). Close codes match the ttyd attach (4000 unreachable, 4001 ended). The job-finished event ends the relay. Teardown closes both hops as soon as either pump exits, so a client disconnect propagates instead of deadlocking in a blocking read. - worker: XJOB jobs with exdata x11=1 skip VNC, listen on 6100-6199, generate a per-job xauth file (/tmp/job_<id>_xauth, worker-side since the worker has no session), export DISPLAY/XAUTHORITY to the command, and bridge the app connection (loopback) with the relay connection (non-loopback; roles classified by source address, not arrival order). A 60 s no-client watchdog reaps jobs nothing ever attached to; the v1 single-connection boundary refuses a second X connection loudly. t1-verified per the plan matrix: window rendering (xclock/xeyes on Xvfb), normal end -> DONE + xauth removed, no-client 60 s reap (137), kill -> immediate teardown with no residue, -I/-I --browse regression, daemon health, missing-DISPLAY error, second-connection refusal. (--xjob VNC on t1 fails for lack of a vncserver binary; pre-existing.) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
4f188310ba
commit
85a22271c1
19 changed files with 1744 additions and 50 deletions
|
|
@ -549,8 +549,8 @@ type rawJobData struct {
|
|||
submit *rawSubmitData
|
||||
execHosts []rawHostSlot
|
||||
pendReason *rawPendReason
|
||||
runUsage *rawRunUsage // nil until the job's first usage report
|
||||
pidInfo *rawPidInfo // nil until the worker's start report
|
||||
runUsage *rawRunUsage // nil until the job's first usage report
|
||||
pidInfo *rawPidInfo // nil until the worker's start report
|
||||
}
|
||||
|
||||
// rawWorkerData mirrors the C xce_worker_t fields used by the Go model.
|
||||
|
|
@ -606,6 +606,24 @@ func interactiveURLFromExdata(exdata string) string {
|
|||
return obj.URL
|
||||
}
|
||||
|
||||
// x11CookieFromExdata extracts the per-job X11 auth cookie of an -X job
|
||||
// from the same start-report exdata (the worker packs it as
|
||||
// {"x11cookie": ...} next to the x11:// URL). The xcecli -X client needs it
|
||||
// to register the session's MIT-MAGIC-COOKIE-1 with the user's Xauthority
|
||||
// before the app's first X connection is relayed through.
|
||||
func x11CookieFromExdata(exdata string) string {
|
||||
if exdata == "" {
|
||||
return ""
|
||||
}
|
||||
var obj struct {
|
||||
Cookie string `json:"x11cookie"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(exdata), &obj); err != nil {
|
||||
return ""
|
||||
}
|
||||
return obj.Cookie
|
||||
}
|
||||
|
||||
// pidInfoJSONB marshals a start report's pids/pgids into the PIDInfo Jsonb
|
||||
// the API exposes (processJobData parses the JSONB string before the row
|
||||
// leaves the API, so clients see an object with pids/pgids). Shared by
|
||||
|
|
@ -730,11 +748,13 @@ func rawJobToModel(r rawJobData) *models.JobInfo {
|
|||
// string before the row leaves the API, so clients see an object
|
||||
// with pids/pgids. The interactive URL is promoted to its own
|
||||
// top-level field — it is what xcecli's waitForInteractiveURL and
|
||||
// the --browse flow consume.
|
||||
// the --browse flow consume. An -X job's auth cookie rides the same
|
||||
// report and gets the same treatment.
|
||||
if jb, ok := pidInfoJSONB(p.pids, p.pgids); ok {
|
||||
info.PIDInfo = jb
|
||||
}
|
||||
info.InteractiveJobURL = interactiveURLFromExdata(p.exdata)
|
||||
info.X11Cookie = x11CookieFromExdata(p.exdata)
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue