CI and release verification¶
bty ships from a single GitHub Actions pipeline,
.github/workflows/ci-cd.yml
(workflow name CI). The same suite runs on every pull request and every
push to main; pushing a v* tag runs it again and additionally publishes.
Nothing is published from a red build.
Triggers¶
Event |
What runs |
|---|---|
Pull request to |
Full verification suite (no publish) |
Push to |
Full suite, then auto-tag if the version is new ( |
Push of a |
Full suite, then publish (PyPI + ghcr.io + GitHub release) |
|
|
Verification jobs¶
Everything below must be green before anything publishes.
check-not-published – aborts early if PyPI already has the version, so a re-tag can’t rebuild release assets from a newer commit while PyPI stays frozen (the destinations would drift out of sync).
lint –
pre-commit(ruff check + format, shellcheck, hygiene hooks).test – the typecheck + pytest matrix across Python 3.11-3.14.
flash-integration – the flash pipeline against a loop device.
build-wheel –
uv buildof thebty-labwheel + sdist.build-ipxe – compiles bty’s custom embedded-chain
ipxe.efi(see below).build-netboot-pc – the netboot live image (kernel + initrd + squashfs).
build-usbboot-pc – the bootable USB live ISO.
test-pxe – the end-to-end PXE chain test (see below).
test-usb-grow – boots the USB ISO in QEMU and asserts the first-boot service grows
BTY_IMAGESto fill the stick.test-usb-ventoy – Ventoy-boots the ISO in QEMU and asserts image + catalog discovery surfaces an operator-dropped image and catalog entry.
docs – HTML + PDF build (a broken PDF blocks the release).
The PXE chain test¶
test-pxe is the end-to-end proof that a target can PXE-boot and flash
itself. It builds the bty-web image from the checkout, runs it as a
container, and PXE-boots a QEMU client VM against it over a host bridge:
A host bridge (
br-pxe) carries the server-side IP and a user-owned tap for the client VM. A test-sidednsmasqon the bridge does DHCP + TFTP (bty itself never runs DHCP – this is synthetic test machinery).The bty-web container publishes
:8080; the test drives the production HTTP API to seed the live trio, a dummy flash image, and a per-MACboot_mode=bty-flash-alwaysassignment.The QEMU client PXE-boots, chainloads iPXE, fetches the per-MAC bootstrap, loads kernel + initrd + squashfs over HTTP, and runs
btyin auto-flash mode.
The test tails the client’s serial console and asserts every stage marker
appears – iPXE loaded, /pxe-bootstrap.ipxe fetched, the per-MAC chain, the
kernel/initrd/squashfs fetch, bty: auto-flash starting, and bty: flash complete; rebooting. It runs make test-pxe
(cijoe/scripts/pxe_prepare.py + pxe_run_chain_test.py).
The custom iPXE¶
build-ipxe runs make ipxe, which compiles bty’s slim, embedded-chain
ipxe.efi (~1 MB). Its embedded script chains to
http://${next-server}:8080/pxe-bootstrap.ipxe, so the operator’s DHCP only
needs a single bootfile – no userclass logic. The binary is:
baked into the bty-web image (
docker/seed/), which seeds it into the HTTP boot dir for UEFI HTTP-Boot;baked into the bty-tftp sidecar (
deploy/tftp/seed/) for TFTP;attached to the GitHub release.
It’s x86_64-EFI only; the arm64 images fall back to stock iPXE (the documented BIOS/arm64 chain-loop caveat).
Auto-release¶
A version bump on main releases itself, but only off a green build:
tag-release runs on
mainafter the entire verification suite passes. It reads the version frompyproject.tomland, if no matchingv<version>tag exists, creates and pushes it. The push uses a PAT (RELEASE_PAT) so the new tag triggers a fresh workflow run – aGITHUB_TOKEN-pushed tag would not.The tagged run re-runs the suite and then publishes:
attach-to-release – gathers the wheel/sdist, custom
ipxe.efi, netboot trio, USB ISO, PDF docs, a generatedcatalog.toml, and arelease.tomlmanifest, and attaches them to the GitHub release.publish-pypi – trusted-publishes the wheel + sdist (strictly last; a published PyPI version can never be re-uploaded).
publish-docker / publish-tftp – build and push the multi-arch
ghcr.io/safl/bty-webandghcr.io/safl/bty-tftpimages, staging the customipxe.efiinto each build context first.
Running it locally¶
make ci # lint + format-check + typecheck + test (the package side)
make test-pxe # the end-to-end PXE chain test (needs QEMU + KVM + podman)
make ipxe # build the custom ipxe.efi -> dist/ipxe/ipxe.efi
make build VARIANT=netboot-pc # the netboot live image