ADR-0002 tracer bullet (infra#13), per Viktor's go-ahead. Idempotent script: GitHub mirror repo (create/unarchive/visibility), GHA secrets via gh, Forgejo push-mirror (sync_on_commit) + initial sync, Woodpecker mirror registration, renders build.yml/deploy.yml from templates (single-manifest provenance:false, svu semver to Forgejo, ghcr keep-10 retention, Slack notify-failure, manual-event deploy), removes the old in-cluster build pipeline, commits on the Canonical side. f1-stream stack gains the ghcr-credentials imagePullSecret (first consumer). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
115 lines
4.7 KiB
Cheetah
115 lines
4.7 KiB
Cheetah
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
|