From 8fc657f431dde3442a5c669ec88fe9ef9f3d800b Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Thu, 2 Jul 2026 14:29:23 +0000 Subject: [PATCH] excalidraw: migrate image build to GHA -> private ghcr (ADR-0002) The image was still built by hand and pushed to DockerHub (v1..v4), predating the all-builds-off-infra doctrine; Viktor chose to move it onto the standard pipeline while shipping the export/rename feature rather than keep the manual flow. Mirrors the k8s-portal pattern: .github/workflows/build-excalidraw.yml (go test + buildx linux/amd64, pushes ghcr latest+sha), excalidraw ns added to the Kyverno ghcr-credentials allowlist (package is PRIVATE), deployment now pins ghcr :latest with pullPolicy Always + pull secret, Keel force/match-tag/5m annotations seed the metadata (live values win via ignore_changes). DockerHub viktorbarzin/excalidraw-library:v4 stays frozen as the rollback image. Docs: ci-cd.md + .claude/CLAUDE.md image lists updated (also backfilled the missing k8s-portal rows in ci-cd.md). Co-Authored-By: Claude Fable 5 --- .claude/CLAUDE.md | 6 ++- .github/workflows/build-excalidraw.yml | 42 +++++++++++++++++++ docs/architecture/ci-cd.md | 4 +- stacks/excalidraw/main.tf | 27 ++++++++++-- .../modules/kyverno/ghcr-credentials.tf | 5 +++ 5 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/build-excalidraw.yml diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index facc4c2a..ea6b2a51 100755 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -137,7 +137,7 @@ audiobook-search) now also land on ghcr. chrome-service-novnc, android-emulator. - **PRIVATE ghcr:** f1-stream, job-hunter, instagram-poster, payslip-ingest, wealthfolio-sync, fire-planner, recruiter-responder, tripit, infra-cli, - infra-ci, k8s-portal. Pulled via the Kyverno-synced `ghcr-credentials` allowlist + infra-ci, k8s-portal, excalidraw-library. Pulled via the Kyverno-synced `ghcr-credentials` allowlist (`stacks/kyverno/modules/kyverno/ghcr-credentials.tf`; NOT cluster-wide; cred = Vault `secret/viktor/ghcr_pull_token`, a dedicated classic PAT scoped to `read:packages` (UI-minted 2026-06-15; no longer the admin `github_pat` @@ -153,7 +153,9 @@ github↔forgejo divergence was deliberately NOT reconciled): `build-cli.yml` → DockerHub `viktorbarzin/infra` (kept) + `ghcr.io/viktorbarzin/infra-cli`; `build-infra-ci.yml` → `ghcr.io/viktorbarzin/infra-ci`; `build-k8s-portal.yml` → PRIVATE `ghcr.io/viktorbarzin/k8s-portal` (Keel-deployed; the LAST in-cluster -Woodpecker build, migrated 2026-06-13 — completes "no local builds"). **infra-ci** +Woodpecker build, migrated 2026-06-13 — completes "no local builds"); `build-excalidraw.yml` → +PRIVATE `ghcr.io/viktorbarzin/excalidraw-library` (Keel-deployed; replaced +manual DockerHub pushes 2026-07-02 — DockerHub `:v4` frozen as rollback). **infra-ci** is the image the `.woodpecker/default.yml` apply step + `drift-detection.yml` run in (proven by pipelines 165/166). chatterbox-tts is already built by tripit's GHA → ghcr. The Woodpecker `build-ci-image.yml` + `build-cli.yml` pipelines were REMOVED; diff --git a/.github/workflows/build-excalidraw.yml b/.github/workflows/build-excalidraw.yml new file mode 100644 index 00000000..7f58131f --- /dev/null +++ b/.github/workflows/build-excalidraw.yml @@ -0,0 +1,42 @@ +name: Build excalidraw-library + +# ADR-0002 / no-local-builds: excalidraw-library (infra-owned Go app behind +# draw.viktorbarzin.me) builds off-infra on GHA → private ghcr; Keel polls +# ghcr:latest and rolls the deployment. Replaces the manual DockerHub pushes +# (viktorbarzin/excalidraw-library:v4 stays frozen as the rollback image). +on: + push: + branches: [master] + paths: + - 'stacks/excalidraw/project/**' + workflow_dispatch: {} + +permissions: + contents: read + packages: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.21' + - run: go test ./... + working-directory: stacks/excalidraw/project + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/build-push-action@v6 + with: + context: stacks/excalidraw/project + platforms: linux/amd64 + provenance: false + push: true + tags: | + ghcr.io/viktorbarzin/excalidraw-library:latest + ghcr.io/viktorbarzin/excalidraw-library:${{ github.sha }} diff --git a/docs/architecture/ci-cd.md b/docs/architecture/ci-cd.md index b8cfcdd5..17d0859f 100644 --- a/docs/architecture/ci-cd.md +++ b/docs/architecture/ci-cd.md @@ -94,7 +94,7 @@ can't reach Forgejo's public hairpin. | Visibility | Packages | Pull mechanism | |------------|----------|----------------| | **Public** | beadboard, nextcloud-todos, claude-agent-service, claude-memory-mcp, kms-website, freedify, tuya_bridge, x402-gateway, chrome-service-novnc, android-emulator | Anonymous | -| **Private** | f1-stream, job-hunter, instagram-poster, payslip-ingest, wealthfolio-sync, fire-planner, recruiter-responder, tripit, infra-cli, infra-ci | `ghcr-credentials` dockerconfigjson | +| **Private** | f1-stream, job-hunter, instagram-poster, payslip-ingest, wealthfolio-sync, fire-planner, recruiter-responder, tripit, infra-cli, infra-ci, k8s-portal, excalidraw-library | `ghcr-credentials` dockerconfigjson | Private-image pulls use the `ghcr-credentials` dockerconfigjson, cloned by the kyverno stack's `sync-ghcr-credentials` ClusterPolicy to an explicit @@ -188,6 +188,8 @@ reconciled — the workflows were added to the GitHub lineage via PR): | android-emulator | `build-android-emulator.yml` | public `ghcr.io/viktorbarzin/android-emulator` | | infra CLI | `build-cli.yml` | DockerHub `viktorbarzin/infra` (kept) + `ghcr.io/viktorbarzin/infra-cli` | | infra-ci | `build-infra-ci.yml` | private `ghcr.io/viktorbarzin/infra-ci` | +| k8s-portal | `build-k8s-portal.yml` | private `ghcr.io/viktorbarzin/k8s-portal` (Keel rolls `:latest` digests) | +| excalidraw-library | `build-excalidraw.yml` | private `ghcr.io/viktorbarzin/excalidraw-library` (Keel rolls `:latest` digests; DockerHub `:v4` frozen as rollback) | **`infra-ci`** is the image the `.woodpecker/default.yml` apply step and `drift-detection.yml` run in (proven by pipelines 165/166). `chatterbox-tts` is diff --git a/stacks/excalidraw/main.tf b/stacks/excalidraw/main.tf index 41ab48a0..b7a33117 100644 --- a/stacks/excalidraw/main.tf +++ b/stacks/excalidraw/main.tf @@ -10,7 +10,7 @@ resource "kubernetes_namespace" "excalidraw" { name = "excalidraw" labels = { "istio-injection" : "disabled" - tier = local.tiers.aux + tier = local.tiers.aux "keel.sh/enrolled" = "true" } } @@ -45,6 +45,15 @@ resource "kubernetes_deployment" "excalidraw" { app = "excalidraw" tier = local.tiers.aux } + # Keel rolls new ghcr:latest digests (k8s-portal pattern). Values here are + # recreate-correct seeds only — the keys are in ignore_changes below, so + # the live annotations win on an existing deployment. + annotations = { + "keel.sh/policy" = "force" + "keel.sh/trigger" = "poll" + "keel.sh/match-tag" = "true" + "keel.sh/pollSchedule" = "@every 5m" + } } spec { replicas = 1 @@ -67,9 +76,19 @@ resource "kubernetes_deployment" "excalidraw" { } } spec { + # GHCR pull secret: the ghcr-credentials Secret in this namespace is + # cloned in by the kyverno stack's sync-ghcr-credentials ClusterPolicy + # (allowlisted private-ghcr namespaces only — ADR-0002). Source of + # truth: stacks/kyverno/modules/kyverno/ghcr-credentials.tf. + image_pull_secrets { + name = "ghcr-credentials" + } container { - image = "viktorbarzin/excalidraw-library:v4" - image_pull_policy = "IfNotPresent" + # ADR-0002: GHA-built (.github/workflows/build-excalidraw.yml), + # PRIVATE ghcr; Keel rolls new :latest digests. DockerHub + # viktorbarzin/excalidraw-library:v4 is the frozen rollback image. + image = "ghcr.io/viktorbarzin/excalidraw-library:latest" + image_pull_policy = "Always" name = "excalidraw" port { container_port = 8080 @@ -107,7 +126,7 @@ resource "kubernetes_deployment" "excalidraw" { } lifecycle { ignore_changes = [ - spec[0].template[0].spec[0].dns_config, # KYVERNO_LIFECYCLE_V1 + spec[0].template[0].spec[0].dns_config, # KYVERNO_LIFECYCLE_V1 spec[0].template[0].spec[0].container[0].image, # KEEL_IGNORE_IMAGE — Keel manages tag updates metadata[0].annotations["keel.sh/policy"], metadata[0].annotations["keel.sh/trigger"], diff --git a/stacks/kyverno/modules/kyverno/ghcr-credentials.tf b/stacks/kyverno/modules/kyverno/ghcr-credentials.tf index e4a5ec6a..8ef531c5 100644 --- a/stacks/kyverno/modules/kyverno/ghcr-credentials.tf +++ b/stacks/kyverno/modules/kyverno/ghcr-credentials.tf @@ -43,6 +43,11 @@ locals { # ghcr.io/passionprojectsanca/book-plotter (built by GHA in Anca's repo, # under her own org's ghcr). The deployment references the cloned secret. "plotting-book", + # excalidraw: infra-owned image migrated from manual DockerHub pushes to + # PRIVATE ghcr.io/viktorbarzin/excalidraw-library (ADR-0002, built by + # .github/workflows/build-excalidraw.yml). The deployment references the + # cloned secret. + "excalidraw", ] }