The browser reached the daemon through maestro-web, a second binary that served the SPA and reverse-proxied /api/* — two TCP connections and a WebSocket frame re-encode on every keystroke and every byte of PTY output. On one machine that hop buys nothing. The daemon now embeds the bundle and serves it off its own listener, behind a default-on `web` feature. `--no-default-features` builds without pnpm or node; MAESTRO_WEB_DIST still takes a prebuilt bundle. The router's fallback answers an unmatched /api/* with 404 rather than the SPA shell, so a client expecting JSON gets a status it can read instead of HTML. Removes maestro-web and the [web] config section. An existing [web] block is ignored, not rejected. The installer deletes a leftover maestro-web.service on upgrade. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
303 lines
11 KiB
Markdown
303 lines
11 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
|
|
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
|
|
chord_timeout_ms = 1000 # how long to hold a partial chord like `ctrl-w`
|
|
|
|
[tui.keymap.normal]
|
|
"i" = "EnterInsert"
|
|
"s" = "OpenSpawnModal"
|
|
"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 <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. |
|
|
| `-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 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 `<repo>/worktrees/<branch>`;
|
|
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.
|