name: release

# Builds and deploys the production environment on `v*` tag push.
# Runs on the self-hosted runner via Docker-out-of-Docker; images are
# tagged with the actual git tag (e.g. v1.0.0) so rollback is
#   `TAG=<previous> docker compose -f docker-compose.prod.yml -p archiv-production up -d --wait`
#
# Operational assumptions (see docs/DEPLOYMENT.md §3 for the full setup):
#
#   1. Single-tenant self-hosted runner. The "Write production env file"
#      step writes every secret to .env.production on the runner
#      filesystem; the `if: always()` cleanup step removes it. A
#      multi-tenant runner would need to switch to
#      `docker compose --env-file <(stdin)` instead.
#
#   2. Host docker layer cache is authoritative. There is no
#      actions/cache; we rely on the host daemon to keep Maven and npm
#      layers warm between runs. A `docker system prune` on the host
#      will cause the next release build to be cold (5–10 min slower).
#
# Production environment:
#   - project name: archiv-production
#   - host ports:   backend 8080, frontend 3000
#   - profile:      (none) — mailpit is excluded; real SMTP relay is used
#
# The obs-stack deploy, Caddy reload, and smoke test are shared with
# nightly.yml via the composite actions under .gitea/actions/ (ADR-029).
# actions/checkout MUST stay the first step: a local `uses: ./…` action
# only exists on disk after checkout.
#
# Required Gitea secrets:
#   PROD_POSTGRES_PASSWORD
#   PROD_MINIO_PASSWORD
#   PROD_MINIO_APP_PASSWORD
#   PROD_OCR_TRAINING_TOKEN
#   PROD_APP_ADMIN_USERNAME       (CRITICAL: see docs/DEPLOYMENT.md)
#   PROD_APP_ADMIN_PASSWORD       (CRITICAL: locked in on first deploy)
#   MAIL_HOST
#   MAIL_PORT
#   MAIL_USERNAME
#   MAIL_PASSWORD
#   GRAFANA_ADMIN_PASSWORD
#   GRAFANA_DB_PASSWORD           (read-only grafana_reader DB role, issue #651)
#   GLITCHTIP_SECRET_KEY
#   SENTRY_DSN                    (set after GlitchTip first-run; empty = Sentry disabled)

on:
  push:
    tags:
      - "v*"

env:
  DOCKER_BUILDKIT: "1"

jobs:
  deploy-production:
    # See nightly.yml — same rationale: `ubuntu-latest` matches the
    # advertised label of our single-tenant self-hosted runner.
    runs-on: ubuntu-latest
    steps:
      # MUST be first: the composite actions below live under .gitea/actions/
      # and only exist on disk once the repo is checked out (ADR-029).
      - uses: actions/checkout@v4

      - name: Write production env file
        run: |
          cat > .env.production <<EOF
          TAG=${{ gitea.ref_name }}
          PORT_BACKEND=8080
          PORT_FRONTEND=3000
          APP_DOMAIN=archiv.raddatz.cloud
          POSTGRES_PASSWORD=${{ secrets.PROD_POSTGRES_PASSWORD }}
          MINIO_PASSWORD=${{ secrets.PROD_MINIO_PASSWORD }}
          MINIO_APP_PASSWORD=${{ secrets.PROD_MINIO_APP_PASSWORD }}
          OCR_TRAINING_TOKEN=${{ secrets.PROD_OCR_TRAINING_TOKEN }}
          APP_ADMIN_USERNAME=${{ secrets.PROD_APP_ADMIN_USERNAME }}
          APP_ADMIN_PASSWORD=${{ secrets.PROD_APP_ADMIN_PASSWORD }}
          MAIL_HOST=${{ secrets.MAIL_HOST }}
          MAIL_PORT=${{ secrets.MAIL_PORT }}
          MAIL_USERNAME=${{ secrets.MAIL_USERNAME }}
          MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}
          MAIL_SMTP_AUTH=true
          MAIL_STARTTLS_ENABLE=true
          APP_MAIL_FROM=noreply@raddatz.cloud
          IMPORT_HOST_DIR=/srv/familienarchiv-production/import
          POSTGRES_USER=archiv
          SENTRY_DSN=${{ secrets.SENTRY_DSN }}
          GRAFANA_DB_PASSWORD=${{ secrets.GRAFANA_DB_PASSWORD }}
          EOF

      - name: Build images
        # `--pull` forces re-fetching pinned base images so a CVE
        # re-publication of the same tag is picked up rather than served
        # from the host's stale Docker layer cache.
        run: |
          docker compose \
            -f docker-compose.prod.yml \
            -p archiv-production \
            --env-file .env.production \
            build --pull

      - name: Deploy production
        run: |
          docker compose \
            -f docker-compose.prod.yml \
            -p archiv-production \
            --env-file .env.production \
            up -d --wait --remove-orphans

      # POSTGRES_HOST is derived from the Compose project name (archiv-production)
      # and service name (db). A project rename requires updating this value.
      - uses: ./.gitea/actions/deploy-obs
        with:
          grafana_admin_password: ${{ secrets.GRAFANA_ADMIN_PASSWORD }}
          grafana_db_password: ${{ secrets.GRAFANA_DB_PASSWORD }}
          glitchtip_secret_key: ${{ secrets.GLITCHTIP_SECRET_KEY }}
          postgres_password: ${{ secrets.PROD_POSTGRES_PASSWORD }}
          postgres_host: archiv-production-db-1

      - uses: ./.gitea/actions/reload-caddy

      - uses: ./.gitea/actions/smoke-test
        with:
          host: archiv.raddatz.cloud

      - name: Cleanup env file
        # LOAD-BEARING: `if: always()` is the linchpin of the ADR-011
        # single-tenant runner trust model. Every secret in
        # .env.production is plain text on the runner filesystem until
        # this step runs. If a future refactor drops `if: always()`, a
        # failed deploy leaves the env-file behind. Do not remove this
        # conditional without first re-evaluating ADR-011.
        if: always()
        run: rm -f .env.production
