Files
einkaufsapp/README.md

9.3 KiB
Raw Permalink Blame History

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

git clone <repository-url> einkaufsapp
cd einkaufsapp
cp .env.example .env

In der .env mindestens setzen:

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 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://<host>: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)

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.

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

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:

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

curl -sI https://einkauf.example.de | grep -i content-security-policy

Es darf genau eine Zeile erscheinen.

Betrieb

Aktualisieren

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

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

Prüfskripte

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 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

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/ Ausführliche Erklärung des Codes, 17 Notizen mit Diagrammen
docs/betrieb.md Sicherung, Wiederherstellung, Fehlersuche
docs/sicherheit.md Prüfergebnisse und bekannte Grenzen
docs/reverse-proxy.md Zusammenspiel mit einem vorgelagerten Proxy
docs/entwicklung.md Entstehung der Anwendung, Phase für Phase
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 und stehen unter der Open Database License (ODbL).