name: Build and Push # Off-infra build (ADR-0002). Canonical repo is Forgejo viktor/fire-planner, which # push-mirrors here; this workflow builds on GitHub-hosted runners, pushes the # image to GHCR, then signals the Woodpecker deploy pipeline (repo 208) # to roll the cluster — the homelab never sees build IO or registry pushes. # # Committed on the FORGEJO side (the mirror is one-way; commits made on GitHub # are overwritten by the next sync). Generated by infra/scripts/offinfra-onboard. on: push: branches: [master] workflow_dispatch: {} permissions: contents: read packages: write jobs: lint-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" # Mirrors the original .woodpecker.yml lint-and-test step (python:3.12-slim, # poetry 1.8.4, ruff + pytest). mypy is deliberately NOT run here: the # in-cluster lint step was dropped from CI on 2026-05-07 (Phase 4) and # strict-mypy drifted unchecked in 4 test files (44 errors as of # 2026-06-13). Re-add `poetry run mypy fire_planner tests` once the # tests are typed clean. - name: Lint + test run: | pip install --no-cache-dir "poetry==1.8.4" poetry install --no-interaction --no-root poetry run ruff check . poetry run pytest -q build: needs: lint-and-test runs-on: ubuntu-latest outputs: image_tag: ${{ steps.meta.outputs.sha }} steps: - uses: actions/checkout@v6 with: fetch-depth: 0 # full history + tags so svu sees the last vX.Y.Z fetch-tags: true # Auto-semver (svu): tag-only, pushed to CANONICAL Forgejo (GitHub tags # would be wiped by the next mirror sync). Best-effort: never blocks the build. - name: Compute + tag semver (svu) env: FORGEJO_GIT_TOKEN: ${{ secrets.FORGEJO_GIT_TOKEN }} run: | set +e git config user.email "ci@viktorbarzin.me" git config user.name "fire-planner-ci" git config --global --add safe.directory "$GITHUB_WORKSPACE" curl -sSL https://github.com/caarlos0/svu/releases/download/v3.4.1/svu_3.4.1_linux_amd64.tar.gz | tar -xz svu CUR=$(./svu current 2>/dev/null) NEXT=$(./svu next 2>/dev/null) echo "svu current=[$CUR] next=[$NEXT]" if [ -n "$NEXT" ] && [ "$NEXT" != "$CUR" ]; then git tag "$NEXT" 2>/dev/null git push "https://viktor:${FORGEJO_GIT_TOKEN}@forgejo.viktorbarzin.me/viktor/fire-planner.git" "$NEXT" && echo "pushed tag $NEXT to forgejo" || echo "tag push failed (non-blocking)" fi exit 0 - uses: docker/setup-buildx-action@v4 - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: meta run: echo "sha=$(echo ${{ github.sha }} | cut -c1-8)" >> "$GITHUB_OUTPUT" - uses: docker/build-push-action@v7 with: context: . push: true platforms: linux/amd64 # Single-manifest images (no provenance/SBOM attestation children) so # registry retention can never orphan index children (ADR-0002). provenance: false tags: | ghcr.io/viktorbarzin/fire-planner:${{ steps.meta.outputs.sha }} ghcr.io/viktorbarzin/fire-planner:latest cache-from: type=gha cache-to: type=gha,mode=max # Keep the newest ~10 versions on ghcr (latest rides the newest one). - name: ghcr retention (keep 10) uses: actions/delete-package-versions@v5 continue-on-error: true with: package-name: fire-planner package-type: container min-versions-to-keep: 10 deploy: needs: build runs-on: ubuntu-latest steps: # Signal Woodpecker (repo 208 = ViktorBarzin/fire-planner mirror) to run # .woodpecker/deploy.yml — kubectl set image in-cluster (agent SA is cluster-admin). - name: Trigger Woodpecker deploy run: | for attempt in 1 2 3; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ "https://ci.viktorbarzin.me/api/repos/208/pipelines" \ -H "Authorization: Bearer ${{ secrets.WOODPECKER_TOKEN }}" \ -H "Content-Type: application/json" \ -d "{\"branch\":\"master\",\"variables\":{\"IMAGE_TAG\":\"${{ needs.build.outputs.image_tag }}\",\"IMAGE_NAME\":\"ghcr.io/viktorbarzin/fire-planner\"}}") if [ "$STATUS" -ge 200 ] && [ "$STATUS" -lt 300 ]; then echo "Woodpecker deploy triggered (HTTP $STATUS)"; exit 0 fi echo "Attempt $attempt failed (HTTP $STATUS), retrying in 30s..."; sleep 30 done echo "Failed to trigger Woodpecker deploy after 3 attempts"; exit 1 notify-failure: needs: [lint-and-test, build, deploy] if: failure() runs-on: ubuntu-latest steps: - name: Slack notify run: | curl -sf -X POST -H 'Content-Type: application/json' \ -d "{\"text\":\":rotating_light: fire-planner off-infra build FAILED: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \ "${{ secrets.SLACK_WEBHOOK }}" || true