-- allium: 1 -- Headless Server Mode (issue #26, phase 1) -- Scope: extension (client/server split — SSH transport for TUI and GUI) -- Distilled from: lib/bds/server.ex, lib/bds/application.ex, lib/bds/tui.ex entity BootMode { kind: desktop | server | tui -- Resolved from the BDS_MODE environment variable at application start. -- "server" is headless; "tui" is the headless server plus a TUI on the -- launching terminal; anything else is desktop. } entity SshKeyMaterial { dir: String -- /ssh, next to the database host_key: String -- ssh_host_rsa_key, generated on first boot authorized_keys: String -- created empty (mode 600) on first boot -- Invariant: key material lives in the private app data dir, -- never in the repo or an application priv dir. } surface ServerRuntimeSurface { facing _: ServerRuntime provides: ApplicationStarted(mode) SshClientConnected() GuiConnectRequested() } rule ModeResolution { when: ApplicationStarted(mode) -- BDS_MODE environment variable decides the mode once per boot. -- Fallback (issue #33): resolved desktop mode boots as tui when no -- graphical session is available, so the app always opens a UI -- rather than crashing wx; a log line records the switch. Headless -- means: non-Darwin unix without DISPLAY/WAYLAND_DISPLAY (plain ssh -- included; ssh -X with a forwarded display keeps the GUI), or macOS -- inside an ssh session (SSH_CONNECTION/SSH_CLIENT/SSH_TTY — macOS -- never sets DISPLAY). Explicit server/tui modes and Windows are -- never rewritten. Because the :desktop dependency boots wx in its -- own application start, config/runtime.exs sets NO_WX=1 for every -- non-desktop effective mode (BDS.Server.prepare_boot_env) so the -- dependency starts inert before any BDS supervisor runs. In tui -- mode prepare_boot_env also silences every other terminal writer — -- the default logger handler moves to a rotating file in the private -- app dir and Bumblebee's model-download progress bar is disabled — -- because the TUI owns the terminal and each stray console line -- scrolls the alternate screen up one row. Desktop/server modes and -- SSH-served TUI sessions keep normal console logging. ensures: BootMode.created(kind: mode) } rule DesktopBoot { when: ApplicationStarted(mode: desktop) -- Unchanged behavior: wx window shell, loopback HTTP endpoint. ensures: DesktopWindowStarted() } rule ServerBoot { when: ApplicationStarted(mode: server) -- Started by `bds-cli server` or explicit desktop boot mode. Headless: no -- desktop UI children at all. Works on macOS, Windows, Linux. ensures: LoopbackEndpointStarted() -- HTTP endpoint stays bound to 127.0.0.1; never exposed directly ensures: CliSyncWatcherStarted() ensures: SshDaemonStarted() -- Public-key auth only (no passwords). Direct TCP/IP forwarding is -- restricted to the server-owned loopback endpoint; the native GUI -- protocol and terminal sessions use channels on the same connection. ensures: SshKeyMaterial.created() -- host key generated and authorized_keys touched on first boot only } rule GuiRemoteConnection { when: GuiConnectRequested() -- Desktop "Connect to Server…": SSH connects with the same public keys -- (client identity + known_hosts in the private ssh dir), negotiates the -- versioned `ruds` subsystem, lists server projects, and opens the selected -- project through the shared CoreHost application-service operations. The -- channel carries ordered domain events, task snapshots, and the server UI -- locale. Request IDs make replayed writes exactly-once. Disconnect returns -- to the local project without exposing or copying the SQLite database. ensures: NativeRemoteProjectOpened() } rule TuiSession { when: SshClientConnected() -- Each SSH client gets its own TUI app instance on the server; -- only terminal cells cross the wire. ensures: TuiAppMounted() -- UI locale comes from the server-side UI language setting, -- identical for every client }