132 lines
5.5 KiB
Bash
Executable File
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
|