release #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-License-Identifier: AGPL-3.0-only | |
| # Copyright (C) 2026 RS-Key contributors | |
| # Cut a release from a pushed `v*` tag. | |
| # | |
| # `build` builds the 8 firmware flavors reproducibly via `nix build`, then GATES | |
| # on a bit-identical rebuild of all eight (a non-reproducible image fails the job | |
| # before anything is published), generates a CycloneDX SBOM, hashes everything | |
| # into SHA256SUMS, signs that with keyless cosign (sigstore/Fulcio via OIDC — no | |
| # private key), attests GitHub build provenance for every .uf2, and publishes the | |
| # GitHub Release. | |
| # | |
| # The provenance is a GitHub attestation (Sigstore-signed, in the attestation API | |
| # + the public Rekor log), NOT a release asset — so it stays compatible with | |
| # immutable releases. A consumer verifies which workflow / commit / runner built | |
| # each .uf2 with `gh attestation verify` (docs/supply-chain.md). | |
| # | |
| # The .uf2 images are UNSIGNED for secure boot — cosign + the SLSA provenance | |
| # attest the BUILD, not the boot seal. On a secure-boot device, seal an image | |
| # with your own key before flashing (docs/production.md). Verifying a download: | |
| # docs/releases.md and docs/supply-chain.md. | |
| name: release | |
| on: | |
| push: | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "tag to (re)build a release for, e.g. v0.1.0" | |
| required: true | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 150 | |
| permissions: | |
| contents: write # create the release + upload assets | |
| id-token: write # keyless cosign + the attestation's Fulcio OIDC token | |
| attestations: write # GitHub build-provenance attestation (immutable-safe) | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event.inputs.tag || github.ref }} | |
| - uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22 | |
| - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2 | |
| with: | |
| primary-key: nix-${{ runner.os }}-${{ hashFiles('flake.lock') }} | |
| restore-prefixes-first-match: nix-${{ runner.os }}- | |
| - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock', 'flake.lock') }} | |
| restore-keys: cargo-${{ runner.os }}- | |
| - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 | |
| - name: resolve tag | |
| id: tag | |
| run: | | |
| tag="${{ github.event.inputs.tag || github.ref_name }}" | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| echo "version=${tag#v}" >> "$GITHUB_OUTPUT" | |
| - name: build the 8 reproducible firmware flavors | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| mkdir -p dist | |
| for pkg in firmware firmware-pqc firmware-fips firmware-fips-pqc \ | |
| firmware-no-touch firmware-no-touch-pqc \ | |
| firmware-no-touch-fips firmware-no-touch-fips-pqc; do | |
| echo "::group::nix build .#$pkg" | |
| out="$(nix build ".#$pkg" --no-link --print-out-paths)" | |
| label="${pkg#firmware}"; label="${label#-}" | |
| [ -z "$label" ] && label="default" | |
| cp "$out/$pkg.uf2" "dist/rs-key-${tag}-${label}.uf2" | |
| echo "::endgroup::" | |
| done | |
| ls -l dist | |
| - name: reproducibility gate — rebuild all 8, require bit-identical | |
| run: | | |
| # `nix build --rebuild` recompiles the derivation already in the store | |
| # and fails with a hash mismatch if the output is not bit-identical. | |
| # A non-reproducible flavor fails here, before the release is created. | |
| for pkg in firmware firmware-pqc firmware-fips firmware-fips-pqc \ | |
| firmware-no-touch firmware-no-touch-pqc \ | |
| firmware-no-touch-fips firmware-no-touch-fips-pqc; do | |
| echo "::group::nix build .#$pkg --rebuild" | |
| nix build ".#$pkg" --rebuild --no-link | |
| echo "::endgroup::" | |
| done | |
| echo "all 8 flavors rebuilt bit-identical" | |
| - name: generate the CycloneDX SBOM | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| # Scoped to the firmware crate (the shipped artifact) and its build | |
| # target; cargo-cyclonedx writes firmware/firmware.cdx.json (plus a | |
| # per-crate file for every workspace member, which we ignore). | |
| nix develop -c cargo cyclonedx \ | |
| --manifest-path firmware/Cargo.toml \ | |
| --target thumbv8m.main-none-eabihf \ | |
| --format json | |
| sbom="firmware/firmware.cdx.json" | |
| [ -f "$sbom" ] || sbom="$(ls firmware/*.cdx.json 2>/dev/null | head -1)" | |
| if [ -z "$sbom" ] || [ ! -f "$sbom" ]; then echo "no SBOM produced" >&2; exit 1; fi | |
| cp "$sbom" "dist/rs-key-${tag}-sbom.cdx.json" | |
| - name: checksums | |
| run: | | |
| cd dist | |
| sha256sum ./*.uf2 ./*.cdx.json > SHA256SUMS | |
| cat SHA256SUMS | |
| - name: attest build provenance | |
| # GitHub-native build provenance for every .uf2 — signed keyless via | |
| # Sigstore/Fulcio against this run's OIDC identity, recorded in the GitHub | |
| # attestation API + the public Rekor log (NOT a release asset, so it is | |
| # compatible with immutable releases). Verify with `gh attestation verify`. | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| with: | |
| subject-path: dist/*.uf2 | |
| - name: sign SHA256SUMS (keyless cosign) | |
| env: | |
| COSIGN_YES: "true" | |
| run: | | |
| cosign sign-blob \ | |
| --bundle dist/SHA256SUMS.cosign.bundle \ | |
| dist/SHA256SUMS | |
| - name: extract release notes from CHANGELOG | |
| run: | | |
| ver="${{ steps.tag.outputs.version }}" | |
| awk -v v="$ver" ' | |
| $0 ~ "^## \\[" v "\\]" { f = 1; print; next } | |
| /^## \[/ { if (f) exit } | |
| f { print } | |
| ' CHANGELOG.md > release-notes.md | |
| if [ ! -s release-notes.md ]; then | |
| echo "RS-Key ${{ steps.tag.outputs.tag }}" > release-notes.md | |
| fi | |
| { | |
| echo | |
| echo "---" | |
| echo "Verify a download: https://github.com/${{ github.repository }}/blob/${{ steps.tag.outputs.tag }}/docs/supply-chain.md" | |
| } >> release-notes.md | |
| cat release-notes.md | |
| - name: create the GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| gh release create "$tag" \ | |
| --title "RS-Key $tag" \ | |
| --notes-file release-notes.md \ | |
| dist/* |