First-Boot Package Initialization#
Bluefin-based bootc images (like those built from Finpilot) use a build-time pre-staging + runtime initialization pattern for package managers. Homebrew and Flatpak are never installed or invoked during the OCI image build — instead, everything they need is baked into the image as data, and dedicated systemd services activate them on first boot.
This pattern is critical for bootc: the image must be reproducible and composable at build time, while per-user and network-dependent steps happen at runtime.
Architecture Overview#
Build-Time Pre-Staging#
Homebrew#
The ghcr.io/ublue-os/brew OCI layer is imported as a separate stage in the Containerfile and copied into the context stage . During the build, 10-build.sh overlays it into the image root with rsync -rvK /ctx/oci/brew/ / — this drops the Homebrew tarball and its systemd unit files directly into the image filesystem.
Brewfiles are placed into the image at /usr/share/ublue-os/homebrew/ at build time . Custom images (built from Finpilot) declare their packages in custom/brew/*.Brewfile; these are copied verbatim — no brew binary is invoked during the build .
The build script then enables three systemd units that will fire at runtime :
| Unit | Scope | Purpose |
|---|---|---|
brew-setup.service | system | Extracts the pre-staged Homebrew tarball on first boot |
brew-update.timer | system | Periodic Homebrew update |
brew-upgrade.timer | system | Periodic package upgrades |
Flatpak#
Flatpak preinstall lists are INI-format .preinstall files. The build copies them from custom/flatpaks/ to /usr/share/flatpak/preinstall.d/ in the image . No flatpaks are downloaded or installed during the build.
First-Boot Runtime Initialization#
brew-setup.service (from ublue-os/brew)#
The brew-setup.service (shipped via the ublue-os/brew OCI layer) is the first Homebrew trigger. It extracts the pre-staged tarball into /home/linuxbrew/.linuxbrew on first boot. Until this completes, the brew binary does not exist and all downstream brew services are blocked.
Verify with:
systemctl status brew-setup.service
Note: If
brewis unavailable after switching to an image viabootc switch, this service is the first thing to check.
flatpak-preinstall.service (from projectbluefin/common)#
flatpak-preinstall.service is a system-scoped oneshot that runs flatpak preinstall -y after network-online.target. It reads all .preinstall files from /usr/share/flatpak/preinstall.d/ and installs the declared apps.
| Property | Value |
|---|---|
| Scope | system (multi-user.target) |
| Trigger | After=network-online.target |
| Retries | Up to 3×, 30s apart, within 600s |
| Guard | ConditionPathExists=/usr/bin/flatpak |
Flatpaks are not installed during bootc switch — only on first boot with network access .
Verify with:
systemctl status flatpak-preinstall.service
Post-Setup: brew-preinstall.service (from projectbluefin/common)#
After brew-setup.service has run (i.e., Homebrew is available), brew-preinstall.service takes over ongoing OS-managed package maintenance. This is a user-scoped oneshot that runs on every login session and handles image updates.
| Property | Value |
|---|---|
| Scope | user (graphical-session.target) |
| Trigger | After=network-online.target ublue-user-setup.service |
| Guard | ConditionPathExists=/var/home/linuxbrew/.linuxbrew/bin/brew |
| Retries | Up to 3×, 30s apart, within 600s |
Mechanism — content-addressed Brewfile sync:
- SHA-256 hashes all
*.Brewfilefiles under/usr/share/ublue-os/homebrew/preinstall.d/. - Compares against the stored hash in
~/.local/share/ublue-os/brew-preinstall-state.json. - If unchanged → fast exit, no
brewinvocation. - If changed → runs
brew bundlefor each Brewfile, then removes packages dropped from the managed set without touching user-added packages. - Writes updated state atomically via temp-file rename to survive SIGKILL.
Verify with:
systemctl --user status brew-preinstall.service
Customization in Finpilot#
| What to customize | Where |
|---|---|
| CLI packages (Homebrew) | custom/brew/*.Brewfile → auto-copied to /usr/share/ublue-os/homebrew/ |
| GUI apps (Flatpak) | custom/flatpaks/*.preinstall → auto-copied to /usr/share/flatpak/preinstall.d/ |
| Additional systemd units to enable | build/10-build.sh → systemctl enable … |
See custom/brew/README.md and the Finpilot README for the full customization workflow.
Key References#
| Resource | Link |
|---|---|
| Finpilot Containerfile | Containerfile |
| Build script | 10-build.sh |
| Homebrew preinstall script | brew-preinstall |
| Homebrew preinstall service | brew-preinstall.service |
| Flatpak preinstall service | flatpak-preinstall.service |
| Troubleshooting (Finpilot README) | README.md |
| Brew customization guide | custom/brew/README.md |