# Einkaufsliste Selbst gehostete Einkaufslisten-App für Haushalt, WG oder Verein. Läuft als Progressive Web App auf dem Telefon, funktioniert ohne Verbindung weiter und gleicht ab, sobald wieder Netz da ist. Keine Werbung, kein Tracking, keine externen Schriften oder Skripte. Alle Daten bleiben auf dem eigenen Server. ## Funktionen - **Listen teilen** – per Einladung mit Rollen (lesen, bearbeiten, verwalten) oder über einen zeitlich begrenzten öffentlichen Link - **Offline nutzbar** – Änderungen werden lokal festgehalten und später nachgereicht, ohne dass etwas verlorengeht - **Nach Märkten und Warengruppen sortiert** – die Reihenfolge lässt sich an den Weg durch den Laden anpassen - **Strichcodes scannen** – eigener EAN-Decoder, Produktdaten von Open Food Facts (Abfrage über den Server, nicht vom Gerät) - **Preise erfassen und vergleichen** – je Markt, ohne Personenbezug - **Push-Benachrichtigungen** bei Änderungen, gedrosselt auf höchstens eine Meldung je Liste alle zwei Stunden - **Druckansicht** auf A4, nach Märkten gegliedert - **Benutzerverwaltung** mit einstellbaren Fristen für Bereinigung und Archiv ## Technik | Schicht | Verwendet | |---|---| | Backend | Python 3.12, FastAPI, SQLAlchemy 2, Alembic | | Datenbank | MariaDB 11.4 | | Frontend | ES-Module, kein Framework, kein Bauschritt | | Auslieferung | nginx | | Betrieb | Docker Compose, drei Container | ## Voraussetzungen - Docker mit Compose-Plugin - Ein SMTP-Zugang für Bestätigungs- und Einladungsmails - Für den öffentlichen Betrieb: ein Reverse Proxy mit TLS (siehe unten) ## Installation ```bash git clone einkaufsapp cd einkaufsapp cp .env.example .env ``` In der `.env` mindestens setzen: ```ini DB_PASSWORD=… # eigenes Passwort DB_ROOT_PASSWORD=… # anderes Passwort ADMIN_EMAIL=du@example.de # das erste Konto ADMIN_INITIAL_PASSWORD=… # mind. 12 Zeichen, danach ändern PUBLIC_BASE_URL=https://einkauf.example.de COOKIE_SECURE=true # false nur beim Testen ohne TLS SMTP_HOST=mail.example.de SMTP_PORT=587 SMTP_SECURITY=starttls SMTP_USER=… SMTP_PASSWORD=… SMTP_FROM=einkauf@example.de ``` Prüfen und starten: ```bash bash tools/check-env.sh .env docker compose up -d docker compose logs -f api ``` Beim ersten Start legt die Anwendung das Administratorkonto an und führt die Datenbankmigrationen aus. Danach ist sie unter `http://:8080/` erreichbar. **Nach der ersten Anmeldung:** Passwort ändern und `ADMIN_INITIAL_PASSWORD` in der `.env` leeren – der Wert wird danach ignoriert, stünde aber weiter im Klartext in der Umgebung. ### Push-Benachrichtigungen (freiwillig) ```bash python3 tools/vapid-keys.py ``` Die drei ausgegebenen Zeilen in die `.env` übernehmen – **vorhandene Zeilen ersetzen, nicht ergänzen.** Bei doppelten Einträgen nimmt Docker Compose den letzten, und das ist oft die leere Vorlagenzeile. ```bash docker compose up -d ``` ## TLS und Reverse Proxy > **Die Anwendung fordert keine Zertifikate an und verwaltet sie nicht.** > Sie liefert ausschließlich HTTP auf dem konfigurierten Port aus. Für TLS ist > ein vorgeschalteter Reverse Proxy zuständig. Ohne HTTPS fehlen wesentliche Funktionen: Service Worker, Kamerazugriff zum Scannen und Push-Benachrichtigungen sind an einen sicheren Kontext gebunden. Für lokale Tests gilt `localhost` als sicher, jede andere Adresse nicht. ### Variante A: nginx davor ```nginx server { listen 443 ssl http2; server_name einkauf.example.de; ssl_certificate /etc/letsencrypt/live/einkauf.example.de/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/einkauf.example.de/privkey.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Server-Sent Events: ohne diese beiden Zeilen bekommt die App # keine Änderungen mit, die andere gemacht haben. proxy_buffering off; proxy_read_timeout 3600s; } } ``` **Wichtig:** Setze am vorgelagerten Proxy **keine eigene Content-Security-Policy.** Der mitgelieferte nginx bringt bereits eine mit. Zwei Richtlinien werden vom Browser als Schnittmenge angewandt – die Anwendung hört dann ohne erkennbaren Fehler auf zu funktionieren. Die übrigen Sicherheitskopfzeilen (HSTS, `X-Frame-Options`, `Referrer-Policy`) doppelt zu setzen ist unschädlich. ### Variante B: Traefik `docker-compose.override.yml` im Projektverzeichnis: ```yaml services: web: ports: [] # kein direkter Port mehr nötig networks: [einkaufsapp, traefik] labels: - "traefik.enable=true" - "traefik.docker.network=traefik" - "traefik.http.routers.einkauf.rule=Host(`einkauf.example.de`)" - "traefik.http.routers.einkauf.entrypoints=websecure" - "traefik.http.routers.einkauf.tls.certresolver=le" - "traefik.http.services.einkauf.loadbalancer.server.port=80" # SSE: Antworten nicht puffern - "traefik.http.services.einkauf.loadbalancer.responseforwarding.flushinterval=100ms" networks: traefik: external: true ``` Traefik setzt `X-Forwarded-*` von sich aus. Auch hier gilt: keine zusätzliche CSP über eine Middleware. ### Nach dem Einrichten prüfen ```bash curl -sI https://einkauf.example.de | grep -i content-security-policy ``` Es darf **genau eine** Zeile erscheinen. ## Betrieb ### Aktualisieren ```bash cd einkaufsapp git pull bash tools/check-all.sh # alle Prüfungen docker compose up -d --build docker compose logs --tail=30 api ``` Migrationen laufen beim Start automatisch. **Vorher sichern** – Migrationen lassen sich nicht immer zurücknehmen. ### Sichern ```bash docker compose exec -T db mariadb-dump -u root -p"$DB_ROOT_PASSWORD" \ --single-transaction --quick --routines --events \ --default-character-set=utf8mb4 einkaufsapp | gzip > einkaufsapp-$(date +%F).sql.gz ``` Sichere den **Datenbankauszug**, nicht das Volume: Ein Dateisystemabbild einer laufenden Datenbank ist nicht zuverlässig wiederherstellbar. `--single-transaction` liefert einen konsistenten Stand, ohne die Anwendung anzuhalten. Die `.env` gehört ebenfalls gesichert – aber getrennt und verschlüsselt. Sie enthält Datenbankpasswort, SMTP-Zugangsdaten und den privaten VAPID-Schlüssel. Ausführlich: [`docs/betrieb.md`](docs/betrieb.md) ### Prüfskripte ```bash bash tools/check-all.sh ``` Prüft Konfiguration, Berechtigungen aller Endpunkte, Datenbankschema gegen die Modelle, nginx-Kopfzeilen, JavaScript-Module, Fristen und den Strichcode-Decoder. Rückgabewert 1 bei Problemen – als Git-Hook oder in einer CI verwendbar. ### Funktionstest gegen eine laufende Instanz ```bash bash tools/smoke-test.sh http://localhost:8080 admin@example.de deinpasswort ``` Legt eine Liste an, fügt Einträge hinzu, hakt ab, räumt auf und löscht sich selbst wieder. Nützlich nach einem Update. ### Entwicklungsmodus ```bash cp docker-compose.override.yml.example docker-compose.override.yml docker compose up -d ``` Bindet den Quelltext ein und startet uvicorn bei Änderungen neu. **Die Datei gehört nicht auf den Produktivserver** – dort liefe sonst Code vom Host statt aus dem gebauten Abbild. ## Rechtliches `web/html/impressum.html` und `web/html/datenschutz.html` sind **Entwürfe** mit Platzhaltern. Vor der Veröffentlichung ausfüllen und prüfen lassen – die Hinweiskästen im Dokument nennen, worauf zu achten ist. Beide sind aus jeder Ansicht über die Fußzeile erreichbar. ## Dokumentation | Datei | Inhalt | |---|---| | [`doku/`](doku/) | Ausführliche Erklärung des Codes, 17 Notizen mit Diagrammen | | [`docs/betrieb.md`](docs/betrieb.md) | Sicherung, Wiederherstellung, Fehlersuche | | [`docs/sicherheit.md`](docs/sicherheit.md) | Prüfergebnisse und bekannte Grenzen | | [`docs/reverse-proxy.md`](docs/reverse-proxy.md) | Zusammenspiel mit einem vorgelagerten Proxy | | [`docs/entwicklung.md`](docs/entwicklung.md) | Entstehung der Anwendung, Phase für Phase | | [`db/schema.sql`](db/schema.sql) | Vollständiges Datenbankschema | ## Struktur ``` backend/ FastAPI, SQLAlchemy, Alembic-Migrationen web/ nginx-Konfiguration und das Frontend db/schema.sql Schema als ein Stück SQL tools/ Prüfskripte docs/ Betriebs- und Sicherheitsdokumentation doku/ Code-Dokumentation zum Nachlesen ``` ## Entstehung Diese Anwendung ist in Zusammenarbeit mit **Claude (Anthropic)** entstanden. Konzept, Anforderungen, fachliche Entscheidungen und das Testen im echten Betrieb kamen von mir; der Code wurde im Dialog erarbeitet – einschließlich der Fehlersuche, wenn etwas nicht funktionierte. Ich halte diese Angabe für angebracht: Wer den Code liest, soll wissen, wie er entstanden ist. Die Verantwortung für das, was hier läuft, liegt trotzdem bei mir – geprüft, angepasst und im Alltag erprobt habe ich es selbst. Die Nutzung erfolgt ohne jegliche Garantie und auf eigene Verantwortung des jeweiligens Anwenders. Es wird keine Haftung für Datenverlust oder andere Schäden übernommen. ## Lizenz GPL v3 (General Public Licence Version 3) Produktdaten zu Strichcodes stammen von [Open Food Facts](https://openfoodfacts.org) und stehen unter der Open Database License (ODbL).