feat(booterm): structured pty_exited WS notifications. Plan-validated, impl-validated, code-reviewed green (contracts build clean, contracts test 29/29, booterm + web typecheck clean). wip: in-progress inference/provider refactor (agents.ts, provider.ts, new llama-providers.ts, removed llama-args-validator), plus arena, dispatcher, compaction, schema changes. openspec: pty-exit-notifications complete; x-agent-flags planned (not yet implemented).
2.9 KiB
BooControl SSH editor verb-mode + model pull — proposal
Status: READY. Extends BooControl P9.1 (the SSH config editor) so it works against a forced-command-locked SSH key and can pull HuggingFace models into a host's models directory.
Why
P9.1 shipped the SSH config editor sending raw shell commands (cat, cp,
cat >, the restart command) over SSH. To restrict the BooControl key to a
single drive/folder, the operator has deployed an authorized_keys
forced command on the GPU hosts that binds the key to a wrapper script
(apps/control/remote/boocontrol-edit.{ps1,sh}). A forced command ignores the
client's command string and only honors fixed verbs (read / backup /
write / restart / pull <repo>). So the editor's raw-shell commands are now
rejected by those hosts, and there is no way to drive the wrapper's pull verb.
This change teaches the editor to speak verbs (per host) and adds a model-pull capability, closing the loop so a locked-down key is fully usable from the cockpit.
What changes
- Per-host SSH mode.
control_hosts.ssh_mode(shell|wrapper, defaultshellfor backward compatibility).shellkeeps today's raw-command behavior for hosts without a wrapper;wrappersends verbs. - Verb-mode remote ops.
ssh-config.tsgains aRemoteOpsseam with two implementations (shellOps,wrapperOps).applyRemoteConfigand the read/diff paths route through it. The pipeline (validate -> read -> diff -> backup -> write -> restart -> health-wait) is unchanged; only the wire commands differ. - Model pull.
POST /api/hosts/:id/pull {repo}runs a non-blocking job that invokes the host'spull <repo>verb, streaming progress over the existingcontrol_jobframe (jobTypeaction,detail.kind = "pull"). The repo id is validated server-side (^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$) as defense in depth on top of the wrapper's own check. - UI. The Host config editor gains an SSH-mode selector and a "Pull model" field that posts a repo id and shows job progress.
Out of scope
- Changing the wrapper scripts (already in
apps/control/remote/). - A new
control_jobjobType (reuseactionto avoid a contracts change). - Progress percentage parsing from
huggingface-clioutput (stream raw lines).
Risks
| Risk | Mitigation |
|---|---|
| Refactor breaks existing P9.1 shell-mode tests | shellOps emits the identical cat/cp/cat >/restart command strings; existing assertions hold. mode defaults to shell. |
| Repo id injection via the pull verb | server-side regex validation + the wrapper's own regex; repo passed as a single token. |
| Long pull blocks the HTTP request | non-blocking job (fire-and-forget like bench/eval), progress over control_job. |
Operator points a wrapper-mode host at a box without the wrapper |
verbs fail loudly (the forced command / shell returns "denied"/127); reported per step, no silent fallback. |