#!/usr/bin/env bash # build-oci.sh [sb-key] [sb-cert] [push] # # Turns the extracted build artifacts into the fleet release unit: # 1. ukify: vmlinuz + initramfs + cmdline(roothash) -> fleet-.efi # 2. sbsign via ukify if a Secure Boot key/cert pair is supplied # (key should live in CI/HSM -- it is deliberately NEVER inside the # Packer build VM, which is why this runs on the build host) # 3. consistency gate: roothash inside the UKI cmdline must match # verity.json -- a mismatched pair is an unbootable fleet # 4. builds a FROM-scratch OCI image containing ONLY: # /artifacts/fleet-.efi (the signed UKI) # /artifacts/rootfs.erofs (erofs + appended verity tree) # /artifacts/verity.json # /artifacts/SHA256SUMS # Deploy as an initContainer (or k8s image volume) feeding the # SPDK + nginx serving containers. Pin deployments by digest. # # Requires on the build host: systemd-ukify, sbsigntools (if signing), # jq, and buildah (preferred) or docker. set -euo pipefail ART=${1:?usage: build-oci.sh [sb-key] [sb-cert] [push]} IMAGE=${2:?image ref required, e.g. registry.example.org/fleet-boot} VERSION=${3:?version required} SB_KEY=${4:-} SB_CERT=${5:-} PUSH=${6:-false} for bin in ukify jq; do command -v "$bin" >/dev/null || { echo "ERROR: $bin not found on build host" >&2; exit 1; } done UKI="$ART/fleet-${VERSION}.efi" CMDLINE=$(cat "$ART/cmdline.txt") if printf '%s' "$CMDLINE" | grep -q NVMET_TRADDR; then echo "WARNING: cmdline still contains the NVMET_TRADDR placeholder." >&2 echo " Substitute the real target address before a production build;" >&2 echo " the cmdline is immutable once signed into the UKI." >&2 fi # --- 1+2. Build (and sign) the UKI ------------------------------------------- # os-release stamped into the UKI's .osrel section (identifies the image to # bootctl / boot menus). ukify takes this as inline text; a value starting with # '@' is instead read from a file of that name (newline-separated KEY=VALUE). OSREL=$(printf 'NAME=FleetDiskless\nID=fleet-diskless\nVERSION_ID=%s\nPRETTY_NAME="Fleet Diskless %s"\n' "$VERSION" "$VERSION") UKIFY_ARGS=(build --linux "$ART/vmlinuz" --initrd "$ART/initramfs.img" --cmdline "$CMDLINE" --os-release "$OSREL" --output "$UKI") if [ -n "$SB_KEY" ] && [ -n "$SB_CERT" ]; then command -v sbsign >/dev/null || { echo "ERROR: sbsigntools required for signing" >&2; exit 1; } UKIFY_ARGS+=(--signtool sbsign --secureboot-private-key "$SB_KEY" --secureboot-certificate "$SB_CERT") else echo "WARNING: building UNSIGNED UKI (no sb-key/sb-cert supplied)." >&2 echo " Fine for the lab; a production fleet must sign in CI/HSM." >&2 fi ukify "${UKIFY_ARGS[@]}" # --- 3. Roothash coupling gate ------------------------------------------------ WANT=$(jq -r .root_hash "$ART/verity.json") GOT=$(printf '%s' "$CMDLINE" | grep -o 'roothash=[0-9a-f]*' | cut -d= -f2) if [ "$WANT" != "$GOT" ]; then echo "FATAL: roothash in UKI cmdline ($GOT) != verity.json ($WANT)." >&2 echo " Refusing to build a mismatched (unbootable) release image." >&2 exit 1 fi echo "==> roothash coupling verified: $WANT" # --- 4. Build the OCI release-artifact image ---------------------------------- STAGE=$(mktemp -d) trap 'rm -rf "$STAGE"' EXIT mkdir "$STAGE/artifacts" cp "$UKI" "$ART/rootfs.erofs" "$ART/verity.json" "$STAGE/artifacts/" ( cd "$STAGE/artifacts" && sha256sum -- * > SHA256SUMS ) REF="${IMAGE}:${VERSION}" LABELS=( "org.opencontainers.image.title=fleet-boot-artifacts" "org.opencontainers.image.version=${VERSION}" "org.fleet.roothash=${WANT}" "org.fleet.uki=fleet-${VERSION}.efi" ) if command -v buildah >/dev/null; then ctr=$(buildah from scratch) buildah copy "$ctr" "$STAGE/artifacts" /artifacts for l in "${LABELS[@]}"; do buildah config --label "$l" "$ctr"; done buildah commit --format oci --rm "$ctr" "$REF" DIGEST=$(buildah inspect --type image --format '{{.FromImageDigest}}' "$REF" || true) [ "$PUSH" = "true" ] && buildah push "$REF" elif command -v docker >/dev/null; then { printf 'FROM scratch\nCOPY artifacts /artifacts\n' for l in "${LABELS[@]}"; do printf 'LABEL "%s"\n' "$l"; done } > "$STAGE/Dockerfile" docker build -t "$REF" "$STAGE" DIGEST=$(docker inspect --format '{{index .RepoDigests 0}}' "$REF" 2>/dev/null || true) [ "$PUSH" = "true" ] && docker push "$REF" else echo "ERROR: need buildah or docker to build the OCI image" >&2; exit 1 fi cat < Release image built: $REF roothash: $WANT digest: ${DIGEST:-"(available after push)"} Next steps: - cosign sign the image for supply-chain provenance (the UKI's sbsign signature is for firmware; cosign is for your deployment pipeline) - update the fleet-boot Deployment/compose manifest in git to this version's DIGEST (one commit per fleet release; that manifest is the single place coupling UKI <-> root namespace) EOF