No description
  • Python 45.3%
  • Vue 41.6%
  • TypeScript 9.5%
  • Dockerfile 1.2%
  • Shell 1.1%
  • Other 1.3%
Find a file
2026-10-01 21:23:32 +02:00
.claude Live Update/deploy Log ergänzt 2026-09-18 12:03:06 +02:00
backend Update Deployment Stabilisierung 2026-10-01 21:23:32 +02:00
frontend Update DeploymentHistoryTable.vue 2026-09-18 15:07:08 +02:00
scripts Update setup-dgit.sh 2026-07-23 22:16:54 +02:00
.env.example Document VPN-only access via UniFi WireGuard, deprecate public Basic Auth 2026-07-17 13:06:32 +02:00
.gitignore Render the new-project guide as an in-app manual, fix SPA fallback and add dgit helper 2026-07-16 21:22:29 +02:00
docker-compose.yml Fix container-to-host git access via host.docker.internal, UTC display, toast timing 2026-07-16 16:42:47 +02:00
Dockerfile Move onboarding guide content into the DB with an in-app editor 2026-07-17 13:45:23 +02:00
Entwicklungsauftrag.txt Initial DevHub scaffold: Phase 1 (auth, models, service layer, project CRUD, minimal UI) 2026-07-16 00:19:41 +02:00
NEUES-PROJEKT-vorschlag.md deploy timeout, multi Container Project, deploy process notification 2026-07-21 15:28:06 +02:00
NEUES-PROJEKT.md deploy timeout, multi Container Project, deploy process notification 2026-07-21 15:28:06 +02:00
project_devhub.md neues Repo und Performance Update 2026-09-10 11:06:34 +02:00
Projektbeschreibung.txt Initial DevHub scaffold: Phase 1 (auth, models, service layer, project CRUD, minimal UI) 2026-07-16 00:19:41 +02:00
README.md Document VPN-only access via UniFi WireGuard, deprecate public Basic Auth 2026-07-17 13:06:32 +02:00

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

  1. Forgejo aufsetzen (falls noch nicht vorhanden): eigener Container, z. B. codeberg.org/forgejo/forgejo:15, SQLite reicht.
  2. 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_ed25519 legen (wird ins Repo nicht eingecheckt).
  3. Repo nach /volume1/docker/projects/DevHub/ klonen (DSM hat kein natives git — 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.
  4. .env auf der NAS befüllen (siehe .env.example), insbesondere ADMIN_PASSWORD_HASH (mit $$-Escaping!) und GIT_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.
  5. docker compose up -d --build.
  6. 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 git auf DSM. Statt bei jedem Aufruf einen Wegwerf-Container von Hand zu bauen, gibt's die dgit-Shell-Funktion — Einrichtung per scripts/setup-dgit.sh (auch herunterladbar direkt aus DevHub → Einstellungen → "Setup-Hilfen", siehe NEUES-PROJEKT.md). Kapselt Container, safe.directory und host.docker.internal weg, danach reicht dgit 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 .bashrc allein 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. dgit lö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.yml setzt dafür extra_hosts: host.docker.internal:host-gateway; dgit macht dasselbe für Wegwerf-Container.
  • docker compose up -d --build merged Listen wie ports: additiv statt sie zu ersetzen — eine docker-compose.override.yml für einen anderen Port funktioniert deshalb nicht wie erwartet. Stattdessen: ${DEVHUB_HOST_PORT:-8000} in docker-compose.yml, Wert einfach in .env setzen.
  • bcrypt-Hashes und .env: $-Zeichen in ADMIN_PASSWORD_HASH müssen als $$ geschrieben werden, sonst interpretiert Docker Compose sie als (nicht existierende) Variablen und zerstört den Hash lautlos.
  • scp von macOS auf DSM kann mit "subsystem request failed" scheitern (DSMs SSH fehlt oft ein funktionierendes SFTP-Subsystem) — scp -O erzwingt 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() in frontend/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ür git push/pull über HTTPS nicht mehr — ein Personal Access Token (Forgejo → Einstellungen → Anwendungen) muss als Passwort verwendet werden. ROOT_URL muss in Forgejos docker-compose.yml auf 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.at lä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-headers und vertraut X-Forwarded-Proto.
  • .env: COOKIE_SECURE=true bleibt 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.