Contributing
Thanks for helping out! This guide covers the dev setup, the test loop, and the few conventions that keep the code testable and easy to rebrand.
Rebranding/forking into your own distro is a different workflow — see DERIVING.md. You almost never need to edit
src/to derive; you editconfig/distro.conf.
Prerequisites
Section titled “Prerequisites”You never need Arch-specific tooling on your host — pacman, archiso/mkarchiso, etc. run
inside the build container and the QEMU guest. What you actually install depends on which targets
you run:
make test(the fast inner loop):shellcheckandbats(for lint + unit tests), plussystemd-analyze, which is already present on most systemd Linux hosts and is used byverify-units.- Nix is not required — it’s just a convenience. By default the
Makefilefetchesshellcheck/batson demand vianix shell nixpkgs#shellcheck nixpkgs#bats, so you can install nothing and let Nix handle them. But you can equally installshellcheckandbatsyourself (your distro’s package manager,brew, etc.). Once they’re on yourPATH, run the targets with an emptyNIXRUNso they’re used directly:make test NIXRUN=(likewisemake lint NIXRUN=/make test-unit NIXRUN=). You can also just invoke the tools straight:shellcheck -x …,bats tests/unit(ortests/unit/run.sh, which already usesbatsfromPATH).
- Nix is not required — it’s just a convenience. By default the
make build-iso:docker(the build runs--privileged) and network access.make test-qemu:qemu-system-x86_64+qemu-img(and OVMF firmware).
In short: the unit tests need only shellcheck + bats (via Nix or installed yourself); the
full pipeline (make ci) additionally needs Docker and QEMU.
Dev loop
Section titled “Dev loop”| Command | What it does | Needs |
|---|---|---|
make test |
lint + test-unit + verify-units — the fast inner loop |
nix, systemd-analyze |
make build-iso |
Build the bootable ISO via Docker (also writes iso/output/SHA256SUMS) |
docker (--privileged) + network |
make test-qemu |
Boot the ISO in QEMU; run the happy-path + rollback tests | qemu (KVM or TCG) |
make test-qemu-interactive |
Drive the interactive installer in QEMU; boot + verify the result | qemu (KVM or TCG) |
make ci |
test + build-iso + test-qemu (full pipeline) |
all of the above |
Run make test before every change — it is fast and needs no docker/qemu.
Targeted runs:
nix shell nixpkgs#bats --command bats tests/unit # all unit testsnix shell nixpkgs#bats --command bats tests/unit/test_rollback.bats # one filebash tests/qemu/run.sh # integration (systemd-boot, default)bash tests/qemu/run.sh --bootloader grub # integration against GRUBbash tests/qemu/run.sh --net # update cycle over real pacman -Syubash tests/qemu/run.sh --interactive # interactive installer end-to-endConventions
Section titled “Conventions”These are load-bearing — please follow them:
- shellcheck-clean. Every shell script must pass
shellcheck -xwith zero findings. New scripts go in theSHELL_FILESlist in theMakefilesomake lintcovers them. - Scripts are sourceable. The engine and the init scripts only run their
main/*_main(and enable strict mode) when executed directly, guarded byif [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then set -euo pipefail; main "$@"; fi. This lets the bats testssourcethem and call individual functions in isolation. Keep new entry-point scripts sourceable the same way. - Dependency-inject external commands. The engine reaches
btrfs,bootctl,arch-chroot, etc. through overridable variables (BTRFS,BOOTCTL,ARCH_CHROOT,GRUB_EDITENV, …) and reads config fromSB_*env vars, so tests can mock them. Prefer adding pure, unit-testable functions over inlining effectful logic. SILVERBLUE-*markers are a contract. The uppercase progress markers (e.g.SILVERBLUE-MARKGOOD-OK,SILVERBLUE-ROLLBACK-ARMED,SILVERBLUE-INSTALL-PROMPT) are grepped literally by the QEMU harness, and the build’srender()deliberately leaves them untouched. Don’t rename them. The interactive installer’s prompt order is part of the same contract:phase_interactive_install()intests/qemu/harness.pyanswers the prompts in the ordergather_answers()(insrc/installer/silverblue-install) asks them — change one and you must change the other.- Keep
src/generic. The source tree keeps the upstreamsilverbluenames; all rebranding happens at build/install time fromconfig/distro.conf(see DERIVING.md). Don’t hardcode brand-specific strings intosrc/.
Every push and pull request runs make test. Pushes to main and tags also build the ISO and
upload it with its SHA256SUMS; pushing a v* tag additionally publishes them to a GitHub
Release. The QEMU integration job is manual (workflow_dispatch, with a scenario picker for
the unattended and/or interactive suites) because GitHub runners have no KVM and the TCG
fallback is slow. See .github/workflows/ci.yml.
Pull requests
Section titled “Pull requests”- Keep changes focused and explain the “why”.
- Add or update tests for behavior changes; ensure
make testis green. - For anything that touches the boot/update/rollback path, note whether you ran
make test-qemu(and against which--bootloader).