On this page

Upgrading a host's Stado

stado host upgrade brings the Stado a registry host runs to a newer committed revision when the host's own Stado cannot take the release path itself: it cannot read the registry authority, or it holds no checkout to build from. Everything the host's Stado would need is done from the machine running the command, and the host's new Stado does the parts that must run there.

Invocation

stado host upgrade <TARGET> [--source-commit <COMMIT>] [--builder <HOST>]
    [--signing-keychain <KEYCHAIN>] [--signing-identity <IDENTITY>]
    [--join-network] [--plan] [--json]
  • --source-commit installs that commit instead of origin/main's tip; origin/main must carry it.
  • --builder names the registry host that compiles the revision. Without it, this machine compiles it when it shares the target's release_platform, otherwise the target does.
  • --signing-keychain and --signing-identity sign with a keychain of your own and an identity in it (by name or SHA-1). Without them the fleet's signing role in Skarbiec signs, exactly as stado product install chooses.
  • --join-network joins the target to Stado's network after the install, run by the target's new Stado (stado network up --host <TARGET>).
  • --plan reads and reports every step and changes nothing: nothing is built, signed, delivered or installed.

Stado Desktop has both: Plan the upgrade of the selected host's Stado and Upgrade the selected host's Stado.

What it does, in order

Every step prints one line, <step>: <ok|failed|planned|skipped> — <what it found>; --json carries the same steps, the source, the installed program, the identity, the dialogs and both sets of checks in one document.

  1. source — fetches origin's main into the canonical Stado checkout with GIT_TERMINAL_PROMPT=0, so a remote that wants a password refuses instead of asking, and reads the version the commit's stado-rs/Cargo.toml declares.

  2. installed — reads on the target, through its declared connection, ~/.stado/bin/stado's SHA-256, its --version line and its code signature, and the same for ~/.stado/bin/stado.previous, the rollback target an install keeps. A revision older than the installed version, or one that does not descend from the installed revision, is refused; a host that runs the commit already, from a clean tree, is left alone.

  3. builder — the host that compiles the revision with stado host kompilator build; its release_platform must be the target's.

  4. identity — the code identity the signature will carry: the installed program's identifier when it carries a stable signature, else the product's own (com.wisent.stado), anchored to the team of the identity that signs. An installed program signed under the retired spelling (ai.wisent.stado.stado) is signed as com.wisent.stado, which is an identity change, planned with its dialogs below. The team is read from the signing certificate (the role's or the PEM pair's) or from the keychain's identity name, before anything is built and without reading a private key.

  5. console — on a Mac, whether anybody can answer a dialog there, measured now exactly as stado host console record measures it (who owns /dev/console, and the display panels macOS reports online), beside what the registry records. A measurement that differs from the record is recorded before the install; a host that could not be measured is treated as attended and the line says why.

  6. units — on a Mac measured headless: Stado's host unit declared as a LaunchAgent is moved by the install into launchd's system domain (/Library/LaunchDaemons/<label>.plist, run as the host's account), which macOS never asks about the local network. sudo -n -l is asked first whether root's steps of that move (placing the plist, loading it, booting it out, removing it) are granted, and the upgrade is refused when one is not, or when another unit of Stado's runs there as a LaunchAgent the install would not move. A host unit already in the system domain is said so.

  7. dialogs — when the identity changes on a Mac, every dialog macOS will show, where, and the answer. macOS keeps every privacy decision under a program's designated requirement, so a program with another requirement is a stranger to it and is asked again:

    • on this Mac, codesign wants to access key "<IDENTITY>" in your keychain. when the key is read from a keychain of yours — enter the keychain's password, then Always Allow;
    • on the target, "stado" would like to access files in your <Documents|Desktop|Downloads> folder. for each folder targets[].privacy_grants declares for the program, and for the folder the target's work_root lies in — Allow;
    • on the target, "stado" would like to find and connect to devices on your local network. — unless Stado's host unit runs there as a launchd daemon after the install (a headless Mac, or a host unit already in the system domain), because a host's Stado dials other hosts' SSH paths and its network's peers; the line says why: a grant declaring Local Network, the decision macOS logged for the installed program in the last day, --join-network, or no logged decision at all — Allow (Don't Allow keeps every local-network path from the host refused).

    On a Mac measured headless no dialog may come there: the upgrade is refused, naming each dialog and its cause, instead of waiting for a click. A headless Mac with no privacy grant and no work root in a protected folder, whose host unit moves into the system domain, plans no dialog at all.

  8. declarations — the SSH keepalive keys the target's configuration declares (ssh.keepalive_seconds, ssh.keepalive_count_max); with neither, the target's resolver tunnel keeps itself alive by that host's kernel TCP keepalive, and the line says so. The incoming program's config validate refuses only one key without the other.

  9. before — the checks below, read once.

  10. gates — the target's claim gates, disk use and beacon age.

--plan ends here. Without it:

  1. When a dialog is planned, the upgrade needs a terminal: it asks for the target's name to be typed back, with someone where the dialogs come (at the target, or at this Mac for its keychain alone) to answer them, and changes nothing until then. With no dialog planned it needs neither.
  2. build, then console when the measurement is recorded, sign (on a Mac target; the signature must be one stable Apple identity, and a requirement that differs from the installed one when the plan said it would not stops the upgrade), package (bin/stado in a gzip tar), registry (the registry authority's current document, read on this machine, placed beside the program as registry.json; skipped, with the reason, when the authority's document cannot be read here or declares no service directory), deliver (into a run of its own below ~/.stado/work/runs on the target).
  3. validate — the incoming program runs config validate on the target; a configuration it refuses stops the upgrade before anything is installed.
  4. registry copy — the incoming program runs registry push <run>/upgrade/registry.json --last-known-good on the target, before the install recycles its units. A host reads the registry through its own resolver, routed to the fleet store only as its last-known-good copy declares, so a copy older than that route is never refreshed by the host: its serve starts from the copy and stops on what the copy lacks (a coordinator selector, an adapter), publishing no beacon and no capacity. The handover writes the current document into the copy. skipped when the target's authority answers (handover-authority-answers: it reads the current registry itself) or its copy already holds this service-directory generation or a later one (handover-rollback); failed with the program's words for any other answer. None of them stops the install.
  5. install — the incoming program runs release local install --archive … --sha256 … on the target, with --announced-identity <REQUIREMENT> when the identity changes. Before it replaces anything, the incoming program passes its trial on the target: it compiles the kompilator it carries into a directory of the trial's own, and one whose sources rustc refuses is not installed (the trial of an incoming Stado). The install renders the host unit through the incoming program's service ensure, which on a headless Mac moves a LaunchAgent into the system domain: the agent is stopped, the daemon read until it serves its declared port, then the agent's file removed, and the agent loaded again when the daemon does not answer. The install keeps the replaced program as stado.previous, recycles every unit still running the replaced image, and puts the predecessor back itself when activation fails. A host unit that runs the queue worker is left to hand off by itself only while its loop goes around (it wrote its record within its promise plus one more promised span); a worker whose loop stopped is restarted onto the installed program, the line naming when the loop last spoke and the jobs it held (a live process still executing a replaced binary).
  6. after — the installed revision must be the commit, from a clean tree, and the checks are read again against the declarations as the install left them. A restarted service is read until it serves its declared port or shows why it cannot.
  7. rollback — when a check that answered before no longer answers, the incoming program runs release local restore --kept-previous <SHA256> with the digest the installed program had, and the target's program is read again to show the kept one runs.
  8. network with --join-network, privacy log (the last hour of tccd's log lines naming stado, through the allowlisted host read), and cleanup of the run on the target and the export on this machine.

Checks

Read before the install and after it:

  • registry — the registry authority answers and declares the target;
  • registry read on <TARGET> — the target's own stado registry doctor read the registry and the host: it answered clean, or with its verdict naming the divergences it found;
  • vault on <TARGET> — the target's skarbiec doctor;
  • service <NAME> — each service the registry declares on the target serves the port its service directory entry declares there; a service with no declared port is not read.

A check that did not answer before the install is reported and never causes a rollback.

release local install --announced-identity

An install over the host channel has no terminal. One that changes the installed program's code identity is refused, because nobody at the Mac would know why macOS asks again, unless the caller already said so from a terminal of its own: --announced-identity <REQUIREMENT> installs a candidate whose designated requirement is exactly that requirement, and refuses any other change.

release local restore --kept-previous

release local restore --kept-previous <SHA256> puts ~/.stado/bin/stado.previous, the program the last install replaced, back as ~/.stado/bin/stado byte for byte when it has that SHA-256, and recycles the units running the image it replaces. It is the rollback for a host whose previous Stado no delivery retained as an archive. The kept program ran on the host before, so its signature and its reading of the configuration are not judged again.

Exact refusal sentences

Values in angle brackets are filled with what was read.

  • Older source: <COMMIT> declares Stado <VERSION>, older than the <INSTALLED> <TARGET> runs (<VERSION LINE>); an upgrade never installs an older release.
  • Diverged source: <TARGET> runs <RUNNING> (<VERSION LINE>), which <COMMIT> does not descend from: the install would take <TARGET> back or sideways; name a later --source-commit.
  • Source off origin/main: --source-commit <REV> (<FULL>) is not on origin/main (<ORIGIN MAIN>); an upgrade installs only a revision origin/main carries.
  • Installed revision unknown: <TARGET> runs <VERSION LINE>, which names no source revision, so whether <COMMIT> is newer is not known; nothing was changed.
  • No installed Stado: <TARGET> has no Stado at <HOME>/.stado/bin/stado; an upgrade replaces an installed Stado, and a host without one is enrolled (stado fleet enroll).
  • Builder of another platform: --builder <HOST> builds for <PLATFORM>, and <TARGET> runs <PLATFORM>; name a builder of <PLATFORM>.
  • No terminal: the upgrade of <TARGET> makes macOS ask the person at <WHERE> the questions listed above; this upgrade runs with no terminal for anyone to see them; run it from a terminal with someone at <WHERE>; nothing was changed, where <WHERE> is the target, or this Mac when its keychain is the only dialog.
  • A dialog on a headless Mac: <TARGET> is headless (<EVIDENCE>), and the upgrade would make macOS ask “<DIALOG>” <WHEN>, because <CAUSE>; nobody is at its screen to answer, so nothing was changed, with each further dialog joined by ; and.
  • Root's steps refused on a headless Mac: <TARGET> is headless, and moving <LABEL> from the LaunchAgent <PATH> into the system domain needs root there, which sudo -n refuses: sudo -n <COMMAND> (<SUDO'S WORDS>); as a LaunchAgent the new program would make macOS ask “"stado" would like to find and connect to devices on your local network.” at a screen nobody answers; nothing was changed.
  • Another of Stado's units is a LaunchAgent on a headless Mac: <TARGET> is headless, and <LABEL> runs Stado there as the LaunchAgent <PATH>, which the install does not move: macOS would ask “"stado" would like to find and connect to devices on your local network.” at its screen, and nobody is there to answer; move it first with stado service ensure --host --as-daemon --reason "nobody answers macOS at this host's screen"; nothing was changed.
  • Not confirmed: the host's name was not typed back, so the upgrade of <TARGET> did not start; nothing was changed.
  • Signature differs from the plan: the signed program carries <REQUIREMENT>, not the installed <REQUIREMENT> the plan read as unchanged, so macOS would ask at <TARGET> questions nobody was told about; nothing was delivered.
  • Configuration refused: the incoming Stado refuses <TARGET>'s configuration (<WHY>); nothing was installed.
  • Rolled back: after the install on <TARGET> these no longer answered: <CHECKS>; rolled back: <TARGET> runs the kept <VERSION LINE> again.
  • Install over the host channel without the announcement: stado would change code identity (<BEFORE> → <AFTER>), so macOS would ask the person at this Mac again for every privacy decision it keeps for <PROGRAM> (targets[].privacy_grants, stado host privacy ), from the background process that runs it; this install runs with no terminal for anyone to see why; run it at this Mac, or with stado host upgrade from a terminal, which announces the identity first.
  • Kept program is not the replaced one: release local restore --kept-previous: <PROGRAM> has sha256 <ACTUAL>, not <EXPECTED>, so it is not the program the caller replaced; nothing was put back.

Source: this website