Exit codes#
A status code is the interface a script actually uses, so each of these is deliberate.
The one worth memorising is wheelhouse-agent plan: exit 2 means the router has
drifted, the way diff -q behaves, so it drops into a shell test without parsing text.
wheelhouse-agent, the daemon#
| Code | When |
|---|---|
| 0 | version or --version. Also a clean shutdown after SIGTERM or SIGINT. |
| 2 | The command line or the configuration is wrong, and the agent never started. |
| 1 | The agent could not open something it needs, or the listener died. |
Exit 2, before anything is opened:
- an unexpected non-flag argument, or an unknown subcommand — both print the usage text;
- a flag the flag package cannot parse;
- neither
--demonor both--api-urland an API key; - a secret file that cannot be read, or one that is group- or world-readable;
- single sign-on misconfigured — see the validation list;
--reconcile-modethat is notstageorcommit;--tls-certwithout--tls-key, or the reverse.
Exit 1, after startup began:
- the data directory cannot be created;
- another agent already owns that data directory —
another agent is running against this data dir; - the state file was written by a newer agent (
the state file is newer than this agent); catalog.jsonwill not parse —app catalog is invalid;- the first admin account could not be created;
--tls-self-signedcould not generate a certificate;--fleet-configwill not load, or is not mode 0600;- the listener stopped with anything other than a clean shutdown.
A handler that panics does not exit the process: it is caught, logged with its stack,
and answered as a 500.
wheelhouse-agent plan#
| Code | Means |
|---|---|
| 0 | The router already matches the file. |
| 2 | It does not. There are operations to apply. |
| 1 | The file will not parse, holds an empty value, contradicts the router, or the router did not answer. |
if ! wheelhouse-agent plan --file /config/wheelhouse/agent.yaml >/dev/null; then
echo "drift"
fiNote the asymmetry: 2 is not a failure, it is an answer. A script that treats every non-zero status as an error will report drift as a crash.
wheelhouse-agent apply#
| Code | Means |
|---|---|
| 0 | Nothing needed changing, or the operations were staged, or they were committed and confirmed. |
| 1 | No usable licence; no --agent-url and no --commit; --commit without --confirm-minutes; the agent refused the staging request; the commit failed; or the confirm did not reach the router. |
There is no 2 here. apply either did the work or did not.
The installer#
wheelhouse-install:
| Code | When |
|---|---|
| 0 | Installed. Also --commands, which prints and stops. Also choosing "keep running the live system" from the menu. |
| 1 | Under --answers: DISK missing, both passwords missing, the answers file unreadable, --commands without --answers, the system is already installed, or the install failed and ON_ERROR is exit. |
Interactively, a failed install returns to the menu rather than exiting, and the exit
status is whatever the eventual menu choice produces. Under --answers the failure path
is ON_ERROR — which may halt or power off the
machine instead of returning a status at all.
wheelhouse-autoinstall:
| Code | Means |
|---|---|
| 0 | A seed was found. Its converted answer file's path is on stdout. |
| 1 | No seed, or the search was switched off with wheelhouse.autoinstall=off. |
That is its whole interface. It writes to no disk.
install-driver.py, which drives the base installer through a pseudo-terminal:
| Code | Means |
|---|---|
| 0 | The image was written. |
| 1 | It was not — including an unknown prompt, which stops rather than guesses. |
The helper scripts#
| Program | 0 | 1 | 2 |
|---|---|---|---|
wait-for-vyos.sh | the router finished loading its configuration | it failed, or five minutes passed | — |
firstboot.sh | the key exists, or was created | the key could not be generated, or did not reach the configuration | — |
console-banner.sh | always | — | — |
seed-to-answers.py | converted | the seed is not a mapping, or has no wheelhouse: section | wrong number of arguments |
The tools#
| Tool | 0 | 1 | 2 |
|---|---|---|---|
install.sh | installed, verified, or rolled back | any die: a missing flag, no Go and no binary, an unknown verb, a failed health check, a rollback with nothing to roll back to | — |
sign.sh | the manifest was written, the files were signed, or verification passed | any die: checksums do not match, no signing key, a signature that does not verify, or REQUIRE_SIGNATURE=1 with nothing signed | no verb, -h or --help |
check-images.py | every image is well formed and, without --offline, reachable | any malformed or unreachable image — and, with --require-digest, any floating tag | — |
opnsense-import.py | the import ran; with --check-against, nothing is missing | with --check-against, something is missing; or the file is not valid XML or not an OPNsense configuration | — |
wheelhouse-seed.py | the seed was written | no password and nowhere to ask for one, the two entries differ, the importer failed, or no ISO tool is installed | — |
build-deb.py | the package was written | --ui is not a built UI | — |
release-index.py | the index was written, or an older tag was left alone | the tag directory is missing, holds no artefacts, or is not a vX.Y.Z tag | — |
build-iso.sh | the image was built | any failing step — it runs under set -euo pipefail, and several checks fail deliberately when upstream has changed | — |
opnsense-import.py --check-against exiting 1 for "something is missing" is the point of
it: it is meant to be a cron job after a migration.
HTTP, not exit codes#
An API caller gets a status code, not an exit status. Those are on
Status codes, and they do not line up — 402 has no
command-line equivalent, and a plan exit of 2 has no HTTP one.
See also#
wheelhouse-agent- Every flag — what each exit-2 refusal is about.
- The desired-state file — where the
plancodes come from. - Log lines worth alerting on — what an exit 1 leaves in the journal.
- Troubleshooting
- Desired state
Checked against#
agent/main.go (main, registerFlags, runDaemon,
loadSecretFiles),
agent/desired.go (runPlanApply),
agent/store.go (errStoreLocked, errStateTooNew),
agent/oidc.go (validateOIDCFlags),
agent/security.go (recoverPanics),
packaging/wheelhouse-install,
packaging/wheelhouse-autoinstall,
packaging/install-driver.py,
packaging/seed-to-answers.py,
packaging/wait-for-vyos.sh,
packaging/firstboot.sh,
packaging/console-banner.sh,
packaging/build-deb.py,
packaging/iso/build-iso.sh,
install/install.sh,
install/sign.sh,
scripts/check-images.py,
tools/opnsense-import.py,
tools/wheelhouse-seed.py,
.forgejo/workflows/release-index.py.