Files
git.stella-ops.org/.gitea/workflows/verify-reproducibility.yml
2026-01-28 02:30:48 +02:00

259 lines
8.1 KiB
YAML

# .gitea/workflows/verify-reproducibility.yml
# Verifies that builds are reproducible (same inputs produce same outputs)
name: Verify Reproducibility
on:
push:
branches: [main]
paths:
- 'src/**'
- 'Directory.Build.props'
- 'Directory.Packages.props'
- 'global.json'
pull_request:
branches: [main]
paths:
- 'src/**'
- 'Directory.Build.props'
- 'Directory.Packages.props'
- 'global.json'
schedule:
# Run weekly to catch any drift
- cron: '0 6 * * 0'
workflow_dispatch:
env:
DOTNET_VERSION: '10.0.100'
BUILD_CONFIGURATION: Release
jobs:
verify-deterministic-build:
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
include-prerelease: true
- name: Compute SOURCE_DATE_EPOCH
id: epoch
run: |
EPOCH=$(git show -s --format=%ct HEAD)
echo "epoch=$EPOCH" >> "$GITHUB_OUTPUT"
echo "SOURCE_DATE_EPOCH=$EPOCH"
- name: Build pass 1
env:
SOURCE_DATE_EPOCH: ${{ steps.epoch.outputs.epoch }}
CI: true
run: |
set -euo pipefail
rm -rf build1/
# Build a representative set of projects
PROJECTS=(
"src/Attestor/__Libraries/StellaOps.Attestor.EvidencePack/StellaOps.Attestor.EvidencePack.csproj"
"src/__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj"
"src/Signer/StellaOps.Signer/StellaOps.Signer.Core/StellaOps.Signer.Core.csproj"
)
for project in "${PROJECTS[@]}"; do
if [ -f "$project" ]; then
name=$(basename "$(dirname "$project")")
echo "Building: $name (pass 1)"
dotnet build "$project" \
--configuration $BUILD_CONFIGURATION \
--output "build1/$name" \
/p:Deterministic=true \
/p:ContinuousIntegrationBuild=true \
/p:SourceRevisionId=${{ github.sha }}
fi
done
# Generate checksums
find build1 -name "*.dll" -type f -exec sha256sum {} \; | sort > build1.checksums
echo "Pass 1 checksums:"
cat build1.checksums
- name: Clean build
run: |
dotnet clean --configuration $BUILD_CONFIGURATION || true
rm -rf obj/ bin/ */obj/ */bin/
- name: Build pass 2
env:
SOURCE_DATE_EPOCH: ${{ steps.epoch.outputs.epoch }}
CI: true
run: |
set -euo pipefail
rm -rf build2/
PROJECTS=(
"src/Attestor/__Libraries/StellaOps.Attestor.EvidencePack/StellaOps.Attestor.EvidencePack.csproj"
"src/__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj"
"src/Signer/StellaOps.Signer/StellaOps.Signer.Core/StellaOps.Signer.Core.csproj"
)
for project in "${PROJECTS[@]}"; do
if [ -f "$project" ]; then
name=$(basename "$(dirname "$project")")
echo "Building: $name (pass 2)"
dotnet build "$project" \
--configuration $BUILD_CONFIGURATION \
--output "build2/$name" \
/p:Deterministic=true \
/p:ContinuousIntegrationBuild=true \
/p:SourceRevisionId=${{ github.sha }}
fi
done
# Generate checksums
find build2 -name "*.dll" -type f -exec sha256sum {} \; | sort > build2.checksums
echo "Pass 2 checksums:"
cat build2.checksums
- name: Compare builds
id: compare
run: |
set -euo pipefail
echo "Comparing build outputs..."
# Extract just the hashes for comparison (paths may differ)
cut -d' ' -f1 build1.checksums | sort > build1.hashes
cut -d' ' -f1 build2.checksums | sort > build2.hashes
if diff build1.hashes build2.hashes > /dev/null; then
echo "✅ Builds are reproducible! All checksums match."
echo "reproducible=true" >> "$GITHUB_OUTPUT"
else
echo "❌ Builds are NOT reproducible!"
echo ""
echo "Differences:"
diff build1.checksums build2.checksums || true
echo "reproducible=false" >> "$GITHUB_OUTPUT"
exit 1
fi
- name: Upload build artifacts for debugging
if: failure()
uses: actions/upload-artifact@v4
with:
name: reproducibility-debug
path: |
build1.checksums
build2.checksums
build1/
build2/
retention-days: 7
verify-cli-reproducibility:
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
include-prerelease: true
- name: Compute SOURCE_DATE_EPOCH
id: epoch
run: |
EPOCH=$(git show -s --format=%ct HEAD)
echo "epoch=$EPOCH" >> "$GITHUB_OUTPUT"
- name: Build CLI pass 1
env:
SOURCE_DATE_EPOCH: ${{ steps.epoch.outputs.epoch }}
CI: true
run: |
set -euo pipefail
dotnet publish src/Cli/StellaOps.Cli/StellaOps.Cli.csproj \
--configuration $BUILD_CONFIGURATION \
--runtime linux-x64 \
--self-contained false \
--output cli-build1 \
/p:Deterministic=true \
/p:ContinuousIntegrationBuild=true \
/p:SourceRevisionId=${{ github.sha }}
sha256sum cli-build1/StellaOps.Cli.dll > cli-build1.checksum
cat cli-build1.checksum
- name: Clean
run: |
dotnet clean --configuration $BUILD_CONFIGURATION || true
rm -rf src/Cli/StellaOps.Cli/obj src/Cli/StellaOps.Cli/bin
- name: Build CLI pass 2
env:
SOURCE_DATE_EPOCH: ${{ steps.epoch.outputs.epoch }}
CI: true
run: |
set -euo pipefail
dotnet publish src/Cli/StellaOps.Cli/StellaOps.Cli.csproj \
--configuration $BUILD_CONFIGURATION \
--runtime linux-x64 \
--self-contained false \
--output cli-build2 \
/p:Deterministic=true \
/p:ContinuousIntegrationBuild=true \
/p:SourceRevisionId=${{ github.sha }}
sha256sum cli-build2/StellaOps.Cli.dll > cli-build2.checksum
cat cli-build2.checksum
- name: Compare CLI builds
run: |
set -euo pipefail
HASH1=$(cut -d' ' -f1 cli-build1.checksum)
HASH2=$(cut -d' ' -f1 cli-build2.checksum)
if [ "$HASH1" = "$HASH2" ]; then
echo "✅ CLI builds are reproducible!"
echo " Hash: $HASH1"
else
echo "❌ CLI builds are NOT reproducible!"
echo " Pass 1: $HASH1"
echo " Pass 2: $HASH2"
exit 1
fi
report:
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [verify-deterministic-build, verify-cli-reproducibility]
if: always()
steps:
- name: Report results
run: |
echo "========================================"
echo " REPRODUCIBILITY VERIFICATION"
echo "========================================"
echo ""
echo "Library builds: ${{ needs.verify-deterministic-build.result }}"
echo "CLI builds: ${{ needs.verify-cli-reproducibility.result }}"
echo ""
if [ "${{ needs.verify-deterministic-build.result }}" = "success" ] && \
[ "${{ needs.verify-cli-reproducibility.result }}" = "success" ]; then
echo "✅ All builds are reproducible!"
else
echo "❌ Some builds are not reproducible"
exit 1
fi