chore(maestro): retarget installer, e2e harness, and docs at unified maestro binary (NAP-245)

- Installer: one `cargo install maestro` instead of two; ExecStart becomes
  `~/.cargo/bin/maestro daemon`; user unit renamed `maestrod.service` →
  `maestro.service`; configure step is `maestro daemon configure`.
- E2E harness: both `Daemon` and `Registry` subprocess wrappers shell out to
  the unified `maestro` binary with the appropriate subcommand.
- State dir: `~/.local/state/maestrod` → `~/.local/state/maestro`.
- Docs: CLAUDE.md repo layout, design spec repo layout + "Daemon (`maestro daemon`)"
  heading, INSTALL.md (full rewrite of CLI references + new thin-builds section).
This commit is contained in:
2026-05-27 10:43:51 +00:00
parent 35482118b1
commit ade0d49096
12 changed files with 170 additions and 141 deletions
+49 -31
View File
@@ -13,13 +13,13 @@ The repo is private, so grab `install.sh` while signed in to gitea, then run it:
```
The bootstrap script builds `maestro-installer` into a temp directory, runs the
wizard, and cleans up — only `maestrod` and `maestro` end up persisted in
wizard, and cleans up — only the unified `maestro` binary ends up persisted in
`~/.cargo/bin`.
**Updating is the same command.** Re-running `./install.sh` detects an
existing `[daemon]` section in `~/.config/maestro/config.toml` and an
existing systemd unit, skips the configure prompt, refreshes the unit, and
runs `systemctl --user restart maestrod` instead of `start`. No `--update`
runs `systemctl --user restart maestro` instead of `start`. No `--update`
flag needed.
Inside the script, `cargo install --git https://git.naps.pt/naps62/maestro …`
@@ -38,20 +38,39 @@ Pass flags through to the wizard after `./install.sh`:
The wizard:
1. Runs `cargo install` for `maestro-daemon` and `maestro-cli` (pinned to the
committed `Cargo.lock` via `--locked`).
2. Optionally runs `maestrod configure` to populate `~/.config/maestro/config.toml`
(one shared TOML for `maestrod`, `maestro-registry`, and the `maestro` CLI).
1. Runs `cargo install` for the unified `maestro` crate (pinned to the
committed `Cargo.lock` via `--locked`). The same binary hosts the CLI,
the per-host daemon (`maestro daemon`), and the always-on registry
(`maestro registry`), gated by Cargo features.
2. Optionally runs `maestro daemon configure` to populate `~/.config/maestro/config.toml`
(one shared TOML for the CLI, daemon, registry, and web).
3. On systemd hosts, optionally writes a user unit at
`~/.config/systemd/user/maestrod.service`, reloads systemd, and offers to
`~/.config/systemd/user/maestro.service`, reloads systemd, and offers to
enable + start it.
Both binaries land in `~/.cargo/bin` — make sure that's on your `$PATH`.
The binary lands in `~/.cargo/bin/maestro` — make sure that's on your `$PATH`.
## Thin builds for single-role hosts
The default install pulls in everything. Registry-only and daemon-only hosts
can opt out of unused dependencies:
```sh
# Registry server — no tmux/PTY/jwt-verifier code:
cargo install --git https://git.naps.pt/naps62/maestro \
--no-default-features --features registry --locked maestro
# Daemon-only host — no embedded web assets, no registry HTTP surface:
cargo install --git https://git.naps.pt/naps62/maestro \
--no-default-features --features daemon --locked maestro
```
The wizard always installs the default-feature build.
## Adding the browser UI (optional)
`maestro-web` is a separate binary that serves the SPA bundle and reverse-
proxies API calls to a co-located `maestrod`.
proxies API calls to a co-located `maestro daemon`.
**Interactive install (default path).** The wizard now offers it as an
optional step right after the daemon flow:
@@ -63,7 +82,7 @@ Install maestro-web? [Y/n]
Press `Y` and it `cargo install`s `maestro-web`, drops a
`~/.config/systemd/user/maestro-web.service` unit (ordered after
`maestrod.service`), reloads systemd, and offers to enable + start it. Re-runs
`maestro.service`), reloads systemd, and offers to enable + start it. Re-runs
of `./install.sh` detect the existing unit and refresh + restart silently.
Pass `--no-web` to skip the prompt entirely (headless hosts), or `--yes`
@@ -100,18 +119,18 @@ where you want browser access answer yes — both binaries end up in
|---|---|
| `--git <url>` | Install from a different git repo (default: this one). |
| `--branch <name>` | Pin a branch with `--git`. |
| `--path <dir>` | Install from a local workspace checkout (`--path` + `-p <crate>`). |
| `--path <dir>` | Install from a local workspace checkout (`--path` + `-p maestro`). |
| `--no-locked` | Drop `--locked` from `cargo install` (resolves a fresh dep tree). |
| `--exec-start <path>` | Override the systemd unit's `ExecStart` (default `~/.cargo/bin/maestrod`). |
| `--exec-start <path>` | Override the systemd unit's `ExecStart` (default `~/.cargo/bin/maestro daemon`). |
| `--no-systemd` | Skip the systemd step even if detected. |
| `--no-configure` | Skip the `maestrod configure` step. |
| `--no-configure` | Skip the `maestro daemon configure` step. |
| `--no-web` | Skip the optional `maestro-web` install step. |
| `--web-exec-start <path>` | Override the web unit's `ExecStart` (default `~/.cargo/bin/maestro-web`). |
| `-y`, `--yes` | Non-interactive: install binaries + drop systemd unit + reload + enable, but **don't** auto-run `configure` or auto-start the unit (those need real config). Also skips the web step unless an existing `maestro-web.service` is being refreshed. |
## Where tmux sockets land
`maestrod` defaults `TMUX_TMPDIR` to `$XDG_RUNTIME_DIR` (= `/run/user/$UID`)
`maestro daemon` defaults `TMUX_TMPDIR` to `$XDG_RUNTIME_DIR` (= `/run/user/$UID`)
so a plain `tmux ls` in your shell — which typically uses the same env var
— sees maestro-managed sessions. Override in
`~/.config/maestro/config.toml`:
@@ -129,13 +148,13 @@ precedence over the file.
## Hot-reloading policy
`[daemon.policy].allow_inbound` (the list of peer daemons allowed to broker-
attach into this one) can be changed without restarting `maestrod` — restarting
would kill every live session. Two ways to apply an edit:
attach into this one) can be changed without restarting the daemon — restarting
would kill every live session. Three ways to apply an edit:
```sh
maestro policy reload # POSTs to the local daemon's /api/policy/reload
maestro policy show # prints the current effective list
kill -HUP <maestrod-pid> # equivalent; what `systemctl --user reload` does
kill -HUP <maestro-pid> # equivalent; what `systemctl --user reload` does
```
Only newly-arriving attach attempts see the change — sessions already
@@ -143,18 +162,18 @@ attached stay attached. To boot a live attacher out, kill the session.
## What the systemd unit looks like
The wizard writes a *user* unit (`systemctl --user`), since `maestrod` runs
per-user and uses tmux sockets under `$XDG_RUNTIME_DIR`. Body:
The wizard writes a *user* unit (`systemctl --user`), since `maestro daemon`
runs per-user and uses tmux sockets under `$XDG_RUNTIME_DIR`. Body:
```ini
[Unit]
Description=Maestro per-host daemon (maestrod)
Description=Maestro per-host daemon
Documentation=https://git.naps.pt/naps62/maestro
After=network-online.target
[Service]
Type=simple
ExecStart=%h/.cargo/bin/maestrod
ExecStart=%h/.cargo/bin/maestro daemon
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
@@ -166,8 +185,8 @@ WantedBy=default.target
Check status / logs:
```sh
systemctl --user status maestrod
journalctl --user -u maestrod -f
systemctl --user status maestro
journalctl --user -u maestro -f
```
## Running the wizard without the bootstrap script
@@ -185,15 +204,14 @@ script avoids that by installing to a scratch dir.
## Fully manual install (no wizard)
```sh
cargo install --git https://git.naps.pt/naps62/maestro --locked maestro-daemon
cargo install --git https://git.naps.pt/naps62/maestro --locked maestro-cli
maestrod configure
maestrod # foreground; or wire your own service manager
cargo install --git https://git.naps.pt/naps62/maestro --locked maestro
maestro daemon configure
maestro daemon # foreground; or wire your own service manager
```
## Non-Linux hosts
The wizard works on macOS for the `cargo install` + `maestrod configure` parts.
The systemd step is automatically skipped (probe of `/run/systemd/system`).
launchd integration is not yet supported — start `maestrod` from your shell or
your own service manager.
The wizard works on macOS for the `cargo install` + `maestro daemon configure`
parts. The systemd step is automatically skipped (probe of
`/run/systemd/system`). launchd integration is not yet supported — start
`maestro daemon` from your shell or your own service manager.
@@ -61,7 +61,7 @@ UI polish, layout/tiling, edge-case terminals — all out of scope.
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.
- **Daemon (`maestro daemon`)** — 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. 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).
@@ -85,7 +85,7 @@ Non-responsibilities:
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 `[registry].bind_addr` of the unified config at `~/.config/maestro/config.toml`).
### Daemon (`maestrod`)
### Daemon (`maestro daemon`)
Responsibilities:
@@ -253,16 +253,21 @@ Single-user trust model. The operator's network (Wireguard / private LAN / etc.)
## Repo layout
Cargo workspace:
Cargo workspace. NAP-245 collapsed the three binaries (`maestro`, `maestrod`, `maestro-registry`) into a single `maestro` binary with feature-gated subcommands. Library crates kept their existing shape; per-binary `main.rs` files are gone.
```
maestro/
├── Cargo.toml # workspace
├── crates/
│ ├── maestro-protocol/ # message types, shared between binaries
│ ├── maestro-registry/ # registry binary
│ ├── maestro-daemon/ # daemon binary
│ └── maestro-cli/ # `maestro` CLI for scripting (spawn/list/kill from shell)
│ ├── maestro/ # umbrella binary: `maestro <list|spawn|attach|kill|set-registry|policy|daemon|registry>` (features: cli/daemon/registry)
│ ├── maestro-protocol/ # message types, shared between subsystems
│ ├── maestro-config/ # unified on-disk config schema
│ ├── maestro-cli/ # CLI subcommand types + dispatch (library)
│ ├── maestro-daemon/ # daemon subsystem (library + `maestro daemon` dispatch)
│ ├── maestro-registry/ # registry subsystem (library + `maestro registry` dispatch)
│ ├── maestro-web/ # standalone `maestro-web` browser-UI binary
│ ├── maestro-installer/ # `maestro-install` wizard
│ └── maestro-e2e/ # cross-process integration tests
├── web/
│ ├── src/
│ ├── package.json
@@ -273,7 +278,7 @@ maestro/
└── CLAUDE.md
```
`maestro-cli` is a low-priority bonus — handy for scripted spawning and for testing the registry HTTP API. Drops out of scope if it slows the plan.
Thin builds for single-role hosts: `cargo install --no-default-features --features {cli|daemon|registry} maestro` produces a binary with only that subsystem's dependencies compiled in.
## Out of scope (prototype)