Der Synapse-Stack: Matrix-Homeserver, Messenger-Brücken und ein LLM-Bot
Im ersten Post habe ich kurz beschrieben, welche Dienste in meinem Stack laufen. Heute geht es tiefer in den Synapse-Stack – meinen Matrix-Homeserver inklusive drei Messenger-Brücken und einem KI-Chatbot.
Warum Matrix?
Matrix ist ein offenes, föderiertes Kommunikationsprotokoll. „Föderiert" bedeutet: Jeder kann seinen eigenen Homeserver betreiben, und Nutzer verschiedener Server können miteinander kommunizieren – ähnlich wie bei E-Mail, aber für Instant Messaging.
Der entscheidende Vorteil für mich: Matrix lässt sich mit anderen Messengern brücken. Ich empfange und sende Signal-, Telegram- und WhatsApp-Nachrichten aus einem einzigen Client heraus, ohne eine dieser Apps installiert zu haben.
Überblick: Was läuft alles im Stack?
|
|
Insgesamt laufen elf Container in diesem Stack. Jede Brücke hat eine eigene PostgreSQL-Instanz – bewusst, um Isolation zu gewährleisten und ein einzelnes Datenbankproblem nicht den gesamten Stack lahmzulegen.
Synapse: Der Homeserver
Synapse ist die Referenzimplementierung des Matrix-Protokolls in Python. Er lauscht auf zwei Ports:
:8008– HTTP für Client-Anfragen und Federation, hinter Traefik:8448– HTTPS direkt (TLS) für Matrix-Federation, direkt öffentlich erreichbar
Federation auf Port 8448 muss direkt erreichbar sein, weil andere Matrix-Homeserver beim Zustellen von Nachrichten diesen Port direkt ansteuern.
Daneben exportiert Synapse auf Port 9000 Prometheus-Metriken, die vom Monitoring-Stack abgeholt werden.
App Services: Wie Brücken eingebunden werden
Jede Brücke registriert sich bei Synapse als sogenannter App Service (Anwendungsdienst).
Das funktioniert über eine registration.yaml-Datei, die die Brücke generiert und die
Synapse kennen muss. In der homeserver.yaml sind alle drei Registrierungen eingetragen:
|
|
Das Besondere: Diese Registrierungsdateien werden nicht manuell gepflegt.
Stattdessen gibt es im Makefile ein make update-mautrix-registration-Target,
das die von den Brücken generierten Dateien aus den Docker-Volumes in das Synapse-Datenverzeichnis
kopiert. Nach Änderungen muss der Stack neu deployed werden.
Die Messenger-Brücken
Alle drei Brücken stammen aus dem Mautrix-Ökosystem und folgen demselben Architekturmuster: Sie verbinden sich als App Service mit Synapse und übersetzen Nachrichten bidirektional zwischen Matrix-Räumen und dem jeweiligen Messenger.
In der Praxis bedeutet das: Für jeden Signal-Kontakt, jede Telegram-Gruppe und jeden WhatsApp-Chat erstellt die Brücke automatisch einen Matrix-Raum. Nachrichten, die ich in diesem Raum schreibe, landen beim Empfänger im jeweiligen Messenger – und umgekehrt.
Signal: Die komplexeste Brücke
Signal ist die technisch anspruchsvollste Brücke, weil Signal kein offizielles API hat. Der Stack löst das mit einem zusätzlichen Dienst: signald.
|
|
Signald ist ein separater Go-Daemon, der das Signal-Protokoll implementiert.
Er hält den eigentlichen Gerätezustand (Schlüsselmaterial, Sitzungen) in seinem Volume.
mautrix-signal kommuniziert mit signald über einen internen Socket und überlässt
ihm die gesamte kryptografische Arbeit.
Der Vorteil dieser Trennung: mautrix-signal kann neu gestartet oder aktualisiert
werden, ohne die Signal-Verbindung zu verlieren – der Gerätezustand liegt in signald.
Telegram und WhatsApp
Die Telegram- und WhatsApp-Brücken kommen ohne zusätzliche Daemons aus, weil beide Plattformen mit offiziellen oder zumindest dokumentierten Protokollen arbeiten.
Die Telegram-Brücke nutzt die offizielle MTProto-API (mit eigener api_id und api_hash
aus einer registrierten Telegram-App). Die WhatsApp-Brücke implementiert das
WhatsApp-Web-Protokoll, das technisch gesehen die Mobile-App über einen QR-Code
verknüpft.
Berechtigungsmodell der Brücken
Alle drei Brücken nutzen dasselbe dreistufige Berechtigungsmodell:
| Ebene | Wer | Rechte |
|---|---|---|
relay |
Beliebige Matrix-User | Nachrichten über die Brücke weiterleiten |
user |
User auf matrix.bugtrack.eu |
Eigene Konten mit der Brücke verknüpfen |
admin |
@karsten:matrix.bugtrack.eu |
Voller Zugriff inkl. Verwaltung |
Baibot: Der KI-Chatbot
Baibot ist ein Matrix-Bot, der Large Language
Models in Matrix-Räume einbindet. Er läuft unter dem Benutzernamen @chad:matrix.bugtrack.eu.
Der Bot unterstützt zwei Backends gleichzeitig:
- OpenAI (GPT): Für Textgenerierung, Sprache-zu-Text (Whisper) und Text-zu-Sprache
- OpenRouter (Gemini): Als Alternative für andere Modelle und Anbieter
Beide API-Keys werden zur Laufzeit per Docker Secret injiziert – dazu gleich mehr.
Das Besondere an baibots Sicherheitskonfiguration: Der Container läuft mit
einem vollständig gesperrten Dateisystem (read_only: true) und ohne jegliche
Linux-Capabilities (cap_drop: ALL). Das zwingt zu einer interessanten Lösung
bei der Konfigurationsverwaltung, die ich im nächsten Abschnitt erkläre.
Konfigurationsmanagement: Templates und Secrets
Das größte Betriebsproblem bei einem Stack dieser Größe: Wie verwaltet man Konfigurationen mit sensiblen Werten so, dass sie im Git-Repository liegen können?
Das Template-Muster
Die Lösung ist einfach: Konfigurationsdateien liegen als Templates im Repository, mit Platzhaltern statt echter Werte:
|
|
Beim Containerstart liest ein kleines Entrypoint-Skript das Docker Secret und ersetzt den Platzhalter:
|
|
Die fertige Konfiguration mit den echten Werten entsteht nur im Arbeitsspeicher des Containers – sie wird nie gespeichert oder geloggt.
URL-Encoding: Ein häufig übersehenes Detail
PostgreSQL-Verbindungsstrings haben eine Besonderheit: Das Passwort muss
URL-encodiert sein. Ein Passwort mit Sonderzeichen wie @ oder # bricht
den Connection String ohne Encoding.
Das Entrypoint-Skript für die Brücken löst das in reinem Bash:
|
|
Baibots Sonderfall: /dev/shm statt /tmp
Weil baibot mit read_only: true läuft, kann das Entrypoint-Skript nicht in
/tmp schreiben. Die Lösung: /dev/shm – das Shared-Memory-Dateisystem,
das in Linux-Containern immer als beschreibbares tmpfs verfügbar ist.
|
|
Docker Secrets und Rotation
Alle Secrets folgen dem Namensschema <name>_YYYYMMDD – z.B.
synapse_db_pass_20260101. Das ermöglicht Rotation ohne Downtime:
|
|
Das Makefile hat dafür fertige Targets:
|
|
Konfigurations-Sync: Das tägliche Werkzeug
Da Bridges und Homeserver Konfigurationsdateien in Docker-Volumes lesen, müssen Änderungen an den Templates im Repository aktiv synchronisiert werden. Das Makefile bietet dafür drei hilfreiche Targets:
|
|
Damit ist der Workflow bei Config-Änderungen:
- Template im Repository anpassen
make diff-mautrix-configuration– prüfen, was sich ändertmake install-mautrix-configuration– Änderungen einspielenmake start– Stack neu deployen
Netzwerkarchitektur
Der Stack nutzt drei Netzwerke:
|
|
Das interne synapse-network ist ein Docker-Overlay-Netzwerk. Dienste finden sich
gegenseitig per Hostname (z.B. synapse-db, mautrix-signal-db). Von außen ist
nichts von diesem Netzwerk erreichbar.
Fazit
Der Synapse-Stack ist mit Abstand der komplexeste Teil meiner Infrastruktur – elf Container, vier Datenbanken, drei externe Ökosysteme (Signal, Telegram, WhatsApp) und ein LLM-Backend.
Was den Betrieb handhabbar macht:
- Konsequente Trennung von Template und Konfiguration
- Docker Secrets für alle sensiblen Werte
- Separate Datenbanken pro Bridge für Isolation
- Makefile-Targets für wiederkehrende Aufgaben
Im nächsten Post schaue ich mir den Nextcloud-Stack an – der ist strukturell ähnlich komplex, aber mit ganz anderen Herausforderungen. +++