b2in/dev/betrieb/README.md
Kevin Adametz b5ed756d67 Git-Deploy per Tag, Betriebsüberwachung und Release-Seeder für den Livegang
- dev/betrieb/deploy.sh: Deploy nur per Tag, mit --seed (Seeder im Wartungsmodus)
  und --initial (Vorschau/Live einmalig an Ort und Stelle auf Git umstellen)
- dev/betrieb/vorschau-auffrischen.sh: Live-DB und Live-Medien in die Vorschau (Generalprobe)
- InteriorReleaseSeeder: alle Inhaltsschritte des Relaunchs in einem Lauf, idempotent;
  Displays, Playlists und Mediathek bleiben unberührt
- DisplayInteriorBrandingSeeder überschreibt keinen inzwischen gepflegten Claim mehr
- Monitoring: GlitchTip (Server + Browser), /health mit Token, Scheduler-Herzschlag,
  Alarme per Mail und ntfy, Version in about und /health
- public/build versioniert; .gitignore: macOS-Muster Icon traf resources/views/flux/icon/
- Runbook: dev/betrieb/README.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:56:23 +00:00

235 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Betrieb B2in: Git-Deploy, Livegang, Monitoring
**Stand:** 08.10.2026. Grundlage ist der Baukasten unter `dev/git-deploy-monitoring/`. Dort steht,
warum etwas so gebaut ist. Hier steht, wie es für B2in läuft.
| Was | Wo |
|---|---|
| Deploy-Skript | `dev/betrieb/deploy.sh`, liefert nur Git-Tags `vX.Y.Z` aus |
| Vorschau auffrischen | `dev/betrieb/vorschau-auffrischen.sh` kopiert Live-DB und Live-Medien in die Vorschau |
| Inhaltsschritte Relaunch | `InteriorReleaseSeeder`, alle 29 Seeder in einem Lauf, idempotent |
| Remote `origin` | lokal `ssh://git@forge.adametz.media:222/kadmin/b2in.git`, auf dem Server `forge-b2in:kadmin/b2in.git` (Host-Alias, Abschnitt 2): Deploy-Quelle für Vorschau und Live |
| Remote `NAS` | `ssh://git@git.adametz.media:222/gitmedia/b2in.git`: lokale Sicherung, wird nie deployt |
| Fehlerprotokoll | GlitchTip `https://errors.adametz.media`, Organisation `adametz-media`, Projekte `server-b2in` (ID 8) und `browser-b2in` (ID 7) |
| Push-Alarme | ntfy `https://push.adametz.media`, Topic `b2in-ops` |
| Externer Monitor | Uptime Kuma auf `/health` (mit Token) und `/up` je Domain |
---
## 1. Release lokal (jedes Mal)
```bash
git status # nichts offen
php artisan test --compact
npm ci && npm run build:web && npm run build:portal # public/build ist versioniert, der Server hat kein Node
git add public/build && git diff --cached --quiet public/build || git commit -m "build: Assets für vX.Y.Z"
git tag -a vX.Y.Z -m "vX.Y.Z – <Inhalt>
Deploy: <keine Migration | N Migrationen>, <Seeder>, <neue .env-Werte>
bash dev/betrieb/deploy.sh vX.Y.Z [--migrations] [--seed=…]"
git push origin <branch> --follow-tags
git push NAS <branch> --follow-tags # Sicherung
```
Versionen folgen SemVer: Ein Feature-Release erhöht die mittlere Stelle, ein Hotfix die letzte.
Der Interior-Relaunch ist `v2.0.0`.
---
## 2. Einmalig auf dem Server
Als App-Benutzer, gilt für Vorschau und Live (gleicher Server):
1. **Deploy-Key**, nur Leserecht am Repository auf forge.adametz.media. Auf dem Server liegen mehrere
Deploy-Keys für denselben Forgejo (B2in, Breco), deshalb gibt es je Projekt einen Host-Alias.
Die Repository-Adresse heißt dann **`forge-b2in:kadmin/b2in.git`**, nicht `ssh://git@forge.adametz.media:222/…`;
mit der vollen Adresse greift kein Alias, und ssh bietet keinen Schlüssel an („Permission denied (publickey)“).
```bash
ssh-keygen -t ed25519 -f ~/.ssh/deploy_b2in -N "" -C "b2in-server"
cat ~/.ssh/deploy_b2in.pub # in Forgejo: kadmin/b2in ▸ Einstellungen ▸ Deploy-Schlüssel, ohne Schreibrecht
```
`~/.ssh/config`:
```
Host forge-b2in
HostName forge.adametz.media
Port 222
User git
IdentityFile ~/.ssh/deploy_b2in
IdentitiesOnly yes
HostKeyAlias forge.adametz.media
```
Test: `git ls-remote forge-b2in:kadmin/b2in.git` zeigt die Tags.
2. **Flux Pro für Composer:** `deploy.sh` führt `composer install` aus. Dafür braucht der Server die
Flux-Zugangsdaten, also `auth.json` im App-Verzeichnis (ignoriert, bleibt liegen) oder
`composer config --global http-basic.composer.fluxui.dev <e-mail> <lizenz>`.
3. **PHP-FPM-Reload ohne Passwort testen:** `echo "" | sudo -S service php8.4-fpm reload; echo $?`.
Ergibt das Exit 0, lädt das Skript FPM selbst neu. Fragt sudo nach einem Passwort, beim Deploy
`DEPLOY_FPM_RELOAD=` setzen; dann wartet das Skript, bis FPM im Panel neu geladen ist. Heißt
der Dienst anders: `DEPLOY_PHP_FPM_SERVICE=…`. **Immer Reload, nie Restart.**
4. **Cron für den Scheduler:** `* * * * * cd <app> && php artisan schedule:run >> /dev/null 2>&1`.
Er trägt den Herzschlag für `/health` und `cabinet:reset-overrides`. Eine Queue braucht B2in
nicht; der Worker-Test im Skript ist aus.
5. **Server-Panel:** „Quick Deploy“ und Push-Webhooks bleiben aus. Ein Push ist noch kein Release.
### `.env`-Werte je Umgebung
| Schlüssel | Live | Vorschau |
|---|---|---|
| `SENTRY_LARAVEL_DSN`, `SENTRY_BROWSER_DSN` | wie lokal (Projekte server-b2in / browser-b2in), **erst nach der Datenschutzerklärung (Abschnitt 6)** | gleich, optional |
| `SENTRY_ENVIRONMENT` | `production` | `preview` |
| `MONITORING_NTFY_URL`, `_TOPIC`, `_TOKEN` | wie lokal | leer (keine Push-Alarme aus der Vorschau) |
| `MONITORING_ALERT_EMAIL` | Ops-Postfach | leer |
| `MONITORING_HEALTH_TOKEN` | **eigener Wert** (`openssl rand -hex 24`), nicht der lokale | eigener Wert oder leer |
| `MAIL_MAILER` | wie bisher | `log` (die Vorschau hat Live-Daten) |
`APP_VERSION` bleibt leer. Die Version kommt aus der Datei `RELEASE`, die das Skript schreibt.
---
## 3. Livegang Interior-Relaunch (v2.0.0)
**Grundidee:** Auf Live hat der Kunde Displays, Playlists und die Mediathek gepflegt, an der Website
im CMS aber nichts geändert. Deshalb kopieren wir **nicht** die Vorschau-Datenbank nach Live und
übertragen Display-Daten einzeln. Stattdessen bleibt die Live-Datenbank die Grundlage, und
`InteriorReleaseSeeder` bringt deren Website-Inhalte auf den neuen Stand. Displays,
Display-Mediathek, CMS-Mediathek, Benutzer und hochgeladene Dateien bleiben unberührt. Angepasst
werden nur:
- das Display-Branding, und zwar nur bei Modulen, die noch kein Interior-Logo tragen
- absolute Medien-URLs, die auf `b2in.test` oder `b2in.eu` zeigen; sie werden host-unabhängig
Damit der Kunde genau dieses Ergebnis vorher sieht, läuft derselbe Ablauf zuerst auf der Vorschau
mit einer frischen Kopie der Live-Daten (Generalprobe).
### 3.1 Generalprobe auf der Vorschau
```bash
# Skripte aus dem Tag holen (Vorschau und Live sind noch keine Git-Checkouts)
git clone -q --depth 1 --branch v2.0.0 forge-b2in:kadmin/b2in.git /tmp/b2in-v2.0.0
# 1. Live-Daten und Live-Medien in die Vorschau (Live wird nur gelesen; legt ~/b2in-live-<zeit>.sql.gz an)
bash /tmp/b2in-v2.0.0/dev/betrieb/vorschau-auffrischen.sh <live> <vorschau>
# 2. Vorschau einmalig auf Git umstellen und v2.0.0 ausliefern, mit allen Inhaltsschritten
cd <vorschau>
bash /tmp/b2in-v2.0.0/dev/betrieb/deploy.sh v2.0.0 --initial --migrations --seed=InteriorReleaseSeeder
```
`--initial` sichert vorher den Code nach `~/<verzeichnis>-vor-git-<zeit>.tgz`. Dann zeigt es die
Dateien, die es nur auf dem Server gibt (Reste aus SFTP- und Archiv-Updates). Erst nach einem
`ja` verschiebt es sie nach `~/<verzeichnis>-vor-git-<zeit>/`. Danach ist `git status` leer.
Vorschau prüfen: die Liste unter 8.3 im Entwicklungsplan, dazu im Portal der Vorschau die
**Displays und Playlists mit Live vergleichen**. Dort müssen dieselben Module, Bilder und Videos
erscheinen. Neue Generalprobe, etwa nach einem Hotfix-Tag oder weil auf Live weiter gepflegt
wurde:
```bash
cd <vorschau> && bash dev/betrieb/deploy.sh v2.0.1 --migrations # nur bei neuem Tag
bash dev/betrieb/vorschau-auffrischen.sh <live> <vorschau> --seed=InteriorReleaseSeeder
```
### 3.2 Livegang nach der Freigabe
1. Wartungsfenster mit Marcel abstimmen. Die Displays holen ihre Konfiguration zyklisch, laufen
also weiter, ein Wechsel im Laden ist aber sichtbar.
2. **DB-Backup von Live:**
`mysqldump --single-transaction --no-tablespaces -u … -p b2in_live | gzip > ~/b2in-live-vor-v2.sql.gz`
3. Live-`.env` ergänzen (Tabelle oben). `SENTRY_*` erst nach Abschnitt 6.
4. Gleicher Befehl wie auf der Vorschau, nur im Live-Verzeichnis:
```bash
cd <live>
bash /tmp/b2in-v2.0.0/dev/betrieb/deploy.sh v2.0.0 --initial --migrations --seed=InteriorReleaseSeeder
rm -rf /tmp/b2in-v2.0.0
```
5. Prüfen: `php artisan about --only=b2in` zeigt `v2.0.0`. `/health` mit Token liefert
`"status":"ok"` (der Herzschlag braucht nach dem Deploy bis zu einer Minute). Danach die
Liste 8.3 im Entwicklungsplan, die vier Displays sichten, `storage/logs` und GlitchTip auf neue
Fehler prüfen.
**Rückweg** (nur Live, falls etwas grundlegend nicht passt): Seite in den Wartungsmodus nehmen,
`.git` entfernen, die Code-Sicherung `~/<live>-vor-git-<zeit>.tgz` zurückspielen, das DB-Backup
aus Schritt 2 einspielen und FPM neu laden. Die geparkten Nur-Server-Dateien aus
`~/<live>-vor-git-<zeit>/` liegen ebenfalls noch vor.
---
## 4. Folge-Deploys (ab v2.0.0)
```bash
cd <app>
bash dev/betrieb/deploy.sh vX.Y.Z # ohne Migrationen
bash dev/betrieb/deploy.sh vX.Y.Z --migrations # mit Migrationen, vorher DB-Backup
bash dev/betrieb/deploy.sh vX.Y.Z --seed=<Seeder> # Inhaltsschritt laut Release-Notiz
bash dev/betrieb/deploy.sh <vorheriger Tag> # Rollback
```
Das Skript bricht ab, **bevor** es etwas ändert, wenn:
- im Verzeichnis jemand von Hand etwas geändert hat
- der Tag fehlt
- ein Build-Manifest fehlt
- neue Migrationen ohne `--migrations` kommen
Auf dem Server wird nichts von Hand geändert, auch Hotfixes laufen über Commit, Tag und Deploy.
Exit-Codes: `0` fertig · `1` abgebrochen · `2` falscher Aufruf.
---
## 5. Monitoring
Eingebaut nach `dev/git-deploy-monitoring/` (02 und 03), Tests unter `tests/Feature/Monitoring/`.
| Ausfall | Wer meldet | Kanal |
|---|---|---|
| Exception (Request, Command, Livewire) | Sentry-SDK | GlitchTip `server-b2in` |
| JavaScript-Fehler (Website und Portal) | `resources/js/error-reporting.js` | GlitchTip `browser-b2in` (Tag `area`: `web`/`portal`) |
| Toter interner Link, 419, 403/429 für Angemeldete | `ReportErrorSignals` | GlitchTip (Warnung) |
| Geplanter Task fehlgeschlagen | `onFailure` in `routes/console.php` | Log `monitoring`, Mail, ntfy |
| Cron steht, DB/Cache weg, Platte voll | `/health` | Uptime Kuma, dann ntfy und Mail |
| Server, FPM, Nginx oder SSL weg | `/up` nicht erreichbar | Uptime Kuma |
**Uptime Kuma** (status.adametz.media):
| Monitor | Ziel | Intervall |
|---|---|---|
| B2in /health | `https://b2in.eu/health`, Header `{"Authorization": "Bearer <MONITORING_HEALTH_TOKEN von Live>"}`, Status 200–299 | 60 s, 2 Wiederholungen |
| B2in /up | `https://b2in.eu/up` und `https://portal.b2in.eu/up` | 5 min |
| Zertifikate | Ablaufwarnung 14 Tage vorher | – |
`/health` antwortet auf jeder Domain, ist von der Basic Auth ausgenommen und liefert ohne oder
mit falschem Token 404.
**Abnahme auf Live:**
- `php artisan tinker --execute 'App\Support\OperationalAlert::send("Test B2in");'`: Push und Mail kommen an
- `php artisan tinker --execute 'report(new RuntimeException("Test B2in"));'`: erscheint in `server-b2in`, Umgebung `production`, Release `v2.0.0`
- Browser-Konsole auf b2in.eu: `setTimeout(() => { throw new Error('Test test@example.com') })` erscheint in `browser-b2in`, die E-Mail als `[email]`
- Uptime Kuma: Ein Test-Monitor mit falschem Token löst einen Alarm aus, danach löschen
**GlitchTip auswerten** (lokal, Lese-Token `GLITCHTIP_API_TOKEN` in der `.env`): Die Befehle stehen
in `dev/git-deploy-monitoring/02-fehlerprotokoll-glitchtip.md`, Abschnitt 7, mit
`B=https://errors.adametz.media/api/0`, `ORG=adametz-media`, Projekt `8` (Server) bzw. `7` (Browser).
Lokal sind die DSNs gesetzt (`SENTRY_ENVIRONMENT=local`). Fehler beim Entwickeln landen also in
GlitchTip, aber getrennt nach Umgebung. Die Testsuite meldet nie etwas; `phpunit.xml` leert DSNs
und ntfy.
---
## 6. Datenschutzerklärung (vor dem Einschalten der DSNs auf Live)
Die Fehlerprotokollierung muss in die Datenschutzerklärung, bevor auf Live `SENTRY_*` gesetzt wird.
Hier ein Textvorschlag, den Marcel bzw. die Rechtsberatung freigeben muss:
> **Fehlerprotokollierung.** Tritt beim Aufruf unserer Website ein technischer Fehler auf,
> speichern wir dazu die aufgerufene Adresse (ohne Formularinhalte und ohne Zugangs-Token), Browser
> und Betriebssystem, die Fehlermeldung und, bei angemeldeten Nutzern, deren interne Nutzer-ID.
> IP-Adressen und Cookies werden nicht übermittelt. Die Daten verarbeiten wir auf einem eigenen
> Server in Deutschland, um Fehler zu erkennen und zu beheben. Rechtsgrundlage ist unser
> berechtigtes Interesse am sicheren und fehlerfreien Betrieb (Art. 6 Abs. 1 lit. f DSGVO). Die
> Einträge werden nach 90 Tagen gelöscht.
Vor der Freigabe prüfen: Serverstandort und Aufbewahrungsfrist (`GLITCHTIP_RETENTION_DAYS`).
Eingetragen wird der Text in `resources/lang/de/b2in_legal.php` und im CMS unter „Rechtliches“.