Harden pre-production deployment with external environment and preflights
This commit is contained in:
+17
-45
@@ -46,7 +46,7 @@ Automatisierte Backend-Tests simulieren Firebase und belegen nicht die Cloud-IAM
|
||||
|
||||
### Bestand und Grenzen
|
||||
|
||||
Im Repository gibt es `compose.yml` für Web/DB, die optionale Secret-Einbindung `compose.push.yml` und eine lokale, ignorierte `compose.dev.yml` mit Reload/Quellcode-Mount. Die vom Betreiber bestätigte aktuelle Pre-Production-Adresse ist **https://konzerte.pinguholic.de/**; das Repository bleibt **`kai/pingu-concerts`**. Der absolute Server-Checkout-Pfad und zusätzliche Reverse-Proxy-/Compose-Overrides sind nicht dokumentiert. Die folgenden Befehle werden **vom Betreiber im bestehenden Pre-Production-Checkout** ausgeführt. Den vorhandenen Compose-Projektnamen und gegebenenfalls serverseitige Overrides beibehalten, damit dieselbe Pre-Production-Datenbank und dieselben Upload-Volumes verwendet werden. Keine zweite Installation mit denselben festen Containernamen auf demselben Docker-Host starten.
|
||||
Im Repository gibt es `compose.yml` für Web/DB, die optionale Secret-Einbindung `compose.push.yml` und eine lokale, ignorierte `compose.dev.yml` mit Reload/Quellcode-Mount. Die vom Betreiber bestätigte aktuelle Pre-Production-Adresse ist **https://konzerte.pinguholic.de/**; das Repository bleibt **`kai/pingu-concerts`**. Der Server-Checkout liegt unter `/opt/pingu-concerts`; die aktive Konfiguration liegt außerhalb unter `/home/kai/.config/metalcircle/preprod.env`. Die folgenden Befehle werden **vom Betreiber im bestehenden Pre-Production-Checkout** ausgeführt. Den vorhandenen Compose-Projektnamen und gegebenenfalls serverseitige Overrides beibehalten, damit dieselbe Pre-Production-Datenbank und dieselben Upload-Volumes verwendet werden. Keine zweite Installation mit denselben festen Containernamen auf demselben Docker-Host starten.
|
||||
|
||||
Neu ist `compose.preprod.yml`: Es übernimmt per `extends` die Einbindung aus `compose.push.yml`, aktiviert Push und erzwingt sichere Cookies. Start mit `compose.yml` + `compose.preprod.yml`; die dritte Datei muss nicht zusätzlich angegeben werden. Pre-Production benötigt HTTPS am vorhandenen Reverse Proxy. Die Basisdatei behält `PUSH_ENABLED=false` als Standard; lokale Konfiguration und spätere Produktion werden nicht automatisch aktiviert.
|
||||
|
||||
@@ -61,66 +61,36 @@ Der lokale Account `metalcircle-push-local@metalcircle-30d9b.iam.gserviceaccount
|
||||
|
||||
### A. Einmalig auf dem Pre-Production-Host
|
||||
|
||||
Es ist kein bestehendes Secret-Verzeichnis dokumentiert. **Vorschlag**, falls der Betreiber noch keine Konvention hat: `$HOME/.secrets/metalcircle/firebase-push-preprod.json` im Home-Verzeichnis des Deployment-Benutzers, außerhalb von Checkout und Docker-Buildkontext.
|
||||
Verbindlicher Host-Pfad: `/home/kai/.secrets/metalcircle/firebase-push-preprod.json`. Der Key gehört dem Deployment-Benutzer kai und hat Modus 600, das Verzeichnis Modus 700. Der bestehende read-only Mount bleibt `/run/secrets/firebase-service-account.json`; `create_host_path: false` verhindert Verzeichnisse anstelle fehlender Dateien. Bei Rootless-Docker oder abweichender Container-UID muss der Betreiber die UID-Abbildung prüfen; nicht auf weltweite Leserechte ausweichen.
|
||||
|
||||
```bash
|
||||
install -d -m 700 "$HOME/.secrets/metalcircle"
|
||||
# Hier ausschließlich den NEUEN Pre-Production-Schlüssel sicher ablegen.
|
||||
chmod 600 "$HOME/.secrets/metalcircle/firebase-push-preprod.json"
|
||||
```
|
||||
|
||||
Die Datei muss dem Deployment-Benutzer gehören. Falls die sichere Übertragung einen anderen Eigentümer gesetzt hat, korrigiert der Betreiber ihn, z. B. mit `sudo chown "$(id -u):$(id -g)" "$HOME/.secrets/metalcircle/firebase-push-preprod.json"`. Das bestehende Dockerfile läuft als Container-root; bei Rootless-Docker oder zusätzlichen `user:`-Overrides muss die UID-Abbildung berücksichtigt werden. Nicht auf `chmod 644` ausweichen.
|
||||
|
||||
In der **bestehenden, nicht versionierten Server-`.env`** nur diese Werte ergänzen; DB-/Admin-/Gitea-Konfiguration beibehalten:
|
||||
Die gesamte aktive Konfiguration liegt in `/home/kai/.config/metalcircle/preprod.env` (Eigentümer kai, Modus 600). Vorlage: `config/preprod.env.example`. Neben vollständigen DB-/Admin-/Gitea-Werten benötigt Firebase:
|
||||
|
||||
```dotenv
|
||||
PUSH_ENABLED=true
|
||||
FIREBASE_PROJECT_ID=metalcircle-30d9b
|
||||
FIREBASE_SERVICE_ACCOUNT_FILE=/absoluter/hostpfad/zur/firebase-push-preprod.json
|
||||
FIREBASE_SERVICE_ACCOUNT_FILE=/home/kai/.secrets/metalcircle/firebase-push-preprod.json
|
||||
COOKIE_SECURE=true
|
||||
```
|
||||
|
||||
Den Beispielpfad durch den tatsächlichen absoluten Pfad ersetzen, kein `~` in `.env`. `GOOGLE_APPLICATION_CREDENTIALS` wird durch Compose auf `/run/secrets/firebase-service-account.json` gesetzt. Der Bind-Mount ist `read_only: true`; `create_host_path: false` verhindert, dass eine fehlende Schlüsseldatei unbemerkt als Verzeichnis angelegt wird. Ein Host-Pfad ist kein Schlüsselinhalt und darf dokumentiert werden; JSON-Inhalt, private Schlüssel, OAuth- und FCM-Tokens niemals.
|
||||
`GOOGLE_APPLICATION_CREDENTIALS` setzt Compose intern. Kein `~` als Pfad und keine Credential-Inhalte in diese Anleitung übernehmen. Die alte Checkout-`.env` wird vom Deployment nicht mehr gelesen und nicht automatisch gelöscht. Die vollständige [Migrationsanleitung](Deployment.md#migration-der-bisherigen-checkout-env) beschreibt Übernahme, Prüfung, Deployment und das spätere manuelle Archivieren.
|
||||
|
||||
### B. Deployment im bestehenden Pre-Production-Checkout
|
||||
|
||||
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:
|
||||
### B. Deployment im Pre-Production-Checkout
|
||||
|
||||
```bash
|
||||
cd /opt/pingu-concerts
|
||||
./scripts/deploy-preprod.sh --check
|
||||
./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.
|
||||
Das Script verlangt einen sauberen `main`-Checkout und verwendet durchgehend `--env-file /home/kai/.config/metalcircle/preprod.env` mit `compose.yml` + `compose.preprod.yml`. Pflichtwerte, Host-Dateirechte und externer Firebase-Key werden vor dem Build geprüft. Vor dem Containerwechsel müssen anschließend **Firebase und Gitea** in temporären Containern PASS liefern. Danach wird die Env-Datei außerhalb des Checkouts gesichert (700/600, letzte zehn Kopien) und nur `web` mit `up -d --no-deps web` aktualisiert. Beide Preflights laufen danach erneut im tatsächlichen Webcontainer. [Ablauf, Abbruchverhalten und manueller Fallback](Deployment.md).
|
||||
|
||||
#### Manueller Fallback / Troubleshooting
|
||||
Der bestehende Firebase-Preflight bleibt offline: Push aktiv, Containerpfad, reguläre Datei, restriktive Rechte, read-only Mount, gültiges Credential, Projekt und exakter Pre-Production-Service-Account. PASS belegt **kein** IAM und keine FCM-Zustellung. Gitea wird zusätzlich read-only als `metalcircle-bot` auf Erreichbarkeit geprüft; keine Test-Issues oder Attachments werden erstellt.
|
||||
|
||||
Die einzelnen Schritte bleiben für Diagnose oder Script-Ausfall verfügbar. In allen Befehlen dieselben gegebenenfalls vorhandenen Server-Overrides ergänzen:
|
||||
Vor Aktivierung bestätigen: Die vorhandene DB ist die Pre-Production-DB, keine lokalen Daten/Sessions wurden importiert. Frisch in Pre-Production anmelden und das Android-Gerät dort registrieren. Keine Local-Datenbank kopieren.
|
||||
|
||||
```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.
|
||||
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
|
||||
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'
|
||||
```
|
||||
Eine bewusste **Notabschaltung** des Pushversands erfolgt außerhalb des normalen Deployment-Scripts: In der externen `preprod.env` `PUSH_ENABLED=false` setzen und den Webcontainer ausdrücklich mit `--env-file /home/kai/.config/metalcircle/preprod.env`, `compose.yml` + `compose.push.yml` aktualisieren. Das reguläre Pre-Production-Override erzwingt Push und ist für diese Ausnahme wegzulassen; `COOKIE_SECURE=true` in der externen Env beibehalten. Kein `down`, kein DB-Recreate. Für den nächsten normalen Deployment-Lauf Push wieder aktivieren und beide Preflights bestehen lassen.
|
||||
|
||||
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.
|
||||
|
||||
`push_preflight.py` prüft nur lokal: Push aktiv, Pfad, reguläre Datei, restriktive Rechte, read-only Mount, syntaktisch lesbares Credential, Projekt und exakte Service-Account-Adresse. Damit fällt auch ein versehentlich eingesetzter Local-Key auf. Es gibt keine Netzwerkanfrage, keinen Push und keinen IAM-Nachweis. PASS ersetzt den echten Smoke-Test nicht.
|
||||
|
||||
Zum Deaktivieren in der Server-`.env` `PUSH_ENABLED=false` setzen und den Webcontainer **mit `compose.yml` + `compose.push.yml`** neu erstellen. Das Pre-Production-Override setzt explizit `true` und muss für diese Abschaltung entfallen. Der Mount kann bleiben. Bei erneutem Aktivieren können noch nicht abgelaufene Aufträge verarbeitet werden; während der Abschaltung entstehen keine neuen. Schlüsselrotation: sichere neue Datei am Host ersetzen und Webcontainer neu erstellen, damit Bind-Mount und gecachtes SDK-Credential erneuert werden.
|
||||
Schlüsselrotation: neue Pre-Production-Datei geschützt am Host ersetzen und den Webcontainer über den normalen Deployment-Weg neu erstellen, damit Mount und SDK-Credential erneuert werden. Während eines laufenden Deployments Env/Key nicht bearbeiten.
|
||||
|
||||
### Android mit Pre-Production verbinden
|
||||
|
||||
@@ -150,7 +120,7 @@ Zwei **Pre-Production-Testkonten** A/B verwenden. B ist auf dem Android-Testger
|
||||
Für die Backend-Prüfung können selektive SQL-Abfragen benutzt werden (IDs/Zeitpunkt eingrenzen; kein `SELECT *`):
|
||||
|
||||
```bash
|
||||
docker compose -f compose.yml -f compose.preprod.yml exec -T db \
|
||||
sudo docker compose --env-file /home/kai/.config/metalcircle/preprod.env -f compose.yml -f compose.preprod.yml exec -T db \
|
||||
sh -c 'exec psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"' <<'SQL'
|
||||
SELECT user_id, platform, app_version, created_at, last_seen_at FROM push_devices ORDER BY last_seen_at DESC LIMIT 10;
|
||||
SELECT user_id, language, friend_request, direct_message, event_invitation FROM notification_preferences ORDER BY user_id;
|
||||
@@ -198,6 +168,8 @@ Alle drei Tests zusätzlich mit B in EN wiederholen; ursprüngliche Sprache/Prä
|
||||
|
||||
### Lokal geprüfte Pre-Production-Vorbereitung
|
||||
|
||||
Die Repository-Vorbereitung wurde mit 64 erfolgreichen Backend-Tests (isolierte lokale PostgreSQL-Schemas, simuliertes Firebase) und 11 JavaScript-Tests für Push-Registrierung/-Navigation und Android-Zurück-Verhalten geprüft. Neue Prüfungen decken insbesondere falsche Service Accounts, Projekt-/Mount-/Dateirechte, relative authentifizierte Registrierungs-URLs, die gemeinsamen Payload-Eigenschaften aller drei Push-Arten und den Lesestatus von Direktnachrichten ab. Ein veralteter Sprach-Test wurde an das bereits bestehende DE/EN-Dropdown angepasst; keine Oberflächenänderung.
|
||||
Die ursprüngliche Push-Vorbereitung wurde mit 64 erfolgreichen Backend-Tests (isolierte lokale PostgreSQL-Schemas, simuliertes Firebase) und 11 JavaScript-Tests für Push-Registrierung/-Navigation und Android-Zurück-Verhalten geprüft. Diese Prüfungen decken insbesondere falsche Service Accounts, Projekt-/Mount-/Dateirechte, relative authentifizierte Registrierungs-URLs, die gemeinsamen Payload-Eigenschaften aller drei Push-Arten und den Lesestatus von Direktnachrichten ab.
|
||||
|
||||
Die anschließende Absicherung des Deployments mit externer Environment-Datei wurde mit inzwischen **84 Backend-Tests, 22 Host-/Deployment-Tests und 11 JavaScript-Tests** geprüft. Dazu gehören fehlende Gitea-Werte, Bot-Identität, API-Fehler, Rechte, fehlende Dateien, sichere Backup-Rotation und Abbruch vor dem Containerwechsel. Die Compose-Interpolation wurde mit synthetischen Werten auch gegen den echten lokalen Compose-Parser geprüft. Keine Pre-Production-Secrets und kein Serverdeployment waren dafür nötig.
|
||||
|
||||
Compose-Konfigurationen für Basis, Local-Push und Pre-Production wurden mit synthetischen Konfigurationswerten geprüft, einschließlich Abbruch bei fehlendem Projekt/Secret-Pfad. Das Backend-Image wurde lokal gebaut und auf Credential-Dateien/Schlüssel geprüft; der einzige Schlüssel-Marker war ein kurzer synthetischer Text im bestehenden Gitea-Redaktionstest. `.env`, Android-Firebase-Konfiguration und die vorgesehenen Schlüsseldateinamen sind Git-ignoriert. Diese Prüfungen verwenden keinen Pre-Production-Key und belegen weder Server-IAM noch Zustellung auf dem Pre-Production-Gerät. Native Android-Dateien wurden nicht geändert; ein neuer APK-Build war dafür nicht erforderlich.
|
||||
|
||||
Reference in New Issue
Block a user