# 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.