feat(release): sign + notarize macOS binaries (IDEA-830) (#278) #3
Workflow file for this run
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
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| # Serialize all release runs. If two v* tags land close together (e.g. | |
| # rc.3 then rc.4 within a minute), queue rather than race — they share | |
| # mutable outputs (the GHCR `:latest` tag, the homebrew cask in the | |
| # separate tap repo, the GitHub Releases page) and parallel runs would | |
| # interleave nondeterministically. Group is intentionally NOT keyed by | |
| # `github.ref`: we want different tag names to serialize too, not just | |
| # repeat pushes of the same tag. cancel-in-progress=false so a queued | |
| # tag never aborts a release mid-publish (which could leave GHCR and the | |
| # brew tap in inconsistent states). | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| packages: write | |
| # id-token: write is required for keyless cosign signing (GitHub OIDC | |
| # exchanges this workflow's identity token for a short-lived Fulcio | |
| # certificate) and for actions/attest-build-provenance to mint SLSA | |
| # v1 provenance statements. | |
| id-token: write | |
| # attestations: write is required by actions/attest-build-provenance so | |
| # the resulting provenance bundles can be stored against the repo. | |
| attestations: write | |
| # All third-party Actions are pinned to a 40-char commit SHA with a trailing | |
| # '# vX.Y.Z' comment so a compromised maintainer or moved tag cannot silently | |
| # execute attacker code in the release pipeline (this workflow has | |
| # contents:write + packages:write + the GHCR token, so a malicious action | |
| # here could publish tampered binaries). Bump the SHA + comment together. | |
| jobs: | |
| release: | |
| name: Build & Release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 | |
| with: | |
| go-version: "1.26" | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: "22" | |
| cache: "npm" | |
| cache-dependency-path: web/package-lock.json | |
| - name: Create web build placeholder for tests | |
| run: mkdir -p web/build && echo "placeholder" > web/build/.gitkeep | |
| - name: Run tests | |
| run: go test ./... | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # cosign + syft need to be on PATH before goreleaser runs — goreleaser | |
| # shells out to both for the signs/docker_signs/sboms sections. | |
| - name: Install cosign | |
| uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5 # v3.10.1 | |
| - name: Install syft (for SBOM generation) | |
| uses: anchore/sbom-action/download-syft@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0 | |
| # Build the SvelteKit web UI before GoReleaser so the static assets | |
| # get embedded into the Go binary. Done as a dedicated step (instead | |
| # of a goreleaser `before:` hook) so the npm install/build does NOT | |
| # inherit the MACOS_* signing secrets — those are scoped only to the | |
| # `Run GoReleaser` step's env block below. This isolates the 5-year | |
| # Developer ID cert from any npm supply-chain compromise during | |
| # dependency install. | |
| - name: Build web UI | |
| run: cd web && npm ci && npm run build | |
| - name: Run GoReleaser | |
| id: goreleaser | |
| # GoReleaser binary is pinned to an exact version (not "~> v2") to | |
| # match the SHA-pinning policy applied to the Actions themselves — | |
| # see the comment at the top of this file. With Apple signing | |
| # credentials now flowing through this step, a compromised or | |
| # regressed GoReleaser release would carry meaningful blast radius; | |
| # pinning forces an explicit, reviewed bump. | |
| uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 | |
| with: | |
| version: "v2.15.4" | |
| # --timeout=2h overrides GoReleaser's 1h default. With Apple | |
| # notarization (`wait: true`, up to 20m per the .goreleaser.yaml | |
| # notarize block) layered on top of build + cosign blob-sign + | |
| # SBOM + multi-arch docker manifest, slow notary days could push | |
| # close to the default ceiling. 2h gives comfortable headroom | |
| # without burning excessive Action minutes when notarization | |
| # actually fails fast (the worker exits as soon as Apple replies). | |
| args: release --clean --timeout=2h | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Cross-repo PAT for pushing the brew formula to PerpetualSoftware/homebrew-tap. | |
| # The default GITHUB_TOKEN above only has access to PerpetualSoftware/pad. | |
| # Add this secret in repo settings before tagging a release that ships | |
| # a brew formula — without it goreleaser fails at the brew publish step. | |
| HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} | |
| # macOS code-signing + Apple notarization (per IDEA-830). The | |
| # `notarize:` block in .goreleaser.yaml is gated on MACOS_CERT_P12 | |
| # being set, so PR builds + snapshot mode skip cleanly when these | |
| # are absent. The .p12 cert and .p8 notary key are stored | |
| # base64-encoded; GoReleaser's Quill backend decodes them in-process, | |
| # so no external signing tool needs to be installed on the runner. | |
| MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} | |
| MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} | |
| MACOS_NOTARY_KEY_P8: ${{ secrets.MACOS_NOTARY_KEY_P8 }} | |
| MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} | |
| MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} | |
| # SLSA build provenance for every archive GoReleaser produced. | |
| # Writes a Sigstore-backed attestation to the repo so downstream | |
| # consumers can verify this binary was actually built by this | |
| # workflow from this commit, e.g.: | |
| # gh attestation verify pad_v1.0.0_linux_amd64.tar.gz \ | |
| # --repo PerpetualSoftware/pad | |
| - name: Generate build provenance for archives | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| with: | |
| subject-path: "dist/pad_*_*_*.tar.gz,dist/pad_*_*_*.zip" |