docs(maestro): make network layer agnostic in spec

Removes Tailscale assumptions from the design. Operator configures bind
addresses and is responsible for reachability + perimeter (Wireguard,
private LAN, or any other mechanism); maestro does not police the network
or terminate TLS itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 11:52:17 +00:00
co-authored by Claude Opus 4.7
parent 7af764c1a3
commit 810a4fbc28
@@ -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 |