From 651745ecccd2da38de358f4a3a51d4315b449b39 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 19:39:21 +0000 Subject: [PATCH] Spec: Preview 2.0 mit Preview-Runner, Modi und Variablen Co-Authored-By: Claude Opus 5.5 --- .../specs/2026-09-30-preview-runner-design.md | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-30-preview-runner-design.md diff --git a/docs/superpowers/specs/2026-09-30-preview-runner-design.md b/docs/superpowers/specs/2026-09-30-preview-runner-design.md new file mode 100644 index 0000000..6d2ee27 --- /dev/null +++ b/docs/superpowers/specs/2026-09-30-preview-runner-design.md @@ -0,0 +1,135 @@ +# Preview 2.0 – Preview-Runner, Modi, Variablen – Design + +Stand: 2026-09-30 · Status: vom Nutzer im Chat freigegeben (Abschnitte 1–3), Spec zur Durchsicht +Teilprojekt **2** von 3 (1 Projektarten – live seit 2026-09-30; 3 Website-Analyse – eigene Spec). +Regel des Nutzers: bauen → live → Nutzer testet live → erst dann das nächste Teilprojekt. +Recherche: `/root/reports/analyse-funktion-2026-09-30/projekttypen-preview.md`. + +## Ziel + +Previews starten automatisch, auch für vibe-gecodete Projekte, die man mit `npm run dev` startet. +Builds laufen **nicht mehr im Portal-Container** (der Appwrite-, Gitea-, SMTP- und Stripe-Zugangsdaten hat), +sondern in Wegwerf-Containern ohne Zugangsdaten. Projekte können Preview-Variablen bekommen, ohne dass +eine `.env` im Repo liegt. + +## Entscheidungen (vom Nutzer) + +| # | Entscheidung | +|---|---| +| V1 | Modus **pro Projekt**: `auto` (Vorauswahl) · `build` · `dev` · `off`. `auto` = erst statisch/Build, Dev-Server nur als Ausweg. | +| V2 | Neuer Dienst **Preview-Runner** als eigener Container (Variante A). Nur er darf Docker-Container starten. | +| V3 | **Preview-Variablen pro Projekt** im WOMS, verschlüsselt gespeichert, nur an Build/Dev dieses Projekts. | +| V4 | Build-Fehler ⇒ **letzte funktionierende Version bleibt online** (keine Platzhalterseite mehr). | +| V5 | Dev-Server starten beim ersten Aufruf, stoppen nach **20 min** ohne Aufruf, **max. 3** gleichzeitig. | +| V6 | Kundenportal unverändert (außer der Seite „Preview startet …“ bei schlafendem Dev-Server). | + +## 1. Aufbau + +``` +Gitea-Push ──▶ Portal (webhook) ──Auftrag──▶ Preview-Runner ──▶ Docker + │ │ ├─ Build-Container (Wegwerf) + │ │ └─ Dev-Container (bei Bedarf) + │ ▼ + │ /var/www/previews/ (fertige Dateien) + ▼ + Traefik: .project.webklar.com ──preview-chain──▶ preview-nginx (static/build) + └────▶ Runner-Proxy (dev) +``` + +### 1.1 Portal (bleibt Chef) +- Neue Attribute an `websiteProjects` (Schema-Skript `scripts/preview-runner-setup.mjs`, idempotent, + Attributliste mit `limit 200` lesen): + `previewMode` (string 10: auto|build|dev|off; leer = auto), `previewEnv` (string 20000, verschlüsselt), + `previewState` (string 20: online|building|failed|sleeping|starting|off), `previewDetected` (string 40, + z. B. `build:vite:npm`, `static`, `dev:npm`), `previewBuiltSha` (string 40), `previewBuiltAt` (ISO), + `previewError` (string 500). +- Vorauswahl des Modus bei neuer Zuordnung aus dem Projektarten-Katalog (`projectKinds.js`): + Art website/webapp ⇒ `auto`, alle anderen ⇒ `off`. Bestehende Projekte ohne Wert gelten als `auto`. +- `server/services/previewDeploy.js` bekommt einen Schalter `config.previewRunner.enabled` + (`PREVIEW_RUNNER=on|off`, Vorgabe off). `off` = bisheriger Weg unverändert. `on` = Aufträge an den Runner. +- `syncPreviewTls()` schreibt für Projekte im Modus dev (oder `auto` mit Ergebnis dev) den Router auf den + Service `preview-runner@docker` statt `preview-nginx@docker`; Middleware `preview-chain` bleibt gleich + (öffentliche Subdomains wie heute ohne Middleware). +- Verschlüsselung `server/services/previewEnv.js`: AES-256-GCM, Schlüssel `PREVIEW_ENV_KEY` (32 Byte base64) + aus `/home/webklar/apps/.env`; Format `v1:::` (base64). Namen `^[A-Z_][A-Z0-9_]*$`, + max. 30 Variablen, Wert max. 4096 Byte. Ohne Schlüssel ⇒ Variablen-Funktion aus, Fehlermeldung im WOMS. +- Protokolle: `upload-data/preview-logs//.log`, letzte 300 Zeilen je Lauf, + die letzten 5 Läufe je Projekt, ältere löschen. Variablenwerte (≥ 4 Zeichen) werden vor dem Speichern + durch `•••` ersetzt. +- Admin-API (requireAppwriteStaff): + `GET /api/admin/website-projects/:id/preview` (Modus, Status, erkannt, Namen der Variablen, Protokoll-Liste), + `PATCH …/preview` `{ mode }`, `PUT …/preview/env` `{ set: {NAME: wert}, remove: [NAME] }`, + `POST …/preview/rebuild`, `POST …/preview/dev/start|stop`, `GET …/preview/logs/:runId`. + Änderung von Variablen oder Modus ⇒ automatisch Neubau bzw. Dev-Neustart. + +### 1.2 Preview-Runner (neuer Container `webklar-preview-runner`) +- Code im Portal-Repo unter `runner/` (eigenes `package.json`, Node 20, keine Abhängigkeit zum Portal-Code). + Compose-Dienst in `/home/webklar/apps/docker-compose.yml`, Image `node:20-alpine` + Docker-CLI, + Mount `build/runner` (read-only), `/var/run/docker.sock`, `/var/www/previews`, Cache-Volume. + `deploy.sh` startet den Runner neu, wenn sich `runner/` geändert hat. +- Netze: `webklar-network` (für Portal und Traefik) und `preview-sandbox` (Dev-Container). **Kein** + veröffentlichter Port. +- Umgebung: nur `PREVIEW_RUNNER_TOKEN`, `GITEA_DEPLOY_TOKEN` (read:repository), Gitea-URL, Limits. + **Keine** Appwrite-/SMTP-/Stripe-Zugangsdaten. +- HTTP-API (Bearer `PREVIEW_RUNNER_TOKEN`, Port 8080 nur intern): + `POST /jobs/build` `{ projectId, repoFullName, branch, sha, subdomain, mode, env, index }` ⇒ `{ runId }`; + `GET /jobs/:runId` ⇒ `{ state, detected, sha, log, error }`; + `POST /dev/:subdomain/start|stop`; `GET /status` (laufende Dev-Server, Warteschlange, freier Speicher). + Nur diese festen Aufträge – keine freien Docker-Befehle. +- **Erkennung (`auto`)**: `index.html` im Wurzelverzeichnis (oder `index` aus `.webklar-preview.json`) und + kein `build`-Skript ⇒ `static`; sonst `build`-Skript vorhanden ⇒ Build; Build fehlgeschlagen oder kein + Build-Skript, aber `dev`-Skript ⇒ `dev`; sonst `static` mit Hinweis. Paketmanager aus Lock-Datei: + `pnpm-lock.yaml` ⇒ pnpm, `yarn.lock` ⇒ yarn, `bun.lockb`/`bun.lock` ⇒ bun, sonst npm (`npm ci`, ohne Lock + `npm install`). Ausgabeordner: `dist`, `build`, `out`, `web-build` (wie heute) bzw. aus `.webklar-preview.json` + (`output`). +- **Build-Container**: `node:20` (bun: `oven/bun`), `--memory 1g --cpus 1 --pids-limit 512 --cap-drop ALL + --security-opt no-new-privileges --user 1000:1000 --network preview-sandbox`, Zeitlimit 15 min, genau **einer** + gleichzeitig (Warteschlange). Quelle per `git clone --depth 1` mit Deploy-Token in ein Arbeitsverzeichnis; + Cache `node_modules` je Repo (Volume, max. 5 GB gesamt, Einträge > 14 Tage löschen). Erfolg ⇒ Ausgabe atomar + nach `/var/www/previews/` (in `.new` kopieren, dann umbenennen). Fehler ⇒ vorhandener Ordner bleibt. +- **Dev-Container**: gleiches Sicherheitsprofil, `--memory 512m`, Name `preview-dev-`, nur im Netz + `preview-sandbox`. Start `npm run dev -- --host 0.0.0.0 --port 5173` (bzw. passender Paketmanager; Port aus + `.webklar-preview.json` `devPort` überschreibbar); Vite bekommt `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=.project.webklar.com`. + Bereit, sobald der Port antwortet (max. 180 s). Leerlauf 20 min ⇒ stoppen (Container entfernen, Cache bleibt). + Max. 3; beim 4. weicht der am längsten ungenutzte. Neuer Push ⇒ Code aktualisieren und neu starten. +- **Runner-Proxy** (Port 8081, Traefik-Service `preview-runner`): leitet `Host: .project.webklar.com` + inkl. WebSocket (Hot-Reload) an `preview-dev-:` weiter; läuft keiner ⇒ Start anstoßen und Seite + „Preview startet …“ (deutsch, Neuladen alle 3 s, nach 180 s Fehlermeldung „Preview konnte nicht starten“). +- **Schutzschalter**: freier Speicher (`/proc/meminfo` MemAvailable des Hosts) < 1,5 GB ⇒ keine neuen Builds + oder Dev-Starts; Antwort `503 { error: 'Server ausgelastet' }`, Build bleibt in der Warteschlange. + +## 2. WOMS + +Block **„Preview“** auf der Projektkarte im Ticket (`TicketProjects`/`AssignedProjectCard`) und im +„Bearbeiten“-Dialog unter Projekte: +1. **Modus**: Automatisch · Build · Dev-Server · Aus; darunter „Automatisch → Build (Vite, npm)“ o. ä. aus + `previewDetected`. +2. **Status**: 🟢 „Online seit TT.MM. HH:MM (Build )“ · 🟡 „Baut gerade …“ / „Dev-Server schläft, + startet beim nächsten Aufruf“ · 🔴 „Build fehlgeschlagen – letzte funktionierende Version ist online“. + Knöpfe: Neu bauen · Protokoll ansehen · bei Dev: Jetzt starten / Stoppen. +3. **Protokoll**: letzte 300 Zeilen, aufklappbar, letzte 5 Läufe wählbar. +4. **Preview-Variablen**: Tabelle Name/Wert, Werte nach dem Speichern nur `•••`; Hinweis „Alles mit `VITE_` / + `NEXT_PUBLIC_` ist im Browser sichtbar“. +5. **Services-Seite**: Preview-Punkt (grün/gelb/rot/grau) je Projektzeile. + +## 3. Sicherheit, Speicher, Tests, Livegang + +Sicherheit und Grenzen wie in Abschnitt 1.2. Zusätzlich: Runner-Eingaben streng prüfen (Subdomain +`^[a-z0-9-]{1,63}$`, Repo `^[\w.-]+/[\w.-]+$`, sha `^[0-9a-f]{7,40}$`), Pfade nur unterhalb der festen Wurzeln. + +**Tests**: Runner-Unit-Tests mit Schein-Docker (Auftragsprüfung, Warteschlange, Erkennung, Leerlauf-Stopp, +Verdrängung, Schutzschalter, Proxy inkl. Startseite); Portal (Verschlüsselung, Maskierung, Modus-Vorgaben, +TLS-Router für dev, Schalter off = altes Verhalten); WOMS (Preview-Block-Logik, Playwright gegen Fixtures); +echter Durchlauf auf dem Server mit drei Beispiel-Repos (HTML, Vite-Build, nur `dev`). + +**Livegang** (jeder Schritt mit OK des Nutzers): +L1 Netz `preview-sandbox`, Schlüssel `PREVIEW_RUNNER_TOKEN` + `PREVIEW_ENV_KEY` in `.env`, Runner-Dienst in +Compose, Schema-Skript · L2 Portal pushen mit `PREVIEW_RUNNER=off` · L3 Schalter `on`, 12 Previews einzeln neu +bauen und vorher/nachher vergleichen · L4 WOMS pushen · L5 Live-Test des Nutzers mit Dev-Modus. +Rückweg jederzeit: `PREVIEW_RUNNER=off` + Portal neu starten. + +## Nicht Teil dieser Spec + +Datenbanken/Backends als Beiwagen-Container, Next.js-SSR mit eigenem Start-Modus (`start`), gemeinsame +Standard-Variablen (Variante C), Previews für Handy-Apps (Expo-Web), Website-Analyse (Teilprojekt 3), +„Art ändern“ aus Teilprojekt 1.