Run #4257 cleared sanity-tests entirely (sq-git verification of every
submodule signature: ✅; tag/uncommitted relaxation: ✅) and reached
1200_prepare-build-machine, where it died:
+ sudo systemctl daemon-reload
sudo: systemctl: command not found
ERROR detected in script!: ././build-steps.d/1200_prepare-build-machine
derivative-maker assumes systemd is PID 1 on the build host. Upstream's
own container (linux/build/derivative-maker/docker/) runs
systemd-as-init via an entrypoint that masks irrelevant units and
declares its own. We don't want that surgery for M1.1 — it pulls in
cgroup mounts, --cgroupns=host, and a much bigger debugging surface.
Shim approach instead: install /usr/local/bin/systemctl that logs the
attempt to stderr and exits 0. /usr/local/bin precedes /usr/bin in
both default $PATH and sudo's secure_path, so it satisfies any
systemctl call regardless of whether the real binary later gets pulled
in by a package install. Standard pattern for systemd-aware Debian
build scripts in transient containers.
Risk if it doesn't suffice: the shim makes daemon-reload / restart /
mask calls succeed, but doesn't actually run any service. If a later
build step depends on (say) approx actually being up to serve cached
debs, we'll see the next failure and decide whether to escalate to
real systemd-in-container or skip the relevant build step.
Changes:
- Dockerfile.builder: add the shim with a brief log line to stderr;
comment block documents the trade-off.
- build.sh: BUILDER_IMAGE digest re-pinned to sha256:70f160ab…5460
(built natively on 10.0.0.51, shim verified working with
`docker run … systemctl daemon-reload` returning 0).
Verified: shim emits "systemctl-shim: daemon-reload" to stderr and
exits 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4256 finally cleared every preceding obstacle and reached
git_sanity_test's per-submodule verification phase. sq-git authenticated
every commit signature in the chain — that part is working perfectly —
but failed at:
ERROR: Untagged commit in: qubes/qubes-template-kicksecure
INFO: As a developer or advanced user you might want to use:
WARNING: This can be insecure if you cannot audit the changes.
--allow-untagged true --allow-uncommitted true
git_sanity_test runs two orthogonal checks:
1. signatures (sq-git, verified ✅)
2. tagged-commit-only mode (verified ❌ for one submodule)
The pinned upstream tag (18.1.7.4-developers-only — the name itself
flags the intent) deliberately ships with some submodule pointers at
intermediate / merge commits rather than release tags. parse-cmd
documents `--allow-untagged true` and `--allow-uncommitted true` for
exactly this case. Signatures remain verified; we're only relaxing the
release-tag check, which is appropriate when we've deliberately pinned
to a developer tag.
If/when we move to a redistributable upstream tag in M1.10+ (signing
ceremony milestone), these flags should come back out.
No image rebuild needed — script-only change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4255 reached deeper into 1100_sanity-tests, finished its apt-get
phase, and then died at the supply-chain verification step:
/workspace/.../help-steps/git_sanity_test: line 184: sq-git: command not found
ERROR: sq-git verification failed: main repo
INFO: If this is intentional, configure your own sq-git policy file.
See 'buildconfig.d/30_signing_key.conf'.
derivative-maker uses sq-git (sequoia-git) to authenticate the commit
chain against an OpenPGP policy file before building. The policy file
itself ships in the upstream repo (./openpgp-policy.toml) and the
trust-root defaults are correctly configured by help-steps/variables
(line 232 + 290) for non-redistributable builds — i.e. the verification
machinery is fully wired and just needs the binary.
Aligns with the upstream container's package list at
linux/build/derivative-maker/docker/derivative-maker-docker-setup.
Changes:
- Dockerfile.builder: add sq, sqv, sqop, sequoia-git,
sequoia-chameleon-gnupg, gpg-agent. All available in trixie main.
- build.sh: BUILDER_IMAGE digest re-pinned to sha256:c1490bab…5c97
(rebuilt on 10.0.0.51, sq-git binary verified present at /usr/bin/sq-git).
No reproducibility implications — image rebuilds against the same
pinned snapshot timestamp.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4254 finally got past every harness issue and into derivative-
maker's actual sanity-tests, where it died with:
You are attempting to build on an unsupported operating system or version.
detected operating system codename: 'bookworm'
expected operating system codename: 'trixie'
The pinned derivative-maker tag (18.1.7.4-developers-only) requires
Debian 13 (trixie) as the build host. Upstream's own
linux/build/derivative-maker/docker/Dockerfile uses
`FROM debian:trixie-slim`. We picked bookworm originally and the tag
mismatch wasn't caught until the build actually ran.
Changes:
- Dockerfile.builder: FROM debian:bookworm-slim →
debian:trixie-slim @ sha256:cedb1ef4…2c5a (resolved 2026-05-07 on
the runner host). sources.list suite names follow:
`bookworm` → `trixie`, `bookworm-security` → `trixie-security`.
snapshot.debian.org pin (20260415T000000Z) is unchanged — snapshots
are date-keyed, so the same timestamp resolves trixie's dists/.
- silvermetal-base.conf: DERIVATIVE_DIST `bookworm` → `trixie` for
consistency (the value isn't passed to derivative-maker — there's
no --dist option — but it's referenced by the build.sh prologue
and we shouldn't have a stale codename floating around).
- build.sh: BUILDER_IMAGE digest re-pinned to sha256:7d893178…1890
(rebuilt natively on 10.0.0.51 against the new base, pushed).
The reproducibility guarantee is unchanged in shape — same snapshot
timestamp, same source-date-epoch derivation, just a different stable
host OS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4253 finally got past all the harness failures and into
derivative-maker's actual build steps, where 1100_sanity-tests
rejected our invocation with:
unknown option (1): '--build'
The CLI we'd been passing was built from invented flag names rather
than the real grammar in derivative-maker/help-steps/parse-cmd.
Concretely:
- `--build` is not a real option (just wrong)
- `--flavour` should be `--flavor` (upstream uses American spelling)
- `--dist` is not a real option; dist is implicit from `--flavor`
(kicksecure-cli ⇒ bookworm)
- `--config` is not a real option; the silvermetal-base.conf is
sourced into env above the invocation, no flag needed
- `--freedom true|false` was missing entirely; parse-cmd requires it
for `--arch amd64` (line 70 in parse-cmd) — the script
exits if neither is set
Fix: build-inner.sh now invokes
./derivative-maker --flavor … --target … --arch … --freedom …
which is the minimal valid form per parse-cmd's case-branches.
Set DERIVATIVE_FREEDOM=false in silvermetal-base.conf, matching
Kicksecure's own public-ISO choice — `--freedom true` would omit
firmware-nonfreedom and the resulting ISO wouldn't initialise wifi /
many GPUs / Intel microcode on most hardware. Privacy/functionality
trade-off documented inline; the hardening overlay in M1.2+ can
revisit if that conversation becomes useful.
Verified: bash -n on both scripts. No image rebuild needed — pure
script and config changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4252 died at:
runuser: failed to execute /workspace/SilverLABS/SilverMetal/linux/build/scripts/build-inner.sh:
Permission denied
The script was created on the WSL/Windows side (/mnt/c) where every
file appears world-rwx regardless of git's index, so the local
`chmod +x` was a no-op as far as git was concerned and the file got
committed at mode 100644 like any other regular file. Sibling scripts
(build.sh, verify-reproducibility.sh, diagnose-divergence.sh) all
correctly carry 100755 in the index.
Fix: `git update-index --chmod=+x` to set the bit in the index
explicitly, independent of the working-tree perms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4251 advanced past checkout and into derivative-maker, then died
immediately:
ERROR: This must NOT be run as root (sudo)!
ERROR: Exiting ./derivative-maker with non-zero exit code 1.
Errors Detected: 0. Execution Time: 00:00:00.
Kicksecure's derivative-maker explicitly refuses to run as root — it
expects a regular user with passwordless sudo and uses sudo internally
for the privileged operations (debootstrap, mksquashfs, chroot mounts).
Our minimal debian-slim builder image had a `builder` user (uid 1000)
but no sudo, no sudoers entry, and the container ran as root.
Aligns with the upstream Kicksecure container pattern at
linux/build/derivative-maker/docker/derivative-maker-docker-setup
(uses USER=user with `${USER} ALL=(ALL) NOPASSWD:ALL`).
Changes:
- Dockerfile.builder: install `sudo` (and `fakeroot` while we're here —
upstream sanity-tests pulls this in via apt at build time, but having
it baked avoids a snapshot.debian.org round-trip every run); add
passwordless sudoers entry for builder; correct the misleading
comment that claimed root was needed.
- New scripts/build-inner.sh: the inner derivative-maker invocation
pulled out of build.sh's heredoc. Once we needed to drop privileges
via runuser, the nested-heredoc / nested-quoting situation became
unmaintainable; a regular script with normal quoting is far cleaner.
- build.sh: inner heredoc now just chowns the workspace to builder and
runuser's into build-inner.sh. ${REPO_ROOT} and ${BUILD_DIR} continue
to be forwarded into the container via -e.
- build.sh: BUILDER_IMAGE digest re-pinned to sha256:f8f0db37…1bedc
(rebuilt and pushed natively on 10.0.0.51 — never on the WSL/aarch64
dev box, see reference_silvermetal_runner.md memory).
Verified: bash -n on both scripts; image builds and pushes cleanly.
Pushing this commit triggers a fresh CI run that will exercise it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build.sh ran fine locally but failed in Gitea Actions on the first
reproducibility-gated run (#4250) with:
bash: line 3: /work/linux/build/config/silvermetal-base.conf:
No such file or directory
Root cause: classic Docker-out-of-Docker confusion. build.sh runs
inside the act_runner job container, which talks to the host's docker
daemon via the mounted /var/run/docker.sock. The "-v ${REPO_ROOT}:/work"
flag was being interpreted by the host daemon against the host
filesystem, where /workspace/SilverLABS/SilverMetal does not exist;
docker silently auto-created an empty dir there and mounted that as
/work, so the config source target was missing.
Fix: detect GITHUB_ACTIONS and use --volumes-from "$(hostname)" in CI
to inherit the parent job container's /workspace mount intact. Locally
we keep a bind mount, but use the same path inside and outside
(${REPO_ROOT}:${REPO_ROOT}) so the inner heredoc is identical in both
modes. Inner script now references "${REPO_ROOT}/..." and
"${BUILD_DIR}/..." instead of the synthetic /work and /out paths.
No reproducibility implications — bind topology doesn't affect bytes
inside the ISO.
Verified locally: bash -n passes; structural change only, behaviour
preserved for the non-CI path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two corrections to f9e606d:
1. Registry hostname: docker-registry:5000 isn't DNS-resolvable on the
SLAB docker host (verified). The fleet-wide convention is the canonical
docker-registry.silverlabs.uk URL, registered as an insecure-registry
in /etc/docker/daemon.json on every docker host.
2. Architecture: the original push from WSL2-on-aarch64 produced an arm64
image that won't run on the amd64 runner. Rebuilt natively on the docker
host. New manifest digest (amd64-only):
sha256:9e7161f9f180483f434074d7f32c27c907955232bd0c44efe6dc0ee1d9e56ae0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
act_runner-based deployment that handles `runs-on: silvermetal-builder` jobs.
Adapted from the stinky-roger-tv flutter-builder pattern with three changes:
- privileged: true (live-build needs loop devices + chroot)
- 4h job timeout (covers two reproducibility-gated ISO builds + diffoscope)
- silvermetal-builder label maps to catthehacker/ubuntu:act-latest, not the
silvermetal-builder image — the builder image stays minimal (no docker-cli),
and build.sh invokes it via `docker run` from the catthehacker job shell
Deployed at /opt/silvermetal-builder-runner/ on the SLAB docker host
(10.0.0.51); registered with git.silverlabs.uk and reporting healthy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Image built from Dockerfile.builder@36f7672 was pushed to both
docker-registry:5000 (internal) and docker-registry.silverlabs.uk
(external) under tags m1.1-bootstrap + latest. Both URLs serve the
same registry, so the manifest digest is identical:
sha256:cedef039425e0b0f5901c1023eda820c7aa38ab4b81c2bb1e12d64cadb3d6c85
Default points at the internal hostname for CI; external dev overrides
via BUILDER_IMAGE env var.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
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>