The web terminal. The lichen-shell crate is folded into lichen-site as its shell module. On a multi-site server with the sandbox on, the shell now runs as a child of its site's process, inside that process's sandbox, instead of in one of the old per-terminal sandboxes (bubblewrap, systemd-run, an incus VM); those are deleted. The front server opens each terminal's PTY pair for a site process, so the sandbox grants no terminal device.
AST-verified changes
- PER-SITE-PROCESS P2c-1 shell fold (317941bd):
crates/lichen-shell/src/**became lichen-site'ssrc/shell.rsandsrc/shell/(21 files). Mapmachine-docs/reviews/P2C1-SHELL-MOVE-MAP-2026-10-09.json. Checked by the builder (run id not recorded): "Shell move check (rn6, b33796d7..317941bd) ok: 3683 items, 21 renamed, 145 carried, 0 visibility". The adversary's rerun: "317941bd equal=true 21-renamed 145-carried 0-vis". Allowed: the scoped rewriteslichen_shell→lichen_site::shell, andcrate::→crate::shell::inside the folded files.
Roughly 112 of this PR's 1,833 changed lines are code the fold carried verbatim. In git diff -M, git pairs pty.rs, router.rs and session.rs with their lichen-shell originals, so only their later changes show. Most of the rest is the deleted sandbox backends (−1,116 in lichen-shell), which have no new home: they were removed, not moved.
On this forge the 3 renamed files here show as a whole deletion and a whole addition, so its diff reads 3,727 changed lines; with rename detection (git diff -M full/19-site-api full/20-shell) it is 1,833.
What changed and why:
- The fold (P2c-1). The crate became a module, with no change of behaviour in the fold commit itself (above).
- The terminal runs in its site's process (P4-4b, design note §18 A10;
shell/sandbox.rs, new). portable-pty starts the shell through the pre-main hook'snestmode (/proc/self/exe nest -- <shell>), which gives it a nested Landlock domain of its own inside the site's. Its environment is the base list,TERM, and aHOMEin the site's.lichen. In local mode the shell runs unwrapped with the user's ownHOME,USER,LOGNAMEandSHELL. The old backends (lichen-shell/src/sandbox/{bwrap,systemd,incus,unsandboxed}.rs,incus.rs's VM pool,ShellState::with_incus) are deleted; the module docs say they are archived atlichen-code-archive1f01c136. - The PTY pair comes from the front server (STC-PTS1;
shell/peer_pty.rs, new). A sandboxed process that opened its own terminals needed all of/dev/pts, which let one site's terminal open another's. A site process now asks the front server for each pair; outside a site process the pair is opened locally. The child end is opened from the pair, never by path. - Closing a terminal hangs it up (
pty.rs, SITE-FRONT-BOUNDARY decision 6). In a site process thenestmode is the session's reaper: closing sendsSIGHUP, the nest mode passes it to the shell (and kills a shell that ignores it), and it ends once nothing of the session is left, anohup'd job included. Local mode keeps portable-pty's kill. - The routes are a resource under the site's API, in the site's
WRITESgroup (router.rs):POST /cms/api/shell,GET /cms/api/shell/{id}/wsandDELETE /cms/api/shell/{id}.session.rskeeps the idle and hard timeouts.
Files (crates/lichen-site/src/ unless noted)
shell/sandbox.rs— new: what a shell is started as, in a site process and in local mode. Read this first.shell/peer_pty.rs— new: the PTY pair from the front server (STC-PTS1), and since FULL-SITE-PROCESS 6g the shell's own child-routes socket at its descriptor (ROUTES_FD), where it may ask for a publish or a push (#40).shell/pty.rs(fromlichen-shell/src/pty.rs) — spawning, and the hangup and reaping on close; bash is found on thePATHwithout runningwhichas a child (SECURITY-DEBTFSP-NESTLESS1).shell/session.rs(fromlichen-shell/src/session.rs, −146) — sessions and timeouts, without the backend choice.shell/router.rs(fromlichen-shell/src/router.rs),shell.rs— the routes and the module root.crates/lichen-shell/(Cargo.toml,lib.rs,incus.rs,sandbox/*.rs) — deleted.
Lives elsewhere
- The
nestmode itself and the nested domain (prepare_ordinary): #17. The front server's side of the PTY request (front_pty,ToFront::Pty): #19. - Who may open a terminal (the routes' groups and
Permissions): #18. - Not in any PR (base commit):
shell/README.md, the built-in editor's terminal (xterm), and the tests (tests/terminal_ws_tests.rsand the shell's#[path]tests).
Category: The shell: lichen-shell folded into the site
Size: 14 files, +484/−1,349
Rounds: round 0 at stack ae42e3c0, unchanged at 29a7c15b and 6b650b2a; round 1 at b66397c0 (FULL-SITE-PROCESS): shell/peer_pty.rs gives the shell its own child-routes socket, where it may ask for a publish or a push (6g, #40), and shell/pty.rs finds bash on the PATH itself instead of running which as a child with no domain of its own (FSP-NESTLESS1). Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/36/round/1/interdiff
Plans and ledger: FULL-SITE-PROCESS-2026-10-25.md (6g); PER-SITE-PROCESS-2026-10-09.md (P2c-1; P4-4b, design note PER-SITE-PROCESS-P0-DESIGN-2026-10-09.md §18 and amendments A7, A10), CHILD-ENV-CLEAR-2026-10-08.md (E1, E2: the shell's environment), SITE-FRONT-BOUNDARY-2026-10-17.md (decision 6), the fix STC-PTS1.
Inline #[cfg(test)] modules: none. 3 files point to a test file with #[path] (shell/pty.rs, shell/sandbox.rs, shell/session.rs), and shell/peer_pty.rs has test-only items.
Bucket moves: none from round 1, which put the shell in site core. Split out here because the lichen-shell fold and the backends' removal make it a subject of its own.
Round 1: #13 (Site crate core) covered shell.rs, shell/peer_pty.rs, shell/pty.rs and shell/router.rs. The crate deletion, shell/sandbox.rs and shell/session.rs are new to the set in this range.