# 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 – Deploy: , , bash dev/betrieb/deploy.sh vX.Y.Z [--migrations] [--seed=…]" git push origin --follow-tags git push NAS --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 `. 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 && 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-.sql.gz an) bash /tmp/b2in-v2.0.0/dev/betrieb/vorschau-auffrischen.sh # 2. Vorschau einmalig auf Git umstellen und v2.0.0 ausliefern, mit allen Inhaltsschritten cd bash /tmp/b2in-v2.0.0/dev/betrieb/deploy.sh v2.0.0 --initial --migrations --seed=InteriorReleaseSeeder ``` `--initial` sichert vorher den Code nach `~/-vor-git-.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 `~/-vor-git-/`. 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 && bash dev/betrieb/deploy.sh v2.0.1 --migrations # nur bei neuem Tag bash dev/betrieb/vorschau-auffrischen.sh --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 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 `~/-vor-git-.tgz` zurückspielen, das DB-Backup aus Schritt 2 einspielen und FPM neu laden. Die geparkten Nur-Server-Dateien aus `~/-vor-git-/` liegen ebenfalls noch vor. --- ## 4. Folge-Deploys (ab v2.0.0) ```bash cd 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= # Inhaltsschritt laut Release-Notiz bash dev/betrieb/deploy.sh # 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 "}`, 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“.