9.0 KiB
📅 calender_sync
Google Calendar (ICS-Feed) → MariaDB Sync fürs Homelab.
Läuft als Docker Container, pollt periodisch einen privaten Google Calendar ICS-Feed, expandiert RRULE/EXDATE/RECURRENCE-ID und schreibt einzelne Instanzen in eine MariaDB-Datenbank.
✨ Features
- 🔄 Automatische Synchronisation alle 15 Minuten (konfigurierbar)
- 📆 Expandiert wiederkehrende Events (RRULE) und Serien-Exceptions (EXDATE, RECURRENCE-ID)
- 🗑️ Soft-Delete: Entfernte Events werden als
deleted=1markiert, nicht gelöscht - 🛠️ Optionales Database-Bootstrap: Erstellt DB und User automatisch bei
DB_BOOTSTRAP=true - ⏰ Zeitfenster-konfiguration für Vergangenheit/Future (standardmäßig -90 Tage / +365 Tage)
- 🌐 Web-UI zur Anzeige anstehender Termine mit Suchfunktion
- 🔌 REST API für programmsprachigen Zugriff auf Kalenderdaten
- 📧 Optionale tägliche E-Mail-Benachrichtigung um konfigurierte Uhrzeit
- 📬 Optionale wöchentliche Vorab-Info (z.B. freitags) mit Termine nach Suchbegriffen
📋 Voraussetzungen
- 🐳 Docker & Docker Compose
- 🗃️ MariaDB Instanz (z.B. als Proxmox LXC)
- 📱 Google Calendar mit privatem ICS-Feed
🚀 Quick Start
-
🔧 MariaDB Setup
Entweder manuell ausführen:
mysql -u root -p < mariadb-setup.sqlOder automatisch beim Start (in
.envsetzen):DB_BOOTSTRAP=true DB_ROOT_USER=root DB_ROOT_PASSWORD=dein_root_passwort -
📄 .env anlegen
cp .env.example .envVariablen anpassen, insbesondere:
ICS_URL: Privater ICS-Link aus den Google Calendar EinstellungenDB_PASSWORD: Sicheres Passwort für den calendar_sync User
-
▶️ Starten
docker compose up -d
🎮 Demo / Lokaler Test (ohne Docker)
Schneller Weg um Web-UI und API lokal zu testen, ohne MariaDB oder Docker:
./demo.sh
Das Script erstellt eine virtuelle Python-Umgebung, installiert Dependencies, legt eine SQLite Demo-Datenbank mit 4 Beispiel-Terminen an und startet die API auf Port 8000.
🔗 Endpoints:
- 🌐 Web-UI: http://localhost:8000/
- 📊 API JSON: http://localhost:8000/api/events
- ❤️ Health: http://localhost:8000/api/health
🎯 Demo-Events (4 Stück):
| Termin | Zeit | Status |
|---|---|---|
| 📅 Team Daily | heute 14:00–15:00 | ✅ CONFIRMED |
| 🧘 Yoga Kurs | heute 18:30–20:00 | ✅ CONFIRMED |
| ⚠️ Projekt-Deadline | morgen Ganztag | 🟡 TENTATIVE |
| 👨⚕️ Arzttermin | übermorgen 10:00–11:30 | ✅ CONFIRMED |
Die Demo verwendet SQLite (DB_BACKEND=sqlite) statt MariaDB. Die Events werden relativ zum heutigen Datum erstellt.
⚙️ Konfiguration
| Variable | Default | Beschreibung |
|---|---|---|
ICS_URL |
required | Privater ICS-Feed aus Google Calendar |
CALENDAR_LABEL |
default |
Label für den Kalender (für Multi-Kalender) |
DB_HOST |
mariadb.fritz.box |
MariaDB Host |
DB_PORT |
3306 |
MariaDB Port |
DB_NAME |
calendar_sync |
Datenbank-Name |
DB_USER |
required | Datenbank-User |
DB_PASSWORD |
required | Datenbank-Passwort |
SYNC_INTERVAL_MINUTES |
15 |
Sync-Intervall in Minuten |
WINDOW_PAST_DAYS |
90 |
Wie viele Tage in die Vergangenheit synchronisieren |
WINDOW_FUTURE_DAYS |
365 |
Wie viele Tage in die Zukunft synchronisieren |
LOG_LEVEL |
INFO |
Python Logging Level |
DB_BOOTSTRAP |
false |
DB + User beim Start erstellen |
DB_ROOT_USER |
- | Root-User fürs Bootstrap |
DB_ROOT_PASSWORD |
- | Root-Passwort fürs Bootstrap |
API_PORT |
8000 |
Port für den API/Web-UI Container |
TIMEZONE |
UTC |
Zeitzone für API/Web-UI Anzeige (z.B. Europe/Berlin) |
📧 Optionale E-Mail-Benachrichtigung
Sendet täglich eine HTML-Email mit den anstehenden Terminen. Wird aktiviert wenn SMTP_HOST und NOTIFY_EMAIL gesetzt sind.
| Variable | Default | Beschreibung |
|---|---|---|
SMTP_HOST |
- | SMTP Server Hostname |
SMTP_PORT |
587 |
SMTP Server Port |
SMTP_USER |
- | SMTP Login Username |
SMTP_PASSWORD |
- | SMTP Login Passwort |
SMTP_FROM |
- | Absender E-Mail Adresse |
SMTP_USE_TLS |
true |
TLS verschlüsselung nutzen |
NOTIFY_EMAIL |
- | Empfänger E-Mail Adresse |
NOTIFY_TIME |
6 |
Uhrzeit für Benachrichtigung (Stunde, 0-23) |
NOTIFY_TIMEZONE |
Europe/Berlin |
Zeitzone für die Benachrichtigung |
📬 Subject-Logik:
- 📌 1 Termin:
Kalender heute: 09:00 - Meeting mit Team - 📌 2+ Termine:
Kalender heute: 3 Termine
⚠️ Hinweis: Ganztagstermine werden nicht in der Benachrichtigung berücksichtigt.
📬 Optionale wöchentliche Vorab-Info
Sendet wöchentlich (standardmäßig freitags) eine HTML-Email mit Terminen, die auf konfigurierte Suchbegriffe passen. Zeitraum ist immer Samstag bis Freitag der nächsten Woche. Wird aktiviert wenn WEEKLY_NOTIFY_ENABLED=true und mindestens ein Suchbegriff gesetzt ist.
| Variable | Default | Beschreibung |
|---|---|---|
WEEKLY_NOTIFY_ENABLED |
false |
Feature aktivieren |
WEEKLY_NOTIFY_DAY |
5 |
Wochentag (0=Mo, 1=Di, ..., 5=Fr) |
WEEKLY_NOTIFY_TIME |
16 |
Uhrzeit für Benachrichtigung (Stunde, 0-23) |
WEEKLY_NOTIFY_TIMEZONE |
Europe/Berlin |
Zeitzone für die Benachrichtigung |
WEEKLY_NOTIFY_EMAIL |
- | Empfänger (Fallback: NOTIFY_EMAIL) |
WEEKLY_SEARCHWORDS |
- | Komma-separierte Suchbegriffe |
📬 Subject-Logik:
- 📌 1 Termin:
Vorab-Info: Termin am Fr, 15.08. - 📌 2+ Termine:
Vorab-Info: 3 Termine naechste Woche
💡 Beispiel:
WEEKLY_NOTIFY_ENABLED=true
WEEKLY_NOTIFY_DAY=5
WEEKLY_NOTIFY_TIME=16
WEEKLY_SEARCHWORDS=Fussball,Arzttermine
⚠️ Hinweis: Keine Email wenn keine Treffer für die Suchbegriffe im Zeitraum.
🗃️ Datenbank-Schema
Tabelle calendar_events:
| Spalte | Typ | Beschreibung |
|---|---|---|
id |
🆔 BIGINT PK | Auto-Increment |
calendar_label |
🏷️ VARCHAR(64) | Kalender-Label |
instance_key |
🔑 VARCHAR(255) | SHA1-basierte eindeutige Instanz-ID |
uid |
📌 VARCHAR(255) | ICS UID |
recurrence_id |
🔄 VARCHAR(64) | RECURRENCE-ID bei Serien-Exceptions |
summary |
📝 VARCHAR(512) | Titel |
description |
📄 TEXT | Beschreibung |
location |
📍 VARCHAR(512) | Ort |
start_at |
🚀 DATETIME | Startzeit (UTC) |
end_at |
🏁 DATETIME | Endzeit (UTC) |
all_day |
☀️ TINYINT(1) | Ganz-tagig Flag |
status |
📊 VARCHAR(32) | Event-Status |
deleted |
🗑️ TINYINT(1) | Soft-Delete Flag |
last_seen_at |
👁️ DATETIME | Letzte Synchronisation |
created_at |
🎨 DATETIME | Erstellungszeitpunkt |
updated_at |
🔄 DATETIME | Letzte Änderung |
Tabelle daily_notification_log (optional, für E-Mail-Benachrichtigung):
| Spalte | Typ | Beschreibung |
|---|---|---|
id |
🆔 INT PK | Auto-Increment |
notify_date |
📅 DATE | Datum der Benachrichtigung |
sent_at |
⏰ DATETIME | Zeitpunkt des Versands |
event_count |
🔢 INT | Anzahl Termine in der Email |
Tabelle weekly_notification_log (optional, für wöchentliche Vorab-Info):
| Spalte | Typ | Beschreibung |
|---|---|---|
id |
🆔 INT PK | Auto-Increment |
notify_date |
📅 DATE | Datum der Benachrichtigung |
sent_at |
⏰ DATETIME | Zeitpunkt des Versands |
event_count |
🔢 INT | Anzahl Termine in der Email |
🌐 Web-UI & API
Das Projekt enthält eine FastAPI-basierte Webanwendung die als separater Container (calendar-api) läuft und auf Port 8000 erreichbar ist.
🔗 Endpoints
| Endpoint | Beschreibung |
|---|---|
GET / |
🌐 HTML-Seite mit anstehenden Terminen und Suchfunktion |
GET /api/health |
❤️ Health Check (gibt {"status": "ok"} zurück) |
GET /api/events?limit=10&search=... |
📊 JSON-Liste zukünftiger Events (nicht gelöscht) |
GET /api/events/{event_id} |
🔍 Einzelnes Event als JSON |
💡 API Beispiel
# Alle anstehenden Events (max. 10)
curl http://localhost:8000/api/events
# Suche nach Titel
curl "http://localhost:8000/api/events?search=Meeting&limit=5"
# Einzelnes Event
curl http://localhost:8000/api/events/42
📋 JSON Response Format
{
"events": [
{
"id": 1,
"summary": "Teammeeting",
"description": "Wöchentliches Teammeeting",
"location": "Konferenzraum 1",
"start_at": "2025-01-15T10:00:00",
"end_at": "2025-01-15T11:00:00",
"all_day": false,
"status": "CONFIRMED",
"timezone": "Europe/Berlin"
}
],
"count": 1,
"query_time": "2025-01-15T09:30:00Z",
"timezone": "Europe/Berlin"
}
🔄 CI/CD
GitHub Actions Workflow (docker-publish.yml) baut und published das Docker Image automatisch nach GitHub Container Registry:
ghcr.io/skoelle/calender_sync:latest
📜 Lizenz
Lizenziert unter der MIT License - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)