# 📅 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=1` markiert, 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 [![UI](docs/screenshot_thumbnail.png)](docs/screenshot.png) ## 📋 Voraussetzungen - 🐳 Docker & Docker Compose - 🗃️ MariaDB Instanz (z.B. als Proxmox LXC) - 📱 Google Calendar mit privatem ICS-Feed ## 🚀 Quick Start 1. **🔧 MariaDB Setup** Entweder manuell ausführen: ```bash mysql -u root -p < mariadb-setup.sql ``` Oder automatisch beim Start (in `.env` setzen): ``` DB_BOOTSTRAP=true DB_ROOT_USER=root DB_ROOT_PASSWORD=dein_root_passwort ``` 2. **📄 .env anlegen** ```bash cp .env.example .env ``` Variablen anpassen, insbesondere: - `ICS_URL`: Privater ICS-Link aus den Google Calendar Einstellungen - `DB_PASSWORD`: Sicheres Passwort für den calendar_sync User 3. **▶️ Starten** ```bash docker compose up -d ``` ## 🎮 Demo / Lokaler Test (ohne Docker) Schneller Weg um Web-UI und API lokal zu testen, ohne MariaDB oder Docker: ```bash ./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:** ```bash 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 ```bash # 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 ```json { "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](LICENSE) - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)