Reproducible builds
Linux and macOS release archives (.tar.gz) are produced deterministically:
the gzip header carries mtime=0 and no filename, and every tar entry has
sorted order, fixed uid/gid 0, empty owner/group names, and mtime set to
SOURCE_DATE_EPOCH derived from the tagged commit. Packaging is performed
by scripts/pack.py, and the same script is used
both at release time and during validation, so each archive's SHA-256 can
be reproduced bit-for-bit from source.
Windows .zip archives **cannot** be byte-reproduced at the archive level
— the .exe inside is Authenticode-signed by Azure Trusted Signing and
the signing key is, deliberately, not available to rebuilders. Instead,
the reproducibility workflow verifies Windows builds at the .exe level
(issue #78, Option E): it strips the Authenticode signature off the
published .exe and compares its sha256 against a locally-rebuilt
unsigned .exe. The stripping is performed by
ghr validate strip-authenticode — a small
subcommand that reverses exactly what Trusted Signing appends
(the certificate table at end of file, the IMAGE_DIRECTORY_ENTRY_SECURITY
entry, and OptionalHeader.CheckSum).
The Reproducibility workflow
runs automatically after each successful Release workflow and can also be
triggered manually with workflow_dispatch (provide the tag, e.g. v0.3.0).
For every release target it:
- checks out the source at the tag,
- installs the Zig version selected from the checked-out tag's manifest,
- rebuilds with the same flags as
release.yml, - for non-Windows targets: repackages with
scripts/pack.py, downloads the published.tar.gz, verifies it against GitHub's published asset digest, and fails on hash mismatch against the rebuild (withtar tvfandcmpdiffs as a diagnostic artifact), - for Windows targets: also builds a host-native
ghr.exeas the stripper, downloads the published.zip, extractsbin/ghr.exe, strips its Authenticode signature, and fails if the stripped sha256 doesn't match the locally-rebuilt unsigned.exe. Diagnostics includecmp -lbyte differences and side-by-side header dumps.
Releases tagged at or before v0.2.1 predate deterministic packaging and
the Authenticode signing pipeline; they will not reproduce.
Compiler selection
Release and reproducibility jobs read .minimum_zig_version from the
checked-out source's build.zig.zon, after checkout. For the supported
release history this also records the exact release compiler:
| Checked-out manifest | Signed compiler release | Optimization flag |
|---|---|---|
0.16.0 |
cataggar/zig@v0.16.0 |
-Doptimize=ReleaseSafe |
0.17.0 |
cataggar/zig@v0.17.0 |
-Doptimize=safe |
Both installations require the existing trusted minisign key. Missing, ambiguous, or unknown manifest versions fail explicitly; there is no silent fallback to the newest compiler. Selection stays inline in the workflows because old tags do not contain newly added helper scripts. Future compiler upgrades must extend this allowlist without changing the historical rows.
For a local rebuild, first check out the release tag, install its matching
official compiler, set SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct HEAD),
and use that tag's release target, version, strip, and optimization flags.
Current source builds require official Zig 0.17.0; its optimization enum is
debug / safe / fast / small. Do not pass these new spellings to old
tags built with Zig 0.16.0, and do not rebuild those tags with Zig 0.17.0.
The compiler selection does not alter archive metadata, packaging scripts,
or the Windows stripped-signed comparison.
Running the strip locally
ghr validate strip-authenticode <input.exe> <output.exe> reads a
signed PE, removes the embedded WIN_CERTIFICATE table at end of file,
zeroes the security data-directory entry, and zeroes
OptionalHeader.CheckSum. Given a deterministic Zig-built unsigned
.exe, signing-then-stripping returns the exact bytes the compiler
emitted — the foundation of Option E.
$ ghr validate strip-authenticode published/ghr.exe stripped.exe
stripped published/ghr.exe -> stripped.exe: dropped 15672 bytes (cert table at offset 0x238be00)
$ sha256sum stripped.exe locally-rebuilt-ghr.exe