- dev/roadmap.md neu: alle offenen Punkte aus Plänen, Audits und Protokollen, gegen Code und Git abgeglichen (Stand v1.7.0) - Abgeschlossenes ins Archiv: 2026-09-01 → archive/2026-09-nutzergewinnung, 2026-09-11-audit → archive/2026-09-audit, lagacy-rechnungen → archive/legacy-rechnungen, Deploy-Protokoll v1.1.0, Umami-Snippets, Go-Live-Roadmap vom 20.07. - Aktiv bleiben eigene Ordner bildrechte/ (Plan Fassung 2, Decision-Update) und changelog/ (Decision-Update Changelog) - dev/README.md als Ordnerkarte neu, Verweise und zwei Code-Kommentare auf die neuen Pfade Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 KiB
Betriebsüberwachung, Bedienbarkeit, Nutzungsstatistik — Plan
Stand: 05.10.2026 · Phase 1 live seit 05.10.2026 (v1.1.0)
Ziel: Fehler fallen auf, bevor Kunden sie melden — Serverfehler aus der Programmierung ebenso wie Bedienungs- und Usability-Probleme. Dazu eine Nutzungsstatistik, aus der sich ablesen lässt, wer was tut und wo Arbeit aufhört.
Entscheidungen (Betreiber, 02.10.2026)
| Nr. | Frage | Entscheidung |
|---|---|---|
| M1 | Fehlerwerkzeug | GlitchTip, selbst gehostet (wie Umami auf sys.adametz.media), angebunden über die offiziellen Sentry-SDKs (sentry/sentry-laravel, @sentry/browser) |
| M2 | Umami im Portal | ja, mit Zuordnung zum Nutzer (umami.identify mit der internen User-ID, keine E-Mail, keine Namen) |
| M3 | Alarmweg | Mail bleibt, zusätzlich ntfy (HTTP-POST, JSON) |
Bei der Umsetzung getroffen (Bestätigung offen):
| Nr. | Frage | Entscheidung |
|---|---|---|
| M4 | Personenbezug in GlitchTip | Nur die interne User-ID, send_default_pii aus, Request-Bodys nie (max_request_body_size = never) |
| M5 | Browserfehler-SDK | Wird erst beim ersten Fehler nachgeladen (dynamischer Import) — kein Ladegewicht auf fehlerfreien Seiten, dafür ohne Breadcrumbs |
| M6 | Umami-Schalter | Getrennt: UMAMI_ENABLED (Webseiten), UMAMI_PORTAL_ENABLED (Kundenbereich), UMAMI_IDENTIFY_USERS (Zuordnung). Portal und Zuordnung erst nach DSE-Anpassung einschalten |
| M7 | Umami im Admin, unter Impersonation, auf Auth-Seiten | Nicht getrackt: Admin-Nutzung verfälscht die Kundenstatistik, Impersonation würde Admin-Klicks dem Kunden zuschreiben, Auth-Seiten tragen Tokens im Pfad (/reset-password/{token}) |
| M8 | Query-Parameter an Umami | Nur utm_*, src, ref, q, page gehen raus; alles andere (Signaturen, Tokens) filtert data-before-send — gilt auch für die Webseiten |
| M9 | Fehlgeschlagene Logins | Im Verlauf mit User-Bezug (falls Konto existiert) und gekürztem HMAC der IP statt Klartext-IP |
Phase 1 — Fehler fallen sofort auf
| Schritt | Inhalt | Stand |
|---|---|---|
| 1.1 | GlitchTip-Anbindung Server: sentry/sentry-laravel, Integration::handles() in bootstrap/app.php, Release aus SENTRY_RELEASE, User-ID als einziger Personenbezug |
erledigt |
| 1.2 | Browserfehler (JS-Fehler, unbehandelte Promises) auf Webseiten, Auth-Seiten und Portal; eigener Vite-Eintrag resources/js/error-reporting.js, SDK erst beim ersten Fehler |
erledigt |
| 1.3 | Fehler ohne Exception als GlitchTip-Warnung: 404 mit Referrer der eigenen Domain (kaputte interne Links), 403 und 429 für angemeldete Nutzer, 419 (Seite abgelaufen, Arbeit verloren). Je Signal gedrosselt | erledigt |
| 1.4 | Scheduler-Herzschlag (monitoring:heartbeat, minütlich) und Check im Systemstatus |
erledigt |
| 1.5 | Tiefer Health-Endpunkt /health mit Token: DB, Cache, Queue-Rückstau, Scheduler-Herzschlag, Plattenplatz → 200/503 JSON, für Uptime Kuma bzw. Ploi |
erledigt |
| 1.6 | ntfy als zweiter Alarmweg in OperationalAlert |
erledigt |
| 1.7 | Fehlgeschlagene Logins, Login-Sperren und 2FA-Fehler im Aktivitäts-Verlauf | erledigt |
| 1.8 | queue:prune-failed (30 Tage) im Scheduler |
erledigt |
| 1.9 | Umami: Portal (Kundenbereich) mit identify, Query-Filter, Web Vitals; Schalter getrennt (M6) |
erledigt |
Signale, die nicht im Browser erfasst werden, weil der Server sie schon sieht: fehlgeschlagene Livewire-Requests mit 5xx (Exception in GlitchTip) und 419 (Schritt 1.3).
Phase 2 — Bedienprobleme sichtbar machen
- Validierungsfehler zentral zählen: Formular/Komponente × Feld × Regel, nie der Wert.
- Schritt-Tracking für die Kernabläufe (Registrierung, Newsroom anlegen,
PM erstellen/einreichen, Checkout, Bildbestätigung):
step_viewed/step_completed, pseudonymer Ablauf-Schlüssel, eigene kleine Tabelle, Schreiben perdefer(). - Benannte Umami-Events (
data-umami-event) an den Kern-Buttons. - „Problem melden“ im Portal: URL, Browser, letzte GlitchTip-Event-ID, Freitext.
- Geschäftstrichter aus vorhandenen Daten (Registrierung → erste PM → eingereicht → bezahlt → veröffentlicht → zweite PM), ohne neues Tracking.
Phase 3 — Rückgänge und Abwanderung erkennen
- Tägliche Kennzahlen-Tabelle (Datum × Kennzahl × Dimension → Anzahl), nächtlich verdichtet.
- Alarme bei ausbleibender Aktivität: Einlieferer ohne Einlieferung, Tage ohne Registrierung/Login/Zahlung, Funktionen deutlich unter dem 4-Wochen-Mittel.
- Überfällig-Liste je Kunde (übliches PM-Intervall × 2).
- Admin-Seite „Betrieb & Nutzung“;
conversion:funnelundapi:usage-reportmit Oberfläche. - Morgendliche Sammelmail.
- Löschfristen für
activity_eventsundapi_usage_logs(Vorschlag 395 Tage).
Datenschutz (Betreiber/Anwalt, vor dem Einschalten)
- DSE §5 (Umami): Portal-Nutzung mit Zuordnung zur Nutzer-ID ist personenbezogen
(pseudonym) — Abschnitt ergänzen, bevor
UMAMI_PORTAL_ENABLED/UMAMI_IDENTIFY_USERSan. - Neuer Abschnitt Fehlerprotokollierung (GlitchTip, selbst gehostet; User-ID, URL, Browser, Fehlermeldung; Frist = GlitchTip-Aufbewahrung, Vorschlag 90 Tage).
- Aktivitäts-Verlauf mit fehlgeschlagenen Anmeldungen (IP nur als gekürzter HMAC).
- Unabhängig davon offen:
activity_events/api_usage_logsohne Löschfrist und ohne DSE-Erwähnung.
Live-Schritte Phase 1
Ausgeführt mit Deploy v1.1.0 — vollständiger Ablauf in deploy-v1.1.0.md.
Infrastruktur (einmalig)
- GlitchTip auf sys.adametz.media (Docker Compose: web, worker, Postgres,
Redis;
GLITCHTIP_RETENTION_DAYS=90, vor GlitchTip 6GLITCHTIP_MAX_EVENT_LIFE_DAYS). Zwei Projekte anlegen:pressekonto-server(PHP/Laravel) undpressekonto-browser(JavaScript). Alert-Regel je Projekt: Mail an das Ops-Postfach bei neuem Fehlertyp. - ntfy: Server (ntfy.sh oder selbst gehostet), Topic mit Zugriffsschutz, Token anlegen. Optional GlitchTip-Webhook an ntfy über dessen Template-Funktion.
- Uptime Kuma (oder Ploi-Monitor) auf
https://pressekonto.com/healthmit HeaderAuthorization: Bearer <MONITORING_HEALTH_TOKEN>, Intervall 1 min, Benachrichtigung über ntfy und Mail.
.env auf Live
SENTRY_LARAVEL_DSN=… # Projekt pressekonto-server
SENTRY_ENVIRONMENT=production
SENTRY_RELEASE= # leer lassen, siehe RELEASE-Datei unten
SENTRY_BROWSER_DSN=… # Projekt pressekonto-browser
MONITORING_HEALTH_TOKEN=<zufällig, ≥ 32 Zeichen>
MONITORING_NTFY_URL=https://push.adametz.media
MONITORING_NTFY_TOPIC=pressekonto-ops
MONITORING_NTFY_TOKEN=…
UMAMI_ENABLED=true # Webseiten
UMAMI_PORTAL_ENABLED=false # erst nach DSE-Anpassung
UMAMI_IDENTIFY_USERS=false # erst nach DSE-Anpassung
Danach config:cache, npm run build, queue:restart.
Bei jedem Deploy (SFTP/SSH, solange es kein Git-Deploy gibt): lokal vor
dem Upload git describe --tags --always --dirty > RELEASE und die Datei RELEASE
mit hochladen, danach auf dem Server php artisan config:cache. GlitchTip
ordnet Fehler so dem Stand zu (…-dirty = mit nicht committeten Änderungen
deployt). Beim späteren Git-Deploy schreibt das Deploy-Skript dieselbe Datei.
GlitchTip-Instanz: errors.adametz.media, Projekt 1 = Server, Projekt 2 = Browser.
Umgebungen: production (Live), local (Entwicklung).
Umsetzungsprotokoll
02.10.2026 — Phase 1 umgesetzt (Branch technik/monitoring-phase-1)
Neue Abhängigkeiten: sentry/sentry-laravel ^4.28 (Composer, nur neue
Pakete), @sentry/browser ^11.3 (npm).
| Schritt | Dateien |
|---|---|
| 1.1 | config/sentry.php (veröffentlicht, gehärtet), app/Support/Monitoring/ErrorReportScrubber.php, bootstrap/app.php (Integration::handles) |
| 1.2 | resources/js/error-reporting.js + error-reporting-sdk.js (Chunk 32 kB gzip, nur bei Fehler), resources/views/components/monitoring/browser-errors.blade.php, eingebunden in partials/head (Portal), web/layouts/web-master, components/layouts/auth/pressekonto; Vite-Einträge in beiden Configs |
| 1.3 | app/Support/Monitoring/ErrorSignal.php, app/Http/Middleware/ReportErrorSignals.php (global) |
| 1.4 | app/Console/Commands/MonitoringHeartbeat.php, Scheduler minütlich, Check scheduler im Systemstatus |
| 1.5 | app/Services/Monitoring/HealthProbe.php, app/Http/Controllers/HealthController.php, Route /health (ohne Gruppe, throttle 30/min), Basic-Auth-Ausnahme; Check disk im Systemstatus |
| 1.6 | app/Support/OperationalAlert.php (Mail und ntfy unabhängig, beide Best-Effort) |
| 1.7 | ActivityType::LoginFailed/LoginLockout/TwoFactorFailed, RecordAuthActivity (Grund unknown_account/no_password/wrong_password, ip_hash), Login-Komponente feuert Failed und Lockout mit Adresse |
| 1.8 | queue:prune-failed --hours=720 täglich 03:55 |
| 1.9 | components/web/umami.blade.php (Web + Portal, data-tag, data-before-send, data-performance, identify('user-<id>')), config/services.php (portal_enabled, identify_users) |
Nebenbefund: Das Umami-Snippet der Webseiten schickte bisher die volle Query
mit — signierte Claim-Links (?signature=…) landeten so in Umami. Der Filter
(M8) gilt jetzt auch dort.
Tests: tests/Feature/Monitoring/* (neu), tests/Feature/Auth/FailedLoginActivityTest.php
(neu), OperationalAlertTest und UmamiSnippetTest erweitert. Gesamtsuite
2312 grün (1 übersprungen), Larastan ohne Meldung, Pint sauber.
Nicht im Browser geprüft (kein GlitchTip/ntfy angebunden): das tatsächliche
Einliefern an GlitchTip und die Darstellung dort. Erster Test nach dem
Aufsetzen: im Browser setTimeout(() => { throw new Error('GlitchTip-Test') })
auf einer Portalseite, serverseitig php artisan tinker --execute 'report(new RuntimeException("GlitchTip-Test"));'.
05.10.2026 — Composer-Update
composer update innerhalb der bestehenden Grenzen (48 Updates, u. a.
Laravel 12.69.3, Livewire 4.4.7, Flux 2.20.1, Fortify 1.40.0); damit sind die
beiden league/commonmark-Advisories behoben (2.10.3), composer audit sauber.
- Livewire 4.4.7 rendert
ModelNotFoundExceptionim Testhelfer als 404, statt sie zu werfen — zwei Tests aufassertNotFound()umgestellt (CustomerCompanyContextTest,ContactAccessTest). Laufzeitverhalten unverändert. - Deploy: Liegt auf Live
public/vendor/livewire/(veröffentlichte Assets), nachcomposer installphp artisan vendor:publish --tag=livewire:assets --forceausführen — sonst läuft ein alteslivewire.jsgegen das neue Backend. Lokal war die Kopie schon vor dem Update veraltet (Hash 3dabe98a statt 8ea5922c).
05.10.2026 — GlitchTip angebunden (lokal)
DSNs eingetragen (errors.adametz.media, Projekte 1 und 2), lokal
SENTRY_ENVIRONMENT=local. Release-Kennung ohne Git auf dem Server:
App\Support\Monitoring\Release liest SENTRY_RELEASE, sonst die beim
Deploy hochgeladene Datei RELEASE (in .gitignore). Test-Events lokal
abgeschickt: Server-Exception (report()), toter interner Link per HTTP.
05.10.2026 — ntfy angebunden (lokal)
ntfy auf push.adametz.media (gleicher Server wie GlitchTip),
auth-default-access: deny-all (anonymes Lesen → 403 geprüft). Eigener
Benutzer pressekonto-alerts mit write-only auf pressekonto-ops, Token in
MONITORING_NTFY_TOKEN; Lesen über den persönlichen Benutzer in der App.
Test-Alert aus OperationalAlert ohne Fehler angenommen.
05.10.2026 — Versionsnummer
Versionen sind Git-Tags nach SemVer (v1.0.0 Go-Live 20.07., nächster Deploy
v1.1.0). config('app.version') = APP_VERSION oder Datei RELEASE
(git describe --tags --always --dirty); dieselbe Kennung als GlitchTip-Release,
in /health (version), im Admin-Systemstatus und in php artisan about.
Grundlage für die spätere Changelog-Seite.
05.10.2026 — Phase 1 live (v1.1.0)
Mit Deploy v1.1.0 ausgeliefert (deploy-v1.1.0.md).
GlitchTip (production), ntfy und Scheduler-Herzschlag auf Live aktiv.
Offen aus Phase 1: externer Monitor auf /health; DSE-Absätze, danach
UMAMI_PORTAL_ENABLED/UMAMI_IDENTIFY_USERS.
06.10.2026 — Erste Auswertung von GlitchTip
Lesezugriff per API eingerichtet; Arbeitsanleitung, Regeln und Protokoll der
Einträge in ../glitch-tip/README.md. Befunde:
Testsuite meldete an die echte DSN (behoben, 2f02acc), Search-Console-Lauf
robuster (a90f24a), Registrierung mit Alpine-Fehler, Promise-Ablehnungen
lesbar, Tippfehler bei artisan nicht mehr gemeldet (16af374).
06.10.2026 — Nachtrag 1.7: fehlgeschlagene Anmeldungen
Befund auf Live (05.10. 15:26 – 06.10. 16:25): rund 250 Fehlversuche aus
53 Quellen, alle unknown_account, kein falsches Passwort, keine Sperre,
gleichmäßig auch nachts — verteiltes, langsames Durchprobieren fremder
Zugangsdaten („low and slow“, jede Quelle bleibt unter der Sperre von
5/Minute). Kein Konto betroffen. Die Einträge verdeckten im Verlauf die
relevanten Fälle. Das Nginx-Zugriffsprotokoll der Site ist bei Ploi aus,
der Einstieg ließ sich so nicht klären.
Umgesetzt:
meta.entryan jedem Fehlversuch, jeder Sperre und jedem 2FA-Fehler:livewire(Formular),fortify(POST /login, alle Domains, auch JSON),two-factor, sonstother.- Unbekannte Adressen nicht mehr im Verlauf, sondern in
failed_login_tallies(Tag × Einstieg × IP-Hash → Versuche, erster/letzter Zeitpunkt). Löschfrist 90 Tage übermodel:prune(täglich 04:05). - Systemstatus-Kachel „Anmeldeversuche mit unbekannter Adresse“ (heute/gestern,
Versuche und Quellen);
warnabMONITORING_FAILED_LOGIN_WARN_PER_DAY(1000).
Auswertung auf Live (nach dem Deploy):
php artisan tinker --execute '
DB::table("failed_login_tallies")
->where("day", ">=", now()->subDays(7)->toDateString())
->selectRaw("day, entry, count(*) as quellen, sum(attempts) as versuche")
->groupBy("day", "entry")->orderBy("day")->orderBy("entry")
->get()
->each(fn ($r) => print(sprintf("%s %-10s %4d Quellen %5d Versuche\n", $r->day, $r->entry, $r->quellen, $r->versuche)));
'
Danach entscheiden: überwiegend fortify → POST /login auf den Hub
beschränken bzw. je IP drosseln (vorher klären, wer den JSON-Login nutzt);
überwiegend livewire → Honeypot/Turnstile im Formular.
Externer Monitor auf /health — Einrichtung (erledigt 06.10.2026, status.adametz.media)
Ziel: Ausfälle melden, die die App selbst nicht melden kann (Server weg,
PHP-FPM hängt, Scheduler steht, Platte voll). Uptime Kuma auf dem
GlitchTip-Server (178.105.154.106); die App läuft auf 178.105.240.70 —
ein anderer Server, also echte Außensicht. ntfy liegt auf demselben Server
wie Uptime Kuma; fällt dieser aus, kommt kein Push (siehe Schritt 6).
-
Installation (Docker, neben GlitchTip, hinter demselben Reverse Proxy, z. B.
status.adametz.media):services: uptime-kuma: image: louislam/uptime-kuma:1 restart: unless-stopped volumes: - ./uptime-kuma:/app/data ports: - "127.0.0.1:3001:3001"Admin-Konto beim ersten Aufruf anlegen, 2FA in den Einstellungen aktivieren.
-
Benachrichtigung (Settings ▸ Notifications ▸ ntfy): Server
https://push.adametz.media, Topicpressekonto-ops, Priorität 4, Authentifizierung „Access Token“ mit dem Token vonpressekonto-alerts. Zusätzlich SMTP-Mail an das Ops-Postfach. -
Monitor „pressekonto /health“: Typ HTTP(s), URL
https://pressekonto.com/health, Methode GET, Header (JSON){"Authorization": "Bearer <MONITORING_HEALTH_TOKEN aus der Live-.env>"}, Intervall 60 s, Wiederholungen 2 (Retry-Intervall 60 s), akzeptierte Statuscodes200-299. 503 = mindestens ein Check kritisch, Details in der JSON-Antwort. -
Monitore „Domains“ ohne Token, Typ HTTP(s) - Keyword oder HTTP(s):
https://presseecho.de/up,https://businessportal24.com/up,https://pressekonto.com/up— prüft Nginx/SSL/PHP je Domain, Intervall 5 min. Zertifikatsablauf-Warnung aktivieren (Settings ▸ „Certificate Expiry Notification“, 14 Tage). -
Test: Einen zweiten Monitor mit absichtlich falschem Token anlegen —
/healthantwortet dann 404, der Alarm muss auf dem Handy und per Mail ankommen. Danach diesen Test-Monitor löschen. Beim nächsten Deploy zeigt der echte Monitor die Wartungsphase (down→ 503) als kurzen Ausfall. -
Zweite Ebene (optional, empfohlen): ein kostenloser externer Dienst (z. B. UptimeRobot) mit einem einfachen Monitor auf
https://pressekonto.com/upund Mail-Benachrichtigung. Er meldet, wenn der Monitoring-Server selbst ausfällt oder beide Server gemeinsam nicht erreichbar sind.
Nach der Einrichtung: Punkt 29 in ../archive/betrieb/deploy-v1.1.0.md und Phase 1 hier als erledigt eintragen.
06.10.2026 — Worker-Ausfall und Folgen (v1.3.2)
Nach dem Deploy von v1.3.0 (16:51) lief über eine Stunde kein Queue-Worker:
Supervisor war gestoppt, queue:restart beendete den Worker, niemand startete
ihn neu. Sichtbar war es nur in der Systemstatus-Kachel; einen Alarm gab es
nicht — /health hätte 503 geliefert, der externe Monitor war noch nicht
eingerichtet. Beim Abarbeiten scheiterten drei Veröffentlichungsmails vom
03./08.09., die erneut eingestellt worden waren (GlitchTip #23, alte
Serialisierung ohne $viaSyndication) — entfernt, nicht nachgesendet.
- Externer Monitor läuft seit 06.10.2026 (
status.adametz.media, Uptime Kuma). Er deckt einen stehenden Worker ab:/healthmeldetqueuekritisch ab 5 Minuten Wartezeit. Ein zusätzlicher Alarm aus dem Scheduler wurde deshalb bewusst nicht gebaut (wäre doppelt). - Worker-Prüfung im Deploy (
monitoring:queue-probe, Ende vondeploy.sh, Exit 3 ohne Worker). - Runbook: kein pauschales
queue:retry allmehr; Vorgehen bei stehendem Worker.
07.10.2026 — Allgemeine Fassung für andere Projekte
Git-Deploy und Betriebsüberwachung als übertragbarer Baukasten mit Vorlagen
(Laravel-Dateien, deploy.sh, Docker-Compose für GlitchTip/ntfy/Uptime Kuma):
../git-deploy-monitoring/. In einem
frischen Laravel 12 eingebaut und getestet.