Files
SilverMetal/linux/build/README.md
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

117 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
```bash
# 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)
```bash
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:
```bash
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.