Release v0.36.0 (#308) #91
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 runs for the same tag. The intake-time run (gate skips) and the | |
| # post-merge re-tag run are far apart in time, so this never queues in practice; | |
| # it makes a manual "re-run all jobs" deterministic instead of racing GoReleaser. | |
| concurrency: | |
| group: release-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| # Gate: only cut a release when the tag is on main (i.e. it was created on the | |
| # squash commit by post-merge-release after the dev->main PR merged). A v* tag | |
| # that is not reachable from main is NOT released, which neutralizes a stray or | |
| # legacy direct tag push on a dev commit. A tag pushed directly onto a commit | |
| # already on main (legacy escape hatch) passes the gate immediately. | |
| gate: | |
| name: gate | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| on_main: ${{ steps.check.outputs.on_main }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Check tag is on main | |
| id: check | |
| run: | | |
| set -euo pipefail | |
| git fetch --force origin main | |
| TAG_SHA="$(git rev-list -n 1 "${GITHUB_REF#refs/tags/}")" | |
| if git merge-base --is-ancestor "$TAG_SHA" origin/main; then | |
| echo "on_main=true" >> "$GITHUB_OUTPUT" | |
| echo "Tag is on main; proceeding with release." | |
| else | |
| echo "on_main=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Tag is not on main; skipping release. A release fires only after the squash-merge creates the tag on main." | |
| fi | |
| release: | |
| needs: gate | |
| if: needs.gate.outputs.on_main == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # Create the GitHub release | |
| id-token: write # Required for OIDC / attestations | |
| attestations: write # Required for actions/attest-build-provenance | |
| actions: read # Required to read run metadata | |
| steps: | |
| ######################################## | |
| # CHECKOUT (fetch-depth 0 for changelog and GoReleaser) | |
| ######################################## | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| fetch-depth: 0 | |
| # No git write happens in this job: GoReleaser publishes over the API with an | |
| # explicit token, so there is nothing here that needs the credential left behind | |
| # in .git/config. Matches the checkout in the gate job above. | |
| persist-credentials: false | |
| ######################################## | |
| # VALIDATE TAG | |
| ######################################## | |
| - name: Validate SemVer tag | |
| run: | | |
| TAG="${GITHUB_REF#refs/tags/}" | |
| echo "Current tag: $TAG" | |
| if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z\.-]+)?$ ]]; then | |
| echo "❌ Tag '$TAG' is not a valid SemVer (expected vMAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH-rc1/beta1)" | |
| exit 1 | |
| fi | |
| echo "✔ Tag '$TAG' is valid SemVer" | |
| ######################################## | |
| # SETUP GO | |
| ######################################## | |
| - name: Set up Go | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e | |
| with: | |
| go-version-file: 'go.mod' | |
| # Privileged job (contents: write, id-token: write): opt out of the | |
| # default Go cache to avoid cache-poisoning from lower-privileged | |
| # workflows, matching codecov/codeql/security-ultimate. | |
| cache: false | |
| - name: Show Go version | |
| run: go version | |
| ######################################## | |
| # VERIFY MODULES | |
| ######################################## | |
| - name: Verify Go modules | |
| run: go mod verify | |
| ######################################## | |
| # INSTALL SYFT (for CycloneDX SBOM via GoReleaser) | |
| ######################################## | |
| - name: Install Syft (for SBOM generation) | |
| uses: anchore/sbom-action/download-syft@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2 | |
| with: | |
| syft-version: v1.19.0 | |
| ######################################## | |
| # VALIDATE THE SIGNING KEY *BEFORE* PUBLISHING | |
| # GoReleaser publishes the release (incl. SHA256SUMS) before the signing step | |
| # below; if signing then failed, the tag would go live without SHA256SUMS.sig | |
| # and both install.sh and the in-app upgrade hard-fail on the missing file, | |
| # blocking every install/upgrade. Fail here, before anything is published, if | |
| # the signing secret is missing or does not match the pinned public key. | |
| ######################################## | |
| - name: Validate release signing key before publishing | |
| env: | |
| PROXSAVE_KEY_SIGNATURE: ${{ secrets.PROXSAVE_KEY_SIGNATURE }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${PROXSAVE_KEY_SIGNATURE:-}" ]; then | |
| echo "::error::PROXSAVE_KEY_SIGNATURE is empty; refusing to publish a release that cannot be signed." >&2 | |
| exit 1 | |
| fi | |
| KEY_FILE="$(mktemp)" | |
| trap 'rm -f "$KEY_FILE"' EXIT | |
| printf '%s\n' "$PROXSAVE_KEY_SIGNATURE" > "$KEY_FILE" | |
| # Derive the public key from the signing secret and compare its base64 body | |
| # to the pinned key install.sh ships, so a missing/wrong/corrupt secret fails | |
| # HERE, before GoReleaser publishes - otherwise the tag would go live without a | |
| # usable SHA256SUMS.sig and block every install/upgrade. We compare base64 | |
| # bodies (not whole PEM files): install.sh stores the key as a shell-quoted | |
| # value (PUBKEY_PEM='-----BEGIN...'), so a naive PEM extraction is not openssl- | |
| # readable; and the dash-less awk patterns never look like a PEM block to | |
| # TestReleaseSigningKeyNoDrift, so no second hard-coded copy is introduced. | |
| derived="$(openssl pkey -in "$KEY_FILE" -pubout 2>/dev/null | awk '/BEGIN PUBLIC KEY/{f=1;next} /END PUBLIC KEY/{f=0} f{print}' | tr -d '\r\n ')" || true | |
| pinned="$(awk '/BEGIN PUBLIC KEY/{f=1;next} /END PUBLIC KEY/{f=0} f{print}' install.sh | tr -d '\r\n ')" | |
| if [ -z "$derived" ]; then | |
| echo "::error::could not derive a public key from PROXSAVE_KEY_SIGNATURE (corrupt or wrong-format signing secret)." >&2 | |
| exit 1 | |
| fi | |
| if [ -z "$pinned" ]; then | |
| echo "::error::could not read the pinned public key from install.sh." >&2 | |
| exit 1 | |
| fi | |
| if [ "$derived" != "$pinned" ]; then | |
| echo "::error::PROXSAVE_KEY_SIGNATURE does not match the pinned public key; refusing to publish." >&2 | |
| exit 1 | |
| fi | |
| echo "Release signing key validated against the pinned public key." | |
| ######################################## | |
| # GORELEASER | |
| ######################################## | |
| - name: Run GoReleaser | |
| uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7 | |
| with: | |
| version: latest | |
| workdir: ${{ github.workspace }} | |
| args: release --clean --config .github/.goreleaser.yml | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Baked into the binary via ldflags (support.supportEmail) - the maintainer | |
| # support-email recipient. Empty (secret unset) just disables the support email. | |
| EMAIL_SUPPORT: ${{ secrets.EMAIL_SUPPORT }} | |
| ######################################## | |
| # SIGN SHA256SUMS (ECDSA P-256) + upload as a release asset | |
| ######################################## | |
| - name: Sign and publish SHA256SUMS signature | |
| env: | |
| PROXSAVE_KEY_SIGNATURE: ${{ secrets.PROXSAVE_KEY_SIGNATURE }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| test -f build/SHA256SUMS | |
| KEY_FILE="$(mktemp)" | |
| trap 'rm -f "$KEY_FILE"' EXIT | |
| printf '%s\n' "$PROXSAVE_KEY_SIGNATURE" > "$KEY_FILE" | |
| # Sign the checksum file with the project's ECDSA P-256 private key. | |
| openssl dgst -sha256 -sign "$KEY_FILE" -out build/SHA256SUMS.sig build/SHA256SUMS | |
| # Defense in depth: verify the signature against the SAME public key that | |
| # install.sh pins, so a wrong/corrupt signing secret fails the release here | |
| # instead of shipping an unverifiable artifact. | |
| printf '%s\n' \ | |
| '-----BEGIN PUBLIC KEY-----' \ | |
| 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElks05mPtm1vm0YtHlSGX1HlgdXjn' \ | |
| 'liDJEnB+RgiWOQR+6xLWeX7PyauuMxUh/HNnvBQAokK91fLWes4r9Xlwzw==' \ | |
| '-----END PUBLIC KEY-----' > pinned_pub.pem | |
| openssl dgst -sha256 -verify pinned_pub.pem -signature build/SHA256SUMS.sig build/SHA256SUMS | |
| # Upload the signature to the release GoReleaser just created. | |
| gh release upload "${GITHUB_REF#refs/tags/}" build/SHA256SUMS.sig --clobber | |
| ######################################## | |
| # BUILD PROVENANCE ATTESTATION | |
| ######################################## | |
| - name: Attest Build Provenance | |
| uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 | |
| with: | |
| subject-path: build/proxsave_* |