Wheelhouse docs

What first boot does#

wheelhouse-firstboot.service runs /usr/lib/wheelhouse/firstboot.sh once per machine, as root, before the agent starts. It does three things: mints the agent its own router API key, pins the router's HTTP API to loopback, and — only if nobody else has — writes the product's login banners. It is the reason a customer never types an API key.

The unit's condition is the key file itself:

ini
ConditionPathExists=!/config/wheelhouse/api-key

So it runs until it has produced that file and then never again, including after an image upgrade, because /config survives one.

In order#

  1. Wait for the router. wait-for-vyos.sh polls vyos-router.service until its SubState reads exited, which is the only reliable signal that the boot configuration has been loaded. Exits 1 if the router service failed, or after five minutes.
  2. Stop if the key already exists. A non-empty /config/wheelhouse/api-key means there is nothing to do.
  3. Generate a key. 30 bytes from /dev/urandom, base64, with /, + and = stripped, truncated to 40 characters. If fewer than 32 characters survive, the script fails rather than writing a short key.
  4. Read the base version and the documentation directory, for the banner text below.
  5. Commit three nodes in one configuration session:

    set service https api keys id wheelhouse key <the generated key>
    set service https api rest
    set service https listen-address 127.0.0.1
  6. Save, naming the file explicitly.
  7. Verify by reading the saved configuration back, not by trusting the exit status.
  8. Write the key to disk at /config/wheelhouse/api-key, mode 0600.
  9. Log it: logger -t wheelhouse "first boot: API key created, HTTP API bound to loopback".
  10. Optionally write the login banners, in a second commit.

Why each of the three nodes#

NodeWhy
service https api keys id wheelhouse key …The agent's own credential. Root-equivalent on this router, which is why the file it lands in is 0600 and why the API it opens is closed in the same commit.
service https api restThe REST endpoints are off until asked for. With only a key configured, the router's API answers 404 to everything — which is the single most confusing failure mode there is, because the key looks right and nothing works.
service https listen-address 127.0.0.1The router's API stops being a network service. The agent, on the same box, is the management surface.

See Ports and listeners.

Believe the file, not the exit status#

commit can answer 0 while refusing the change — it does exactly that during boot, with "the configuration was not applied". So the script greps the saved configuration for the key it just set, and refuses to write the key file if it is not there:

wheelhouse first boot: the API key did not reach the configuration; not writing it

That refusal is load-bearing. A failed run leaves /config/wheelhouse/api-key absent, which leaves the unit's condition true, so the next boot tries again — rather than starting an agent that holds a key the router never accepted.

The login banners#

Written only when nobody else has written any, checked before the configuration session opens:

bash
grep -qE '^[[:space:]]*(pre-login|post-login) ' /config/config.boot

A Wheelhouse OS image already carries banners from its image flavor, and an operator may have written their own; neither is overwritten. The case this covers is the package installed on a plain VyOS box, or an image built before the flavor carried them.

set system login banner pre-login "Wheelhouse\n"
set system login banner post-login "Wheelhouse\n\n  Web UI     https://[this router]:8443\n  Licences   <docs>\n\nBuilt on VyOS <base> (GPL). Corresponding source: <docs>\n"

<docs> is /usr/share/doc/wheelhouse on an image and /usr/share/doc/wheelhouse-agent when only the package is installed, picked at runtime so two banners on one box never name different directories. <base> is VYOS_VERSION from /etc/os-release, falling back to VERSION_ID when /etc/os-release is VyOS' own — never /opt/vyatta/etc/version, which on a product image holds the product's version and would credit the base with the wrong number. An empty value prints the credit without a version rather than with a wrong one.

Without these nodes, VyOS renders its own template into /etc/motd: "Welcome to VyOS", the VyOS logo and the upstream support portal, on the console and — through pam_motd — over SSH. Setting the node replaces that template wholesale. The upstream credit stays, on its own line, which is where it belongs.

This is a second commit, deliberately after the key is on disk: a failure here must not cost the router its agent. If it fails, the script says so and carries on.

The pre-login node is also what VyOS writes into /etc/issue.net, the file sshd serves before authentication — which is why the console banner script no longer writes that file itself. The console banner carries the first-boot password and every interface address; neither belongs in front of an unauthenticated SSH connection.

Two shell traps this script documents#

Worth knowing if you ever write a script against the router's CLI:

  • set -e cannot be used. VyOS' script template sources the CLI's completion setup, whose shopt -p extglob returns non-zero, and the script dies there.
  • exit is an alias. After source /opt/vyatta/etc/functions/script-template, exit expands to eval $(vyatta_exit_configure) — it tears down the configuration session and returns; it does not end the script. exit 1 is worse than useless: it passes a stray 1 to that eval, bash reports 1: command not found, and execution carries straight on to the next line. Everywhere the script really means "stop" it says builtin exit.

Checking it worked#

bash
systemctl status wheelhouse-firstboot
journalctl -t wheelhouse
sudo ls -l /config/wheelhouse/api-key            # -rw------- root root
show configuration commands | match 'service https'

If the key file is missing and the unit has failed, the journal line names which of the two verifications did not pass.

See also#

Checked against#

packaging/firstboot.sh, packaging/wheelhouse-firstboot.service, packaging/wait-for-vyos.sh, packaging/iso/wheelhouse.toml, packaging/console-banner.sh, docs/security.md "The default posture".

Updated 2026-09-02 boot first-boot api-key banners