# 🏠 Oikos **Selbstgehosteter Familienplaner — privat, offen, ohne Abonnement** [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org) [![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white)](https://www.docker.com) [![SQLite](https://img.shields.io/badge/SQLite-SQLCipher%20verschlĂŒsselt-003B57?logo=sqlite&logoColor=white)](https://www.zetetic.net/sqlcipher/) [![PWA](https://img.shields.io/badge/PWA-offline--fĂ€hig-5A0FC8?logo=pwa&logoColor=white)](https://web.dev/progressive-web-apps/) [![Lizenz: MIT](https://img.shields.io/badge/Lizenz-MIT-yellow.svg)](./LICENSE) Alle Daten bleiben auf deinem eigenen Server. Kein Cloud-Zwang. Keine Datenweitergabe. Kein Tracking. [Screenshots](#screenshots) · [Module](#module) · [Schnellstart](#schnellstart) · [Konfiguration](#konfiguration) · [Kalender-Sync](#kalender-synchronisation) · [Sicherheit](#sicherheit)
--- ## Screenshots
### ☀ Light Mode
Dashboard
Dashboard
Wetter · Termine · Aufgaben · Essen
Aufgaben
Aufgaben
PrioritÀten · Zuweisung · Filter
Kalender
Kalender
Monatsansicht · Google & Apple Sync
Einkaufsliste
Einkauf
Mehrere Listen · Kategorien · Fortschritt
Essensplan
Essensplan
Wochenplan · Zutaten · → Einkaufsliste
### 🌙 Dark Mode
Dashboard Dark
Dashboard
Wetter · Termine · Aufgaben · Essen
Aufgaben Dark
Aufgaben
PrioritÀten · Zuweisung · Filter
Kalender Dark
Kalender
Monatsansicht · Google & Apple Sync
Einkaufsliste Dark
Einkauf
Mehrere Listen · Kategorien · Fortschritt
Essensplan Dark
Essensplan
Wochenplan · Zutaten · → Einkaufsliste
Dark Mode: Automatisch via Systemeinstellung oder manuell umschaltbar unter Einstellungen → Design.
--- ## Module | | Modul | Highlights | |---|---|---| | 📋 | **Dashboard** | Wetter-Widget, anstehende Termine, dringende Aufgaben, Essen heute, Pinnwand-Vorschau | | ✅ | **Aufgaben** | Listenansicht + Kanban, Teilaufgaben, Swipe-Gesten, Wiederholungen (tĂ€glich/wöchentlich/monatlich) | | 🛒 | **Einkauf** | Mehrere Listen, automatische Kategorie-Sortierung, Integration mit Essensplan | | đŸœïž | **Essensplan** | Wochenansicht, Zutatenverwaltung, Zutaten → Einkaufsliste mit einem Klick | | 📅 | **Kalender** | 4 Ansichten (Monat/Woche/Tag/Agenda), wiederkehrende Termine, Google & Apple Sync | | 📌 | **Pinnwand** | Farbige Sticky Notes, Markdown-Light (fett, kursiv, Listen) | | đŸ‘„ | **Kontakte** | Wichtige Familien-Kontakte, Direktanruf (`tel:`), Maps-Links | | 💰 | **Budget** | Einnahmen/Ausgaben, Kategorien, Monatsvergleich, CSV-Export | | ⚙ | **Einstellungen** | Dark Mode (System/Hell/Dunkel), Passwort Ă€ndern, Kalender-Sync, Familienmitglieder | --- ## Tech Stack **Backend:** Node.js · Express · SQLite/SQLCipher · express-session · bcrypt **Frontend:** Vanilla JavaScript (ES-Module) · Kein Framework · Kein Build-Step **Deployment:** Docker · Nginx Reverse Proxy · PWA (Service Worker + Manifest) **Optional:** Google Calendar API v3 (OAuth 2.0) · Apple iCloud CalDAV (tsdav) --- ## Installation ### Voraussetzungen - **Docker** und **Docker Compose** (auf dem Server installiert) - Einen Linux-Server oder eine lokale Linux-Maschine - FĂŒr den produktiven Betrieb: eine Domain und einen Reverse Proxy mit SSL (empfohlen: [Nginx Proxy Manager](https://nginxproxymanager.com)) --- ### Schritt 1 — Repository klonen ```bash git clone https://github.com/ulsklyc/oikos.git cd oikos ``` --- ### Schritt 2 — Umgebungsvariablen konfigurieren ```bash cp .env.example .env ``` Die `.env`-Datei mit einem Texteditor öffnen und mindestens diese Felder ausfĂŒllen: ```env # ZufĂ€lliger String mit mindestens 32 Zeichen — z.B. generieren mit: # openssl rand -base64 32 SESSION_SECRET=hier_einen_langen_zufaelligen_string_eintragen # VerschlĂŒsselungsschlĂŒssel fĂŒr die Datenbank (AES-256) # Leer lassen = keine VerschlĂŒsselung (nicht empfohlen fĂŒr Produktion) DB_ENCRYPTION_KEY=hier_einen_starken_schluessel_eintragen ``` > **Tipp:** ZufĂ€llige SchlĂŒssel lassen sich einfach im Terminal generieren: > ```bash > openssl rand -base64 32 > ``` > Alle verfĂŒgbaren Variablen: [Konfigurationsreferenz](#konfiguration) --- ### Schritt 3 — Container bauen und starten ```bash docker compose up -d --build ``` > Der erste Build dauert **2–3 Minuten**, da SQLCipher gegen better-sqlite3 kompiliert wird. Folgende Builds sind deutlich schneller (Docker-Layer-Cache). Den Status des Containers prĂŒfen: ```bash docker compose ps docker compose logs oikos --tail=20 ``` Der Server ist bereit, wenn in den Logs erscheint: ``` [Oikos] Server lĂ€uft auf Port 3000 ``` --- ### Schritt 4 — Admin-Account anlegen ```bash docker compose exec oikos node setup.js ``` Das interaktive Script fragt nach **Benutzername**, **Anzeigename** und **Passwort**. Dieser erste Account erhĂ€lt Admin-Rechte und kann spĂ€ter weitere Familienmitglieder anlegen. --- ### Schritt 5 — App öffnen und einloggen Ohne Reverse Proxy (lokaler Test): ``` http://localhost:3000 ``` > **Wichtig bei direktem HTTP-Zugriff ohne Reverse Proxy:** > Da `NODE_ENV=production` gesetzt ist, erwartet der Server standardmĂ€ĂŸig HTTPS fĂŒr Session-Cookies. Beim Zugriff per `http://` muss daher in der `.env` folgende Zeile ergĂ€nzt werden: > > ```env > SESSION_SECURE=false > ``` > > Danach Container neu starten: `docker compose down && docker compose up -d` > Diese Einstellung **unbedingt entfernen**, sobald ein Reverse Proxy mit SSL eingerichtet ist. --- ### Schritt 6 — Reverse Proxy mit SSL einrichten (Produktion) FĂŒr den produktiven Betrieb sollte Oikos hinter einem Nginx Reverse Proxy mit SSL betrieben werden. **Mit Nginx Proxy Manager:** 1. Neuen Proxy Host anlegen: `oikos.deine-domain.de` → `localhost:3000` 2. SSL-Zertifikat via Let's Encrypt ausstellen 3. Im Feld „Advanced" den Inhalt aus [`nginx.conf.example`](./nginx.conf.example) eintragen **Wichtig:** Der Header `X-Forwarded-Proto` muss gesetzt sein (ist in der Beispielkonfiguration enthalten). Ohne ihn weiß der Server nicht, dass er hinter HTTPS lĂ€uft, und setzt Cookies nicht korrekt. Sobald SSL aktiv ist: `SESSION_SECURE=false` aus der `.env` entfernen und Container neu starten. --- ## Konfiguration ### Pflichtfelder | Variable | Beschreibung | |---|---| | `SESSION_SECRET` | ZufĂ€lliger String ≄ 32 Zeichen fĂŒr Session-Signing | | `DB_ENCRYPTION_KEY` | SQLCipher AES-256-SchlĂŒssel (leer lassen = keine VerschlĂŒsselung) | ### Sicherheit | Variable | Standard | Beschreibung | |---|---|---| | `SESSION_SECURE` | *(nicht gesetzt)* | `false` setzen wenn kein HTTPS-Reverse-Proxy vorhanden (direktes HTTP) | | `RATE_LIMIT_MAX_ATTEMPTS` | `5` | Max. Login-Versuche pro Minute pro IP | ### Wetter-Widget Kostenlosen API-Key bei [openweathermap.org](https://openweathermap.org/api) registrieren: ```env OPENWEATHER_API_KEY=dein_api_key OPENWEATHER_CITY=Berlin OPENWEATHER_UNITS=metric # metric = °C, imperial = °F OPENWEATHER_LANG=de ``` ### Weitere Optionen | Variable | Standard | Beschreibung | |---|---|---| | `PORT` | `3000` | Server-Port | | `NODE_ENV` | `production` | Umgebung (nicht Ă€ndern) | | `DB_PATH` | `/data/oikos.db` | Pfad zur SQLite-Datei im Container | | `SYNC_INTERVAL_MINUTES` | `15` | Automatischer Kalender-Sync-Intervall | VollstĂ€ndige Vorlage: [`.env.example`](./.env.example) --- ## Kalender-Synchronisation ### Google Calendar
Einrichtung anzeigen #### Google Cloud Console vorbereiten 1. Projekt unter [console.cloud.google.com](https://console.cloud.google.com) anlegen 2. **Google Calendar API** aktivieren 3. **OAuth 2.0-Client-ID** erstellen (Typ: „Webanwendung") 4. Autorisierte Redirect-URI eintragen: ``` https://oikos.deine-domain.de/api/v1/calendar/google/callback ``` 5. In `.env` eintragen: ```env GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... GOOGLE_REDIRECT_URI=https://oikos.deine-domain.de/api/v1/calendar/google/callback ``` 6. Container neu starten: `docker compose up -d` #### Verbindung herstellen 1. Mit einem **Admin**-Konto einloggen 2. **Einstellungen → Kalender-Synchronisation → Mit Google verbinden** 3. Google-Konto autorisieren → automatische Weiterleitung zurĂŒck **Sync-Verhalten:** - Erster Sync: Events der letzten 3 Monate + nĂ€chsten 12 Monate - Folge-Syncs: nur Änderungen via Google syncToken (effizient) - Outbound: neue lokale Termine werden nach Google ĂŒbertragen - Konflikt: Google gewinnt bei gleichzeitiger Änderung
### Apple Calendar (iCloud CalDAV)
Einrichtung anzeigen #### App-spezifisches Passwort erstellen 1. [appleid.apple.com](https://appleid.apple.com) → „Anmeldung und Sicherheit" → „App-spezifische Passwörter" 2. Neues Passwort fĂŒr „Oikos" erstellen 3. In `.env` eintragen: ```env APPLE_CALDAV_URL=https://caldav.icloud.com APPLE_USERNAME=deine@apple-id.de APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx ``` 4. Container neu starten: `docker compose up -d` Der Sync-Button erscheint automatisch in den Einstellungen.
--- ## Familienmitglieder Neue Mitglieder können nur Admins anlegen — es gibt keinen öffentlichen Registrierungs-Endpoint. **Im Browser:** Einstellungen → Familienmitglieder → Mitglied hinzufĂŒgen **Per Script** (z.B. fĂŒr weiteren Admin): ```bash docker compose exec oikos node setup.js ``` --- ## Updates ```bash git pull docker compose up -d --build ``` Datenbank-Migrationen laufen automatisch beim Start — kein manueller Eingriff nötig. Alle Daten im Volume `oikos_data` bleiben erhalten. > **Empfehlung:** Vor jedem Update ein Backup erstellen — siehe [Datensicherung](#datensicherung). --- ## Entwicklung ```bash npm install cp .env.example .env # SESSION_SECRET setzen — DB_ENCRYPTION_KEY weglassen (kein SQLCipher lokal) npm run dev # Server mit Auto-Reload ``` ```bash npm test # 146 Tests, 7 Suiten (In-Memory-SQLite, keine laufende App nötig) ``` --- ## Datensicherung ```bash # Backup erstellen docker run --rm \ -v oikos_oikos_data:/data \ -v $(pwd):/backup \ alpine tar czf /backup/oikos-backup-$(date +%Y%m%d).tar.gz /data # Backup wiederherstellen docker compose down docker run --rm \ -v oikos_oikos_data:/data \ -v $(pwd):/backup \ alpine tar xzf /backup/oikos-backup-YYYYMMDD.tar.gz -C / docker compose up -d ``` --- ## Sicherheit - Sessions: `httpOnly`, `SameSite=Strict`, `Secure` in Produktion, 7 Tage TTL - CSRF-Schutz via Double Submit Cookie (`crypto.timingSafeEqual`) auf allen schreibenden Requests - Passwörter mit bcrypt (Cost Factor 12) gehasht - Login-Rate-Limit: 5 Versuche/Minute, API-Rate-Limit: 300 Requests/Minute pro IP - Content Security Policy via Helmet (strikte CSP, HSTS, X-Frame-Options) - Zentrale Input-Validation auf allen Endpoints (LĂ€ngenlimits, Typ-PrĂŒfung, Enum-Whitelists) - Parametrisierte SQL-Queries (kein String-Concatenation, kein `eval()`) - Datenbank optional mit SQLCipher AES-256 verschlĂŒsselt (im Docker-Container) - Kein API-Endpoint ohne Session-Auth erreichbar (außer `/api/v1/auth/login`) --- ## Lizenz [MIT](./LICENSE) © 2025–2026 ulsklyc