The Image Factory: Manifest-Driven Builds
Biggest change to the build system since I started this thing: desktops are now just a YAML file. No shell script per desktop, no touching the Containerfile, no CI edits. Wanted to write this one up because I'm pretty happy with how it turned out.
What it was beforeβ
Every desktop had its own 150-line bash script β gnome.sh, kde.sh, cosmic.sh, niri.sh, xfce.sh. Each one was a pile of if fedora; dnf install X; elif el10; dnf group install Y; fi. Wanted GNOME 50? Copy-paste gnome.sh and hope you didn't miss a spot. Wanted Ubuntu support? Go add elif apt; pkg_install Z to five different files and pray you got them all consistent.
The Justfile wasn't much better. The _build recipe was 150 lines of positional arguments, and if you wanted to know what arg 7 did you had to go read the whole signature. Not fun to touch.
What it is nowβ
Desktop manifestsβ
Each desktop is now a YAML file in manifests/desktops/:
# manifests/desktops/kde.yaml
display_manager: sddm
packages:
fedora:
groups: [kde-desktop]
packages: [sddm, dolphin, konsole, kate, ...]
el10:
groups: ["KDE Plasma Workspaces", ...]
optional: [kdeconnect, nvtop, ...]
pacman:
- plasma-desktop
- sddm
- dolphin
- konsole
apt:
- kde-plasma-desktop
- sddm
versionlock: [plasma-desktop, "qt6-*"]
One generic installer (install-desktop.sh) reads the manifest and does the rest β groups, packages, COPRs, version locks, enabling the display manager, all of it.
Pulling the build engine outβ
The _build monolith in the Justfile is now scripts/build-image-inner.sh β 198 lines, driven by env vars instead of 11 positional args you had to memorize. The Justfile itself is down to a 25-line wrapper.
Flavor resolutionβ
scripts/resolve-flavor.sh maps a flavor to its build params now, and I actually wrote 18 BATS tests to cover the paths instead of just hoping. The old 50-line if/elif/case block in the Justfile is just gone.
Numbers, if you like that sort of thingβ
| Before | After |
|---|---|
| 5 Γ 150-line DE scripts | 1 Γ 159-line generic installer + 5 YAML manifests |
150-line _build recipe (11 positional args) | 25-line wrapper + 198-line env-var script |
| 3 Containerfiles for HWE/nvidia (213 lines dead code) | 1 parameterized Containerfile.overlay (72 lines) |
| 0 tests for flavor routing | 18 BATS cases |
What's nextβ
Manifests now cover 4 package managers β dnf, apt, pacman, zypper β and I've since added Arch (marlin) and Debian (flounder, flounder-sid) on top of it. The matrix just keeps growing:
9 base variants Γ 5-6 desktops Γ 3 hardware layers
Adding a new distro at this point is: write a Containerfile that bootc-ifies it, add the pacman/apt/dnf sections to the manifests, done. The build system doesn't care what you throw at it.
