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:
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user