diff --git a/.env.example b/.env.example index 00811c8..b4bc398 100644 --- a/.env.example +++ b/.env.example @@ -56,6 +56,21 @@ APP_SECRET="bitte-aendern-mindestens-32-zeichen-langer-zufallswert" # Lebensdauer einer Anmeldung in Tagen. SESSION_TTL_DAYS="30" +# ── Benachrichtigungen ─────────────────────────────────────── +# ntfy und Webhook brauchen nichts weiter – die Ziele werden in der +# Anwendung unter "Mein Konto" eingerichtet. +# +# E-Mail steht erst zur Verfuegung, wenn SMTP_HOST gesetzt ist: +# SMTP_HOST="smtp.example.com" +# SMTP_PORT="587" +# SMTP_SECURE="false" # true bei Port 465, sonst STARTTLS +# SMTP_USER="nebula@example.com" +# SMTP_PASSWORD="geheim" +# SMTP_FROM="Nebula " + +# Timeout fuer das Zustellen in Millisekunden +NOTIFY_TIMEOUT_MS="10000" + # ── Ausgehende Git-Zugriffe ────────────────────────────────── # Timeout fuer Git-API-Aufrufe in Millisekunden GIT_API_TIMEOUT_MS="15000" diff --git a/FEATURES.md b/FEATURES.md index 74796da..a0e0ca6 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -322,6 +322,56 @@ Dateneinsicht. Ein Administrator sieht dadurch weiterhin keine fremden Projekte. --- +## 4b. Benachrichtigungen + +Einzurichten unter *Mein Konto → Benachrichtigungen*, pro Konto und beliebig viele Ziele. + +### Anlässe + +| Anlass | Wann | +| --- | --- | +| ⚠️ **Abgleich fehlgeschlagen** | Das Git-System war nicht erreichbar oder hat abgelehnt | +| 🔴 **CI rot** | Der letzte Lauf auf dem Default-Branch ist fehlgeschlagen | +| 🏷️ **Neues Release** | Ein neuer Tag oder ein neues Release ist aufgetaucht | + +### Nur bei Änderung + +Der entscheidende Punkt: Gemeldet wird **ausschließlich, wenn sich etwas ändert** +gegenüber dem, worüber zuletzt benachrichtigt wurde. Ein Repository mit abgelaufenem +Token meldet sich also **einmal**, nicht bei jedem stündlichen Durchlauf. Wird der Fehler +ein anderer, kommt eine neue Meldung; ist er behoben und tritt später erneut auf, ebenso. + +Zwei Feinheiten: + +- **Beim allerersten Abgleich** eines Repositories wird kein Release gemeldet — sonst + wäre jede bereits vorhandene Version eine „Neuigkeit". +- Der Merkstand wird auch dann fortgeschrieben, wenn **kein** Ziel eingerichtet ist. Sonst + käme beim ersten eingerichteten Ziel die ganze Vergangenheit hinterher. + +### Zustellwege + +| Weg | Details | +| --- | --- | +| **ntfy** | Vollständige Themen-URL. Titel, Priorität und Symbol als Kopfzeilen; optionales Bearer-Token für geschützte Themen. Läuft auch gegen ein selbst gehostetes ntfy im eigenen Netz. | +| **Webhook** | JSON per POST mit Anlass, Titel, Text, Projektbezug und Tieflink. Mit hinterlegtem Geheimnis wird der Rumpf per HMAC-SHA256 signiert (`X-Nebula-Signature`), damit der Empfänger die Herkunft prüfen kann. | +| **E-Mail** | Über SMTP. Steht erst zur Verfügung, wenn `SMTP_HOST` in der `.env` gesetzt ist — sonst ist die Auswahl in der Oberfläche gesperrt und erklärt, warum. | + +Jedes Ziel lässt sich einzeln **testen** und **pausieren**; Ergebnis und Zeitpunkt der +letzten Zustellung stehen an der Karte. + +### Absicherung + +- Zieladressen sind Benutzereingaben, die der Server abruft — sie laufen deshalb durch + denselben **SSRF-Schutz** wie die Git-Verbindungen. Ein ntfy im eigenen Netz bleibt + erlaubt, Loopback und Cloud-Metadaten nicht. +- Redirects werden **nicht verfolgt**: ein Umleitungsziel wäre nicht geprüft. +- Geheimnisse liegen AES-256-GCM-verschlüsselt und verlassen den Server nie; die + Oberfläche zeigt nur einen Hinweis wie `test…mnis`. +- Eine fehlgeschlagene Zustellung lässt den Abgleich nicht scheitern — der Fehler landet + am Ziel, nicht im Sync. + +--- + ## 5. Oberfläche - **Dark Glassmorphism**: milchige Glasflächen, driftende Nebel im Hintergrund, feines @@ -349,6 +399,7 @@ Dateneinsicht. Ein Administrator sieht dadurch weiterhin keine fremden Projekte. | `AppSettings` | Betriebsart und Registrierung (eine Zeile) | | `Project` | Projektstammdaten, Status, Fortschritt, Repo-Verknüpfung, Besitzer | | `Task` | Aufgaben mit Spalte, Position, Fälligkeit und Labels | +| `NotificationTarget` | Zustellwege je Konto, mit verschlüsseltem Geheimnis | | `Note` | Notizen (n:1 zu Projekt, Cascade-Delete) | | `GitConnection` | Git-Systeme inkl. verschlüsseltem Token, Besitzer | | `RepoSnapshot` | Zwischengespeicherter Repo-Stand (1:1 zu Projekt) | @@ -414,6 +465,7 @@ Datenbank): | `security/ssrf` | Sperrlisten für Loopback, Link-Local, Metadaten-Endpunkt, CGNAT und Multicast; Formprüfung; die drei Umgebungsschalter | | `auth/password` | scrypt-Rundlauf, unterschiedliches Salz, Ablageformat, Verhalten bei kaputten Hashes, Passwortregeln | | `tasks` | Berechnung des abgeleiteten Fortschritts, Spaltenreihenfolge | +| `notify/pending` | Wann eine Benachrichtigung fällig ist: neuer Fehler ja, gleicher Fehler nein; CI-Wechsel nach rot; Release erst ab dem zweiten Abgleich | **`npm run test:e2e` — Ende-zu-Ende** (Playwright, 13 Stück, rund 20 Sekunden). Läuft gegen die gebaute Anwendung mit eigener Datenbank auf Port 3111 — also durch Middleware, diff --git a/README.md b/README.md index 73d6991..865f33f 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ Alle Variablen sind in [`.env.example`](.env.example) dokumentiert. | `GIT_ALLOW_LOOPBACK` | Git-Instanz auf demselben Rechner erlauben | `false` | | `GIT_BLOCK_PRIVATE` | Zugriff ins private LAN sperren | `false` | | `GIT_ALLOWED_HOSTS` | Strengster Modus: nur diese Hosts erlauben | leer | +| `NOTIFY_TIMEOUT_MS` | Timeout beim Zustellen von Benachrichtigungen | `10000` | +| `SMTP_HOST` … `SMTP_FROM` | SMTP-Zugang; ohne `SMTP_HOST` steht der Weg „E-Mail" nicht zur Verfügung | leer | `APP_NAME`, `APP_TAGLINE` und `APP_URL` werden **zur Laufzeit** gelesen. Dasselbe Docker-Image lässt sich also ohne Neubau umbenennen oder unter einer anderen Adresse @@ -338,6 +340,9 @@ ausschließlich Daten des angemeldeten Kontos. | `POST` | `/api/projects/import` | Import, mit `dryRun` für die Vorschau | | `GET` | `/api/admin/backup` | Vollständige Sicherung als JSON (Admin) | | `GET` | `/api/search?q=` | Volltextsuche über die eigenen Notizen | +| `GET` `POST` | `/api/notifications` | Benachrichtigungsziele | +| `PATCH` `DELETE` | `/api/notifications/:id` | Ziel ändern / entfernen | +| `POST` | `/api/notifications/:id/test` | Testnachricht senden | | `GET` `PATCH` `DELETE` | `/api/projects/:id` | Projekt lesen / ändern / löschen | | `POST` | `/api/projects/:id/sync` | Repo-Daten vom Git-System holen | | `GET` `POST` | `/api/projects/:id/tasks` | Aufgaben des Projekts | diff --git a/ROADMAP.md b/ROADMAP.md index 0d52707..a82b247 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,31 +11,30 @@ Was bereits steht, listet [FEATURES.md](FEATURES.md). ## 1 · Als Nächstes empfohlen -### 1.1 Benachrichtigungen `M` +### 1.1 Stillstehende Projekte melden `S` -Bei Sync-Fehlern, roter CI, neuen Releases oder Projekten, die lange -stillstehen. Kanäle: E-Mail (SMTP), ntfy, generischer Webhook. Zusammen mit -dem automatischen Abgleich und dem CI-Status ergibt das ein brauchbares -Frühwarnsystem — die Daten liegen bereits vor. +Die Benachrichtigungen decken Fehler und Releases ab. Was fehlt, ist der +sanfte Stups: „Seit 60 Tagen kein Commit". Braucht einen eigenen Durchlauf, +weil es kein Ereignis gibt, an dem es hängt — der Zeitgeber ist da. ---- - -## 2 · Danach - -### 2.1 Webhooks statt Polling `M` +### 1.2 Webhooks statt Polling `M` Ein Push aktualisiert den Snapshot sofort, statt bis zum nächsten Durchlauf zu warten. Braucht einen öffentlich erreichbaren Endpunkt mit Signaturprüfung je Provider — sinnvoll erst, wenn die Instanz von außen erreichbar ist. Der Zeitplan bleibt als Rückfalllösung. -### 2.2 Commit-Aktivität als Heatmap `S` +--- + +## 2 · Danach + +### 2.1 Commit-Aktivität als Heatmap `S` Die Sparkline gibt es auf Karte und Projektseite. Eine Heatmap über ein Jahr wäre die nächste Ausbaustufe — dafür braucht es allerdings mehr als eine Seite Commits, also Paginierung oder die Statistik-Endpunkte. -### 2.3 Kanban mit freier Reihenfolge `M` +### 2.2 Kanban mit freier Reihenfolge `M` Heute folgt die Reihenfolge innerhalb einer Spalte der gewählten Sortierung. Frei sortieren hieße, eine Position je Projekt zu speichern. @@ -117,6 +116,7 @@ Damit klar ist, was **nicht** kommt und warum: | **v0.4.0** Kanban mit Drag & Drop, CI-Status je Branch, Commit-Aktivität | ✓ | | **v0.5.0** Aufgabenbrett je Projekt mit sortierbaren Spalten, Fortschritt aus Aufgaben | ✓ | | **v0.6.0** Ende-zu-Ende-Tests mit Playwright, CI-Pipeline für Gitea Actions | ✓ | +| **v0.7.0** Benachrichtigungen über ntfy, Webhook und E-Mail | ✓ | Zur Entscheidung beim Export: Git-Tokens gehen **nicht** mit. Die Verbindung wird nur beschrieben, das Token ist nach dem Import neu zu hinterlegen. Snapshots bleiben diff --git a/docker-compose.yml b/docker-compose.yml index 3b119b5..622ea16 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,6 +59,13 @@ services: APP_URL: ${APP_URL:-http://localhost:3000} SESSION_TTL_DAYS: ${SESSION_TTL_DAYS:-30} GIT_API_TIMEOUT_MS: ${GIT_API_TIMEOUT_MS:-15000} + NOTIFY_TIMEOUT_MS: ${NOTIFY_TIMEOUT_MS:-10000} + SMTP_HOST: ${SMTP_HOST:-} + SMTP_PORT: ${SMTP_PORT:-587} + SMTP_SECURE: ${SMTP_SECURE:-} + SMTP_USER: ${SMTP_USER:-} + SMTP_PASSWORD: ${SMTP_PASSWORD:-} + SMTP_FROM: ${SMTP_FROM:-} GIT_ALLOW_LOOPBACK: ${GIT_ALLOW_LOOPBACK:-false} GIT_BLOCK_PRIVATE: ${GIT_BLOCK_PRIVATE:-false} GIT_ALLOWED_HOSTS: ${GIT_ALLOWED_HOSTS:-}