ci: move image build off-infra to GHA -> ghcr (ADR-0002)
Some checks failed
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build (push) Has been cancelled
Build and Push / notify-failure (push) Has been cancelled

Generated by infra/scripts/offinfra-onboard: GHA builds+tests on the
GitHub mirror, pushes ghcr.io/viktorbarzin/wealthfolio-sync, then triggers the
Woodpecker deploy (repo 0). Old in-cluster build pipeline
removed: .woodpecker/build.yml

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-13 01:40:07 +00:00
parent 0d23487608
commit b632d951e4
2 changed files with 103 additions and 45 deletions

103
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,103 @@
name: Build and Push
# Off-infra build (ADR-0002). Canonical repo is Forgejo viktor/broker-sync, which
# push-mirrors here; this workflow builds on GitHub-hosted runners, pushes the
# image to GHCR, then signals the Woodpecker deploy pipeline (repo 0)
# 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"
- name: Lint + type-check + test
run: |
pip install --no-cache-dir "poetry==1.8.4"
poetry install --no-interaction --no-root
poetry run ruff check .
poetry run mypy broker_sync tests
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 "broker-sync-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/broker-sync.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/wealthfolio-sync:${{ steps.meta.outputs.sha }}
ghcr.io/viktorbarzin/wealthfolio-sync: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: wealthfolio-sync
package-type: container
min-versions-to-keep: 10
notify-failure:
needs: [lint-and-test, build]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Slack notify
run: |
curl -sf -X POST -H 'Content-Type: application/json' \
-d "{\"text\":\":rotating_light: broker-sync off-infra build FAILED: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
"${{ secrets.SLACK_WEBHOOK }}" || true

View file

@ -1,45 +0,0 @@
when:
event: push
branch: [main, master]
clone:
git:
image: woodpeckerci/plugin-git
settings:
attempts: 5
backoff: 10s
steps:
- name: lint-and-test
image: python:3.12-slim
commands:
- pip install --no-cache-dir "poetry==1.8.4"
- poetry install --no-interaction --no-root
- poetry run ruff check .
- poetry run mypy broker_sync tests
- poetry run pytest -q
- name: build-and-push
image: woodpeckerci/plugin-docker-buildx
depends_on:
- lint-and-test
settings:
# Image name is `wealthfolio-sync` to match the deployment in
# infra/stacks/wealthfolio/main.tf (CronJob `wealthfolio-sync`).
# The repo is called `broker-sync` because the source covers
# multiple brokers (Trading 212, Schwab, Fidelity, IMAP-CSV) —
# we just happen to publish it under the wealthfolio name since
# that's the consumer stack.
repo:
- forgejo.viktorbarzin.me/viktor/wealthfolio-sync
logins:
- registry: forgejo.viktorbarzin.me
username:
from_secret: forgejo_user
password:
from_secret: forgejo_push_token
dockerfile: Dockerfile
context: .
auto_tag: true
platforms:
- linux/amd64