# 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; } }