Fix gamma site count test #39
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*' | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| packages: write # for the docker job that pushes to ghcr after release | |
| jobs: | |
| build: | |
| name: Test and build binaries | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| - name: Run tests | |
| run: go test -race -count=1 ./... | |
| - name: Build binaries | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/}" | |
| COMMIT="${GITHUB_SHA::8}" | |
| DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| LDFLAGS="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" | |
| mkdir -p dist | |
| platforms=( | |
| "linux/amd64" | |
| "linux/arm64" | |
| "darwin/amd64" | |
| "darwin/arm64" | |
| "windows/amd64" | |
| ) | |
| for platform in "${platforms[@]}"; do | |
| GOOS="${platform%/*}" | |
| GOARCH="${platform#*/}" | |
| binary="fss" | |
| archive="fss-${VERSION}-${GOOS}-${GOARCH}" | |
| if [ "$GOOS" = "windows" ]; then | |
| binary="fss.exe" | |
| fi | |
| echo "Building ${GOOS}/${GOARCH}..." | |
| GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "$LDFLAGS" -o "dist/${binary}" . | |
| cd dist | |
| if [ "$GOOS" = "windows" ]; then | |
| zip "${archive}.zip" "${binary}" | |
| else | |
| tar czf "${archive}.tar.gz" "${binary}" | |
| fi | |
| rm "${binary}" | |
| cd .. | |
| done | |
| - name: Build .deb and .rpm packages | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| COMMIT="${GITHUB_SHA::8}" | |
| DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| LDFLAGS="-s -w -X main.version=v${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" | |
| # Pinned so a breaking nfpm bump can't silently corrupt .deb/.rpm | |
| # artifacts on the next release. Bump deliberately. | |
| go install github.com/goreleaser/nfpm/v2/cmd/[email protected] | |
| for arch in amd64 arm64; do | |
| echo "Packaging linux/${arch}..." | |
| GOOS=linux GOARCH=$arch go build -ldflags "$LDFLAGS" -o dist/fss . | |
| GOARCH=$arch VERSION=$VERSION nfpm package --packager deb --target dist/ | |
| GOARCH=$arch VERSION=$VERSION nfpm package --packager rpm --target dist/ | |
| rm dist/fss | |
| done | |
| - name: Upload release artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-binaries | |
| path: dist/ | |
| retention-days: 7 | |
| release: | |
| name: Publish GitHub release | |
| runs-on: ubuntu-latest | |
| needs: build | |
| # Pauses here until a reviewer of the `manual-smoke-gate` environment | |
| # approves — the approver attests they have run the manual integration | |
| # smoke tests (`make smoke`) that CI cannot run because Cloudflare blocks | |
| # GitHub-runner IP ranges. The downstream `docker` job inherits this gate | |
| # transitively via `needs: release`, so one approval covers both. | |
| # See CONTRIBUTING.md → "Cutting a release" for the full checklist. | |
| environment: | |
| name: manual-smoke-gate | |
| url: ${{ github.server_url }}/${{ github.repository }}/blob/master/CONTRIBUTING.md#cutting-a-release | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Pre-release reminder | |
| run: | | |
| echo "::notice title=Manual checks confirmed::Approver attests they have run the release checklist (integration smoke tests, changelog review)." | |
| - name: Download release artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: release-binaries | |
| path: dist/ | |
| - name: Generate SHA256SUMS | |
| run: | | |
| cd dist | |
| # Capture checksums into a variable BEFORE creating SHA256SUMS — bash | |
| # creates redirect targets before expanding globs, so `* > SHA256SUMS` | |
| # would otherwise hash an empty SHA256SUMS into its own listing. | |
| # Sort by filename so the file is stable and diffable across runs. | |
| sums=$(sha256sum -- *) | |
| echo "$sums" | LC_ALL=C sort -k2 > SHA256SUMS | |
| echo "Generated SHA256SUMS:" | |
| cat SHA256SUMS | |
| # Self-check: every line must verify cleanly before we hand it to users. | |
| sha256sum --check --strict SHA256SUMS | |
| - name: Generate changelog | |
| id: changelog | |
| run: | | |
| PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") | |
| if [ -z "$PREV_TAG" ]; then | |
| CHANGELOG=$(git log --oneline --no-merges) | |
| else | |
| CHANGELOG=$(git log --oneline --no-merges "${PREV_TAG}..HEAD") | |
| fi | |
| echo "changelog<<EOF" >> $GITHUB_OUTPUT | |
| echo "$CHANGELOG" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| files: dist/* | |
| generate_release_notes: true | |
| body: | | |
| ## Changes | |
| ${{ steps.changelog.outputs.changelog }} | |
| ## Installation | |
| Download the binary for your platform, extract, and place it on your PATH: | |
| ```sh | |
| # Linux (amd64) | |
| tar xzf fss-${{ github.ref_name }}-linux-amd64.tar.gz | |
| chmod +x fss | |
| ./fss --version | |
| # macOS (Apple Silicon) | |
| tar xzf fss-${{ github.ref_name }}-darwin-arm64.tar.gz | |
| chmod +x fss | |
| ./fss --version | |
| ``` | |
| ### Linux packages | |
| ```sh | |
| # Arch Linux (AUR) | |
| yay -S fss | |
| # Debian / Ubuntu | |
| sudo dpkg -i fss_*_amd64.deb | |
| # Fedora / RHEL | |
| sudo rpm -i fss_*_amd64.rpm | |
| ``` | |
| Or build from source: | |
| ```sh | |
| go install github.com/Wasylq/FSS@${{ github.ref_name }} | |
| ``` | |
| Or pull the Docker image: | |
| ```sh | |
| docker pull ghcr.io/wasylq/fss:${{ github.ref_name }} | |
| ``` | |
| ## Verifying your download | |
| Every artifact attached to this release is hashed in **`SHA256SUMS`**. After downloading, verify the file you have against that line: | |
| **Linux / macOS** — download `SHA256SUMS` next to your artifact, then run: | |
| ```sh | |
| sha256sum --ignore-missing --check SHA256SUMS | |
| # fss-${{ github.ref_name }}-linux-amd64.tar.gz: OK | |
| ``` | |
| On macOS, `shasum -a 256` works the same way (`shasum -a 256 -c SHA256SUMS`). | |
| **Windows (PowerShell)**: | |
| ```powershell | |
| # Compare the printed hash against the matching line in SHA256SUMS. | |
| Get-FileHash fss-${{ github.ref_name }}-windows-amd64.zip -Algorithm SHA256 | |
| ``` | |
| A mismatch — or `FAILED` from `sha256sum --check` — means the file is corrupted or tampered with; do not run it. | |
| ### Verifying the Docker image | |
| The Docker image is published with a [SLSA v1 build attestation](https://slsa.dev/) that you can verify before pulling. The attestation is stored on the GitHub release run (not embedded in the image index, so the manifest list stays clean). | |
| Requires the [`gh` CLI](https://cli.github.com/) v2.49+: | |
| ```sh | |
| # Look up the image digest you want to verify | |
| docker pull ghcr.io/wasylq/fss:${{ github.ref_name }} | |
| digest=$(docker buildx imagetools inspect ghcr.io/wasylq/fss:${{ github.ref_name }} --format '{{json .Manifest}}' | jq -r '.digest') | |
| # Verify provenance against the GitHub workflow that built it | |
| gh attestation verify oci://ghcr.io/wasylq/fss@$digest --owner Wasylq | |
| ``` | |
| A successful run prints the build workflow's identity (commit SHA, repo, runner). Failure means the image wasn't built by this repo's release pipeline. | |
| aur: | |
| name: Publish to AUR | |
| runs-on: ubuntu-latest | |
| # Decoupled from the `release` job: a transient AUR/SSH failure here used | |
| # to leave AUR stale because the steps lived in the release job, which is | |
| # one-shot and gates everything downstream. As its own job it can be re-run | |
| # from the Actions UI without re-cutting the GitHub release. | |
| needs: release | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Stamp PKGBUILD version | |
| # Defensive substitution: matches any prior pkgver= line so the step | |
| # still works if the sentinel `VERSION` placeholder is ever changed. | |
| run: sed -i "s/^pkgver=.*/pkgver=${GITHUB_REF#refs/tags/v}/" ./packaging/aur/PKGBUILD | |
| # AUR pushes can fail transiently on the SSH side (aur.archlinux.org | |
| # accepts only a handful of concurrent sessions). We retry once with a | |
| # short delay before surfacing a real failure to the user — the job can | |
| # still be re-run from the Actions UI if even the retry fails. | |
| - name: Publish to AUR (attempt 1) | |
| id: aur1 | |
| uses: KSXGitHub/[email protected] | |
| continue-on-error: true | |
| with: | |
| pkgname: fss | |
| pkgbuild: ./packaging/aur/PKGBUILD | |
| commit_username: Wasylq | |
| commit_email: [email protected] | |
| ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| commit_message: "Update to ${{ github.ref_name }}" | |
| # Run `updpkgsums` so the committed PKGBUILD ships real sha256 hashes | |
| # for the GitHub source tarball — replaces the SKIP placeholder. | |
| updpkgsums: true | |
| - name: Wait before retry | |
| if: steps.aur1.outcome == 'failure' | |
| run: sleep 30 | |
| - name: Publish to AUR (attempt 2) | |
| if: steps.aur1.outcome == 'failure' | |
| uses: KSXGitHub/[email protected] | |
| with: | |
| pkgname: fss | |
| pkgbuild: ./packaging/aur/PKGBUILD | |
| commit_username: Wasylq | |
| commit_email: [email protected] | |
| ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| commit_message: "Update to ${{ github.ref_name }}" | |
| updpkgsums: true | |
| docker: | |
| name: Build and push docker image | |
| runs-on: ubuntu-latest | |
| # Runs only after the gated release job succeeds, so no image lands on | |
| # GHCR unless the manual smoke-test gate was approved. | |
| needs: release | |
| permissions: | |
| contents: read | |
| packages: write | |
| # Needed for actions/attest-build-provenance — OIDC + attestation write. | |
| # Attestation lives on the workflow run, not in the registry image | |
| # index (`push-to-registry: false`), so the manifest list stays clean | |
| # of `unknown/unknown` entries that pollute GHCR's UI. | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute image metadata | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/${{ github.repository }} | |
| tags: | | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}} | |
| type=raw,value=latest | |
| - name: Resolve build args | |
| id: args | |
| run: | | |
| echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" | |
| echo "commit=${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT" | |
| echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" | |
| # Mirror go.mod's `go` directive into the Docker build so the | |
| # builder image always matches the toolchain go.mod declares. | |
| echo "go_version=$(./scripts/go-version.sh)" >> "$GITHUB_OUTPUT" | |
| - name: Build and push | |
| id: build | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| # Intentionally disabled: provenance: true embeds attestation | |
| # manifests in the image index as `unknown/unknown` entries, | |
| # which clutters GHCR's UI. SLSA provenance is generated below | |
| # via `actions/attest-build-provenance` and stored on the | |
| # workflow run instead, verifiable via `gh attestation verify`. | |
| provenance: false | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-args: | | |
| GO_VERSION=${{ steps.args.outputs.go_version }} | |
| VERSION=${{ steps.args.outputs.version }} | |
| COMMIT=${{ steps.args.outputs.commit }} | |
| DATE=${{ steps.args.outputs.date }} | |
| - name: Attest build provenance | |
| # SLSA v1 attestation pinned to the pushed image digest. Stored on | |
| # the workflow run, not in the registry — keeps the multi-arch | |
| # manifest list free of `unknown/unknown` entries. Users verify with | |
| # gh attestation verify oci://ghcr.io/<owner>/<repo>@<digest> --owner <owner> | |
| # (see the release notes' "Verifying the docker image" section). | |
| uses: actions/[email protected] | |
| with: | |
| subject-name: ghcr.io/${{ github.repository }} | |
| subject-digest: ${{ steps.build.outputs.digest }} | |
| push-to-registry: false | |
| - name: Lowercase image repo for Trivy | |
| # OCI references must be all-lowercase. ${{ github.repository }} can | |
| # contain uppercase (e.g. "Wasylq/FSS"), and GitHub expressions have | |
| # no case-conversion function — so we bounce through bash to produce | |
| # a lowercase ref that Trivy can parse. Same fix as docker.yml. | |
| run: echo "IMAGE_REPO_LC=ghcr.io/${REPO,,}" >> "$GITHUB_ENV" | |
| env: | |
| REPO: ${{ github.repository }} | |
| - name: Trivy scan (informational) | |
| # Reports HIGH/CRITICAL CVEs in the pushed image; informational only | |
| # (`exit-code: '0'`) for the same reason govulncheck is informational | |
| # — a "fail on any finding" gate would force base-image bumps on | |
| # every new CVE. Findings show up in the workflow log; bump deliberately. | |
| uses: aquasecurity/[email protected] | |
| with: | |
| image-ref: ${{ env.IMAGE_REPO_LC }}@${{ steps.build.outputs.digest }} | |
| severity: HIGH,CRITICAL | |
| format: table | |
| exit-code: '0' | |
| ignore-unfixed: true |