Reproducible Builds
dvb-WarpPool produces bit-identical release binaries: anyone with the
same source commit, the same Rust toolchain, and a Linux-x86_64 host can
rebuild the release and compare it against sha256. This closes the gap
between “we trust the build maintainer” and “we only trust the source
code”.
Why
- Supply-chain resilience — if someone pushes a compromised binary, they either changed the source (visible diff) or patched the binary after the fact (sha256 mismatch).
- Cosign signatures say “the build came from the project” (GitHub-era releases ≤ v1.15.0 additionally carried SLSA workflow provenance). Reproducible builds additionally say “the build is the source”.
- Audit-friendly — pool operators can periodically verify that the pulled image matches the tagged source.
How we achieve it
Cargo profile (in Cargo.toml)
[profile.release]
lto = "fat" # single-threaded LTO, no parallelism drift
codegen-units = 1 # a single LLVM pass
strip = "symbols" # removes debug-info drift
panic = "abort"
incremental = false # explicit
lto = "thin" was deliberately switched to “fat” — thin-LTO is
faster (parallel) but can produce byte drift between host configurations.
Toolchain pin
rust-toolchain.toml pins the rustc + cargo
version. CI and local verification use exactly this version.
Path remapping
Build-time paths ($CARGO_HOME, $GITHUB_WORKSPACE, $HOME) would
otherwise end up embedded as debug info in the binary. We remap them to
generic strings:
RUSTFLAGS="\
--remap-path-prefix=$WORKSPACE=/repo \
--remap-path-prefix=$HOME/.cargo/registry=/cargo/registry \
--remap-path-prefix=$HOME/.cargo/git=/cargo/git"
SOURCE_DATE_EPOCH
repro.yml and scripts/verify-reproducible.sh both derive
SOURCE_DATE_EPOCH from the commit timestamp, so a verification build uses
the same convention as the reference build. Archive formats (deb/rpm,
tarballs) use it for mtime fields instead of now(). (The Rust binary itself
currently does not embed timestamps, but wrappers and installers do.)
Cargo.lock
We commit Cargo.lock and the build uses --locked. This also pins
every transitive dependency version.
Verifying — CI side
repro.yml
runs on manual dispatch and as a fixed step of the release routine:
build-a ─┐
├─► verify-determinism (sha256 comparison)
build-b ─┘
Two matrix runs build the same dvb-warppool-daemon binary from the same
commit. On drift the workflow fails and prints both sha256s.
The push-to-
mainauto-trigger is currently off: the double release build is too heavy for the 2-vCPU Forgejo runner. It returns if a bigger runner is added.
Verifying — end-user side
scripts/verify-reproducible.sh
is a bash script that:
- determines the
latestrelease tag (or an explicitly passed tag) - sets
SOURCE_DATE_EPOCHfrom the commit timestamp - locally runs
cargo build --release -p dvb-warppool-daemon --lockedwith the path-remapping flags - downloads the published release asset
- compares both sha256s and exits 0 (match) or 1 (drift)
Note (post-Forgejo migration): the script still resolves the tag and the asset URL against the old GitHub repository, which is no longer reachable. Until it is ported to the
git.warppool.orgrelease API, pass the tag explicitly and compare against a manually downloaded asset from the Forgejo releases page.
# Verify the latest release
./scripts/verify-reproducible.sh
# A specific version
./scripts/verify-reproducible.sh v0.1.0
# Keep build artifacts for debugging
./scripts/verify-reproducible.sh --keep-target
Limitations
- Linux-x86_64 only — macOS
.dmgand Windows.msiare not byte-deterministic (code signing, installer metadata). Cosign signatures cover those. Since the move to Forgejo there are no macOS or Windows CI runners either: the macOS assets are built and signed manually per release, and the Windows.msiis paused (the WiX sources stay maintained). - Glibc drift — if a pool operator builds on an old distro (Debian Buster) while the CI runner uses Ubuntu Latest, glibc drift can occur. Workaround: build in the same Docker image the CI uses, or use a static MUSL build (phase 8c).
- LLVM version drift — rust-toolchain.toml pins rustc, but the LLVM version shipped with it varies minor across rustup channels. We accept this as a known limitation.
When the build drifts
- Local HEAD vs tag commit:
git rev-parse HEAD git rev-parse refs/tags/<TAG> git checkout <TAG> # if different - Match the Rust toolchain:
rustup show active-toolchain # should match rust-toolchain.toml - Check RUSTFLAGS:
echo "$RUSTFLAGS" # must contain the three --remap-path-prefix entries - Glibc version:
ldd --version # CI uses Ubuntu LTS — a minor mismatch is OK, a major one breaks it - If everything matches but drift persists: please file an issue with
both sha256s and the
ldd --versionoutput.