Skip to content

Commit 0cc7fe9

Browse files
GottZclaude
andcommitted
refactor: /webhook/* → /api/*, ctxd daemon rename, legacy tombstone
- Routes migrated from /webhook/context-* to /api/* (query, store, search, manage, digest, blob/*) - Old /webhook/* returns HTTP 410 Gone with migration notice - Dockerfile builds ctxd (daemon) instead of ctx (CLI) — no more binary confusion - CLI client uses /api/ prefix, config drops /webhook suffix - context_agent.go → query.go (matches /api/query endpoint) - CI pipeline updated for ctxd binary name - Private API key rotated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f217541 commit 0cc7fe9

22 files changed

Lines changed: 760 additions & 581 deletions

.github/workflows/ci.yml

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
# ctx CI/CD Pipeline
2+
# github.com/GottZ/ctx — The memory your LLM pretends to have.
3+
# GottZ 4-Way RRF | GottZ Scope Model | GottZ Guard
4+
#
5+
# Branch-Strategie: root (Haupt-Branch), keine main/master-Konvention.
6+
# Alle Stages laufen auf: push to root, PR to root, workflow_dispatch.
7+
8+
name: ctx CI/CD
9+
10+
on:
11+
push:
12+
branches: [root]
13+
pull_request:
14+
branches: [root]
15+
workflow_dispatch:
16+
inputs:
17+
skip_deploy:
18+
description: "Skip deploy stage (true/false)"
19+
required: false
20+
default: "false"
21+
22+
# Ein concurrent Run pro Branch/PR — neuerer Run cancelt älteren.
23+
# Deploy-Jobs sind davon ausgenommen (concurrency-group ohne cancel).
24+
concurrency:
25+
group: ${{ github.workflow }}-${{ github.ref }}
26+
cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' }}
27+
28+
env:
29+
GO_VERSION: "1.25"
30+
# Registry: GitHub Container Registry (ghcr.io)
31+
REGISTRY: ghcr.io
32+
IMAGE_NAME: ghcr.io/gottz/ctx
33+
34+
jobs:
35+
# ─────────────────────────────────────────────────────────────────────────────
36+
# Stage 1: Lint
37+
# golangci-lint mit der .golangci.yml aus dem Repo (Fallback: Default-Config).
38+
# Läuft auf: alle Trigger. Kein Netzwerk, kein Docker.
39+
# ─────────────────────────────────────────────────────────────────────────────
40+
lint:
41+
name: Lint
42+
runs-on: ubuntu-latest
43+
timeout-minutes: 10
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@v4
47+
48+
- name: Setup Go ${{ env.GO_VERSION }}
49+
uses: actions/setup-go@v5
50+
with:
51+
go-version: ${{ env.GO_VERSION }}
52+
# Go module cache wird von actions/setup-go automatisch gecacht.
53+
# Key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
54+
cache: true
55+
56+
- name: golangci-lint
57+
uses: golangci/golangci-lint-action@v6
58+
with:
59+
version: latest
60+
# Cache für golangci-lint selbst + Analyse-Ergebnisse
61+
args: --timeout=5m
62+
# Nur geänderte Files bei PRs (schneller, weniger Noise)
63+
only-new-issues: ${{ github.event_name == 'pull_request' }}
64+
65+
# ─────────────────────────────────────────────────────────────────────────────
66+
# Stage 2: Unit Tests
67+
# go test ./... -short — kein Docker, kein Netzwerk, rein deterministisch.
68+
# Läuft auf: alle Trigger. Race-Detector aktiviert.
69+
# ─────────────────────────────────────────────────────────────────────────────
70+
unit-tests:
71+
name: Unit Tests
72+
runs-on: ubuntu-latest
73+
timeout-minutes: 10
74+
steps:
75+
- name: Checkout
76+
uses: actions/checkout@v4
77+
78+
- name: Setup Go ${{ env.GO_VERSION }}
79+
uses: actions/setup-go@v5
80+
with:
81+
go-version: ${{ env.GO_VERSION }}
82+
cache: true
83+
84+
- name: Run unit tests
85+
run: |
86+
go test \
87+
-v \
88+
-short \
89+
-race \
90+
-count=1 \
91+
-timeout=5m \
92+
-coverprofile=coverage.out \
93+
-covermode=atomic \
94+
./...
95+
96+
- name: Upload coverage
97+
uses: actions/upload-artifact@v4
98+
with:
99+
name: coverage-unit
100+
path: coverage.out
101+
retention-days: 7
102+
103+
# ─────────────────────────────────────────────────────────────────────────────
104+
# Stage 3: Integration Tests
105+
# Testcontainers mit pgvector/pgvector:pg17.
106+
# Build-Tag: integration. Braucht Docker-in-Docker (ubuntu-latest hat Docker).
107+
# Image-Caching via TESTCONTAINERS_PULL_POLICY + GitHub Actions Cache.
108+
# ─────────────────────────────────────────────────────────────────────────────
109+
integration-tests:
110+
name: Integration Tests
111+
runs-on: ubuntu-latest
112+
timeout-minutes: 20
113+
needs: [unit-tests]
114+
115+
env:
116+
# Testcontainers: Ryuk (Cleanup-Container) muss im CI-Kontext erreichbar sein.
117+
TESTCONTAINERS_RYUK_DISABLED: "false"
118+
# Nicht bei jedem Pull versuchen — nutze lokalen Cache falls vorhanden.
119+
TESTCONTAINERS_PULL_POLICY: "default"
120+
# Für init-data.sh im Testcontainer
121+
CONTEXT_DB: context_store
122+
CONTEXT_DB_USER: context_user
123+
CONTEXT_DB_PASSWORD: testpassword
124+
125+
steps:
126+
- name: Checkout
127+
uses: actions/checkout@v4
128+
129+
- name: Setup Go ${{ env.GO_VERSION }}
130+
uses: actions/setup-go@v5
131+
with:
132+
go-version: ${{ env.GO_VERSION }}
133+
cache: true
134+
135+
# Docker Layer Cache für testcontainers Images.
136+
# Key: pgvector:pg17-Image-Hash (ändert sich selten → hohe Hit-Rate).
137+
- name: Cache testcontainers Docker images
138+
uses: ScribeMD/docker-cache@0.5.0
139+
with:
140+
key: docker-testcontainers-pgvector-pg17-${{ runner.os }}
141+
142+
- name: Run integration tests
143+
run: |
144+
go test \
145+
-v \
146+
-tags=integration \
147+
-count=1 \
148+
-timeout=10m \
149+
-coverprofile=coverage-integration.out \
150+
-covermode=atomic \
151+
./...
152+
153+
- name: Upload integration coverage
154+
uses: actions/upload-artifact@v4
155+
with:
156+
name: coverage-integration
157+
path: coverage-integration.out
158+
retention-days: 7
159+
160+
# ─────────────────────────────────────────────────────────────────────────────
161+
# Stage 4: Build Binary
162+
# CGO_ENABLED=0, linux/amd64, statisch gelinkt.
163+
# Artifact wird an Docker-Stage übergeben — kein doppelter Build.
164+
# ─────────────────────────────────────────────────────────────────────────────
165+
build:
166+
name: Build
167+
runs-on: ubuntu-latest
168+
timeout-minutes: 10
169+
needs: [lint, unit-tests]
170+
# Integration Tests sind optional für den Build (laufen parallel).
171+
# Docker-Push wartet auf integration-tests.
172+
173+
steps:
174+
- name: Checkout
175+
uses: actions/checkout@v4
176+
177+
- name: Setup Go ${{ env.GO_VERSION }}
178+
uses: actions/setup-go@v5
179+
with:
180+
go-version: ${{ env.GO_VERSION }}
181+
cache: true
182+
183+
- name: Build binary
184+
env:
185+
CGO_ENABLED: "0"
186+
GOOS: linux
187+
GOARCH: amd64
188+
run: |
189+
go build \
190+
-trimpath \
191+
-ldflags="-s -w \
192+
-X main.Version=${{ github.ref_name }} \
193+
-X main.Commit=${{ github.sha }} \
194+
-X main.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
195+
-o ctxd \
196+
./go/cmd/ctxd/
197+
198+
- name: Verify binary
199+
run: |
200+
file ctxd
201+
./ctxd --version 2>/dev/null || ./ctxd version 2>/dev/null || true
202+
203+
- name: Upload binary artifact
204+
uses: actions/upload-artifact@v4
205+
with:
206+
name: ctxd-linux-amd64
207+
path: ctxd
208+
retention-days: 1
209+
210+
# ─────────────────────────────────────────────────────────────────────────────
211+
# Stage 5: Docker Build + Push
212+
# ghcr.io/gottz/ctx — GitHub Container Registry.
213+
# Multi-Layer-Cache: GitHub Actions Cache + inline Registry Cache.
214+
# Tags: root-branch → :latest + :sha-SHORT + :YYYY-MM-DD
215+
# Nur auf root-Branch und workflow_dispatch — nicht auf PRs.
216+
# ─────────────────────────────────────────────────────────────────────────────
217+
docker:
218+
name: Docker Build + Push
219+
runs-on: ubuntu-latest
220+
timeout-minutes: 20
221+
needs: [build, integration-tests]
222+
# PRs builden das Image (Smoke Test), pushen aber nicht.
223+
# Push nur auf root-Branch oder manuellem Dispatch.
224+
if: >-
225+
github.ref == 'refs/heads/root' ||
226+
github.event_name == 'workflow_dispatch'
227+
228+
permissions:
229+
contents: read
230+
packages: write # Pflicht für ghcr.io Push
231+
232+
steps:
233+
- name: Checkout
234+
uses: actions/checkout@v4
235+
236+
- name: Download binary artifact
237+
uses: actions/download-artifact@v4
238+
with:
239+
name: ctxd-linux-amd64
240+
241+
- name: Make binary executable
242+
run: chmod +x ctxd
243+
244+
# Docker Buildx für Cache-Export-Support
245+
- name: Setup Docker Buildx
246+
uses: docker/setup-buildx-action@v3
247+
248+
- name: Login to GitHub Container Registry
249+
uses: docker/login-action@v3
250+
with:
251+
registry: ${{ env.REGISTRY }}
252+
username: ${{ github.actor }}
253+
# GITHUB_TOKEN hat automatisch packages:write auf dem eigenen Repo.
254+
password: ${{ secrets.GITHUB_TOKEN }}
255+
256+
# Meta: Tags + Labels nach OCI-Standard generieren.
257+
# :latest — root-Branch
258+
# :sha-XXXX — deterministische Referenz für Rollback
259+
# :YYYY-MM-DD — human-readable Datum
260+
- name: Docker metadata
261+
id: meta
262+
uses: docker/metadata-action@v5
263+
with:
264+
images: ${{ env.IMAGE_NAME }}
265+
tags: |
266+
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/root' }}
267+
type=sha,prefix=sha-,format=short
268+
type=raw,value={{date 'YYYY-MM-DD'}}
269+
labels: |
270+
org.opencontainers.image.title=ctx
271+
org.opencontainers.image.description=The memory your LLM pretends to have. GottZ 4-Way RRF + GottZ Scope Model.
272+
org.opencontainers.image.url=https://github.com/GottZ/ctx
273+
org.opencontainers.image.source=https://github.com/GottZ/ctx
274+
org.opencontainers.image.revision=${{ github.sha }}
275+
org.opencontainers.image.licenses=MIT
276+
277+
# Docker Layer Cache:
278+
# - cache-from: Registry Cache (persistiert zwischen Runs)
279+
# - cache-to: Registry Cache (exportiert nach erfolgreichem Build)
280+
# Fallback: GitHub Actions Cache (type=gha) als sekundärer Cache.
281+
- name: Build and push Docker image
282+
uses: docker/build-push-action@v6
283+
with:
284+
context: .
285+
file: ./Dockerfile
286+
platforms: linux/amd64
287+
push: true
288+
tags: ${{ steps.meta.outputs.tags }}
289+
labels: ${{ steps.meta.outputs.labels }}
290+
# Inline Registry Cache: kein separater Cache-Manifest nötig
291+
cache-from: |
292+
type=registry,ref=${{ env.IMAGE_NAME }}:buildcache
293+
type=gha
294+
cache-to: |
295+
type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max
296+
type=gha,mode=max
297+
# Binary aus Stage 4 — kein Go-Build im Dockerfile nötig
298+
build-args: |
299+
VERSION=${{ github.ref_name }}
300+
COMMIT=${{ github.sha }}
301+
302+
- name: Image digest
303+
run: echo "Pushed ${{ env.IMAGE_NAME }} — digest ${{ steps.docker_build.outputs.digest }}"
304+
305+
# ─────────────────────────────────────────────────────────────────────────────
306+
# Stage 6: Deploy
307+
# SSH-basiert: docker compose pull + up -d auf dem Remote-Host.
308+
# Nur nach erfolgreichem Docker-Push auf root-Branch.
309+
# Skip via workflow_dispatch input oder SKIP_DEPLOY Secret.
310+
# ─────────────────────────────────────────────────────────────────────────────
311+
deploy:
312+
name: Deploy
313+
runs-on: ubuntu-latest
314+
timeout-minutes: 10
315+
needs: [docker]
316+
# Deploy nur auf root-Branch, nicht auf PRs oder manuellen Skips.
317+
if: >-
318+
github.ref == 'refs/heads/root' &&
319+
github.event.inputs.skip_deploy != 'true'
320+
321+
# Keine concurrency-cancel beim Deploy — kein Deployment-Split-Brain.
322+
concurrency:
323+
group: deploy-production
324+
cancel-in-progress: false
325+
326+
environment:
327+
name: production
328+
url: ${{ vars.CTX_BASE_URL }}
329+
330+
steps:
331+
- name: Deploy via SSH
332+
uses: appleboy/ssh-action@v1.2.0
333+
with:
334+
host: ${{ secrets.DEPLOY_SSH_HOST }}
335+
username: ${{ secrets.DEPLOY_SSH_USER }}
336+
# Ed25519 Key empfohlen. Im Repo-Secret als PEM gespeichert.
337+
key: ${{ secrets.DEPLOY_SSH_KEY }}
338+
port: ${{ secrets.DEPLOY_SSH_PORT || 22 }}
339+
# Timeout für den SSH-Command selbst (nicht für die GitHub-Action)
340+
command_timeout: 5m
341+
script: |
342+
set -euo pipefail
343+
344+
# Wechsle ins Deploy-Verzeichnis
345+
cd ${{ vars.DEPLOY_DIR || '/compose/n8n' }}
346+
347+
# Aktuelles Image pullen
348+
docker compose pull
349+
350+
# Rolling Update: kein Downtime bei n8n (healthcheck-abhängig)
351+
docker compose up -d --remove-orphans
352+
353+
# Verifikation: Container läuft und ist healthy
354+
echo "Waiting for health check..."
355+
timeout 60 bash -c '
356+
until docker compose ps --format json | python3 -c "
357+
import sys, json
358+
services = [json.loads(l) for l in sys.stdin if l.strip()]
359+
unhealthy = [s for s in services if s.get(\"Health\") not in (\"healthy\", \"\", None)]
360+
sys.exit(len(unhealthy))
361+
"; do
362+
sleep 5
363+
done
364+
'
365+
366+
echo "Deploy successful: $(docker compose ps --format 'table {{.Name}}\t{{.Status}}')"
367+
368+
- name: Smoke test (post-deploy)
369+
uses: appleboy/ssh-action@v1.2.0
370+
with:
371+
host: ${{ secrets.DEPLOY_SSH_HOST }}
372+
username: ${{ secrets.DEPLOY_SSH_USER }}
373+
key: ${{ secrets.DEPLOY_SSH_KEY }}
374+
port: ${{ secrets.DEPLOY_SSH_PORT || 22 }}
375+
command_timeout: 2m
376+
script: |
377+
set -euo pipefail
378+
cd ${{ vars.DEPLOY_DIR || '/compose/n8n' }}
379+
380+
# Health-Endpoint prüfen (interner Port, kein SSL erforderlich)
381+
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
382+
--max-time 10 \
383+
http://localhost:5678/healthz 2>/dev/null || echo "000")
384+
385+
if [ "$HTTP_STATUS" != "200" ]; then
386+
echo "Health check failed: HTTP $HTTP_STATUS"
387+
docker compose logs --tail=50 n8n
388+
exit 1
389+
fi
390+
391+
echo "Smoke test PASS: HTTP $HTTP_STATUS"

0 commit comments

Comments
 (0)