# Installing Maestro The repo is private, so grab `install.sh` while signed in to gitea, then run it: 1. Open , 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 ` | Install from a different git repo (default: this one). | | `--branch ` | Pin a branch with `--git`. | | `--path ` | Install from a local workspace checkout (`--path` + `-p maestro`). | | `--no-locked` | Drop `--locked` from `cargo install` (resolves a fresh dep tree). | | `--exec-start ` | 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 ` | 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.