292 lines
10 KiB
Markdown
292 lines
10 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 two packages and a Home Manager module:
|
|
|
|
```sh
|
|
nix run git+https://git.naps.pt/naps62/maestro -- list # the maestro binary
|
|
nix build git+https://git.naps.pt/naps62/maestro#maestro-web
|
|
```
|
|
|
|
`packages.maestro` is the unified CLI + daemon + TUI binary, wrapped so `tmux`
|
|
and `git` are on its `PATH`. `packages.maestro-web` embeds a prebuilt SPA
|
|
bundle — 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 both services running as *user* units (same trust boundary
|
|
as the tmux server they wrap):
|
|
|
|
```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`
|
|
web.enable = true; # runs `maestro-web`
|
|
settings = {
|
|
daemon.bind_addr = "127.0.0.1:7070";
|
|
web.bind_addr = "0.0.0.0:8081";
|
|
};
|
|
};
|
|
}
|
|
```
|
|
|
|
`settings` is written verbatim to `~/.config/maestro/config.toml` in the schema
|
|
below; every field has a default, so `{}` writes no file at all. There is no
|
|
auth layer — bind the web UI 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`). 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
|
|
```
|
|
|
|
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 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]
|
|
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 is the `Action` enum in
|
|
`crates/maestro-tui/src/action.rs`.
|
|
|
|
## 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 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.** There is no authentication layer: anyone who can reach the
|
|
bind address (of either the daemon or `maestro-web`) can 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.
|
|
|
|
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-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-start the unit. 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.
|
|
|
|
## 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, worktree root, spawn presets, or TUI
|
|
keymap.
|
|
|
|
## 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.
|