ajhahn.de
← eeco
YAML 131 lines
# Release: on `v*` tag push, cross-build the six-platform matrix,
# generate the package-manager manifests, sign the checksums (keyless),
# attest build provenance, and publish everything to the GitHub Release
# for that tag. Operator tags + pushes; CI never tags or pushes on its
# own. Keyless signing uses the workflow's OIDC identity — no secrets.

name: release

on:
  push:
    tags: ['v*']

concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false

# attest-build-provenance@v2 still bundles Node-20 sub-actions; opt them
# into Node-24 now (forced runner default from 2026-06-02 regardless).
env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

permissions:
  contents: write       # create the release + upload assets
  id-token: write       # keyless cosign + provenance OIDC
  attestations: write   # build-provenance attestation

jobs:
  publish:
    name: build + sign + publish
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v5
        with:
          fetch-depth: 0
      - name: setup-go
        uses: actions/setup-go@v6
        with:
          go-version-file: go.mod
          cache: true
      - name: derive build metadata
        id: meta
        run: |
          TAG="${GITHUB_REF_NAME}"
          BUILD_DATE="$(git log -1 --format=%cI "refs/tags/${TAG}")"
          echo "tag=${TAG}"               >> "$GITHUB_OUTPUT"
          echo "build_date=${BUILD_DATE}" >> "$GITHUB_OUTPUT"
          case "${TAG}" in
            *-*) echo "prerelease=true"  >> "$GITHUB_OUTPUT" ;;
            *)   echo "prerelease=false" >> "$GITHUB_OUTPUT" ;;
          esac
      - name: cross-build matrix
        env:
          VERSION:    ${{ steps.meta.outputs.tag }}
          BUILD_DATE: ${{ steps.meta.outputs.build_date }}
        run: make release
      - name: generate package manifests
        env:
          VERSION: ${{ steps.meta.outputs.tag }}
        run: make packaging
      - name: install cosign
        uses: sigstore/cosign-installer@v3
      - name: sign checksums (keyless)
        run: |
          cosign sign-blob --yes \
            --output-signature dist/SHA256SUMS.sig \
            --output-certificate dist/SHA256SUMS.pem \
            dist/SHA256SUMS
      - name: attest build provenance
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: 'dist/eeco_*.tar.gz, dist/eeco_*.zip'
      - name: write release notes
        run: |
          cat > release-notes.md <<EOF
          eeco ${{ steps.meta.outputs.tag }}

          Pre-built binaries for darwin/linux/windows (amd64 + arm64),
          SHA256SUMS, a keyless cosign signature, and the package
          manifests (eeco.rb, eeco.json).

          Verify the checksums signature (no key needed):

              cosign verify-blob \\
                --certificate dist/SHA256SUMS.pem \\
                --certificate-identity-regexp \\
                  '^https://github.com/ajhahnde/eeco/\.github/workflows/release\.yml@refs/tags/v' \\
                --certificate-oidc-issuer https://token.actions.githubusercontent.com \\
                --signature dist/SHA256SUMS.sig \\
                SHA256SUMS

          Build provenance is attested; verify a downloaded archive with
          'gh attestation verify <archive> --repo ajhahnde/eeco'.

          Install routes and checksum verification: docs/USAGE.md §1.
          EOF
      - name: publish
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          TAG="${{ steps.meta.outputs.tag }}"
          ASSETS="dist/eeco_*.tar.gz dist/eeco_*.zip dist/SHA256SUMS \
            dist/SHA256SUMS.sig dist/SHA256SUMS.pem dist/eeco.rb dist/eeco.json \
            dist/eeco.1"
          PRE_FLAG=""
          if [ "${{ steps.meta.outputs.prerelease }}" = "true" ]; then
            PRE_FLAG="--prerelease"
          fi
          if gh release view "${TAG}" >/dev/null 2>&1; then
            gh release upload "${TAG}" ${ASSETS} --clobber
          else
            gh release create "${TAG}" ${ASSETS} \
              --title "${TAG}" \
              --notes-file release-notes.md \
              ${PRE_FLAG}
          fi
      - name: sync package-manager taps
        # Stable releases only; taps never carry a prerelease. Skips
        # cleanly when no PAT is configured, so the release still
        # succeeds before the tap repos / token exist (Phase 1 setup).
        if: steps.meta.outputs.prerelease == 'false'
        env:
          VERSION: ${{ steps.meta.outputs.tag }}
          TAP_PUSH_TOKEN: ${{ secrets.TAP_PUSH_TOKEN }}
        run: |
          if [ -z "${TAP_PUSH_TOKEN}" ]; then
            echo "TAP_PUSH_TOKEN unset; skipping tap sync" >&2
            exit 0
          fi
          ./scripts/sync-taps.sh