Skip to content

Commit 2cd172f

Browse files
committed
feat(docker): unify wrapper helpers, add reproducibility checks, and expand docs
Introduce a shared docker/common.sh helper library and refactor docker_* wrappers to use consistent image resolution, USB/KVM/X11 handling, and reproducibility verification. Add new tooling to fetch/pin image digests and improve documentation for users, developers, and maintainers. Key changes: - Add shared docker helper library with Nix setup, image resolution, USB cleanup, X11/KVM passthrough, and reproducibility comparison logic in [docker/common.sh](docker/common.sh). - Add reproducibility verification flow and configurable remote target via HEADS_CHECK_REPRODUCIBILITY and HEADS_CHECK_REPRODUCIBILITY_REMOTE in [docker/common.sh](docker/common.sh) and document behavior in [README.md](README.md). - Provide standalone reproducibility checker in [docker/check_reproducibility.sh](docker/check_reproducibility.sh). - Add digest utilities and pinning helpers: [docker/get_digest.sh](docker/get_digest.sh) and [docker/pin-and-run.sh](docker/pin-and-run.sh). - Add a pinned digest file for reproducible builds: [docker/DOCKER_REPRO_DIGEST](docker/DOCKER_REPRO_DIGEST). - Add Nix installer fetch helper with checksum guidance: [docker/fetch_nix_installer.sh](docker/fetch_nix_installer.sh). - Refactor wrapper scripts to use shared helpers and digest pinning: [docker_latest.sh](docker_latest.sh), [docker_local_dev.sh](docker_local_dev.sh), [docker_repro.sh](docker_repro.sh). - Update QEMU documentation to prefer Docker wrappers and clarify supported workflow in [targets/qemu.md](targets/qemu.md). - Expand README with reproducibility verification, wrapper options, fork/maintainer overrides, and workflow guidance in [README.md](README.md). Behavioral highlights: - docker_latest.sh and docker_repro.sh now resolve pinned digests and validate against .circleci/config.yml when applicable. - docker_local_dev.sh can optionally verify reproducibility against a configured remote image. - docker/common.sh shows usage when executed directly (meant to be sourced). Files changed: - [README.md](README.md) - [docker/DOCKER_REPRO_DIGEST](docker/DOCKER_REPRO_DIGEST) - [docker/check_reproducibility.sh](docker/check_reproducibility.sh) - [docker/common.sh](docker/common.sh) - [docker/fetch_nix_installer.sh](docker/fetch_nix_installer.sh) - [docker/get_digest.sh](docker/get_digest.sh) - [docker/pin-and-run.sh](docker/pin-and-run.sh) - [docker_latest.sh](docker_latest.sh) - [docker_local_dev.sh](docker_local_dev.sh) - [docker_repro.sh](docker_repro.sh) - [targets/qemu.md](targets/qemu.md) Signed-off-by: Thierry Laurion <insurgo@riseup.net>
1 parent 81dbf23 commit 2cd172f

11 files changed

Lines changed: 2346 additions & 264 deletions

README.md

Lines changed: 462 additions & 37 deletions
Large diffs are not rendered by default.

docker/DOCKER_REPRO_DIGEST

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Optional: pin the Docker image used by ./docker_repro.sh to an immutable digest
2+
# This file is read by docker_repro.sh if DOCKER_REPRO_DIGEST is not set in the
3+
# environment. The first non-empty, non-comment line is used as the digest value.
4+
# Acceptable formats are:
5+
# - sha256:<64-hex>
6+
# - sha256-<64-hex> (will be normalized to sha256:<hex>)
7+
# - <64-hex> (will be normalized to sha256:<hex>)
8+
# Example:
9+
# sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
10+
11+
# Place the digest on the first non-comment line below (remove the leading '#')
12+
sha256-50a9110cdfc6a74a383169d7c624139c3b3e05567b87203498118a8a33dd79f1

docker/check_reproducibility.sh

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/bin/bash
2+
# Helper to compare local Docker image digest with remote docker.io
3+
# Usage: ./docker/check_reproducibility.sh [local_image] [remote_image]
4+
# Example:
5+
# ./docker/check_reproducibility.sh linuxboot/heads:dev-env tlaurion/heads-dev-env:latest
6+
7+
set -euo pipefail
8+
9+
usage() {
10+
cat <<'USAGE' >&2
11+
Usage: $0 [local_image] [remote_image]
12+
13+
Compare a local Docker image digest with a remote docker.io image.
14+
15+
Arguments:
16+
local_image Local image to check (default: linuxboot/heads:dev-env)
17+
remote_image Remote docker.io image to compare against (default: ${HEADS_MAINTAINER_DOCKER_IMAGE}:latest, where HEADS_MAINTAINER_DOCKER_IMAGE defaults to tlaurion/heads-dev-env)
18+
19+
Environment:
20+
HEADS_MAINTAINER_DOCKER_IMAGE Override the canonical maintainer's image repository (default: tlaurion/heads-dev-env)
21+
22+
Examples:
23+
./docker/check_reproducibility.sh
24+
./docker/check_reproducibility.sh linuxboot/heads:dev-env tlaurion/heads-dev-env:latest
25+
./docker/check_reproducibility.sh linuxboot/heads:dev-env tlaurion/heads-dev-env:v0.2.7
26+
HEADS_MAINTAINER_DOCKER_IMAGE="myuser/heads-dev-env" ./docker/check_reproducibility.sh
27+
28+
Requirements:
29+
- docker CLI (to inspect local images)
30+
- Network access (to pull remote images for digest comparison)
31+
32+
USAGE
33+
}
34+
35+
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
36+
usage
37+
exit 0
38+
fi
39+
40+
# Respect HEADS_MAINTAINER_DOCKER_IMAGE environment variable for fork maintainers
41+
HEADS_MAINTAINER_DOCKER_IMAGE="${HEADS_MAINTAINER_DOCKER_IMAGE:-tlaurion/heads-dev-env}"
42+
43+
local_image="${1:-linuxboot/heads:dev-env}"
44+
remote_image="${2:-${HEADS_MAINTAINER_DOCKER_IMAGE}:latest}"
45+
46+
echo "=== Docker Image Reproducibility Check ===" >&2
47+
echo "" >&2
48+
49+
# Get local image digest
50+
echo "Fetching local image digest from: ${local_image}" >&2
51+
local_digest=$(docker inspect --format='{{.Id}}' "${local_image}" 2>/dev/null || true)
52+
if [ -z "${local_digest}" ]; then
53+
echo "Error: Local image '${local_image}' not found" >&2
54+
exit 1
55+
fi
56+
57+
# Normalize to just the hex part
58+
local_digest_hex="${local_digest##*:}"
59+
echo " Found: ${local_digest}" >&2
60+
echo "" >&2
61+
62+
# Get remote image digest
63+
echo "Fetching remote image digest from: ${remote_image}" >&2
64+
65+
# Try skopeo first (if available, doesn't require full image pull)
66+
remote_digest=""
67+
if command -v skopeo >/dev/null 2>&1; then
68+
echo " Attempting with skopeo (no pull required)..." >&2
69+
remote_digest=$(skopeo inspect "docker://${remote_image}" 2>/dev/null | grep -o '"Digest":"[^"]*' | head -n1 | cut -d'"' -f4 || true)
70+
if [ -n "${remote_digest}" ]; then
71+
echo " Found: ${remote_digest}" >&2
72+
fi
73+
fi
74+
75+
# If skopeo didn't work, pull the image and inspect it locally
76+
if [ -z "${remote_digest}" ]; then
77+
echo " Pulling image locally to inspect..." >&2
78+
if docker pull "${remote_image}" >/dev/null 2>&1; then
79+
remote_digest=$(docker inspect --format='{{.Id}}' "${remote_image}" 2>/dev/null || true)
80+
if [ -n "${remote_digest}" ]; then
81+
echo " Found: ${remote_digest}" >&2
82+
fi
83+
else
84+
echo "Error: Could not pull remote image '${remote_image}'" >&2
85+
exit 1
86+
fi
87+
fi
88+
89+
if [ -z "${remote_digest}" ]; then
90+
echo "Error: Failed to retrieve remote digest" >&2
91+
exit 1
92+
fi
93+
94+
# Normalize to just the hex part
95+
remote_digest_hex="${remote_digest##*:}"
96+
97+
echo "" >&2
98+
echo "=== Comparison ===" >&2
99+
echo "Local: ${local_image}" >&2
100+
echo " ${local_digest_hex}" >&2
101+
echo "" >&2
102+
echo "Remote: ${remote_image}" >&2
103+
echo " ${remote_digest_hex}" >&2
104+
echo "" >&2
105+
106+
# Compare
107+
if [ "${local_digest_hex}" = "${remote_digest_hex}" ]; then
108+
echo "✓ SUCCESS: Digests match!" >&2
109+
echo " Your local build is reproducible and identical to ${remote_image}" >&2
110+
echo "" >&2
111+
exit 0
112+
else
113+
echo "✗ MISMATCH: Digests differ" >&2
114+
echo "" >&2
115+
echo "This is expected if:" >&2
116+
echo " - Nix/flake.lock versions differ from the remote build" >&2
117+
echo " - There are uncommitted changes in flake.nix" >&2
118+
echo " - Different Nix dependencies are resolved locally vs remotely" >&2
119+
echo "" >&2
120+
exit 1
121+
fi
122+

0 commit comments

Comments
 (0)