Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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-main auto-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:

  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 published release asset
  5. 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.org release 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 .dmg and Windows .msi are 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 .msi is 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

  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.