Ein Simpler Matrixbot der den Öffnungsstatus via SpaceAPI Queried und bei Change eine Nachricht in den Matrix Raum postet
Find a file
agentk 89ae18970b docker-compose.yml aktualisiert
fixed debug whitelist env not being included
2026-03-25 18:17:58 +01:00
.env.example Moved Debug Feature to direct Message 2026-03-25 17:58:30 +01:00
chaoschemnitz_bot.py moved all debug output to dm 2026-03-25 18:13:54 +01:00
docker-compose.yml docker-compose.yml aktualisiert 2026-03-25 18:17:58 +01:00
Dockerfile initialer commit 2026-03-09 11:25:34 +01:00
dockerignore Dateien nach „/“ hochladen 2026-03-23 16:50:49 +01:00
env.example Dateien nach „/“ hochladen 2026-03-23 16:50:49 +01:00
README.md Moved Debug Feature to direct Message 2026-03-25 17:58:30 +01:00
requirements.txt Added Markdown Support 2026-03-24 17:42:37 +01:00

🤖 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

  • 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-nio gesendet
  • Raum-Alias-Auflösung akzeptiert sowohl #alias:server als 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.org oder eigenem Homeserver)
  • Der Bot muss Mitglied des Zielraums sein
  • Der Zielraum muss E2EE aktiviert haben
  • libolm3 wird 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:

  1. In Element den Bot-Account öffnen → Sicherheit → Sitzungen
  2. Die neue Bot-Sitzung als vertrauenswürdig markieren oder
  3. 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.tld haben 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

\n in MSG_OPEN und MSG_CLOSED wird 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, \n erzeugt 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 Öffnungsstatus
  • ical_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_PASSWORD erscheint nie in ENV-Layern oder docker 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.open muss ein bool sein, 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 in DEBUG_WHITELIST
  • Fehler-Logging ohne Details Exceptions loggen nur den Typ, keine internen Objekte
  • asyncio.Lock bot_state und last_status sind 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 auf False gesetzt 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>/environ lesen.
  • 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 DTSTART erfasst, nicht mit allen zukünftigen Wiederholungen.