Derive Your Own Distro
Arch Silverblue is built so you can fork it into your own atomic Arch-based distro by editing
one file — config/distro.conf — and rebuilding. You do not need to
hunt through the scripts for the string silverblue; the build reads everything it needs to
rebrand from that config.
Reference implementation: SilverDeck — a gaming-console distribution built on Arch Silverblue — is a real-world derivative and the reference example of this fork-and-own model.
How it works (fork-and-own model)
Section titled “How it works (fork-and-own model)”Your derivative is a full copy of this repository that you own and maintain. You edit
config/distro.conf, run the build, and ship your ISO. To pick up later upstream fixes you
git merge (or cherry-pick) from upstream like any other fork.
The build performs two strictly separated transformations, which is why a default (unedited) build is identical to upstream:
- The source tree is never rewritten.
src/keeps itssilverbluedefaults so the unit tests keep working. Branding in the engine is just env-var indirection whose defaults equal today’s values. - Renaming/branding happens at build & install time.
iso/build.shreadsconfig/distro.confand renders/renames copies of the tools into the ISO; the autoinstaller (which gets the same config copied into the ISO) lays those down on the target and writes/etc/os-release, hostname, timezone, locale, and any extra repos.
config/distro.conf ──► iso/build.sh ──► ISO (renamed binary, units, paths, os-release.in, config) │ └──► autoinstaller (in the ISO) ──► installed target (os-release, hostname, tz, locale, repos, tools)Prerequisites
Section titled “Prerequisites”Same as upstream (see README.md):
make test(lint + unit tests + unit verification): just needsbash;shellcheck/batsare pulled on demand vianix shell.make build-iso: Docker with--privilegedand network (all Arch tooling runs in the container).make test-qemu: QEMU.
The one file you edit: config/distro.conf
Section titled “The one file you edit: config/distro.conf”It is plain bash (KEY="value" and arr=(...)), sourced by the build. The defaults reproduce
stock Arch Silverblue. Variables, grouped:
Identity / branding (also fills /etc/os-release)
Section titled “Identity / branding (also fills /etc/os-release)”| Variable | Purpose |
|---|---|
DISTRO_ID |
lowercase machine id → os-release ID, LOGO, and the Docker image tag |
DISTRO_NAME |
pretty name → boot-entry titles and os-release NAME/PRETTY_NAME |
DISTRO_VERSION / DISTRO_VERSION_ID |
os-release BUILD_ID / VERSION_ID |
DISTRO_ANSI_COLOR |
os-release ANSI_COLOR |
DISTRO_HOME_URL |
os-release HOME_URL and the Documentation= URL in the systemd units |
DISTRO_DOC_URL / DISTRO_SUPPORT_URL / DISTRO_BUG_URL |
the matching os-release URLs |
Names & paths (rendered into the image only)
Section titled “Names & paths (rendered into the image only)”| Variable | Purpose | Required for a rename? |
|---|---|---|
BIN_PREFIX |
the CLIs become ${BIN_PREFIX}-update and ${BIN_PREFIX}-install (/usr/bin/silverblue-update / -install by default) |
yes |
UNIT_PREFIX |
the units become ${UNIT_PREFIX}-mark-good.service / -rollback.service / -rollback.target |
yes |
LIB_DIR |
helper/library dir (/usr/lib/silverblue) |
yes |
ESP_SUBDIR |
per-snapshot kernel dir on the ESP (/efi/silverblue/<snap>, systemd-boot) |
cosmetic |
SORT_KEY |
systemd-boot sort-key grouping value |
cosmetic |
TOPLEVEL_MNT |
transient Btrfs subvolid=5 mountpoint |
cosmetic |
BIN_PREFIX and UNIT_PREFIX are usually set to the same value as DISTRO_ID.
System defaults (written into the installed target)
Section titled “System defaults (written into the installed target)”| Variable | Purpose |
|---|---|
HOSTNAME |
/etc/hostname |
TIMEZONE |
zoneinfo path (e.g. Europe/Amsterdam) |
LOCALE |
locale.gen entry + /etc/locale.conf LANG |
KEYMAP |
vconsole keymap ("" = leave default) |
These are what the unattended test install writes verbatim, and what the interactive
installer offers as prompt defaults (BOOTLOADER below likewise seeds its bootloader menu).
Bootloader / filesystem
Section titled “Bootloader / filesystem”| Variable | Purpose |
|---|---|
BOOTLOADER |
default bootloader when none is forced (systemd-boot or grub) |
FS_LABEL |
Btrfs root label + GPT partition name |
ESP_LABEL |
FAT32 ESP label |
EFI_DIR |
ESP mountpoint on the installed system — advanced; leave at /efi (the autoinstaller’s grub-install/mount paths assume /efi) |
KEEP_SNAPSHOTS |
max snapshots retained by the engine (SB_KEEP) |
BOOT_TRIES |
systemd-boot boot-counting tries (SB_TRIES) |
Packages & repos
Section titled “Packages & repos”| Variable | Purpose |
|---|---|
PKGS_BASE |
array of packages pacstrapped onto the target |
PKGS_ISO |
array of extra packages added to the live ISO so the installer can run |
EXTRA_REPOS |
array of complete pacman.conf section blocks appended to the target (networked installs only — see below) |
What gets rendered where
Section titled “What gets rendered where”| You set… | …and it lands in |
|---|---|
DISTRO_ID |
the ISO file name (<id>-YYYY.MM.DD-x86_64.iso) and the Docker image tag |
BIN_PREFIX |
/usr/bin/<prefix>-update and /usr/bin/<prefix>-install; the baked engine defaults; file_permissions in the ISO profile |
UNIT_PREFIX |
the three unit files + the two *-mark-good.sh/*-rollback.sh scripts; the enable symlink; the watchdog drop-in |
LIB_DIR |
where the engine finds its helpers/scripts (SB_LIB_DIR baked in) |
DISTRO_NAME |
systemd-boot/GRUB entry titles; unit Description=; os-release NAME/PRETTY_NAME |
DISTRO_* URLs/ids |
/etc/os-release (rendered from config/os-release.in) and unit Documentation= |
HOSTNAME / TIMEZONE / LOCALE / KEYMAP |
the installed /etc/hostname, /etc/localtime, /etc/locale.conf, /etc/vconsole.conf |
FS_LABEL / ESP_LABEL |
the Btrfs/ESP labels and the GPT partition name |
PKGS_ISO |
packages.x86_64 in the archiso profile |
PKGS_BASE |
the pacstrap package list of both installers (the interactive one adds the user’s microcode/firmware/network/sudo choices on top) |
EXTRA_REPOS |
the target’s /etc/pacman.conf |
What deliberately stays generic: the whole src/ tree on disk, the upstream unit tests,
tools/verify-units.sh, the uppercase SILVERBLUE-* progress markers (the QEMU harness greps
them), and the test-only silverblue-autoinstall.sh / synthetic [silverblue-local] repo.
The installer library and interactive frontend are installed verbatim (they read your
distro.conf at runtime); only the frontend’s file name is derived from BIN_PREFIX.
Branding
Section titled “Branding”- Set
DISTRO_NAMEfor the pretty name shown in the boot menu,systemctl status, and/etc/os-release. - Tweak
config/os-release.indirectly if you want extra fields (e.g. a differentLOGOorANSI_COLOR). It’s a template with@TOKEN@placeholders that the autoinstaller fills in from the config.
Packages & repos
Section titled “Packages & repos”- Edit
PKGS_BASEto change what’s installed on the target (addlinux-firmwarefor real hardware; add a desktop, NetworkManager, etc.). - Edit
PKGS_ISOif your installer needs more tools in the live environment. - Add
EXTRA_REPOSto point the installed system at your own pacman repo, e.g.:Caveat:Terminal window EXTRA_REPOS=($'[mydistro]\nSigLevel = Optional TrustAll\nServer = https://repo.mydistro.org/$arch')EXTRA_REPOSis applied by the interactive installer (always networked) and by the unattended test install’s networked path (net=1). The offline/hermetic test path leaves the target with just the bundledfile://repo so the self-contained update test still works.
Build & verify
Section titled “Build & verify”# 1. Sanity-check the engine/units (independent of your config; must stay green):make test
# 2. Build your ISO:make build-iso # -> iso/output/*.iso
# 3. (optional) Boot + install + update + rollback in QEMU:make test-qemuAfter make build-iso, confirm your rename took (replace mydistro with your BIN_PREFIX):
# Inspect the assembled airootfs staging (or extract the squashfs from the ISO):grep -R "mydistro-update" iso/output/ 2>/dev/null # or look in the build profile
# Inside the image you should find:# /usr/bin/mydistro-update# /usr/lib/mydistro/{sdboot-helpers.sh,grub-helpers.sh,mydistro-mark-good.sh,mydistro-rollback.sh}# /usr/lib/systemd/system/mydistro-mark-good.service (ExecStart=/usr/lib/mydistro/...,# OnFailure=mydistro-rollback.target)# the baked engine header: SB_DISTRO_NAME, SB_LIB_DIR, SB_VERIFY_UNIT set to your valuesOn the installed target, cat /etc/os-release shows your ID/PRETTY_NAME, hostnamectl
shows your hostname, the boot menu title shows your DISTRO_NAME, and mydistro-update --dry-run
runs.
Constraints & gotchas
Section titled “Constraints & gotchas”- Keep the source defaults if you reuse the upstream unit tests. The bats tests pin the
defaults (
Arch Silverblue,sort-key silverblue, ESP path/silverblue/...). They test the source tree, which is never rewritten, so they keep passing regardless of your config. If you rename and want a greenmake test-qemu, pointtests/qemu/harness.py/tests/qemu/run.shat yourBIN_PREFIX-named binary and units (the harness uses the literal names). - sed-safe values.
DISTRO_NAMEand the URLs are substituted withsed; the build escapes&,|, and\, but avoid embedding a literal|inDISTRO_NAMEto be safe. ESP_SUBDIRmust agree everywhere. Both the engine (baked) and the autoinstaller read it from this one config, so they stay in lockstep — don’t hand-edit it in only one place.EFI_DIRis wired into the engine but the autoinstaller assumes/efi; leave it unless you’re also willing to adjust the autoinstaller’sgrub-install/mount paths.
FAQ / troubleshooting
Section titled “FAQ / troubleshooting”mark-good.servicefails to start / “permission denied”. The executables must be 0755 in the ISO.iso/build.shinjectsfile_permissionsentries for your renamed paths; if you changed the injection, double-check those entries matchBIN_PREFIX/LIB_DIR/UNIT_PREFIX./etc/os-releasedidn’t change. Arch ships/etc/os-releaseas a symlink to/usr/lib/os-release; the autoinstaller writes a regular file that overrides it. If you overrideOSRELEASE_IN, make sure the template still has the@TOKEN@placeholders.- Bootloader “not detected” after install. Make sure
BOOTLOADERissystemd-bootorgrub, and thatESP_SUBDIRis consistent (see above). - Build uses the wrong image tag.
IMAGEin theMakefileis derived fromDISTRO_ID; if the config can’t be sourced it falls back toarch-silverblue-iso.