diff --git a/linux/build/docker/Dockerfile.builder b/linux/build/docker/Dockerfile.builder index 6bbecb8..8924cd4 100644 --- a/linux/build/docker/Dockerfile.builder +++ b/linux/build/docker/Dockerfile.builder @@ -53,6 +53,7 @@ RUN set -eux; \ debootstrap \ diffoscope-minimal \ dosfstools \ + fakeroot \ git \ gnupg \ isolinux \ @@ -61,15 +62,21 @@ RUN set -eux; \ reprepro \ rsync \ squashfs-tools \ + sudo \ syslinux-common \ xorriso; \ apt-get clean; \ rm -rf /var/lib/apt/lists/* -# Non-root user for the parts of the build that don't need privilege. -# live-build itself still needs root inside the container for chroot/mount, -# so build.sh runs the container as root; this user exists for diagnostic -# tooling and matches uid 1000 to play nicely with bind mounts. -RUN useradd --uid 1000 --create-home --shell /bin/bash builder +# Non-root user for derivative-maker. +# Kicksecure's derivative-maker explicitly refuses to run as root and uses +# sudo internally for its privileged operations (debootstrap, mksquashfs, +# chroot mounts). build.sh chowns the workspace to this user inside the +# container, then runuser's to it before invoking derivative-maker. +# uid 1000 is conventional and plays nicely with bind mounts of files +# created by other Linux tools. +RUN useradd --uid 1000 --create-home --shell /bin/bash builder \ + && echo 'builder ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/builder \ + && chmod 440 /etc/sudoers.d/builder WORKDIR /work diff --git a/linux/build/scripts/build-inner.sh b/linux/build/scripts/build-inner.sh new file mode 100644 index 0000000..3975f97 --- /dev/null +++ b/linux/build/scripts/build-inner.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# SilverMetal Linux — inner build step. +# +# Runs *inside* the silvermetal-builder container, as the unprivileged +# `builder` user. build.sh sets up the container, chowns the workspace, +# and runuser's into here. derivative-maker takes it from there and uses +# sudo internally for its privileged operations. +# +# Why this is its own file: +# The previous incarnation lived as a heredoc inside build.sh's docker +# run command. Once we needed to drop privileges from root to builder, +# the nested-heredoc / nested-quoting situation became unreadable; a +# plain script with normal quoting is far easier to maintain. +# +# Required env vars (set by build.sh and forwarded into the container): +# REPO_ROOT — absolute path to the SilverMetal repo root +# BUILD_DIR — where to drop the resulting *.iso and manifests +# SOURCE_DATE_EPOCH — reproducibility timestamp (forwarded to live-build) +# SNAPSHOT_TIMESTAMP — apt snapshot pin (forwarded to live-build) + +set -euo pipefail + +: "${REPO_ROOT:?REPO_ROOT must be set}" +: "${BUILD_DIR:?BUILD_DIR must be set}" + +# shellcheck disable=SC1091 +source "${REPO_ROOT}/linux/build/config/silvermetal-base.conf" + +cd "${REPO_ROOT}/linux/build/derivative-maker" + +./derivative-maker \ + --build \ + --target "${DERIVATIVE_BUILD_TARGET}" \ + --flavour "${DERIVATIVE_FLAVOUR}" \ + --arch "${DERIVATIVE_TARGET_ARCH}" \ + --dist "${DERIVATIVE_DIST}" \ + --config "${REPO_ROOT}/linux/build/config/silvermetal-base.conf" + +# derivative-maker writes into its own build/ tree; collect into BUILD_DIR. +# 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 "{}" "${BUILD_DIR}/" + +# 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 "{}" "${BUILD_DIR}/" 2>/dev/null || true diff --git a/linux/build/scripts/build.sh b/linux/build/scripts/build.sh index 686635d..4fa69c6 100755 --- a/linux/build/scripts/build.sh +++ b/linux/build/scripts/build.sh @@ -32,7 +32,7 @@ cd "${REPO_ROOT}" # outside the LAN — it's the entry that fleet-wide /etc/docker/daemon.json # registers as an insecure-registry. The host-style "docker-registry:5000" # is *not* DNS-resolvable; do not use it. -BUILDER_IMAGE="${BUILDER_IMAGE:-docker-registry.silverlabs.uk/silvermetal-builder@sha256:9e7161f9f180483f434074d7f32c27c907955232bd0c44efe6dc0ee1d9e56ae0}" +BUILDER_IMAGE="${BUILDER_IMAGE:-docker-registry.silverlabs.uk/silvermetal-builder@sha256:f8f0db3756df220d3de79371054fd43cf7f824ad27d9900328fef5723821bedc}" if [[ "${BUILDER_IMAGE}" != *"@sha256:"* ]]; then echo "build.sh: BUILDER_IMAGE must be pinned by digest, got: ${BUILDER_IMAGE}" >&2 @@ -121,30 +121,13 @@ docker run --rm --privileged \ -w "${REPO_ROOT}" \ "${BUILDER_IMAGE}" \ bash -euo pipefail -c ' - # shellcheck disable=SC1091 - source "${REPO_ROOT}/linux/build/config/silvermetal-base.conf" - - cd "${REPO_ROOT}/linux/build/derivative-maker" - - ./derivative-maker \ - --build \ - --target "${DERIVATIVE_BUILD_TARGET}" \ - --flavour "${DERIVATIVE_FLAVOUR}" \ - --arch "${DERIVATIVE_TARGET_ARCH}" \ - --dist "${DERIVATIVE_DIST}" \ - --config "${REPO_ROOT}/linux/build/config/silvermetal-base.conf" - - # derivative-maker writes into its own build/ dir; collect into - # BUILD_DIR. 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 "{}" "${BUILD_DIR}/" - - # 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 "{}" "${BUILD_DIR}/" 2>/dev/null || true + # derivative-maker refuses to run as root (it uses sudo internally + # for the privileged ops). Hand the workspace ownership to the + # unprivileged builder user (uid 1000, created in the Dockerfile + # with passwordless sudo), then drop privs and let build-inner.sh + # do the actual work. + chown -R builder:builder "${REPO_ROOT}" "${BUILD_DIR}" + runuser -u builder -- "${REPO_ROOT}/linux/build/scripts/build-inner.sh" ' || { echo "build.sh: derivative-maker failed"; exit 3; } # --- Hash artefacts ---------------------------------------------------------