Ich betreibe einen eigenen
Matrix
-Homeserver —
Synapse
, erreichbar unter matrix.this-is-fine.social. Der laufende Betrieb ist erfreulich langweilig; ein Aspekt aber hat mich mehr Nerven gekostet als alles andere zusammen: die Ende-zu-Ende-Verschlüsselung, und zwar ausgerechnet für meinen automatisierten Teilnehmer, den Bot ZeroClaw. Dieser Beitrag handelt von diesem Schmerz — und dem Tooling, das ich bauen musste, um ihn zu zähmen.
Der Homeserver
Der unkomplizierte Teil zuerst. Synapse läuft als Helm-Release auf hydra, mit einer CloudNativePG-Datenbank (synapse-v1) als Backend. Nach außen teilt er sich den Host mit meiner Fediverse-Präsenz: Dieselbe Domain bedient über das Envoy-Gateway sowohl snac als auch Synapse — die /_matrix/- und /.well-known/matrix/-Pfade werden an den Homeserver geroutet, der Rest geht ans Fediverse.
serverName: "${SOCIAL_DOMAIN}"
public_baseurl: "https://matrix.${SOCIAL_DOMAIN}/"
externalPostgresql:
host: synapse-v1-rw
database: synapse
Das alles tut, was es soll, und meldet sich selten zu Wort. Interessant wird es erst bei der Verschlüsselung.
Warum E2EE für einen Bot wehtut
Ende-zu-Ende-Verschlüsselung ist für Menschen ein Segen und für einen langlebigen, automatisierten Account eine Quelle subtiler Schmerzen. Der Grund liegt in der Natur des Krypto-Zustands: Geräte-Identität, One-Time-Keys und Cross-Signing-Schlüssel sind gerätegebunden und müssen lokal und serverseitig konsistent bleiben.
ZeroClaw nutzt das
matrix-rust-sdk
, dessen Krypto-Store auf genau ein Gerät ausgelegt ist und annimmt, dass der lokale Zustand mit dem Homeserver synchron bleibt. Dieser Store liegt auf dem PVC des Pods unter state/matrix. Und hier beginnt das Drama: Wird dieser Store gelöscht — sei es durch einen bewussten PVC-Wipe oder einen Unfall —, während die One-Time-Keys des alten Geräts serverseitig noch registriert sind, kann das SDK keine neuen Keys für dieselbe device_id mehr hochladen. Der Server setzt ein dauerhaftes OneTimeKeyAlreadyUploaded-Flag, und der Bot dreht sich im Kreis:
Matrix one-time key upload conflict detected; stopping sync to avoid infinite retry loop
Im Klartext: Der Matrix-Kanal verstummt. ZeroClaw kann nichts mehr entschlüsseln und nichts mehr senden. Der reflexhafte „dann lösch ich den State halt und logge neu ein" macht es nur schlimmer, solange dieselbe device_id wiederverwendet wird — man läuft direkt erneut in den Konflikt.
device_id ist im Normalbetrieb heilig und muss stabil bleiben. Ein voller PVC-Wipe ist kein „dann startet er halt neu", sondern ein geplantes Ereignis, dem zwingend eine Recovery mit einer frischen device_id folgen muss.Das Tooling: .taskfiles/matrix/
Diesen Recovery-Tanz von Hand über die Matrix-Client-API zu machen, ist fehleranfällig und im Stress genau das, was man nicht will. Also habe ich ihn in eine Handvoll Tasks gegossen. Der entscheidende Kniff: Alle Tasks nutzen den eigenen Access-Token des Bots aus dem SOPS-Secret — kein Synapse-Admin-Token nötig, sie fassen nur den @claw-Account an.
Der schlanke Diagnose-Teil verschafft erst einmal Überblick:
task matrix:bot-info # Homeserver / User / aktive device_id aus SOPS
task matrix:bot-devices # alle Geräte des Bots über die Client-API auflisten
Das Herzstück ist recover-e2ee. Es meldet den Bot per Passwort-Login mit einer frisch generierten device_id an, umgeht so den One-Time-Key-Konflikt und schreibt den neuen Token samt Geräte-ID zurück ins SOPS-Secret:
task matrix:recover-e2ee # neue device_id + Token, SOPS-Secret wird neu geschrieben
Weil das Secret Flux-verwaltet ist, schließt sich der Kreis über GitOps: committen, Flux reconcilen lassen, Pod neu starten. Der gesamte Ablauf:
Der Stolperstein nach der Recovery
Eine Recovery hinterlässt das alte Gerät serverseitig — und wer recover-e2ee mehrfach laufen lässt, stapelt tote Geräte auf. Das ist nicht kosmetisch: Mehrere parallele Bot-Geräte produzieren InvalidSignature-Fehler im Olm-Protokoll und können die Auslieferung von Room-Keys blockieren, bis der eigene Client nur noch „Waiting for this message" anzeigt. Deshalb gibt es prune-bot-devices, das alle Geräte bis auf das aktive löscht — abgesichert über die User-Interactive-Auth der Matrix-API mit dem Bot-Passwort:
task matrix:prune-bot-devices # behält nur MATRIX_DEVICE_ID, löscht den Rest
Fazit
Einen Matrix-Homeserver zu betreiben ist einfach; einen verschlüsselten Bot-Account über Pod-Neustarts und PVC-Wipes hinweg gesund zu halten, ist die eigentliche Kunst. Die Erkenntnis war, dass E2EE-Recovery kein manueller Notfalleinsatz sein darf, sondern ein deklarativer, wiederholbarer Vorgang — drei Tasks, die den Bot in Minuten statt in einer durchwachten Nacht wieder sprechen lassen. Genau dafür ist Tooling da: damit der seltene, komplizierte Fall trotzdem langweilig bleibt.
