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#
| Step | What it runs |
|---|---|
| Provide node | Node 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 | |
| Version | A 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 |
| Agent | go vet ./..., then go test -race -count=1 ./... |
| Tools | All four Python suites: the importer, the seed reader, the installer's commands and its pty driver |
| Site | python3 site/tests/test_build.py |
| Shell | bash -n over the release scripts, install/, packaging/iso/build-iso.sh and the packaging shell, plus py_compile over the Python helpers |
| App catalogue | check-images.py agent/catalog.json --offline |
| UI | npm ci, tsc --noEmit, vite build, with VITE_WHEELHOUSE_VERSION set from the version step |
| Agent build | amd64 and arm64, static, trimmed, version stamped |
| Package | Two .debs, two tarballs, CHANGELOG.md copied in, and a SHA256SUMS over the lot |
| Sign | install/sign.sh sign dist/SHA256SUMS, only when RELEASE_SIGNING_KEY is set |
| Upload artifact | The whole of dist/ |
| Release (tags only) | Creates the forge release, with this version's CHANGELOG.md section as the body, and uploads every artefact |
| Publish (tags only) | publish.sh <tag> /srv/wheelhouse-releases dist/* |
Three details worth knowing:
The version step runs before the UI build. VITE_WHEELHOUSE_VERSION is what
ui/src/components/product.ts reads for the version
shown in the sidebar, on the Dashboard, on System and on About. Without it the bundle
falls back to a literal in the source, and every future image would keep reporting the
version that literal was last edited to.
The shell syntax check is cheap and unflakeable. bash -n parses without running, and
it exists because the release scripts are the ones that only fail at tag time — the worst
possible moment to find out.
The release body is the changelog section for that version. A fixed sentence every
time tells a subscriber nothing about what they just got, so 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).
| Step | What it runs |
|---|---|
| Provide node and the Docker CLI | Node from a tarball, Docker's static client binary |
| Checkout | |
| Version | A tag gives both the version and the tag; a manual run that names a release takes its version from that name |
| Build the agent package | Builds ui/dist with VITE_WHEELHOUSE_VERSION set, builds the amd64 agent, and packages the .deb into $BUILD_DIR |
| Build the ISO | packaging/iso/build-iso.sh, 30 to 60 minutes |
| Inspect the image | The standard-library Python check described on Building the image |
| Upload artifact | Everything in $BUILD_DIR/out matching the version |
| Attach to the release (when a tag is known) | Creates the release if ci.yml has not yet, then uploads |
| Publish to the download host | The same publish.sh |
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.
The two jobs write to the same directory, on purpose#
ci.yml publishes the packages; iso.yml publishes the image and the
corresponding-source files. Neither knows what the other published, so neither writes
SHA256SUMS itself. publish.sh calls
release-index.py, which rescans the whole tag
directory every time — so whichever job finishes last 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. There are no UI tests;
tscandvite buildare a type check and a bundle. - Whether it works against a router. No runner has one.
agent/test.shis 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#
- The test suites — every suite these steps invoke.
- Making a release — the tag-only half of
ci.yml, in order. - Building the image — the script
iso.ymlwraps. - Working agreements — the definition of done that a green pipeline does not satisfy on its own.
- Release history — what the tag-only steps end up producing.
- Download and verify — what a customer does with the artefacts these jobs publish.
Checked against#
.forgejo/workflows/ci.yml ·
.forgejo/workflows/iso.yml ·
.forgejo/workflows/publish.sh ·
.forgejo/workflows/release-index.py ·
ui/src/components/product.ts ·
packaging/README.md ·
agent/test.sh