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

12 KiB
Raw Blame History

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)

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)“).
    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

# 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:

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:
    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)

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“.