Files
SilverMetal/linux/build/scripts/build.sh
SysAdmin 4444dc11f3 feat(linux/build): scaffold reproducible ISO build pipeline (M1.1)
Vendors Kicksecure derivative-maker as a pinned submodule (18.1.7.4),
adds the wrapper + verify + diagnose scripts, the pinned builder image,
and the reproducibility-gated Gitea Actions workflow. Base flavour only —
no hardening overlay (that's M1.2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 04:25:48 +01:00

141 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# SilverMetal Linux — ISO build wrapper.
#
# Runs the Kicksecure derivative-maker inside the pinned builder container
# with the reproducibility levers locked down. This script is the single
# entry point for both local developer builds and CI — there is no separate
# CI-only path. If you need to debug, run *this*, not lb directly.
#
# Usage:
# linux/build/scripts/build.sh # writes to linux/build/output/<commit>
# BUILD_DIR=/tmp/build-a linux/build/scripts/build.sh # override output root
#
# Exit codes:
# 0 ISO produced and SHA256SUMS written
# 1 argument / environment error
# 2 derivative-maker submodule missing
# 3 build failed
# 4 post-build hash/manifest step failed
set -euo pipefail
# --- Locate repo root -------------------------------------------------------
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../../.." && pwd)"
cd "${REPO_ROOT}"
# --- Pinned builder image ---------------------------------------------------
# Bumped together with linux/build/docker/Dockerfile.builder. The digest form
# is required; refusing the tag-only form is what stops a silent host drift.
BUILDER_IMAGE="${BUILDER_IMAGE:-docker-registry:5000/silvermetal-builder@sha256:REPLACE_WITH_PUSHED_DIGEST}"
if [[ "${BUILDER_IMAGE}" != *"@sha256:"* ]]; then
echo "build.sh: BUILDER_IMAGE must be pinned by digest, got: ${BUILDER_IMAGE}" >&2
exit 1
fi
# --- Sanity: submodule present ---------------------------------------------
if [[ ! -f "linux/build/derivative-maker/.git" && ! -d "linux/build/derivative-maker/.git" ]]; then
echo "build.sh: linux/build/derivative-maker submodule is not initialised." >&2
echo " Run: git submodule update --init --recursive" >&2
exit 2
fi
# --- Compute SOURCE_DATE_EPOCH ---------------------------------------------
# Order of preference:
# 1. Explicit env var passed in (CI may set it for cross-runner consistency)
# 2. config/source-date-epoch.env override (offline rebuilds)
# 3. git commit timestamp of HEAD (default)
# shellcheck disable=SC1091
source linux/build/config/source-date-epoch.env || true
if [[ -z "${SOURCE_DATE_EPOCH:-}" ]]; then
if [[ -n "${SOURCE_DATE_EPOCH_OVERRIDE:-}" ]]; then
SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH_OVERRIDE}"
echo "build.sh: using SOURCE_DATE_EPOCH override = ${SOURCE_DATE_EPOCH}"
else
SOURCE_DATE_EPOCH="$(git log -1 --pretty=%ct)"
fi
fi
export SOURCE_DATE_EPOCH
# --- Pinned snapshot timestamp ---------------------------------------------
# shellcheck disable=SC1091
source linux/build/config/snapshot-pin.env
export SNAPSHOT_TIMESTAMP
# --- Resolve commit & output dir -------------------------------------------
COMMIT_SHA="$(git rev-parse --short=12 HEAD)"
BUILD_DIR="${BUILD_DIR:-${REPO_ROOT}/linux/build/output/${COMMIT_SHA}}"
mkdir -p "${BUILD_DIR}"
echo "build.sh: commit=${COMMIT_SHA} epoch=${SOURCE_DATE_EPOCH} snapshot=${SNAPSHOT_TIMESTAMP}"
echo "build.sh: output -> ${BUILD_DIR}"
# --- Run the build inside the container ------------------------------------
# --privileged is required because live-build mounts loop devices and chroots.
# --network=host lets the container reach snapshot.debian.org without us
# fighting CI proxy config; tighten if/when that becomes a concern.
docker run --rm --privileged \
--network=host \
-e SOURCE_DATE_EPOCH \
-e SNAPSHOT_TIMESTAMP \
-e LC_ALL=C.UTF-8 \
-e LANG=C.UTF-8 \
-e TZ=UTC \
-v "${REPO_ROOT}:/work:rw" \
-v "${BUILD_DIR}:/out:rw" \
-w /work \
"${BUILDER_IMAGE}" \
bash -euo pipefail -c '
# shellcheck disable=SC1091
source /work/linux/build/config/silvermetal-base.conf
cd /work/linux/build/derivative-maker
./derivative-maker \
--build \
--target "${DERIVATIVE_BUILD_TARGET}" \
--flavour "${DERIVATIVE_FLAVOUR}" \
--arch "${DERIVATIVE_TARGET_ARCH}" \
--dist "${DERIVATIVE_DIST}" \
--config /work/linux/build/config/silvermetal-base.conf
# derivative-maker writes into its own build/ dir; collect into /out.
# Exact upstream output paths can shift between tags — keep this
# tolerant. Anything matching *.iso under the tree is what we want.
find . -maxdepth 6 -type f -name "*.iso" -print0 \
| xargs -0 -I{} cp -av "{}" /out/
# Manifest of file metadata that lives inside the ISO. Useful when
# diagnosing reproducibility regressions without re-extracting.
find . -maxdepth 6 -type f -name "*.manifest" -print0 \
| xargs -0 -I{} cp -av "{}" /out/ 2>/dev/null || true
' || { echo "build.sh: derivative-maker failed"; exit 3; }
# --- Hash artefacts ---------------------------------------------------------
# Run hashing on the host (not in the container) so a busted container image
# can't tamper with the digests we publish.
shopt -s nullglob
ISO_FILES=("${BUILD_DIR}"/*.iso)
shopt -u nullglob
if (( ${#ISO_FILES[@]} == 0 )); then
echo "build.sh: no ISO produced in ${BUILD_DIR}" >&2
exit 4
fi
(
cd "${BUILD_DIR}"
sha256sum -- *.iso > SHA256SUMS
cp -- "${REPO_ROOT}/linux/build/config/snapshot-pin.env" snapshot-pin.env
{
echo "commit=${COMMIT_SHA}"
echo "source_date_epoch=${SOURCE_DATE_EPOCH}"
echo "snapshot_timestamp=${SNAPSHOT_TIMESTAMP}"
echo "builder_image=${BUILDER_IMAGE}"
echo "host_uname=$(uname -srm)"
} > BUILD_INFO
)
echo "build.sh: SHA256SUMS:"
cat "${BUILD_DIR}/SHA256SUMS"