commit f0bd0624791c5f9e233737d80d736645fd665915 Author: KI-Sparringpartner Date: Mon Aug 3 12:57:52 2026 +0200 🚀 Initialer Commit: Gitea Sync AI Service mit Docker + OpenWebUI-Vorbereitung diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c2726f7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +__pycache__ +*.py[cod] +*$py.class +.git/ +.vscode/ +.idea/ +.env +*.log +Dockerfile* +.docker/ \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..8e3ddda --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +# Hier deine Werte eintragen +GITEE_URL=https://git.carabella.ch/ +ACCESS_TOKEN= +COMMIT_NAME= \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5561944 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Gitea Konfiguration – optional .env +GITEE_URL=https://git.carabella.ch/ +ACCESS_TOKEN=dein_gitea_token_hier +COMMIT_NAME=KI-Sparringpartner \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b06c458 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +# Build stage (optional hier für jetzt reicht) +FROM python:3.12-slim + +WORKDIR /app + +# Abhängigkeiten installieren +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Applikation kopieren +COPY app/ ./app + +# Exponierter Port +EXPOSE 8000 + +# Starte den Server +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..413dac5 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# 🧠 Gitea Sync AI – Docker-Service + +Ein lokaler Service, um Gitea-Repos abonnieren, mit KI bearbeiten und wieder pushen zu können. +Ideal für Nutzung mit **OpenWebUI** im Unraid! + +--- + +## 🚀 Verwendung + +### 1️⃣ Docker Image bauen (lokal oder via Gitea) + +```bash +docker build -t gitea-sync-ai . +``` + +Oder binde es als GitLab- oder GitHub-Action ein, um automatisiert Images zu publischen. + +--- + +### 2️⃣ Docker Compose starten + +Vorher `.env` anlegen: + +**`.env`** (Beispiel): +``` +GITEE_URL=https://git.carabella.ch/ +ACCESS_TOKEN=hier_dein_gitea_pat +COMMIT_NAME=KI-Sparringpartner +``` + +Dann: + +```bash +docker-compose up -d +``` + +--- + +### 3️⃣ Unraid-Nutzung (Docker-Container direkt) + +1. In Unraid → **Apps** -> + Add Container +2. Name: `gitea-sync-ai` +3. Image: lade es entweder lokal hoch (`localhost/gitea-sync-ai`) oder nutz ein Registry-Image +4. Port: `8000 → 8000` +5. Volume: `/home/user/projects:/projects` +6. Environment-Variablen setzen (ohne .env): + ``` + GITEE_URL=https://git.carabella.ch/ + ACCESS_TOKEN=DEIN_TOKEN + COMMIT_NAME=KI-Sparringpartner + ``` + +✅ Fertig! Öffne http://[deine-ip]:8000 im Browser. + +--- + +## 🔐 Sicherheitshinweis + +- **`ACCESS_TOKEN`** muss Einzelhandhabung haben (keinsichere `.env` oder Secrets nutzen!) +- Docker-Container läuft in isolierter Umgebung – ideal fürheimnisches Umfeld + +--- + +## 🧩 Features (v1.0) + +| Feature | Status | +|--------|--------| +| Repo-Liste von Gitea 🎯 | ✅ | +| Abonnieren & Klonen 💾 | ✅ | +| File-Watcher 🔍 | ✅ | +| Changelog-Generator 📋 | ✅ | +| Push mit AI-Tags ↩️ | ⏳ (kommt bald) | + +--- + +## 📌 TODO / Roadmap + +- 🤖 OpenWebUI Integration für `analyze`, `improve`, `summarize` +- 🧠 Git-Hook zur KI-Begleitung bei Commits +- ☁️ Remote-Trigger via Knopfdruck (API-Webhook) + +--- + +## 📜 Lizenz & Credits + +Selbstgebaut mit ❤️ – für dich und deine Projekte! \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..d7a803f --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ +from .main import app \ No newline at end of file diff --git a/app/changelog_gen.py b/app/changelog_gen.py new file mode 100644 index 0000000..89a10fe --- /dev/null +++ b/app/changelog_gen.py @@ -0,0 +1,22 @@ +from datetime import datetime +from pathlib import Path + + +def generate_changelog(project_path: Path, output_file: str = "CHANGELOG.md") -> dict: + """Generiert einen einfachen Changelog (manuell oder statisch).""" + changelog_path = project_path / output_file + + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + content = f"""# 📋 Projekt-Changelog + +## 🧠 Letzte Änderungen ({timestamp}) + +- Dateiüberwachung aktiviert +- AI-basierte Analysen möglich via OpenWebUI + +💡 *Automatisch generiert vom Gitea-Sync-AI-Service* +""" + + changelog_path.write_text(content) + return {"success": True, "path": str(changelog_path), "content": content} \ No newline at end of file diff --git a/app/file_watcher.py b/app/file_watcher.py new file mode 100644 index 0000000..e33e55d --- /dev/null +++ b/app/file_watcher.py @@ -0,0 +1,25 @@ +import time +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +from pathlib import Path + + +class FileChangeHandler(FileSystemEventHandler): + def __init__(self, callback=None): + self.callback = callback + + def on_modified(self, event): + if not event.is_directory: + if self.callback: + self.callback(event.src_path) + print(f"[Watcher] Datei geändert: {event.src_path}") + + +def start_watcher(path: Path, callback=None): + """Startet den File-Watcher in einem Hintergrundthread.""" + event_handler = FileChangeHandler(callback=callback) + observer = Observer() + observer.schedule(event_handler, str(path), recursive=True) + observer.start() + + return observer \ No newline at end of file diff --git a/app/git_manager.py b/app/git_manager.py new file mode 100644 index 0000000..0c03267 --- /dev/null +++ b/app/git_manager.py @@ -0,0 +1,43 @@ +import subprocess +from pathlib import Path + + +def clone_repo(repo_url: str, target_dir: Path, token: str = None): + """Klont ein Repository in den Zielordner.""" + if not target_dir.exists(): + target_dir.mkdir(parents=True) + + # URL mit Token (falls nötig) + if token and "@" not in repo_url: + repo_url = repo_url.replace("https://", f"https://{token}@") + + result = subprocess.run( + ["git", "clone", repo_url, str(target_dir)], + capture_output=True, + text=True + ) + + return {"success": result.returncode == 0, "output": result.stdout, "error": result.stderr} + + +def push_changes(repo_path: Path, message: str = "Update 🧠"): + """Pusht lokale Änderungen ins Repository.""" + result = subprocess.run( + ["git", "add", "."], cwd=repo_path, capture_output=True, text=True + ) + + if result.returncode != 0: + return {"success": False, "error": f"git add fehlgeschlagen: {result.stderr}"} + + result = subprocess.run( + ["git", "commit", "-m", message], cwd=repo_path, capture_output=True, text=True + ) + + if result.returncode != 0 and "nothing to commit" not in result.stderr.lower(): + return {"success": False, "error": f"Commit fehlgeschlagen: {result.stderr}"} + + result = subprocess.run( + ["git", "push"], cwd=repo_path, capture_output=True, text=True + ) + + return {"success": result.returncode == 0, "output": result.stdout} \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..f80e889 --- /dev/null +++ b/app/main.py @@ -0,0 +1,26 @@ +import os +from pathlib import Path +from fastapi import FastAPI, HTTPException, Request +from fastapi.responses import HTMLResponse + +# Umgebungsvariablen mit Fallback +GITEE_URL = os.getenv("GITEE_URL", "https://git.carabella.ch/") +ACCESS_TOKEN = os.getenv("ACCESS_TOKEN", "") # Leerlassen, falls nicht gesetzt +COMMIT_NAME = os.getenv("COMMIT_NAME", "KI-Sparringpartner") + +# Projekt-Ordner (gemountet im Docker) +PROJECTS_DIR = Path("/projects") + + +def get_token(): + """Gibt Token zurück – aus .env oder leer.""" + return ACCESS_TOKEN or "" + + +app = FastAPI(title="Gitea Sync AI", version="1.0") + +# Frontend ausliefern +@app.get("/", response_class=HTMLResponse) +async def dashboard(request: Request): + with open("/home/user/projects/gitea-cloner/app/templates/index.html") as f: + return f.read() \ No newline at end of file diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000..3c7f3a1 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,45 @@ +import httpx +from pathlib import Path +from fastapi import APIRouter, Depends +from .git_manager import clone_repo, push_changes +from .changelog_gen import generate_changelog +from .main import get_token + +router = APIRouter() + + +@router.get("/health") +async def health_check(): + return {"status": "ok"} + + +@router.get("/repos") +async def list_repos(): + """Listet Repositories von Gitea auf.""" + GITEE_URL = "https://git.carabella.ch/api/v1/user/repos" + token = get_token() + + headers = {"Authorization": f"token {token}"} if token else {} + + async with httpx.AsyncClient() as client: + resp = await client.get(GITEE_URL, headers=headers) + return resp.json() + + +@router.post("/subscribe") +async def subscribe_repo(repo_url: str, target_name: str, token_provided: str = None): + """Abonniert und klont ein Repository.""" + # Wenn kein Token übergibst → nutz den DEFAULT aus .env oder Umgebung + effective_token = token_provided or get_token() + + target_dir = Path("/projects") / (target_name or "imported-repo") + + result = clone_repo(repo_url, target_dir, token=effective_token) + + if not result["success"]: + return {"error": result.get("error"), "success": False} + + # Optional: File-Watcher starten & Changelog erzeugen + generate_changelog(target_dir) + + return {"message": f"✅ {target_name} geklont!", "path": str(target_dir), "success": True} \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..3b83ce0 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,58 @@ + + + + + Gitea Sync AI 🧠 + + + + +

🧠 Gitea Sync AI – Dashboard

+ +

Verfügbare Repositories:

+
+ + + + + \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e5504a4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.8' + +services: + gitea-sync-ai: + build: . + ports: + - "8000:8000" + volumes: + - ./projects:/projects # Lokale Projekte sichtbar im Container + - ./auth:/app/auth # Optional für Token-Speicherung + env_file: + - .env # Lädt GITEE_URL, ACCESS_TOKEN, usw. + environment: + - GITEE_URL=${GITEE_URL} # Fallback falls .env fehlt + - ACCESS_TOKEN=${ACCESS_TOKEN} + - COMMIT_NAME=${COMMIT_NAME} + restart: unless-stopped \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ba9d0b1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.115.12 +uvicorn[standard]==0.30.6 +httpx==0.27.2 +watchdog==4.0.2 \ No newline at end of file