From 4b5764f4081d07d620bf316c8487da407526d468 Mon Sep 17 00:00:00 2001 From: DURAND Malo Date: Sun, 27 Sep 2026 17:23:00 +0200 Subject: [PATCH] chore: ci setup --- .githooks/pre-commit | 5 +-- .github/workflows/ci.yml | 83 ++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 4 +- docs/setup.md | 18 ++++++--- scripts/lint.sh | 27 +++++++++++++ 5 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100755 scripts/lint.sh diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 8a56ce2..e17fc24 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -19,8 +19,5 @@ if ! command -v golangci-lint >/dev/null 2>&1; then exit 1 fi -root=$(git rev-parse --show-toplevel) -cfg="$root/proxy/.golangci.yml" # Lints the working tree, not just the staged content. -(cd "$root/proxy" && golangci-lint run ./... && golangci-lint run --build-tags stress ./...) -(cd "$root/tests/stress/driver" && golangci-lint run --config "$cfg" ./...) +exec "$(git rev-parse --show-toplevel)/scripts/lint.sh" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f2ab56a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,83 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: proxy/go.mod + cache-dependency-path: "**/go.sum" + - uses: golangci/golangci-lint-action@v9 + with: + version: v2.14 + install-only: true + - run: ./scripts/lint.sh + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: proxy/go.mod + cache-dependency-path: proxy/go.sum + - run: go test ./... -race -count=1 + working-directory: proxy + + image: + needs: [lint, test] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write # OIDC token that signs the provenance attestation + attestations: write + steps: + - uses: actions/checkout@v7 + - uses: docker/setup-buildx-action@v4 + # GHCR image names must be lowercase; the org name isn't. + - run: echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" + - id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=sha + - if: github.event_name == 'push' + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # PRs only fill the cache; main reuses it and pushes. PR caches are + # scoped to their ref, so main's own cache is what PRs start from. + - id: build + uses: docker/build-push-action@v7 + with: + context: proxy + push: ${{ github.event_name == 'push' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + - if: github.event_name == 'push' + uses: actions/attest-build-provenance@v4 + with: + subject-name: ${{ env.IMAGE }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true diff --git a/CLAUDE.md b/CLAUDE.md index 1cba957..5688e64 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -146,9 +146,9 @@ docker compose logs -f hallmaster-proxy go vet ./... && go vet -tags stress ./... go test ./... -race -count=1 go test -run '^$' -bench . -benchmem ./... # in-process microbenchmarks -golangci-lint run && golangci-lint run --build-tags stress ./... # v2 config; brew install golangci-lint +../scripts/lint.sh # golangci-lint v2, all tags + stress driver; quiet on success -# once per clone (from repo root): pre-commit = secret guard + golangci-lint +# once per clone (from repo root): pre-commit = secret guard + scripts/lint.sh git config core.hooksPath .githooks # end-to-end load harness (from repo root) diff --git a/docs/setup.md b/docs/setup.md index f0c899e..23b5b21 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -198,10 +198,11 @@ Useful while developing: - `go test ./... -race -count=1` — run the full test suite, including the end-to-end handler harness (`handlers/https_test.go`) and the `Serve` graceful-shutdown tests (`mitm_test.go`). -- `golangci-lint run` — full lint per the config in `.golangci.yml` +- `../scripts/lint.sh` — full lint per the config in `.golangci.yml` (`errcheck`, `govet`, `ineffassign`, `staticcheck`, `unused`, `gofmt`, - `goimports`). The config uses the v2 format, so it needs golangci-lint 2.x - (`brew install golangci-lint`). + `goimports`) over the proxy (default and `stress` build tags) and the + stress driver. Prints one line on success. The config uses the v2 format, + so it needs golangci-lint 2.x (`brew install golangci-lint`). Enable the pre-commit hook once per clone: @@ -211,9 +212,14 @@ git config core.hooksPath .githooks [.githooks/pre-commit](../.githooks/pre-commit) refuses to commit `.env` / `.env.*` (except `*.example`), `*.pem` and `*.key` (removing them from the index still -works). When Go files are staged, it also runs `golangci-lint` on the proxy -(default and `stress` build tags) and on the stress driver. It lints the -working tree, not just the staged changes. +works). When Go files are staged, it also runs `scripts/lint.sh`. It lints +the working tree, not just the staged changes. + +CI ([.github/workflows/ci.yml](../.github/workflows/ci.yml)) runs the same +lint script and the race tests on every PR and push to `main`, then builds +the proxy image with a GitHub Actions layer cache. Only pushes to `main` +publish it, to `ghcr.io/hallmasterorg/hallmaster-proxy` (`latest` and +`sha-`), with a signed build-provenance attestation. When you change the Go code, rebuild the container with: diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 0000000..78327a0 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Lints every Go target with golangci-lint. Used by the pre-commit hook and CI. +# Quiet on success (one summary line); issues print under their target's name. +set -u + +root=$(cd "$(dirname "$0")/.." && pwd) +cfg="$root/proxy/.golangci.yml" +failed="" + +lint() { # [golangci-lint args...] + name=$1 dir=$2 + shift 2 + if ! out=$(cd "$dir" && golangci-lint run --show-stats=false --config "$cfg" "$@" ./... 2>&1); then + printf '\n── %s ──\n%s\n' "$name" "$out" >&2 + failed="$failed $name" + fi +} + +lint proxy "$root/proxy" +lint proxy/stress "$root/proxy" --build-tags stress +lint stress-driver "$root/tests/stress/driver" + +if [ -n "$failed" ]; then + echo "lint: FAILED:$failed" >&2 + exit 1 +fi +echo "lint: ok (proxy, proxy/stress, stress-driver)"