Files
SilverMetal/linux/build
SysAdmin 36f7672c6f fix(linux/build): make builder image actually build (M1.1)
- Pin debian:bookworm-slim by real digest (resolved 2026-04-26).
- Two-phase install: seed ca-certificates from the default mirror first
  so HTTPS to snapshot.debian.org works, then swap to the pinned snapshot
  for the toolchain itself. Slim images don't ship the CA bundle, so the
  one-shot pinned-source-only install would deadlock on cert verification.

Validated locally: image builds clean, 302MB, all live-build / debootstrap /
mksquashfs / xorriso / diffoscope-minimal present.

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

SilverMetal Linux — reproducible ISO build pipeline

Milestone: Phase 1 / M1.1 — Kicksecure fork builds reproducibly. Exit criterion: two clean builds of the same commit produce a byte-identical SHA256.

This directory holds everything that turns a SilverMetal commit into a SilverMetal Linux ISO. M1.1 ships only the base (un-hardened) Kicksecure derivative. Hardening overlay, kernel swap, AppArmor profiles, etc. land in M1.2+ and must not be added here in the M1.1 PR.

Layout

linux/build/
├── README.md                    (this file)
├── derivative-maker/            git submodule -> Kicksecure/derivative-maker
├── config/
│   ├── silvermetal-base.conf    derivative selection + branding
│   ├── snapshot-pin.env         pinned snapshot.debian.org timestamp
│   └── source-date-epoch.env    optional SOURCE_DATE_EPOCH override
├── docker/
│   └── Dockerfile.builder       pinned debian:bookworm-slim builder image
└── scripts/
    ├── build.sh                 wrapper: container run -> derivative-maker
    ├── verify-reproducibility.sh build twice, compare SHA256
    └── diagnose-divergence.sh   diffoscope on mismatch

How reproducibility is achieved

The same levers any deterministic Debian build relies on, stacked together:

Lever Where it lives
Pinned snapshot.debian.org mirror config/snapshot-pin.env
SOURCE_DATE_EPOCH from commit time scripts/build.sh (auto)
Pinned builder image (by digest) docker/Dockerfile.builder + BUILDER_IMAGE
Deterministic mksquashfs flags MKSQUASHFS_OPTIONS in base conf
Pinned upstream toolchain derivative-maker/ submodule
LC_ALL=C.UTF-8, TZ=UTC scripts/build.sh

diffoscope is the diagnostic tool used by diagnose-divergence.sh; the gate itself is plain sha256sum.

Reproduce a release locally

Procedure mirrors docs/trust-model.md § Reproducible builds.

Prerequisites: a Linux host (or WSL2) with Docker, ~30 GB free disk, ~8 GB RAM.

# 1. Clone the repo at the release tag.
git clone --recurse-submodules https://git.silverlabs.uk/SilverLABS/SilverMetal.git
cd SilverMetal
git checkout v1.1.0   # whichever release you want to verify

# 2. Build twice and compare. ~60-90 minutes per build.
linux/build/scripts/verify-reproducibility.sh

# 3. Compare against the published release.
sha256sum -c <(curl -fsSL https://git.silverlabs.uk/SilverLABS/SilverMetal/releases/download/v1.1.0/SHA256SUMS)

Mismatch with the published artefact = supply-chain anomaly. Report channel: security@silverlabs.uk.

Build once (no reproducibility check)

linux/build/scripts/build.sh
# Output lands in linux/build/output/<short-sha>/

The wrapper requires BUILDER_IMAGE to be pinned by digest. Local dev that hasn't built and pushed an image yet should override:

BUILDER_IMAGE=docker-registry:5000/silvermetal-builder@sha256:<digest> \
    linux/build/scripts/build.sh

Gitea Actions

The CI workflow (.gitea/workflows/build-iso-linux.yaml) is the authority for "did this commit build reproducibly?". It:

  1. Checks out the commit with submodules.
  2. Runs build.sh twice in ${GITHUB_WORKSPACE}/build-{a,b}.
  3. Fails the run if the two ISO SHA256s differ, and uploads a diffoscope report as an artefact.
  4. On a tag push, attaches the verified ISO + SHA256SUMS + BUILD_INFO to a Gitea release.

Self-hosted runner setup

The workflow runs on runs-on: silvermetal-builder, a self-hosted, privileged-capable Gitea Actions runner. Create it before merging the workflow:

  1. Provision a Debian 12 VM on the cluster with ≥ 8 vCPU, ≥ 16 GB RAM, ≥ 100 GB disk.
  2. Install Docker (apt install docker.io); ensure the runner user can run docker run --privileged.
  3. Register act_runner against git.silverlabs.uk with label silvermetal-builder.
  4. Pre-pull the builder image so the first reproducibility run isn't a cold start: docker pull docker-registry:5000/silvermetal-builder:latest
  5. Cache the apt snapshot in a Docker volume to avoid throttling: docker volume create silvermetal-apt-cache

The runner host name must not leak into ISO content. LC_ALL=C.UTF-8 and a constant TZ in the wrapper guard against that, but spot-check with diagnose-divergence.sh.

Bumping pinned inputs

Each of these is a deliberate, reviewed action — never automate:

  • derivative-maker submodule — bump in its own PR, with a verification log showing two clean builds match.
  • snapshot-pin.env — same procedure.
  • Builder image (Dockerfile.builder digest) — rebuild, push, update BUILDER_IMAGE in build.sh, run reproducibility check, commit all four together.

What this milestone is not

  • No hardening overlay (M1.2)
  • No SilverBrowser/SilverVPN/SilverSync/SilverChat integration (M1.61.9)
  • No installer branding (M1.5)
  • No update server (M1.10)
  • No SBOM publication (M1.11)
  • No signing ceremony / MOK / Secure Boot wiring (separate milestone)

If a change to this directory expands its scope into one of those, push back — the M1.1 gate is intentionally narrow.