Files
vogelalbum/deploy/COOLIFY.md
T
christian c11fe9ab04 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.
2026-10-15 14:30:02 +00:00

171 lines
7.0 KiB
Markdown

# Die Web-App auf Coolify stellen
Für den Umzug der Sammlung von einem Rechner im Heimnetz auf eine öffentlich
erreichbare Instanz. Der Detector zieht **nicht** mit um — er braucht
Sichtverbindung zur Kamera und bleibt zu Hause; er bekommt am Ende nur eine
neue Adresse.
Vorlage ist `docker-compose.yml` im Wurzelverzeichnis, als
**Docker-Compose-Anwendung aus dem Git-Repository**.
## Was dieses Setup von einem Ein-Container-Deploy unterscheidet
Zwei Dienste, und der Eingang ist **nicht** die Django-App:
```
Traefik ──▶ medien (nginx, Port 8080) ──┬──▶ /media/… direkt vom Volume
└──▶ alles andere ▶ web (Gunicorn, 8000)
```
🔑 **Die Domain muss auf `medien` zeigen, nicht auf `web`.** Django liefert
unter `DJANGO_DEBUG=0` keine Medien aus, und Coolify stellt keinen Webserver
davor, der das übernehmen könnte — Traefik routet auf einen Container und kennt
das Volume nicht. Zeigt die Domain auf `web`, läuft die Seite scheinbar
normal, aber **jedes Vogelfoto ist 404**: die Sammlung ist das Einzige, was
dieses Projekt ist, und sie wäre leer. Die ausführliche Begründung steht in
`nginx-media.conf`.
## 1. Anwendung anlegen
* Typ **Docker Compose**, Quelle das Git-Repository, Branch `main`,
Compose-Datei `docker-compose.yml`.
* Ist das Repository privat, muss Coolify über die Gitea-Anbindung
(GitHub-App-Äquivalent bzw. Deploy-Key) darauf zugreifen können — dasselbe
Verfahren wie bei den anderen Projekten auf derselben Instanz.
## 2. Domain auf den Dienst `medien`
In Coolify hat bei einer Compose-Anwendung **jeder Dienst sein eigenes
Domain-Feld**. Die Domain gehört an `medien` (Port 8080). `web` bekommt
**keine** Domain — der Dienst wird nur intern angesprochen.
Danach prüfen, dass der DNS-Eintrag der Domain auf die Coolify-Maschine zeigt.
## 3. Umgebungsvariablen
Pflicht:
| Variable | Wert |
|---|---|
| `DJANGO_ALLOWED_HOSTS` | die Domain, z. B. `vogelalbum.gaertner.onl` |
| `DJANGO_CSRF_TRUSTED_ORIGINS` | dieselbe Domain **mit** `https://` |
Optional, aber meist gewollt:
| Variable | Wert |
|---|---|
| `DJANGO_SECRET_KEY` | leer lassen ist in Ordnung — der Entrypoint erzeugt einen und legt ihn aufs Volume, er bleibt über Neustarts stabil |
| `VOGELALBUM_INGEST_TOKEN` | **leer lassen**, wenn jede Kamera im Admin registriert ist und ihren eigenen Token hat. Leer schaltet den Sammel-Token ab, und das ist für eine öffentliche Instanz das Richtige |
| `VOGELALBUM_LESE_TOKEN` | nur nötig, wenn Home Assistant `/api/zustand/` abfragt. **Leer heißt: der Endpunkt antwortet mit 401** — wer die Einbindung zu Hause schon hat und das hier vergisst, sucht den Fehler im Heimnetz |
Nicht anfassen, die stehen fest in `docker-compose.yml`:
`DJANGO_DEBUG=0`, `VOGELALBUM_LIVE_ERLAUBT=0`, `SQLITE_DIR=/data`,
`MEDIA_ROOT=/data/media`.
⚠️ **Lässt man Coolify ein Geheimnis erzeugen, darf es kein `$` enthalten.**
Docker Compose liest `$xxx` als Variable und zerstört den Wert dabei still.
Selbst erzeugen mit `openssl rand -base64 32`.
🔒 `VOGELALBUM_LIVE_ERLAUBT=0` ist der Grund, warum auf der öffentlichen Instanz
kein Livebild einschaltbar ist — auch nicht mit einem Fehlklick im Admin. Ein
Bild aus dem eigenen Garten soll nicht an einem Häkchen hängen.
## 4. Volume
Das Compose-File deklariert das benannte Volume `vogelalbum-data` und hängt es in
beide Dienste (`web` beschreibbar, `medien` nur lesend). Coolify übernimmt das;
es muss nichts von Hand angelegt werden.
⚠️ **Dort liegen Datenbank *und* Fotos.** `SQLITE_DIR` und `MEDIA_ROOT` zeigen
beide auf `/data`. Fehlt das Volume oder zeigt eine der beiden Variablen
woandershin, ist die Sammlung nach jedem Deploy weg.
## 5. Erster Start
Der Entrypoint erledigt `migrate`, `collectstatic`, `seed_dex` und holt
anschließend die Referenzfotos der Arten von Wikipedia.
⏱️ **Der erste Start dauert deshalb mehrere Minuten** — rund 150 Arten, und
Wikimedia drosselt, weshalb zwischen den Abrufen gewartet wird. Das ist kein
hängender Deploy. Jeder weitere Start überspringt alle Arten, die schon ein
Foto haben, und kostet keine einzige Anfrage mehr.
Danach steht eine leere Sammlung: Artenkatalog vollständig, keine Sichtungen.
## 6. Eine bestehende Sammlung mitnehmen
Wer von einer Heiminstanz umzieht, bringt Datenbank und Fotos mit. Beides liegt
beim Umzugspaket im selben Layout, in dem es auf dem Volume liegen muss:
```
db.sqlite3
media/sightings/…
media/crops/…
media/referenz/…
```
🔑 **Eine 1:1-Kopie, keine Neueinlieferung über die API.** Nur so kommen die
Kamera-Tokens, die Schwellwerte je Kamera, Handbestimmungen, Abzeichen und
Erstfang-Vermerke unverändert mit. Die Kameras zu Hause liefern danach mit
**ihrem bisherigen Token** weiter — es muss nichts neu registriert werden.
Auf der Heiminstanz ein konsistentes Paket bauen (`VACUUM INTO` kopiert die
Datenbank sauber, auch während die App läuft):
```sh
cd /pfad/zum/vogelalbum
python - <<'PY'
import sqlite3
sqlite3.connect('web/db.sqlite3').execute("VACUUM INTO '/tmp/db.sqlite3'")
PY
mkdir -p /tmp/umzug/media
mv /tmp/db.sqlite3 /tmp/umzug/
cp -r web/media/sightings web/media/crops web/media/referenz /tmp/umzug/media/
tar czf /tmp/vogelalbum-daten.tgz -C /tmp/umzug db.sqlite3 media
```
Das Paket auf die Coolify-Maschine legen, dann dort:
```sh
# Anwendung anhalten (in Coolify: Stop) — SQLite nicht im Betrieb tauschen.
VOL=$(docker volume ls -q | grep vogelalbum-data | head -1)
docker run --rm -v "$VOL":/data -v /root:/rein:ro alpine \
sh -c 'tar xzf /rein/vogelalbum-daten.tgz -C /data && chown -R 0:0 /data && ls -la /data'
# Anwendung wieder starten.
```
Der Container läuft als `root`, daher `chown -R 0:0`.
## 7. Nach dem Umzug prüfen
```sh
curl -sI https://DEINE-DOMAIN/ | head -1 # 200
curl -sI https://DEINE-DOMAIN/sichtungen/ | head -1
```
Und im Browser: **eine Sichtungskarte muss ihr Foto zeigen.** Ein 404 auf
`/media/…` heißt, die Domain hängt am falschen Dienst (Schritt 2).
## 8. Die Kameras zu Hause umstellen
Jeder Detector und jede weitere Anbindung zeigt noch auf die alte Adresse im
Heimnetz. Umzustellen ist:
* **jeder Detector**: `VOGELALBUM_API` auf `https://DEINE-DOMAIN` (in
`~/.vogelalbum.env` bzw. `--api`), danach neu starten. Laufen zwei Kameras,
gibt es zwei Detector-Prozesse — womöglich auf zwei verschiedenen Maschinen.
* **Home Assistant**, falls eingebunden: neue Adresse und `VOGELALBUM_LESE_TOKEN`.
Dass eine Kamera angekommen ist, sieht man im Admin an `letzter_kontakt` —
der Konfigurationsabruf setzt es, auch ohne einen einzigen Vogel.
## 9. Was bewusst nicht mit umzieht
* **Der Detector.** Er braucht die Modelle, Rechenzeit und das Kameranetz.
* **Das Livebild.** `VOGELALBUM_LIVE_ERLAUBT=0`, siehe oben.
* **Der Störungswächter** (`vogelalbum-stoerungen.service`). 🔑 Er darf nicht auf
derselben Maschine laufen wie das, was er überwacht, und nicht an derselben
Stromversorgung: beim Stromausfall am 30.09.2026 war er von 22:31 bis 06:19
stumm, weil er selbst tot war. Wer das Ausbleiben von Sichtungen bemerken
soll, muss von außen schauen.