Files
2026-09-30 21:54:01 +00:00

157 lines
7.8 KiB
Markdown

# Preview-Runner: Livegang-Anleitung (R18)
> **Umgesetzt am 2026-10-01 (L1), abweichend von diesem Vorschlag:** Der Runner laeuft wie das Portal
> als `node:20-alpine` mit schreibgeschuetzt eingehaengtem `build/runner` (`/runner:ro`,
> `command: apk add docker-cli git && node src/index.js`) statt eines gebauten Images; `deploy.sh`
> macht bei Aenderungen in `runner/` und leerer Warteschlange nur `docker restart webklar-preview-runner`
> (Status-Abfrage ueber die Nicht-Sandbox-Adresse, nicht 127.0.0.1). Git-Zugang: `GITEA_DEPLOY_TOKEN`
> (nur lesen). Firewall: `/usr/local/sbin/preview-sandbox-firewall.sh` + systemd-Dienst
> `preview-sandbox-firewall.service` (Bridge-Name fest `br-preview`), Einzelregeln statt der
> fehlerhaften kombinierten Zeile unten. Massgeblich ist die echte Konfiguration auf dem Server.
Stand: Vorschlag aus der Abschluss-Prüfung. Nichts davon ist im Repo umgesetzt, `deploy.sh`
(`/home/webklar/apps/kundenbereich.webklar.com/deploy.sh`) liegt außerhalb des Repos und wird hier
nur als Vorschlag beschrieben. Reihenfolge laut Spezifikation: L1 (Netz, Geheimnisse, Compose,
Schema-Skript) vor L2 (Portal mit `PREVIEW_RUNNER=off`), dann L3 (`PREVIEW_RUNNER=on`).
## 1. Netzwerk `preview-sandbox` (VOR dem ersten Start anlegen)
```sh
docker network create --subnet 172.30.0.0/24 preview-sandbox
```
Der Runner verweigert den Start, wenn er keine IPv4-Adresse außerhalb dieses Subnetzes hat.
`PREVIEW_SANDBOX_SUBNET` muss exakt dem Subnetz entsprechen. Das Subnetz nicht in Compose anders
definieren (Netz als `external: true` einbinden).
Verzeichnisse auf dem Host anlegen (root-eigen genügt, der Runner setzt Besitzer 1000 selbst):
```sh
mkdir -p /srv/webklar-preview-runner/work /srv/webklar-preview-runner/cache /var/www/previews
```
## 2. Compose-Dienst (in `/home/webklar/apps/docker-compose.yml`)
Der Image-Build-Kontext ist `kundenbereich.webklar.com/build/runner` (Dockerfile im Repo unter
`runner/`). Hostpfade stehen im Container unter dem IDENTISCHEN Pfad (R6).
```yaml
preview-runner:
build:
context: ./kundenbereich.webklar.com/build/runner
image: webklar-preview-runner:latest
container_name: webklar-preview-runner
restart: unless-stopped
init: true
mem_limit: 256m
# keine ports: - erreichbar nur im Docker-Netz webklar-network (Traefik) und vom Portal
environment:
- PREVIEW_RUNNER_TOKEN=${PREVIEW_RUNNER_TOKEN}
- GITEA_URL=https://git.webklar.com
- GITEA_TOKEN=${GITEA_SYSTEM_TOKEN} # lesender Token, der die WEBklar-Repos sehen darf
- PREVIEW_BASE_HOST=project.webklar.com
- PREVIEWS_ROOT=/var/www/previews
- RUNNER_WORK_ROOT=/srv/webklar-preview-runner/work
- RUNNER_CACHE_ROOT=/srv/webklar-preview-runner/cache
- PREVIEW_SANDBOX_SUBNET=172.30.0.0/24
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/www/previews:/var/www/previews
- /srv/webklar-preview-runner:/srv/webklar-preview-runner
networks:
- webklar-network
- preview-sandbox
labels:
- traefik.enable=true
- traefik.docker.network=webklar-network
- traefik.http.services.preview-runner.loadbalancer.server.port=8081
# nur Dienst, kein Router: die Router erzeugt previewDeploy.syncPreviewTls (preview-runner@docker)
networks:
preview-sandbox:
external: true
```
(Den `networks:`-Block in die bestehende Datei einfügen, nicht doppelt anlegen.) Keine Appwrite-,
SMTP- oder Stripe-Variablen und kein `PREVIEW_ENV_KEY` im Runner.
Erster Start: `docker compose build preview-runner && docker compose up -d preview-runner`.
## 3. Portal-Umgebung (Dienst `kundenbereich-server`, Variablen einzeln auflisten)
```yaml
- PREVIEW_RUNNER=${PREVIEW_RUNNER:-off} # L2: off, L3: on
- PREVIEW_RUNNER_URL=http://webklar-preview-runner:8080
- PREVIEW_RUNNER_TOKEN=${PREVIEW_RUNNER_TOKEN}
- PREVIEW_ENV_KEY=${PREVIEW_ENV_KEY} # 32 Byte, Base64 (openssl rand -base64 32)
```
Das Verzeichnis `upload-data/preview-logs` muss persistent sein (liegt in `./build/`).
## 4. Firewall für die Sandbox-Bridge (Livegang-Schritt, kein Code)
Zuerst den Bridge-Namen ermitteln: `BR=br-$(docker network inspect preview-sandbox -f '{{.Id}}' | cut -c1-12)`.
```sh
# Sandbox -> Host (INPUT): alles verwerfen, bereits bestehende Antworten erlauben
iptables -I INPUT -i $BR -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i $BR -j DROP
# Sandbox -> andere Ziele (DOCKER-USER, wird vor Docker-Regeln ausgewertet)
iptables -I DOCKER-USER -s 172.30.0.0/24 -d 169.254.169.254 -j DROP # Hetzner-Metadaten
iptables -I DOCKER-USER -s 172.30.0.0/24 -d 10.0.0.0/8 -j DROP
iptables -I DOCKER-USER -s 172.30.0.0/24 -d 192.168.0.0/16 -j DROP
# 172.16.0.0/12 (andere Docker-Netze) sperren, ausser dem eigenen /24; Runner-Zugriff wird nicht gebraucht
iptables -I DOCKER-USER -s 172.30.0.0/24 -d 172.30.0.0/24 -j ACCEPT # Dev-zu-Dev im eigenen Netz (R13, bekannt)
iptables -I DOCKER-USER -s 172.30.0.0/24 ! -d 172.30.0.0/24 -d 172.16.0.0/12 -j DROP
```
Reihenfolge beachten: `-I` setzt vorne ein, die ACCEPT-Regel für das eigene /24 muss vor dem
172.16/12-DROP stehen (also zuletzt mit `-I` eingefügt, wie oben die Zeilen in umgekehrter
Wirkungsreihenfolge; nach dem Einrichten mit `iptables -S DOCKER-USER` prüfen). DNS läuft über den
Docker-Resolver (127.0.0.11) und bleibt erlaubt, Ausgang ins Internet (npm, Registries) ebenfalls.
Regeln persistent machen (z. B. `iptables-persistent`), sonst gehen sie beim Neustart verloren.
## 5. deploy.sh-Erweiterung (Vorschlag, bewusst NICHT eingespielt)
Voraussetzung: der Build-Container (`kundenbereich-build`) bekommt zusätzlich
`/home/webklar/apps/docker-compose.yml:/compose/docker-compose.yml:ro` eingehängt und hat
`docker-cli` samt Compose-Plugin. Der Runner wird nur neu gebaut und neu erstellt, wenn sich
`runner/` geändert hat UND `GET /status` eine leere Warteschlange meldet (sonst würden laufende
Builds/Dev-Server beendet); andernfalls überspringen und protokollieren, der nächste Deploy versucht
es erneut. Der Runner wird VOR dem Portal neu gestartet, damit dessen Dev-Registrierung gleich
klappt (der Abgleich im Portal holt Verpasstes ohnehin alle 2 Minuten nach).
Einfügen direkt vor dem Neustart des Portals; vor dem `git reset --hard` den alten Stand merken
(`OLD_HEAD=$(git rev-parse HEAD 2>/dev/null || true)`):
```sh
# --- Preview-Runner (nur wenn runner/ geaendert) ---
if [ -S /var/run/docker.sock ] && command -v docker >/dev/null 2>&1 && [ -f /compose/docker-compose.yml ]; then
NEW_HEAD=$(git rev-parse HEAD)
if [ -z "${OLD_HEAD}" ] || ! git diff --quiet "${OLD_HEAD}" "${NEW_HEAD}" -- runner 2>/dev/null; then
QUEUE=$(docker exec webklar-preview-runner sh -c \
'wget -qO- --header "Authorization: Bearer $PREVIEW_RUNNER_TOKEN" http://127.0.0.1:8080/status' 2>/dev/null \
| sed -n 's/.*"queue":\([0-9]*\).*/\1/p')
if [ -z "${QUEUE}" ] || [ "${QUEUE}" = "0" ]; then
echo "Preview-Runner: Image neu bauen und Container neu erstellen..."
docker build -t webklar-preview-runner:latest /app/runner \
&& docker compose -f /compose/docker-compose.yml --project-directory /home/webklar/apps \
up -d --no-build --no-deps preview-runner \
|| echo "Preview-Runner: Aktualisierung fehlgeschlagen (Portal-Deploy laeuft weiter)"
else
echo "Preview-Runner: Warteschlange nicht leer (${QUEUE}) - Aktualisierung uebersprungen, naechster Deploy versucht es erneut"
fi
fi
fi
```
Hinweise: Ist der Runner noch nicht angelegt (`docker exec` schlägt fehl, `QUEUE` leer), wird gebaut
und erstellt, das ist beim ersten Mal gewollt. `wget` ist im Alpine-Image enthalten. Die
Status-Abfrage bitte vor dem Einspielen einmal von Hand gegenprüfen.
## 6. Rückfall
`PREVIEW_RUNNER=off` und Portal neu starten: Der bisherige Weg läuft unverändert. Dokumente behalten
`previewMode`/`previewState`, die Dev-Router verschwinden beim nächsten TLS-Sync.