4.4 KiB
4.4 KiB
Reproducible Builds
Stella Ops releases are reproducible: given the same source code and build environment, anyone can produce byte-identical artifacts.
Overview
Reproducible builds provide:
- Verifiability - Anyone can verify that released binaries match source code
- Trust - No need to trust the build infrastructure
- Auditability - Build process can be independently audited
- Security - Compromised builds can be detected
How It Works
SOURCE_DATE_EPOCH
All timestamps in build outputs use the SOURCE_DATE_EPOCH environment variable instead of the current time. This is set to the git commit timestamp:
export SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)
Deterministic Build Settings
The following MSBuild properties ensure deterministic .NET builds:
<!-- src/Directory.Build.props -->
<PropertyGroup>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PathMap>$(MSBuildProjectDirectory)=/src/</PathMap>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
Pinned Dependencies
All dependencies are pinned to exact versions in Directory.Packages.props:
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
Containerized Builds
Release builds run in containerized environments with:
- Fixed base images
- Pinned tool versions
- Isolated network (no external fetches during build)
Reproducing a Build
Prerequisites
- .NET SDK (version in
global.json) - Git
- Docker (optional, for containerized builds)
Steps
- Clone the repository
git clone https://git.stella-ops.org/stella-ops.org/git.stella-ops.org.git
cd git.stella-ops.org
- Checkout the release tag
git checkout v1.2.3
- Set SOURCE_DATE_EPOCH
Get the value from the release evidence pack manifest.json:
export SOURCE_DATE_EPOCH=1705315800
Or compute from git:
export SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)
- Build
# Using make
make release
# Or using dotnet directly
dotnet publish src/Cli/StellaOps.Cli/StellaOps.Cli.csproj \
--configuration Release \
--runtime linux-x64 \
--self-contained true \
/p:Deterministic=true \
/p:ContinuousIntegrationBuild=true \
/p:SourceRevisionId=$(git rev-parse HEAD)
- Compare checksums
sha256sum dist/stella-* | diff - path/to/evidence-pack/checksums/SHA256SUMS
CI Verification
The CI pipeline automatically verifies reproducibility:
- Builds artifacts twice with the same
SOURCE_DATE_EPOCH - Compares checksums between builds
- Fails if checksums don't match
See .gitea/workflows/verify-reproducibility.yml.
What Can Cause Non-Reproducibility
Timestamps
- Problem: Build tools embed current time
- Solution: Use
SOURCE_DATE_EPOCH
Path Information
- Problem: Absolute paths embedded in binaries/PDBs
- Solution: Use
PathMapto normalize paths
Random Values
- Problem: GUIDs, random seeds
- Solution: Use deterministic generation or inject via DI
Unordered Collections
- Problem: Dictionary/HashSet iteration order varies
- Solution: Use
ImmutableSortedDictionaryor explicit sorting
External Resources
- Problem: Network fetches return different content
- Solution: Pin dependencies, use hermetic builds
Compiler/Tool Versions
- Problem: Different tool versions produce different output
- Solution: Pin all tool versions in
global.jsonand CI
Debugging Non-Reproducible Builds
Compare binaries
# Install diffoscope
pip install diffoscope
# Compare two builds
diffoscope build1/stella.dll build2/stella.dll
Check for timestamps
# Look for embedded timestamps
strings stella.dll | grep -E '20[0-9]{2}-[0-9]{2}'
Check PDB content
# Examine PDB for path information
dotnet tool install -g dotnet-symbol
dotnet symbol --symbols stella.dll
Verification in Evidence Pack
The Release Evidence Pack includes:
- SOURCE_DATE_EPOCH in
manifest.json - Source commit for exact source checkout
- Checksums for comparison
- Build instructions in
VERIFY.md