Commit 2d51973f by PLN (Algolia)

feat(tools): rig-install.sh + SETUP.md — clone the gig onto a new box

The install counterpart to rig-doctor.py: the doctor only ever reads,
this converges. Idempotent by construction — detect, report, act only
if something is actually missing, never fail the whole run for one
optional piece. Covers every checked item in rig-doctor.py: system
packages (printed, pacman+--yes is the only path this script ever
executes itself), SuperCollider quarks at current HEAD (no lockfile —
PLN rejected pinning), the mi-UGens extension, the private
parvagues-synths repo + its link.sh, the two ~/.local/bin shims two
systemd units silently crash-loop without (nothing in the repo created
these before now — a top-5 fresh-box breakage), the systemd --user
units from rig_units.py's one authored table, and the sample carry-on
bundle (rsync, out of band from git on purpose).

Root-owned installs (perf-audio+sudoers, parvagues-protect, the
[midiviz-pin] KWin rule) are print-only, always — this script never
runs sudo. A script that quietly escalates itself is one nobody can
audit before a gig; `--print-root-steps` shows just those three
commands and does nothing else.

Every path is resolved at run time from the script's own location,
$HOME, and $XDG_DATA_HOME — never baked in. This rig's oldest
recurring bug is a binding resolved once and never re-resolved; an
installer is exactly the kind of code tempted to commit that bug by
writing down what it found on its own author's box. For the same
reason it never repoints an existing systemd-unit or ~/.local/bin
symlink that already resolves correctly, even to a DIFFERENT checkout
than this one — only creates what's entirely missing, or repairs a
dangling link. Tested twice on PLN's own performing laptop (already
fully provisioned): both runs report "already ok" throughout and
change no running state, except parvagues-synths/link.sh adopting 18
already-identical synthdefs as symlinks (its own designed
no-op-equivalent).

Also regenerates tools/parvagues-rig.target from rig_units.py's
generator: it had drifted (still Wanted the now-manual lcxl3-driver,
missing midiviz) since the unit table changed under it.

SETUP.md is the human procedure: the fast path, what rig-install.sh
does and doesn't do, the LCXL2/LCXL3 hardware-detected split, a
troubleshooting section keyed off the doctor's real FAIL text, and an
explicit "known not portable yet" — start_and_midi.scd and
sample_watch.scd hardcode /home/pln, so a non-pln account gets a rig
that boots clean with banks and preload silently missing (task #28).
Named honestly rather than hidden from the next person who hits it.
parent 06039c8a
# SETUP — cloning the gig onto a new machine
This is the procedure for getting the ParVagues rig from nothing to
performing, on a box that has never seen it before: PLN's own new XPS24, or a
friend's laptop that will never touch PLN's home directory.
Two repos and one out-of-band transfer make up "the rig":
* **Tidal** (this repo) — the `.tidal` tracks, the SuperDirt/MIDI/Ardour
glue, systemd units, and the tools (`rig-doctor.py`, `rig-install.sh`,
`gig-preflight.py`, `gig-up.sh`) that check and converge a machine.
* **[parvagues-synths](https://git.nech.pl/pln/parvagues-synths)**
PLN's custom SynthDefs, a **private** repo. You need SSH access to
`git.nech.pl` to clone it (ask PLN for an account/key). `rig-install.sh`
clones it automatically next to this repo and runs its `link.sh`, which
symlinks 18 `.scd` files into SuperCollider's synthdefs folder.
* **The samples** travel **out of band** — git cannot carry 15 GB
reasonably. They come from a separately-staged carry-on bundle (rsync) or
get rebuilt from a box that already has the corpus. Nothing about the
install fails without them; they can arrive after everything else works.
## Fast path
```
git clone git@git.nech.pl:pln/Tidal.git && cd Tidal
python3 tools/rig-doctor.py # what's missing, with fixes
tools/rig-install.sh # converge; prints any sudo steps
rsync -a /mnt/freebox/PLN/parvagues-carryon/ ~/.local/share/SuperCollider/downloaded-quarks/Dirt-Samples/
./gig-up.sh # play
```
The `rsync` line assumes the default `$XDG_DATA_HOME` (`~/.local/share`); if
yours is set to something else, the Dirt-Samples root is
`${XDG_DATA_HOME:-~/.local/share}/SuperCollider/downloaded-quarks/Dirt-Samples`.
`rig-install.sh` prints this same path, resolved for your box, so you don't
have to compute it by hand.
## What `rig-install.sh` does and doesn't do
It's idempotent — run it as many times as you like. Re-running it on a box
that's already set up should report "already ok" on nearly every line
(that's how it was tested — twice, on PLN's own performing laptop). The one
thing that moved on the first run: `parvagues-synths/link.sh` adopted 18
already-identical synthdefs as symlinks — its own designed no-op-equivalent,
not a change in what SuperCollider loads.
It detects your package manager (pacman/apt/dnf) and **prints** the install
command for SuperCollider, Ardour, PipeWire(+jack), alsa-utils, ffmpeg, ghc/
cabal (via ghcup), and the Python deps — it does not run your package
manager for you, except on `pacman` with `--yes` (Arch is the only
known-good, tested distro here; on Debian/Fedora, review the printed command
before running it — package names and quirks vary).
It installs the SuperCollider quarks (SuperDirt, Dirt-Samples, BatLib,
SCLOrkSynths, Vowel) at **current HEAD**, not a pinned commit — there is no
lockfile here on purpose (a version gate that fails at showtime because a
quark drifted six months ago is worse than the drift itself).
It clones `parvagues-synths` and runs its `link.sh` — never re-implements
the linking, never overwrites a differing file without `--force`.
It creates the two `~/.local/bin` shims (`tidal-ardour-autoroute.sh`,
`midi-autoconnect.sh`) that two systemd units depend on. **Forgetting these
is the single most common fresh-box breakage** — without them, those units
restart forever and nothing about the failure is obvious from `systemctl
--user status`. Nothing else in the repo created them before this script
existed.
It enables/starts the systemd `--user` units from `tools/rig_units.py`'s one
authored table — but only actually applies with `--yes` (otherwise it shows
you a dry run). `--yes` also starts SuperDirt itself, by that table's own
"on-demand" policy — the same thing `gig-up.sh` does at gig time.
It **never runs `sudo`**, anywhere, for anything except the one `pacman
--yes` path above. Three things need root and are print-only —
`--print-root-steps` shows just these three, nothing else:
1. `perf-audio` + its sudoers rule (lets the perf tray switch CPU modes
without a password prompt).
2. `parvagues-protect` + its root systemd unit (protects `scsynth` from the
OOM killer). Copied, not symlinked — a root unit whose `ExecStart` lives
under a user's home is a privilege-escalation hole.
3. The `[midiviz-pin]` KWin rule (keeps the MIDI lens pinned above every
window, on every desktop) — not root, but it rewrites your window-rules
file, so it's print-only the same way.
A script that quietly escalates itself is one nobody can audit before a gig
— that's the whole reason these three are commands you read and run
yourself, not something this script does behind your back.
## LCXL2 vs LCXL3 — both first-class, but pick ONE
The rig supports both generations of the Novation Launch Control XL. Which
one is plugged in is detected live, not configured — `rig-install.sh` looks
at the MIDI bus and tells you which systemd unit matches what's plugged in
right now (`lcxl3-driver.service` for the v3 board, `lcxl-leds-watch.service`
for the original). **These two `Conflicts=` each other on purpose** — only
one painter may run, because starting the wrong one for the hardware
actually plugged in silently kills the right one (this cost PLN 86
crash-loop restarts in one afternoon, 2026-09-05). Neither is auto-started at
boot; `gig-up.sh` chooses the right one every time you bring the rig up,
based on what's on the bus at that moment. If your board's LEDs are dark or
its faders don't reach Ardour, this — not a broken unit — is the first thing
to check: `systemctl --user status lcxl3-driver.service lcxl-leds-watch.service`.
## Troubleshooting, from the doctor's own FAIL messages
`tools/rig-doctor.py` names the exact fix for everything it checks; a few
worth calling out because they're easy to misread:
* **`quark <name>: missing`** — SuperCollider's Quarks GUI, or
`git clone <repo> ~/.local/share/SuperCollider/downloaded-quarks/<name>`.
`rig-install.sh` does this for you; run the doctor again after to confirm.
* **`synthdefs: only N/18 .scd files`** — you're missing (or haven't linked)
the private synths repo. `cd parvagues-synths && ./link.sh`. If it reports
"DIFFERS, kept yours" for any file, that's a real local edit — don't
`--force` it away without checking.
* **`root: /home/<other-user>/...` (FAIL, hardcoded to a different user)**
— see "known not portable yet" below. This is the one class of failure
the doctor can point at but this installer cannot fix for you.
* **`perf-audio + sudoers: permission denied to check`** — expected as a
non-root user (`/etc/sudoers.d` is `750 root:root`); it means "can't tell",
not "missing". Verify as root, or just run the print-root-steps install
command — it's idempotent.
* **`midi painter unit: wrong painter running`** — see the LCXL section
above; stop the wrong one, start the matching one.
* **sample coverage FAIL, listing missing banks** — you have the repo but
not (all of) the audio yet. Get the carry-on bundle (fast path above) or
rebuild with `tools/sample-pack.py --all --pack DEST` on a box that has
the corpus. Every bank travels WHOLE — never copy a subset of one, `bank:N`
indexes files in glob order and dropping one renumbers the rest.
## Known NOT portable yet — read this before you assume a clean install
**Two `.scd` files hardcode `/home/pln` as literal strings**:
`start_and_midi.scd` (the three sample-bank roots it loads at boot, *and*
the repo-root path it loads `preload.scd`/`sample_watch.scd` from) and
`sample_watch.scd` (the same three sample roots, for its live-reload
watcher). `preload.scd` itself has no such literal — it just never runs,
because the path *to* it is one of the hardcoded strings above. On a box
where the account is not literally `pln`, **SuperDirt boots completely
cleanly, with no error anywhere** — and every bank under those roots, plus
preload warming and the sample-reload watcher, is just silently absent. `rig-doctor.py`'s `check_scd_sample_roots` check is the one
thing standing between you and finding this out by ear, mid-set. This is
tracked as task #28; `rig-install.sh` cannot fix it because it would require
rewriting `.tidal`/`.scd` source under version control on someone else's
clone, which is out of scope for an installer. If you hit this with no
warning, that's a bug in how this was rolled out — you should never have to
discover it yourself. Run the doctor before you play; if it FAILs on any
`root: /home/...`, either create matching paths on your box, or fix the
three literals in those two files locally (not upstream, unless your
username is going to be `pln` for everyone who ever runs this, which it
won't).
**Everything else in this repo resolves paths at run time** (`$HOME`,
`$XDG_DATA_HOME`, the script's own directory) — this is the ONE known
exception, called out explicitly rather than discovered the hard way.
......@@ -16,7 +16,7 @@ Wants=parvagues-sc.service
Wants=parvagues-sc-watchdog.service
Wants=tidal-ardour-autoroute.service
Wants=midi-autoconnect.service
Wants=lcxl3-driver.service
Wants=parvagues-bridge.service
Wants=gig-log.service
Wants=perf-tray.service
Wants=midiviz.service
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment