# 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 systemd unit, refreshes it, 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 TUI, gated by Cargo features. 2. 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`. ## Nix / NixOS The repo is a flake. It exposes the `maestro` package, the SPA bundle it embeds, and a Home Manager module: ```sh nix run git+https://git.naps.pt/naps62/maestro -- list # the maestro binary ``` `packages.maestro` is the unified CLI + daemon + TUI binary, with the browser SPA embedded, wrapped so `tmux` and `git` are on its `PATH`. The Nix build points `MAESTRO_WEB_DIST` at `packages.webFrontend`, so `build.rs` skips pnpm entirely and no network fetch happens mid-compile. Set that variable yourself to reuse a bundle built elsewhere; leave it unset and the build falls back to running `pnpm install && pnpm build` in `web/`. Home Manager, with the daemon running as a *user* unit (same trust boundary as the tmux server it wraps): ```nix { inputs.maestro.url = "git+https://git.naps.pt/naps62/maestro"; # in your Home Manager configuration: imports = [ inputs.maestro.homeManagerModules.default ]; services.maestro = { enable = true; # installs the binary, runs `maestro daemon` settings = { daemon.bind_addr = "127.0.0.1:7070"; }; }; } ``` `settings` is written verbatim to `~/.config/maestro/config.toml` in the schema below; every field has a default, so `{}` writes no file at all. The daemon serves the browser UI off that same address, and there is no auth layer — bind it to a LAN or VPN address only on a trusted segment, never a public IP. `services.maestro.daemon.enable = false` installs the CLI and TUI without running a local daemon, for a host that only talks to another machine's. The daemon unit carries the same `KillMode=process` as the wizard-written one, so `systemctl --user restart maestro-daemon` leaves the tmux server and every session alive. ## Thin builds for single-role hosts The default install pulls in everything (`cli` + `daemon` + `tui` + `web`). Single-role hosts can opt out of unused dependencies: ```sh # Daemon-only host — no CLI or TUI: 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 ``` Neither of those builds the browser UI: `web` is left out, so pnpm and node aren't needed. Add `--features daemon,web` to put it back. 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. A bare `maestro` opens the TUI too — the missing subcommand defaults to `tui` (#119), so `maestro --daemon-url http://host:7070` works as well. Builds without the `tui` feature have no UI to open and print help instead (exit 0). Drop the `tui` feature to skip it on hosts that don't need the UI surface (`--no-default-features --features cli,daemon`). It talks to the local `maestro daemon` over `http://127.0.0.1:7070` by default. 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] theme = "dark" # see the theme list below leader = "ctrl-a" # one byte that escapes Insert mode; the web reads # the same setting off /api/info chord_timeout_ms = 1000 # how long to hold a partial chord like `ctrl-w` [tui.keymap.normal] "i" = "EnterInsert" "n" = "OpenSpawnModal" "N" = "FastForwardSpawn" "x" = "KillHighlighted" "r" = "RenameHighlighted" "?" = "ShowHelp" "ctrl-w h" = "FocusSidebar" "enter" = "AttachHighlighted" "pageup" = "ScrollPanePageUp" "pagedown" = "ScrollPanePageDown" [tui.keymap.insert] # (leader is added automatically as LeaveInsert; only override here when you # want extra Insert-mode bindings) ``` `theme` selects the complete TUI palette. `dark` is the default: Maestro's graphite palette, near-neutral chrome where the activity colours are the only hue on screen; `light` is its light peer; `ansi` uses the terminal's named ANSI colours for terminals without truecolour. Five house presets recolour the graphite base with an accent, and the remaining presets adapt well-known editor themes — in both groups individual colours are lifted or darkened where they would fall short of the WCAG AA contrast floors the palette tests assert: | name | based on | |-------------------|------------------| | `ice-cyan` | graphite base, signal-cyan accent | | `periwinkle` | indigo base, blue-violet accent | | `amber-phosphor` | warm graphite, CRT-amber accent | | `ember` | warm graphite, orange-red accent | | `orchid` | violet base, muted violet accent | | `solarized-dark` | Solarized Dark | | `solarized-light` | Solarized Light | | `catppuccin` | Catppuccin Mocha | | `github-dark` | GitHub Dark | | `github-light` | GitHub Light | | `tokyo-night` | Tokyo Night | | `gruvbox` | Gruvbox Dark | | `nord` | Nord | | `dracula` | Dracula | An unknown name is rejected as an `[tui].theme` configuration error. The full action vocabulary is the `Action` enum in `crates/maestro-tui/src/action.rs`. ## The browser UI The daemon serves the SPA off its own listener. Open `http://127.0.0.1:7070/` — or whatever `[daemon].bind_addr` is — and the browser talks to the same process the CLI and TUI do. One binary, one port, nothing extra to install or start. **Build prerequisite.** The `web` cargo feature is on by default and embeds the bundle at compile time: `build.rs` runs `pnpm install --frozen-lockfile && pnpm build` in `web/`, so pnpm and node must be on `PATH`. Two ways around that — point `MAESTRO_WEB_DIST` at a bundle built elsewhere, or build with `--no-default-features` and pick the features you want (see *Thin builds* above). **Trust model.** There is no authentication layer: anyone who can reach `[daemon].bind_addr` can load the UI and drive every session on the host. Pick `127.0.0.1` for local-only, a WireGuard/LAN address for one-segment access, and never a public IP — the network perimeter is the only boundary Maestro has. **Upgrading from the two-binary install.** Re-running `./install.sh` removes the old `maestro-web.service` unit for you; nothing to undo by hand. A leftover `[web]` block in `~/.config/maestro/config.toml` is ignored rather than rejected, but it no longer does anything — move the address you care about to `[daemon].bind_addr`. ## 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. | | `-y`, `--yes` | Non-interactive: install the binary + drop systemd unit + reload + enable, but **don't** auto-start the unit. | ## 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. ## What happens to sessions after a reboot A host reboot kills the tmux server, so every session the daemon knew is dead while its record on disk survives. What recovery does with those is `[daemon].resume_on_boot`: ```toml [daemon] resume_on_boot = "dormant" # "dormant" (default) | "auto" | "never" ``` - `dormant` — the sessions are listed but not running, and you resume the ones you want from the web UI or the TUI. - `auto` — the daemon resumes them all itself during startup. Only pick this on a host where restarting every agent unattended is safe: they come back on the flags they were spawned with, with nobody watching. - `never` — the records are deleted and a reboot is a clean slate. See `docs/RESTART.md` for the full contract. ## 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 # foreground; or wire your own service manager ``` Every setting has a default, so `~/.config/maestro/config.toml` is optional — create it only to change the bind address, spawn presets, or TUI keymap. (Worktrees live inside their base repos at `/worktrees/`; set `[daemon].worktree_root` only to override that.) ## Non-Linux hosts The wizard works on macOS for the `cargo install` part. 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.