name: Build and Push # Off-infra build (ADR-0002). Canonical repo is Forgejo viktor/{{NAME}}, which # push-mirrors here; this workflow builds on GitHub-hosted runners, pushes the # image to GHCR, then signals the Woodpecker deploy pipeline (repo {{WP_REPO_ID}}) # 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 {{TEST_STEPS}} 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 "{{NAME}}-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/{{NAME}}.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: {{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/{{IMAGE}}:${{ steps.meta.outputs.sha }} ghcr.io/viktorbarzin/{{IMAGE}}: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: {{IMAGE}} package-type: container min-versions-to-keep: 10 deploy: needs: build runs-on: ubuntu-latest steps: # Signal Woodpecker (repo {{WP_REPO_ID}} = ViktorBarzin/{{NAME}} 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/{{WP_REPO_ID}}/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/{{IMAGE}}\"}}") 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: {{NAME}} off-infra build FAILED: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \ "${{ secrets.SLACK_WEBHOOK }}" || true