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