# CI workflow — runs on every PR into main.
#
# Notes for anyone editing this file:
#
# Custom runner image
#   Every job runs inside `gitea.dcglab.co.uk/steve/ci-runner-go`
#   (recipe: https://gitea.dcglab.co.uk/steve/ci/src/branch/main/images/ci-runner-go).
#   That image already ships:
#     * Go on PATH at /usr/local/go/bin (so `actions/setup-go` is
#       redundant and intentionally NOT used here — the action would
#       otherwise re-download Go on every job)
#     * Node.js + npm (used by docs / e2e workflows)
#     * Docker CLI, Buildx, Compose v2 (used by docker-build steps)
#   When bumping the Go floor, push a new ci-runner-go image with
#   the matching Go version and bump the date pin in IMAGE below.
#
# Self-hosted runner expectations
#   Each runner host bind-mounts persistent volumes for
#   /root/go/pkg/mod (GOMODCACHE), /root/.cache/go-build (GOCACHE),
#   and /root/.cache/act (action clones) into every job container —
#   regardless of which image the container is built from. As a
#   result:
#     * Common GitHub actions (actions/checkout, actions/upload-artifact,
#       golangci/golangci-lint-action) are pre-cloned into
#       /root/.cache/act on the runner, so the per-job
#       "git clone https://github.com/actions/..." step is a fetch,
#       not a full clone.
#     * golangci-lint is pre-installed at /usr/local/bin/golangci-lint
#       on the runner host BUT that's outside the job's filesystem
#       view; the golangci-lint-action below pins a specific version
#       and re-downloads — that's fine (deterministic CI > marginal
#       speed).
#
# Build matrix
#   Linux amd64 + arm64 + Windows amd64. CGO_ENABLED=0 throughout —
#   modernc.org/sqlite is pure-Go so no cross-compile toolchain is
#   needed. -trimpath + -ldflags="-s -w" for reproducible, smaller
#   binaries.
#
# Go version
#   Anchored by the ci-runner-go image (currently Go 1.25.7). Floor
#   is set by the heaviest dep (modernc.org/sqlite v1.50+ requires
#   Go 1.23+; we run 1.25 so golangci-lint's Go-version compatibility
#   check is happy — see the version pin in the lint job).
#
# upload-artifact
#   Pinned at v3 historically; v3 was deprecated upstream. v4 should
#   work but hasn't been validated against this runner's act_runner
#   version yet. Bump when convenient.

name: CI

on:
  pull_request:
    branches: [main]

# Force bash as the default shell. With `container:` set on every
# job, Gitea Actions otherwise picks `sh -e` and our `set -euo
# pipefail` fails on dash with "Illegal option -o pipefail".
defaults:
  run:
    shell: bash

jobs:
  test:
    # Sharded by package group. server/http and store are the two
    # heavy packages (~156s and ~75s in CI respectively under
    # `-race`); pulling them onto their own runners lets each shard
    # have all CPUs to itself instead of CPU-starving each other on
    # one runner. The third shard ("rest") covers everything else.
    name: Test (${{ matrix.name }})
    runs-on: ubuntu-latest
    container:
      image: docker.dcglab.co.uk/ci-runner-go:2026-05-15
      credentials:
        username: ${{ secrets.ZOT_USERNAME }}
        password: ${{ secrets.ZOT_PASSWORD }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: server-http
            packages: ./internal/server/http/...
          - name: store
            packages: ./internal/store/...
          - name: rest
            # Computed at runtime — see the "go test" step below.
            packages: ""
    steps:
      - uses: actions/checkout@v4
      - name: go vet
        run: go vet ./...
      - name: go test
        run: |
          set -euo pipefail
          if [ -n "${{ matrix.packages }}" ]; then
            pkgs="${{ matrix.packages }}"
          else
            # "rest" shard: everything except the dedicated shards.
            pkgs=$(go list ./... \
              | grep -v '/internal/server/http$' \
              | grep -v '/internal/store$')
          fi
          # shellcheck disable=SC2086
          go test -race -coverprofile=coverage.out $pkgs
      - name: coverage summary
        run: go tool cover -func=coverage.out | tail -1

  lint:
    name: Lint
    runs-on: ubuntu-latest
    container:
      image: docker.dcglab.co.uk/ci-runner-go:2026-05-15
      credentials:
        username: ${{ secrets.ZOT_USERNAME }}
        password: ${{ secrets.ZOT_PASSWORD }}
    steps:
      - uses: actions/checkout@v4
      - uses: golangci/golangci-lint-action@v7
        with:
          # Must be built against the same Go release as go.mod targets,
          # otherwise the linter refuses to load with "Go language
          # version used to build golangci-lint is lower than the
          # targeted Go version". v2.5.0 is the first v2.x line built
          # with Go 1.25; bump in lockstep with go.mod.
          version: v2.5.0
          args: --timeout=5m

  build:
    name: Build (${{ matrix.goos }}/${{ matrix.goarch }})
    runs-on: ubuntu-latest
    container:
      image: docker.dcglab.co.uk/ci-runner-go:2026-05-15
      credentials:
        username: ${{ secrets.ZOT_USERNAME }}
        password: ${{ secrets.ZOT_PASSWORD }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - goos: linux
            goarch: amd64
          - goos: linux
            goarch: arm64
          - goos: windows
            goarch: amd64
            ext: ".exe"
    steps:
      - uses: actions/checkout@v4
      - name: build server + agent
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          CGO_ENABLED: "0"
        run: |
          mkdir -p bin
          go build -trimpath -ldflags="-s -w" \
            -o bin/restic-manager-server-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }} \
            ./cmd/server
          go build -trimpath -ldflags="-s -w" \
            -o bin/restic-manager-agent-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }} \
            ./cmd/agent
      - uses: actions/upload-artifact@v3
        with:
          name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
          path: bin/*
          retention-days: 7
