From 7abff715613be28ef2556cc1f223bde638edad83 Mon Sep 17 00:00:00 2001 From: Tarik Moussa Date: Tue, 9 Jun 2026 17:05:33 +0200 Subject: [PATCH] feat: graceful skip without API key + finalize workflows (container/memory caps, ARM64) Co-Authored-By: Claude Opus 4.8 --- scripts/ai_explain.py | 4 +++ scripts/ai_review.py | 5 +++ scripts/llm.py | 16 +++++++++ workflows/ai-review.example.yml | 22 ++++++------- workflows/security.example.yml | 57 +++++++++++++++++---------------- 5 files changed, 66 insertions(+), 38 deletions(-) diff --git a/scripts/ai_explain.py b/scripts/ai_explain.py index 23b9390..db86d3f 100644 --- a/scripts/ai_explain.py +++ b/scripts/ai_explain.py @@ -48,6 +48,10 @@ def main() -> int: return 2 kind = KINDS[sys.argv[1]] + ok, why = llm.configured() + if not ok: + print(f"[ai_explain] KI inaktiv ({why}) — Erklärung sauber übersprungen, kein Fehler.") + return 0 with open(sys.argv[2], encoding="utf-8", errors="replace") as fh: text = fh.read() if len(text) > LOG_CAP: diff --git a/scripts/ai_review.py b/scripts/ai_review.py index da2c458..c786bac 100644 --- a/scripts/ai_review.py +++ b/scripts/ai_review.py @@ -74,6 +74,11 @@ def main() -> int: print("[ai_review] PR vom Bot selbst — übersprungen.") return 0 + ok, why = llm.configured() + if not ok: + print(f"[ai_review] KI inaktiv ({why}) — Schritt sauber übersprungen, kein Fehler.") + return 0 + diff = filter_diff(gitea_api.get_pr_diff(owner, repo, pr["index"])) if not diff: print("[ai_review] Leerer/gefilterter Diff — nichts zu reviewen.") diff --git a/scripts/llm.py b/scripts/llm.py index d58dc20..367bba7 100644 --- a/scripts/llm.py +++ b/scripts/llm.py @@ -23,6 +23,22 @@ import urllib.error import urllib.request +def configured(): + """(ok, grund) — ob das gewählte Backend nutzbar konfiguriert ist. + + 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() + if provider == "anthropic": + if not os.environ.get("ANTHROPIC_API_KEY"): + return False, "ANTHROPIC_API_KEY ist nicht gesetzt" + return True, "" + if provider == "ollama": + return True, "" # Erreichbarkeit wird beim eigentlichen Call geprüft + return False, f"unbekannter AI_PROVIDER: {provider!r}" + + 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() diff --git a/workflows/ai-review.example.yml b/workflows/ai-review.example.yml index 8833181..15f485a 100644 --- a/workflows/ai-review.example.yml +++ b/workflows/ai-review.example.yml @@ -1,18 +1,22 @@ -# Feature A — KI-PR-Review + Summary. -# In ein Ziel-Repo kopieren nach: .gitea/workflows/ai-review.yml +# Feature A — KI-PR-Review + Summary. Kopie -> .gitea/workflows/ai-review.yml # -# Nutzt KEINE JS-Actions (kein node nötig): schlankes python-Image, ci-ai wird -# manuell geklont. GITHUB_REPOSITORY / GITHUB_EVENT_PATH setzt Gitea automatisch. +# Läuft auf dem Pi-Runner (capacity 1, ~3-4 GB RAM) -> Memory-Cap + schlankes +# python-Image. KEINE JS-Actions (kein node nötig): ci-ai wird manuell geklont, +# git.eulernest.eu löst per Runner-add-host auf die LAN-IP auf. Ohne +# ANTHROPIC_API_KEY überspringt das Skript den KI-Schritt sauber (Job bleibt grün). name: AI PR Review on: + workflow_dispatch: {} pull_request: types: [opened, synchronize, reopened] jobs: review: runs-on: eulernest - container: python:3.12-slim + container: + image: python:3.12-slim + options: "--memory=512m --memory-swap=768m" steps: - name: Tooling (git) run: apt-get update -qq && apt-get install -y -qq --no-install-recommends git ca-certificates @@ -20,20 +24,16 @@ jobs: - name: ci-ai-Skripte holen run: git clone --depth 1 https://git.eulernest.eu/user2595/ci-ai.git /opt/ci-ai - - name: KI-Review + - name: KI-Review (überspringt sauber ohne Key) env: AI_PROVIDER: anthropic AI_MODEL: claude-3-5-haiku-latest ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - # Kommentieren: Actions-Auto-Token. Reicht dessen Schreibrecht nicht, - # stattdessen GITEA_BOT_TOKEN (PAT) als Repo-/Org-Secret setzen. GITEA_TOKEN: ${{ github.token }} GITEA_API: https://git.eulernest.eu/api/v1 run: python /opt/ci-ai/scripts/ai_review.py -# ───────────────────────────────────────────────────────────────────────────── -# Späterer Umstieg auf den Jetson (lokales Ollama) — NUR diese env-Werte ändern, -# kein Code-Change: +# Jetson-Umstieg später (kein Code-Change, nur diese env): # AI_PROVIDER: ollama # AI_MODEL: qwen-light # AI_BASE_URL: http://192.168.178.20:11434/v1 diff --git a/workflows/security.example.yml b/workflows/security.example.yml index 2f866b4..cf45aa1 100644 --- a/workflows/security.example.yml +++ b/workflows/security.example.yml @@ -1,53 +1,56 @@ -# Feature B (+C) — Security-Scan, HART blockierend, mit KI-Klartext-Erklärung. -# In ein Ziel-Repo kopieren nach: .gitea/workflows/security.yml +# Feature B (+C) — Security-Scan (HART blockierend) + KI-Klartext-Erklärung. +# Kopie -> .gitea/workflows/security.yml # -# Ablauf: scannen (ohne sofort abzubrechen) -> Gate (failt hart bei Funden) -> -# bei Fehler erklärt die KI die Funde im PR. So failt der Check verlässlich UND -# der Erklär-Schritt sieht beide Reports. +# Schlankes alpine-Image + Memory-Cap (Pi-Runner, capacity 1). ⚠ ARM64-Binaries! +# Gitleaks (Secrets) + Trivy config (IaC/Misconfig) — bewusst OHNE schweren +# Vuln-DB-Scan (RAM-/zeitschonend auf dem Pi). Funde lassen den Check hart +# fehlschlagen; danach erklärt die KI sie im PR (überspringt sauber ohne Key). name: Security on: + workflow_dispatch: {} pull_request: - push: - branches: [main, master] + +env: + GITLEAKS_VERSION: "8.18.4" jobs: scan: runs-on: eulernest - container: ghcr.io/catthehacker/ubuntu:act-latest + container: + image: alpine:3.20 + options: "--memory=768m --memory-swap=1024m" steps: - - name: Checkout (volle Historie für Gitleaks) - uses: actions/checkout@v4 - with: - fetch-depth: 0 + - name: Tooling + run: apk add --no-cache git curl bash tar python3 - - name: Gitleaks (Secret-Scan) - id: gitleaks - continue-on-error: true + - name: Repo auschecken (git.eulernest.eu -> LAN via Runner-add-host) + run: git clone --depth 1 "https://git.eulernest.eu/${GITHUB_REPOSITORY}.git" src + + - name: Gitleaks (Secret-Scan, ARM64) + id: gl run: | - curl -sSL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_x64.tar.gz \ + curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_arm64.tar.gz" \ | tar -xz -C /usr/local/bin gitleaks - gitleaks detect --source . --report-format json --report-path /tmp/gitleaks.json --no-banner \ - | tee /tmp/gitleaks.txt; echo "code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT" + cd src && gitleaks detect --no-git --source . --redact -v 2>&1 | tee /tmp/scan.log + echo "code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT" - - name: Trivy (fs + IaC, HIGH/CRITICAL) - id: trivy - continue-on-error: true + - name: Trivy (IaC/config, leicht) + id: tv run: | curl -sSL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ | sh -s -- -b /usr/local/bin - trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL \ - --exit-code 1 --no-progress . | tee /tmp/trivy.txt; echo "code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT" + cd src && trivy config --severity HIGH,CRITICAL --exit-code 1 --no-progress . 2>&1 | tee -a /tmp/scan.log + echo "code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT" - name: Gate — hart blockieren bei Funden run: | - cat /tmp/gitleaks.txt /tmp/trivy.txt > /tmp/scan.log 2>/dev/null || true - if [ "${{ steps.gitleaks.outputs.code }}" != "0" ] || [ "${{ steps.trivy.outputs.code }}" != "0" ]; then - echo "Security-Funde — Check schlägt fehl (hart blockierend)."; exit 1 + if [ "${{ steps.gl.outputs.code }}" != "0" ] || [ "${{ steps.tv.outputs.code }}" != "0" ]; then + echo "Security-Funde -> Check schlägt fehl (hart blockierend)."; exit 1 fi echo "Keine HIGH/CRITICAL-Funde." - - name: KI erklärt die Funde (nur bei Fehler) + - name: KI erklärt die Funde (nur bei Fehler, überspringt ohne Key) if: failure() env: AI_PROVIDER: anthropic