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 + SLSA provenance say "the build came from this workflow". 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

The release.yml workflow sets SOURCE_DATE_EPOCH from the commit timestamp. deb/rpm archives 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

Every push to main triggers repro.yml:

build-a ─┐
         ├─► verify-determinism (sha256 comparison)
build-b ─┘

Two separate runners build the same dvb-warppool-daemon binary. On drift the workflow fails and prints both sha256s.

Verifying — end-user side

scripts/verify-reproducible.sh is a bash script that:

  1. determines the latest release tag (or an explicitly passed tag)
  2. sets SOURCE_DATE_EPOCH from the commit timestamp
  3. locally runs cargo build --release -p dvb-warppool-daemon --locked with the path-remapping flags
  4. downloads the GitHub release asset
  5. compares both sha256s and exits 0 (match) or 1 (drift)
# 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 .dmg and Windows .msi are not byte-deterministic (code signing, installer metadata). Cosign signatures cover those.
  • 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

  1. Local HEAD vs tag commit:
    git rev-parse HEAD
    git rev-parse refs/tags/<TAG>
    git checkout <TAG>  # if different
    
  2. Match the Rust toolchain:
    rustup show active-toolchain
    # should match rust-toolchain.toml
    
  3. Check RUSTFLAGS:
    echo "$RUSTFLAGS"
    # must contain the three --remap-path-prefix entries
    
  4. Glibc version:
    ldd --version
    # CI uses Ubuntu LTS — a minor mismatch is OK, a major one breaks it
    
  5. If everything matches but drift persists: please file an issue with both sha256s and the ldd --version output.