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:
commit
c11fe9ab04
189 files changed
+29806
No files matched your search
@@ -0,0 +1,170 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Der Medien-Eingang, mit seiner Konfiguration fest im Image.
|
||||
#
|
||||
# 🔑 Warum nicht einfach die .conf hineinmounten: Einzeldatei-Bind-Mounts sind
|
||||
# auf Coolify brüchig. Schlägt ein Deploy fehl, legt Docker die fehlende
|
||||
# Mount-Quelle als leeres VERZEICHNIS auf dem Host an — der nächste Versuch
|
||||
# mountet dann ein Verzeichnis auf eine Datei und der Container kommt in eine
|
||||
# Absturzschleife, die nach einem Konfigurationsfehler aussieht und keiner
|
||||
# ist. Das hat auf derselben Coolify-Instanz schon einmal einen Abend
|
||||
# gekostet; deshalb hier gebacken statt gemountet.
|
||||
#
|
||||
# Preis dafür: eine Änderung an nginx-media.conf braucht einen Rebuild, nicht
|
||||
# nur einen Neustart. Die Datei ändert sich praktisch nie.
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
COPY deploy/nginx-media.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 8080
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# Detector als Dienst auf dem dedizierten Erkennungs-Host.
|
||||
#
|
||||
# Wird von scripts/setup_detector_host.sh installiert. Von Hand:
|
||||
# sudo cp deploy/vogelalbum-detector.service /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload && sudo systemctl enable --now vogelalbum-detector
|
||||
#
|
||||
# Zugangsdaten in /etc/vogelalbum.env (chmod 600, root:root):
|
||||
# VOGELALBUM_SOURCE=http://vogelkamera.local:8080/stream
|
||||
# VOGELALBUM_API=https://vogelalbum.gaertner.onl
|
||||
# VOGELALBUM_TOKEN=<Token aus 'manage.py camera add'>
|
||||
# VOGELALBUM_SAVE_TO=/var/lib/vogelalbum/rohdaten
|
||||
#
|
||||
# Für eine zweite Kamera: Datei als vogelalbum-detector@.service ablegen, %i im
|
||||
# EnvironmentFile-Pfad verwenden und je Kamera eine /etc/vogelalbum-<name>.env
|
||||
# anlegen. Dann läuft `systemctl enable --now vogelalbum-detector@nistkasten`.
|
||||
|
||||
[Unit]
|
||||
Description=Vogelalbum Detector (Arterkennung am Vogelhaus)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=vogelalbum
|
||||
Group=vogelalbum
|
||||
WorkingDirectory=/opt/vogelalbum
|
||||
EnvironmentFile=/etc/vogelalbum.env
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
# Die Schwellwerte stehen bewusst hier und nicht im Code: sie gehören zur
|
||||
# Kamera, nicht zum Programm. 'gate detector' ignoriert Bewegung — nötig,
|
||||
# weil unsere Kamera auf eine Hecke schaut, die im Wind steht.
|
||||
# --nachtsicht ergaenzen, wenn die Kamera Infrarot hat. Ohne den Schalter
|
||||
# pausiert die Analyse, sobald das Bild zu dunkel zum Erkennen ist.
|
||||
ExecStart=/opt/vogelalbum/venv/bin/python -m detector watch \
|
||||
--gate detector \
|
||||
--classify-every 2 \
|
||||
--cooldown 5 \
|
||||
--min-bird-frames 1 \
|
||||
--verbose
|
||||
|
||||
# Der Stream reißt nachts gern mal ab; einfach wieder hochkommen.
|
||||
Restart=always
|
||||
RestartSec=15
|
||||
|
||||
# Ein Bilderkenner braucht weder Schreibzugriff aufs System noch neue Rechte.
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
ReadWritePaths=/var/lib/vogelalbum
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,70 @@
|
||||
# Ein Detector je Kamera. Der Name hinter dem @ waehlt die Umgebungsdatei:
|
||||
#
|
||||
# systemctl enable --now vogelalbum-detector@futterhaus
|
||||
# -> liest /etc/vogelalbum-futterhaus.env
|
||||
#
|
||||
# Genau so vorgeschlagen im Kopf von vogelalbum-detector.service, umgesetzt am
|
||||
# 24.09.2026 beim Aufsetzen von LXC 112.
|
||||
#
|
||||
# Zugangsdaten je Kamera in /etc/vogelalbum-<name>.env (chmod 600, root:root):
|
||||
# VOGELALBUM_SOURCE=http://vogelkamera.local:8080/stream
|
||||
# VOGELALBUM_API=http://sammlung.local:8000
|
||||
# VOGELALBUM_TOKEN=<Token aus dem Backend>
|
||||
# VOGELALBUM_SAVE_TO=/var/lib/vogelalbum/<name>/rohdaten
|
||||
# VOGELALBUM_LIVE_PORT=8131
|
||||
# VOGELALBUM_KLASSIFIZIERE=2
|
||||
#
|
||||
# 🔑 Ein Dienst je Kamera und nicht einer fuer beide: Der Detector liest
|
||||
# genau einen Bildstrom. Abwechselnd zu lesen hiesse, die Aufmerksamkeit je
|
||||
# Kamera zu halbieren -- und an der Traenke ist ein Vogel womoeglich kuerzer
|
||||
# da als am Futterhaus.
|
||||
|
||||
[Unit]
|
||||
Description=Vogelalbum-Detector fuer Kamera %i
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=vogelalbum
|
||||
Group=vogelalbum
|
||||
WorkingDirectory=/opt/vogelalbum
|
||||
EnvironmentFile=/etc/vogelalbum-%i.env
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
# Die Schwellwerte stehen bewusst hier und nicht im Code: sie gehören zur
|
||||
# Kamera, nicht zum Programm. 'gate detector' ignoriert Bewegung — nötig,
|
||||
# weil unsere Kamera auf eine Hecke schaut, die im Wind steht.
|
||||
# --nachtsicht ergaenzen, wenn die Kamera Infrarot hat. Ohne den Schalter
|
||||
# pausiert die Analyse, sobald das Bild zu dunkel zum Erkennen ist.
|
||||
ExecStart=/opt/vogelalbum/venv/bin/python -m detector watch \
|
||||
--gate detector \
|
||||
--classify-every ${VOGELALBUM_KLASSIFIZIERE} \
|
||||
--cooldown 5 \
|
||||
--min-bird-frames 1 \
|
||||
--verbose
|
||||
|
||||
# Der Stream reißt nachts gern mal ab; einfach wieder hochkommen.
|
||||
Restart=always
|
||||
RestartSec=15
|
||||
|
||||
# ⚠ Die Kameras lassen nur EINEN Zuschauer zu. Beim Neustart muss der alte
|
||||
# Prozess seine Verbindung erst loslassen, sonst bekommt der neue nichts.
|
||||
KillSignal=SIGTERM
|
||||
TimeoutStopSec=20
|
||||
|
||||
# Ein Bilderkenner braucht weder Schreibzugriff aufs System noch neue Rechte.
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
ReadWritePaths=/var/lib/vogelalbum
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,14 @@
|
||||
# Vorlage für die Zugangsdaten der Home-Assistant-Brücke.
|
||||
#
|
||||
# ⚠ Die echte Datei gehört NICHT ins Repo. Kopieren nach
|
||||
# ~/.config/vogelalbum-ha.env und mit chmod 600 schützen.
|
||||
|
||||
VOGELALBUM_URL=http://127.0.0.1:8130
|
||||
VOGELALBUM_LESE_TOKEN=
|
||||
VOGELALBUM_INSTANZ=garten
|
||||
VOGELALBUM_TAKT=10
|
||||
|
||||
MQTT_HOST=
|
||||
MQTT_PORT=1883
|
||||
MQTT_BENUTZER=
|
||||
MQTT_PASSWORT=
|
||||
@@ -0,0 +1,25 @@
|
||||
# Brücke von Vogelalbum nach Home Assistant (MQTT-Discovery).
|
||||
#
|
||||
# ⚠ Läuft dort, wo die WEB-ANWENDUNG steht — sie holt sich den Zustand
|
||||
# über deren Schnittstelle. Zieht die Weboberfläche um, zieht diese
|
||||
# Einheit mit.
|
||||
#
|
||||
# Die Zugangsdaten stehen in ~/.config/vogelalbum-ha.env, nicht hier und
|
||||
# nicht im Repo.
|
||||
|
||||
[Unit]
|
||||
Description=Vogelalbum: Bruecke nach Home Assistant
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/vogelalbum-web
|
||||
EnvironmentFile=%h/.config/vogelalbum-ha.env
|
||||
ExecStart=/opt/vogelalbum-web/venv/bin/python homeassistant/bruecke.py
|
||||
# Stirbt sie, sagt der letzte Wille Home Assistant Bescheid -- und systemd
|
||||
# holt sie zurueck. Beides zusammen heisst: kein stummer Ausfall.
|
||||
Restart=always
|
||||
RestartSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,17 @@
|
||||
# Prueft, ob sich die Kameras noch melden.
|
||||
#
|
||||
# ⚠ Laeuft dort, wo die WEB-ANWENDUNG steht -- nicht beim Detector. Der
|
||||
# Befehl braucht die Datenbank, und ein abgestuerzter Detector kann
|
||||
# seinen eigenen Ausfall ohnehin nicht melden. Wer das Ausbleiben
|
||||
# bemerken soll, muss von aussen schauen.
|
||||
#
|
||||
# Zieht die Weboberflaeche nach Coolify um, zieht diese Einheit mit.
|
||||
|
||||
[Unit]
|
||||
Description=Vogelalbum: melden, wenn eine Kamera schweigt
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=/opt/vogelalbum-web
|
||||
ExecStart=/opt/vogelalbum-web/venv/bin/python web/manage.py stoerungen_pruefen
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Stoerungswache alle zehn Minuten
|
||||
|
||||
[Timer]
|
||||
# Zehn Minuten sind ein Kompromiss: Haeufiger meldet nichts frueher, weil
|
||||
# die Stille-Grenze im Backend ohnehin bei 30 Minuten liegt. Seltener
|
||||
# hiesse, einen Ausfall spaeter zu bemerken als noetig.
|
||||
OnBootSec=5min
|
||||
OnUnitActiveSec=10min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,75 @@
|
||||
#
|
||||
# Fotos bestimmen, die über die App oder das Formular hochgeladen wurden.
|
||||
#
|
||||
# 🔑 **Der Dienst holt sich die Arbeit SELBST ab.** Er fragt die Web-App
|
||||
# jede Minute nach offenen Fotos (GET /api/offen/) und meldet das Ergebnis
|
||||
# zurück (POST /api/offen/<id>/). Er braucht deshalb weder eine erreichbare
|
||||
# Adresse noch einen offenen Port — dieselbe Richtung wie bei der
|
||||
# Fernkonfiguration, und aus demselben Grund: Der Erkennungs-Host steht im
|
||||
# Heimnetz hinter einem Router, die Web-App nicht.
|
||||
#
|
||||
# ⚠️ **Ohne diesen Dienst bleibt JEDES App-Foto unbestimmt liegen.** Die App
|
||||
# zeigt dann dauerhaft "wird bestimmt …", und zwar ohne Fehler — der Upload
|
||||
# war ja erfolgreich. Das sieht nach einem Fehler in der App aus und liegt
|
||||
# auf einer anderen Maschine.
|
||||
#
|
||||
# Gehört auf denselben Host wie die Kamera-Detektoren: Dort liegen die
|
||||
# Modelle und das venv schon.
|
||||
#
|
||||
# cp deploy/vogelalbum-warteschlange.service /etc/systemd/system/
|
||||
# # Zugangsdaten anlegen (chmod 600!):
|
||||
# cat > /etc/vogelalbum-warteschlange.env <<ENV
|
||||
# VOGELALBUM_API=https://<deine-instanz>
|
||||
# VOGELALBUM_TOKEN=<Token einer Kamera -- zum Zurueckmelden>
|
||||
# VOGELALBUM_LESE_TOKEN=<Lese-Token -- zum Abrufen der offenen Fotos>
|
||||
# ENV
|
||||
# chmod 600 /etc/vogelalbum-warteschlange.env
|
||||
# systemctl enable --now vogelalbum-warteschlange
|
||||
#
|
||||
# ✅ **Einmal von Hand auslösen, bevor man sich darauf verlässt**: ein Foto
|
||||
# hochladen und `journalctl -u vogelalbum-warteschlange -f` mitlesen. Ein
|
||||
# Dienst, der sauber läuft und nichts tut, sieht im Status genauso aus wie
|
||||
# einer, der arbeitet.
|
||||
#
|
||||
# 🔑 Zum Prüfen ein Bild OHNE Vogel nehmen. Dann durchläuft es die ganze
|
||||
# Kette, aber es entsteht keine Sichtung — ein Testvogel in einer echten
|
||||
# Sammlung muss hinterher wieder heraus.
|
||||
|
||||
[Unit]
|
||||
Description=Vogelalbum: Fotos aus der App bestimmen
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=vogelalbum
|
||||
Group=vogelalbum
|
||||
WorkingDirectory=/opt/vogelalbum
|
||||
EnvironmentFile=/etc/vogelalbum-warteschlange.env
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
# --alle 60: Häufiger bringt nichts. Ein gerade hochgeladenes Foto ist beim
|
||||
# nächsten Durchgang dran, und die App wartet ohnehin mit wachsenden
|
||||
# Abständen auf das Ergebnis.
|
||||
ExecStart=/opt/vogelalbum/venv/bin/python -m detector warteschlange --alle 60
|
||||
|
||||
Restart=always
|
||||
RestartSec=15
|
||||
|
||||
KillSignal=SIGTERM
|
||||
TimeoutStopSec=20
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
ReadWritePaths=/var/lib/vogelalbum
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in new issue
Block a user