Files
2026-09-16 06:26:16 +02:00

172 lines
9.5 KiB
Markdown

# 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.