Files
Webklar-Kundenbereich/runner/DEPLOY.md
T
2026-09-30 21:54:01 +00:00

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-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)

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.