Wheelhouse docs

Building the package#

wheelhouse-agent_<version>_<arch>.deb is written by packaging/build-deb.py, a single Python file that uses nothing outside the standard library. There is no dpkg-deb, no debhelper and no build container: a .deb is an ar archive of three members, and tarfile and a sixty-byte header are enough to write one. That is why the same command produces the same package in CI, on a laptop and inside the image build, and why the packaging step has no toolchain of its own to break.

The command#

bash
(cd ui && npm ci && npm run build)
(cd agent && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \
   -ldflags="-s -w -X main.version=1.2.3" -o wheelhouse-agent-amd64 .)
python3 packaging/build-deb.py --version 1.2.3 --arch amd64 \
  --binary agent/wheelhouse-agent-amd64 --ui ui/dist --out dist/
FlagDefaultMeaning
--versionrequiredGoes into control, the file name and the on-box README
--archamd64Architecture: and the file name
--binaryrequiredThe built agent for that architecture
--uirequiredThe built UI. The script exits if it holds no index.html
--out.Output directory, created if missing
--maintainerWheelhouse <releases@rhymelikedi.me>Maintainer:

SOURCE_DATE_EPOCH, if set, becomes the modification time on every archive member instead of the build time — the usual reproducible-build lever, so a rebuild of the same inputs does not differ only in timestamps.

What the package installs#

PathContents
/usr/bin/wheelhouse-agentThe agent, mode 0755
/usr/share/wheelhouse/ui/The built UI — what --ui-dir points at
/usr/bin/wheelhouse-installThe disk installer
/usr/bin/wheelhouse-autoinstallThe seed finder
/usr/bin/wheelhouse-opnsense-importThe OPNsense importer, so a migration can be done on the router itself
/usr/lib/wheelhouse/firstboot.shMints the router API key, enables REST, pins the API to loopback
/usr/lib/wheelhouse/wait-for-vyos.shBoth units wait for VyOS to finish loading its configuration
/usr/lib/wheelhouse/console-banner.shRenders /etc/issue
/usr/lib/wheelhouse/install-driver.pyDrives VyOS' install image through a pseudo-terminal
/usr/lib/wheelhouse/seed-to-answers.pyCloud-config in, installer answers out
/usr/share/wheelhouse/dialogrcThe installer's colours
/usr/share/wheelhouse/logo.txtThe product logo in ANSI, for fastfetch --logo-type file-raw and neofetch --ascii
/lib/systemd/system-generators/wheelhouse-live-installerTurns getty@tty1 into the installer on a live boot
/lib/systemd/system/wheelhouse-agent.serviceThe agent
/lib/systemd/system/wheelhouse-firstboot.serviceFirst boot
/lib/systemd/system/wheelhouse-console.service + .timerThe console banner, redrawn on a timer
/usr/share/doc/wheelhouse-agent/copyrightpackaging/copyright — every licence in full
/usr/share/doc/wheelhouse-agent/READMEGenerated: what the package is, the VyOS credit, and where the corresponding source lives
/usr/share/doc/wheelhouse-agent/third-party.mdGenerated: the third-party map the web UI's About page sends customers to by name
python
data.symlink(f"./etc/systemd/system/multi-user.target.wants/{unit}",
             f"/lib/systemd/system/{unit}")

The wants-symlinks ship inside the package rather than being created by systemctl enable in postinst. That is what lets the package be installed inside a live-build chroot, where there is no systemd to talk to — and the image build is exactly that.

The console banner carries two of those symlinks. multi-user.target.wants is what runs it; getty.target.wants is what puts it in the same transaction as the gettys, so its Before= ordering has something to bite on — ordering only constrains units that are already in the transaction. Without the second symlink, the first screen of a new box is the stock banner until someone presses Enter.

control#

Package: wheelhouse-agent
Architecture: amd64
Depends: bash, python3, dialog, curl
Section: net
Priority: optional
Homepage: https://releases.rhymelikedi.me

Four dependencies, and each is load-bearing: bash for the installer and first boot, python3 for the pty driver and the seed reader, dialog for the installer's screens, curl for the health checks. Installed-Size is computed from the binary plus the UI tree.

The description names VyOS and points at the on-box README, so a machine with no network and no login to the forge still says what it is built on.

postinst and prerm#

postinst
#!/bin/sh
set -e
if [ "$1" = configure ] && [ -d /run/systemd/system ]; then
    systemctl daemon-reload || true
    systemctl start wheelhouse-console.timer || true
    systemctl try-restart wheelhouse-agent.service || true
fi
exit 0

Two things about it are deliberate. The -d /run/systemd/system guard is what makes the package installable in a chroot, where every systemctl call would fail. And try-restart rather than restart means installing the package on a machine where the agent was not running does not start it.

prerm stops the agent on remove, under the same guard.

Inspecting a built package#

Nothing exotic is needed, because nothing exotic went in:

bash
ar t dist/wheelhouse-agent_1.2.3_amd64.deb        # debian-binary control.tar.gz data.tar.gz
ar p dist/wheelhouse-agent_1.2.3_amd64.deb control.tar.gz | tar tzvf -
ar p dist/wheelhouse-agent_1.2.3_amd64.deb data.tar.gz    | tar tzvf - | head -40

# Or, with dpkg available:
dpkg-deb -I dist/wheelhouse-agent_1.2.3_amd64.deb
dpkg-deb -c dist/wheelhouse-agent_1.2.3_amd64.deb

Where the package goes next#

Two places, and they are different jobs:

  1. The release. CI builds amd64 and arm64, packages both, and attaches them to the tag's release and the public download host — Making a release.
  2. The image. The ISO job builds the amd64 package from its own checkout and drops it into vyos-build/packages/, where live-build installs every package it finds — Building the image.

The arm64 package exists so the agent can run off-router against an arm64 router. There is no arm64 image.

See also#

Checked against#

packaging/build-deb.py · packaging/README.md · packaging/copyright · packaging/wheelhouse-agent.service · packaging/wheelhouse-firstboot.service · packaging/wheelhouse-console.service · packaging/wheelhouse-console.timer · packaging/wheelhouse-live-installer · .forgejo/workflows/ci.yml · .forgejo/workflows/iso.yml

Updated 2026-09-02 development packaging deb systemd