7.8 KiB
Preview-Runner: Livegang-Anleitung (R18)
Umgesetzt am 2026-10-01 (L1), abweichend von diesem Vorschlag: Der Runner laeuft wie das Portal als
node:20-alpinemit schreibgeschuetzt eingehaengtembuild/runner(/runner:ro,command: apk add docker-cli git && node src/index.js) statt eines gebauten Images;deploy.shmacht bei Aenderungen inrunner/und leerer Warteschlange nurdocker 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-Dienstpreview-sandbox-firewall.service(Bridge-Name festbr-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)
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):
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).
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)
- 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).
# 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)):
# --- 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.