Files and directories#
Two install layouts exist and they share almost nothing. The appliance — the ISO or
the .deb — puts the binary in /usr/bin, its data in /config/wheelhouse and its
units in /lib/systemd/system. The developer install, install/install.sh, puts
the binary in /opt/wheelhouse and its secrets in /etc/wheelhouse, because the host it
runs on has no /config. Nothing reads both locations: a secret written under
/etc/wheelhouse on an appliance is a file the agent will never open.
/config is the partition VyOS keeps across an image upgrade. That is the only reason
the agent's data lives there and not under /var/lib, which does not survive one.
Installed by the package#
Everything here comes from packaging/build-deb.py, and
the modes are the ones it sets.
| Path | Mode | What |
|---|---|---|
/usr/bin/wheelhouse-agent | 0755 | The agent. |
/usr/bin/wheelhouse-install | 0755 | The disk installer. |
/usr/bin/wheelhouse-autoinstall | 0755 | The seed finder. Prints a path and exits 0, or exits 1; writes nothing. |
/usr/bin/wheelhouse-opnsense-import | 0755 | tools/opnsense-import.py, so a migration can be done on the router itself. |
/usr/share/wheelhouse/ui/ | 0755 dir, 0644 files | The built web UI the agent serves with --ui-dir. |
/usr/share/wheelhouse/dialogrc | 0644 | The installer's dialog colour scheme. |
/usr/lib/wheelhouse/firstboot.sh | 0755 | What first boot does. |
/usr/lib/wheelhouse/wait-for-vyos.sh | 0755 | Blocks until the router has finished loading its configuration. |
/usr/lib/wheelhouse/console-banner.sh | 0755 | The console banner. |
/usr/lib/wheelhouse/install-driver.py | 0755 | Drives the base installer through a pseudo-terminal. |
/usr/lib/wheelhouse/seed-to-answers.py | 0755 | Turns a cloud-config seed into answer-file lines. |
/lib/systemd/system/wheelhouse-agent.service | 0644 | |
/lib/systemd/system/wheelhouse-firstboot.service | 0644 | |
/lib/systemd/system/wheelhouse-console.service | 0644 | |
/lib/systemd/system/wheelhouse-console.timer | 0644 | |
/lib/systemd/system-generators/wheelhouse-live-installer | 0755 | On a live boot, turns getty@tty1 into the installer. |
/etc/systemd/system/multi-user.target.wants/wheelhouse-{agent,firstboot,console}.service | symlink | Enabled by symlink rather than by postinst, so the package works inside a live-build chroot where systemctl cannot run. |
/etc/systemd/system/timers.target.wants/wheelhouse-console.timer | symlink | Same reason. |
/usr/share/doc/wheelhouse-agent/copyright | 0644 | The Debian copyright file. |
/usr/share/doc/wheelhouse-agent/README | 0644 | What this is, what it is built on, and where the corresponding source lives. The units' Documentation= points here, because the forge is private and a URL is no substitute for a file on the box. |
Added by the image#
Only on a Wheelhouse OS image, staged by
packaging/iso/build-iso.sh and asserted by
packaging/iso/branding/50-wheelhouse-brand.chroot,
which fails the build if any of them is missing.
| Path | What |
|---|---|
/usr/share/doc/wheelhouse/TERMS.txt | The product's terms. |
/usr/share/doc/wheelhouse/THIRD-PARTY.txt | Third-party notices. |
/usr/share/doc/wheelhouse/SOURCE-OFFER.txt | The written offer for the corresponding source. |
/usr/share/doc/wheelhouse/SOURCES.txt | The vyos-build commit, the build container, and the version of every installed package. |
/usr/share/vyos/EULA | A symlink to ../doc/wheelhouse/TERMS.txt, so show license on the router pages the Wheelhouse terms. |
/etc/os-release | Rewritten by the image flavor. See Identifying the system. |
/usr/share/wheelhouse/autoinstall.conf | Optional, and never shipped by default: a seed baked into a custom image. See Where the installer looks for a seed. |
The data directory#
--data-dir, /config/wheelhouse on the appliance. Created at 0700 if it does not
exist. Everything the agent must remember across a restart is here, and nothing else is.
| Path | Mode | Written by | What |
|---|---|---|---|
api-key | 0600 | first boot | The router's own API key, 40 characters from /dev/urandom. Root-equivalent on that router. |
admin-token | 0600 | you | The optional break-glass token. The shipped unit names it unconditionally; a missing file simply means there is none. |
license-key | 0600 | you | An optional pinned licence key. The web UI stores one in state.json instead. |
initial-password | 0600 | the agent | The generated first-run admin password. The console banner shows it until that account's password is changed, then the agent removes it. |
state.json | 0600 | the agent | Accounts, password hashes, TOTP secrets, sessions, API token hashes, settings, enabled feature modules, and the licence key the UI stored. |
state.json.bak | 0600 | the agent | A hard link to the previous state.json, refreshed before each write, so at every instant at least one complete state file exists. |
state.json.tmp | 0600 | the agent | Transient. Written, fsynced, then renamed over state.json. |
state.json.corrupt-<timestamp> | 0600 | the agent | Only when both copies failed to parse. The agent starts empty rather than refusing to boot, and keeps the unreadable files for inspection. |
audit.jsonl | 0600 | the agent | One JSON object per line, appended and fsynced per entry. Rotates at 8 MB. |
audit.jsonl.1 | 0600 | the agent | Exactly one previous generation. The audit log therefore costs at most 16 MB on disk. |
agent.crt | 0644 | --tls-self-signed | The generated certificate. |
agent.key | 0600 | --tls-self-signed | Its private key. |
agent.yaml | yours | you | The conventional name for the desired-state file. It is a convention, not a format promise — JSON parses too. |
install.commands | 0600 | the installer | The exact set lines the install applied, copied onto the new disk as a record of how the box was built. |
oidc-secret | 0600 | you | The identity provider's client secret. Nothing reads this by default — the shipped unit sets no --oidc-* flags. The path is a convention this documentation keeps so the secret lives beside the others. |
The permission rule#
The agent refuses to read any secret file that is group- or world-readable, and says which one:
api-key-file (/config/wheelhouse/api-key) is mode 644; it must not be readable by
group or other (chmod 600)That applies to --api-key-file, --admin-token-file, --license-key-file,
--initial-admin-password-file, --oidc-client-secret-file and --fleet-config.
A missing file is fatal only for --api-key-file; the others warn and continue.
Paths outside the data directory the agent touches#
| Path | Access | Why |
|---|---|---|
/config/archive/config.boot.<N>.gz | read, through the router | POST /api/rollback loads one of these. The router's HTTP API has no rollback operation; the CLI's command works by loading the archived file, so the agent does the same. |
/config/config.boot | through the router | Where POST /api/config/save writes by default. |
/config/apps/… | created by POST /api/apps/prepare | Volume source directories for installed apps. |
/etc/machine-id | read | Hashed with a constant into the licence fingerprint. Falls back to the host name when absent. |
The systemd unit runs with ProtectSystem=strict and ReadWritePaths=/config, so
/config is the only writable tree the agent has.
Runtime files#
None of these survive a reboot; /run is a tmpfs.
| Path | Written by | What |
|---|---|---|
/etc/issue | console-banner.sh | The console banner. Mode 0600 while it carries the first-boot password, 0644 afterwards. /etc/issue.net is deliberately not written here — sshd serves that file before authentication. |
/run/wheelhouse/banner-facts | console-banner.sh | Cached version strings and the live/installed answer, keyed on the mtime of /var/lib/dpkg/status. |
/var/log/wheelhouse-install.log | the installer, the seed finder | Every installer step, and the base installer's transcript. The first place to look when an install fails; the installer names it in its own error message. |
/run/wheelhouse-install.commands | the installer | The set lines the install will run, written before the summary so they can be read, and run from that file afterwards. |
/run/wheelhouse-install.lock | the installer | Present while the installer runs. The banner refuses to restart tty1's getty while it exists. |
/run/wheelhouse-install.stage | the installer | Which stage the install is in, for the progress screen. |
/run/wheelhouse-install.disk-touched | the install driver | Set once the target disk has been partitioned. It is what makes the failure message say the disk is gone rather than that nothing was written. |
/run/wheelhouse-install.no-ipv6-firewall | the installer | Set when the image has no firewall ipv6 node, so the absence of a v6 ruleset is reported rather than discovered later. |
/run/wheelhouse-autoinstall.attempted | the installer | An unattended install runs at most once per boot. This file is why a failure cannot become a loop that keeps erasing the disk. |
/run/wheelhouse-seed/answers.conf | the seed finder | The converted seed. |
/run/wheelhouse-seed/{seed,config.commands,config.boot,authorized_keys} | the seed finder | Payloads copied off the seed medium, so they are still readable once it is unmounted. |
The developer install#
install/install.sh is the lab path, not the product.
| Path | Mode | What |
|---|---|---|
/opt/wheelhouse/wheelhouse-agent | 0755 | The running binary. |
/opt/wheelhouse/wheelhouse-agent.old | 0755 | The previous binary, kept for the automatic rollback. |
/opt/wheelhouse/wheelhouse-agent.new | 0755 | Transient staging path, so a failed build never clobbers a working binary. |
/opt/wheelhouse/ui/ | 0755 | The built UI. |
/opt/wheelhouse/agent.log | — | Only when there is no systemd unit and the script backgrounds the agent itself. |
/opt/wheelhouse/agent.pid | — | Same. It is what stop_service kills, rather than pattern-matching a command line. |
/etc/wheelhouse/ | 0700 | |
/etc/wheelhouse/api-key | 0600 | |
/etc/wheelhouse/admin-token | 0600 | Removed when --admin-token is not given, rather than left stale. |
/config/wheelhouse | 0700 | The data directory, same as the appliance — accounts and the audit log belong on the persistent partition even in a lab. |
Finding it all on a running box#
dpkg -L wheelhouse-agent # everything the package owns
ls -la /config/wheelhouse # the data directory, with modes
systemctl cat wheelhouse-agent # the flags actually in use
find /config/wheelhouse -perm /0077 # any secret file with loose permissionsSee also#
- Every flag —
--data-dir, and the five secret-file flags. - systemd units — what runs, and what it may write.
- What first boot does — where
api-keycomes from. - The
.deband The ISO - Audit entries — the format of
audit.jsonl. - Ports and listeners
- Backup and Restore — which of these paths matter.
- Deployment modes
Checked against#
packaging/build-deb.py,
packaging/wheelhouse-agent.service,
packaging/firstboot.sh,
packaging/console-banner.sh,
packaging/wheelhouse-install,
packaging/wheelhouse-autoinstall,
packaging/iso/build-iso.sh,
packaging/iso/branding/50-wheelhouse-brand.chroot,
agent/store.go,
agent/tls.go,
agent/main.go (loadSecretFiles, initialPasswordPath,
archiveDir),
agent/license.go (licenseFingerprint),
install/install.sh,
docs/deploy.md "Secrets on disk",
packaging/README.md.