- Django 5.2 + PostgreSQL 17 via docker compose (app + db, volumes, healthcheck on /healthz, test profile running pytest against Postgres) - Custom e-mail user model with user/admin roles - Invitation-only registration with single-use, hashed, expiring links - Admin area: invitations, role changes, deactivation, password reset via one-time link or temporary password (forced change on login) - First admin created from ADMIN_EMAIL/ADMIN_PASSWORD on startup - Login rate limiting per e-mail and IP (DB-backed, worker-safe) - Proxy middleware honouring X-Forwarded-* only from TRUSTED_PROXIES, optional CLIENT_IP_HEADER (e.g. CF-Connecting-IP) - Security headers (CSP, HSTS, Permissions-Policy), secure __Host- cookies - Mobile-first German UI, CLAUDE.md, README, init-env script - 65 tests for auth, permissions, proxy handling and rate limiting Co-Authored-By: Claude Opus 5.5 <[email protected]>
2.5 KiB
Kochbuch
Selbst gehostetes Familien-Kochbuch mit Einladungen, privaten und öffentlichen Rezepten und KI-Import. Läuft per docker compose (App + PostgreSQL) hinter einem bestehenden Reverse Proxy.
Stand: Phase 1 (Konten, Einladungen, Rollen). Die vollständige Anleitung inkl. Update und Backup folgt in Phase 6.
Installation
git clone <gitea-url>/kochbuch.git && cd kochbuch
./scripts/init-env.sh [email protected] # erzeugt .env mit zufälligen Secrets und Admin-Passwort
nano .env # APP_BASE_URL, TRUSTED_PROXIES, APP_PORT prüfen
docker compose up -d --build
docker compose ps # app sollte nach ~30 s "healthy" sein
Danach mit der Admin-E-Mail und dem ausgegebenen Passwort anmelden. ADMIN_PASSWORD kann nach dem ersten
Start aus der .env entfernt werden – ein bestehendes Konto wird nie überschrieben.
Weitere Personen lädst du unter Menü → Einladungen ein. Der Link wird nur einmal angezeigt. Passwort vergessen? Menü → Nutzer verwalten → Aktionen: Reset-Link oder temporäres Passwort.
Reverse Proxy (Nginx + Cloudflare)
Die App spricht nur HTTP auf APP_PORT. Beispiel für Nginx:
server {
server_name kochbuch.dammann.vip;
# listen/ssl wie bei deinen anderen Diensten
client_max_body_size 30m; # Foto-Uploads
location / {
proxy_pass http://<docker-host>:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_read_timeout 120s; # KI-Aufrufe können dauern
}
}
Client-IP: Die App wertet X-Forwarded-For nur aus, wenn die Anfrage von einer IP aus
TRUSTED_PROXIES kommt. Das ist wichtig für das Login-Rate-Limit. Mit Cloudflare gibt es zwei Wege:
- Nginx ermittelt die echte IP selbst (
set_real_ip_from <Cloudflare-Netze>; real_ip_header CF-Connecting-IP;). Dann reichtTRUSTED_PROXIES=<IP deines Nginx>. - Oder in der
.envCLIENT_IP_HEADER=CF-Connecting-IPsetzen. Nur sinnvoll, wenn dein Nginx ausschließlich über Cloudflare erreichbar ist, sonst kann der Header gefälscht werden.
Konfiguration
Alle Variablen stehen kommentiert in .env.example.
Entwicklung
python -m venv .venv && . .venv/bin/activate
pip install -r requirements-dev.txt
python -m pytest # Tests (SQLite)
python scripts/dev_server.py # http://localhost:8000
docker compose --profile test run --rm test # Tests gegen PostgreSQL