Vogelalbum 1.0.0

Eine Kamera am Vogelhaus, ein Rechner der erkennt wer da war — und eine
Sammlung, die sich von allein fuellt.

Zwei neuronale Netze: eines findet den Vogel im Bild, eines bestimmt die
Art. 148 heimische Arten, auf Deutschland eingeschraenkt und neu normiert —
ohne diese Einschraenkung wird aus einer Kohlmeise eine nordamerikanische
Schwesterart. Daraus entsteht ein Sammelalbum: nummerierte Seiten,
gefundene Arten in Farbe, fehlende als Schatten, dazu Erstfang, Punkte,
Stufen und 15 Abzeichen.

Laeuft vollstaendig auf eigener Hardware. Kein Cloud-Dienst, kein Konto,
keine Telemetrie; die Bilder verlassen das eigene Netz nicht.

Zwei getrennt deploybare Teile: die Sammlung als Django-Anwendung, die
Erkennung als eigener Dienst. Der Erkennungs-Dienst fragt selbst an und
braucht deshalb weder eine erreichbare Adresse noch einen offenen Port —
er laeuft hinter einem gewoehnlichen Router.

Dabei: Firmware fuer Raspberry Pi und ESP32-S3, Anbindung an Home
Assistant ueber MQTT, Oberflaeche auf Deutsch und Englisch, Einstellungen
je Kamera aus dem Browser, und ein Weg fuer Fotos von unterwegs.

AGPL-3.0.
This commit is contained in:
christian committed 2026-10-15 14:30:02 +00:00
commit c11fe9ab04
189 files changed
+29806

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
# Eingang der oeffentlichen Instanz: liefert die Medien selbst aus und reicht
# alles andere an Gunicorn weiter.
#
# 🔑 Warum es diesen Dienst ueberhaupt gibt: Django liefert unter
# DJANGO_DEBUG=0 keine Medien aus (siehe web/vogelalbum/urls.py). Im Betrieb soll
# das der Webserver tun -- nur stellt Coolify keinen: Traefik routet auf einen
# Container und kennt das Volume nicht. Ohne diesen Dienst antwortet die
# oeffentliche Instanz auf jedes /media/... mit 404, und jede Karte der
# Sammlung haette ein kaputtes Bild.
#
# ⚠️ Whitenoise waere hier die falsche Loesung, obwohl es im Projekt schon
# steckt: es liest seinen Bestand beim Start ein. Vogelfotos kommen aber
# laufend dazu -- jede neue Sichtung waere bis zum naechsten Neustart
# unsichtbar.
# 🔴 **Das Schema kommt von Traefik, nicht von uns.**
#
# Hier stand `proxy_set_header X-Forwarded-Proto $scheme;` — und `$scheme` ist
# das Schema, mit dem jemand DIESEN nginx anspricht. Der lauscht auf einfachem
# HTTP hinter Traefik, also stand dort immer `http`, und zwar überschreibend:
# Traefik hatte den Kopf schon richtig auf `https` gesetzt, wir haben ihn
# wieder kaputtgemacht.
#
# Folge: Django hielt sich trotz SECURE_PROXY_SSL_HEADER für unverschlüsselt
# und baute **jede** absolute Adresse mit `http://` — Vogelfotos, Abzeichen-
# Pins, Links in Mails. Im Browser unsichtbar, weil Traefik auf `https`
# umleitet. **Auf dem Telefon ein Ausfall:** iOS blockt einfaches HTTP, ohne
# der Umleitung zu folgen; das Album bleibt bilderlos.
#
# Die Abbildung nimmt den Kopf von vorne, wenn es einen gibt, und fällt sonst
# auf das eigene Schema zurück — damit funktioniert dieselbe Konfiguration
# auch ohne Traefik davor (`docker compose` zu Hause).
#
# ⚠️ `map` gehört auf die http-Ebene, nicht in den server-Block. Diese Datei
# liegt als conf.d/default.conf genau dort.
map $http_x_forwarded_proto $eingangs_schema {
default $http_x_forwarded_proto;
"" $scheme;
}
server {
listen 8080;
server_name _;
# ⚠️ nginx erlaubt von Haus aus nur 1 MB pro Anfrage. Der Detector liefert
# seine Bilder per POST ein; bei hoher Aufloesung ist ein Vogelfoto
# schnell groesser, und die Kamera bekaeme ein 413 zurueck, waehrend im
# Django-Protokoll nichts stuende.
client_max_body_size 20m;
# Die Medien direkt vom Volume. Dieselbe Platte, die der Web-Dienst unter
# MEDIA_ROOT=/data/media beschreibt.
location /media/ {
alias /data/media/;
access_log off;
expires 7d;
add_header Cache-Control "public";
# ⚠️ **Hier liegen fremde Dateien auf unserer Herkunft.** Die Medien
# kommen von Kameras und aus Uploads; sie werden zwar beim Annehmen
# neu geschrieben (web/dex/bilder.py), aber dieser Pfad liefert aus,
# was auf der Platte liegt — auch Altbestand von vorher.
#
# nosniff: Der Browser soll den Inhaltstyp nicht erraten. Ohne das
# kann eine Datei, die wie ein Bild heißt, als etwas anderes
# ausgeführt werden, und zwar same-origin mit dem Admin-Cookie.
# Content-Disposition: notfalls herunterladen statt anzeigen.
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
try_files $uri =404;
}
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# ⚠️ Ohne diesen Kopf haelt Django sich fuer unverschluesselt und weist
# jedes Formular mit einem CSRF-Fehler ab -- der Login im Admin
# waere nicht benutzbar. Siehe die Abbildung oben: hier stand
# $scheme, und das war genau die Luege, die es zu vermeiden galt.
proxy_set_header X-Forwarded-Proto $eingangs_schema;
proxy_redirect off;
}
}