diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml new file mode 100644 index 0000000..13f2423 --- /dev/null +++ b/.gitea/workflows/cd.yml @@ -0,0 +1,46 @@ +# CD — rebuild and restart ci-ai-bot on the Pi whenever bot/ or scripts/ change. +# +# How it works (Docker-out-of-Docker): +# 1. Job runs in docker:cli on the Pi runner (eulernest label). +# 2. Mounts the HOST docker socket → builds the new image directly in the host +# Docker daemon (no registry push needed — image is already local). +# 3. Mounts /mnt/external:ro → runs `docker compose up --force-recreate` +# which picks up the freshly built image and recreates the container. +# +# Prerequisite (one-time): +# - Runner config must list /var/run/docker.sock and /mnt/external in valid_volumes +# (set in ansible-eulernest roles/gitea_runner/templates/config.yaml.j2). +# - Set org variable JETSON_OLLAMA_URL in conformallab → Actions → Variables: +# value = http://192.168.178.103:11434/v1 +name: CD — ci-ai-bot + +on: + push: + branches: [main] + paths: + - "bot/**" + - "scripts/**" + +jobs: + deploy: + runs-on: eulernest + container: + image: docker:27-cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /mnt/external:/mnt/external:ro + + steps: + - uses: actions/checkout@v4 + + - name: Build ci-ai-bot image + # Build from repo root so bot/Dockerfile can COPY scripts/ + run: docker build -t ci-ai-bot:latest -f bot/Dockerfile . + + - name: Recreate ci-ai-bot container + # --force-recreate: always replace the container even if config is unchanged, + # so the freshly built image is picked up. + run: | + docker compose \ + -f /mnt/external/docker-compose.yml \ + up -d --no-deps --force-recreate ci-ai-bot diff --git a/.gitea/workflows/reusable-ai-review.yml b/.gitea/workflows/reusable-ai-review.yml index 0012434..1618d6e 100644 --- a/.gitea/workflows/reusable-ai-review.yml +++ b/.gitea/workflows/reusable-ai-review.yml @@ -30,7 +30,8 @@ jobs: env: AI_PROVIDER: ollama AI_MODEL: coder - AI_BASE_URL: http://192.168.178.103:11434/v1 + # JETSON_OLLAMA_URL: org variable in conformallab → Actions → Variables + AI_BASE_URL: ${{ vars.JETSON_OLLAMA_URL || 'http://192.168.178.103:11434/v1' }} GITEA_TOKEN: ${{ github.token }} GITEA_API: https://git.eulernest.eu/api/v1 run: python /opt/ci-ai/scripts/ai_review.py diff --git a/.gitea/workflows/reusable-commands.yml b/.gitea/workflows/reusable-commands.yml index f70c537..aebb10e 100644 --- a/.gitea/workflows/reusable-commands.yml +++ b/.gitea/workflows/reusable-commands.yml @@ -22,7 +22,8 @@ jobs: env: AI_PROVIDER: ollama AI_MODEL: coder - AI_BASE_URL: http://192.168.178.103:11434/v1 + # JETSON_OLLAMA_URL: org variable in conformallab → Actions → Variables + AI_BASE_URL: ${{ vars.JETSON_OLLAMA_URL || 'http://192.168.178.103:11434/v1' }} GITEA_TOKEN: ${{ github.token }} GITEA_API: https://git.eulernest.eu/api/v1 steps: diff --git a/.gitea/workflows/reusable-security.yml b/.gitea/workflows/reusable-security.yml index 5311edd..bfd354a 100644 --- a/.gitea/workflows/reusable-security.yml +++ b/.gitea/workflows/reusable-security.yml @@ -73,7 +73,8 @@ jobs: env: AI_PROVIDER: ollama AI_MODEL: coder - AI_BASE_URL: http://192.168.178.103:11434/v1 + # JETSON_OLLAMA_URL: org variable in conformallab → Actions → Variables + AI_BASE_URL: ${{ vars.JETSON_OLLAMA_URL || 'http://192.168.178.103:11434/v1' }} GITEA_TOKEN: ${{ github.token }} GITEA_API: https://git.eulernest.eu/api/v1 run: | diff --git a/README.md b/README.md index 756ac8d..a0be065 100644 --- a/README.md +++ b/README.md @@ -125,15 +125,16 @@ Provider-Wechsel = **nur Env**, kein Code-Change (`scripts/llm.py`). Anthropic-V ## Konfiguration (Env-Vars) -> Hinweis: Die **Code-Defaults in `llm.py` sind noch der alte Stand** (Anthropic, Jetson-IP -> `.20`, `qwen-light`). Im Betrieb überschreiben Workflows + Bot diese per Env mit den -> Jetson-Werten (`ollama` / `coder` / `.103`). Ein Refresh der Defaults ist vorgemerkt. +> Hinweis: Die **Code-Defaults in `llm.py` entsprechen jetzt dem Jetson-Betrieb** +> (`ollama` / `coder` / `.103`). Workflows + Bot setzen die Werte trotzdem explizit +> per Env — ein Provider-Wechsel (z.B. zurück auf `anthropic`) bleibt damit ein +> reiner Env-Change, keine Code-Änderung. | Var | Default (`llm.py`) | Im Betrieb | |---|---|---| -| `AI_PROVIDER` | `anthropic` | **`ollama`** | -| `AI_MODEL` | `claude-3-5-haiku-latest` / `qwen-light` | **`coder`** | -| `AI_BASE_URL` | `http://192.168.178.20:11434/v1` *(veraltet)* | **`…178.103…`** | +| `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` | diff --git a/scripts/llm.py b/scripts/llm.py index 367bba7..a3ea0dd 100644 --- a/scripts/llm.py +++ b/scripts/llm.py @@ -4,18 +4,18 @@ Bewusst NUR stdlib (urllib) — so braucht der CI-Job kein `pip install` und läuft in jedem schlanken Python-Image sofort. Umschalten zwischen Backends über Env-Vars: - AI_PROVIDER=anthropic -> api.anthropic.com/v1/messages (Claude, jetzt) - AI_PROVIDER=ollama -> /chat/completions (Jetson, später) + AI_PROVIDER=ollama -> /chat/completions (Jetson, self-hosted, Default) + AI_PROVIDER=anthropic -> api.anthropic.com/v1/messages (Claude, optional) Weitere Env-Vars: - AI_MODEL Modellname (Default je nach Provider) - ANTHROPIC_API_KEY (für anthropic) + AI_MODEL Modellname (Default je nach Provider; ollama: "coder" = qwen2.5-coder:7b) AI_BASE_URL OpenAI-kompatible Basis-URL (für ollama), - Default http://192.168.178.20:11434/v1 + Default http://192.168.178.103:11434/v1 JETSON_API_KEY Bearer-Token (für ollama, optional) + ANTHROPIC_API_KEY (für anthropic) -Der Wechsel auf den Jetson ist damit ein reiner Env-Change in der Workflow- -Datei — KEINE Code-Änderung. +Provider-Wechsel ist damit ein reiner Env-Change in der Workflow-Datei +— KEINE Code-Änderung. """ import json import os @@ -29,7 +29,7 @@ def configured(): Erlaubt den Workflows, den KI-Schritt sauber zu ÜBERSPRINGEN (exit 0) statt zu failen, solange z.B. noch kein ANTHROPIC_API_KEY hinterlegt ist. """ - provider = os.environ.get("AI_PROVIDER", "anthropic").lower() + provider = os.environ.get("AI_PROVIDER", "ollama").lower() if provider == "anthropic": if not os.environ.get("ANTHROPIC_API_KEY"): return False, "ANTHROPIC_API_KEY ist nicht gesetzt" @@ -41,12 +41,12 @@ def configured(): def complete(system: str, user: str, *, max_tokens: int = 1500) -> str: """Schickt (system, user) ans konfigurierte Backend, gibt reinen Text zurück.""" - provider = os.environ.get("AI_PROVIDER", "anthropic").lower() + provider = os.environ.get("AI_PROVIDER", "ollama").lower() if provider == "anthropic": model = os.environ.get("AI_MODEL", "claude-3-5-haiku-latest") return _anthropic(system, user, model, max_tokens) if provider == "ollama": - model = os.environ.get("AI_MODEL", "qwen-light") + model = os.environ.get("AI_MODEL", "coder") return _ollama_openai(system, user, model, max_tokens) raise SystemExit(f"[llm] Unbekannter AI_PROVIDER: {provider!r}") @@ -88,7 +88,7 @@ def _anthropic(system: str, user: str, model: str, max_tokens: int) -> str: def _ollama_openai(system: str, user: str, model: str, max_tokens: int) -> str: - base = os.environ.get("AI_BASE_URL", "http://192.168.178.20:11434/v1").rstrip("/") + base = os.environ.get("AI_BASE_URL", "http://192.168.178.103:11434/v1").rstrip("/") headers = {"content-type": "application/json"} key = os.environ.get("JETSON_API_KEY", "") if key: