Skip to content

Give every member a persistent master terminal - #61

Merged
ilovecrayons merged 1 commit into
mainfrom
feat/member-terminal
Sep 4, 2026
Merged

Give every member a persistent master terminal#61
ilovecrayons merged 1 commit into
mainfrom
feat/member-terminal

Conversation

@ilovecrayons

Copy link
Copy Markdown
Contributor

Implements plan Step 2 (the master terminal): Group 1's second and final PR, on top of #50.

What changed

  • Every member gets one long-lived container ("your environment") running the standard image with their persistent home mounted at $HOME, created on first open and re-adopted across server restarts. /bin/sh -l fallback when the image carries no bash.
  • Tabs are TTY execs inside that container: main plus up to five named tabs ([a-z0-9-]{1,32}, cap error terminal: at most 6 tabs), each a ptyhost session keyed terminal:<member>:<tab> with its own transcript. Exec falls back to /bin/sh -l on exit 126/127.
  • New SSH subsystem aether-terminal: the authenticated member can only ever open their own terminal; membership is re-validated live (removal closes the socket with the membership-revoked exit status). Control methods terminal.status and terminal.stop are self-scoped.
  • Exiting the main shell destroys the container and prunes the row; the next open recreates it. member.remove stops the terminal and removes the member home (the home is retained when the stop fails, so a live bind mount is never deleted underneath a running container).
  • Local gateway: GET /ws/terminal?tab=<name> (1008 on membership loss, 1000 on shell exit); ws capability list gains terminal.
  • CLI: aether terminal [--tab <name>], aether terminal status, aether terminal stop. aether agent add <shipped> opens the terminal and types the vendor install script; static guidance remains the fallback.
  • Web: board terminal dock (shared Dock + xterm-host, tabs, stop confirm, "Your environment starts on first open" empty state) behind the terminal capability; the agents wizard embeds the live terminal for setup with a static fallback.
  • A live terminal attach holds the self-update idle check open; the container alone does not (it survives restarts).
  • Docs: new docs/terminal.md; local-gateway, environment-home, harnesses, quickstart updated.

Verification

Plan verification item 3 (master terminal), plus the Step 1 leftovers:

  • go test -race -tags integration -run TestIntegrationMasterTerminal ./internal/server/ (real Docker):

    ok   github.com/3xDevOps/Aether/internal/server 14.070s
    

    Drives the full lifecycle: first open starts the container (echo home=$HOME prints /root), a second tab shares it, detach + server restart re-adopts the running container (terminal.status still running, tabs intact, fresh attach answers), terminal.stop tears it down and the next status reports not running.

  • Membership revocation: go test ./internal/sshd/ -run TestTerminalDropsOnMembershipRevocation (removed member's terminal socket closes with the membership-revoked exit status).

  • Scheduler lifecycle unit tests: exit-then-reopen recreates (exited container destroyed, row pruned), tab regex/cap with the literal error, sh fallback, startup re-adoption, BuildEnvironmentPlan(nil ws, terminal) uses StandardImage.

Repo checks after rebasing onto current main (af3adb1):

  • make fmt-check, make vet, make lint: 0 issues
  • make test: 39 packages ok; three pre-existing machine-local failures that fail identically on a clean origin/main checkout on this machine (internal/localgw TestUpdateApply* - a real desktop app is installed; internal/selfupdate TestOwnedOnlyByWalksToTheTop from Update a root-owned CLI from the dashboard through the macOS admin dialog #49); CI passes them
  • make test-integration: ok apart from the same machine-local cases
  • make public-audit: PASS
  • web/: bun install --frozen-lockfile && bun run typecheck && bun run test: 50 files, 520 tests passed

Adversarial review findings fixed before this PR:

  • Exited terminal containers were never destroyed and wedged every subsequent open; adoption now probes the container (short Wait) and destroys corpses, supervision destroys on exit, with a regression test.
  • Supervision cleanup raced a StopTerminal-then-reopen and could delete the successor's row; cleanup now runs under the per-member lock and only when it still owns the entry.
  • Tab errors are mapped by sentinel (errors.Is) instead of message substring.
  • The idle-update hold moved from container lifetime to live attaches, so a parked environment no longer blocks scheduled server updates forever.
  • The wizard no longer re-types the install script on remount, and the dock surfaces membership refusals instead of freezing.

One long-lived container per member ("your environment"), reachable from
the CLI and a dock under the run board. The standard image with the
member's home mounted at $HOME; /bin/sh -l fallback when the image has no
bash.

- domain.Terminal + member_terminals store methods (table shipped with the
  Step 1 migration); scheduler EnsureTerminal/EnsureTerminalTab/
  TerminalStatus/StopTerminal with per-member locking, container
  supervision (exit destroys and prunes so the next open recreates), and
  startup re-adoption of surviving containers.
- Tabs are TTY execs in the same container: main plus up to five named
  tabs ([a-z0-9-]{1,32}), each a ptyhost session keyed
  terminal:<member>:<tab>.
- New SSH subsystem aether-terminal with membership-only live revocation;
  control methods terminal.status/terminal.stop; member.remove stops the
  terminal and removes the home (retained if the stop fails).
- Local gateway GET /ws/terminal?tab=; ws capability list gains terminal;
  cli TerminalStream; new aether terminal [--tab] / status / stop.
- aether agent add and the GUI wizard type the harness install script
  into the live terminal (static instructions as fallback).
- Web: env-terminal store slice, board terminal dock (shared Dock +
  xterm-host, empty state, stop confirm), wizard live setup.
- A live terminal attach holds the self-update idle check; the container
  itself does not (it is re-adopted after restart).
- Docs: new docs/terminal.md; local-gateway, environment-home, harnesses,
  quickstart updated.
- Integration: TestIntegrationMasterTerminal drives start, tabs, restart
  re-adoption, and stop against real Docker.
@ilovecrayons
ilovecrayons merged commit 2114e60 into main Sep 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant