This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/docs/INSTALL.md
T
naps62-yoloandClaude Opus 4.8 b6833ede43 refactor(maestro): drop allow_inbound policy gate, keep JWT session binding (NAP-339)
The peer (T2) attach path had a per-pair access-control layer
(`[daemon.policy].allow_inbound`): a target-side allow-list of daemon ids
permitted to broker-attach, enforced inside JWT verification, hot-reloadable via
SIGHUP / `POST /api/policy/reload`, with a `maestro policy` CLI. On a flat-trust
perimeter (the operator's VPN/firewall is the boundary for which hosts connect)
that granularity is unused, and deny-by-default made it config friction —
cross-daemon attach silently failed until every peer was listed.

Remove the allow-list; keep the registry-minted pair-attach JWT and ALL its
binding checks (iss=registry, aud=target, sid=session, exp). The firewall gates
host->port reachability; it does not stop a reachable host from attaching to a
session it wasn't handed or replaying a token elsewhere — that's the JWT's job
and it stays. Only the `sub in allow_inbound` check is dropped.

Removed: DaemonPolicySection / [daemon.policy].allow_inbound (config + daemon),
policy.rs (SIGHUP listener + GET/POST /api/policy), the `maestro policy` CLI,
PairAttachVerifier's allow-list field/reload/snapshot + JwtError::NotAllowed,
and the e2e broker_rejects_when_target_doesnt_allow_inbound test. The NAP-265
pre-upgrade-502 path stays covered by broker_attach_returns_502_on_perimeter_mismatch.

Also dropped the unit's ExecReload=kill -HUP: with no SIGHUP handler left,
default SIGHUP disposition is terminate, so wiring `systemctl reload` to it would
kill the daemon. Re-introducing a per-pair gate later is a verify-time change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 07:07:55 +00:00

259 lines
9.4 KiB
Markdown

# Installing Maestro
The repo is private, so grab `install.sh` while signed in to gitea, then run it:
1. Open <https://git.naps.pt/naps62/maestro/src/branch/main/install.sh>, click
**Raw**, save as `install.sh`. Or from any existing clone:
```sh
git show origin/main:install.sh > install.sh
```
2. Run it:
```sh
chmod +x install.sh && ./install.sh
```
The bootstrap script builds `maestro-installer` into a temp directory, runs the
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 maestro` instead of `start`. No `--update`
flag needed. **The restart keeps your sessions:** the unit sets
`KillMode=process`, so systemd signals only the daemon — the tmux server (and
every session) survives, and the new daemon re-adopts them on start. A unit
written before this default lacks the line; re-run `./install.sh` to refresh it.
Inside the script, `cargo install --git https://git.naps.pt/naps62/maestro …`
uses your already-configured git credentials for this host (HTTPS helper or
SSH agent). If only SSH is set up:
```sh
MAESTRO_REPO_URL=ssh://git@git.naps.pt/naps62/maestro ./install.sh
```
Pass flags through to the wizard after `./install.sh`:
```sh
./install.sh --yes --no-systemd
```
The wizard:
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/maestro.service`, reloads systemd, and offers to
enable + start it.
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 (`cli` + `daemon` + `registry` + `tui`).
Single-role 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
# CLI-only client (no local daemon, no TUI):
cargo install --git https://git.naps.pt/naps62/maestro \
--no-default-features --features cli --locked maestro
```
The wizard always installs the default-feature build.
## The terminal UI: `maestro tui`
`maestro tui` is the keyboard-and-mouse driven terminal UI client. It ships
in the default feature set, so a vanilla `cargo install maestro` builds it.
Drop the `tui` feature to skip it on hosts that don't need the UI surface
(`--no-default-features --features cli,daemon`).
It talks only to the **local** `maestro daemon` over `http://127.0.0.1:7070`
by default; peer daemons reach you transparently via the catalog mirror.
Override the daemon URL with `--daemon-url` or `$MAESTRO_DAEMON_URL`.
The `[tui]` section in `~/.config/maestro/config.toml` (or whatever path you
pass via the global `--config` flag) controls the keymap. Shipped defaults
cover every v1 action — only override what you want to change:
```toml
[tui]
leader = "ctrl-a" # one byte that escapes Insert mode
chord_timeout_ms = 1000 # how long to hold a partial chord like `g`
[tui.keymap.normal]
"i" = "EnterInsert"
"s" = "OpenSpawnModal"
"x" = "KillHighlighted"
"?" = "ShowHelp"
"ctrl-t" = "NewTab"
"g t" = "NextTab"
"ctrl-w h" = "FocusSidebar"
"enter" = "AttachHighlighted"
[tui.keymap.insert]
# (leader is added automatically as LeaveInsert; only override here when you
# want extra Insert-mode bindings)
```
The full action vocabulary lives in
`docs/superpowers/specs/2026-05-28-maestro-tui-design.md` §3.8.
## 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 `maestro daemon`.
**Interactive install (default path).** The wizard now offers it as an
optional step right after the daemon flow:
```text
Optional: install the browser UI (`maestro-web`)?
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
`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`
with `--no-web` for non-interactive daemon-only installs. (Bare `--yes`
without `--no-web` *skips* the web step too — opt in by re-running once
interactively.)
**Frontend build prerequisite.** `maestro-web` embeds the SPA bundle at
compile time, so before running the installer:
```sh
pnpm -C web install --frozen-lockfile
pnpm -C web build
```
Or `just web` from a workspace checkout.
**Defaults.** `bind_addr = 127.0.0.1:8080`, `daemon_url =
http://127.0.0.1:7070`. The `[web]` section of
`~/.config/maestro/config.toml` overrides both — see
`crates/maestro-web/README.md`.
**Trust model.** Anyone who can reach the bind address can use it. Pick
`127.0.0.1` for local-only, a WireGuard/LAN address for one-segment access,
never a public IP without adding auth first.
Headless hosts (servers, dev VMs) skip the prompt with `--no-web`. Hosts
where you want browser access answer yes — both binaries end up in
`~/.cargo/bin`.
## Flags
| Flag | Purpose |
|---|---|
| `--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 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/maestro daemon`). |
| `--no-systemd` | Skip the systemd step even if detected. |
| `--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
`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`:
```toml
[daemon]
tmux_tmpdir = "/tmp"
```
Pick `/tmp` if the daemon must outlive your login session and you can't run
`loginctl enable-linger` (the runtime dir is wiped on logout, taking the
tmux sockets with it). The env var `MAESTRO_DAEMON_TMUX_TMPDIR` takes
precedence over the file.
## Peer attach trust model
There is no per-peer allow-list. The operator's network perimeter (VPN/firewall)
is the trust boundary for *which hosts can connect*; every daemon reachable on
the perimeter may broker-attach to any other. Attach is still authenticated: the
registry mints a short-lived, pair-scoped JWT bound to the target daemon and the
specific session (`iss`/`aud`/`sid`/`exp`), which the target verifies — so a
reachable host still can't attach to a session it wasn't handed, or replay a
token elsewhere. (NAP-339 dropped the earlier `[daemon.policy].allow_inbound`
gate; re-introducing a per-pair allow-list later is a verify-time change.)
## What the systemd unit looks like
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
Documentation=https://git.naps.pt/naps62/maestro
After=network-online.target
[Service]
Type=simple
ExecStart=%h/.cargo/bin/maestro daemon
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
```
Check status / logs:
```sh
systemctl --user status maestro
journalctl --user -u maestro -f
```
## Running the wizard without the bootstrap script
If you'd rather skip the script:
```sh
cargo install --git https://git.naps.pt/naps62/maestro maestro-installer
maestro-install
```
This leaves `maestro-installer` permanently in `~/.cargo/bin` — the bootstrap
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
maestro daemon configure
maestro daemon # foreground; or wire your own service manager
```
## Non-Linux hosts
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.