Updating a fleet that is already installed
The same delivery, repeated — and the traps that make it look like it worked when it did not.
An update is the same delivery as the first install, repeated — there is no separate upgrade path and no in-place self-update. What changes is only that machines already have binaries, so every step below must be safe against a machine that is running work right now.
Step 1 — know which release you are delivering.
Name the source deliberately: the public line, a release channel (--channel stable), or a
distribution tree you hold (--from <path or URL>). ⚠ A source that was not refreshed delivers
the version it last held, with no error anywhere — so the first question of an update is which
release the source is actually serving, and
Verifying an install is how you answer it by content
rather than by a version string.
Step 2 — deliver, one of two ways.
With the installer — the same line as the first install, run on each machine:
It re-detects OS and architecture, downloads the whole machine payload —
{saphan,saphan-agent,saphan-gateway,saphan-server} — re-verifies it against SHA256SUMS, and
refuses on a mismatch with nothing installed. When the source publishes a version manifest, it
also refuses to install an older version over a newer one unless you pass --allow-downgrade.
Prefer this route: it is the only one that delivers every binary and verifies what it
delivered.
⚠ --no-serve is what makes it an update. Run by a person at a terminal without it, the
installer takes the first-install path: it refuses while a Saphan Studio process of the same
installation is running, and otherwise sets the workspace up and starts the console again. With
--no-serve it replaces the binaries and starts nothing.
Over ssh, without the installer, when you just want the new binaries on hosts you already reach,
from a distribution tree laid out as <os>-<arch>/<binary>:
⚠ Ship the WHOLE payload the installer would have. A machine left with a new saphan and a
stale saphan-agent reports the mismatch nowhere.
⚠ This route verifies nothing. Unlike the installer, scp checks no checksum; the content
check in Step 3 is the only verification you get. ⚠ And "same version" is not "same artifact":
two builds of one release can differ byte for byte, so compare against the tree you actually
shipped from and never against a version string.
Two properties of that pair are load-bearing, not style:
- Match the architecture per machine.
uname -s/uname -mon each host; a fleet is routinely mixed (Linux runners plus a macOS machine). mv -fis a rename, not an overwrite. A rename publishes a new inode, so work already executing the old binary keeps running against its own open file instead of being killed mid-run; an in-placecpover a live binary can also poison macOS's per-inode code-signature cache.
Then restart what runs. A running process keeps the binary it started with: until the console
and the server are restarted (saphan-gateway stop / start, saphan-server stop / start, or
your supervisor's own restart), they are the old release.
⚠ A far machine must run the same build as the machine that dispatches to it, or its governed
runs are refused by name (legacy-far-wall-unavailable, at no cost, nothing spawned).
Step 3 — verify every machine, by content and by behaviour (Verifying an install). A fleet where some hosts took the update and others did not is worse than one where none did: the halves disagree, and the disagreement is silent. Verify the whole roster before calling the update done:
What the installer does
What the one line fetches, what it verifies before anything lands, and the admission request it makes for this machine.
First run: setting the machine up at /setup
A machine with no owner is set up in a browser at /setup, or from a terminal with saphan install: the window, the screens, and the key that is shown once.