No description
  • Vue 44.2%
  • Python 39.2%
  • TypeScript 11.8%
  • Dockerfile 1.8%
  • Shell 1.4%
  • Other 1.6%
Find a file
Robert Vieider 529baa43e6 Render the new-project guide as an in-app manual, fix SPA fallback and add dgit helper
The onboarding checklist was a raw downloadable .md; render it in-app instead
with copy-to-clipboard code blocks (falling back to execCommand when the
Clipboard API is unavailable/denied, since DevHub may be served over plain
HTTP). Also fixes a real bug where the SPA fallback route swallowed actual
static files (e.g. the logo) by returning index.html for every non-API path.

Adds the dgit helper script (wraps git operations in a throwaway container
with persistent safe.directory config), downloadable from Settings, to
replace manually typing the full docker run incantation on the NAS for
every project.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 21:22:29 +02:00
backend 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
frontend 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
scripts 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
.env.example Fix port remapping: use DEVHUB_HOST_PORT env var instead of override file 2026-07-16 13:12:27 +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 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
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.md 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
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 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

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 pulldocker compose pulldocker 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 öffentlich über Reverse Proxy (https://git.plus-immobilien.at): 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.

Reverse Proxy / externer Zugriff

DevHub soll auch von außen über VPN/Reverse-Proxy erreichbar sein. TLS wird vom vorgeschalteten Proxy terminiert (z. B. Synology Reverse Proxy, Caddy oder Traefik), nicht von DevHub selbst:

  • Uvicorn läuft mit --proxy-headers und vertraut X-Forwarded-Proto.
  • .env: COOKIE_SECURE=true setzen, sobald HTTPS vor DevHub terminiert wird (sonst werden Session-Cookies mit Secure-Flag vom Browser verworfen).
  • Der Reverse Proxy muss WebSocket-Upgrade-Header weiterleiten (Upgrade, Connection: upgrade) — relevant für den Live-Log-Endpoint.

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"). Läuft produktiv auf der Synology NAS, verwaltet DevHub selbst sowie ooo-manager, Forgejo öffentlich über Reverse Proxy erreichbar.

Offen: echtes Beispiel-Plugin, Kategorien-Filter im Dashboard, optionales Client-seitiges Auto-Refresh der Docker-Metriken.