- Python 45.3%
- Vue 41.6%
- TypeScript 9.5%
- Dockerfile 1.2%
- Shell 1.1%
- Other 1.3%
| .claude | ||
| backend | ||
| frontend | ||
| scripts | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| Entwicklungsauftrag.txt | ||
| NEUES-PROJEKT-vorschlag.md | ||
| NEUES-PROJEKT.md | ||
| project_devhub.md | ||
| Projektbeschreibung.txt | ||
| README.md | ||
DevHub
Zentrale interne Weboberfläche zum Deployen und Überwachen der eigenen Docker-Compose-Projekte auf der Synology NAS. Ersetzt den manuellen Prozess (Ordner kopieren, Container Manager Projekt neu anlegen) durch einen Deploy-Klick (git pull → docker compose pull → docker compose up -d --build), inklusive Live-Logs, Deployment-Historie und einfacher Benutzerverwaltung.
Architektur- und Umsetzungsdetails: siehe Entwicklungsauftrag.txt / Projektbeschreibung.txt (ursprüngliche Spezifikation) sowie die Commit-Historie für die tatsächlichen Entscheidungen.
Stack
- Backend: FastAPI (Python 3.12), SQLAlchemy 2.0 + SQLite (kein Alembic, handgeschriebene
_ensure_column()-Migrationen) - Frontend: Vue 3 + Vite + TypeScript + Tailwind, als statische Assets in dasselbe Docker-Image gebaut
- Ein Container, ein
docker-compose.yml— kein separater Nginx/Reverse-Proxy-Container - Git-Server: Forgejo (eigener Container auf derselben NAS)
Lokale Entwicklung
Voraussetzungen: Python 3.12+, Node.js 22+.
# Backend
cd backend
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp ../.env.example ../.env # ausfüllen, siehe unten
.venv/bin/uvicorn app.main:app --reload --port 8000
# Frontend (separates Terminal)
cd frontend
npm install
npm run dev # http://localhost:5173, proxied auf Backend :8000
Ersten Admin-Nutzer anlegen
python3 -c "import bcrypt; print(bcrypt.hashpw(b'DEIN_PASSWORT', bcrypt.gensalt()).decode())"
Den Hash in .env als ADMIN_PASSWORD_HASH eintragen. Beim ersten Start wird daraus automatisch ein Admin-Nutzer angelegt (nur wenn die users-Tabelle noch leer ist — danach ist dieser Wert wirkungslos). Achtung: bcrypt-Hashes enthalten $-Zeichen, die Docker Compose in .env-Dateien als Variablen interpretiert — jedes $ muss dort als $$ geschrieben werden, sonst wird der Hash beim Start stillschweigend zerstört (siehe Abschnitt "Bekannte Stolperfallen").
Für das Einrichten eines neuen Projekts (nicht DevHub selbst) siehe die Schritt-für-Schritt-Checkliste in NEUES-PROJEKT.md.
Erst-Deployment auf die Synology NAS
- Forgejo aufsetzen (falls noch nicht vorhanden): eigener Container, z. B.
codeberg.org/forgejo/forgejo:15, SQLite reicht. - Deploy-Key anlegen (einmalig, manuell): SSH-Keypair erzeugen, öffentlichen Schlüssel in Forgejo pro Repo als Read-only Deploy Key hinterlegen. Privaten Schlüssel nach
./data/ssh/id_ed25519legen (wird ins Repo nicht eingecheckt). - Repo nach
/volume1/docker/projects/DevHub/klonen (DSM hat kein nativesgit— dafür einen Wegwerf-Container nutzen, siehe unten). Das ist ein einmaliger manueller Schritt, der klassische Henne-Ei-Fall: DevHub kann sich nicht selbst deployen, solange es noch nicht läuft. .envauf der NAS befüllen (siehe.env.example), insbesondereADMIN_PASSWORD_HASH(mit$$-Escaping!) undGIT_SSH_KEY_PATH=/app/data/ssh/id_ed25519. Falls Port 8000 auf der NAS schon belegt ist (z. B. durch Portainer),DEVHUB_HOST_PORT=<anderer Port>setzen.docker compose up -d --build.- Danach DevHub selbst als Projekt in der UI anlegen (Pfad
/volume1/docker/projects/DevHub) — mit der Checkbox "Dies ist DevHub selbst" aktiviert. Das zeigt Status/Git-Historie/Logs für DevHub mit an, deaktiviert aber den Deploy-Button. Warum, siehe nächster Abschnitt.
DevHub selbst aktualisieren (immer manuell, nie über den eigenen Deploy-Button)
Der Deploy-Button für DevHub selbst ist absichtlich deaktiviert (Checkbox "Dies ist DevHub selbst" beim Anlegen des Projekts). Grund: Der Prozess, der den Deploy ausführt, läuft innerhalb des Containers, der dabei gerade ersetzt wird. Docker Compose tauscht den Container mitten in der laufenden Anfrage aus — dabei wurde in der Praxis beobachtet, dass der neue Container zwar gebaut, aber nie gestartet wurde, während der alte schon beendet war (beide Container standen anschließend auf "off"). Kein Datenverlust (SQLite-DB liegt im Bind-Mount), aber ein manueller Eingriff war nötig, um wieder hochzufahren.
Andere Projekte (z. B. ooo-manager) sind davon nicht betroffen — dort ersetzt der Deploy-Vorgang einen anderen Container als den, der den Befehl ausführt.
So aktualisierst du DevHub selbst (per SSH auf der NAS, mit der dgit-Hilfsfunktion — siehe NEUES-PROJEKT.md für die einmalige Einrichtung):
cd /volume1/docker/projects/DevHub
dgit pull
# Neu bauen und starten — das läuft hier sicher durch, weil es direkt vom
# NAS-eigenen Docker-Client kommt, nicht von DevHub selbst aus
sudo docker compose up -d --build
Falls dabei mal ein alter, gestoppter Container übrig bleibt (z. B. nach einem unterbrochenen Vorgang):
sudo docker compose ps -a # Zustand ansehen
sudo docker rm devhub # falls ein gestoppter "devhub"-Container blockiert
sudo docker compose up -d # Compose übernimmt/startet den bereits gebauten Container
Bekannte Stolperfallen (aus echtem NAS-Betrieb)
- Kein natives
gitauf DSM. Statt bei jedem Aufruf einen Wegwerf-Container von Hand zu bauen, gibt's diedgit-Shell-Funktion — Einrichtung perscripts/setup-dgit.sh(auch herunterladbar direkt aus DevHub → Einstellungen → "Setup-Hilfen", sieheNEUES-PROJEKT.md). Kapselt Container,safe.directoryundhost.docker.internalweg, danach reichtdgit pull,dgit status,dgit clone ...wie normales Git. Das Skript schreibt bewusst in mehrere Profil-Dateien (.bash_profile,.profile,.bashrc), weil SSH-Login-Shells nicht zuverlässig.bashrcallein einlesen. - "detected dubious ownership": Git verweigert die Arbeit, wenn Datei-Owner ≠ ausführender Nutzer (üblich bei Bind-Mounts). Im DevHub-Image dauerhaft über
git config --system --add safe.directory '*'im Dockerfile gelöst.dgitlöst das für NAS-seitige Aufrufe über eine gemountete, persistente~/.dgit/gitconfig. - Container erreicht den eigenen Host nicht über dessen LAN-/Tailscale-/öffentliche IP ("Hairpin"-NAT, insbesondere auf Synology beobachtet — tritt sowohl auf Docker- als auch auf Router-Ebene auf). Git-Remote-URLs für Repos, die vom DevHub-Container selbst gepullt werden, müssen
http://host.docker.internal:3000/...verwenden, nicht die NAS-eigene IP oder gar die öffentliche Domain.docker-compose.ymlsetzt dafürextra_hosts: host.docker.internal:host-gateway;dgitmacht dasselbe für Wegwerf-Container. docker compose up -d --buildmerged Listen wieports:additiv statt sie zu ersetzen — einedocker-compose.override.ymlfür einen anderen Port funktioniert deshalb nicht wie erwartet. Stattdessen:${DEVHUB_HOST_PORT:-8000}indocker-compose.yml, Wert einfach in.envsetzen.- bcrypt-Hashes und
.env:$-Zeichen inADMIN_PASSWORD_HASHmüssen als$$geschrieben werden, sonst interpretiert Docker Compose sie als (nicht existierende) Variablen und zerstört den Hash lautlos. scpvon macOS auf DSM kann mit "subsystem request failed" scheitern (DSMs SSH fehlt oft ein funktionierendes SFTP-Subsystem) —scp -Oerzwingt das ältere SCP-Protokoll.- Zeitstempel wirken falsch (UTC statt lokal): SQLite/SQLAlchemy verlieren die Zeitzone beim Round-Trip. Frontend parst Backend-Zeitstempel deshalb explizit als UTC (
parseUtcTimestamp()infrontend/src/utils/datetime.ts). - Forgejo über Reverse Proxy (
https://git.plus-immobilien.at, mittlerweile nur per VPN erreichbar): sobald 2FA aktiv ist, funktioniert das normale Account-Passwort fürgit push/pullüber HTTPS nicht mehr — ein Personal Access Token (Forgejo → Einstellungen → Anwendungen) muss als Passwort verwendet werden.ROOT_URLmuss in Forgejosdocker-compose.ymlauf die öffentliche Domain gesetzt sein, sonst generiert Forgejo falsche Klon-URLs. - Let's-Encrypt-Erneuerung nach Firewall-Sperre — noch zu prüfen: Das Zertifikat für
git.plus-immobilien.at/devhub.plus-immobilien.atläuft über Synologys eingebauten Let's-Encrypt-Client, der standardmäßig HTTP-01-Validierung nutzt (braucht kurzzeitig erreichbaren Port 80 von außen). Jetzt, wo die Firewall eingehenden Zugriff dauerhaft blockt, könnte die nächste automatische Erneuerung (alle 60-90 Tage) stillschweigend fehlschlagen. Entweder rechtzeitig vor Ablauf Port 80 kurz manuell öffnen, oder auf DNS-01-Validierung umstellen (setzt einen ACME-Client mit API-Zugriff auf den DNS-Provider voraus, DSMs Certificate Manager unterstützt das nicht nativ).
Zugriff: WireGuard-VPN über UniFi (kein öffentlicher Zugriff mehr)
Forgejo (git.plus-immobilien.at) und DevHub (devhub.plus-immobilien.at) sind bewusst nicht mehr direkt aus dem Internet erreichbar — beide DNS-Namen lösen zur internen NAS-IP auf, der Zugriff darauf ist per Firewall auf das Firmennetz beschränkt. Von außerhalb (Homeoffice etc.) läuft der Zugriff über eine WireGuard-VPN-Verbindung ins Firmennetz, eingerichtet über UniFis Boardmittel (Teleport oder der klassische WireGuard-Server der UDM-Konsole) — nicht mehr über Tailscale. Sobald die VPN-Verbindung steht, funktionieren beide Domains ganz normal (Browser-Zugriff auf DevHub, git push/pull gegen Forgejo).
TLS wird weiterhin vom vorgeschalteten Reverse Proxy terminiert, nicht von DevHub selbst:
- Uvicorn läuft mit
--proxy-headersund vertrautX-Forwarded-Proto. .env:COOKIE_SECURE=truebleibt gesetzt, solange HTTPS vor DevHub terminiert wird.- Der Reverse Proxy muss WebSocket-Upgrade-Header weiterleiten (
Upgrade,Connection: upgrade) — relevant für den Live-Log-Endpoint.
Basic-Auth-Gate und Login-Lockout (Code vorhanden, aktuell nicht aktiviert)
Für den Zeitraum, in dem DevHub direkt aus dem Internet erreichbar war, gab es zusätzlich ein HTTP-Basic-Auth-Gate vor der ganzen App (Login-Seite, statische Assets, alles) — Grund: der gemountete Docker-Socket macht eine kompromittierte Session praktisch zu Root auf der NAS. Jetzt, wo der Zugriff ohnehin nur über VPN möglich ist, ist dieses zusätzliche Gate nur noch doppelte Reibung ohne echten Mehrwert: BASIC_AUTH_USERNAME/BASIC_AUTH_PASSWORD in der .env auf der NAS leer lassen, dann bleibt der zusätzliche Auth-Dialog aus. Der Code dafür (backend/app/main.py) bleibt bestehen und lässt sich jederzeit reaktivieren, falls einer der beiden Dienste nochmal öffentlich erreichbar gemacht wird.
Das Login-Lockout (5 Fehlversuche pro Benutzername → 5 Minuten Sperre) ist davon unabhängig und bleibt aktiv — reine Prozessspeicher-Zählung, kein Datenverlust bei Neustart, keine spürbare Reibung im Normalbetrieb.
Docker-Socket-Zugriff
Der Container mountet /var/run/docker.sock und läuft als root — das ist notwendig, damit DevHub Docker-Compose-Befehle für andere Container auf dem Host ausführen kann (dieselbe Technik wie Portainer/Dockge). Das gewährt root-äquivalenten Host-Zugriff; für ein internes Tool auf einer LAN-only bzw. VPN-abgesicherten NAS ist das ein akzeptierter Trade-off. Optionale spätere Härtung: ein docker-socket-proxy-Sidecar, der die API-Oberfläche einschränkt (nicht umgesetzt).
Projektstruktur
backend/app/ FastAPI-Anwendung (routes/, services/, plugins/, utils/)
frontend/src/ Vue 3 SPA (views/, components/, stores/, api/)
data/ bind-gemountet: SQLite-DB + SSH-Deploy-Key (gitignored)
Status
Umgesetzt: Auth mit Rollen (Admin/Entwickler), Benutzerverwaltung inkl. Passwort-Änderung, Projekt-CRUD mit Verzeichnis-Browser, Deploy/Restart/Stop/Start (Deploy läuft als Hintergrund-Task mit Fortschritts-Toast statt einer minutenlang offenen Anfrage), Deployment-Historie, Live-Logs per WebSocket, Projekt-Detailseite mit README (Markdown-gerendert), Git-Historie, maskierter .env-Ansicht, Plugin-Hook-Seam (noch ohne echtes Plugin), Plus-Immobilien-Branding, herunterladbare Setup-Hilfen (Einstellungen → "Setup-Hilfen"), optionales Basic-Auth-Gate + Login-Lockout (aktuell deaktiviert). Läuft produktiv auf der Synology NAS, verwaltet DevHub selbst sowie ooo-manager. Forgejo und DevHub sind nicht mehr öffentlich erreichbar, sondern nur noch über WireGuard-VPN (UniFi) aus dem Firmennetz.
Offen: echtes Beispiel-Plugin, Kategorien-Filter im Dashboard, optionales Client-seitiges Auto-Refresh der Docker-Metriken.