# Deployment 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. ## Pre-Production: verbindliche Pfade | 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` | 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: ```bash sudo docker compose \ --env-file /home/kai/.config/metalcircle/preprod.env \ -f compose.yml -f compose.preprod.yml ``` 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).