diff --git a/docs/superpowers/specs/2026-05-24-maestro-design.md b/docs/superpowers/specs/2026-05-24-maestro-design.md index 9edef8c..9569b69 100644 --- a/docs/superpowers/specs/2026-05-24-maestro-design.md +++ b/docs/superpowers/specs/2026-05-24-maestro-design.md @@ -48,7 +48,7 @@ UI polish, layout/tiling, edge-case terminals — all out of scope. │ tmux ↔ WS │ │ tmux ↔ WS │ │ tmux ↔ WS │ └──────▲──────┘ └──────▲──────┘ └──────▲──────┘ │ │ │ - │ WS streams (PTY bytes, peer-to-peer over Tailscale) + │ WS streams (PTY bytes, peer-to-peer over the operator's network) │ │ │ └──────────────┬───────────┴──────────────┬───────────┘ │ │ @@ -58,13 +58,13 @@ UI polish, layout/tiling, edge-case terminals — all out of scope. └───────────────┘ └───────────────┘ ``` -Three components, all reachable via the user's Tailscale tailnet: +Three components, all reachable over an operator-managed network (LAN, VPN, mesh — maestro is network-agnostic; the operator configures bind addresses and ensures clients can route to them): - **Registry** — one always-on instance. Knows who's online and what sessions exist. Serves the web client bundle. Never touches PTY bytes. - **Daemon (`maestrod`)** — one per host. Wraps local tmux. Streams sessions directly to clients over WebSocket. - **Web client** — one TS bundle. Connects to the registry for the catalog, then directly to whichever daemon owns the session being attached. -The registry is a phone book, not a relay. This keeps it tiny and pushes byte traffic to a direct daemon ↔ client link, which Tailscale already secures end-to-end. +The registry is a phone book, not a relay. This keeps it tiny and pushes byte traffic to a direct daemon ↔ client link. Confidentiality of that link is the operator's responsibility (Wireguard, a private LAN, mTLS in a future iteration — maestro itself does not encrypt at the transport layer beyond what the operator's network provides). ## Components @@ -83,7 +83,7 @@ Non-responsibilities: - No persistent storage in the prototype. State is in-memory; daemons re-register on reconnect. - No multi-user model. One shared secret, one effective user. -Runs as a single Rust binary on a designated VM. Default port `:8080`, bound to the Tailscale interface. +Runs as a single Rust binary on a designated VM. Listens on a configured `bind_addr` (default `127.0.0.1:8080`; operator sets the actual reachable address — e.g. a Wireguard interface IP — in `~/.config/maestro/registry.toml`). ### Daemon (`maestrod`) @@ -101,13 +101,13 @@ Non-responsibilities: - Doesn't relay to other daemons. It only knows about local tmux. - Doesn't manage its own auth tokens; the registry issues them. -Runs as a single Rust binary on every host that holds sessions. Small footprint matters because some hosts are LXC containers. Default port `:8081`, bound to the Tailscale interface. +Runs as a single Rust binary on every host that holds sessions. Small footprint matters because some hosts are LXC containers. Listens on a configured `bind_addr` (default `127.0.0.1:8081`; operator sets the actual reachable address in `~/.config/maestrod/config.toml`). Also requires `registry_url` so it knows where to register on startup. ### Web client Responsibilities: -- Authenticate to the registry (cookie set on first tailnet-IP visit). +- Authenticate to the registry (cookie set on first visit; assumes the operator restricts who can reach the registry's bind address). - Render the session catalog as a tree, using each session's `metadata.label` (e.g. `personal/maestro`) to derive grouping. - Provide search across session names and labels. - Open a WS to a daemon to attach to a chosen session; render via xterm.js. @@ -156,7 +156,7 @@ The HTTP `POST /api/sessions` handler in the registry holds the client request o | `GET /api/sessions/:id/connect-info` | returns `{ daemon_url, jwt }` for direct attach | | `WS /api/events` | live catalog + presence deltas | -Auth: the registry binds its listener exclusively to the Tailscale interface (resolved at startup from `tailscale ip --4` or equivalent), so non-tailnet packets never reach the process. On first request, it sets an httpOnly cookie signed with the shared secret; all subsequent API calls require it. The cookie is the second factor against a curious tailnet member. +Auth: the registry binds its listener to the operator-configured `bind_addr` only, so packets from elsewhere never reach the process. The operator is expected to point this at a private interface (Wireguard, LAN, etc.) — maestro does not police the network itself. On first request, the registry sets an httpOnly cookie signed with the shared secret; all subsequent API calls require it. The cookie is the second factor against anyone who happens to have routing to the bind address. ### Client ↔ Daemon — one WS per attached session @@ -246,10 +246,10 @@ struct Session { - Registry generates `MAESTRO_TOKEN` at first run, printed once, stored in `~/.config/maestro/registry.toml`. - Same token deployed to each daemon (config file or env var) and used in `Hello`. -- Web client gets a cookie from the registry, scoped by tailnet IP. All registry API calls use it. +- Web client gets a cookie from the registry on first visit. All registry API calls use it. - Per-attach JWT issued by registry, signed with the shared secret, validated locally by the daemon. ~5 min expiry; client refreshes by re-requesting `GET /api/sessions/:id/connect-info`. -Single-user trust model. Tailscale is the perimeter; the token is the second factor against a curious tailnet member. +Single-user trust model. The operator's network (Wireguard / private LAN / etc.) is the perimeter; the token is the second factor against anyone who happens to have routing to the bind addresses. ## Repo layout @@ -282,7 +282,7 @@ maestro/ | UI polish, themes, animations | prove it works first | | Side-by-side / split-pane layout in one tab | protocol supports concurrent attaches; two browser tabs is the workaround | | Native TUI client, Leptos/WASM client, native Android | protocol is stable; addable later | -| Public internet (non-Tailscale) access | Tailscale assumption stands | +| Public internet exposure / TLS on the wire | maestro doesn't terminate TLS or police the network; operator runs it on a private network | | Multi-user / roles / RBAC | single user, single token | | Persistent registry storage | in-memory, re-derived from daemon reconnects | | Auto-adopting foreign tmux sessions | daemon only owns sessions it created |