Files
diskless-workstation/.gitea/workflows/ipxe-image.yaml
T
2026-06-21 09:18:22 +00:00

159 lines
6.5 KiB
YAML

#
# .gitea/workflows/ipxe-image.yaml
#
# Builds the fleet-ipxe image: the iPXE network bootloader (snponly.efi) for the
# PXE-only chainload path (firmware without UEFI HTTP boot, e.g. EliteBook 820
# G4), SIGNED with the fleet `db` key and packaged as a tiny FROM-scratch OCI
# image. The dnsmasq serving container mounts it at /ipxe and copies snponly.efi
# into its TFTP root — so the serving host never carries a loose, unsigned .efi
# and the bootloader is digest-pinned + signed exactly like the UKI.
#
# Kept separate from the (generic) dnsmasq image so the daemon image stays
# key-agnostic and this one is the only per-Secure-Boot-environment artifact.
# See scripts/build-ipxe-oci.sh, docs/secure-boot-keys.md, CLAUDE.md invariant #9.
#
# Tagging mirrors gitea-main-ci.yaml so every branch builds a uniquely tagged,
# pushed image you can test without clobbering main:
# - tag push (refs/tags/v1.2.3) -> "1.2.3" (+ :latest)
# - main push (refs/heads/main) -> "<ipxe-pkg-ver>" (+ :latest)
# - branch push (refs/heads/<x>) -> "<x>-<sha7>" (dev image, no :latest)
# - dispatch input always wins.
# Set IPXE_VERSION in serving/.env to the tag you want to run.
#
# Runs only when the iPXE inputs change (path filter) or on demand.
#
# Required Gitea repo secrets: PACKAGE_USER, PACKAGE_TOKEN (registry creds).
# Optional: SB_KEY, SB_CERT (db PRIVATE key + cert, PEM). Unset => UNSIGNED lab
# image (loud warning; Secure-Boot-enforcing hosts will reject it). PK/KEK
# never come near CI — only `db` signs (docs/secure-boot-keys.md).
#
name: Build fleet-ipxe image
run-name: ${{ gitea.actor }} builds the signed fleet-ipxe bootloader image (${{ gitea.ref_name }})
on:
push:
branches:
- "**" # every branch — dev branches get a branch-name+sha tag
tags:
- "v*" # tag push -> clean semver tag (v1.2.3 -> 1.2.3)
paths:
- "scripts/build-ipxe-oci.sh"
- ".gitea/workflows/ipxe-image.yaml"
workflow_dispatch:
inputs:
ipxe_version:
description: "Override the image tag (default: sanitized ipxe package version)"
required: false
concurrency:
group: ipxe-image-${{ gitea.ref }}
cancel-in-progress: true
env:
IPXE_IMAGE: "git.bitasylum.org/audiobuzz/fleet-ipxe"
jobs:
ipxe-build:
name: Build & push fleet-ipxe image
runs-on: ubuntu-latest
container:
image: git.bitasylum.org/audiobuzz/ubuntu-act-runner-qemu:main
steps:
-
name: Checkout
uses: actions/checkout@v6
-
name: Install tooling
# buildah (OCI build), sbsigntool (sbsign/sbverify — Debian singular), and
# the ipxe package which ships the prebuilt snponly.efi.
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends buildah sbsigntool ipxe
-
name: Locate snponly.efi + derive tag
id: src
run: |
# snponly.efi: UEFI x64 iPXE that rides the firmware's NIC driver (SNP),
# so it binds where iPXE's native driver won't (e.g. I219-LM). Ubuntu's
# ipxe package installs it under /usr/lib/ipxe.
SNP=$(find /usr/lib/ipxe /usr/share/ipxe -name 'snponly.efi' 2>/dev/null | head -1)
[ -n "$SNP" ] || { echo "snponly.efi not found in the ipxe package" >&2; exit 1; }
echo "snponly=$SNP" >> "$GITHUB_OUTPUT"
# Tag scheme (same as gitea-main-ci.yaml, with the ipxe pkg version as the
# main-branch identifier). :latest moves only on main / release tags.
short_sha="$(echo "${GITHUB_SHA}" | cut -c1-7)"
RAW=$(dpkg-query -W -f='${Version}' ipxe 2>/dev/null || echo unknown)
PKGVER=$(printf '%s' "$RAW" | sed 's/[^A-Za-z0-9_.-]/-/g')
push_latest=false
if [ -n "${{ inputs.ipxe_version }}" ]; then
VER="${{ inputs.ipxe_version }}"
elif [ "${GITHUB_REF#refs/tags/}" != "$GITHUB_REF" ]; then
VER="${GITHUB_REF#refs/tags/}"; VER="${VER#v}"; push_latest=true
elif [ "${GITHUB_REF}" = "refs/heads/main" ]; then
VER="$PKGVER"; push_latest=true
else
branch="${GITHUB_REF#refs/heads/}"
safe_branch=$(printf '%s' "$branch" \
| sed -e 's#[^A-Za-z0-9._-]#-#g' -e 's#^[.-]*##' | cut -c1-80)
[ -n "$safe_branch" ] || safe_branch="branch"
VER="${safe_branch}-${short_sha}"
fi
echo "version=$VER" >> "$GITHUB_OUTPUT"
echo "push_latest=$push_latest" >> "$GITHUB_OUTPUT"
echo "Using snponly.efi=$SNP tag=$VER (ipxe pkg $RAW, latest=$push_latest)"
-
name: Materialize Secure Boot signing material
# Same db key that signs the UKI; written to a runner-temp file and passed
# to build-ipxe-oci.sh as a path. If SB_KEY is unset this no-ops and the
# build produces an UNSIGNED (lab) image with its loud warning.
if: ${{ secrets.SB_KEY != '' }}
env:
SB_KEY: ${{ secrets.SB_KEY }}
SB_CERT: ${{ secrets.SB_CERT }}
run: |
umask 077
d="$RUNNER_TEMP/sb"; mkdir -p "$d"
printf '%s\n' "$SB_KEY" > "$d/db.key"
printf '%s\n' "$SB_CERT" > "$d/db.crt"
echo "SB_KEY_FILE=$d/db.key" >> "$GITHUB_ENV"
echo "SB_CERT_FILE=$d/db.crt" >> "$GITHUB_ENV"
echo "Secure Boot signing material staged (signed fleet-ipxe build)."
-
name: Registry login
run: |
buildah login \
--username "${{ secrets.PACKAGE_USER }}" \
--password "${{ secrets.PACKAGE_TOKEN }}" \
git.bitasylum.org
-
name: Build & push
env:
STORAGE_DRIVER: vfs
run: |
VER="${{ steps.src.outputs.version }}"
bash scripts/build-ipxe-oci.sh \
"${{ steps.src.outputs.snponly }}" \
"${IPXE_IMAGE}" \
"$VER" \
"${SB_KEY_FILE:-}" \
"${SB_CERT_FILE:-}" \
true
# main / release tags also move :latest; dev branches never clobber it.
if [ "${{ steps.src.outputs.push_latest }}" = "true" ]; then
buildah tag "${IPXE_IMAGE}:${VER}" "${IPXE_IMAGE}:latest"
buildah push "${IPXE_IMAGE}:latest"
echo "Updated ${IPXE_IMAGE}:latest"
else
echo "Dev branch build — :latest left untouched (run with IPXE_VERSION=${VER})"
fi
-
name: Shred Secure Boot signing key
if: always()
run: shred -u "$RUNNER_TEMP/sb/db.key" 2>/dev/null || rm -f "$RUNNER_TEMP/sb/db.key" 2>/dev/null || true