Harden pre-production deployment with external environment and preflights
This commit is contained in:
@@ -5,9 +5,9 @@
|
||||
| `POSTGRES_DB` | Name der PostgreSQL-Datenbank | erforderlich |
|
||||
| `POSTGRES_USER` | Datenbankbenutzer | erforderlich |
|
||||
| `POSTGRES_PASSWORD` | Datenbankpasswort | erforderlich, geheim |
|
||||
| `INITIAL_ADMIN_USERNAME` | Erstes Admin-Konto | lokal erforderlich |
|
||||
| `INITIAL_ADMIN_PASSWORD` | Passwort des Erstadmins | lokal erforderlich, geheim |
|
||||
| `INITIAL_ADMIN_EMAIL` | E-Mail des Erstadmins | lokal erforderlich |
|
||||
| `INITIAL_ADMIN_USERNAME` | Erstes Admin-Konto | lokal und Pre-Production erforderlich |
|
||||
| `INITIAL_ADMIN_PASSWORD` | Passwort des Erstadmins | lokal und Pre-Production erforderlich, geheim |
|
||||
| `INITIAL_ADMIN_EMAIL` | E-Mail des Erstadmins | lokal und Pre-Production erforderlich |
|
||||
| `COOKIE_SECURE` | Secure-Flag der Session-Cookies | Produktion `true` |
|
||||
| `GITEA_URL` | interne Gitea-Basisadresse | für Bugreporter erforderlich |
|
||||
| `GITEA_TOKEN` | Token des `metalcircle-bot` | erforderlich, geheim |
|
||||
@@ -24,4 +24,6 @@
|
||||
|
||||
`.env.example` enthält nur Platzhalter. `.env` wird nie committed. `google-services.json` liegt ausschließlich lokal im Android-App-Modul und wird durch `.gitignore` ausgeschlossen.
|
||||
|
||||
Pre-Production verwendet ausschließlich `/home/kai/.config/metalcircle/preprod.env` über `--env-file`; die Checkout-`.env` ist dafür keine Konfigurationsquelle mehr. Vorlage: `config/preprod.env.example`. Alle PostgreSQL-/Initial-Admin-/Gitea-Werte sowie `COOKIE_SECURE`, `PUSH_ENABLED`, `FIREBASE_PROJECT_ID` und `FIREBASE_SERVICE_ACCOUNT_FILE` sind dort verpflichtend. Env und Firebase-Key: Eigentümer Deployment-Benutzer, Modus 600 (oder 400), außerhalb des Checkouts. Die automatische Env-Sicherung hält zehn Kopien in einem privaten externen Verzeichnis. Details und Migration: [Deployment](Deployment.md).
|
||||
|
||||
`compose.preprod.yml` verwendet dieselben Push-Variablen und denselben Credential-Mount, setzt `PUSH_ENABLED=true` sowie `COOKIE_SECURE=true` und verlangt eine Projekt-ID. Der Host-Pfad muss zum eigenen Pre-Production-Key zeigen. Siehe [Pre-Production Deployment](Firebase.md#pre-production-deployment), einschließlich Offline-Prüfung gegen den erwarteten Service Account und Abschaltung ohne das aktivierende Override. Die Android-Variablen sind Build-/Sync-Einstellungen und werden nicht aus der Backend-`.env` an die APK übertragen.
|
||||
|
||||
+170
-8
@@ -1,15 +1,177 @@
|
||||
# Deployment
|
||||
|
||||
Es gibt drei getrennte Umgebungen:
|
||||
MetalCircle hat getrennte Umgebungen: lokale Entwicklung auf PinguCore, Pre-Production unter **https://konzerte.pinguholic.de/** und spätere Produktion. Codex bereitet Änderungen lokal vor; Serverzugriff und Deployment erfolgen durch den Betreiber. Keine lokalen Datenbanken oder Credentials auf Pre-Production übernehmen.
|
||||
|
||||
1. Lokale Entwicklung auf PinguCore/Codex mit Docker Compose und Testdaten.
|
||||
2. Aktuelle Pre-Production / Cloud-Staging unter **https://konzerte.pinguholic.de/** für gemeinsame Integrationstests.
|
||||
3. Produktion.
|
||||
## Pre-Production: verbindliche Pfade
|
||||
|
||||
Lokale Änderungen werden in Git geprüft und gepusht. Der Cloud-Testserver zieht den Stand anschließend eigenständig; Codex soll ihn nicht automatisch anmelden, verändern oder deployen. Produktion wird durch diese Dokumentation nicht verändert. Zugangsdaten und konkrete produktive Adressen gehören nicht ins Repository.
|
||||
| Zweck | Pfad |
|
||||
|---|---|
|
||||
| Git-Checkout | `/opt/pingu-concerts` |
|
||||
| Aktive Environment-Datei | `/home/kai/.config/metalcircle/preprod.env` |
|
||||
| Lokale Env-Sicherungen | `/home/kai/.config/metalcircle/backups/` |
|
||||
| Firebase-Key auf dem Host | `/home/kai/.secrets/metalcircle/firebase-push-preprod.json` |
|
||||
| Read-only Mount im Webcontainer | `/run/secrets/firebase-service-account.json` |
|
||||
|
||||
## Pre-Production Push
|
||||
Der Checkout benötigt keine aktive `.env` mehr. `scripts/deploy-preprod.sh` definiert den Environment-Pfad zentral und führt über den stdlib-Helfer `scripts/preprod_config.py` diese Compose-Kombination aus:
|
||||
|
||||
Die vorbereitete Kombination `compose.yml` + `compose.preprod.yml` übernimmt den read-only Firebase-Mount aus `compose.push.yml`, aktiviert Push und erzwingt HTTPS-Cookies. Ein eigener Pre-Production-Service-Account ist erforderlich. Basis-/Local-Konfiguration wird damit nicht automatisch umgestellt. Die HTTPS-Domain wurde vom Betreiber bestätigt. Die Befehle werden im bestehenden Server-Checkout von `kai/pingu-concerts` ausgeführt; dessen absoluter Pfad und gegebenenfalls zusätzliche serverseitige Overrides sind im Repository nicht hinterlegt.
|
||||
```bash
|
||||
sudo docker compose \
|
||||
--env-file /home/kai/.config/metalcircle/preprod.env \
|
||||
-f compose.yml -f compose.preprod.yml <Unterbefehl>
|
||||
```
|
||||
|
||||
Der vollständige Ablauf mit IAM, Secret-Rechten, Offline-Preflight, Android-Ziel-URL, Aktivierung/Abschaltung und drei Gerätetests steht unter [Firebase → Pre-Production Deployment](Firebase.md#pre-production-deployment). Bestehenden Compose-Projektnamen und Daten-Volumes erhalten; keine Local-Datenbank auf den Server kopieren.
|
||||
Voraussetzungen: Bash, Python 3.10+, Git, Docker Compose v2 mit `config --environment` und sudo-Berechtigung für lokales Docker. Als Deployment-Benutzer **kai**, nicht das gesamte Script mit sudo starten. Auf dem Host sind keine zusätzlichen Python-Pakete nötig. Compose-Projekt `pingu-concerts` und vorhandene Daten-/Upload-Volumes beibehalten.
|
||||
|
||||
## Konfiguration und Rechte
|
||||
|
||||
[`config/preprod.env.example`](../../config/preprod.env.example) ist die Vorlage ohne Secrets. Pflichtvariablen:
|
||||
|
||||
- `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`
|
||||
- `INITIAL_ADMIN_USERNAME`, `INITIAL_ADMIN_PASSWORD`, `INITIAL_ADMIN_EMAIL`
|
||||
- `GITEA_URL`, `GITEA_TOKEN`, `GITEA_OWNER`, `GITEA_REPO`
|
||||
- `PUSH_ENABLED`, `FIREBASE_PROJECT_ID`, `FIREBASE_SERVICE_ACCOUNT_FILE`, `COOKIE_SECURE`
|
||||
|
||||
Die Bootstrap-Adminwerte bleiben auch bei bestehender Datenbank Teil des vollständigen Deployment-Vertrags. DB-/Adminwerte aus der bestehenden Installation übernehmen: Ein geändertes `POSTGRES_PASSWORD` in der Datei ändert **nicht** das Passwort der bereits initialisierten PostgreSQL-Rolle.
|
||||
|
||||
Für normale Pre-Production-Deployments müssen `PUSH_ENABLED=true`, `COOKIE_SECURE=true` und `FIREBASE_PROJECT_ID=metalcircle-30d9b` gesetzt sein. `GOOGLE_APPLICATION_CREDENTIALS` setzt Compose intern. `ALPHA_TESTER_UNTIL` und `BETA_TESTER_UNTIL` bleiben optional.
|
||||
|
||||
Env-Datei und Firebase-Key müssen reguläre Dateien außerhalb des Checkouts sein, dem Deployment-Benutzer gehören und Modus **600** (alternativ 400) besitzen. Symlinks und Gruppen-/Weltzugriff werden abgelehnt. Verzeichnisse unter `.config/metalcircle` und `.secrets/metalcircle` mit **700** anlegen. Keine Keys in Buildkontext oder Image ablegen.
|
||||
|
||||
Compose interpretiert die dotenv-Syntax selbst. Einzeilige Passwörter/Tokens mit `$` oder `#` in einfache Anführungszeichen setzen; die Datei niemals mit `source` ausführen. Der Helfer entfernt geerbte Shell-Variablen, die sonst laut [Compose-Priorität](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/) die Env-Datei übersteuern könnten. Fehlende Variablen werden weder durch die alte Checkout-`.env` noch durch Shell-Exports ergänzt. Keine `COMPOSE_*`-/`DOCKER_*`-Overrides in `preprod.env` eintragen; ein abweichendes Compose-Projekt wird abgelehnt.
|
||||
|
||||
## Migration der bisherigen Checkout-.env
|
||||
|
||||
Diese Schritte führt der Betreiber einmalig auf Pre-Production aus. Das Script löscht oder verändert `/opt/pingu-concerts/.env` nicht.
|
||||
|
||||
1. Nach Bereitstellung der Änderung den Checkout aktualisieren. Bestehende Konfiguration geschützt aufbewahren und auf Vollständigkeit prüfen.
|
||||
2. Geschützte Verzeichnisse anlegen:
|
||||
|
||||
```bash
|
||||
install -d -m 700 /home/kai/.config/metalcircle /home/kai/.secrets/metalcircle
|
||||
```
|
||||
|
||||
3. Eine bestehende Zieldatei nicht überschreiben. Die bisherige `.env` kann als Ausgangspunkt dienen:
|
||||
|
||||
```bash
|
||||
test ! -e /home/kai/.config/metalcircle/preprod.env && \
|
||||
install -m 600 /opt/pingu-concerts/.env /home/kai/.config/metalcircle/preprod.env
|
||||
```
|
||||
|
||||
Alternativ bei fehlender/ungeeigneter Altdatei die Vorlage verwenden, ebenfalls nur bei noch nicht vorhandenem Ziel:
|
||||
|
||||
```bash
|
||||
test ! -e /home/kai/.config/metalcircle/preprod.env && \
|
||||
install -m 600 /opt/pingu-concerts/config/preprod.env.example /home/kai/.config/metalcircle/preprod.env
|
||||
```
|
||||
|
||||
Die aktiven DB-/Adminwerte und den dedizierten `metalcircle-bot`-Token im Editor ergänzen. Alle Variablennamen mit der Vorlage abgleichen. Keine Secret-Inhalte in Terminalausgabe, Tickets oder Chats kopieren.
|
||||
4. Den eigenen **Pre-Production-Key** am oben angegebenen Host-Pfad bereitstellen, Eigentümer kai und Modus 600 prüfen. `FIREBASE_SERVICE_ACCOUNT_FILE` muss diesen absoluten Pfad enthalten. Local-Key nicht wiederverwenden. [Firebase-Einrichtung und IAM](Firebase.md#pre-production-deployment).
|
||||
5. Nur die Prüfungen ausführen; dies baut das Web-Image, ersetzt aber keinen laufenden Container:
|
||||
|
||||
```bash
|
||||
cd /opt/pingu-concerts
|
||||
./scripts/deploy-preprod.sh --check
|
||||
```
|
||||
|
||||
6. Bei PASS normal deployen, danach Login, einen Bugreport mit optionalem Screenshot und die drei Push-Typen prüfen:
|
||||
|
||||
```bash
|
||||
./scripts/deploy-preprod.sh
|
||||
```
|
||||
|
||||
7. Erst nach erfolgreichem Anwendungstest die alte `/opt/pingu-concerts/.env` **manuell** geschützt außerhalb des Checkouts archivieren oder entfernen. Editor-Swap-/Backupdateien können ebenfalls Secrets enthalten. Der neue Ablauf ist bereits vor diesem Aufräumen unabhängig von der Altdatei.
|
||||
|
||||
## Normaler Ablauf und Abbruchbedingungen
|
||||
|
||||
`./scripts/deploy-preprod.sh`:
|
||||
|
||||
1. Verlangt Repository-Kontext, Branch `main` und sauberen Working Tree; führt `git pull --ff-only origin main` aus.
|
||||
2. Prüft Env-Datei, Eigentümer, Rechte und Pflichtwerte sowie den externen Firebase-Key. Ausgabe enthält nur Variablennamen und `set` bzw. den fehlenden Namen. Compose-Parsing erfolgt intern, ohne Werte oder rohe Fehlermeldungen auszugeben.
|
||||
3. Führt `config --quiet` aus und baut ausschließlich `web`.
|
||||
4. Startet zwei kurzlebige Container mit `run --rm --no-deps -T web`: zuerst `push_preflight.py`, dann `gitea_preflight.py`. Beide müssen Exit-Code 0 **und** `PASS` liefern. Diese Programme starten weder FastAPI noch den Push-Worker und führen keine Migration aus.
|
||||
5. Prüft, ob Env-Datei oder Key seit Beginn der Prüfung verändert wurden, und bricht gegebenenfalls ab. Während eines Deployments diese Dateien nicht bearbeiten.
|
||||
6. Sichert die geprüfte Env-Datei. Erst dann folgt `up -d --no-deps web`.
|
||||
7. Wiederholt beide Preflights per `exec -T web` im laufenden Container und zeigt Containerstatus, gefilterte Web-Logs (letzte zwei Minuten, höchstens 100 Eingabezeilen) und den deployten Commit.
|
||||
|
||||
Beispiel vor dem Containerwechsel:
|
||||
|
||||
```text
|
||||
ERROR: required variable GITEA_TOKEN is missing
|
||||
Deployment aborted. Running container unchanged.
|
||||
```
|
||||
|
||||
Fehler bei Konfiguration, Build, Vorprüfungen oder Backup ersetzen keinen laufenden Container. Scheitert eine Prüfung **nach** `up`, meldet das Script ausdrücklich den bereits erfolgten Update-Versuch. Kein automatischer Rollback, kein `down`, kein Volume-Löschen, kein DB-Recreate und kein `--force-recreate`. Beim regulären FastAPI-Start läuft weiterhin die bestehende Schema-Initialisierung; diese Änderung führt keine neue Migration ein.
|
||||
|
||||
`--check` überspringt Pull, Env-Backup und Containerwechsel. Der vorhandene Checkout wird geprüft und das Image gebaut. Die lokale Vorbereitung ersetzt keine Pre-Production-Smoke-Tests.
|
||||
|
||||
## Was die Container-Preflights prüfen
|
||||
|
||||
**Firebase:** Der bestehende Offline-Preflight prüft Aktivierung, exakten Containerpfad, reguläre Datei, restriktive Rechte, read-only Mount, lesbares Credential, Projekt und exakt `metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com`. Falsche Local-Credentials werden abgelehnt. IAM, OAuth und Zustellung werden dabei nicht über das Netzwerk getestet. Die erforderliche Rolle bleibt **`roles/firebasecloudmessaging.admin`**.
|
||||
|
||||
**Gitea:** `gitea_preflight.py` validiert die vier vorhandenen Variablen, URL und Repository-Namen. Danach ausschließlich:
|
||||
|
||||
- `GET /api/v1/user`: verlangt `login == "metalcircle-bot"`.
|
||||
- `GET /api/v1/repos/{owner}/{repo}/issues?limit=1&page=1`: prüft Erreichbarkeit des Repository-Issue-Bereichs mit den bereits benötigten Token-Scopes `read:user` und `write:issue`. Kein zusätzliches `read:repository` nötig. Siehe [Gitea-API-Zugang](https://docs.gitea.com/1.26/development/api-usage).
|
||||
|
||||
Es werden weder Issues noch Anhänge angelegt. Response-Inhalte und Tokens werden nicht geloggt. Ein read-only PASS bestätigt keinen Schreib-/Attachment-Vorgang; ein vom Betreiber ausgelöster Bugreport gehört deshalb zum Smoke-Test. Timeouts, Authentifizierungsfehler, fehlende Rechte und ungültige Antworten stoppen das Deployment mit festen Fehlerkategorien.
|
||||
|
||||
## Env-Backups
|
||||
|
||||
Nach bestandenen Vorprüfungen entsteht unter `/home/kai/.config/metalcircle/backups/` eine Kopie `preprod.env.YYYYMMDD-HHMMSS-microseconds` (UTC). Verzeichnis **700**, Dateien **600**, nur die letzten **10** Sicherungen dieses Namensschemas werden behalten. Fremde Dateien werden nicht gelöscht. Gesichert wird ausschließlich die Env-Datei, nicht der Firebase-Key oder die Datenbank.
|
||||
|
||||
Eine Sicherung bleibt auch bei einem später fehlgeschlagenen Container-Update erhalten. Fehler beim Sichern/Aufräumen brechen vor dem Update ab. Diese Dateien enthalten Secrets und gehören niemals ins Git. Bei Wiederherstellung im Editor kontrollieren, geschützt nach `preprod.env` kopieren, `--check` ausführen und normal deployen. Die Sicherungen sind keine unabhängige Offsite-Backup-Strategie.
|
||||
|
||||
## Manueller Fallback / Troubleshooting
|
||||
|
||||
Die einzelnen Compose-Schritte bleiben verfügbar. Diese Hilfsfunktion verwendet dieselbe explizite Env-Datei, bereinigte Shell-Umgebung und sichere Ausgabe wie das Script. Intern wird jeweils der oben gezeigte `sudo docker compose --env-file ... -f compose.yml -f compose.preprod.yml`-Befehl ausgeführt:
|
||||
|
||||
```bash
|
||||
cd /opt/pingu-concerts
|
||||
git status --short
|
||||
# Nur main und einen sauberen Checkout verwenden.
|
||||
git pull --ff-only origin main
|
||||
compose_preprod() {
|
||||
python3 scripts/preprod_config.py \
|
||||
--env-file /home/kai/.config/metalcircle/preprod.env compose "$@"
|
||||
}
|
||||
python3 scripts/preprod_config.py --env-file /home/kai/.config/metalcircle/preprod.env check
|
||||
compose_preprod config --quiet
|
||||
compose_preprod build web
|
||||
compose_preprod run --rm --no-deps -T web python push_preflight.py \
|
||||
--expected-service-account metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com
|
||||
compose_preprod run --rm --no-deps -T web python gitea_preflight.py
|
||||
# Nur nach ALLEN erfolgreichen Prüfungen weiter; bei jedem Fehler abbrechen.
|
||||
python3 scripts/preprod_config.py --env-file /home/kai/.config/metalcircle/preprod.env backup
|
||||
compose_preprod up -d --no-deps web
|
||||
compose_preprod exec -T web python push_preflight.py \
|
||||
--expected-service-account metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com
|
||||
compose_preprod exec -T web python gitea_preflight.py
|
||||
compose_preprod ps --format json
|
||||
compose_preprod logs --since=2m --tail=100 --no-color web
|
||||
git log -1 --format='%h %s'
|
||||
```
|
||||
|
||||
Keine vollständigen `compose config`, `config --environment`, `docker inspect` oder `.env`-Inhalte ausgeben/teilen. Der Helfer erfasst die Compose-Interpolation intern im Speicher; stdout/stderr mit potentiellen Secrets werden nicht durchgereicht. Web-Logs zeigen nur ausgewählte Lebenszyklusmeldungen, HTTP-Methode/Status **ohne URL** und feste Gitea-/Push-Fehlerkategorien. Andere Zeilen werden gezählt und ausgelassen.
|
||||
|
||||
| Meldung | Prüfung durch Betreiber |
|
||||
|---|---|
|
||||
| `required variable ... is missing` | Benannte Variable in der externen Env-Datei ergänzen; alte `.env` hilft nicht |
|
||||
| `requires permissions 600 or 400` | Dateirechte und Eigentümer prüfen |
|
||||
| `gitea_configuration` | Basis-URL ohne Login/Query/Fragment, Owner und Repository prüfen |
|
||||
| `gitea_wrong_account` | Dedizierten `metalcircle-bot`-Token verwenden |
|
||||
| `gitea_http_401` | Token ungültig/widerrufen |
|
||||
| `gitea_http_403` | Bot-Zugriff und `read:user`/`write:issue` prüfen |
|
||||
| `gitea_http_404` | Repository/URL oder Sichtbarkeit für Bot prüfen |
|
||||
| `gitea_http_5xx` / `gitea_network_or_timeout` | Gitea-Verfügbarkeit, WireGuard, DNS/Netz prüfen |
|
||||
| `gitea_invalid_response` | Falsches Ziel/Proxy oder unerwartete API-Antwort prüfen |
|
||||
| Firebase `credential_service_account_mismatch` | Pre-Production-Key statt Local-Key verwenden |
|
||||
| `Compose build failed` | Build/Registry-Erreichbarkeit prüfen; laufender Container wurde nicht ersetzt |
|
||||
|
||||
## Lokale Verifikation
|
||||
|
||||
```bash
|
||||
bash -n scripts/deploy-preprod.sh
|
||||
python3 -m unittest discover -s scripts/tests -v
|
||||
```
|
||||
|
||||
Shell-Ablauftests benutzen temporäre Verzeichnisse und simulierte Git-/Docker-Kommandos, keine echten Deployments. Ein Test verwendet nur den lokalen Compose-Parser mit synthetischer Env-Datei, ohne Docker-Engine/Netz. Die Backend-Suite unter `app/tests` deckt Gitea- und Firebase-Preflights ab; [Testaufruf](Development-Setup.md#tests).
|
||||
|
||||
+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.
|
||||
|
||||
@@ -38,4 +38,4 @@ Ein [fertiger ChatGPT-Prompt](Firebase-Setup-Prompt.md) begleitet die Einrichtun
|
||||
|
||||
## Pre-Production Deployment
|
||||
|
||||
[Firebase → Pre-Production Deployment](Firebase.md#pre-production-deployment) beschreibt die vorbereitete Compose-Konfiguration, den ausschließlich dort verwendeten Service Account, Secret-Mount und Offline-Preflight sowie den Smoke-Test einschließlich Direktnachrichten und sicherer Gerätebeobachtung. Ein erfolgreicher SDK-Aufruf allein ist kein Android-Empfangsnachweis.
|
||||
[Deployment](Deployment.md) beschreibt die externe `preprod.env`, geschützte Env-Backups und die verpflichtenden Firebase-/Gitea-Preflights vor dem Containerwechsel. [Firebase → Pre-Production Deployment](Firebase.md#pre-production-deployment) ergänzt Service Account, Secret-Mount, IAM und die drei Smoke-Tests einschließlich Direktnachrichten und sicherer Gerätebeobachtung. Ein erfolgreicher SDK-Aufruf allein ist kein Android-Empfangsnachweis.
|
||||
|
||||
Reference in New Issue
Block a user