From 39af9ecdb4982d253d8cec48273958c5fc0fa685 Mon Sep 17 00:00:00 2001 From: MetalCircle Codex Bot Date: Tue, 15 Sep 2026 20:29:43 +0200 Subject: [PATCH] Add guarded pre-production deployment script --- docs/wiki/Firebase.md | 30 +++++++++++++----- scripts/deploy-preprod.sh | 66 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-) create mode 100755 scripts/deploy-preprod.sh diff --git a/docs/wiki/Firebase.md b/docs/wiki/Firebase.md index 30541f0..d94fbf8 100644 --- a/docs/wiki/Firebase.md +++ b/docs/wiki/Firebase.md @@ -86,20 +86,34 @@ Den Beispielpfad durch den tatsächlichen absoluten Pfad ersetzen, kein `~` in ` Vor Aktivierung bestätigen: Die vorhandene DB ist die Pre-Production-DB, keine lokalen Daten oder Sessions wurden importiert. Übernommene `sessions`, `push_devices` und `push_notifications` aus einem anderen Umfeld müssen vor Aktivierung gezielt bereinigt werden; dies nicht durch Kopieren lokaler Daten lösen. Für den Smoke-Test frisch in Pre-Production anmelden und das Gerät dort registrieren. +Der bevorzugte reguläre Deployment-Weg im bestehenden Checkout ist nun: + ```bash -git pull --ff-only -docker compose -f compose.yml -f compose.preprod.yml config --quiet -docker compose -f compose.yml -f compose.preprod.yml build web -docker compose -f compose.yml -f compose.preprod.yml run --rm --no-deps web \ +./scripts/deploy-preprod.sh +``` + +Das Script verlangt Branch `main` und einen sauberen Working Tree, führt `git pull --ff-only origin main` aus und benutzt durchgehend `sudo docker compose -f compose.yml -f compose.preprod.yml`. Es prüft die Konfiguration still, baut nur das Web-Image und führt den erwarteten Service-Account-Preflight in einem temporären Container aus. Erst bei Exit-Code 0 und `PASS` aktualisiert es mit `up -d --no-deps web`; danach prüft es den laufenden Container. Zum Abschluss zeigt es Compose-Status, höchstens 100 Web-Logzeilen der letzten zwei Minuten und den deployten Commit. Build- oder Preflight-Fehler vor `up` lassen den laufenden Webcontainer unverändert. Das Script führt kein `down`, keine Volume-Operation, keinen erzwungenen Recreate und keine DB-Aktualisierung aus. + +#### Manueller Fallback / Troubleshooting + +Die einzelnen Schritte bleiben für Diagnose oder Script-Ausfall verfügbar. In allen Befehlen dieselben gegebenenfalls vorhandenen Server-Overrides ergänzen: + +```bash +git status --short +git pull --ff-only origin main +sudo docker compose -f compose.yml -f compose.preprod.yml config --quiet +sudo docker compose -f compose.yml -f compose.preprod.yml build web +sudo docker compose -f compose.yml -f compose.preprod.yml run --rm --no-deps web \ python push_preflight.py \ --expected-service-account metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com # Nur bei PASS fortsetzen. Das bestehende db-Service muss bereits laufen. -docker compose -f compose.yml -f compose.preprod.yml up -d --no-deps web -docker compose -f compose.yml -f compose.preprod.yml exec -T web \ +sudo docker compose -f compose.yml -f compose.preprod.yml up -d --no-deps web +sudo docker compose -f compose.yml -f compose.preprod.yml exec -T web \ python push_preflight.py \ --expected-service-account metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com -docker compose -f compose.yml -f compose.preprod.yml ps -docker compose -f compose.yml -f compose.preprod.yml logs --since=5m --tail=100 web +sudo docker compose -f compose.yml -f compose.preprod.yml ps +sudo docker compose -f compose.yml -f compose.preprod.yml logs --since=2m --tail=100 --no-color web +git log -1 --format='%h %s' ``` Zusätzliche bisher verwendete Server-Overrides bei diesen Befehlen beibehalten und ihre endgültigen Werte kontrollieren. Keine neue DB anlegen, kein `down -v`, kein Entwicklungs-Reload-Mount. `config --quiet` prüft ohne Ausgabe der interpolierten Secrets; vollständige `compose config`-/`docker inspect`-Ausgaben nicht teilen. Der normale FastAPI-Start führt die vorhandene Schema-Prüfung aus; diese Push-Konfiguration benötigt keine neue Migration. diff --git a/scripts/deploy-preprod.sh b/scripts/deploy-preprod.sh new file mode 100755 index 0000000..642c1a0 --- /dev/null +++ b/scripts/deploy-preprod.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +trap 'status=$?; printf "FEHLER: Deployment in Zeile %s abgebrochen (Exit %s).\n" "$LINENO" "$status" >&2' ERR + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd -P)" +cd -- "$REPO_ROOT" + +if ! TOP_LEVEL="$(git rev-parse --show-toplevel 2>/dev/null)" || [[ "$(cd -- "$TOP_LEVEL" && pwd -P)" != "$REPO_ROOT" ]]; then + printf 'FEHLER: %s liegt nicht in einem Git-Repository-Checkout.\n' "$REPO_ROOT" >&2 + exit 1 +fi + +BRANCH="$(git branch --show-current)" +if [[ "$BRANCH" != "main" ]]; then + printf 'FEHLER: Deployment ist nur von Branch main erlaubt (aktuell: %s).\n' "${BRANCH:-unbekannt}" >&2 + exit 1 +fi + +if [[ -n "$(git status --porcelain --untracked-files=all)" ]]; then + printf 'FEHLER: Working Tree ist nicht sauber. Änderungen zuerst committen oder entfernen.\n' >&2 + exit 1 +fi + +printf 'Aktualisiere main mit Fast-Forward ...\n' +git pull --ff-only origin main + +compose() { + sudo docker compose -f compose.yml -f compose.preprod.yml "$@" +} + +run_preflight() { + local output + output="$("$@" python push_preflight.py \ + --expected-service-account metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com)" + printf '%s\n' "$output" + if [[ "$output" != PASS:* ]]; then + printf 'FEHLER: Push-Preflight hat kein PASS geliefert.\n' >&2 + return 1 + fi +} + +printf 'Prüfe Pre-Production-Compose-Konfiguration ...\n' +compose config --quiet + +printf 'Baue Web-Image ...\n' +compose build web + +printf 'Prüfe Firebase-Credential im temporären Container ...\n' +run_preflight compose run --rm --no-deps web + +printf 'Aktualisiere ausschließlich den Webcontainer ...\n' +compose up -d --no-deps web + +printf 'Prüfe Firebase-Credential im laufenden Webcontainer ...\n' +run_preflight compose exec -T web + +printf '\nCompose-Status:\n' +compose ps + +printf '\nWeb-Logs der letzten 2 Minuten (maximal 100 Zeilen):\n' +compose logs --since=2m --tail=100 --no-color web + +printf '\nDeployter Git-Commit:\n' +git log -1 --format='%h %s'