Implement TTY/PTY support for interactive processes #58
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Issue #26 requests TTY/PTY support for zinit — the ability to spawn interactive processes (shells, TUI apps) under supervision and attach to them via CLI or web UI. A previous implementation on
development_tty(now archived) cannot be merged due to massive architectural divergence (81 commits behind, every file rewritten). This is a clean reimplementation on the current job-based architecture.Design Decisions
nix::pty::openpty(already in workspace, just add"pty"feature) instead ofportable-pty(10+ new transitive deps)PtyRegistry(file descriptors aren't serializable to SQLite){"resize":{"cols":N,"rows":M}})tokio-tungstenite(for UI proxy + CLI client); axum'swsfeature handles the server sidePhase 1: PTY Infrastructure (zinit_lib + zinit_server core)
1.1 Add
tty: boolto ActionSpeccrates/zinit_lib/src/db/actions/model.rs— Add#[serde(default)] pub tty: booltoActionSpeccrates/zinit_server/openrpc.json— Add"tty": { "type": "boolean", "default": false }to ActionSpec schema1.2 Enable nix pty feature
Cargo.toml(workspace) — Add"pty"to nix features list1.3 Create PtyRegistry
crates/zinit_server/src/supervisor/pty.rsPtyHandlestruct:master_fd: OwnedFd,writer: Arc<Mutex<std::fs::File>>,broadcast_tx: broadcast::Sender<Vec<u8>>PtyRegistrystruct:Mutex<HashMap<u32, Arc<PtyHandle>>>withinsert/get/remove/resizemethodsresize()useslibc::ioctl(TIOCSWINSZ)crates/zinit_server/src/supervisor/mod.rs— Addpub mod pty;, addpty_registry: Arc<PtyRegistry>toSupervisor, pass torun_job1.4 PTY-aware executor
crates/zinit_server/src/supervisor/executor.rs— Key changes:run_job(db, job_id, pty_registry)— accept registryjob.spec.tty == true, branch to PTY path:nix::pty::openpty(None, None)for master/slave fd pairstd::process::Command(needpre_exec) with:pre_exec:setsid(),ioctl(TIOCSCTTY),dup2slave to 0/1/2, close masterTERM=xterm-256colorif not in envPtyHandlein registryspawn_blocking(waitpid)Phase 2: WebSocket Endpoint on zinit_server
2.1 Enable axum ws feature
Cargo.toml(workspace) — Add"ws"to axum features2.2 Add WebSocket route + handler
crates/zinit_server/src/web.rs:pty_registry: Arc<PtyRegistry>toWebState.route("/api/jobs/{id}/pty", get(pty_ws_handler))pty_ws_handler: lookup PTY in registry, return 404 if not found, upgrade to WebSockethandle_pty_session:tokio::select!between:broadcast_rx.recv()→ send binary frame to browserws_rx.next()→ binary data goes to PTY writer, text data parsed for resize JSONcrates/zinit_server/src/main.rs— Passpty_registryclone torun_web_serverPhase 3: UI (zinit_ui proxy + xterm.js)
3.1 Add WebSocket proxy
Cargo.toml(workspace) — Addtokio-tungstenite = "0.26"crates/zinit_ui/Cargo.toml— Addtokio-tungstenite, axumwsfeaturecrates/zinit_ui/src/routes.rs— Add/api/jobs/{id}/ptyroute that:tokio_tungstenite::client_async3.2 Add xterm.js terminal tab
crates/zinit_ui/templates/index.html(or appropriate template):attachToJob(id)opens WebSocket, pipes binary data to/from Terminal{"resize":{...}}on window resizePhase 4: CLI Attach Command
4.1 Add Attach command
crates/zinit/Cargo.toml— Addtokio-tungstenitedependencycrates/zinit/src/cli/args.rs— AddAttach { id: Option<u32>, name: Option<String>, detach_key: String }to Commands enumcrates/zinit/src/cli/attach.rs:--namegiven, RPC lookup to find the running TTY job for that servicetokio_tungstenite::client_asynccrossterm::terminal::enable_raw_mode()with Drop guard for cleanuprun_attach_loop:tokio::select!betweencrossterm::event::EventStream(keyboard → WS binary) and WS recv (binary → stdout)key_event_to_bytes()helper for terminal escape sequences (reuse pattern from archived branch)crates/zinit/src/main.rs— WireCommands::Attachtoattach::cmd_attachPhase 5: Cleanup + Integration
graceful_stop_jobandcancel_job— remove from registry after process deathtty: true, verify spawn, attach via WS, send/recv, detachttyfield round-tripFiles Changed Summary
Cargo.toml(workspace)ptyfeature, axumwsfeature, addtokio-tungstenitecrates/zinit_lib/src/db/actions/model.rstty: boolon ActionSpeccrates/zinit_server/openrpc.jsonttyin ActionSpec schemacrates/zinit_server/src/supervisor/pty.rscrates/zinit_server/src/supervisor/mod.rscrates/zinit_server/src/supervisor/executor.rscrates/zinit_server/src/web.rscrates/zinit_server/src/main.rscrates/zinit_ui/Cargo.tomlcrates/zinit_ui/src/routes.rscrates/zinit_ui/templates/crates/zinit/Cargo.tomlcrates/zinit/src/cli/args.rscrates/zinit/src/cli/attach.rscrates/zinit/src/main.rsVerification
cargo build --workspace— compilestty = trueandexec = "/bin/bash"zinit attach --id <job_id>— verify interactive shell works, keystrokes flow, output displayscargo test --workspace