On this page
Service
What is a managed service, as opposed to the launchd or systemd unit it names? The declaration lives in the registry; the unit lives on the host; and the whole service layer exists because the two used to be unrelated.
What it is
A managed service is one entry in a target's services[] array:
the launchd/systemd unit, program, arguments and host-side unit path Stado is
required to keep running (architecture). The absence of
such a layer is the incident it closes: on the July control-host outage
com.wisent.weles-api existed on the box and was wedged, but nothing in
Stado declared it — so no command could list it, restart it, or even assert
that it was supposed to be running (stado-rs/src/deploy/service/mod.rs).
A declaration carries the name the CLI addresses it by, the unit identity
(launchd label or systemd unit), the unit-file path, and — when the
declaration is the source of the unit rather than a pointer at a plist
somebody installed by hand — program and args. That pair is what makes
the declaration reinstallable from the document alone: service ensure
renders the unit from them, so a host that lost its unit file can be made to
run the right thing again
(deploy/service/model/managed/service.rs, ManagedService). A
declaration that names only a path cannot be reinstalled from the document;
repair records declaration_incomplete and alerts until the entry carries
its program and args — read the truth with stado service show <name>
and write it into the entry (operations).
Sources: registry, recovery, product
The managed set has three sources, and the distinction is load-bearing
(deploy/service/model/vocabulary.rs):
| Source | Meaning |
|---|---|
registry |
Declared in the target's services[]. What adopt/retire/deploy edit, and what stado registry doctor diffs against live host state. |
recovery |
The fixed list every stado host recover pass restarts. Genuinely managed, but by that fixed program and not by the document — so it can be neither adopted nor retired, and is never silently converted into a registry service. |
product |
Located by a shipped product declaration naming both the label and the unit file, addressable without a registry record. |
The product name and its exact native unit identity select the same catalog
entry, including its required environment. Addressing the object API by
com.wisent.always-on.stado-object-api therefore keeps the same defaults as
addressing it by stado. Its physical recovery writer preserves other installed
unit fields, such as resource limits, instead of erasing what the shared service
renderer declared.
Who observes it
Two deliberately separate halves (deploy/service/mod.rs). The read side —
stado service list — joins declarations against the latest host
beacons and issues no ssh, so it stays answerable while a host
is wedged; a stale beacon yields unknown, never a confident active or
missing. The write side (restart, deploy, ensure, retire, logs, env) rides
the approved host channel with a fixed, narrow remote program. The autonomy
cycle joins beacon unit state with a fresh stado service verify
reachability sweep and repairs from that evidence
(operations).
Where it lives in the store
In the registry document, as targets[].services[]. Every
mutation goes through the same validated compare-and-swap write path as
stado registry push, so a mutation that would produce an invalid document
is refused with nothing uploaded (cli).
Ensure
stado service ensure compares the desired unit with the on-disk definition,
launchd's retained program and arguments, and the running executable. It leaves
a matching running unit alone and installs a missing one. One pass reports
created, restarted, already_correct, converged, or reloaded. A
declaration that names its own label is rendered at that label, so an
existing unit is reinstallable from the document without becoming a second
service competing for the same port. Ensure is also the autonomy cycle's
repair path for a proven-missing unit, and the one repair permitted on a
silent host's own beacon unit, because the channel answering is the evidence
that repair is possible (operations).
On macOS a matching loaded definition can be restarted in place. A changed plist or a different program or argument vector retained by launchd requires bootout and bootstrap after executable and plist preflight; kickstart would reuse the old definition. Success requires launchd readback and the running executable to match. An unreadable or malformed retained definition is refused. If activation or readback fails, ensure attempts rollback only when it retained a genuinely different prior unit file. An already-desired plist is not retried as a supposed rollback; the failure explains why no distinct prior definition was available.
After changing a unit, ensure waits up to 30 seconds for an actual authoritative
registry read before recording completion. This also covers a unit that serves
the registry itself: a new PID can appear before its object API is ready.
Only retryable read failures are retried; the host action and conditional
registry write are never repeated. Permanent refusals return immediately.
If registry or audit recording fails, the error states the completed action,
running PID, and original cause, including for --json callers; it does not
pretend that nothing happened on the host.
Adopt
stado service adopt brings a unit that already exists on the host under
management. Adoption requires proof: Stado records a corrected path or unit
record only when the unit is loaded and its live process matches the declared
program; unproven ownership is recorded as identity_unresolved rather than
guessed, and never duplicated (operations). The registry
record is built from what the host actually reported — the resolved unit id,
path, and init system — not from what the operator hoped
(deploy/service/ops/files/records.rs, record_from_report).
Retire
stado service retire removes a service from management: bootout/disable and
forget (cli). It is deliberately not "remove this service" —
stado service remove is the operation that also stops the unit and deletes
its declared file. Removing the last declaration drops the services key
entirely, so a host with nothing declared reads the same as one that never
declared anything (deploy/service/model/document/registry.rs).
Recovery-sourced units are never written into the registry by any of these
paths (operations).
Converge
stado service converge <host> --json is the read-only comparison of declared,
installed, attested, and running bytes. --apply closes only the drift it can
prove. For Stado, the global product release owns units that execute
$HOME/.stado/bin/stado; every registry declaration that executes Stado from
an independently installed $HOME/.stado/services/... tree is completed
afterwards from the same catalog-verified archive through service update.
Private readers are selected from the registry, not from a fixed label list.
The archive stays in the version/platform release tree so a partial failure is
resumable. An already-attested global root is not reinstalled or kicked merely
to repair one old private reader; an absent or corrupt retained copy is fetched
and verified without root activation. service update is idempotent for a
private tree already on that archive and refresh-image --if-needed leaves its
matching process running. A stale tree is installed and its process image must
be proved. Any install, lifecycle, or image-proof failure remains a failed
service converge --apply, and the queue agent keeps its existing
installed-release handshake rather than being interrupted mid-slot.
Failed private updates retain their child JSON or full stdout and stderr in
stado-readers.detail; the enclosing report does not replace that cause with a
closing brace or generic status. Stado Desktop calls the authenticated typed
API for both report and apply; it does not launch a second CLI process.
GET /api/service/converge?target=<host>[&binary=<name>] requires
converge-read; the corresponding bodyless POST requires converge-apply.
Both call the same implementation as stado service converge.
A completed request returns HTTP 200 with the full report and exit_code,
including a failed delivery. Desktop retains that complete JSON and exit code
through refresh, including fields it does not recognize. Missing or
action-ineligible credentials return 401, unavailable verifier state returns
503, and malformed query parameters or a request body return 400. A failure
before a report exists returns 503 with SERVICE_CONVERGE_FAILED and its
cause. See Registry API access for
the separate client bearer and server verifier grant.
Reader resume requires the target receiver to be built from a release containing the retained-archive command arguments. A numeric version banner alone does not establish that source identity. A receiver built without the contract rejects those arguments and the apply remains failed; normal root delivery of a release containing this fix must happen first. No compatibility shim converts an older source into success.
Commands that act on it
stado service list
stado service show <name>
stado service ensure <name> --host <host> --reason <why>
stado service adopt <unit> --host <host>
stado service retire <unit> --host <host>
stado service restart <name>
stado service logs <name>
stado service converge <host> --json
stado service converge <host> --apply --json
Full flags and the deploy/declare contract in the cli reference.
Not to be confused with
- The unit itself. The unit is host state; the service is the fleet's
declaration of it.
service restart weles-apiandservice restart com.wisent.weles-apiaddress the same declaration by its logical name or its unit id (deploy/service/model/managed/service.rs). - A service directory entry.
stado://service/<name>routing — who may call a service and where it currently answers — is the directory, a different primitive in the same document. - A release. Installing signed product bytes is the release flow; the service declaration is what must keep running regardless of which bytes are current.
Source: this website