# Deploy v1.1.0 — Checkliste > **Live seit 05.10.2026** — Abschluss vom Betreiber bestätigt. Protokoll am Ende. **Stand:** 05.10.2026 · **Live bisher:** Stand `98c465b` (Deploy 28.09.2026, ca. 14:34) **Inhalt:** alles danach — Blöcke 9 und 10 aus dem Audit ([`ENTWICKLUNGSPLAN.md`](../2026-09-11-audit/ENTWICKLUNGSPLAN.md), Deploy-Plan dort), Betriebsüberwachung Phase 1 ([`monitoring-plan.md`](monitoring-plan.md)), Composer-Update, Versionsnummer. Umfang gegenüber Live: 8 additive Migrationen, 2 neue Composer-Pakete (`sentry/sentry-laravel`, Abhängigkeiten), 48 Paket-Updates, neue `.env`-Werte, neue Assets (beide Builds), 2 neue Scheduler-Tasks (21 statt 19). Keine gelöschten Code-Dateien. Die Schritte aus dem Deploy-Plan der Blöcke 9/10 sind hier eingeordnet und mit **(B9/10 Nr.)** gekennzeichnet. **Auf dem Server wird kein Git gebraucht.** Git läuft nur lokal (Abschnitt A): Tag, Versionsdatei, Paket. Der Server bekommt das fertige Paket und braucht nur `tar`, `php` und `composer`. ## A — Lokal vorbereiten 1. Arbeitskopie committen, `main` per Fast-Forward nachziehen, Tag setzen: `git tag -a v1.1.0 -m "v1.1.0 — Audit-Blöcke 9/10, Betriebsüberwachung Phase 1"`. Push von `main` und Tag nach `origin`. 2. Auf dem Tag: `composer install`, `npm ci && npm run build`, volle Testsuite (`php artisan test --compact --parallel`), Larastan. 3. Versionsdatei und Paket bauen (im Projektverzeichnis, auf dem Tag): ```bash git describe --tags --always --dirty > RELEASE # muss genau "v1.1.0" ergeben git -c core.quotepath=off diff --name-only --diff-filter=AMR 98c465b v1.1.0 \ -- . ':!dev' ':!docs' ':!tests' ':!.mcp.json' ':!.codex' ':!phpunit.xml' ':!phpstan-baseline.neon' \ > deploy-files.txt tar czf deploy-v1.1.0.tgz -T deploy-files.txt public/build RELEASE ``` Ergibt `RELEASE` etwas anderes als `v1.1.0` (z. B. `-dirty`), ist die Arbeitskopie nicht sauber — erst klären. Im Devcontainer fehlt `docs/` (nicht eingebunden), das allein macht `-dirty`; dort nach Prüfung mit `git describe --tags --always` schreiben. **Erledigt 05.10.2026:** Paket `deploy-v1.1.0.tgz` gebaut (148 Dateien + `public/build` + `RELEASE` = `v1.1.0`), liegt im Projektverzeichnis. 4. Paket per SFTP nach `~/` auf den Server laden. ## B — Live vorbereiten (vor dem Wartungsfenster) 5. **DB-Backup** ziehen; `ARCHIVE_KEY` liegt getrennt gesichert vor. (B9/10 Nr. 4) 6. **Dateibackup** für den Rollback (inkl. `vendor/`, ohne `storage/`): ```bash cd ~ && tar czf backup-vor-v1.1.0.tgz --exclude=./storage --exclude=./node_modules -C ~/pressekonto.com . ``` 7. **`.env` auf Live ergänzen**: | Variable | Wert | Herkunft | |---|---|---| | `NEWSROOM_API_ASSIGNMENT_USER_IDS` | `13993` (vorher prüfen: `select id from users where email = 'info@connektar.de'`) | E19 | | `NEWSLETTER_SENDING_ENABLED` | `false` | E23 | | `BOUNCE_UNSIGNED_KIND` | `policy` | E25/O01 | | `SENTRY_LARAVEL_DSN` | DSN Projekt 1 (errors.adametz.media) | M1 | | `SENTRY_BROWSER_DSN` | DSN Projekt 2 | M1 | | `SENTRY_ENVIRONMENT` | `production` | | | `SENTRY_RELEASE` / `APP_VERSION` | **leer lassen** — kommt aus `RELEASE` | | | `MONITORING_NTFY_URL` | `https://push.adametz.media` | M3 | | `MONITORING_NTFY_TOPIC` | `pressekonto-ops` | | | `MONITORING_NTFY_TOKEN` | Token von `pressekonto-alerts` | | | `MONITORING_HEALTH_TOKEN` | **neuer** Zufallswert, nicht der lokale (`openssl rand -hex 24`) | 1.5 | | `MONITORING_ALERT_EMAIL` | Ops-Postfach prüfen (leer = `LEGAL_CONTACT_EMAIL`) | | | `UMAMI_PORTAL_ENABLED` | `false` — erst nach DSE-Anpassung | M6 | | `UMAMI_IDENTIFY_USERS` | `false` — erst nach DSE-Anpassung | M6 | Optionale Werte (Standards greifen) siehe B9/10 Nr. 1. 8. **Hauptkategorien** gegen `config/canonical.php` prüfen: `select id, slug from category_translations where locale = 'de' and category_id between 1 and 14`. (B9/10 Nr. 2) 9. **Uploadgrenzen** PHP `upload_max_filesize`/`post_max_size` und nginx `client_max_body_size` ≥ 20M (Ploi: PHP-Einstellungen bzw. nginx-Konfiguration). (B9/10 Nr. 3) 10. **Ausgehend erreichbar** vom Server: `curl -sI https://errors.adametz.media` und `curl -sI https://push.adametz.media` (beide antworten). ## C — Deploy (SSH, Wartungsfenster ca. 3–5 Minuten) ```bash cd ~/pressekonto.com php artisan down --retry=60 tar xzf ~/deploy-v1.1.0.tgz composer install --no-dev --optimize-autoloader --no-interaction php artisan migrate --force # 8 Migrationen, additiv (B9/10 Nr. 6) [ -d public/vendor/livewire ] && php artisan vendor:publish --tag=livewire:assets --force php artisan config:cache php artisan route:cache php artisan view:clear && php artisan view:cache php artisan event:cache php artisan schedule:clear-cache # alte 24-h-Sperren (B9/10 Nr. 7) php artisan queue:restart # PHP-FPM neu laden (Ploi: Server ▸ PHP ▸ Restart) — OPcache hält sonst alten Code php artisan up ``` Schlägt `composer install` oder `migrate` fehl: **nicht** `up`, sondern Rollback (Abschnitt G). ## D — Direkt danach (Prüfungen) 11. `php artisan about --only=pressekonto` → Version `v1.1.0`. 12. Admin ▸ Reports ▸ Betriebsüberwachung: Version `v1.1.0`; nach 1–2 Minuten Scheduler grün, Plattenplatz grün; `archive_key`, `archive_pending`, `archive_sweep` grün (nach Nr. 14). 13. `curl -s -H "Authorization: Bearer " https://pressekonto.com/health` → `"status":"ok"`, `"version":"v1.1.0"`. Direkt nach dem Deploy kann der Scheduler noch fehlen (503) — eine Minute warten. 14. `php artisan archive:retry-pending` einmal von Hand. (B9/10 Nr. 8) 15. `php artisan company-memberships:api-assignments` sichten; `--remove` nur bei Treffern ohne Freischaltung. (B9/10 Nr. 9) 16. `php artisan press-releases:assign-canonicals`. (B9/10 Nr. 10) 17. `php artisan press-release-images:sweep-orphan-originals` (Trockenlauf) und `php artisan archive:sweep-orphans --dry-run`. (B9/10 Nr. 11, 12) 18. `php artisan schedule:list` → **21** Tasks, darunter `monitoring:heartbeat` (minütlich), `queue:prune-failed` (03:55), `press-release-images:sweep-orphan-originals` (So 04:45), `press-releases:purge-events` (03:50). (B9/10 Nr. 13) 19. **GlitchTip:** `php artisan tinker --execute 'report(new RuntimeException("Deploy-Test v1.1.0"));'` → erscheint in Projekt 1 mit Umgebung `production`, Release `v1.1.0`. 20. **ntfy:** `php artisan tinker --execute 'App\Support\OperationalAlert::send("Deploy v1.1.0 live");'` → Push auf dem Handy (und Mail an das Ops-Postfach). 21. `storage/logs/laravel-*.log` und `monitoring-*.log` auf neue Fehler prüfen. ## E — Abnahme mit dem Testkonto 22. Anmelden per Passwort, Magic Link und Google; danach einige Seiten aufrufen. Ein eingeloggter Nutzer, der einen eigenen Magic Link öffnet, landet auf dem Ziel. (B9/10 Nr. 14) 23. Einmal absichtlich falsches Passwort → Admin ▸ Reports ▸ Verlauf zeigt „Anmeldung fehlgeschlagen … falsches Passwort“. (1.7) 24. Titelbild mit 13–15 MiB hochladen. (B9/10 Nr. 15) 25. `sitemap.xml`: englische Meldungen unter `/en/press-release/…`; `article:modified_time` = Veröffentlichung/letzte Textänderung. (B9/10 Nr. 16) 26. Admin ▸ Newsletter: Sperrhinweis, „Senden“ aus. (B9/10 Nr. 17) 27. Nach dem nächsten `mail:fetch-bounces`: unsignierte Rückläufer als Richtlinie „(unsigniert)“, ohne Sperre. (B9/10 Nr. 18) 28. Der Smoke-Test aus dem Runbook (Abschnitt 8): drei Domains, robots/sitemap, Test-PM durch die Klassifikation, Formulare mit Mail. ## F — Danach 29. **Erledigt 06.10.2026 (status.adametz.media).** **Externer Monitor** (Uptime Kuma o. Ä.) auf `https://pressekonto.com/health`, Header `Authorization: Bearer …`, 60 s, 2 Wiederholungen, Benachrichtigung über ntfy (`push.adametz.media`, `pressekonto-ops`). (1.5) 30. ~~**KAS-Passwort rotieren**~~ (B9/10 Nr. 19) — erledigt 06.10.2026, SSH-Zugang deaktiviert. 31. **Datenschutzerklärung** (Betreiber/Anwalt): Rückläufer-Protokoll (B9/10 Nr. 20), Fehlerprotokollierung GlitchTip (User-ID, URL, Browser, Fehlermeldung, 90 Tage), fehlgeschlagene Anmeldungen im Verlauf (IP nur als HMAC). Danach — und erst dann — `UMAMI_PORTAL_ENABLED`/`UMAMI_IDENTIFY_USERS` per `.env` + `config:cache`. 32. Restore-Übung (B9/10 Nr. 21). 33. Protokolle ergänzen: Deploy-Datum und Ergebnisse in `ENTWICKLUNGSPLAN.md` (B9/10 Nr. 22) und `monitoring-plan.md`. 34. Backups `~/deploy-v1.1.0.tgz` und `~/backup-vor-v1.1.0.tgz` nach ein paar stabilen Tagen löschen. ## G — Rollback ```bash cd ~/pressekonto.com php artisan down tar xzf ~/backup-vor-v1.1.0.tgz -C ~/pressekonto.com # alter Code inkl. vendor/ php artisan config:cache && php artisan route:cache && php artisan view:clear && php artisan view:cache && php artisan event:cache php artisan queue:restart # + PHP-FPM neu laden php artisan up ``` Die Migrationen sind additiv; der alte Code läuft mit den neuen Spalten und Tabellen. Nur bei Datenproblemen das DB-Backup aus Nr. 5 einspielen und danach die Archiv-Schritte aus Runbook Abschnitt 9 befolgen. ## Protokoll **05.10.2026 — Deploy v1.1.0 live** (Paket aus Abschnitt A, Commit `df7ebdc` = Tag `v1.1.0`). Abschnitt D, Ausgaben auf Live: | Nr. | Ergebnis | |---|---| | 11 | Zunächst Version „—“: Datei `RELEASE` fehlte bzw. Config-Cache war älter. Behoben mit `echo v1.1.0 > RELEASE` + `config:cache`. Eine lokal neu erzeugte `RELEASE` hatte `v1.1.0-2-g8a64bd7-dirty` ergeben (Doku-Commits nach dem Tag, `docs/` nicht eingebunden) — Lehre: `RELEASE` aus dem Paket nehmen, nicht neu erzeugen | | 14 | `archive:retry-pending`: 0 Aufträge, 0 offen | | 15 | `company-memberships:api-assignments`: 3 Mitgliedschaften (connektar, User 13993), alle freigeschaltet — kein `--remove` | | 16 | `assign-canonicals`: 9.887 Paare, 342 geändert, 13 veraltete Verweise zurückgesetzt, Caches geleert | | 17 | `sweep-orphan-originals` (Trockenlauf): 6 geprüft, 0 verwaist; `archive:sweep-orphans --dry-run`: 0 | | 18 | `schedule:list`: 21 Tasks inkl. `monitoring:heartbeat`, `queue:prune-failed` 03:55, `sweep-orphan-originals` So 04:45 | | 19, 20 | GlitchTip- und ntfy-Test ausgeführt; nach `config:cache` wiederholt | Abschnitt E (Abnahme Testkonto) vom Betreiber nicht einzeln gemeldet. Abschnitt F offen: externer Monitor auf `/health` (29), Datenschutzerklärung (31), Restore-Übung (32); dazu Context7-Schlüssel rotieren (stand bis `df7ebdc` im Repository). KAS-Passwort (30) erledigt 06.10.2026.