Wheelhouse docs

Building the image#

Wheelhouse OS is VyOS, built from source in VyOS' own build container, with the Wheelhouse .deb dropped into vyos-build/packages/ and a flavor file that says what the image should be. packaging/iso/build-iso.sh is the whole of it: about 200 lines of shell that clone and pin vyos-build, patch three identity strings in its GPL build tooling, stage the branding overlay, run the build, and write the corresponding-source record the GPL asks for. It takes 30 to 60 minutes and needs Docker and a privileged container, because live-build mounts filesystems.

Running it#

bash
DEB=dist/wheelhouse-agent_1.2.3_amd64.deb VERSION=1.2.3 \
  bash packaging/iso/build-iso.sh
VariableDefaultMeaning
DEBrequiredThe agent package to bake in
VERSIONdevThe image version string
BUILD_DIR/srv/wheelhouse-buildWhere the vyos-build checkout and the output live. Must be a host path — docker -v needs one even when the script itself runs inside a job container
VYOS_COMMITthe pin in the scripthead follows the branch instead. Use that to find the next pin, never to ship
VYOS_BRANCHrollingBranch and container tag. Upstream renamed current to rolling in 2026
VYOS_BUILD_REPOhttps://github.com/vyos/vyos-build
VYOS_BUILD_IMAGEvyos/vyos-build:$VYOS_BRANCH
BUILD_BYreleases@rhymelikedi.meBuilder identity written into the image

Output, in $BUILD_DIR/out/:

FileWhat it is
wheelhouse-<version>-amd64.isoThe image
wheelhouse-<version>-amd64.iso.sha256Its checksum
vyos-build-wheelhouse-<version>.patchThe changes made to the GPL build tooling, published
SOURCES-<version>.txtThe corresponding-source record: the vyos-build commit, the container digest, and every installed package

The base, pinned#

bash
VYOS_COMMIT=${VYOS_COMMIT:-ed1f619f89df0f911918b13a07f6df2cbca0a956}

The pin is a commit rather than a tag, and the script's comment explains why: the newest tag in vyos-build is 1.4.0, which belongs to a branch upstream now marks public-unmaintained, and the maintained branches are crux, equuleus and rolling. Rolling has no tags at all. The pin lives in the source tree rather than in CI so that the base is a property of the source.

To move it: run once with VYOS_COMMIT=head, note the commit the script prints, put it in the script, rebuild, and boot the result on the bench. The branding hooks assert on upstream's own strings and fail the build if the new commit moved them, which is the point of them.

What the script does, in order#

1. Check the splash#

bash
magic=$(od -An -tx1 -N 8 "$splash" | tr -d ' \n')
[ "$magic" = "89504e470d0a1a0a" ] || { echo "$splash is not a PNG" >&2; exit 1; }

It reads the PNG signature and the IHDR dimensions with od and awk, and refuses anything that is not a 640×480 PNG. od rather than an image library because the runner is a build container with neither Python image tools nor a guaranteed font.

The splash matters legally, not only visually: vyos-build's own artwork is copyright Sentrium S.L., all rights reserved, and its LICENSE.artwork permits redistributing a self-built image only if that artwork is replaced. Both copies are replaced — includes.binary/isolinux/splash.png, which is what both bootloaders draw, and bootloaders/grub-pc/splash.png, which live-build copies regardless. packaging/iso/branding/make-splash.py draws it with zlib and struct, with the letterforms defined as strokes rather than loaded from a font file.

2. Fetch and pin vyos-build#

A shallow fetch of the pinned commit, with a fallback to a depth-200 fetch of the branch for mirrors that refuse a bare SHA.

3. Patch the GPL build tooling#

build-vyos-image hardcodes several identity strings as literals inside a Jinja2 template, so no flavor key can reach them. The script patches the file with a helper that fails the build if the expected literal is not there or if the substitution did not take:

ChangedWhy
--iso-application "VyOS""Wheelhouse"A Wheelhouse stick otherwise identifies itself as VyOS
--iso-volume "VyOS""WHEELHOUSE"blkid -L would otherwise see VyOS. Uppercase because ISO 9660 wants it that way
The live kernel command line: hostname=vyoshostname=wheelhouse, and quiet addedThe host name is set before VyOS loads any configuration, so the first console prompt would otherwise be vyos login:. quiet is there because without it the longest screen of the boot is the base introducing itself — vyos-1x ships fourteen systemd units whose descriptions start with "VyOS", and systemd prints every one between our splash and our banner

quiet raises the console log level to KERN_ERR and stops systemd printing unit status; panics, oopses and units that actually fail still reach the screen, so a broken boot still says so. The escape hatch is the fail-safe menu entry, which boots from --bootappend-live-failsafe — a line upstream leaves without quiet. The script fails the build if upstream ever adds quiet there, because the boot menu labels that entry as the verbose one and the label has to stay true. Either bootloader also lets an operator edit the line before booting (Tab in isolinux, e in GRUB).

build-vyos-image is GPL-2, so the diff is published: git diff -- scripts/image-build/build-vyos-image is written to out/vyos-build-wheelhouse-<version>.patch, and the script fails if that diff is empty — an empty patch means the branding did not apply.

4. Render the flavor#

packaging/iso/wheelhouse.toml is a template, not valid flavor input until @VERSION@ and @VYOS_BASE@ are substituted. The script substitutes them, then greps for any surviving @…@ and fails if it finds one. It also refuses the literal string <TODO in the rendered flavor and in the three documents that ship beside it on the image, because /etc/os-release is machine-parsed and a placeholder there is malformed data rather than a visible blank. What the flavor contains is on The flavor and the branding hooks.

5. Stage the branding overlay#

includes.binary/isolinux/splash.png            replaced
bootloaders/grub-pc/splash.png                 replaced
hooks/live/50-wheelhouse-brand.binary          boot menus
hooks/live/50-wheelhouse-brand.chroot          identity and licence files
includes.chroot/usr/share/doc/wheelhouse/      TERMS, THIRD-PARTY, SOURCE-OFFER, SOURCES

Old hooks are swept with rm -f hooks/live/50-wheelhouse-* first, because they are untracked in the vyos-build checkout and a git reset would not remove one that has since been renamed.

6. Drop in the package#

bash
rm -f vyos-build/packages/wheelhouse-agent_*.deb
cp "$DEB" vyos-build/packages/

live-build installs every package it finds there. That is the entire mechanism by which the agent, the UI and the installer get into the image.

7. Write the corresponding-source record#

Twice. Once before the build, into includes.chroot/usr/share/doc/wheelhouse/SOURCES.txt, because it has to be in the chroot — that copy points at /var/lib/dpkg/status for the package list. Once after, beside the ISO as SOURCES-<version>.txt, with the package list vyos-build emitted.

Both name the vyos-build repository and commit, the branch, the container image digest, the agent package, the three patches to the GPL tooling, and every file added to the base. The container digest is only recorded if it looks like a digest — a failing docker still prints something.

8. Build#

bash
docker run --rm --privileged \
  -v "$BUILD_DIR/vyos-build:/vyos" -w /vyos \
  -e GOSU_UID="$(id -u)" -e GOSU_GID="$(id -g)" \
  "$VYOS_BUILD_IMAGE" bash -c "
    sudo ./build-vyos-image --architecture amd64 --build-by '$BUILD_BY' \
      --build-type release --version 'wheelhouse-$VERSION' \
      --build-comment 'Wheelhouse OS $VERSION on VyOS …' wheelhouse"

--privileged is required because live-build mounts filesystems. The version passed to upstream is wheelhouse-<version>, which is what the binary branding hook reads back out of version.json to title the boot menu.

9. Collect#

bash
iso=$(ls -t vyos-build/build/*.iso | head -1)

That line is why the pipeline has an inspection step: in a reused build tree it will happily select a previous run's image, which then gets copied to a name built from this run's $VERSION. See below.

The checks that stop the wrong bytes shipping#

.forgejo/workflows/iso.yml runs a standard-library Python step over the finished file before anything is published. It reads the ISO 9660 primary volume descriptor at sector 16 directly — no loop mount, no privileges, no new tools:

CheckFailure or warning
The file existsFailure
Larger than 200 MBFailure — no VyOS image is that small
CD001 at offset 1 of the descriptorFailure — is this an image at all?
Volume label contains wheelhouseWarning — the --iso-volume patch did not take; a branding defect, not a safety one
Creation timestamp within 48 hoursFailure — this is a stale artefact from an earlier run, not what was just built
.sha256 beside the image matches itFailure
SOURCES-<version>.txt exists and names vyos-buildFailure — the corresponding-source record is missing

The 48-hour window is the one worth understanding. The image's own creation stamp is the single field that cannot be renamed into agreement, so it is what catches the ls -t hazard. The window absorbs the builder's timezone offset without letting a month-old image through.

Building it by hand#

The ISO job runs on the hades runner because that is where /srv/wheelhouse-build keeps the vyos-build checkout between runs, but the script runs anywhere Docker does. By hand, from a clean checkout:

bash
# 1. Build the inputs.
(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 /tmp/wh

# 2. Build the image. 30-60 minutes.
DEB=/tmp/wh/wheelhouse-agent_1.2.3_amd64.deb VERSION=1.2.3 \
  BUILD_DIR=/tmp/wh-build bash packaging/iso/build-iso.sh

What has and has not been verified#

See also#

Checked against#

packaging/iso/build-iso.sh · packaging/iso/wheelhouse.toml · packaging/iso/branding/make-splash.py · packaging/iso/branding/50-wheelhouse-brand.binary · packaging/iso/branding/50-wheelhouse-brand.chroot · packaging/iso/branding/doc/SOURCE-OFFER.txt · packaging/README.md · .forgejo/workflows/iso.yml · docs/hardware.md

Updated 2026-09-02 development iso vyos-build live-build gpl