ci: move image build off-infra to GHA -> ghcr (ADR-0002)
Some checks are pending
Some checks are pending
Generated by infra/scripts/offinfra-onboard: GHA builds+tests on the GitHub mirror, pushes ghcr.io/viktorbarzin/claude-memory-mcp, then triggers the Woodpecker deploy (repo 78). Old in-cluster build pipeline removed: .woodpecker/build.yml .woodpecker/build-fallback.yml Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d49032b697
commit
52d2fc68c3
4 changed files with 131 additions and 134 deletions
122
.github/workflows/build.yml
vendored
Normal file
122
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
name: Build and Push
|
||||||
|
|
||||||
|
# Off-infra build (ADR-0002). Canonical repo is Forgejo viktor/claude-memory-mcp, which
|
||||||
|
# push-mirrors here; this workflow builds on GitHub-hosted runners, pushes the
|
||||||
|
# image to GHCR, then signals the Woodpecker deploy pipeline (repo 78)
|
||||||
|
# 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
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
- name: Lint + type-check + test
|
||||||
|
run: |
|
||||||
|
uv sync --all-extras
|
||||||
|
uv run ruff check src/ tests/
|
||||||
|
uv run mypy src/claude_memory/
|
||||||
|
uv run pytest tests/ -v --tb=short
|
||||||
|
|
||||||
|
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 "claude-memory-mcp-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/claude-memory-mcp.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/claude-memory-mcp:${{ steps.meta.outputs.sha }}
|
||||||
|
ghcr.io/viktorbarzin/claude-memory-mcp: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: claude-memory-mcp
|
||||||
|
package-type: container
|
||||||
|
min-versions-to-keep: 10
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Signal Woodpecker (repo 78 = ViktorBarzin/claude-memory-mcp 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/78/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/claude-memory-mcp\"}}")
|
||||||
|
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: claude-memory-mcp off-infra build FAILED: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
|
||||||
|
"${{ secrets.SLACK_WEBHOOK }}" || true
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
when:
|
|
||||||
- event: deployment
|
|
||||||
|
|
||||||
clone:
|
|
||||||
git:
|
|
||||||
image: woodpeckerci/plugin-git
|
|
||||||
settings:
|
|
||||||
attempts: 5
|
|
||||||
backoff: 10s
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: test
|
|
||||||
image: python:3.12-slim
|
|
||||||
commands:
|
|
||||||
- pip install -e ".[api,dev]"
|
|
||||||
- ruff check src/ tests/
|
|
||||||
- pytest tests/ -v --tb=short
|
|
||||||
|
|
||||||
- name: build-and-push
|
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
settings:
|
|
||||||
username: viktorbarzin
|
|
||||||
password:
|
|
||||||
from_secret: dockerhub-token
|
|
||||||
repo: viktorbarzin/claude-memory-mcp
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
context: .
|
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
tags:
|
|
||||||
- "${CI_PIPELINE_NUMBER}"
|
|
||||||
- latest
|
|
||||||
|
|
||||||
- name: deploy
|
|
||||||
image: bitnami/kubectl:latest
|
|
||||||
depends_on:
|
|
||||||
- build-and-push
|
|
||||||
commands:
|
|
||||||
- kubectl set image deployment/claude-memory claude-memory=viktorbarzin/claude-memory-mcp:${CI_PIPELINE_NUMBER} -n claude-memory
|
|
||||||
- kubectl rollout status deployment/claude-memory -n claude-memory --timeout=120s
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
when:
|
|
||||||
event: push
|
|
||||||
branch: [main, master]
|
|
||||||
|
|
||||||
clone:
|
|
||||||
git:
|
|
||||||
image: woodpeckerci/plugin-git
|
|
||||||
settings:
|
|
||||||
attempts: 5
|
|
||||||
backoff: 10s
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: test
|
|
||||||
image: python:3.12-slim
|
|
||||||
# The woodpecker ns LimitRange defaults containers to a 256Mi memory limit.
|
|
||||||
# `uv sync` + mypy over fastapi/pydantic/sqlalchemy needs far more, so the
|
|
||||||
# step was OOM-killed (exit 137) on every run since the 2026-05-07 Forgejo
|
|
||||||
# switch — repo never built. Pin explicit memory so it never OOMs again.
|
|
||||||
backend_options:
|
|
||||||
kubernetes:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 500m
|
|
||||||
memory: 1Gi
|
|
||||||
limits:
|
|
||||||
memory: 2Gi
|
|
||||||
commands:
|
|
||||||
- pip install --no-cache-dir uv
|
|
||||||
- uv sync --all-extras
|
|
||||||
- uv run ruff check src/ tests/
|
|
||||||
- uv run mypy src/claude_memory/
|
|
||||||
- uv run pytest tests/ -v --tb=short
|
|
||||||
|
|
||||||
- name: build-and-push
|
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
# buildx + image export also exceeds the 256Mi ns default; give it room.
|
|
||||||
backend_options:
|
|
||||||
kubernetes:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 500m
|
|
||||||
memory: 1Gi
|
|
||||||
limits:
|
|
||||||
memory: 2Gi
|
|
||||||
settings:
|
|
||||||
# Phase 4 of forgejo-registry-consolidation 2026-05-07 — Forgejo only.
|
|
||||||
# The DockerHub mirror stays as the public-facing release target via
|
|
||||||
# the GitHub `release.yml` workflow (still enabled), but the cluster
|
|
||||||
# pulls from Forgejo (infra/stacks/claude-memory/main.tf flipped 2026-05-07).
|
|
||||||
repo:
|
|
||||||
- forgejo.viktorbarzin.me/viktor/claude-memory-mcp
|
|
||||||
logins:
|
|
||||||
- registry: forgejo.viktorbarzin.me
|
|
||||||
username:
|
|
||||||
from_secret: forgejo_user
|
|
||||||
password:
|
|
||||||
from_secret: forgejo_push_token
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
context: .
|
|
||||||
# Tag :latest AND the 8-char commit SHA. The SHA tag is what the deploy
|
|
||||||
# step pins — a unique tag forces a fresh pull under the deployment's
|
|
||||||
# imagePullPolicy: IfNotPresent (a re-pushed :latest would not).
|
|
||||||
tags:
|
|
||||||
- "latest"
|
|
||||||
- "${CI_COMMIT_SHA:0:8}"
|
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
|
|
||||||
- name: deploy
|
|
||||||
image: bitnami/kubectl:latest
|
|
||||||
depends_on:
|
|
||||||
- build-and-push
|
|
||||||
when:
|
|
||||||
branch: [main, master]
|
|
||||||
event: [push, manual]
|
|
||||||
# Owned-app deploy model (infra CLAUDE.md): the build pipeline drives the
|
|
||||||
# rollout, so a push self-deploys — no manual `kubectl set image`. The
|
|
||||||
# woodpecker-agent SA is cluster-admin, so the in-cluster kubectl needs no
|
|
||||||
# kubeconfig. Keel stays enrolled as a redundant net.
|
|
||||||
commands:
|
|
||||||
- "kubectl set image deployment/claude-memory claude-memory=forgejo.viktorbarzin.me/viktor/claude-memory-mcp:${CI_COMMIT_SHA:0:8} -n claude-memory"
|
|
||||||
- "kubectl rollout status deployment/claude-memory -n claude-memory --timeout=300s"
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
# Manual-only targeted deploy of a specific tag (set IMAGE_NAME + IMAGE_TAG).
|
# Auto-deploy, triggered ONLY by the GitHub Actions build POSTing to the
|
||||||
# Push-driven deploys are handled by build.yml's deploy step now; this no longer
|
# Woodpecker API (manual event, with IMAGE_TAG + IMAGE_NAME) after a successful
|
||||||
# fires on push (its IMAGE_TAG-absent exit-78 used to red every push pipeline,
|
# off-infra build+push to GHCR (ADR-0002). event:[manual] (NOT push) so the
|
||||||
# since build.yml + deploy.yml are workflows in the same pipeline run).
|
# Forgejo->GitHub mirror's raw pushes don't fire a spurious deploy.
|
||||||
|
# The woodpecker-agent SA is cluster-admin — no kubeconfig needed.
|
||||||
|
# Generated by infra/scripts/offinfra-onboard.
|
||||||
when:
|
when:
|
||||||
- event: manual
|
- event: manual
|
||||||
|
|
||||||
|
|
@ -9,11 +11,10 @@ steps:
|
||||||
- name: check-vars
|
- name: check-vars
|
||||||
image: alpine
|
image: alpine
|
||||||
commands:
|
commands:
|
||||||
- "[ -n \"$IMAGE_TAG\" ] || (echo 'IMAGE_TAG not set, skipping deploy'; exit 78)"
|
- "[ -n \"$IMAGE_TAG\" ] || (echo 'IMAGE_TAG not set — refusing to deploy'; exit 1)"
|
||||||
|
|
||||||
- name: deploy
|
- name: deploy
|
||||||
image: bitnami/kubectl:latest
|
image: bitnami/kubectl:latest
|
||||||
commands:
|
commands:
|
||||||
- "kubectl set image deployment/claude-memory claude-memory=${IMAGE_NAME}:${IMAGE_TAG} -n claude-memory"
|
- "kubectl -n claude-memory set image deployment/claude-memory claude-memory=${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
- "kubectl rollout status deployment/claude-memory -n claude-memory --timeout=300s"
|
- "kubectl -n claude-memory rollout status deployment/claude-memory --timeout=300s"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue