mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-21 18:13:26 +00:00
5673522608
When two lightweight tags point at the same commit (v0.4.0 cut on top
of v0.4.0-rc.1 with no intervening commits, per PLAYB-1160), goreleaser's
git-describe-based auto-detection picked the wrong one on the CI runner
and stamped v0.4.0 artifacts with version 0.4.0-rc.1 — they then collided
with the existing RC release-page assets and the workflow aborted with
422 already_exists. Setting GORELEASER_CURRENT_TAG to ${{ github.ref_name }}
bypasses the auto-detection: it's exactly the tag that triggered the run.
Root-cause-fixes the v0.4.0 ship failure and prevents recurrence for any
future RC → stable sequence where the stable tag sits on top of the RC
without an intervening commit.
150 lines
7.2 KiB
YAML
150 lines
7.2 KiB
YAML
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"
|