Wheelhouse docs

What CI does#

Two workflows live in .forgejo/workflows/. ci.yml tests everything on every push and pull request, and on a v* tag also builds the agent for two architectures, packages both, publishes a release and copies it to the download host. iso.yml builds the OS image on the same tags and attaches it to the same release. Neither is exotic: both run in the golang:1.26-bookworm container on a self-hosted runner, install Node from a tarball because that container has none, and drive the same scripts you can run by hand.

ci.yml#

Triggered by a push to main, a v* tag, any pull request, or by hand from the Actions tab. Two jobs.

Job build — test, package, release#

StepWhat it runs
Provide nodeNode 22.12.0 from the upstream tarball (gzip, not xz — the Go image has no xz), and git config --global --add safe.directory '*' so git will touch a workspace owned by another uid
Checkout
VersionA tag gives ${GITHUB_REF#refs/tags/v}; anything else becomes 0.0.0+<short sha>, so a bundle built off a branch cannot be mistaken for a shipped build. It runs first because the UI build stamps it into the bundle
Agentgo vet ./..., then go test -race -count=1 ./...
ToolsAll four Python suites: the importer, the seed reader, the installer's commands and its pty driver
Sitepython3 site/tests/test_build.py
Shellbash -n over the release scripts, install/, packaging/iso/build-iso.sh and the packaging shell, plus py_compile over the Python helpers
App cataloguecheck-images.py agent/catalog.json --offline
UInpm ci, tsc --noEmit, vitest run (the page tests), npm run test:qr (the QR encoder's vectors), vite build, with VITE_WHEELHOUSE_VERSION set from the version step
Agent buildamd64 and arm64, static, trimmed, version stamped
PackageTwo .debs, two tarballs, CHANGELOG.md copied in, and a SHA256SUMS over the lot
Signinstall/sign.sh sign dist/SHA256SUMS, only when RELEASE_SIGNING_KEY is set
Upload artifactThe whole of dist/
Contracttools/route-gaps.py — every route the mux serves is declared and either reached from the built UI or explained
Toolstools/tests/*.py, including the release-tool suite

Three details:

The version step runs before the UI build. VITE_WHEELHOUSE_VERSION is what ui/src/components/product.ts stamps into the bundle for the version shown in the account menu, on System and on About. Without it the bundle has no version of its own, and every one of those surfaces falls back to the product block on GET /api/system — or, on a box whose agent predates that block, to the literal unknown. There is no hardcoded constant to fall back to.

The shell syntax check needs no network. bash -n parses without running, which catches the release scripts — the ones that otherwise fail only at tag time.

The release body is the changelog section for that version. The step extracts the ## [0.5.1] … section up to the next ## heading and appends the verification instructions.

Job audit — dependency scan#

govulncheck over the agent's module set, and npm audit --audit-level=high over the UI's lockfile.

iso.yml#

Triggered by a v* tag, or by hand with two inputs: release_tag (attach to that existing release; blank means the ISO stays a run artefact) and vyos_branch (default rolling).

StepWhat it runs
Provide node and the Docker CLINode from a tarball, Docker's static client binary
Checkout
VersionA tag gives both the version and the tag; a manual run that names a release takes its version from that name
Build the agent packageBuilds ui/dist with VITE_WHEELHOUSE_VERSION set, builds the amd64 agent, and packages the .deb into $BUILD_DIR
Build the ISOpackaging/iso/build-iso.sh, 30 to 60 minutes
Inspect the imageThe standard-library Python check described on Building the image
Upload artifactEverything in $BUILD_DIR/out matching the version
Install the imagetools/release-readiness.sh — installs from a seed, takes the medium away, boots the disk, and checks the machine that comes back. Nothing below runs if this fails
Attach to the release (when a tag is known)Creates the release, with this version's CHANGELOG.md section as the body, and uploads all nine artefacts
Publish to the download hostpublish.sh <tag> /srv/wheelhouse-releases …
VerifyReads the mirror back: checksums, the channel index and latest

The job builds its own .deb rather than fetching the one ci.yml made, because a job token cannot read another private repository's releases. The build tree lives at /srv/wheelhouse-build on the runner host and is bind-mounted at the same path, so docker -v inside the script sees what the job wrote. The runner mounts the Docker socket into every job on its own — naming it again in the job's options is a duplicate mount point and the container never starts.

Only one job publishes, and only after the image has installed#

ci.yml publishes nothing. It tests, and keeps the packages as a run artifact. Everything that reaches a customer — the forge release, the nine attached files, the mirror, the channel index and latest — is written by iso.yml, and only after the install gate has installed that image from a seed and booted the disk. A green suite is not a working image, and this is where that rule is enforced rather than remembered.

publish.sh still does not write SHA256SUMS itself: release-index.py does, by rescanning the whole tag directory every time, so a re-run of a partly published tag leaves a complete, correctly signed set. The full sequence is on Making a release.

Running the ISO job by hand#

From the Actions tab: choose iso, then Run, and fill in the two inputs. Leave release_tag blank to get an artefact without touching a release. That is the safe way to test a vyos_branch change or a new vyos-build pin.

Everything else is reproducible locally with the commands on The test suites and Building the image. Nothing in either workflow only works on the runner, except the mounted host directories.

What CI cannot tell you#

  • Whether the UI works. The UI step type-checks, runs the page tests and the QR vectors, and builds the bundle — but the configuration tree, reconcile, Support, About and the licence page have no test, and the rest run in jsdom, which has no layout and no real WebSocket. Nothing in the pipeline opens a browser.
  • Whether it works against a router. No runner has one. agent/test.sh is the live check, and it is run by hand.
  • Whether the image installs. The inspection step reads the ISO's own headers; it does not boot it.
  • Performance. Nothing is measured, in CI or on hardware.

See also#

Updated 2026-09-18 development ci forgejo workflows