init
This commit is contained in:
+171
@@ -0,0 +1,171 @@
|
||||
# Betrieb, Provider und Zustellgarantien
|
||||
|
||||
## Start und Upgrade
|
||||
|
||||
```sh
|
||||
go run ./cmd/gateway -config ./data/config.json
|
||||
```
|
||||
|
||||
Die Konfiguration bleibt JSON-Schema 1. Der Server legt `outbox.db` neben der
|
||||
Konfiguration an. Ein anderer Pfad lässt sich mit `-outbox /pfad/outbox.db` setzen.
|
||||
SQLite arbeitet ohne CGO. Das Verzeichnis muss schreibbar sein; dort entstehen
|
||||
auch `outbox.db-wal` und `outbox.db-shm`.
|
||||
|
||||
**Verhaltensänderung:** HTTP-Eingänge antworten nach persistenter Annahme mit
|
||||
**202**, bevor ein Ziel kontaktiert wird:
|
||||
|
||||
```json
|
||||
{"ok":true,"receipt":{"id":"...","duplicate":false}}
|
||||
```
|
||||
|
||||
Die Zustellhistorie zeigt den späteren Status. Speicherfehler führen zu 503,
|
||||
ungültige Nachrichten/fehlende Regeln zu 400. Vorschauen versenden und speichern
|
||||
keine Zustellungen. Verarbeitete Dry-Run-Aufträge erscheinen als `dry_run`.
|
||||
|
||||
## Deduplizierung und Retry
|
||||
|
||||
HTTP-Sender sollten pro logischer Nachricht einen stabilen Schlüssel setzen:
|
||||
|
||||
```sh
|
||||
curl -X POST http://localhost:8080/in/webhook/default \
|
||||
-H 'Authorization: Bearer replace-me' \
|
||||
-H 'Idempotency-Key: monitoring-event-123' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"title":"Server","message":"Dienst ausgefallen"}'
|
||||
```
|
||||
|
||||
- Der Schlüssel gilt für Quelle und Kanal, maximal 256 Bytes. Derselbe Schlüssel
|
||||
und Inhalt liefern dieselbe Referenz mit `duplicate:true`. Anderer Inhalt mit
|
||||
demselben Schlüssel ergibt 409. Empfangszeitpunkte zählen nicht zum Inhalt.
|
||||
- Ohne Schlüssel ist jede HTTP-Anfrage eine neue Nachricht. Gleicher Text allein
|
||||
löst keine Deduplizierung aus: gleiche Meldungen können neue Ereignisse sein.
|
||||
- Alle passenden Regeln werden in einer Transaktion angenommen. Wenn eine kein
|
||||
gültiges Payload erzeugt, wird keine Teilmenge gespeichert.
|
||||
- Jeder Ausgang hat einen eigenen Auftrag. Erfolgreiche Aufträge werden nicht
|
||||
wegen des Fehlers eines anderen Ziels wiederholt.
|
||||
- HTTP-Verbindungsfehler, 408/425/429/5xx sowie temporäre SMTP-4xx-Fehler werden
|
||||
wiederholt: maximal acht Versuche, zunächst etwa fünf Sekunden Wartezeit,
|
||||
danach exponentiell mit Zufallsanteil. `Retry-After` wird bis 24 Stunden beachtet.
|
||||
- Nach dauerhaften Fehlern oder acht Fehlversuchen bleibt der Auftrag `dead`.
|
||||
**Erneut versuchen** startet einen neuen Versuchslauf desselben Auftrags mit
|
||||
ursprünglichem Ziel/Inhalt. Die vorherigen Versuche bleiben in der Historie.
|
||||
- Nach einem Absturz wird ein verwaister Versand nach Ablauf seiner
|
||||
zehnminütigen Reservierung wieder verfügbar. Regulärer Shutdown beendet
|
||||
laufende Netzwerkaufrufe und speichert deren Ergebnisse.
|
||||
|
||||
HTTP-Ausgänge senden `Idempotency-Key` und `X-Notify-Gateway-Delivery` mit der
|
||||
stabilen Delivery-ID. SMTP verwendet eine stabile `Message-ID`. Fremde Provider
|
||||
müssen diese Merkmale selbst zur Deduplizierung unterstützen. Ein Absturz nach
|
||||
erfolgreichem Versand, aber vor dem lokalen Erfolgs-Commit, kann daher eine
|
||||
Doppelzustellung verursachen. Es gibt keine Exactly-once-Garantie.
|
||||
|
||||
Ein Worker arbeitet sequenziell. Ein langsames Ziel verzögert andere Aufträge;
|
||||
Provider-Timeouts begrenzen die Dauer. Durchsatztests und konfigurierbare
|
||||
Parallelität für größere Installationen stehen noch aus.
|
||||
|
||||
## Ausgangsprovider
|
||||
|
||||
Unter **Ausgänge** Verbindungen anlegen und unter **Zuordnungen** auswählen.
|
||||
Neue Verbindungen verwenden `live:false`; Divera247 hat weiterhin `dry_run:true`.
|
||||
Die Live-Einstellung wird beim Annehmen gespeichert. Das spätere Abschalten
|
||||
eines Ausgangs hält bereits angenommene Live-Aufträge nicht an.
|
||||
|
||||
| Provider | Einstellungen |
|
||||
|---|---|
|
||||
| Discord | `https://discord.com/api/webhooks/ID/TOKEN`; Titel und Meldung, keine automatischen Mentions, maximal 2000 UTF-16-Codeeinheiten |
|
||||
| HTTP-Webhook | Vollständige HTTP(S)-URL, optionaler Bearer-Token; POST mit gemappten Nachrichtenfeldern |
|
||||
| ntfy | Server-Basis-URL, Topic, optionaler Bearer-Token; JSON-Publish, positive Priorität auf maximal 5 begrenzt |
|
||||
| Gotify | Vollständiger `/message`-Endpunkt und App-Token; `X-Gotify-Key`, Titel/Meldung/Priorität |
|
||||
| SMTP | Host, Port, `tls` (typisch 465) oder `starttls` (typisch 587), optional Benutzer/Passwort, reine Absender-/Empfängeradressen |
|
||||
|
||||
SMTP verlangt eine gültige Zertifikatskette. `AUTH PLAIN` wird ausschließlich
|
||||
nach TLS verwendet. Mail-Inhalte sind UTF-8-Plain-Text als Base64-MIME; keine Anhänge.
|
||||
|
||||
Quellen: [ntfy JSON-Publish](https://docs.ntfy.sh/publish/#publish-as-json),
|
||||
[Gotify Nachrichten](https://gotify.net/docs/pushmsg),
|
||||
[Discord Webhooks](https://docs.discord.com/developers/resources/webhook#execute-webhook).
|
||||
|
||||
## IMAP-Eingang
|
||||
|
||||
Unter **Eingänge** einen Mail-Eingang einrichten: eindeutige ID, TLS-Adresse
|
||||
wie `imap.example.org:993`, Benutzer, Passwort, Ordner und Zuordnungskanal.
|
||||
Regeln verwenden `source: "mail"` und diesen Kanal.
|
||||
|
||||
- Passwort- oder App-Passwort-Anmeldung; OAuth2 ist noch nicht implementiert.
|
||||
- Der Ordner wird schreibgeschützt geöffnet; Nachrichten werden weder gelöscht
|
||||
noch als gelesen markiert. Checkpoints liegen in SQLite.
|
||||
- Beim ersten erfolgreichen Abruf wird standardmäßig der aktuelle UID-Stand
|
||||
übernommen. **Vorhandene Nachrichten importieren** muss vor diesem ersten
|
||||
Abruf gesetzt sein, wenn alte Nachrichten verarbeitet werden sollen.
|
||||
- UIDVALIDITY-Wechsel setzt den Cursor zurück. Der neue UID-Namensraum wird
|
||||
erneut verarbeitet; alte Inhalte können dadurch erneut auftreten. Wechsel von
|
||||
Adresse/Benutzer/Ordner/Eingangs-ID erzeugt einen neuen Checkpoint und wendet
|
||||
die Erstimport-Einstellung erneut an.
|
||||
- Ein Abruf verarbeitet bis zu 100 neue Nachrichten. Checkpoints werden erst
|
||||
nach Outbox-Annahme oder bewusster Filterung fortgeschrieben.
|
||||
- Maximal 1 MiB Rohmail und dekodierter Text. Plain-Text-MIME wird verwendet;
|
||||
Anhänge werden ignoriert. HTML-only, fehlerhafte/zu große Nachrichten oder ein
|
||||
fehlendes Mapping blockieren den Checkpoint. Die Zustellhistorie zeigt den
|
||||
Abruffehler. Nachricht manuell verschieben oder eine passende Regel ergänzen.
|
||||
- Absender-/Empfängerfilter vergleichen Adressen ohne Beachtung der
|
||||
Großschreibung. Header beweisen keine Absenderidentität; diese Prüfung gehört
|
||||
zusätzlich auf den empfangenden Mailserver.
|
||||
- Eigene Gateway-Mails und `Auto-Submitted`-Nachrichten werden übersprungen,
|
||||
um Rückkopplungen zu vermeiden. HTTP-Rückläufer mit Gateway-Delivery-Header
|
||||
werden ebenfalls abgelehnt.
|
||||
|
||||
## Discord-Eingang
|
||||
|
||||
1. Discord-Anwendung anlegen, Public Key und Application-ID übernehmen.
|
||||
2. Unter **Eingänge** Command-Name, erlaubte Guild-IDs und Channel-IDs setzen.
|
||||
3. Slash-Command entsprechend [discord-command.example.json](discord-command.example.json)
|
||||
bei der Anwendung registrieren; der Name muss zur Konfiguration passen.
|
||||
4. Als Interactions Endpoint die öffentliche HTTPS-Adresse `/in/discord` setzen.
|
||||
5. Regel mit `source: "discord"` und Channel-ID als Kanal erstellen.
|
||||
|
||||
PING und Commands werden mit Ed25519 geprüft; Zeitstempel dürfen maximal fünf
|
||||
Minuten abweichen. Application-ID und beide Allowlists müssen passen.
|
||||
`message` ist erforderlich, `title` und `priority` optional. Die private Antwort
|
||||
bestätigt die Speicherung, nicht den Versand. Es erfolgt kein Mithören beliebiger
|
||||
Channel-Nachrichten und keine automatische Registrierung externer Anwendungen.
|
||||
Siehe [Discord Interactions](https://docs.discord.com/developers/interactions/receiving-and-responding).
|
||||
|
||||
## Historie, Metriken und Sicherheit
|
||||
|
||||
- **Zustellhistorie**: Seiten zu 50 Aufträgen, Status, Referenz, Fehlerklasse und
|
||||
letzte 200 Versuchsereignisse pro Auftrag. Keine Bodies oder Zugangsdaten.
|
||||
- `/healthz`: Prozess erreichbar. `/readyz`: SQLite erreichbar; keine Aussage zur
|
||||
Erreichbarkeit aller Provider.
|
||||
- `/metrics`: Prometheus-Gauge `notify_gateway_deliveries{state="..."}`.
|
||||
Zugriff mit `Authorization: Bearer ...`, wenn `GATEWAY_METRICS_TOKEN` gesetzt ist;
|
||||
sonst nur mit Admin-Session.
|
||||
- Schreibende Admin-APIs/Formulare benötigen einen Token aus `GET /ui/api/csrf`
|
||||
(`X-CSRF-Token` bzw. `csrf_token`). Die WebUI erledigt das automatisch.
|
||||
Zusätzlich werden fremde Origins abgelehnt.
|
||||
- Neue Passwort-Hashes verwenden bcrypt. SHA-256-Hashes werden beim erfolgreichen
|
||||
Login migriert; alte Passwörter über 72 Bytes bleiben bis zum Wechsel im
|
||||
Legacy-Format. Neue Passwörter: 10 bis 72 Bytes. Wechsel widerruft alle Sessions.
|
||||
- Login-Limit: zehn Versuche pro Minute und direkter Client-IP. Proxy-Header
|
||||
werden nicht vertraut; hinter einem Proxy eigenes Rate-Limit verwenden und
|
||||
den originalen Host erhalten.
|
||||
- Request-Limits: 1 MiB für Eingänge, 2 MiB für Admin-Aufrufe. CSP erlaubt das
|
||||
eingebettete UI-Skript per Hash; Admin-Antworten verwenden `no-store`.
|
||||
- Bearer-Token, SMTP-/IMAP-Passwort und Divera-Accesskey akzeptieren `env:VARIABLE`.
|
||||
Fehlende Variablen führen zu Fehlern. Outbox-Snapshots behalten die Referenz;
|
||||
ein später geänderter Variablenwert wird beim Versand verwendet.
|
||||
|
||||
Konfiguration und Outbox enthalten sensible Daten; direkte Secrets und Inhalte
|
||||
werden nicht verschlüsselt. POSIX-Dateimodus ist `0600`, unter Windows gelten
|
||||
zusätzlich die geerbten ACLs. Verzeichniszugriff und Backups entsprechend schützen.
|
||||
HTTPS über einen Reverse Proxy bleibt erforderlich.
|
||||
|
||||
## Backup und Aufbewahrung
|
||||
|
||||
Für ein konsistentes einfaches Backup Gateway regulär stoppen und das ganze
|
||||
Datenverzeichnis einschließlich Konfiguration, Datenbank und gegebenenfalls
|
||||
WAL/SHM-Dateien sichern. Nur die laufende `outbox.db` zu kopieren genügt nicht.
|
||||
Wiederherstellung bei gestopptem Gateway, anschließend starten.
|
||||
|
||||
Historie und Deduplizierungsbelege werden unbegrenzt aufbewahrt; automatische
|
||||
Bereinigung fehlt noch. Größe überwachen. Tabellen nicht manuell löschen:
|
||||
Belege und Checkpoints sind für Wiederanlauf und Deduplizierung nötig.
|
||||
Reference in New Issue
Block a user