Files
RuDS/specs/server.allium
2026-07-22 14:33:42 +02:00

97 lines
4.2 KiB
Plaintext

-- 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 -- <data dir>/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)
requires: mode = desktop
-- Unchanged behavior: wx window shell, loopback HTTP endpoint.
ensures: DesktopWindowStarted()
}
rule ServerBoot {
when: ApplicationStarted(mode)
requires: 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
}