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
|
|
@ -478,6 +478,52 @@ func TestJobHandler_SubmitInteractiveAttachExdata(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestJobHandler_SubmitX11Exdata checks the submit handler packs x11 into
|
||||
// the job's ExtendData for -X jobs: the key selects the X11-forwarding
|
||||
// backend worker-side (DISPLAY to the forwarding port instead of VNC).
|
||||
func TestJobHandler_SubmitX11Exdata(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
req map[string]interface{}
|
||||
wantX11 bool
|
||||
}{
|
||||
{"x11 job", map[string]interface{}{
|
||||
"job_name": "a", "command": "xclock", "interactive": 2, "x11": 1,
|
||||
}, true},
|
||||
{"plain xjob (VNC backend)", map[string]interface{}{
|
||||
"job_name": "a", "command": "xclock", "interactive": 2,
|
||||
}, false},
|
||||
{"tty job without x11", map[string]interface{}{
|
||||
"job_name": "a", "command": "sleep 1", "interactive": 1,
|
||||
}, false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
f.seedQueue("normal")
|
||||
|
||||
w := f.submit(tc.req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
subs := f.submissions()
|
||||
if len(subs) != 1 {
|
||||
t.Fatalf("submitJob calls = %d, want 1", len(subs))
|
||||
}
|
||||
var ex map[string]interface{}
|
||||
if raw := subs[0].Job.ExtendData.String; raw != "" {
|
||||
if err := json.Unmarshal([]byte(raw), &ex); err != nil {
|
||||
t.Fatalf("ExtendData %q is not JSON: %v", raw, err)
|
||||
}
|
||||
}
|
||||
_, hasX11 := ex["x11"]
|
||||
if hasX11 != tc.wantX11 {
|
||||
t.Errorf("ExtendData x11 present = %v, want %v (exdata: %s)", hasX11, tc.wantX11, subs[0].Job.ExtendData.String)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestJobHandler_SmokeControlByID controls one seeded ordinary job by ID
|
||||
// and checks the dispatch is recorded once with its unique_job_id.
|
||||
func TestJobHandler_SmokeControlByID(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue