🚀 Initialer Commit: Gitea Sync AI Service mit Docker + OpenWebUI-Vorbereitung

This commit is contained in:
KI-Sparringpartner
2026-08-03 12:57:52 +02:00
commit f0bd062479
14 changed files with 362 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
from .main import app
+22
View File
@@ -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}
+25
View File
@@ -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
+43
View File
@@ -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}
+26
View File
@@ -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()
+45
View File
@@ -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}
+58
View File
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Gitea Sync AI 🧠</title>
<style>
body { font-family: sans-serif; padding: 20px; background: #f5f5f5; }
h1 { color: #333; }
.repo-card { background: white; margin: 10px 0; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,.1); }
button { padding: 8px 16px; cursor: pointer; border: none; border-radius: 4px; }
.btn-subscribe { background: #007bff; color: white; }
.btn-push { background: #28a745; color: white; }
input, button { margin-top: 10px; }
</style>
</head>
<body>
<h1>🧠 Gitea Sync AI Dashboard</h1>
<h3>Verfügbare Repositories:</h3>
<div id="repos"></div>
<script>
async function loadRepos() {
const res = await fetch("/api/repos");
const repos = await res.json();
const container = document.getElementById("repos");
container.innerHTML = "";
repos.forEach(repo => {
const card = document.createElement("div");
card.className = "repo-card";
card.innerHTML = `
<strong>${repo.name}</strong>
<p>${repo.full_name || repo.id} - ${repo.owner?.login || "unbekannt"}</p>
<button class="btn-subscribe" onclick="subscribe(${JSON.stringify(repo).replace(/"/g, '&quot;')})"> Abonnieren & Klonen</button>
`;
container.appendChild(card);
});
}
async function subscribe(repo) {
const target = prompt("Ziel-Name (optional):") || repo.name;
const token = prompt("Gitea Token:");
await fetch("/api/subscribe", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({repo_url: repo.clone_url, target_name: target, token})
});
alert(`📦 "${target}" wurde geklont!`);
}
loadRepos();
</script>
</body>
</html>