Extensibility via systemd-sysext
Use systemd-sysext for read-only tools and services under /usr and /opt.
Use systemd-confext for files under /etc. The base OS includes bash;
heavier debugging and development tools belong in extensions or containers.
Where extensions live
Sysexts are searched in /etc/extensions/, /run/extensions/, and
/var/lib/extensions/. Use /var/lib/extensions/ for persistent images.
An empty directory under /etc/extensions/, named after an extension without
.raw, masks the same extension in a lower-precedence directory.
Confexts are searched in /run/confexts/, /var/lib/confexts/,
/usr/lib/confexts/, and /usr/local/lib/confexts/.
Extension identity and version matching
The host identifies as ID=bluefin-server, VERSION_ID=<image-version>.
An extension must match the host ID (or use ID=_any) and any VERSION_ID
it pins.
- Image-locked: ZFS, NVIDIA drivers, kubeadm, homelab and its add-ons,
Podman, and Docker use
<name>_<image-version>.raw. Only the matching version merges. Keep older versions for A/B rollback. Kernel modules must match the exact kernel they were built for. - Independent: k0s and the NVIDIA Container Toolkit use
ID=_anywithout pinning the host version. Their update components stage images outside the scanned extension directories so two versions do not merge together.
Installed nodes update image-locked extensions with the OS through optional
sysupdate features. Diskless nodes
receive /etc/extensions/<name>_<ver>.raw through Ignition with a sha256
verification hash.
Fetch extensions for the running image
Enabling a feature affects later updates; it does not fetch the extension for
the running version. The opt-in bluefin-sysext-fetch.service fills that gap.
It is disabled by default and enabled by the
homelab templates.
Once per image version, it fetches missing files for features enabled under
/etc/sysupdate.d/<feature>.feature.d/, using:
- The USB installer's
<ESP>/bluefin/extensions/copy, if present. It checksSHA256SUMS, but these checksums are unsigned (Verify=no): trust is only as strong as the disk. Used files are deleted. - On diskless nodes, the boot-origin directory, with signature verification
(
Verify=yes). - On installed nodes,
systemd-sysupdate update. This repairs an incomplete running version or installs a newer release and reboots into it.
It then refreshes sysexts and starts bluefin-sysext-activate.service, before
kubeadm or k0s first-boot setup. Failed fetches retry every 30 seconds; success
is stamped in /var/lib/bluefin-sysext-fetch/<ver>.
Optional services
-
NVIDIA: select one
nvidia-open-<branch>driver feature. Driver units skip nodes without a GPU; merging two flavours fails the flavour guard. The separately updated toolkit needsnvidia-container-toolkit-activate.service. See NVIDIA sysexts for activation, CDI, and containerd. -
Homelab: the
homelabfeature supplies manifests and an applier. It runs only with/etc/bluefin/homelab.conf, waits for kubeadm or k0s, applies enabled components in order, skips unset inputs, retries failures, and never deletes resources.HOMELAB_ROLE=nodeskips applying. Add-ons and multi-node setup are in Homelab profile. -
Podman/Docker: the
podmananddockerfeatures and their user units default off. They are rootless, create no rootful socket, and do not replace Kubernetes' containerd or CNI. Provision non-overlapping subordinate UID/GID ranges and enable lingering for the intended nonroot user. After fetching and merging the chosen extension, that user runs:systemctl --user daemon-reload# Choose one:systemctl --user enable --now bluefin-podman.socketsystemctl --user enable --now bluefin-docker.servicePrivate binaries live under
/usr/lib/bluefin/{podman,docker}; state is separate. Endpoints are$XDG_RUNTIME_DIR/bluefin-podman/api.sockand$XDG_RUNTIME_DIR/bluefin-docker/api.sock; no client or engine enters base/usr.
Kernel-module sysexts
ZFS and NVIDIA extensions carry signed modules, but no modules.* indexes:
shipping an index would hide the base image's and other extensions' indexes.
Their boot units load modules through /usr/libexec/bluefin-sysext-modules,
which builds a combined index under /run/bluefin/kmods and runs modprobe
against it. Dependencies, modprobe options, blacklists, kernel lockdown, and
signature checks still apply.
A bare modprobe zfs or modprobe nvidia, including udev modalias autoloading,
uses only the base index and may report "module not found". The boot units
are zfs-load-module.service and nvidia-load.service. For manual inspection:
modinfo -b "$(/usr/libexec/bluefin-sysext-modules --basedir)" zfs
Adding an extension
As root, download a compatible image and merge it:
wget <extension-url> -O /var/lib/extensions/myext.raw
systemd-sysext merge
systemd-sysext status
After adding or replacing images on an already merged system, use:
systemd-sysext refresh
systemd-sysext.service refreshes at boot. The enabled
bluefin-sysext-activate.service then re-requests multi-user.target so units
with enable links inside the newly merged extension start in the same boot.
Removing an extension
rm /var/lib/extensions/myext.raw
systemd-sysext refresh
Files and links created in /etc remain on installed nodes; diskless nodes
start with an empty /etc each boot. Remove stale configuration as needed.
Never symlink an sshd drop-in into an extension: a dangling link matching
sshd's Include glob prevents sshd from starting. A real drop-in containing an
Include of an optional /usr path is safe when that path disappears.
The homebrew-integration extension uses this pattern. After removal,
/etc/ssh/sshd_config.d/90-bluefin-homebrew-sysext.conf and the dangling
/etc/sudoers.d/bluefin-homebrew and
/etc/profile.d/90-bluefin-homebrew.sh links remain inert; they may be deleted.
sudo skips entries it cannot stat as regular files, and the base profile
sources only readable scripts.
Key constraints
- Do not ship
/usr/lib/os-release: it would replace the host's OS metadata. - Use a GPT/EROFS/directory-tree format accepted by systemd-sysext, containing
/usr/and/or/opt/. - For
/etc, put a confext in/var/lib/confexts/and usesystemd-confext mergeorsystemd-confext refresh. - Third-party extensions for other distributions can be forced only if they are pure userspace. Never bypass kernel-module version matching.
Debugging
systemd-sysext list # Discovered images
systemd-sysext status # Merge state and compatibility errors
# Debugging only; ignores version mismatches:
systemd-sysext merge --force
See also
- Upstream technical reference for build recipes, module-loader internals, and delivery implementation.
- Enabling extensions (ZFS, NVIDIA, k0s, kubeadm, Podman, Docker).
- k0s, kubeadm, and
- NVIDIA operator guides.
systemd-sysext(8),systemd-confext(8).