name: release

# Builds and publishes the two container images from a version tag.
#
# Tagging v1.2.3 publishes chitai-backend and chitai-frontend as 1.2.3, 1.2, 1 and latest.
# A prerelease tag (v1.2.3-rc.1) publishes only 1.2.3-rc.1 and leaves latest alone, which
# makes -rc tags a safe way to exercise this workflow.
#
# Note that `uses: docker/...` does not mean github.com here the way it would on GitHub. Gitea
# resolves a bare reference against the instance's DEFAULT_ACTIONS_URL, which defaults to
# gitea.com; all five actions below are mirrored there at these tags. If that setting is ever
# pointed somewhere without them, set it to `github` in app.ini rather than editing this file.
#
# Requires a runner with a working Docker daemon (a docker:dind sidecar, or host mode with
# the socket mounted) and, for the smoke job, the compose plugin.

on:
  push:
    tags:
      - 'v*'

env:
  REGISTRY: git.jaroszew.ski

jobs:
  # The blocking half of ci.yml, run again on the tagged commit so a release cannot publish
  # an image whose tests fail. Deliberately duplicated rather than shared: Gitea's support
  # for reusable workflows is thinner than GitHub's, and this is a dozen lines.
  # Keep in step with ci.yml.
  quality:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        run: |
          curl -LsSf https://astral.sh/uv/install.sh | sh
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Backend format, lint and tests
        working-directory: backend
        run: |
          uv sync --locked
          uv run ruff format --check src/
          uv run ruff check src/
          uv run pytest tests/ -q

      - uses: actions/setup-node@v4
        with:
          node-version: 24

      - name: Frontend format
        working-directory: frontend
        run: |
          corepack enable
          pnpm install --frozen-lockfile
          pnpm exec prettier --check .

  build:
    needs: quality
    runs-on: ubuntu-latest

    strategy:
      # The two images are independent artifacts; don't cancel a good build for a bad one.
      fail-fast: false
      matrix:
        include:
          - component: backend
            context: ./backend
          - component: frontend
            context: ./frontend

    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.REGISTRY_TOKEN || secrets.GITEA_TOKEN }}

      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ github.repository }}-${{ matrix.component }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
          labels: |
            org.opencontainers.image.title=chitai-${{ matrix.component }}
            org.opencontainers.image.source=https://git.jaroszew.ski/${{ github.repository }}

      - uses: docker/build-push-action@v6
        with:
          context: ${{ matrix.context }}
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          # Relies on the runner's cache server. If it is disabled, drop these two lines —
          # a cold build of both images is only a few minutes.
          cache-from: type=gha
          cache-to: type=gha,mode=max

  smoke:
    needs: build
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.REGISTRY_TOKEN || secrets.GITEA_TOKEN }}

      # Boots the stack from the images that were just pushed, rather than rebuilding them.
      # This is what catches migrations failing from entrypoint.sh, the frontend being unable
      # to reach the backend, and a missing runtime environment variable.
      - name: Boot the published images
        env:
          TAG: ${{ github.ref_name }}
        run: |
          cp .env.prod-example .env
          echo "CHITAI_VERSION=${TAG#v}" >> .env
          mkdir -p libraries
          docker compose pull backend frontend
          docker compose up -d --wait --wait-timeout 180
          curl -fsS http://localhost:8000/healthcheck
          curl -fsS http://localhost:3000/login > /dev/null

      - name: Tear down
        if: always()
        run: |
          docker compose logs --no-color || true
          docker compose down -v || true
