178 lines
13 KiB
Markdown
178 lines
13 KiB
Markdown
# 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 <Unterbefehl>
|
|
```
|
|
|
|
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).
|