Getting started
OwlMux currently implements the pre-release single-node and clustered profiles. You can initialize one Deployment, authenticate the terminal-first Browser shell, manage generated SSH credentials and complete Machine/Relay lifecycle operations through saved Hosts, explicitly rebind an active Machine for future SSH children, enroll one target Relay, claim the actual Machine owner, and open one or more page-memory workspace tabs that explicitly select or create target tmux sessions. Each Host in the UI maps to one Machine API/domain resource. One owner-local Browser attachment per Machine route may write through bounded pane input, automatic visible-viewport resize, and closed window/pane operations while other attachments observe. In clustered mode Browser and Machine-affine API ingress may use at most one fresh challenge-authenticated internal TLS/WSS hop to the Relay-owning node.
Prerequisites
- stable Rust from
rust-toolchain.toml; - Node.js 24;
- pnpm 11.20.0;
- Docker with Compose v2;
- OpenSSH client for a host-run Server.
Install locked dependencies
make installValidate the implementation
make check
make test
make build
make test-e2e
make test-e2e-clustered
make test-e2e-matrix
make docker-buildmake check verifies generated contracts, formats and lints Rust/Web sources, builds docs, and validates Compose. make test runs Rust and Web tests. make build builds the Web artifact plus both release binaries. make test-e2e creates isolated PostgreSQL and target containers and uses versioned attachment-WebSocket clients to exercise enrollment, owner claim, OpenSSH/tmux projection and writer operations, recovery, hard fencing, and target tmux survival. make test-e2e-clustered covers coherent two-node owner routing and recovery. make test-e2e-matrix is an opt-in compatibility check across the documented tmux and login-shell profiles. make docker-build builds and smoke-tests the unprivileged production image. CI also runs production JavaScript and RustSec dependency audits.
Run the Server
make devOpen http://127.0.0.1:8080, enter the disposable API key from dev/server.env, and select Open OwlMux. Authentication lands on /workspaces. The same-origin header links Workspaces, Hosts, Credentials, and Audit; the Deployment status opens /deployment. The key and at most 16 workspace tabs remain only in current page memory. Internal navigation preserves them, while reload, logout, page close, or navigation away clears them. The Server initializes PostgreSQL, creates the default generated Ed25519 credential, registers one fenced node incarnation, and exposes the protected application.
Start PostgreSQL separately when needed:
make dev-up
make dev-statusRun the target fixture
The opt-in target contains sshd bound only to its own loopback address and target-owned tmux:
make dev-target-up
make dev-target-statusThe fixture intentionally starts with no authorized OwlMux key. Use Credentials to copy the selected generated public key and install it into the target's authorized_keys as the target administrator. Then use Hosts → Add Host to create the underlying Machine with the target Ed25519 host public key and copy the one-use enrollment token.
Relay reads the token from a no-echo prompt or bounded stdin and stores its identity in a mode-0600 state file. After the target administrator installs the displayed Server-generated public key, enroll once and explicitly confirm that authorization is ready:
owlmux-relay enroll \
--server ws://host.docker.internal:8080 \
--state /var/lib/owlmux/state.json \
--account owlmuxThen run the authenticated tunnel:
owlmux-relay run \
--server ws://host.docker.internal:8080 \
--state /var/lib/owlmux/state.jsonowlmux-relay start combines those steps for first use and runs directly from already active state. Use --confirm-ready only in automation that has independently installed the displayed key; the flag skips only the local human acknowledgment and does not mutate target authorization. The independent Server-side SSH proof still runs and must succeed. Active re-enrollment requires the protected Server-side re-enrollment action first, followed by owlmux-relay reset, explicit new-token issuance, enroll, and run.
For production use wss://, a protected persistent state directory, and a target-local Relay process. The Relay endpoint is fixed to 127.0.0.1:22; Browser input cannot choose SSH destinations, options, identities, commands, or tmux syntax.
Current attachment behavior
An attachment upgrade requires the exact configured Origin and a bounded auth.api_key first frame. Only after authentication does the Server resolve the current owner, use the local application boundary or one internal owner WSS hop, open a Relay stream, materialize the current credential in an exclusive child directory, run constrained OpenSSH, verify the target host/account, and probe tmux.
Every attachment stops at a fresh chooser, even for zero or one session. The chooser may refresh its observed list or create a bounded-name session through one closed operation. Selecting an observed session ID plus creation time atomically attaches a tmux -C client as a read-only ignore-size observer. Under the route dispatch barrier, the current writer uses tmux's dedicated read-only toggle, clears ignore-size, and applies its bounded viewport before hydration; takeover reverses that order for the old writer before promoting the claimant. Server pauses the selected client while observing the target-current window and visible panes, then continues each pane and runs one synchronous capture-plus-final-metadata command list as its per-pane snapshot/live cutover. It accepts the two consecutive guarded responses under one deadline, retries only the pre-mutation hydration cutover if pane output separates capture from metadata, uses that final cursor/mode metadata to construct the bootstrap, and revalidates the complete topology afterward. Output already covered by that final capture is discarded; post-capture output is buffered within fixed bounds while projection metadata and binary-safe snapshot chunks reach the Browser. Browser validates and atomically installs the complete target-authoritative layout only at the final ready phase, after which buffered and new binary live output reaches one xterm.js instance per pane.
Exactly one attachment per current Machine route is the owner-local writer, including when multiple page-memory tabs target the same Host. The UI exposes a free claim as Take control, an occupied claim as Take over, and the holder as You have control. Concurrent claims serialize so only one succeeds. Explicit takeover demotes the previous control client to read-only ignore-size, promotes the claimant through tmux's dedicated read-only toggle, replaces the pointer, applies the writer's bounded viewport, and hydrates authoritative state before input is accepted. The UI has no rows/columns form: only the current visible ready writer measures its pane surface and xterm cell size, then debounces and deduplicates bounded resize requests. Observers and hidden tabs never change target geometry, and the replacement target-authoritative projection remains final. Only the writer's currently observed active pane accepts bounded canonical base64url bytes; Server renders them as hex arguments to tmux rather than command grammar. Closed resize and observed window/pane selection operations share the same dispatch barrier. Known failures are reported, while an ambiguous target outcome is never retried or compensated and returns the attachment to fresh discovery. Layout, pane, window, or session notifications trigger a fresh projection epoch. A Relay route replacement closes the stale route-bound attachment; after the replacement claims a higher connection epoch, a new authenticated attachment starts at a fresh chooser rather than silently reselecting a session.
Detach, Relay failure, Server shutdown, database loss, or Browser close removes OwlMux access only; it does not stop target tmux.
Stop development infrastructure
make dev-downRead Relay and roaming for the trust model, Recovery and incident response for executable failure/cold-rotation evidence and operator runbooks, and Architecture for the implemented topology and remaining product boundary.