Wheelhouse docs

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. /var/lib does not survive one.

Installed by the package#

The modes are the ones the package sets.

PathModeWhat
/usr/bin/wheelhouse-agent0755The agent.
/usr/bin/wheelhouse-install0755The disk installer.
/usr/bin/wheelhouse-autoinstall0755The seed finder. Prints a path and exits 0, or exits 1; writes nothing.
/usr/bin/wheelhouse-opnsense-import0755tools/opnsense-import.py, so a migration can be done on the router itself.
/usr/share/wheelhouse/ui/0755 dir, 0644 filesThe built web UI the agent serves with --ui-dir.
/usr/share/wheelhouse/dialogrc0644The installer's dialog colour scheme.
/usr/share/wheelhouse/logo.txt0644The product mark as ANSI text, for fastfetch --logo-type file-raw --logo and neofetch --ascii. Neither finds it on its own; nothing on the box reads it automatically.
/usr/lib/wheelhouse/firstboot.sh0755What first boot does.
/usr/lib/wheelhouse/wait-for-vyos.sh0755Blocks until the router has finished loading its configuration.
/usr/lib/wheelhouse/console-banner.sh0755The console banner.
/usr/lib/wheelhouse/install-driver.py0755Drives the base installer through a pseudo-terminal.
/usr/lib/wheelhouse/seed-to-answers.py0755Turns a cloud-config seed into answer-file lines.
/lib/systemd/system/wheelhouse-agent.service0644
/lib/systemd/system/wheelhouse-firstboot.service0644
/lib/systemd/system/wheelhouse-console.service0644
/lib/systemd/system/wheelhouse-console.timer0644
/lib/systemd/system-generators/wheelhouse-live-installer0755On a live boot, turns getty@tty1 into the installer.
/etc/systemd/system/multi-user.target.wants/wheelhouse-{agent,firstboot,console}.servicesymlinkEnabled by symlink rather than by postinst, so the package installs inside a live-build chroot where systemctl cannot run.
/etc/systemd/system/getty.target.wants/wheelhouse-console.servicesymlinkThe banner's second want. It puts the unit in the same transaction as the gettys, so its Before= ordering applies.
/etc/systemd/system/timers.target.wants/wheelhouse-console.timersymlinkSame reason.
/usr/share/doc/wheelhouse-agent/copyright0644The Debian copyright file.
/usr/share/doc/wheelhouse-agent/README0644What this is, what it is built on, and where the corresponding source lives. The units' Documentation= points here.
/usr/share/wheelhouse-agent/third-party.md0644Every third-party component with its licence. Outside /usr/share/doc on purpose: the image build's exclude list strips everything under /usr/share/doc/<package>/ except copyright and README, so a copy installed there is nowhere in the shipped filesystem. A README-third-party pointer is left in the old directory.

Added by the image#

Only on a Wheelhouse OS image. The image build fails if any of them is missing.

PathWhat
/usr/share/wheelhouse/doc/TERMS.txtThe product's terms.
/usr/share/wheelhouse/doc/THIRD-PARTY.txtThird-party notices.
/usr/share/wheelhouse/doc/SOURCE-OFFER.txtWhere the corresponding source is: the tarball beside it, the release directory, and Debian. GPLv2 §3(a), so nothing has to be requested.
/usr/share/wheelhouse/doc/SOURCES.txtThe vyos-build commit, the build container, and the version of every installed package.
/usr/share/wheelhouse/doc/wheelhouse-<version>-<arch>-build-source.tar.gzThe corresponding source itself: the vyos-build tree at the exact commit, with Wheelhouse's changes applied.
/usr/share/doc/wheelhouseA symlink to /usr/share/wheelhouse/doc, so every path the banners print and this documentation names still reaches those files. The files themselves cannot live under /usr/share/doc: mksquashfs -ef data/live-build-config/rootfs/excludes strips everything below /usr/share/doc/<dir>/ except copyright and README, after every chroot hook has run — which is why v0.7.0 shipped that directory empty. A symlink at that level survives, because the pattern needs a second path component after its *.
/usr/share/vyos/EULAA symlink to ../wheelhouse/doc/TERMS.txt, so show license on the router pages the Wheelhouse terms.
/etc/os-releaseRewritten by the image flavor. See Identifying the system.
/usr/share/wheelhouse/autoinstall.confOptional, 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.

PathModeWritten byWhat
api-key0600first bootThe router's own API key: 30 bytes from /dev/urandom, base64, with /, + and = stripped, so 32–40 characters. Root-equivalent on that router.
admin-token0600youThe optional break-glass token. The shipped unit names it unconditionally; a missing file means there is none.
license-key0600youAn optional pinned licence key. The web UI stores one in state.json instead.
initial-password0600the agentThe generated first-run admin password. The console banner shows it until that account's password is changed, then the agent removes it.
state.json0600the agentAccounts, password hashes, TOTP secrets, sessions, API token hashes, settings, enabled feature modules, and the licence key the UI stored.
state.json.bak0600the agentA hard link to the previous state.json, refreshed before each write, so at every instant at least one complete state file exists.
state.json.tmp0600the agentTransient. Written, fsynced, then renamed over state.json.
state.json.corrupt-<timestamp>0600the agentOnly when both copies failed to parse. The agent starts empty rather than refusing to boot, and keeps the unreadable files for inspection.
audit.jsonl0600the agentOne JSON object per line, appended and fsynced per entry. Rotates at 8 MiB.
audit.jsonl.10600the agentExactly one previous generation. The audit log therefore costs at most 16 MiB on disk.
agent.crt0644--tls-self-signedThe generated certificate.
agent.key0600--tls-self-signedIts private key.
agent.yamlyoursyouThe conventional name for the desired-state file. It is a convention, not a format promise — JSON parses too.
install.commands0600the installerThe exact set lines the install applied, copied onto the new disk as a record of how the box was built.
oidc-secret0600youThe identity provider's client secret. Nothing reads this by default — the shipped unit sets no --oidc-* flags. The path is a convention; nothing enforces it.

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#

PathAccessWhy
/config/archive/config.boot.<N>.gzread, through the routerPOST /api/rollback loads one of these. The router's HTTP API has no rollback operation, so the archived file is loaded instead.
/config/config.bootthrough the routerWhere POST /api/config/save writes by default.
/config/apps/…created by POST /api/apps/prepareVolume source directories for installed apps.
/etc/machine-idreadHashed 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.

PathWritten byWhat
/etc/issueconsole-banner.shThe console banner. Mode 0600 while it carries the first-boot password, 0644 afterwards. /etc/issue.net is not written here — sshd serves that file before authentication.
/run/wheelhouse/banner-factsconsole-banner.shCached version strings and the live/installed answer, keyed on the mtime of /var/lib/dpkg/status.
/var/log/wheelhouse-install.logthe installer, the seed finderEvery 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.commandsthe installerThe set lines the install will run, written before the summary so they can be read, and run from that file afterwards.
/run/wheelhouse-install.lockthe installerPresent while the installer runs. The banner refuses to restart tty1's getty while it exists.
/run/wheelhouse-install.stagethe installerWhich stage the install is in, for the progress screen.
/run/wheelhouse-install.disk-touchedthe install driverSet once the target disk has been partitioned. A failure message uses it to say whether the disk was written to.
/run/wheelhouse-install.no-ipv6-firewallthe installerSet when the image has no firewall ipv6 node, so the absence of a v6 ruleset is reported rather than discovered later.
/run/wheelhouse-autoinstall.attemptedthe installerAn unattended install runs at most once per boot. A failed one does not retry until the next boot.
/run/wheelhouse-seed/answers.confthe seed finderThe converted seed.
/run/wheelhouse-seed/{seed,config.commands,config.boot,authorized_keys}the seed finderPayloads copied off the seed medium, so they are still readable once it is unmounted.

The developer install#

install/install.sh is the developer path, not the product.

PathModeWhat
/opt/wheelhouse/wheelhouse-agent0755The running binary.
/opt/wheelhouse/wheelhouse-agent.old0755The previous binary, kept for the automatic rollback.
/opt/wheelhouse/wheelhouse-agent.new0755Transient staging path, so a failed build never clobbers a working binary.
/opt/wheelhouse/ui/0755The 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. The script stops the agent by this pid.
/etc/wheelhouse/0700
/etc/wheelhouse/api-key0600
/etc/wheelhouse/admin-token0600Removed when --admin-token is not given, rather than left stale.
/config/wheelhouse0700The data directory, same as the appliance.

Finding it all on a running box#

bash
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 permissions

See also#

Updated 2026-09-03 files paths secrets permissions