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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: "1.26" - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "24" 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@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Login to GitHub Container Registry uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.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@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7.2.1 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 }} # Force-set the release tag from the triggering ref to bypass # goreleaser's git-describe-based auto-detection. When two # lightweight tags point at the same commit (e.g. v0.4.0 cut # right on top of v0.4.0-rc.1 with no intervening commits), # git-describe's tiebreaker is non-deterministic across hosts # — locally it picked v0.4.0, the CI runner picked v0.4.0-rc.1 # during the v0.4.0 ship and stamped artifacts with the RC # version. github.ref_name is unambiguous: it's exactly the # tag that triggered the workflow. See PLAYB-1160 failure modes. GORELEASER_CURRENT_TAG: ${{ github.ref_name }} # 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"