Files
diskless-workstation/scripts/build-oci.sh
T

132 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# build-oci.sh <artifact-dir> <image-ref> <version> [sb-key] [sb-cert] [push]
#
# Turns the extracted build artifacts into the fleet release unit:
# 1. ukify: vmlinuz + initramfs + cmdline(roothash) -> fleet-<version>.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-<version>.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 <artifact-dir> <image-ref> <version> [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
# Default buildah to the vfs storage driver. Inside a container (e.g. a CI
# runner) the host fs is overlayfs, and buildah's default 'overlay' driver
# can't stack overlay-on-overlay without fuse-overlayfs ("backing file
# system is unsupported"). vfs needs no overlay and is fine for this tiny
# FROM-scratch image. Override by exporting STORAGE_DRIVER on a build host
# with native overlay support.
export STORAGE_DRIVER="${STORAGE_DRIVER:-vfs}"
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 <<EOF
==> 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