From c166a19b73487793dd99a3c9a4afb7a01f0f72ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 08:43:41 -0400 Subject: [PATCH] [scanner] fix: add PXE netboot installer support Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- AGENTS.md | 1 + Justfile | 2 +- docs/skills/ddi-installer.md | 12 ++-- docs/skills/index.md | 1 + docs/skills/pxe-netboot.md | 33 ++++++++++ elements/installer/installer-stack.bst | 7 ++- elements/oci/bluefin-server-installer.bst | 74 ++++++++++++++++++----- 7 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 docs/skills/pxe-netboot.md diff --git a/AGENTS.md b/AGENTS.md index c5593d0..6328cb9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,6 +40,7 @@ All `just` targets run BuildStream inside the FSDK `bst2` container via `just bs | Task | Skill | |---|---| | Build or debug the installer / DDI | [`docs/skills/ddi-installer.md`](docs/skills/ddi-installer.md), [`docs/skills/ddi-installer-build.md`](docs/skills/ddi-installer-build.md) | +| PXE/netboot installer deployment | [`docs/skills/pxe-netboot.md`](docs/skills/pxe-netboot.md) | | Factory role, k3s sysext rationale, lab integration | [`docs/skills/factory-integration.md`](docs/skills/factory-integration.md) | | Work with `systemd-sysext` / `systemd-confext` | [`docs/skills/systemd-sysext-extensions.md`](docs/skills/systemd-sysext-extensions.md) | | Build or ship the k3s sysext | [`docs/skills/k3s-sysext.md`](docs/skills/k3s-sysext.md), [`docs/skills/k3s-sysext-ops.md`](docs/skills/k3s-sysext-ops.md) | diff --git a/Justfile b/Justfile index 1e91ee1..d174c31 100644 --- a/Justfile +++ b/Justfile @@ -109,7 +109,7 @@ cluster-build REF="main": export-installer: build-installer rm -rf dist/installer-checkout mkdir -p dist dist/installer-checkout - rm -f dist/bluefin-server-installer-*.raw.zst dist/bluefin-server-*.efi dist/SHA256SUMS + rm -f dist/bluefin-server-installer-*.raw.zst dist/bluefin-server-*.efi dist/bluefin-server-pxe-* dist/SHA256SUMS just bst artifact checkout oci/bluefin-server-installer.bst --directory /src/dist/installer-checkout mv dist/installer-checkout/* dist/ rm -rf dist/installer-checkout diff --git a/docs/skills/ddi-installer.md b/docs/skills/ddi-installer.md index 6f36b1d..10002ce 100644 --- a/docs/skills/ddi-installer.md +++ b/docs/skills/ddi-installer.md @@ -24,14 +24,14 @@ metadata: - OCI-only image work (no installer involvement). - Bootc-specific changes. - Desktop or nspawn machine image work. -- Adding a network-pull installer — the design is offline; the DDI is embedded as - a data partition. +- PXE/netboot deployment — see [pxe-netboot.md](pxe-netboot.md). ## Architecture -The installer is offline, self-contained, and systemd-native. The OS DDI payload -(`bluefin-server-ddi.bst`) is embedded as a data partition on the installer -media at build time. No network access is required at install time. +The default installer is offline, self-contained, and systemd-native. The OS DDI +payload (`bluefin-server-ddi.bst`) is embedded as a data partition on installer +media at build time. PXE boot can opt into fetching the same DDI over HTTP(S); +see [pxe-netboot.md](pxe-netboot.md). The installer UI is systemd's built-in `systemd-sysinstall` which provides a terminal-based interactive installation that: @@ -142,7 +142,7 @@ For the detailed build/export/flash/release workflow, see | "A bash script is simpler." | A bash script cannot run the systemd-native interactive installer TUI. Use `systemd-sysinstall`. | | "Use knuckle instead." | knuckle is deprecated in favor of native `systemd-sysinstall` (systemd 261+). | | "Hardcode `root=/dev/vda2` for QEMU." | Bare metal has different device names. Always use PARTUUID. | -| "Pull the DDI from the network at install time." | Network failures = broken installs. The DDI is embedded in the installer media. | +| "Pull the DDI from the network at install time." | Only PXE boot enables this; failures stop before target-disk installation. | | "Put the DDI in the initrd cpio." | The DDI is 2 GiB+. The initrd cpio step must run before the DDI is placed in `/layer`. | | "Store the DDI in the ESP (FAT32)." | FAT32 has a 4 GiB per-file limit. Use a separate XFS partition. | | "Add an 8 GiB minimum size floor to the DDI." | The rootfs is immutable. It never grows in-place. Content + overhead is enough. | diff --git a/docs/skills/index.md b/docs/skills/index.md index 8382e0a..0e85449 100644 --- a/docs/skills/index.md +++ b/docs/skills/index.md @@ -27,6 +27,7 @@ This is the lazy-load routing table for agent skills. Keep this file in memory w | [`ci-tooling.md`](ci-tooling.md) | GitHub Actions, workflow SHA pinning, CI conventions | CI conventions and release pipeline rules. | | [`ddi-installer-build.md`](ddi-installer-build.md) | Building the installer or DDI on the cluster | Cluster build pipeline and local installer/DDI build. | | [`ddi-installer.md`](ddi-installer.md) | Installer boot flow, `systemd-sysinstall`, `systemd-repart` | High-level DDI install architecture and local smoke test. | +| [`pxe-netboot.md`](pxe-netboot.md) | Network booting the installer | PXE artifacts, kernel command line, and network DDI fetch. | | [`factory-integration.md`](factory-integration.md) | Lab integration, boot-test workflow, factory role | How Bluefin Server is consumed by the CI lab. | | [`gap-analysis-distros.md`](gap-analysis-distros.md) | Comparing Bluefin Server to other server OSes | Source-verified comparison to Ubuntu, Talos, Flatcar, FCOS. | | [`k3s-sysext-ops.md`](k3s-sysext-ops.md) | Building the k3s sysext | BuildStream element and publish steps for the k3s sysext. | diff --git a/docs/skills/pxe-netboot.md b/docs/skills/pxe-netboot.md new file mode 100644 index 0000000..bf405f7 --- /dev/null +++ b/docs/skills/pxe-netboot.md @@ -0,0 +1,33 @@ +--- +name: pxe-netboot +description: Use when provisioning Bluefin Server over PXE or iPXE. +metadata: + type: reference + status: stable +--- +# PXE/netboot installer + +Releases include standalone `bluefin-server-pxe-vmlinuz-` and +`bluefin-server-pxe-initrd-.cpio.gz` assets. The matching DDI +(`bluefin-server-ddi-.raw.zst`) is fetched only when `inst.ddi_url` +is supplied; normal installer media remains offline and uses its embedded DDI. + +Example iPXE configuration: + +```ipxe +#!ipxe +set base http://pxe.example/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= inst.target_disk=/dev/sda +initrd ${base}/bluefin-server-pxe-initrd-.cpio.gz +boot +``` + +`inst.ddi_url` must use HTTP(S). The compressed DDI is unpacked into the +installer runtime; `inst.ddi_sha256` is recommended and is checked before +installation. `inst.target_disk` selects a whole-disk block device. Without +it, unattended mode selects the first writable disk, as with USB media. + +Fetch or checksum failures stop installation before the target disk is +modified. Mirror release assets and verify them against the signed +`SHA256SUMS` manifest. Provide DHCP and DNS (or a reachable IP URL), and use +the serial console arguments above for headless systems. diff --git a/elements/installer/installer-stack.bst b/elements/installer/installer-stack.bst index de1ab3a..f654048 100644 --- a/elements/installer/installer-stack.bst +++ b/elements/installer/installer-stack.bst @@ -8,8 +8,8 @@ description: | bluefin-server-installer.bst. This keeps the toolchain minimal and removes the dracut dependency chain. - The DDI payload is embedded in the installer media (offline install). - No network access required at install time. + The DDI payload is embedded in installer media by default. PXE boot can + opt into fetching it over HTTP(S). depends: # Base runtime @@ -27,6 +27,9 @@ depends: - freedesktop-sdk.bst:components/cryptsetup.bst - freedesktop-sdk.bst:components/xfsprogs.bst - freedesktop-sdk.bst:components/dosfstools.bst + # PXE installer support: fetch and unpack an opt-in network DDI. + - freedesktop-sdk.bst:components/curl.bst + - freedesktop-sdk.bst:components/zstd.bst # Kernel (installer boots from this; vmlinuz used as the UKI --linux= arg) - freedesktop-sdk.bst:components/linux.bst diff --git a/elements/oci/bluefin-server-installer.bst b/elements/oci/bluefin-server-installer.bst index d4c85e8..e6489a3 100644 --- a/elements/oci/bluefin-server-installer.bst +++ b/elements/oci/bluefin-server-installer.bst @@ -4,8 +4,8 @@ description: | Output: bluefin-server-installer-.raw.zst + SHA256SUMS - Offline installer: the OS DDI payload is embedded as a data partition on - the installer media. No network required at install time. + Default offline installer: the OS DDI payload is embedded as a data partition + on the installer media. PXE boot can opt into fetching the DDI over HTTP(S). Initrd approach: dracut-free, cpio-native. The installer-stack rootfs is packed directly into a cpio.zst initrd using @@ -127,26 +127,64 @@ config: #!/bin/bash set -euo pipefail - # Auto-detect target disk for unattended install (any raw disk of type "disk" that is not the installer, is not read-only, and has size > 0) - TARGET_DISK="" + CMDLINE="$(cat /proc/cmdline 2>/dev/null || true)" + get_cmdline_value() { + local key="$1" arg + for arg in ${CMDLINE}; do + case "${arg}" in + "${key}"=*) printf '%s' "${arg#*=}"; return 0 ;; + esac + done + return 1 + } + INSTALLER_PART="$(readlink -f /dev/disk/by-partlabel/bluefin-installer-data 2>/dev/null || true)" + DDI_URL="$(get_cmdline_value inst.ddi_url || true)" + DDI_SHA256="$(get_cmdline_value inst.ddi_sha256 || true)" + if [ -n "${DDI_URL}" ]; then + case "${DDI_URL}" in + http://*|https://*) ;; + *) echo "ERROR: inst.ddi_url must use http:// or https://" >&2; exit 1 ;; + esac + DDI_COMPRESSED="/run/installer/bluefin-server-ddi.raw.zst" + DDI_RAW="/run/installer/bluefin-server-ddi.raw" + echo "==> Fetching DDI from ${DDI_URL}..." + mkdir -p /run/installer /dev/disk/by-partlabel + curl --fail --location --retry 3 --output "${DDI_COMPRESSED}" "${DDI_URL}" + if [ -n "${DDI_SHA256}" ]; then + printf '%s %s\n' "${DDI_SHA256}" "${DDI_COMPRESSED}" | sha256sum --check --status - + echo "==> DDI checksum verified." + fi + zstd --decompress --stdout "${DDI_COMPRESSED}" > "${DDI_RAW}" + rm -f "${DDI_COMPRESSED}" + ln -sfn "${DDI_RAW}" /dev/disk/by-partlabel/bluefin-installer-data + INSTALLER_PART="" + fi + + # Auto-detect target disk for unattended install (any raw disk of type "disk" that is not the installer, is not read-only, and has size > 0) + TARGET_DISK="$(get_cmdline_value inst.target_disk || true)" + if [ -n "${TARGET_DISK}" ] && { [ ! -b "${TARGET_DISK}" ] || [ "$(lsblk -ndo TYPE "${TARGET_DISK}")" != "disk" ] || [ "$(lsblk -ndo RO "${TARGET_DISK}")" != "0" ]; }; then + echo "ERROR: inst.target_disk is not a valid whole-disk block device: ${TARGET_DISK}" >&2 + exit 1 + fi - while read -r name type ro size; do - [ "${type}" = "disk" ] || continue - [ "${ro}" = "0" ] || continue - [ "${size}" -gt 0 ] || continue + if [ -z "${TARGET_DISK}" ]; then + while read -r name type ro size; do + [ "${type}" = "disk" ] || continue + [ "${ro}" = "0" ] || continue + [ "${size}" -gt 0 ] || continue - # Skip the disk containing the installer partition - if [ -n "${INSTALLER_PART}" ] && [[ "${INSTALLER_PART}" == "${name}"* ]]; then - continue - fi + # Skip the disk containing the installer partition + if [ -n "${INSTALLER_PART}" ] && [[ "${INSTALLER_PART}" == "${name}"* ]]; then + continue + fi - TARGET_DISK="${name}" - break - done < <(lsblk -p -d -n -o NAME,TYPE,RO,SIZE -b) + TARGET_DISK="${name}" + break + done < <(lsblk -p -d -n -o NAME,TYPE,RO,SIZE -b) + fi # Check for unattended in kernel command line - CMDLINE="$(cat /proc/cmdline 2>/dev/null || true)" if [[ " ${CMDLINE} " == *" unattended "* ]]; then echo "==> Running in UNATTENDED mode..." if [ -n "${TARGET_DISK}" ]; then @@ -316,7 +354,11 @@ config: # Also export the target UKI (.efi) for GitHub Releases and systemd-sysupdate cp /layer/usr/lib/bluefin-server/bluefin-server.efi "bluefin-server-%{installer-version}.efi" + cp /layer/boot/vmlinuz "bluefin-server-pxe-vmlinuz-%{installer-version}" + cp /installer.cpio.gz "bluefin-server-pxe-initrd-%{installer-version}.cpio.gz" sha256sum --binary "${FNAME}.zst" > SHA256SUMS sha256sum --binary "bluefin-server-%{installer-version}.efi" >> SHA256SUMS + sha256sum --binary "bluefin-server-pxe-vmlinuz-%{installer-version}" >> SHA256SUMS + sha256sum --binary "bluefin-server-pxe-initrd-%{installer-version}.cpio.gz" >> SHA256SUMS ls -lh