Summary
Bluefin Server is currently installable only from physical/virtual media containing the full installer disk image (bluefin-server-installer-<ver>.raw.zst), where the OS DDI payload is embedded on the bluefin-installer-data partition. This makes it impossible to install over PXE/netboot, which is the standard provisioning path for homelabs and bare-metal fleets (the audience that a distroless, k3s-ready server image serves especially well).
This issue proposes two additive, backwards-compatible changes:
- Publish standalone PXE boot artifacts (kernel + installer initrd) as release assets, alongside the existing installer/DDI/UKI artifacts.
- Teach the installer to fetch the DDI over the network when directed to by kernel cmdline parameters, falling back to the existing embedded-partition behavior when absent.
With these in place, any PXE server (I'm building this against jeefy/booty, but it applies equally to netboot.xyz, Matchbox, dnsmasq+iPXE, etc.) can install Bluefin Server unattended over the LAN.
Motivation
- Fleet/homelab provisioning: PXE is how bare-metal Kubernetes nodes get imaged. Bluefin Server + k3s sysext is a natural fit for exactly this workflow, but today each machine needs hand-prepared USB/virtual media.
- No hardware media dance: Machines without easy USB access (rack servers via BMC, thin clients, mini-PC fleets) install straight from the network.
- Everything needed already exists in the build — the kernel and initrd are built today, they're just only consumed by
ukify and never exported. The DDI is already a standalone release asset. The gap is small.
Current State (as of bce0067d)
Boot artifacts are baked into the UKI only
In elements/oci/bluefin-server-installer.bst (~L252-257), the installer UKI is assembled from a kernel and cpio initrd that are never exported separately:
ukify build \
--linux="/layer/boot/vmlinuz" \
--initrd=/installer.cpio.gz \
--cmdline="systemd.unit=system-install.target console=tty0 console=ttyS0,115200 rw unattended" \
--output=/layer/boot/efi/EFI/BOOT/BOOTX64.EFI
PXE (via iPXE/GRUB) needs kernel + initrd as separate fetchable files with a caller-controlled cmdline. Chainloading the published .efi UKI directly is not sufficient because its cmdline is immutable — there is no way to inject a DDI URL (see below), and measured-boot-friendly UKIs are the point, so cmdline injection into the UKI would be the wrong direction anyway.
The installer cannot fetch the DDI over the network
The bluefin-sysinstall wrapper (installer.bst, ~L126-178) reads /proc/cmdline only to detect the unattended flag, and sources the DDI exclusively from the bluefin-installer-data GPT partition:
INSTALLER_PART="$(readlink -f /dev/disk/by-partlabel/bluefin-installer-data 2>/dev/null || true)"
Under PXE there is no installer media, therefore no bluefin-installer-data partition, and the install cannot proceed.
Proposal
Part 1: Publish standalone PXE artifacts
Export the two components that already exist at UKI-build time as release assets:
| New asset |
Source in build |
Purpose |
bluefin-server-pxe-vmlinuz-<ver> |
/layer/boot/vmlinuz |
PXE kernel |
bluefin-server-pxe-initrd-<ver>.cpio.gz |
/installer.cpio.gz |
PXE initrd |
Changes:
elements/oci/bluefin-server-installer.bst: copy both files into the export area alongside the UKI.
Justfile: new export-pxe recipe in the installer group.
.github/workflows/build.yml: upload both to the GitHub Release and include them in the (already GPG-signed) SHA256SUMS — consumers get artifact verification for free.
No new build work: these are byproducts of the existing installer build. The DDI (bluefin-server-ddi-<ver>.raw.zst) is already published, so no change is needed there.
Part 2: Network DDI pull in the installer
Extend bluefin-sysinstall (or better, the systemd-native machinery around it — see Design Discussion) to honor new kernel cmdline parameters:
| Parameter |
Required |
Behavior |
inst.ddi_url=<http(s) url> |
for netboot |
Download the (zstd-compressed) DDI instead of reading the embedded partition |
inst.ddi_sha256=<hex> |
recommended |
Verify the downloaded DDI before install; abort on mismatch |
inst.target_disk=/dev/... |
optional |
Explicit target disk, overriding the current first-writable-disk auto-detection |
Behavior:
- If
inst.ddi_url is absent → exactly today's behavior (embedded partition). Zero change for existing media users; offline-first design is preserved.
- If present → bring up networking (DHCP), fetch the DDI, verify SHA256 if provided, then feed it into the same
systemd-sysinstall/systemd-repart flow the embedded path uses.
- Download should stream-decompress (
zstd -d) rather than staging both compressed and decompressed copies in tmpfs, to keep RAM requirements sane. Minimum-RAM guidance should be documented either way.
- On any fetch/verification failure: fail loudly to console, do not touch the target disk.
The installer initrd would need networking + fetch tooling present in installer-stack.bst if it isn't already (systemd-networkd DHCP; fetch via importctl/systemd-pull or curl — see below).
Part 3: Documentation
- New
docs/skills/pxe-netboot.md: boot flow, cmdline parameter reference, example iPXE stanza, RAM requirements, troubleshooting (serial console, DHCP).
- Update
docs/skills/ddi-installer.md with a PXE section and cross-link.
- Add PXE/netboot to the
AGENTS.md skill routing table.
Design Discussion (maintainer input wanted)
1. Fetch mechanism: importctl / systemd-pull vs. curl.
AGENTS.md mandates the installer stay systemd-sysinstall-native with no custom scripting sprawl. systemd-importd's pull machinery (importctl pull-raw / systemd-pull) natively handles HTTP(S) download, zstd decompression, and SHA256SUMS+GPG verification — which matches the project's existing signed-manifest release format exactly. It seems like the most philosophically aligned option, at the cost of shipping importd in the installer initrd. Alternatively a small curl+zstd path in the existing bluefin-sysinstall wrapper is lighter but less "systemd-native." Which direction would you accept?
2. Cmdline namespace. inst.* follows Anaconda convention and is what PXE users will guess, but this project isn't Anaconda. Happy to use bluefin.install.* or sysinstall.* if preferred — bikeshed away.
3. UKI/measured-boot posture. Separate kernel+initrd PXE artifacts bypass the UKI's single-signed-object property. For a v1 targeting lab/fleet provisioning this seems acceptable (the installed system still boots its signed UKI), but if there's a desire to keep netboot within the UKI model long-term, UEFI HTTP boot of a netboot-variant UKI (with inst.ddi_url discovery via SMBIOS/DHCP instead of cmdline) could be a follow-up. I'd suggest not blocking v1 on it.
4. Per-host configuration (hostname, k3s join tokens, etc.) is explicitly out of scope here. A natural follow-up would be installer-fetched systemd credentials (inst.creds_url= or SMBIOS Type 11 OEM strings), which composes cleanly with this proposal.
Resulting PXE flow (for illustration)
#!ipxe
set base http://pxe-server:8080/data
kernel ${base}/bluefin-server-pxe-vmlinuz systemd.unit=system-install.target console=tty0 console=ttyS0,115200 rw unattended inst.ddi_url=${base}/bluefin-server-ddi.raw.zst inst.ddi_sha256=<sha256>
initrd ${base}/bluefin-server-pxe-initrd.cpio.gz
boot
The PXE server mirrors the three release assets (verified against the signed SHA256SUMS) and serves them over the LAN; the installer fetches the DDI locally, verifies, installs via the unchanged systemd-repart/bootctl path, and reboots into the installed OS.
Compatibility & Constraints
- Fully backwards compatible: no cmdline params → identical behavior to today. Existing installer media unaffected.
- Offline-first preserved: the embedded-DDI path remains the default; network pull is strictly opt-in.
- UEFI-only: unchanged — PXE clients boot via
ipxe.efi/UEFI netboot, consistent with the systemd-boot/UKI design.
- Baseline x86_64: unchanged; artifacts are the same binaries already built.
- Distroless target OS: untouched — all changes are installer-side.
Summary
Bluefin Server is currently installable only from physical/virtual media containing the full installer disk image (
bluefin-server-installer-<ver>.raw.zst), where the OS DDI payload is embedded on thebluefin-installer-datapartition. This makes it impossible to install over PXE/netboot, which is the standard provisioning path for homelabs and bare-metal fleets (the audience that a distroless, k3s-ready server image serves especially well).This issue proposes two additive, backwards-compatible changes:
With these in place, any PXE server (I'm building this against jeefy/booty, but it applies equally to netboot.xyz, Matchbox, dnsmasq+iPXE, etc.) can install Bluefin Server unattended over the LAN.
Motivation
ukifyand never exported. The DDI is already a standalone release asset. The gap is small.Current State (as of
bce0067d)Boot artifacts are baked into the UKI only
In
elements/oci/bluefin-server-installer.bst(~L252-257), the installer UKI is assembled from a kernel and cpio initrd that are never exported separately:PXE (via iPXE/GRUB) needs
kernel+initrdas separate fetchable files with a caller-controlled cmdline. Chainloading the published.efiUKI directly is not sufficient because its cmdline is immutable — there is no way to inject a DDI URL (see below), and measured-boot-friendly UKIs are the point, so cmdline injection into the UKI would be the wrong direction anyway.The installer cannot fetch the DDI over the network
The
bluefin-sysinstallwrapper (installer.bst, ~L126-178) reads/proc/cmdlineonly to detect theunattendedflag, and sources the DDI exclusively from thebluefin-installer-dataGPT partition:INSTALLER_PART="$(readlink -f /dev/disk/by-partlabel/bluefin-installer-data 2>/dev/null || true)"Under PXE there is no installer media, therefore no
bluefin-installer-datapartition, and the install cannot proceed.Proposal
Part 1: Publish standalone PXE artifacts
Export the two components that already exist at UKI-build time as release assets:
bluefin-server-pxe-vmlinuz-<ver>/layer/boot/vmlinuzkernelbluefin-server-pxe-initrd-<ver>.cpio.gz/installer.cpio.gzinitrdChanges:
elements/oci/bluefin-server-installer.bst: copy both files into the export area alongside the UKI.Justfile: newexport-pxerecipe in theinstallergroup..github/workflows/build.yml: upload both to the GitHub Release and include them in the (already GPG-signed)SHA256SUMS— consumers get artifact verification for free.No new build work: these are byproducts of the existing installer build. The DDI (
bluefin-server-ddi-<ver>.raw.zst) is already published, so no change is needed there.Part 2: Network DDI pull in the installer
Extend
bluefin-sysinstall(or better, the systemd-native machinery around it — see Design Discussion) to honor new kernel cmdline parameters:inst.ddi_url=<http(s) url>inst.ddi_sha256=<hex>inst.target_disk=/dev/...Behavior:
inst.ddi_urlis absent → exactly today's behavior (embedded partition). Zero change for existing media users; offline-first design is preserved.systemd-sysinstall/systemd-repartflow the embedded path uses.zstd -d) rather than staging both compressed and decompressed copies in tmpfs, to keep RAM requirements sane. Minimum-RAM guidance should be documented either way.The installer initrd would need networking + fetch tooling present in
installer-stack.bstif it isn't already (systemd-networkdDHCP; fetch viaimportctl/systemd-pullor curl — see below).Part 3: Documentation
docs/skills/pxe-netboot.md: boot flow, cmdline parameter reference, example iPXE stanza, RAM requirements, troubleshooting (serial console, DHCP).docs/skills/ddi-installer.mdwith a PXE section and cross-link.AGENTS.mdskill routing table.Design Discussion (maintainer input wanted)
1. Fetch mechanism:
importctl/systemd-pullvs. curl.AGENTS.mdmandates the installer stay systemd-sysinstall-native with no custom scripting sprawl.systemd-importd's pull machinery (importctl pull-raw/systemd-pull) natively handles HTTP(S) download, zstd decompression, and SHA256SUMS+GPG verification — which matches the project's existing signed-manifest release format exactly. It seems like the most philosophically aligned option, at the cost of shipping importd in the installer initrd. Alternatively a small curl+zstd path in the existingbluefin-sysinstallwrapper is lighter but less "systemd-native." Which direction would you accept?2. Cmdline namespace.
inst.*follows Anaconda convention and is what PXE users will guess, but this project isn't Anaconda. Happy to usebluefin.install.*orsysinstall.*if preferred — bikeshed away.3. UKI/measured-boot posture. Separate kernel+initrd PXE artifacts bypass the UKI's single-signed-object property. For a v1 targeting lab/fleet provisioning this seems acceptable (the installed system still boots its signed UKI), but if there's a desire to keep netboot within the UKI model long-term, UEFI HTTP boot of a netboot-variant UKI (with
inst.ddi_urldiscovery via SMBIOS/DHCP instead of cmdline) could be a follow-up. I'd suggest not blocking v1 on it.4. Per-host configuration (hostname, k3s join tokens, etc.) is explicitly out of scope here. A natural follow-up would be installer-fetched systemd credentials (
inst.creds_url=or SMBIOS Type 11 OEM strings), which composes cleanly with this proposal.Resulting PXE flow (for illustration)
The PXE server mirrors the three release assets (verified against the signed
SHA256SUMS) and serves them over the LAN; the installer fetches the DDI locally, verifies, installs via the unchanged systemd-repart/bootctl path, and reboots into the installed OS.Compatibility & Constraints
ipxe.efi/UEFI netboot, consistent with the systemd-boot/UKI design.