- 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>
12 KiB
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):
- 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, nichtssh://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:
Test:Host forge-b2in HostName forge.adametz.media Port 222 User git IdentityFile ~/.ssh/deploy_b2in IdentitiesOnly yes HostKeyAlias forge.adametz.mediagit ls-remote forge-b2in:kadmin/b2in.gitzeigt die Tags. - Flux Pro für Composer:
deploy.shführtcomposer installaus. Dafür braucht der Server die Flux-Zugangsdaten, alsoauth.jsonim App-Verzeichnis (ignoriert, bleibt liegen) odercomposer config --global http-basic.composer.fluxui.dev <e-mail> <lizenz>. - 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 DeployDEPLOY_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. - Cron für den Scheduler:
* * * * * cd <app> && php artisan schedule:run >> /dev/null 2>&1. Er trägt den Herzschlag für/healthundcabinet:reset-overrides. Eine Queue braucht B2in nicht; der Worker-Test im Skript ist aus. - 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.testoderb2in.euzeigen; 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
- Wartungsfenster mit Marcel abstimmen. Die Displays holen ihre Konfiguration zyklisch, laufen also weiter, ein Wechsel im Laden ist aber sichtbar.
- DB-Backup von Live:
mysqldump --single-transaction --no-tablespaces -u … -p b2in_live | gzip > ~/b2in-live-vor-v2.sql.gz - Live-
.envergänzen (Tabelle oben).SENTRY_*erst nach Abschnitt 6. - 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 - Prüfen:
php artisan about --only=b2inzeigtv2.0.0./healthmit 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/logsund 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
--migrationskommen
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 anphp artisan tinker --execute 'report(new RuntimeException("Test B2in"));': erscheint inserver-b2in, Umgebungproduction, Releasev2.0.0- Browser-Konsole auf b2in.eu:
setTimeout(() => { throw new Error('Test test@example.com') })erscheint inbrowser-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“.