# # .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) -> "" (+ :latest) # - branch push (refs/heads/) -> "-" (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