- .gitea/workflows/cd.yml: on push to main (bot/** or scripts/**),
build ci-ai-bot:latest on the Pi runner via DooD and force-recreate
the container with docker compose (no registry push needed)
- reusable-{ai-review,commands,security}.yml: replace hardcoded
192.168.178.103:11434 with ${{ vars.JETSON_OLLAMA_URL }} (org variable
set in conformallab → fallback to old IP if variable is missing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ci-ai — KI-Schicht für Gitea
Self-hosted Äquivalent zu GitLab Duo für Gitea: automatische PR-Reviews,
PR-/Issue-Summaries, KI-erklärte Security-Funde, CI-Failure-Analyse — und ein
interaktiver @ci-ai-Bot. KI-Backend ist der self-hosted Jetson (Ollama, GPU);
Anthropic bleibt als Alternative per Env-Var. Zwei Wege, dieselben Skripte:
- CI-Workflows (über den
gitea-runner): laufen beipush/pull_request/ Slash-Command. - Interaktiver Bot (
bot/, Webhook-Dienst):@ci-ai-Chat + Auto-PR-Review in Sekunden — ohne Runner, ohne Clone, ohne NAT-Hairpin.
Bausteine
| Feature | Skript | Trigger |
|---|---|---|
| A — PR-Review + Summary | scripts/ai_review.py |
pull_request (Runner) |
| B — Security-Scan + KI-Erklärung (hart blockierend) | security.yml + scripts/ai_explain.py |
pull_request, push |
| C — CI-Failure-Analyse | if:failure()-Job + scripts/ai_explain.py |
Job-Fehler |
| D — Test-Generierung (Vorschlag) | scripts/ai_tests.py |
Kommentar /gen-tests (PR) |
| E — Issue-/PR-Zusammenfassung | scripts/ai_summary.py |
Kommentar /summarize |
F — Interaktiver Bot (@ci-ai-Chat + Auto-Review) |
bot/app.py |
Gitea-Webhook (issue_comment, pull_request) |
Slash-Commands (D + E): /gen-tests bzw. /summarize in einem Issue/PR kommentieren →
der commands-Job (siehe caller.example.yml) postet das Ergebnis.
@ci-ai (F): @ci-ai <Frage> in einem Issue/PR → Antwort im Thread (mit Diff-Kontext).
scripts/llm.py ist die provider-agnostische LLM-Schicht — Jetson (Ollama) im Betrieb,
Anthropic als Alternative, umschaltbar per Env-Var. Reine Standardbibliothek, kein pip install.
Aufbau
scripts/
llm.py Provider-Abstraktion (ollama | anthropic), stdlib-only
gitea_api.py PR-Diff/Thread holen, idempotente Marker-Kommentare, post_comment
context.py Gitea-Actions-Event-Kontext (GITHUB_*-Vars)
ai_review.py Feature A
ai_explain.py Features B & C
ai_tests.py Feature D (/gen-tests)
ai_summary.py Feature E (/summarize)
bot/
app.py Feature F — interaktiver Webhook-Bot (FastAPI)
Dockerfile Container fürs Deployment (vom Repo-ROOT bauen)
.gitea/workflows/
reusable-security.yml on: workflow_call — zentraler Security-Scan
reusable-ai-review.yml on: workflow_call — zentraler PR-Review
reusable-commands.yml on: workflow_call — Slash-Commands (/gen-tests, /summarize)
workflows/
caller.example.yml der EINE 12-Zeiler fürs Ziel-Repo (empfohlen)
*.example.yml standalone-Workflows (Alternative: kopieren)
Interaktiver Bot (bot/)
Schlanker FastAPI-Webhook-Dienst, läuft als Docker-Container neben Gitea (auf dem Pi,
web_network). Erreicht die Gitea-API intern über http://gitea:3000 (kein TLS, kein
Hairpin) und das KI-Backend (Jetson-Ollama) LAN-direkt → Antwort in Sekunden, ganz ohne
Runner/Clone. Teilt sich die Logik mit der CI-Variante (scripts/{llm,gitea_api,ai_review}.py).
Events
issue_commentmit@ci-ai <Frage>→ Chat-Antwort im Thread (Thread- + Diff-Kontext, neuer Kommentar).pull_request(opened / synchronized / reopened) → idempotentes KI-Review (gleicher Prompt wie Feature A).
Sicherheit: HMAC-Signaturcheck (WEBHOOK_SECRET); BackgroundTasks → sofort 200,
LLM-Arbeit im Hintergrund (kein Gitea-Webhook-Timeout).
Bauen (vom Repo-ROOT — der Dockerfile kopiert scripts/ + bot/app.py):
docker build -f bot/Dockerfile -t ci-ai-bot:latest .
Deployment & Gitea-Webhook: Der Bot wird als Service im docker_stack des Schwester-Repos
ansible-eulernest deployt — Details, Vars und Troubleshooting dort in
docs/ci-ai-bot.md.
Webhook (Org- oder Repo-Ebene): URL http://ci-ai-bot:8080/webhook, Events issue_comment +
pull_request, Secret = WEBHOOK_SECRET. ⚠️ Giteas SSRF-Schutz blockt Zustellung an private
IPs — [webhook] ALLOWED_HOST_LIST muss den Bot-Host (ci-ai-bot) erlauben.
Env-Vars (Bot)
| Var | Beispiel | Zweck |
|---|---|---|
BOT_NAME |
ci-ai |
Mention-Trigger (@ci-ai) |
WEBHOOK_SECRET |
— | HMAC-Secret, muss zum Gitea-Webhook passen (leer = Check aus) |
GITEA_TOKEN |
— | Token fürs Kommentieren (write:issue) |
GITEA_API |
http://gitea:3000/api/v1 |
Gitea-API (intern, kein Hairpin) |
AI_PROVIDER / AI_MODEL / AI_BASE_URL |
ollama / coder / http://192.168.178.103:11434/v1 |
KI-Backend (Jetson) |
In ein Repo einbauen (CI-Workflows)
Empfohlen: globaler Aufruf (reusable workflow)
Nur eine Datei ins Ziel-Repo: workflows/caller.example.yml → .gitea/workflows/ci-ai.yml.
Sie ruft die zentrale Logik auf (uses: user2595/ci-ai/.gitea/workflows/reusable-*.yml@main
secrets: inherit). Logik-Updates passieren einmal hier, alle Repos ziehen automatisch nach.
Einmalig zentral:
- Runner instanzweit registrieren (Admin → Actions → Runners), Label
eulernest— sonst haben Repos außerhalb der org keinen Runner. - Anonymen Clone für public Repos erlauben (
REQUIRE_SIGNIN_VIEW=false) — der Job klontci-aifür die Skripte. - KI-Backend = Jetson-Ollama (LAN, self-hosted) → kein Secret nötig. (Anthropic-Variante:
ANTHROPIC_API_KEYals Org-/Instanz-Actions-Secret; ohne Key überspringen sich die KI-Teile sauber.) - Für „hart blockieren":
security-Check in der Branch-Protection als required.
Alternative: standalone (kopieren)
workflows/ai-review.example.yml + workflows/security.example.yml direkt nach
.gitea/workflows/ kopieren — volle Logik (klont die Skripte selbst), mehr Copy-Paste,
aber unabhängig von reusable-workflow-Support.
KI-Backend: Jetson (self-hosted, im Betrieb)
Das lokale Ollama auf dem Jetson Orin ist das aktive Backend — kein Anthropic-Key, keine Kosten, kein Datenabfluss. In Workflows und Bot per Env gesetzt:
AI_PROVIDER: ollama
AI_MODEL: coder # qwen2.5-coder:7b
AI_BASE_URL: http://192.168.178.103:11434/v1
Provider-Wechsel = nur Env, kein Code-Change (scripts/llm.py). Anthropic-Variante:
AI_PROVIDER=anthropic + ANTHROPIC_API_KEY.
Konfiguration (Env-Vars)
Hinweis: Die Code-Defaults in
llm.pyentsprechen jetzt dem Jetson-Betrieb (ollama/coder/.103). Workflows + Bot setzen die Werte trotzdem explizit per Env — ein Provider-Wechsel (z.B. zurück aufanthropic) bleibt damit ein reiner Env-Change, keine Code-Änderung.
| Var | Default (llm.py) |
Im Betrieb |
|---|---|---|
AI_PROVIDER |
ollama |
ollama |
AI_MODEL |
coder (ollama) / claude-3-5-haiku-latest (anthropic) |
coder |
AI_BASE_URL |
http://192.168.178.103:11434/v1 |
…178.103… |
JETSON_API_KEY |
— | Bearer für Ollama (optional) |
ANTHROPIC_API_KEY |
— | nur bei provider=anthropic |
GITEA_API |
https://git.eulernest.eu/api/v1 |
Bot: intern http://gitea:3000/api/v1 |
GITEA_TOKEN / GITEA_BOT_TOKEN |
— | Token fürs Kommentieren |
Constraints
- CI-Pfad: Runner hat capacity 1 → Jobs laufen seriell; Docker-Executor abgesichert
(kein privileged, keine Host-Mounts) → Trivy
fs/config+ Gitleaks laufen, Scans laufender Container nicht (unnötig). - Bot-Pfad: umgeht Runner/Clone/Hairpin komplett (interner
gitea:3000-Zugriff). - Modell:
qwen2.5-coder:7b(Jetson, GPU) — brauchbar, ein paar dt. Patzer, < Claude. Kosten 0 pro Call (self-hosted). Anthropic-Variante: Bruchteile eines Cent/PR (Haiku-Klasse).