|
|
||
|---|---|---|
| .env.example | ||
| chaoschemnitz_bot.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| dockerignore | ||
| env.example | ||
| README.md | ||
| requirements.txt | ||
🤖 Chaostreff Chemnitz – Space Status Bot
Ein Matrix-Bot der die SpaceAPI des Chaostreff Chemnitz überwacht und bei Änderung des Öffnungsstatus automatisch eine Nachricht in einen End-to-End-verschlüsselten Matrix-Kanal sendet. Zusätzlich werden bevorstehende Termine aus dem iCal-Kalender angekündigt.
Inhaltsverzeichnis
- Features
- Voraussetzungen
- Projektstruktur
- Installation & Quickstart
- Konfiguration
- Passwort-Management
- Commands
- Architektur
- Betrieb & Wartung
- Troubleshooting
- Sicherheit
Features
- Automatische Statusbenachrichtigung – prüft die SpaceAPI im konfigurierbaren Intervall und sendet bei Statuswechsel eine Nachricht
- iCal-Terminbenachrichtigungen – kündigt bevorstehende Kalendertermine konfigurierbare Zeit im Voraus an
- Konfigurierbare Nachrichten – alle Texte (Öffnung, Schließung, Termine, Stats) per Env-Variable anpassbar
- E2EE – alle Nachrichten und Dateianhänge werden Ende-zu-Ende-verschlüsselt über
matrix-niogesendet - Raum-Alias-Auflösung – akzeptiert sowohl
#alias:serverals auch!id:server - Startup-Nachricht – beim Start wird der aktuelle Status in den Raum gemeldet (deaktivierbar)
- Bot-Commands – interaktive Befehle für alle User und privilegierte Debug-Befehle für Moderatoren/Admins
- Rate-Limiting – verhindert Missbrauch durch zu schnell aufeinanderfolgende Commands
- Docker-Ready – Multi-Stage-Build, Non-Root-User, Docker Secrets, persistente Volumes
Voraussetzungen
- Docker ≥ 24 und Docker Compose ≥ 2
- Ein Matrix-Bot-Account (z.B. auf
matrix.orgoder eigenem Homeserver) - Der Bot muss Mitglied des Zielraums sein
- Der Zielraum muss E2EE aktiviert haben
libolm3wird im Container automatisch installiert
Projektstruktur
.
├── chaoschemnitz_bot.py # Bot-Quellcode
├── Dockerfile # Multi-Stage Docker-Image
├── docker-compose.yml # Compose-Konfiguration mit Secrets & Volumes
├── requirements.txt # Python-Abhängigkeiten
├── .env.example # Vorlage für Umgebungsvariablen
├── .dockerignore # Verhindert .env im Image
└── README.md # Diese Datei
Installation & Quickstart
1. Dateien ablegen
mkdir chch-bot && cd chch-bot
# Alle Projektdateien hier ablegen
2. Konfigurationsdatei anlegen
cp .env.example .env
nano .env
3. Passwort-Secret anlegen (empfohlene Methode)
echo "DEIN_BOT_PASSWORT" > matrix_password.txt
chmod 600 matrix_password.txt
4. Bot starten
docker compose up -d --build
5. Logs prüfen
docker compose logs -f
Beim erfolgreichen Start erscheint im Matrix-Raum:
🤖 Chaostreff Chemnitz Space-Bot gestartet!
Aktueller Status: GESCHLOSSEN 🔴
Prüfintervall: 60s | Befehle: !ping, !status
E2EE-Hinweis beim ersten Start
Da der Bot ein neues Gerät registriert, erscheint er in Element als unbekanntes Gerät. Um Entschlüsselungsprobleme zu vermeiden:
- In Element den Bot-Account öffnen → Sicherheit → Sitzungen
- Die neue Bot-Sitzung als vertrauenswürdig markieren oder
- Im Raum auf „Trotzdem senden" klicken wenn Element nach unbekannten Geräten fragt
Nach dem ersten vollständigen Sync-Zyklus funktioniert die E2EE-Entschlüsselung automatisch.
Konfiguration
Alle Einstellungen werden über Umgebungsvariablen gesetzt. Die .env-Datei wird von Docker Compose automatisch geladen.
Matrix-Zugangsdaten
| Variable | Pflicht | Standard | Beschreibung |
|---|---|---|---|
MATRIX_HOMESERVER |
✅ | https://matrix.org |
URL des Matrix-Homeservers |
MATRIX_USER |
✅ | – | Matrix-ID des Bots, z.B. @bot:matrix.org |
MATRIX_PASSWORD |
✅* | – | Passwort des Bot-Accounts (*oder via Secret-Datei) |
MATRIX_PASSWORD_FILE |
✅* | – | Pfad zu einer Datei die das Passwort enthält |
MATRIX_ROOM_ALIAS |
✅ | #chaoschemnitz:matrix.org |
Raum-Alias oder Raum-ID des Zielkanals |
MATRIX_STORE_PATH |
– | /data/matrix_store |
Pfad für den E2EE-Key-Store |
Format der User-ID: Muss zwingend das Format
@name:server.tldhaben – der Bot validiert dies beim Start und gibt eine klare Fehlermeldung aus wenn das Format falsch ist.
Raum-ID ermitteln: In Element → Raumeinstellungen → Erweitert → Interne Raum-ID
Statusüberwachung & Nachrichten
| Variable | Standard | Beschreibung |
|---|---|---|
POLL_INTERVAL |
60 |
Prüfintervall der SpaceAPI in Sekunden |
STARTUP_MESSAGE_ENABLED |
true |
Startup-Nachricht beim Start senden (true/false) |
MSG_OPEN |
🟢 Chaostreff Chemnitz ist jetzt GEÖFFNET!\nDer Hackerspace hat aufgemacht – komm vorbei! 🎉 |
Nachrichtentext wenn der Space öffnet |
MSG_CLOSED |
🔴 Chaostreff Chemnitz ist jetzt GESCHLOSSEN.\nBis zum nächsten Mal! 👋 |
Nachrichtentext wenn der Space schließt |
\ninMSG_OPENundMSG_CLOSEDwird automatisch in einen Zeilenumbruch umgewandelt.
Statistik-Command
| Variable | Standard | Beschreibung |
|---|---|---|
STATS_URL |
https://mapall.space/heatmap/show.php?id=Chaostreff%20Chemnitz |
URL die !stats postet |
STATS_MESSAGE |
📊 Die Öffnungsstatistiken des Chaostreff Chemnitz findest du unter:\n{url} |
Nachrichtentext – {url} wird durch STATS_URL ersetzt |
{url}ist der Platzhalter für die URL,\nerzeugt einen Zeilenumbruch.
iCal-Terminbenachrichtigungen
| Variable | Standard | Beschreibung |
|---|---|---|
ICAL_ENABLED |
true |
iCal-Feature ein- oder ausschalten |
ICAL_URL |
https://chaoschemnitz.de/chch.ical |
URL des iCal-Feeds |
ICAL_NOTIFY_MINUTES_BEFORE |
60 |
Wie viele Minuten vor Terminbeginn soll benachrichtigt werden |
ICAL_POLL_INTERVAL |
300 |
Prüfintervall des iCal-Feeds in Sekunden |
ICAL_MSG_TEMPLATE |
📅 Terminhinweis: *{title}* beginnt in {minutes} Minuten!\n🕐 {start}{location_line} |
Nachrichtentemplate für Terminbenachrichtigungen |
Platzhalter in ICAL_MSG_TEMPLATE:
| Platzhalter | Inhalt |
|---|---|
{title} |
Titel des Termins |
{start} |
Startzeit, formatiert als TT.MM.YYYY HH:MM Uhr |
{minutes} |
Minuten bis zum Terminbeginn |
{location} |
Ort des Termins (leer wenn nicht gesetzt) |
{location_line} |
Ort mit vorangestelltem Zeilenumbruch und 📍, oder leer |
Doppelmeldungsschutz: Bereits gemeldete Termine werden in ical_notified.json gespeichert. Nach einem Neustart werden sie nicht erneut gesendet. Wiederkehrende Termine werden pro Datum separat getrackt.
Berechtigungen
| Variable | Standard | Beschreibung |
|---|---|---|
DEBUG_WHITELIST |
– | Kommagetrennte Matrix-User-IDs die !debug per DM verwenden dürfen, z.B. @alice:matrix.org,@bob:matrix.org |
Vollständiges .env-Beispiel
# Matrix-Zugangsdaten
MATRIX_HOMESERVER=https://matrix.org
MATRIX_USER=@chch-spacebot:matrix.org
MATRIX_ROOM_ALIAS=#chaostreff-chemnitz:matrix.org
# Prüfintervall SpaceAPI
POLL_INTERVAL=60
# Startup-Nachricht (true/false)
STARTUP_MESSAGE_ENABLED=true
# Statusnachrichten (\n = Zeilenumbruch)
MSG_OPEN=🟢 Chaostreff Chemnitz ist jetzt GEÖFFNET!\nKomm vorbei! 🎉
MSG_CLOSED=🔴 Chaostreff Chemnitz ist jetzt GESCHLOSSEN.\nBis zum nächsten Mal! 👋
# Statistik-Link
STATS_URL=https://mapall.space/heatmap/show.php?id=Chaostreff%20Chemnitz
STATS_MESSAGE=📊 Die Öffnungsstatistiken des Chaostreff Chemnitz findest du unter:\n{url}
# iCal-Terminbenachrichtigungen
ICAL_ENABLED=true
ICAL_URL=https://chaoschemnitz.de/chch.ical
ICAL_NOTIFY_MINUTES_BEFORE=60
ICAL_POLL_INTERVAL=300
# Whitelist für !debug per DM
DEBUG_WHITELIST=@admin:matrix.org
Passwort-Management
Das Passwort wird nie in Umgebungsvariablen des Docker-Images gespeichert. Es gibt zwei Methoden:
Methode A – Docker Secret (empfohlen)
Docker Secrets mounten die Datei unter /run/secrets/ in den Container und sind nicht über docker inspect sichtbar.
echo "DEIN_PASSWORT" > matrix_password.txt
chmod 600 matrix_password.txt
In docker-compose.yml ist dies bereits vorkonfiguriert:
environment:
MATRIX_PASSWORD_FILE: "/run/secrets/matrix_password"
secrets:
matrix_password:
file: ./matrix_password.txt
Methode B – Umgebungsvariable
Weniger sicher, da das Passwort über docker inspect auslesbar ist.
# docker-compose.yml – Kommentar entfernen:
environment:
MATRIX_PASSWORD: "${MATRIX_PASSWORD}"
# .env
MATRIX_PASSWORD=DEIN_PASSWORT
Commands
Öffentliche Commands (alle User)
| Command | Beschreibung | Beispielantwort |
|---|---|---|
!ping |
Verbindungstest mit aktuellem Status und Uptime | 🏓 Pong! Status: 🟢 Space ist GEÖFFNET | Uptime: 2h 14m 37s |
!status |
Aktuellen Öffnungsstatus live abfragen | 🟢 Chaostreff Chemnitz ist aktuell GEÖFFNET! |
!stats |
Statistik-Link posten | 📊 Die Öffnungsstatistiken ... https://mapall.space/... |
!events |
Termine der nächsten 7 Tage aus dem Kalender anzeigen | Liste mit Datum, Uhrzeit, Titel und Ort |
Commands unterliegen einem Rate-Limit von 10 Sekunden pro User. Zu schnell aufeinanderfolgende Aufrufe werden stillschweigend ignoriert.
Beispielausgabe von !events:
📅 Termine der nächsten 7 Tage:
• Mo, 24.03. 19:00 Uhr – Chaostreff 📍 Zwickauer Str. 28
• Di, 25.03. – Aktionstag (ganztägig)
• Sa, 29.03. 14:00 Uhr – Workshop: SDR
Ganztägige Events werden ohne Uhrzeit angezeigt.
Debug-Commands (nur per Direktnachricht + Whitelist)
!debug funktioniert ausschließlich per Direktnachricht an den Bot. Zusätzlich muss die sendende User-ID in DEBUG_WHITELIST eingetragen sein.
| Command | Beschreibung |
|---|---|
!debug restart |
Bot neu starten (Docker startet Container automatisch neu) |
!debug post_last_check |
Timestamp des letzten SpaceAPI-Abrufs anzeigen |
!debug logs |
Letzte 20 Log-Zeilen als verschlüsselte .txt-Datei senden |
!debug simulate_status_change |
Statuswechsel simulieren (aktuellen Status umkehren) |
!debug simulate_status_change open |
Öffnung simulieren |
!debug simulate_status_change closed |
Schließung simulieren |
!debug |
Hilfetext mit allen verfügbaren Optionen anzeigen |
Bei fehlendem Power-Level antwortet der Bot:
⛔ Keine Berechtigung. !debug ist nur für Moderatoren und Admins verfügbar.
Antworten auf
!debug-Commands erscheinen ebenfalls nur im DM-Raum. Simulierte Status- und Terminbenachrichtigungen werden weiterhin in den Hauptkanal gesendet.
Architektur
Der Bot besteht aus drei parallel laufenden asyncio-Tasks:
┌────────────────────────────────────────────────────────────────────┐
│ asyncio.gather() │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ sync_loop() │ │ poll_loop() │ │ ical_loop() │ │
│ │ │ │ │ │ │ │
│ │ client.sync() │ │ SpaceAPI alle │ │ iCal-Feed alle │ │
│ │ Long-Polling │ │ POLL_INTERVAL s │ │ ICAL_POLL_ │ │
│ │ 30s timeout │ │ │ │ INTERVAL s │ │
│ │ │ │ → Statusvergleich│ │ │ │
│ │ → Event-Callbacks│ │ → Benachrichtigung│ │ → Terminprüfung │ │
│ │ (Commands) │ │ │ │ → Benachrichtigung│ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
sync_loop – Long-Polling gegen den Matrix-Homeserver (timeout=30s). Der Server antwortet sofort wenn neue Events eintreffen. Bei einem ungültigen next_batch-Token (SyncError) wird der Token zurückgesetzt und ein frischer Sync gestartet. Beim Start werden die E2EE-Keys aller Raum-Mitglieder via keys_query abgerufen; bei Geräteänderungen erfolgt eine automatische Aktualisierung.
poll_loop – ruft die SpaceAPI im einstellbaren Intervall ab, vergleicht state.open mit dem letzten bekannten Wert und sendet bei Änderung den konfigurierten Nachrichtentext.
ical_loop – ruft den iCal-Feed regelmäßig ab, parst alle VEVENT-Blöcke und sendet für Termine die innerhalb des konfigurierten Vorlaufzeitfensters beginnen eine Benachrichtigung. Bereits gemeldete Termine werden persistent gespeichert.
Persistenz – drei Dateien im Docker-Volume:
matrix_store/– E2EE-Keys (SQLite, nie löschen!)last_status.json– letzter bekannter Öffnungsstatusical_notified.json– bereits gemeldete Termin-UIDs
Betrieb & Wartung
Container-Management
# Starten
docker compose up -d
# Neu bauen und starten (nach Code-Änderungen)
docker compose up -d --build
# Stoppen
docker compose down
# Logs live verfolgen
docker compose logs -f
# Bot-Status
docker compose ps
Konfiguration ändern
Da alle Einstellungen über Umgebungsvariablen gesetzt werden, reicht nach einer .env-Änderung ein Neustart ohne Rebuild:
docker compose up -d
Neustart über Matrix
Moderatoren können den Bot direkt aus dem Kanal neu starten:
!debug restart
Docker startet den Container wegen restart: unless-stopped automatisch neu.
E2EE-Store zurücksetzen
Falls der Bot Nachrichten nicht entschlüsseln kann und ein Neustart nicht hilft:
docker compose down
docker run --rm -v chch-bot_bot_data:/data alpine sh -c \
"rm -rf /data/matrix_store && mkdir /data/matrix_store"
docker compose up -d
⚠️ Nach einem Store-Reset erscheint der Bot als neues unbekanntes Gerät. Das Gerät muss in Element erneut als vertrauenswürdig markiert werden.
Daten & Volumes
# Volume-Inhalt inspizieren
docker run --rm -v chch-bot_bot_data:/data alpine ls -la /data
Das Volume enthält:
/data/matrix_store/– E2EE-Keys (SQLite) – niemals löschen/data/last_status.json– letzter bekannter Öffnungsstatus/data/ical_notified.json– bereits gemeldete Termin-UIDs
Log-Rotation
Logs werden automatisch rotiert (max. 10 MB, 3 Dateien):
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Troubleshooting
| Problem | Ursache | Lösung |
|---|---|---|
Login fehlgeschlagen |
Falsches Passwort oder falsch formatierte User-ID | .env prüfen – User-ID muss @name:server.tld sein |
Raum-Alias konnte nicht aufgelöst werden |
Bot ist nicht Mitglied des Raums | Bot zum Raum einladen |
| Bot sendet keine Nachrichten | E2EE-Keys fehlen oder SyncError |
Store zurücksetzen (siehe oben) |
no session found beim Entschlüsseln |
Megolm-Session fehlt | Bot neu starten; ggf. Store zurücksetzen |
SyncError: next_batch im Log |
Abgelaufener Sync-Token | Wird automatisch behoben (Token-Reset) |
libolm not found |
Image ohne libolm3 gebaut |
docker compose up -d --build |
| Commands werden ignoriert | Rate-Limit aktiv (10s) | 10 Sekunden warten |
!debug verweigert |
Kein DM-Raum oder User-ID nicht in Whitelist | DM an Bot schreiben und DEBUG_WHITELIST prüfen |
!events zeigt keine Termine |
iCal-Feed nicht erreichbar oder leer | ICAL_URL prüfen; !debug logs für Details |
| iCal-Termin wurde nicht angekündigt | Termin lag außerhalb des Vorlaufzeitfensters | ICAL_NOTIFY_MINUTES_BEFORE erhöhen |
| Bot erscheint als unbekanntes Gerät | E2EE-Store neu erstellt | Gerät in Element als vertrauenswürdig markieren |
| Log-Upload schlägt fehl | Media-Endpoint nicht erreichbar | Homeserver-Verbindung prüfen |
Logs über Matrix abrufen
!debug logs
Logs direkt im Container
docker compose logs -f --tail=100
Sicherheit
Implementierte Maßnahmen
- Kein Passwort im Image –
MATRIX_PASSWORDerscheint nie inENV-Layern oderdocker inspect - Docker Secrets – Passwort wird als gemountete Datei übergeben, nur für den Container-Prozess sichtbar
- Config-Validierung beim Start – Passwort, User-ID-Format und Homeserver-URL werden vor dem Login geprüft
- Non-Root-User – Bot läuft als
botuser(UID ≠ 0) im Container - Multi-Stage-Build – Build-Tools (
gcc,libolm-dev) landen nicht im Runtime-Image - TLS-Verifikation – SpaceAPI- und iCal-Abrufe nutzen explizit
ssl.create_default_context()mit System-CA-Bundle - Antwort-Größenlimit – SpaceAPI (1 MB) und iCal-Feed (2 MB) werden bei Überschreitung verworfen
- Typ-Validierung –
state.openmuss einboolsein, andere Typen werden ignoriert - Rate-Limiting – 10s Cooldown pro User verhindert Flooding via Commands
- Whitelist + DM-Pflicht –
!debug-Commands nur per Direktnachricht und nur für User-IDs inDEBUG_WHITELIST - Fehler-Logging ohne Details – Exceptions loggen nur den Typ, keine internen Objekte
- asyncio.Lock –
bot_stateundlast_statussind gegen Race-Conditions geschützt - E2EE – alle Matrix-Nachrichten und Dateianhänge (Logs) sind Ende-zu-Ende-verschlüsselt
Bekannte Einschränkungen
ignore_unverified_devices=True– Nachrichten werden an alle Geräte im Raum gesendet, auch unverifizierte. Für einen öffentlichen Statusbot akzeptabel; für sensible Inhalte sollte dies aufFalsegesetzt werden.- Passwort im Prozess-Speicher – das Passwort liegt zur Laufzeit im Prozess-Speicher. Ein Angreifer mit Zugriff auf den Docker-Daemon kann es über
/proc/<pid>/environlesen. - In-Memory Log-Buffer – der
!debug logs-Buffer (200 Zeilen) wird bei jedem Neustart geleert. Für persistente Log-Archivierung sollte ein externer Logging-Dienst eingebunden werden. - Keine RRULE-Expansion – wiederkehrende iCal-Events (z.B. wöchentliche Treffen) werden nur mit ihrem initialen
DTSTARTerfasst, nicht mit allen zukünftigen Wiederholungen.