# .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