Umgebungsvariablen¶
Alle Konfigurationsparameter des Kamerplanter-Backends werden über Umgebungsvariablen gesteuert. Die Variablen werden von pydantic-settings geladen — Groß-/Kleinschreibung ist nicht relevant.
Lokale Konfiguration
Für die Docker-Compose-Umgebung alle Werte in eine .env-Datei im Repository-Wurzelverzeichnis eintragen. Eine Vorlage liegt als .env.example bereit:
Datenbankverbindung¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
ARANGODB_HOST | localhost | Ja | Hostname oder IP-Adresse der ArangoDB-Instanz |
ARANGODB_PORT | 8529 | Nein | TCP-Port der ArangoDB |
ARANGODB_SCHEME | http | Nein | https für eine ArangoDB, die TLS selbst anbietet (externe oder verwaltete Instanz); andere Werte verweigern den Start |
ARANGODB_DATABASE | kamerplanter | Ja | Name der Zieldatenbank |
ARANGODB_USERNAME | root | Ja | Datenbanknutzer |
ARANGODB_PASSWORD | — | Ja | Passwort des Datenbanknutzers |
ARANGO_ROOT_PASSWORD | — | Ja* | Root-Passwort für den ArangoDB-Container (nur Docker) |
*ARANGO_ROOT_PASSWORD wird direkt an den ArangoDB-Container übergeben und ist für den Start der Datenbank erforderlich.
Produktionspasswörter
Verwenden Sie niemals den Standardwert rootpassword in produktiven Umgebungen. Generieren Sie sichere Passwörter: openssl rand -hex 32
Cache und Aufgaben-Queue¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
REDIS_URL | redis://localhost:6379/0 | Ja | Verbindungs-URL für Redis oder Valkey (Celery Broker und Backend-Cache) |
WORKER_READY_FILE | — | Nein | Nur Celery-Worker. Leer (Standard, Docker Compose) ändert nichts. Gesetzt — das Helm-Chart setzt /tmp/worker-ready — prüft der Worker beim Start seine ArangoDB-Anmeldung, versucht es bis zu 60 Sekunden lang und beendet sich, wenn sie nicht gelingt; er nimmt dann keine Aufgabe an. Läuft sein Consumer, schreibt er die Datei, beim Herunterfahren entfernt er sie. Startup- und Readiness-Probe des Charts prüfen sie. |
Format: redis://[user]:[password]@[host]:[port]/[db]
Beispiele:
redis://localhost:6379/0 # Lokal ohne Auth
redis://:meinpasswort@redis:6379/0 # Mit Passwort
rediss://user:pass@redis-host:6380/1 # TLS (rediss://)
Sicherheit und Authentifizierung¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
JWT_SECRET_KEY | change-me-in-production-... | Ja | Geheimer Schlüssel für JWT-Signierung (HS256) |
JWT_ALGORITHM | HS256 | Nein | JWT-Signaturalgorithmus |
ACCESS_TOKEN_EXPIRE_MINUTES | 15 | Nein | Gültigkeitsdauer des JWT-Access-Tokens in Minuten |
REFRESH_TOKEN_EXPIRE_DAYS | 30 | Nein | Gültigkeitsdauer des Refresh-Tokens in Tagen |
SESSION_TOKEN_EXPIRE_HOURS | 24 | Nein | Gültigkeitsdauer serverseitiger Session-Tokens in Stunden. |
FERNET_KEY | — | Ja | Fernet-Schlüssel zum Verschlüsseln von OIDC-Provider-Secrets und Integrations-Tokens. Unabhängig davon, ob OIDC genutzt wird — der Startup-Gate verweigert den Produktionsstart bei leerem Wert (AP-4, INF-S5). Muss ein gültiger Fernet-Schlüssel sein: 32 Bytes, url-safe base64-kodiert (44 Zeichen) — erzeugt z. B. mit python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())". Denselben Gate prüft inzwischen auch der Celery-Worker beim Start und verweigert bei DEBUG=false ebenfalls den Start, wenn der Wert fehlt oder kein gültiger Fernet-Schlüssel ist — Backend und Celery-Worker müssen denselben Schlüssel verwenden. Wird ein gespeicherter Wert mit einem abweichenden Schlüssel entschlüsselt, schlägt das laut fehl (SecretKeyMismatchError), statt den Chiffretext unbemerkt als Zugangsdaten weiterzugeben. Verschlüsselt sind auch der Home-Assistant-Token, der Pl@ntNet-Key und die Apprise-URLs der Nutzer (#2113): Nach einem Schlüsselwechsel sind sie nicht mehr lesbar und müssen neu eingetragen werden. |
REQUIRE_EMAIL_VERIFICATION | true | Nein | E-Mail-Verifikation bei Registrierung erzwingen. Eine Installation ohne ausgehenden Mailversand setzt false ausdrücklich |
REGISTRATION_MODE | open | Nein | Wer ein Konto anlegen darf (lokal und erste OIDC-Anmeldung): open (jeder), invite_only (nur mit offener E-Mail-Einladung für die Adresse), closed (niemand, auch nicht mit Einladung). Bestehende Konten melden sich in jedem Modus an. Ein unbekannter Wert verweigert den Start |
REGISTRATION_ALLOWED_DOMAINS | — (leer) | Nein | Optionale kommagetrennte E-Mail-Domains (z. B. verein.example,garten.example), exakt, ohne Subdomains. Leer = jede Domain. Eine E-Mail-Einladung für die Adresse ist die Ausnahme; über OIDC zählt nur eine vom Anbieter bestätigte Adresse |
TENANT_MAX_MEMBERS_CEILING | 50 | Nein | Plattform-Obergrenze für das Mitgliederlimit jedes Mandanten (mindestens 1). Wirksam ist min(max_members, TENANT_MAX_MEMBERS_CEILING); max_members darüber wird abgelehnt (422), eine neue Organisation ohne Angabe erhält diesen Wert. Ein voller Mandant verweigert weitere Beitritte mit 422 MEMBER_LIMIT_REACHED. Senken entfernt niemanden |
TENANT_MAX_SERVICE_ACCOUNTS | 20 | Nein | Wie viele aktive Service Accounts ein Mandant haben darf (mindestens 1). Darüber antwortet das Anlegen mit 422 SERVICE_ACCOUNT_LIMIT_REACHED. Ein Service Account belegt zusätzlich einen Platz im Mitgliederlimit. Senken entfernt keinen |
TENANT_MAX_ORGANIZATIONS_PER_USER | 10 | Nein | Wie viele Organisationen ein Konto besitzen darf, gezählt in jedem Zustand außer deleted (mindestens 0). Darüber antwortet POST /tenants mit 422 ORGANIZATION_LIMIT_REACHED. 0 heißt: Kein Konto gründet selbst eine Organisation. Eine Grenze des Kontos, kein Plan-Kontingent |
TENANT_INVITATION_EMAILS_PER_DAY | 50 | Nein | Wie viele E-Mail-Einladungen ein Konto in 24 Stunden verschicken darf, über alle seine Mandanten zusammen (mindestens 1). Jede E-Mail-Einladung geht an eine Adresse, die der Einladende frei wählt; darüber antwortet POST /api/v1/tenants/{slug}/invitations/email mit 429 RATE_LIMIT_EXCEEDED, und es wird nichts angelegt und nichts verschickt. Gezählt wird aus den gespeicherten Einladungen (Issue #2162) |
TENANT_EXPORTS_PER_DAY | 3 | Nein | Wie viele Mandanten-Exporte ein Mandant in 24 Stunden anfordern darf, jeder Zustand gezählt, auch fehlgeschlagene (mindestens 1). Darüber antwortet POST /api/v1/tenants/{slug}/exports mit 429 RATE_LIMIT_EXCEEDED. Siehe Mandanten exportieren und importieren (Issue #2138) |
TENANT_IMPORT_MAX_UPLOAD_MB | 512 | Nein | Größtes Mandanten-Archiv in MiB, das POST /api/v1/admin/platform/tenant-imports annimmt (mindestens 1); entpackt darf es unabhängig davon höchstens 1 GiB sein. Darüber 413 (Issue #2138). Eigene Upload-Grenze dieser Route (+ 1 MiB) — jede andere Route bleibt bei STORAGE_MAX_FILE_SIZE_MB + 1 MiB — und höchstens UPLOAD_SPOOL_BUDGET_MB, weil der Upload vor der Route in /tmp landet; mit den Standardwerten etwa 51 MiB. Helm: tenantExport.importMaxUploadMb. Größe planen: Archivgröße und /tmp |
TENANT_EXPORT_SIGNING_SECRET | — (leer) | Nein | Geheimnis, mit dem Mandanten-Archive signiert und geprüft werden (HMAC-SHA256 über das Manifest). Leer: wie bisher aus JWT_SECRET_KEY abgeleitet. Gesetzt: mindestens 32 Zeichen (openssl rand -hex 32), sonst startet das Backend nicht; vorher signierte Archive gelten beim Import als nicht verifiziert. Backend und Celery-Worker brauchen denselben Wert; Helm liest ihn optional aus tenantExport.signingSecret (Standard kamerplanter-secrets, Schlüssel TENANT_EXPORT_SIGNING_SECRET). Nie in values.yaml (Issue #2138) |
RATE_LIMIT_TENANT_IMPORT | 10/hour | Nein | Mengenbegrenzung für POST /api/v1/admin/platform/tenant-imports, je Konto (Issue #2138) |
HIBP_ENABLED | false | Nein | "Have I Been Pwned"-Prüfung bei Passwortänderung aktivieren |
COOKIE_SECURE | true | Nein | Setzt das Secure-Flag auf dem Refresh-Token-Cookie. Nur für reine HTTP-E2E-Testumgebungen ohne TLS auf false setzen — in Produktion immer true belassen. |
Seit es Pläne gibt (Issue #2142), sind TENANT_MAX_MEMBERS_CEILING, TENANT_MAX_SERVICE_ACCOUNTS, STORAGE_TENANT_QUOTA_MB, STORAGE_MAX_PHOTOS_PER_INSTANCE und die drei AI_BUDGET_* der Wert, den ein Plan mit leerem Kontingent übernimmt — beim Standardplan standard also für jeden Mandanten. Ein Plan kann davon abweichen; nur TENANT_MAX_MEMBERS_CEILING bleibt eine harte Obergrenze. Siehe API-Referenz: Pläne und Kontingente.
JWT_SECRET_KEY in Produktion ändern
Der Standardwert change-me-in-production-use-openssl-rand-hex-32 darf in produktiven Umgebungen nicht verwendet werden. Generieren Sie einen sicheren Wert:
JWT_SECRET_KEY machen alle aktiven Tokens ungültig — alle Nutzer werden abgemeldet. Datenschutz & DSGVO (REQ-025 / NFR-011)¶
Diese Variablen steuern die datenschutzrechtlich vorgeschriebene Löschung/Anonymisierung personenbezogener Daten (siehe Datenschutz (DSGVO)) und sind vom Betriebsmodus unabhängig — sie gelten sowohl im Light- als auch im Full-Modus.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
ERASURE_TOMBSTONE_SALT | — | Ja | Hochentropisches Geheimnis (mindestens 32 Zeichen) zur Pseudonymisierung gelöschter Nutzerkonten (Tombstone-Hash, Löschantrags-Schlüssel und Mandanten-Slug-Digest, NFR-011 §4). Dieser Wert darf nach der ersten Kontolöschung nie mehr geändert werden. Der Startup-Gate verweigert den Produktionsstart, wenn der Wert leer oder kürzer als 32 Zeichen ist — unabhängig vom Betriebsmodus. Erzeugen mit openssl rand -hex 32. |
LOG_PSEUDONYM_SALT | — | Ja | Hochentropisches Geheimnis (mindestens 32 Zeichen), das ausschließlich die Log-Pseudonyme verschlüsselt: die subject=-Referenzen (sub_…) und die email_sha256-Digests auf Protokollzeilen sowie das Herkunftsfeld requested_by_subject von Lösch- und Mandanten-Löschnachweisen (NFR-011 §3.4). Getrennt von ERASURE_TOMBSTONE_SALT, weil dieser Wert — anders als der Tombstone-Salt — rotiert werden darf. Der Startup-Gate verweigert den Produktionsstart für Backend und Celery-Worker, wenn der Wert leer oder kürzer als 32 Zeichen ist — unabhängig vom Betriebsmodus. Erzeugen mit openssl rand -hex 32; sollte sich von ERASURE_TOMBSTONE_SALT unterscheiden (wird nicht erzwungen). |
PRIVACY_DATA_CONTROLLER_NAME | Kamerplanter Operator | Nein | Name des datenschutzrechtlich Verantwortlichen, erscheint in Export- und Auskunftsdokumenten. |
PRIVACY_DATA_CONTROLLER_EMAIL | privacy@kamerplanter.example | Nein | Kontakt-E-Mail des Verantwortlichen für DSGVO-Anfragen. |
RETENTION_SOFT_DELETE_RETENTION_DAYS | 90 | Nein | Frist, nach der ein zur Löschung markiertes (soft-gelöschtes) Konto endgültig (Hard-Delete) entfernt wird (NFR-011 R-01). Minimum: 1, Maximum: 90. Älterer Name PRIVACY_HARD_DELETE_AFTER_DAYS wird weiterhin akzeptiert; sind beide gesetzt, gewinnt der neue Name. |
RETENTION_UNVERIFIED_ACCOUNT_DAYS | 7 | Nein | Anzahl Tage nach der Registrierung, nach denen ein nie bestätigtes Konto automatisch gelöscht wird (NFR-011 R-02). Minimum: 1, Maximum: 7. |
RETENTION_UNVERIFIED_LOCAL_REAP_ENABLED | false | Nein | Freigabe-Schalter für R-02: Ist er false, zählt die Bereinigung lokal registrierte, nie bestätigte Konten nur (Trockenlauf, local_registrations_pending); bei true löscht sie diese ebenfalls. Erst setzen, wenn du die gezählte Anzahl gesehen hast (NFR-011 R-02). |
RETENTION_IP_ANONYMIZATION_DAYS | 7 | Nein | Anzahl Tage nach Ausstellung einer Session, nach denen ihre IP-Adresse anonymisiert wird (IPv4: letztes Oktett → 0; NFR-011 R-03). Minimum: 1, Maximum: 7. Betrifft nur Session-IP-Adressen, nicht die IP-Adresse eines Consent Records. |
RETENTION_EXPORT_FILE_RETENTION_HOURS | 72 | Nein | Aufbewahrungsdauer eines generierten Datenexports (Art. 15/20 DSGVO), bevor die Datei automatisch gelöscht wird (NFR-011 R-05). Minimum: 1, Maximum: 72. Älterer Name PRIVACY_EXPORT_RETENTION_HOURS wird weiterhin akzeptiert; sind beide gesetzt, gewinnt der neue Name. |
RETENTION_ERASURE_AUDIT_RETENTION_YEARS | 3 | Nein | Anzahl Jahre, die ein abgeschlossener Löschungs-Antrag (erasure_requests, status=completed) als Rechenschaftsnachweis (Art. 5 Abs. 2 DSGVO) aufbewahrt wird, bevor er endgültig gelöscht wird (NFR-011 R-06). Gezählt in Kalenderjahren. Minimum: 1, Maximum: 3. |
RETENTION_EMAIL_CHANGE_RETENTION_HOURS | 24 | Nein | Gültigkeitsdauer des Bestätigungslinks bei einer E-Mail-Adressänderung (NFR-011 R-07); danach wird die Anfrage auf den Status expired gesetzt, ein Hard-Delete findet nicht statt. Minimum: 1, Maximum: 24. Älterer Name PRIVACY_EMAIL_CHANGE_TTL_HOURS wird weiterhin akzeptiert; sind beide gesetzt, gewinnt der neue Name. |
RETENTION_EMAIL_CHANGE_REVERT_DAYS | 7 | Nein | Gültigkeitsdauer des Rückgängig-Links, den die vorherige Adresse nach einer bestätigten E-Mail-Änderung erhält (NFR-011 R-07a). Danach nullt derselbe stündliche Retention-Task wie bei R-07 die Felder previous_email und den Hash des Rückgängig-Tokens. Minimum: 1, Maximum: 7. |
RETENTION_CONSENT_RETENTION_YEARS | 3 | Nein | Jahre nach dem Widerruf, nach denen ein Consent Record endgültig gelöscht wird (NFR-011 R-04). Minimum: 1, Maximum: 3. Ein höherer Wert verhindert den Start von Backend und Worker. |
RETENTION_CONSENT_IP_ANONYMIZATION_DAYS | 7 | Nein | Tage nach der Aufzeichnung, nach denen die IP-Adresse eines Consent Records anonymisiert wird (NFR-011 R-04a). Minimum: 1, Maximum: 7. Ein höherer Wert verhindert den Start von Backend und Worker. |
RETENTION_INVITATION_RETENTION_DAYS | 30 | Nein | Tage nach Ablauf, nach denen eine abgelaufene Einladung endgültig gelöscht wird (NFR-011 R-12). Minimum: 1, Maximum: 30. Ein höherer Wert verhindert den Start von Backend und Worker. |
RETENTION_ERASURE_MEMBER_NOTICE_DAYS | 7 | Nein | Tage, die die endgültige Löschung eines Kontos mit geteiltem persönlichem Garten nach der Benachrichtigung der anderen Mitglieder wartet, wenn diese erst nach der Terminierung des Löschantrags benachrichtigt wurden (Antrag aus der Zeit vor #1824 oder fehlgeschlagene erste E-Mail; NFR-011 R-01a). Eine Sofortlöschung durch einen Administrator wartet nie. Minimum: 1, Maximum: 7. Ein höherer Wert verhindert den Start von Backend und Worker. |
RETENTION_TENANT_ERASURE_GRACE_DAYS | 90 | Nein | Tage, um die eine angenommene Mandantenlöschung aufgeschoben wird (NFR-011 R-01b). In dieser Zeit ist der Mandant gesperrt, die Mitglieder sind mit Datum benachrichtigt, und die Verwaltung kann die Löschung abbrechen. 0 löscht sofort (Self-Hosted mit einem Haushalt). Minimum: 0, Maximum: 90. Ein höherer Wert verhindert den Start von Backend und Worker. |
RETENTION_HARVEST_DATA_MIN_RETENTION_YEARS | 5 | Nein | Anzahl Jahre ab harvest_date, nach denen die bei einer Mandantenlöschung aufbewahrten Erntedaten (Ernte, Qualitätsbewertung, Ertragskennzahl) endgültig gelöscht werden (NFR-011 R-16). Minimum: 5 (gesetzliche Mindestfrist nach CanG). |
RETENTION_TREATMENT_MIN_RETENTION_YEARS | 3 | Nein | Dasselbe für Behandlungsanwendungen, gezählt ab applied_at (NFR-011 R-17). Minimum: 3 (PflSchG §11). |
RETENTION_INSPECTION_MIN_RETENTION_YEARS | 3 | Nein | Dasselbe für Inspektionsprotokolle, gezählt ab inspected_at (NFR-011 R-18). Minimum: 3 (PflSchG §11). |
Ein Wert oberhalb des Maximums bricht den Start von API und Worker ab (die Meldung nennt Variable und Grenzwert, nie den Wert). Das Maximum ist der NFR-011-Wert: Fristen dürfen zur Datenminimierung verkürzt, aber nicht verlängert werden. Wird ein älterer PRIVACY_*-Name gesetzt, schreibt der Start eine Warnung; sind beide Namen mit verschiedenen Werten gesetzt, eine Fehlermeldung (der RETENTION_*-Wert gilt).
Für RETENTION_SOFT_DELETE_RETENTION_DAYS, RETENTION_EXPORT_FILE_RETENTION_HOURS und RETENTION_EMAIL_CHANGE_RETENTION_HOURS waren die älteren PRIVACY_*-Namen zwar bereits dokumentiert, hatten aber keine Wirkung — der Code nutzte feste Werte. Sie funktionieren jetzt tatsächlich und bleiben zusätzlich als Alias gültig.
ERASURE_TOMBSTONE_SALT — Boot-Blocker in Produktion, darf nie wechseln
Anders als die meisten anderen Variablen auf dieser Seite ist ERASURE_TOMBSTONE_SALT kein optionales Feature-Flag: Das Backend startet in Produktion (DEBUG=false) grundsätzlich nicht, wenn dieser Wert fehlt oder zu kurz ist — unabhängig davon, ob DSGVO-Löschanfragen aktiv genutzt werden. Dieser Salt schlüsselt den Tombstone-Hash gelöschter Konten, den Löschantrags-Schlüssel und den Mandanten-Slug-Digest — niemals die Log-Pseudonyme (dafür ist LOG_PSEUDONYM_SALT zuständig). Er darf sich nach der ersten Kontolöschung nie mehr ändern, sonst werden bestehende Tombstones unlesbar. Auch der Celery-Worker beendet sich beim Start, wenn der Salt fehlt oder zu kurz ist; gib ihm denselben Wert und dieselbe DEBUG-Einstellung wie dem Backend.
Mit DEBUG=true startet das Backend auch ohne gültigen Wert, etwa im lokalen Entwicklungs-Stack. Die Kontolöschung verweigert dann trotzdem jeden Lauf, bevor sie etwas anfasst: Löscht ein Platform-Admin ein Konto, antwortet das Backend mit 503. Ein Löschantrag nach Art. 17 wird zwar angenommen, der tägliche Lauf führt ihn aber nicht aus. Der Antrag bleibt als partially_completed offen, bis du einen gültigen Salt setzt. Details zu allen unbedingt erforderlichen Secrets: Konfigurationsmatrix — Pflicht-Secrets je aktivierter Funktion.
LOG_PSEUDONYM_SALT — Boot-Blocker in Produktion, für Backend und Worker gleich
LOG_PSEUDONYM_SALT ist ebenfalls kein optionales Feature-Flag: Backend und Celery-Worker verweigern in Produktion (DEBUG=false) den Start, wenn dieser Wert fehlt oder kürzer als 32 Zeichen ist. Er schlüsselt ausschließlich die Log-Pseudonyme — die subject=-Referenz und den email_sha256-Digest auf Protokollzeilen sowie das Herkunftsfeld requested_by_subject von Lösch- und Mandanten-Löschnachweisen. Gib beiden Prozessen denselben Wert. Mit DEBUG=true starten beide auch ohne gültigen Wert; die Konto- und Mandantenlöschung verweigert dann aber jeden Lauf (503), weil ihr Nachweis sonst keine Referenz auf das auslösende Konto trüge.
Rotation. Im Gegensatz zu ERASURE_TOMBSTONE_SALT darf dieser Salt gewechselt werden: Setze den neuen Wert in Backend und Worker (Kubernetes-Secret kamerplanter-secrets bzw. .env bei Docker Compose) und starte beide neu. Danach korrelieren Protokollzeilen und requested_by_subject-Werte von vor dem Wechsel nicht mehr mit späteren — wer nur den neuen Salt kennt, kann alte Referenzen keinem Konto mehr zuordnen. Auch welches Admin-Konto eine gespeicherte Löschung ausgelöst hat, lässt sich danach nur noch mit dem alten Salt nachrechnen — bewahre ihn gesichert auf, solange der Löschnachweis aufbewahrt wird (ein Jahr), wenn diese Zuordnung prüfbar bleiben soll. Keine Abfrage hängt vom Log-Salt ab, eine Rotation hat also sonst keine Nebenwirkungen. Der Wert sollte sich von ERASURE_TOMBSTONE_SALT unterscheiden; das wird nicht erzwungen.
Betriebsmodus¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
KAMERPLANTER_MODE | full | Nein | Betriebsmodus: full (Auth + Mandanten) oder light (kein Auth, lokale Einzelnutzung) |
APPRISE_ALLOW_PRIVATE_TARGETS | abhängig vom Modus: false in full, true in light | Nein | Ob Apprise-Ziele (Gotify, Matrix, ntfy-Server) in privaten Netzen liegen dürfen: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, fc00::/7. Geprüft beim Speichern und beim Senden. Ungesetzt entscheidet KAMERPLANTER_MODE; true oder false überschreibt das in beide Richtungen. Im full-Modus erreicht ein privates Ziel die Dienste im Cluster (API-Server, ArangoDB, Valkey), daher aus. Anders als HA_ALLOW_PRIVATE_ENDPOINT trägt hier jeder Nutzer die Adresse ein. DEBUG lockert die Regel nicht; Loopback, Link-Local und Cloud-Metadaten bleiben immer gesperrt. Siehe Helm: Egress für Benachrichtigungskanäle. |
DEBUG | false | Nein | Debug-Logging aktivieren (verbose, nie in Produktion). Deaktiviert zusätzlich den Startup-Gate für Produktions-Secrets — niemals in Produktion setzen. |
FRONTEND_URL | http://localhost:5173 | Nein | URL des Frontends (wird für E-Mail-Links verwendet) |
APP_BASE_URL | http://localhost:5173 | Nein | Basis-URL für QR-Codes auf Pflanzen-Etiketten (Druckansichten, siehe Druckansichten & Export). In Produktion auf die öffentlich erreichbare Frontend-URL setzen, sonst zeigen gedruckte QR-Codes auf localhost. Bildet auch die Rückruf-URL der OAuth/OIDC-Anmeldung und der erneuten Anmeldung zur Bestätigung ({APP_BASE_URL}/api/v1/auth/oauth/{slug}/callback) — diese URL beim Identity-Provider hinterlegen. Bildet außerdem die Abo-URL der iCal-Feeds (ical_url, REQ-015, #2226); ein http://-Wert wird dort zu https:// angehoben, außer für localhost, 127.0.0.1 und ::1. Der Wert soll mit http:// oder https:// beginnen und einen Host nennen; sonst protokolliert das Backend beim Start die Warnung app_base_url_not_absolute_http, und nur die Abo-URL ergänzt das Schema selbst. |
Light-Modus (KAMERPLANTER_MODE=light)¶
Im Light-Modus entfällt die Token-Authentifizierung. Die API ist ohne Anmeldung verwendbar. Dieser Modus ist für lokale Einzelinstallationen ohne Internet-Exposition gedacht.
Light-Modus nicht öffentlich exponieren
Der Light-Modus deaktiviert alle Authentifizierungsschichten. Niemals mit einem öffentlich erreichbaren Port betreiben.
Netzwerk-Freigabe unter Docker Compose¶
Diese Variable liest nicht das Backend, sondern Docker Compose (docker-compose.yml und docker-compose.release.yml) beim Start.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
KAMERPLANTER_BIND_ADDRESS | 127.0.0.1 | Nein | Host-Adresse, an die Compose die Benutzeroberfläche (8080) und die API (8000) bindet. Mit 0.0.0.0 erreichen andere Geräte im Netzwerk beide Ports, siehe Dauerbetrieb — Zugriff von anderen Geräten. Datenbanken, Valkey und Ollama bleiben unabhängig davon an 127.0.0.1 gebunden. |
CORS-Konfiguration¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
CORS_ORIGINS | ["http://localhost:3000","http://localhost:5173"] | Nein | JSON-Array erlaubter Origins für CORS |
Format: Immer als JSON-Array im String-Format:
E-Mail¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
EMAIL_ADAPTER | console | Nein | E-Mail-Adapter: console (Ausgabe im Log, Link nur mit DEBUG=true), smtp oder resend. Ein anderer Wert — etwa ein Tippfehler — wird beim Start abgelehnt: API und Worker starten dann gar nicht erst, statt still auf console zurückzufallen. |
SMTP_HOST | localhost | Nein | SMTP-Server-Hostname |
SMTP_PORT | 587 | Nein | SMTP-Port |
SMTP_USERNAME | — | Nein | SMTP-Benutzername |
SMTP_PASSWORD | — | Nein | SMTP-Passwort |
SMTP_FROM_EMAIL | noreply@kamerplanter.example | Nein | Absenderadresse für System-E-Mails über SMTP |
SMTP_USE_TLS | true | Nein | STARTTLS für SMTP aktivieren |
RESEND_API_KEY | — | Bedingt | API-Schlüssel für den Versand über Resend (Secret). Pflicht, wenn EMAIL_ADAPTER=resend gesetzt ist — ohne ihn lehnt die Anwendung den Start ab. Trage ihn über das Kubernetes-Secret kamerplanter-secrets bzw. deine .env-Datei ein, niemals über eine Helm-values-Datei. |
RESEND_FROM_EMAIL | noreply@kamerplanter.example | Nein | Absenderadresse für System-E-Mails über Resend. Die Domain dieser Adresse muss im Resend-Konto verifiziert sein, sonst schlägt der Versand fehl. |
Im Entwicklungsmodus (EMAIL_ADAPTER=console) werden E-Mails nicht gesendet, sondern im Backend-Log ausgegeben. Den Bestätigungs- oder Passwort-Reset-Link enthält diese Protokollzeile aber nur, wenn zusätzlich DEBUG=true gesetzt ist — sein Token übernimmt sonst das Konto. Ohne DEBUG=true steht dort lediglich, dass keine E-Mail zugestellt wurde, ohne Token. Startet die API mit EMAIL_ADAPTER=console und DEBUG=false — der Fall bei einer produktiven Installation ohne SMTP- oder Resend-Konfiguration, da das Helm-Chart standardmäßig keinen Adapter setzt — schreibt sie beim Start eine Warnung ins Log (email_adapter_console_in_production). Für eine produktive Installation bleibt dann EMAIL_ADAPTER=smtp oder EMAIL_ADAPTER=resend zu konfigurieren, sonst lassen sich Registrierung und Passwort-Reset nicht abschließen. Auch E-Mail-Einladungen in einen Tenant gehen dann nicht raus: Die Einladung wird angelegt, die Antwort meldet delivered: false, und die Oberfläche zeigt dem Einladenden den Link zum Weitergeben (Issue #2162). Der Link zeigt auf FRONTEND_URL.
Resend: gehostete E-Mail-Zustellung über eine HTTP-API
EMAIL_ADAPTER=resend verschickt dieselben System-E-Mails (E-Mail-Bestätigung, Passwort-Reset, Bestätigungscode für erneute Anmeldung, Benachrichtigungs-E-Mails) wie der SMTP-Adapter, aber über die HTTP-API von Resend statt über eine SMTP-Verbindung. In den Logs erscheint die Empfängeradresse dabei nur als gesalzener Digest, niemals im Klartext; schlägt ein Versand fehl, protokolliert die Anwendung nur den Fehlertyp und den HTTP-Status, nie den Antworttext oder den API-Schlüssel. Jeder Versandversuch hat ein Zeitlimit von 10 Sekunden.
Wird auch vom Benachrichtigungssystem genutzt
Diese Variablen konfigurieren zugleich den E-Mail-Kanal des Benachrichtigungssystems — es gibt keine separate SMTP-Konfiguration für Benachrichtigungen.
Zwei verschiedene Voraussetzungen für föderierte Konten
Ein Konto ohne lokales Passwort mit einem OIDC-fähigen Anbieter (Google, generisches OIDC) bestätigt unumkehrbare Kontoaktionen und Zugangsdaten-Änderungen (Kontolöschung, erstes lokales Passwort, Mandantenlöschung, E-Mail-Änderung, seit Version 1.19 auch API-Key ausstellen, Gerätekopplung und Anmeldeweg entfernen) mit einer frischen Anmeldung bei diesem Anbieter — siehe API-Dokumentation: Authentifizierung. Dafür muss der Anbieter prompt=login/max_age und den Claim auth_time unterstützen, nicht SMTP. Nur ein Konto, dessen verknüpfte Anbieter ausschließlich GitHub und/oder Apple sind (beide können keine frische Anmeldung belegen), nutzt stattdessen den per E-Mail zugestellten Bestätigungscode — siehe Bestätigungscode per E-Mail anfordern. Läuft die Instanz für ein solches Konto mit EMAIL_ADAPTER=console und DEBUG=false, meldet die Anwendung, dass der Bestätigungscode nicht zugestellt werden kann (503), statt ihn stillschweigend verschwinden zu lassen — es kann sich trotzdem nicht löschen, kein erstes lokales Passwort setzen, keinen Mandanten löschen, die E-Mail-Adresse nicht ändern und auch keinen API-Key oder Kopplungscode ausstellen, bis SMTP konfiguriert ist. EMAIL_ADAPTER=smtp ist deshalb für den produktiven Betrieb mit GitHub/Apple-only-Konten Pflicht, nicht nur empfohlen.
Externe Datenanreicherung (REQ-011)¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
PERENUAL_API_KEY | — | Nein | API-Schlüssel für Perenual-Pflanzendatenbank |
TREFLE_API_KEY | — | Nein | API-Schlüssel für Tréflé-Pflanzendatenbank |
ENRICHMENT_HTTP_TIMEOUT | 30 | Nein | HTTP-Timeout für externe API-Anfragen (Sekunden) |
GBIF wird ohne API-Key verwendet (öffentliche API). Perenual und Tréflé erfordern kostenlose Registrierung.
Knowledge Service — Re-Ranking (optional)¶
Diese Variablen konfigurieren den optionalen Cross-Encoder-Re-Ranker des Knowledge Service. Ist RERANKER_URL leer, arbeitet der Knowledge Service im Hybrid-Search-only-Modus (Graceful Degradation). Siehe ADR-007.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
RERANKER_URL | `` (leer) | Nein | HTTP-URL des Reranker-Microservice, z. B. http://reranker-service:8081. Leer = Re-Ranking deaktiviert. |
RERANKER_INITIAL_K | 15 | Nein | Anzahl der Kandidaten aus dem Hybrid-Search-Schritt, die höchstens durch den Cross-Encoder neu bewertet werden (Bewertungs-Obergrenze, kein Limit für die Ergebnisanzahl — siehe KI-Architektur). |
RERANKER_MAX_DOCUMENT_CHARS | 500 | Nein | Maximale Zeichenzahl von titel\ninhalt je Kandidaten-Chunk, bevor er an den Reranker-Sidecar geschickt wird. Der an das LLM übergebene Chunk behält seinen vollständigen Text — nur die Reranker-Eingabe wird gekürzt. |
RERANKER_TOP_K | 5 | Nein | Anzahl der Chunks, die nach dem Re-Ranking an den LLM-Kontext übergeben werden. |
RERANKER_MODEL | bge-reranker-v2-m3 | Nein | ONNX-Modellname im Reranker-Service-Container (Verzeichnis unter /app/models/onnx/). |
RERANKER_MODEL gehört zum Reranker-Service, nicht zum Knowledge Service
RERANKER_MODEL wird als Umgebungsvariable am reranker-service-Container gesetzt — nicht am knowledge-service. Die anderen vier Variablen (RERANKER_URL, RERANKER_INITIAL_K, RERANKER_MAX_DOCUMENT_CHARS, RERANKER_TOP_K) gehören zum Knowledge Service.
Ressourcenbedarf und Zeitbudget
Der Reranker-Service benötigt 1,5–4 GB RAM (je nach Modell) und addiert Latenz pro Anfrage. Bei bge-reranker-v2-m3 am 2-CPU-Limit des Helm-Charts kostet ein (Query, Dokument)-Paar mit 512 Tokens ca. 3 Sekunden; der Sidecar bricht eine Re-Ranking-Anfrage nach 25 Sekunden serverseitig ab (Fallback-Grund deadline, siehe KI-Architektur). Ein realer HTTP-Test über alle 100 Benchmark-Fragen zeigt: Mit den Standardwerten RERANKER_INITIAL_K=15 und RERANKER_MAX_DOCUMENT_CHARS=500 (Paar-Median 166 Tokens, maximal 222) waren alle 100 Anfragen erfolgreich — p50 12,9 Sekunden, p90 15,0 Sekunden, maximal 16,0 Sekunden, innerhalb des 25-Sekunden-Budgets. Ein CI-Guard prüft für die Standardwerte und für jede Deployment-Datei im Repository, die eine der beiden Variablen setzt, dass RERANKER_INITIAL_K × RERANKER_MAX_DOCUMENT_CHARS ≤ 7.500 gilt (RERANK_SCORED_CHARS_BUDGET in config.py) und RERANKER_INITIAL_K höchstens 50 beträgt (die Anfragegrenze des Sidecars). Die Standardwerte schöpfen das Budget mit 15 × 500 = 7.500 exakt aus. Werte, die du außerhalb des Repositorys setzt, sieht der Guard nicht. Wer einen der beiden Werte über dieses Produkt hinaus anheben will, muss die Latenz vorher neu messen. Für Raspberry Pi und ressourcenarme Umgebungen empfiehlt sich weiterhin, RERANKER_URL leer zu lassen.
KI-Assistent ¶
Diese Variablen gehören zum Kamerplanter-Backend und steuern den dreistufigen Freischalt-Mechanismus sowie die Anbindung an den Knowledge Service (siehe KI-Assistent — Benutzerhandbuch). Die Provider-Auswahl (Ollama/Anthropic/OpenAI-kompatibel) ist eine separate Konfiguration am Knowledge Service selbst — siehe KI-Provider einrichten.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
AI_FEATURES_ENABLED | false | Nein | Stufe 1 des dreistufigen Freischalt-Mechanismus. false lässt sämtliche /ai/*-Endpunkte mit HTTP 404 antworten — die KI-API existiert dann faktisch nicht. |
KNOWLEDGE_SERVICE_ENABLED | false | Nein | Aktiviert die Anbindung an den Knowledge Service (wird sowohl vom älteren /api/v1/knowledge/*-Pfad als auch intern vom KI-Assistenten benötigt). |
KNOWLEDGE_SERVICE_URL | http://knowledge-service:8000 | Nein | Basis-URL des Knowledge-Service-Microservice. |
AI_KNOWLEDGE_SERVICE_TIMEOUT_S | 60 | Nein | HTTP-Timeout des KnowledgeServiceAdapter gegen den Knowledge Service (Sekunden). |
AI_CIRCUIT_BREAKER_THRESHOLD | 3 | Nein | Anzahl aufeinanderfolgender Fehler, ab der der Adapter den Knowledge Service als nicht erreichbar markiert. |
AI_CIRCUIT_BREAKER_WINDOW_S | 60 | Nein | Zeitfenster (Sekunden), in dem die Fehler für AI_CIRCUIT_BREAKER_THRESHOLD gezählt werden. |
AI_CIRCUIT_BREAKER_COOLDOWN_S | 60 | Nein | Wartezeit (Sekunden), bevor der Adapter nach dem Auslösen des Circuit Breakers erneut Anfragen an den Knowledge Service zulässt. |
AI_PUBLIC_RATE_LIMIT_PER_MIN | 10 | Nein | IP-Ratenbegrenzung für die Wissensfrage POST /api/v1/public/ai/ask (Anfragen pro Minute). Der Endpunkt existiert nur im Light-Modus und läuft dort als System-User. |
AI_BUDGET_USER_CALLS_PER_DAY | 50 | Nein | KI-Aufrufe, die ein Konto in einem Mandanten pro UTC-Tag starten darf (Tipps, Tagestipp, „Warum?“, Chat-Nachricht, Glossar-Erzeugung, KI-Diagnose zusammen). Darüber 429 AI_BUDGET_EXCEEDED mit Retry-After bis Tagesende. 0 schaltet das Budget ab. |
AI_BUDGET_TENANT_CALLS_PER_DAY | 500 | Nein | KI-Aufrufe aller Mitglieder eines Mandanten zusammen pro UTC-Tag. Ein Mitglied, das an seinem eigenen Budget abgewiesen wird, verbraucht dieses Budget nicht. 0 schaltet es ab. |
AI_BUDGET_TENANT_TOKENS_PER_DAY | 2000000 | Nein | LLM-Tokens (Prompt + Antwort, wie der Knowledge Service sie meldet), die ein Mandant pro UTC-Tag verbrauchen darf. Geprüft wird vor dem nächsten Aufruf; eine einzelne Antwort kann das Budget überschreiten. 0 schaltet es ab. Die Zähler liegen in Valkey; ist Valkey nicht erreichbar, läuft kein KI-Aufruf (503 AI_BUDGET_UNAVAILABLE). |
INTERNAL_SERVICE_TOKEN | — | Bedingt | Gemeinsames Geheimnis für cluster-interne M2M-Aufrufe (u. a. an den Knowledge Service). Pflicht, sobald KNOWLEDGE_SERVICE_ENABLED=true gesetzt ist — ohne Token verweigert der Startup-Gate den Start (AP-4). |
Instanzweite Freischaltung reicht allein nicht aus
AI_FEATURES_ENABLED=true schaltet die KI-API nur instanzweit frei (Stufe 1). Damit ein konkreter Mandant (Garten) KI-Funktionen tatsächlich nutzen kann, muss zusätzlich tenant.settings.ai_features_enabled für diesen Mandanten gesetzt sein (Stufe 2) — dafür existiert aktuell weder eine Oberfläche noch ein eigener API-Endpunkt, siehe KI-Assistent — Für technische Nutzer / Self-Hoster.
Provider-Konfiguration liegt am Knowledge Service, nicht am Backend
LLM_PROVIDER, LLM_API_URL, LLM_API_KEY und LLM_MODEL sind Umgebungsvariablen des eigenständigen Knowledge-Service-Deployments (src/knowledge-service/), nicht dieses Backends. Details: KI-Provider einrichten.
MCP-Server ¶
Diese Variablen steuern den MCP-Server — die Werkzeug-Schnittstelle, über die externe LLM-Clients (Claude Desktop, Claude Code, eigene Agenten) per Service-Account-API-Key auf Kamerplanter zugreifen können.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
MCP_SERVER_ENABLED | false | Nein | Gesamtschalter. Solange nicht true, antworten alle /mcp/*-Endpunkte mit HTTP 404 — die Schnittstelle existiert dann faktisch nicht. |
MCP_IDEMPOTENCY_TTL_HOURS | 24 | Nein | Gültigkeitsdauer eines idempotency_key für Schreibwerkzeuge — danach wird ein Wiederholungs-Aufruf als neue Aktion behandelt. |
MCP_AUDIT_RETENTION_DAYS | 90 | Nein | Aufbewahrungsdauer des mcp_audit_log (NFR-011) — ältere Einträge werden automatisch gelöscht. |
MCP_MAX_IMAGE_PAYLOAD_MB | 4 | Nein | Obergrenze der Gesamt-Nutzlast eines Aufrufs des Tagebuch-Werkzeugs get_diary_entry_photos in Megabyte (Base-64-kodiert). Wird sie überschritten, antwortet das Werkzeug mit dem Fehlercode payload.too_large, statt Fotos still wegzulassen — siehe MCP-Server — Tagebuch-Analyse. |
Kein eigener Prozess, keine eigenen Verbindungsvariablen
Der MCP-Server läuft im bestehenden Backend-Prozess mit und nutzt dessen ArangoDB-/Redis-Verbindung mit — es gibt keine separate Host-, Port- oder Credential-Konfiguration.
Tagebuch — Umgebungs-Schnappschuss¶
Beim Anlegen eines Tagebuch-Eintrags liest Kamerplanter die Sensorwerte, die die Pflanze abdecken, und speichert sie getrennt von den handnotierten Messwerten mit — siehe Tagebuch — Die Umgebung wird automatisch mitgeschrieben.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
DIARY_ENVIRONMENT_CAPTURE_ENABLED | true | Nein | Gesamtschalter. Auf false wird jeder neue Eintrag mit dem Vermerk „nicht versucht" gespeichert — unterscheidbar von „gesucht und nichts gefunden". |
DIARY_ENVIRONMENT_MAX_AGE_MINUTES | 60 | Nein | Ein Messwert, der älter ist, wird gar nicht erfasst. Ein Eintrag, der einen gestrigen Sensorwert als aktuell ausweist, ist schlechteres Belegmaterial als ein Eintrag ohne Klimawerte. |
DIARY_ENVIRONMENT_CAPTURE_TIMEOUT_SECONDS | 3.0 | Nein | Harte Obergrenze für die gesamte Erfassung. Läuft sie ab, wird der Eintrag mit dem gespeichert, was rechtzeitig ankam — das Anlegen wartet nie länger auf einen Sensor. |
Ein Ausfall verhindert nie den Eintrag
Nicht erreichbares Home Assistant, fehlende TimescaleDB oder ein abgelaufenes Zeitbudget führen zu einem Eintrag ohne (oder mit unvollständigen) Umgebungswerten, nie zu einer abgelehnten Anlage.
Metriken (Prometheus)¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
METRICS_PORT | 0 | Nein | Port, auf dem das Backend /metrics im Prometheus-Format ausliefert. 0 startet keinen Listener. Das Helm-Chart setzt 9464, sobald monitoring.enabled an ist. |
METRICS_BIND_ADDRESS | 0.0.0.0 | Nein | Adresse, an die sich der Metrik-Listener bindet. |
Die Metriken laufen auf einem eigenen Port, nie als Route der API auf Port 8000. Der Weg von außen (Ingress → Frontend-nginx → Backend :8000) erreicht sie dadurch nicht; im Cluster lässt die NetworkPolicy nur den konfigurierten Prometheus zu. Die Zeitreihen tragen weder Mandant noch Person: handler ist das Routenmuster (/api/v1/t/{tenant_slug}/…), nie der aufgerufene Pfad. Details: Helm — Metriken.
mDNS / Zeroconf Discovery¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
MDNS_ENABLED | false | Nein | mDNS-Service-Announcement aktivieren (_kamerplanter._tcp.local.) |
INSTANCE_ID | (auto) | Nein | Eindeutige Instanz-ID (z. B. kp-abc123). Wird beim Start automatisch generiert, wenn leer. |
Wenn aktiviert, annonciert das Backend einen _kamerplanter._tcp.local.-Service im lokalen Netzwerk. Home Assistant erkennt diesen Service automatisch und bietet die Einrichtung der Kamerplanter-Integration an.
Stabile Instanz-ID
Die INSTANCE_ID wird für die Duplikat-Erkennung in Home Assistant verwendet. Wenn sie leer bleibt, wird bei jedem Neustart eine neue ID generiert. Für stabile Discovery sollte ein fester Wert gesetzt werden, z. B. INSTANCE_ID=kp-mein-server.
mDNS und Kubernetes¶
mDNS basiert auf Multicast-UDP (Port 5353) im lokalen Layer-2-Netzwerk. In Standard-Kubernetes-Clustern funktioniert mDNS nicht, da:
- Overlay-Netzwerk blockiert Multicast — Standard-CNIs (Calico, Cilium, Flannel) routen nur L3-Traffic. Multicast-Pakete aus einem Pod erreichen das physische LAN nicht — Home Assistant sieht die Announcements nie.
- Pod-IP ist nicht LAN-erreichbar — Selbst bei funktionierendem Multicast wuerde die annoncierte Pod-IP (z. B.
10.42.x.x) von ausserhalb des Clusters nicht erreichbar sein.
| Deployment | MDNS_ENABLED | Begruendung |
|---|---|---|
| Docker Compose / Bare Metal | true | Backend laeuft direkt im LAN — MDNS_ENABLED=true setzen |
K3s / MicroK8s Single-Node + hostNetwork: true | true | Pod teilt Host-Netzwerk — Multicast erreicht das LAN |
| Standard K8s Cluster | false | Overlay-Netzwerk blockiert Multicast — manueller Config Flow in HA als Fallback |
| Cloud (AWS, GCP, Azure) | false | Kein lokales Netzwerk vorhanden |
hostNetwork ist ein Trade-off
Mit hostNetwork: true teilt der Pod den Netzwerk-Namespace des Hosts. Multicast funktioniert, aber auf Kosten der Netzwerk-Isolation (Port-Konflikte möglich, keine NetworkPolicy-Enforcement). Nur für Homelab-/Raspberry-Pi-Szenarien empfohlen.
Im Helm-Chart ist MDNS_ENABLED standardmaessig auf false gesetzt. Der manuelle Config Flow in Home Assistant (URL-Eingabe) funktioniert in jedem Deployment-Szenario als Fallback.
Home Assistant Integration (REQ-005)¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
HA_URL | — | Nein | Home-Assistant-Basis-URL, z. B. http://homeassistant.local:8123 |
HA_ACCESS_TOKEN | — | Nein | Long-Lived Access Token aus Home Assistant |
HA_TIMEOUT | 10 | Nein | HTTP-Timeout für HA-Anfragen (Sekunden) |
HA_ALLOW_PRIVATE_ENDPOINT | false | Nein | SSRF-Opt-in: Home Assistant läuft üblicherweise im LAN über HTTP auf einer privaten/RFC1918-Adresse (homeassistant.local, 192.168.x.x) oder localhost. Ohne diese Freigabe blockiert der SSRF-Schutz Verbindungen zu solchen Adressen. Der Cloud-Metadaten-/Link-Local-Bereich (169.254.0.0/16) bleibt immer blockiert, unabhängig von dieser Variable. |
Sind beide Variablen HA_URL/HA_ACCESS_TOKEN gesetzt, aktiviert das Backend zusätzlich den Home-Assistant-Kanal des Benachrichtigungssystems (persistente Notifications, Mobile Push, TTS).
Apprise-Kanal erfordert zusätzliches Python-Paket
Der apprise-Benachrichtigungskanal ist unabhängig von den Home-Assistant-Variablen immer aktiv, benötigt aber das optionale Python-Paket apprise im Backend-Image (pip install apprise) — dafür gibt es keine eigene Umgebungsvariable. Details siehe Benachrichtigungen — Apprise.
Zeitreihendaten (TimescaleDB, REQ-005)¶
Diese Variablen aktivieren die optionale TimescaleDB-Anbindung für hochfrequente Sensor-Zeitreihen mit automatischem Downsampling (siehe Sensorik). Ohne TIMESCALEDB_ENABLED=true werden manuelle und automatische Messwerte weiterhin in ArangoDB gespeichert — die App bleibt voll funktionsfähig, nur ohne automatisches mehrstufiges Downsampling.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
TIMESCALEDB_ENABLED | false | Nein | Gesamtschalter für die TimescaleDB-Anbindung. |
TIMESCALEDB_HOST | localhost | Nein | Hostname der TimescaleDB-Instanz. |
TIMESCALEDB_PORT | 5432 | Nein | TCP-Port. |
TIMESCALEDB_DATABASE | kamerplanter_sensors | Nein | Datenbankname. |
TIMESCALEDB_USERNAME | postgres | Nein | Datenbankbenutzer. |
TIMESCALEDB_PASSWORD | changeme | Bedingt | Datenbankpasswort. Pflicht in Produktion — der Startup-Gate verweigert den Start, wenn TIMESCALEDB_ENABLED=true gesetzt ist und dieser Wert unverändert changeme lautet (siehe Konfigurationsmatrix — Pflicht-Secrets je aktivierter Funktion). |
TIMESCALEDB_POOL_MIN_SIZE | 2 | Nein | Minimale Connection-Pool-Größe. |
TIMESCALEDB_POOL_MAX_SIZE | 10 | Nein | Maximale Connection-Pool-Größe. |
Docker Compose: eigenes Profil
In der lokalen Docker-Compose-Umgebung startet TimescaleDB nur mit docker-compose --profile timescaledb up -d. In Kubernetes schaltest du ihn mit controllers.timescaledb.enabled: true ein; das Chart setzt dann TIMESCALEDB_ENABLED, TIMESCALEDB_HOST und die übrigen Werte selbst (siehe Helm Charts).
Umgebungssteuerung & Aktorik (REQ-018)¶
Diese Variable steuert die periodische Auswertung von Zeitplänen und Regeln, den stündlichen Override-Ablauf und den 5-Minuten-Online/Offline-Abgleich mit Home Assistant für Umgebungssteuerung & Aktorik.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
ACTUATOR_CONTROL_LOOP_ENABLED | false | Nein | Kill-Switch für die drei periodischen Aktor-Steuerungs-Tasks (evaluate_control_rules alle 30 s, expire_manual_overrides stündlich, sync_actuator_states alle 5 min). Ist die Variable deaktiviert, laufen Zeitpläne und Regeln nicht automatisch — Aktoren bleiben aber jederzeit über die REST-API steuerbar (direkter Befehl, Override, Notabschaltung). |
Kein eigener HA-Schalter nötig
Anders als die anderen Home-Assistant-Funktionen benötigt die Aktor-Steuerung keine zusätzliche Freischaltung — solange HA_URL/HA_ACCESS_TOKEN gesetzt sind und ACTUATOR_CONTROL_LOOP_ENABLED=true ist, dispatcht das System Befehle an Home-Assistant-Aktoren automatisch.
InvenTree-Integration (REQ-016)¶
Diese Variablen aktivieren die optionale Anbindung an InvenTree. Ohne INVENTREE_ENABLED=true liefern alle InvenTree-Endpunkte den Fehler „Funktion deaktiviert" (HTTP 409), ohne die App zu blockieren.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
INVENTREE_ENABLED | false | Nein | Kill-Switch für die gesamte InvenTree-Integration. |
INVENTREE_ALLOW_PRIVATE_ENDPOINT | false | Nein | Erlaubt eine InvenTree-Instanz mit privater/LAN-Adresse (analog zu HA_ALLOW_PRIVATE_ENDPOINT). Ohne diese Freigabe blockiert der SSRF-Schutz Verbindungen zu internen Adressen. |
Verbindung (inkl. API-Token) und Verknüpfungen richtest du anschließend über die REST-API ein — Details siehe Betriebsmittel & Inventar (InvenTree) — Für technische Nutzer / Self-Hoster.
Wettervorhersage & Frost-Frühwarnung ¶
Diese Variablen steuern die Wettervorhersage-Abholung und die darauf aufbauende proaktive Frost-Frühwarnung. Ohne WEATHER_ENABLED=true bleiben beide Funktionen vollständig deaktiviert — Standorte ohne konfigurierte Wetterquelle sind davon unabhängig ebenfalls nicht betroffen.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
WEATHER_ENABLED | false | Nein | Kill-Switch für die gesamte Wetterfunktion (Quellenabholung + Frost-Frühwarnung). Details zur eigentlichen Quellenkonfiguration siehe Wetterquellen je Standort. |
WEATHER_DEFAULT_PUBLIC_SOURCE | open-meteo | Nein | Werkseitig voreingestellte öffentliche Wetterquelle für neue Standorte ohne explizite Auswahl. |
OPEN_METEO_ENABLED | true | Nein | Instanzweiter Default für die Quelle Open-Meteo (keyless, EU-Fokus). Vom Platform-Admin pro Instanz über die Wetterdienste-Verwaltung überschreibbar (siehe Wetterdienste konfigurieren) — diese Variable setzt nur den Ausgangswert. |
DWD_ENABLED | true | Nein | Instanzweiter Default für die Quelle DWD/Bright Sky (Deutscher Wetterdienst). Ebenfalls Platform-Admin-überschreibbar. |
OPENWEATHERMAP_ENABLED | true | Nein | Instanzweiter Default für die Quelle OpenWeatherMap. Ebenfalls Platform-Admin-überschreibbar. |
FROST_FORECAST_HORIZON_DAYS | 2 | Nein | Vorhersage-Zeitraum in Tagen ab heute (inklusive), der auf einen erwarteten Frosttag geprüft wird — Standard deckt heute plus den Folgetag ab. |
FROST_FORECAST_THRESHOLD_CELSIUS | 2.0 | Nein | Minimaltemperatur, ab der ein vorhergesagter Tag als Frosttag gilt. Bewusst getrennt vom reaktiven Schwellwert unten, mit einem etwas konservativeren (näher an 0 °C liegenden) Wert, da eine mehrtägige Vorhersage unsicherer ist als eine aktuelle Messung. |
Zum Vergleich — der bestehende reaktive Frost-Schwellwert (aktuell gemessene Temperatur, unverändert durch diese Erweiterung):
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
FROST_WARNING_THRESHOLD_CELSIUS | 3.0 | Nein | Schwellwert für die reaktive Frost-Warnung (binary_sensor.kp_{location}_frost_warning), basierend auf der zuletzt gemessenen Lufttemperatur. |
Klimanormalen (NASA POWER) ¶
Diese Variablen steuern die monatliche Hintergrund-Abholung der langjährigen Klima-Normalwerte (Abschnitt „Klima am Standort") über die keyless NASA-POWER-Reanalyse-Schnittstelle. Für die Abholung müssen sowohl WEATHER_ENABLED als auch NASA_POWER_CLIMATE_ENABLED aktiv sein.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
NASA_POWER_CLIMATE_ENABLED | true | Nein | Eigener Kill-Switch für den monatlichen Klimanormalen-Task, unabhängig vom allgemeinen WEATHER_ENABLED — beide müssen aktiv sein, damit der Task läuft. |
NASA_POWER_BASE_URL | https://power.larc.nasa.gov/api/temporal | Nein | Basis-URL der NASA-POWER-API. Nur für Self-Hoster mit abweichender Netzwerk-/Proxy-Konfiguration relevant. |
NASA_POWER_CLIMATE_TTL_DAYS | 180 | Nein | Klimanormalen ändern sich kaum; ein bereits abgeholter Datensatz wird erst nach Ablauf dieser TTL erneut abgeholt — hält den monatlichen Task idempotent und schont die NASA-POWER-API. |
NASA_POWER_DATA_LATENCY_DAYS | 7 | Nein | Betrifft die separate Tageswerte-Abholung (nicht die Klimanormalen): Anzahl Tage, die NASA POWER für die Qualitätskontrolle seiner jüngsten Tageswerte benötigt. |
NASA_POWER_DAILY_DAYS_BACK | 14 | Nein | Betrifft ebenfalls nur die Tageswerte-Abholung: Größe des Rückblick-Fensters in Tagen. |
Betrifft nur Freiland- und Gewächshaus-Standorte mit GPS-Koordinaten
Klimanormalen werden ausschließlich für Standorte vom Typ Außenbereich oder Gewächshaus mit hinterlegten GPS-Koordinaten materialisiert — für Innenraum-Standorte sind sie ohne Nutzen und werden nicht abgeholt. NASA POWER ist keyless nutzbar; die Daten unterliegen der CC-BY-4.0-Lizenz (Attribution wird automatisch mit ausgeliefert, siehe Klima am Standort).
Winterhärtezonen (USDA) ¶
Diese Variable steuert die vierteljährliche Hintergrund-Aktualisierung der automatisch aus den Klimanormalen abgeleiteten Winterhärtezone eines Standorts (siehe Klimazonen & Winterhärte). Die Ableitung baut auf den Klimanormalen auf — der zugehörige Task läuft daher nur, wenn zusätzlich sowohl WEATHER_ENABLED als auch NASA_POWER_CLIMATE_ENABLED aktiv sind.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
HARDINESS_ZONE_REFRESH_ENABLED | true | Nein | Eigener Kill-Switch für den vierteljährlichen Winterhärtezonen-Task (1. Januar/April/Juli/Oktober, 05:00 UTC), unabhängig von NASA_POWER_CLIMATE_ENABLED — beide müssen aktiv sein, damit der Task läuft. Manuell gesetzte Zonen (hardiness_zone_source: manual) werden vom Task nie überschrieben. |
Betrifft nur Freiland- und Gewächshaus-Standorte mit GPS-Koordinaten und vorhandenen Klimanormalen
Wie die Klimanormalen selbst wird die Winterhärtezone nur für Standorte vom Typ Außenbereich oder Gewächshaus mit GPS-Koordinaten berechnet — und erst, sobald für diesen Standort bereits mindestens ein Klimanormalen-Datensatz mit verwertbarer Minimaltemperatur vorliegt. Ein sofortiges manuelles Auslösen (unabhängig von diesem Zeitplan) ist über die API möglich, siehe API-Referenz — Winterhärtezonen.
Bewässerungsbedarf (ET₀) ¶
Diese Variablen steuern den täglichen Hintergrund-Task, der aus den Wetterdaten eines Freiland- oder Gewächshaus-Standorts die Referenz-Evapotranspiration (FAO-56, ET₀) und daraus den Netto-Bewässerungsbedarf je Pflanzdurchlauf berechnet. Der Task benötigt zusätzlich WEATHER_ENABLED=true — ohne abgeholte Wetterdaten gibt es nichts zu berechnen. Ergebnis und Verhalten für Endnutzer sind unter Gießprotokoll: Vorgeschlagene Gießmenge und Pflegeerinnerungen: Warum eine Erinnerung ausbleiben kann beschrieben.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
IRRIGATION_DEMAND_ENABLED | true | Nein | Eigener Kill-Switch für den täglichen compute_irrigation_demand-Task (06:15 Uhr), unabhängig vom allgemeinen WEATHER_ENABLED — beide müssen aktiv sein, damit der Task läuft. |
IRRIGATION_ROOT_ZONE_DEPTH_MM | 300.0 | Nein | Angenommene effektive Wurzelzonentiefe in Millimeter Boden. Wird verwendet, um die Wasserhaltekapazität eines Substrats (in Prozent) in eine Millimeter-Obergrenze für den Netto-Bewässerungsbedarf umzurechnen — verhindert eine rechnerisch zu hohe Tagesempfehlung bei sehr trockenen Ausgangsbedingungen. |
Nur Freiland- und Gewächshaus-Standorte, keine neuen REST-Endpunkte
Der Bewässerungsbedarf wird ausschließlich für Standorte vom Typ Außenbereich oder Gewächshaus mit hinterlegten GPS-Koordinaten berechnet — Innenraum-Standorte bleiben beim intervallbasierten Gießplan (REQ-022). Es gibt keinen eigenen REST-Endpunkt dafür; das Ergebnis fließt über den bestehenden Gießmengen-Vorschlag (suggest_volume) und die Pflegeerinnerungs-Engine in die Oberfläche ein.
Berechnungsgrundlage: aquacropeto (BSD-3-Clause)
Die FAO-56-Penman-Monteith- und Hargreaves-Formeln für ET₀ werden über die Python-Bibliothek aquacropeto (PyPI-Paket aquacropeto, BSD-3-Clause-Lizenz) berechnet — keine ShareAlike-/Copyleft-Pflichten für den Kamerplanter-Code. Details siehe NOTICE.md im Projekt-Root.
Health-Endpunkt und Build-Kennung¶
Der unauthentifizierte Endpunkt GET /api/health kann beantworten, welcher Build gerade läuft. Weil er unauthentifiziert ist, ist diese Auskunft standardmäßig abgeschaltet und der Endpunkt mengenbegrenzt.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
HEALTH_EXPOSE_BUILD_REVISION | false (Helm-Chart setzt true) | Nein | Ob GET /api/health das Feld build_revision überhaupt ausliefert. Bei false fehlt der Schlüssel vollständig in der Antwort. Der Anwendungs-Standard bleibt false — helm/kamerplanter/values.yaml setzt für Kubernetes-Installationen seit #1236 true, damit der Auslieferungsstand prüfbar ist. |
BUILD_REVISION | (leer) | Nein | Der vollständige Git-Commit, aus dem das Image gebaut wurde. Wird beim Container-Build eingebacken (docker-publish.yml reicht ihn als Build-Argument in das Dockerfile); von Hand setzen musst du ihn nur, wenn du selbst baust. |
RATE_LIMIT_HEALTH | 60/minute | Nein | Mengenbegrenzung für GET /api/health, je Client-IP und Prozess — gezählt nur im Prozessspeicher, damit der Endpunkt nie auf Valkey wartet, auch nicht bei einem Ausfall. Die Kubernetes-Proben zeigen auf /api/v1/health/live und /api/v1/health/ready und sind davon nicht betroffen. |
Warum die Build-Kennung standardmäßig fehlt
Öffentlich ist nicht der Commit-Hash — das Repository ist ohnehin offen —, sondern die Zuordnung dieser Host läuft auf jenem Commit. Aus ihr folgt der exakte Rückstand gegenüber dem Entwicklungsstand und damit die Liste der Fehlerbehebungen, die dieser Instanz fehlen. Aktiviere das Feld deshalb bewusst — etwa auf einer Instanz, die ohnehin nur im eigenen Netz erreichbar ist, oder für die Dauer einer Fehlersuche.
Drei unterscheidbare Antwortzustände, die nicht verwechselt werden dürfen:
| Antwort | Bedeutung |
|---|---|
Der Schlüssel build_revision fehlt | HEALTH_EXPOSE_BUILD_REVISION ist false. Bewusste Konfiguration, kein Defekt. |
"unknown" | Auskunft ist erlaubt, aber es ist keine Revision eingebacken (Entwicklungs-Image, ungestempelter Build). |
| Ein 7- bis 40-stelliger Hexadezimal-Wert | Die echte Antwort. Ein von docker-publish.yml gebautes Image meldet den vollen 40-stelligen SHA; ein selbst gebautes Image mit BUILD_REVISION=$(git rev-parse --short HEAD) meldet entsprechend weniger. |
Der Wert wird vor der Ausgabe gegen ^[0-9a-f]{7,40}$ geprüft (nach dem Abschneiden von Leerraum, damit ein in YAML umbrochener oder in der Shell gequoteter Wert überlebt). Alles andere wird zu "unknown" — nie zu einem erfundenen oder abgeleiteten Wert.
Betriebssignal, keine Attestierung
build_revision sagt, was die Instanz über sich behauptet. Wer das Deployment kompromittiert hat, kann sie jeden Hash melden lassen. Der belastbare Nachweis bleibt gh attestation verify zusammen mit dem Digest aus .status.containerStatuses[].imageID des Pods — siehe CI/CD — Prüfungen entlang der Auslieferungskette.
Rate Limiting¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
RATE_LIMIT_AUTH | 20/minute | Nein | Rate-Limit für Authentifizierungsendpunkte |
RATE_LIMIT_STORAGE_URL | leer | Nein | Speicher, in dem die IP-Rate-Limits zählen (redis://…, rediss://…, redis+unix://… oder memory://; ein anderes Schema, ein Passwort mit unkodiertem /, # oder ? oder ein @ in Pfad, Query oder Fragment verhindert den Start — hier und in REDIS_URL —, die Meldung nennt den Wert nicht; ein @ im Passwort selbst ist zulässig). Leer bedeutet: der Valkey aus REDIS_URL, damit alle Replikas und Worker-Prozesse gemeinsam zählen. memory:// zählt je Prozess und passt nur, wenn ein Prozess die ganze Installation ist. Scheitert der Speicher mit einem Fehler des Redis-Clients, zählt jeder Prozess für sich weiter (nicht offen, kein Fehler, auch nicht für Anfragen, die beim Ausfall gerade warten); je Prozess kann ein frisches Zeitfenster gelten, eines je Fenster, nicht je Ausfall. Bei hängendem Speicher wartet nur die Anfrage, die den Ausfall bemerkt (und jede, die gerade wartet), einen Socket-Timeout von 0,5 s je Verbindungsversuch und Adresse (DNS, mehrere Adressen, Verbindungsaufbau plus Handshake und NOSCRIPT-Nachladen nicht abgedeckt), ebenso je Prozess eine Prüfanfrage in Abständen von 1, 2, 4, 8, 16, 32 s und danach alle 32 s; jeder Prozess zählt erst mit seiner eigenen nächsten Prüfanfrage wieder gemeinsam. Die Mail-Budgets der Anmelderouten nutzen dieselben Client-Optionen und denselben Prüfplan: Solange Valkey hängt, wartet nur der Speicheraufruf, der den Ausfall bemerkt, und je Prozess eine Prüfanfrage höchstens 0,5 s — siehe IP-Rate-Limits und Proxy-Tiefe. |
TRUSTED_PROXY_HOPS | 0 | Ja, hinter zwei Proxys | Wie viele Proxy-Adressen die eigene Infrastruktur an X-Forwarded-For anhängt, von rechts gezählt. 0 = Client → nginx → Backend (Lauf ohne Ingress, z. B. Docker Compose); 1 = Client → Ingress-Controller → nginx → Backend (das Helm-Chart setzt diesen Wert; in der Referenz-Installation ist der Ingress Contour/Envoy). Zu niedrig löst jeden Aufrufer auf den nächsten Proxy auf — der Device-Pairing-Lockout sperrt dann alle Nutzer gleichzeitig und IP-Allowlist-Service-Accounts scheitern; zu hoch liest Einträge, die ein Aufrufer fälschen kann. Bei 0 und einer X-Forwarded-For-Kette mit mehr als einem Eintrag loggt das Backend einmal je Prozess forwarded_chain_deeper_than_trusted_proxy_hops: entweder ein nicht gezählter Proxy oder ein Aufrufer, der den Header selbst schickt (dann ist 0 richtig); erhöhe den Wert nur bei wirklich mehr Proxys — siehe Login-Schutz und IP-Limits. |
RATE_LIMIT_UPLOAD | 30/minute | Nein | Budget je Konto und Route für Datei-Uploads: Anhänge, Pflanzen- und Aufgabenfotos, Schädlingsbilder, CSV-Import (Upload und Bestätigung). Darüber 429 mit Retry-After. Ohne angemeldetes Konto zählt die Client-IP. |
RATE_LIMIT_INFERENCE | 20/minute | Nein | Budget je Konto und Route für Routen, die ein Modell ausführen: CV-Diagnose, Schädlingserkennung, Pflanzenbestimmung, Referenzbeitrag und jede Route, die ein LLM anspricht (Tipps und Tagestipp erzeugen, „Warum?“, Chat-Nachricht, Glossar-Erzeugung, KI-Diagnose, Wissensfrage POST /api/v1/t/{slug}/ai/knowledge/ask). Die Tagesgrenzen von Bestimmung und Referenzbeitrag sowie die KI-Tagesbudgets (AI_BUDGET_*) gelten zusätzlich. |
RATE_LIMIT_EXPORT | 20/minute | Nein | Budget je Konto und Route für die PDF-Drucke unter /print (Nährstoffplan, Pflegeliste, Pflanzenetiketten). |
RATE_LIMIT_RESEND_VERIFICATION | 10/hour | Nein | Rate-Limit je Client-IP für POST /api/v1/auth/resend-verification. Begrenzt E-Mails an frei wählbare Adressen, deshalb weit unter RATE_LIMIT_AUTH. Die zusätzliche Grenze je Adresse (3 Anfragen, Auffüllung nach einer Stunde Ruhe) ist fest und nicht konfigurierbar. |
RATE_LIMIT_CALENDAR_FEED | 30/minute | Nein | Rate-Limit je Client-IP für den öffentlichen iCal-Abruf GET /api/v1/calendar/feeds/{key}/feed.ics (REQ-015 CF-004). Kalender-Apps rufen ein Abo alle 15 Minuten bis alle paar Stunden ab; darüber 429. Abgewiesene Abrufe mit falschem Token zählen mit. |
RATE_LIMIT_CALENDAR_FEED_PER_TOKEN | 60/hour | Nein | Zusätzliches Rate-Limit je vorgelegtem Feed-Token für denselben Abruf (REQ-015 CF-004, #2226). Begrenzt eine geleakte Abo-URL auch dann, wenn sie von vielen Adressen aus abgerufen wird; ein Abo auf mehreren Geräten (Handy, Laptop, Home Assistant) bleibt weit darunter. Der Bucket ist über einen Digest des Tokens geschlüsselt — ohne den Token kann niemand das Budget verbrauchen. |
RATE_LIMIT_INVITATION_EMAIL | 10/minute | Nein | Rate-Limit je Konto für POST /api/v1/tenants/{slug}/invitations/email: Jeder Aufruf verschickt eine E-Mail an eine frei gewählte Adresse. Die Tagesgrenze je Konto ist TENANT_INVITATION_EMAILS_PER_DAY (Issue #2162) |
RATE_LIMIT_HEALTH | 60/minute | Nein | Rate-Limit für GET /api/health — siehe Health-Endpunkt und Build-Kennung |
Format: [anzahl]/[einheit] — Einheiten: second, minute, hour, day
Verschachtelte Konfiguration (GBIF)¶
GBIF-Einstellungen können über den Unterstrich-Doppelpunkt-Delimiter verschachtelt werden:
| Variable | Standard | Beschreibung |
|---|---|---|
GBIF__BASE_URL | https://api.gbif.org/v1 | GBIF-API-Basis-URL |
GBIF__RATE_LIMIT_PER_MINUTE | 60 | Anfragen pro Minute an GBIF |
GBIF__HTTP_TIMEOUT | 30 | Timeout für GBIF-Anfragen (Sekunden) |
Foto-Identifikation (REQ-029)¶
Diese Variablen konfigurieren die optionale Pflanzenerkennung per Foto. Wenn keine der API-Schlüssel gesetzt ist, ist das Feature vollständig deaktiviert — alle Kamera-Schaltflächen sind ausgeblendet und es wird keine Einwilligung abgefragt.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
PLANTNET_API_KEY | — | Nein | API-Schlüssel für Pl@ntNet (Free-Tier: ≤ 500 Identifikationen/Tag). Registrierung unter my.plantnet.org. |
PLANTNET_ENABLED | true | Nein | Schaltet den Pl@ntNet-Adapter komplett ab, auch wenn PLANTNET_API_KEY gesetzt ist. Auf false setzen, um Pl@ntNet trotz vorhandenem Key zu deaktivieren (z. B. bei ausschließlicher Nutzung der selbst-gehosteten DINOv2-Erkennung). |
PLANTNET_BASE_URL | https://my-api.plantnet.org/v2 | Nein | Basis-URL der Pl@ntNet-API. Nur für Self-Hosting oder Test-Endpunkte ändern. |
PLANT_ID_API_KEY | — | Nein | API-Schlüssel für Plant.id (Kindwise) — ein zusätzlicher, rein Betreiber-initiierter Cloud-Adapter (niemals automatisch primär, anders als Pl@ntNet). |
PLANT_ID_BASE_URL | https://plant.id/api/v3 | Nein | Basis-URL der Plant.id-API. |
INFERENCE_SERVICE_ENABLED | false | Nein | Aktiviert den selbst-gehosteten DINOv2-Erkennungspfad (REQ-029-A). Muss auf Backend UND Celery-Worker identisch gesetzt sein, sobald Nutzer eigene Referenzbilder beitragen können — der Worker führt die planmäßige DSGVO-Art.-17-Löschung dieser Beiträge aus (Issue #1753); fehlt die Variable dort, hält der Worker fällige Löschungen als Konfigurationsfehler zurück und eine Mandantenlöschung antwortet mit HTTP 503. Details zur vollständigen Inbetriebnahme (VectorDB, Referenz-Index-Befüllung, Aktivierungsreihenfolge) siehe Bilderkennung in Betrieb nehmen. |
INFERENCE_SERVICE_URL | http://kamerplanter-recognition:8000 | Nein | Interne URL des Inferenz-Service. Ebenfalls auf Backend und Celery-Worker identisch setzen (siehe oben). |
IDENTIFICATION_PRIMARY_ADAPTER | plantnet | Nein | Bevorzugter Adapter. Mögliche Werte: plantnet, local_embedding (DINOv2, sobald INFERENCE_SERVICE_ENABLED=true). Ein anderer Wert — etwa ein Tippfehler — wird beim Start abgelehnt: API und Worker starten dann gar nicht erst, statt still einen anderen Anbieter zu verwenden. |
IDENTIFICATION_HTTP_TIMEOUT | 60 | Nein | HTTP-Timeout (Sekunden) für den externen Identifikations-Aufruf (Pl@ntNet-Upload + serverseitige ML-Inferenz kann den früheren 30-Sekunden-Standard unter Last überschreiten). |
IDENTIFICATION_CONFIDENCE_AUTO_ACCEPT | 0.85 | Nein | Übereinstimmungsschwelle (0–1), ab der ein Vorschlag als „sehr sicher" hervorgehoben wird. |
IDENTIFICATION_CONFIDENCE_MIN_SHOW | 0.10 | Nein | Mindest-Übereinstimmung (0–1) für die Anzeige eines Vorschlags. Ergebnisse darunter werden gefiltert. |
IDENTIFICATION_MAX_IMAGE_SIZE_MB | 5 | Nein | Maximale Bildgröße in Megabyte. Größere Bilder werden mit HTTP 400 abgelehnt. |
IDENTIFICATION_MAX_IMAGE_DIMENSION | 1024 | Nein | Längste Kante (px), auf die das Nutzerbild vor dem Upload an den Adapter herunterskaliert wird. Kleiner = schnellerer Upload und weniger Drittanbieter-Bandbreite. |
IDENTIFICATION_RATE_LIMIT_PER_USER_DAY | 50 | Nein | Maximale Anfragen pro Nutzer pro Tag (SEC-003-Untergrenze, verhindert dass ein einzelnes Konto das geteilte Free-Tier-Kontingent aufbraucht). 0 verwendet stattdessen das Adapter-Standard-Limit (500 bei Pl@ntNet). |
IDENTIFICATION_EXTERNAL_IN_LIGHT_MODE | false | Nein | Betreiber-Opt-in für den externen Erkennungspfad (Pl@ntNet) im Light-Modus. Im Light-Modus gibt es kein Einwilligungssystem — ein Foto an Dritte zu senden erfordert daher eine bewusste Betreiber-Entscheidung. Solange diese Variable false bleibt, ist im Light-Modus ausschließlich der selbst-gehostete local_embedding-Pfad nutzbar (sobald INFERENCE_SERVICE_ENABLED=true gesetzt ist). |
REFERENCE_CONTRIBUTION_RATE_LIMIT_PER_USER_DAY | 20 | Nein | Maximale Anzahl Referenzbild-Beiträge (POST /identification/reference) pro Nutzer pro Tag — schützt den Erkennungsindex vor Missbrauch/Flutung durch ein einzelnes Konto. 0 deaktiviert das Limit. Nur relevant, wenn die selbst-gehostete DINOv2-Erkennung aktiv ist (siehe Self-Hosted-Erkennung mit DINOv2). |
Pl@ntNet nur für nicht-kommerzielle Nutzung
Der Pl@ntNet Free-Tier ist für nicht-kommerzielle Nutzung zugelassen. Für kommerzielle Instanzen die Nutzungsbedingungen unter my.plantnet.org prüfen.
Kubernetes Secrets
Der PLANTNET_API_KEY sollte als Kubernetes Secret hinterlegt werden:
Feature-Toggle-Logik¶
PLANTNET_API_KEY gesetzt?
├── Ja → Pl@ntNet aktiv (Artbestimmung, ≤ 500 IDs/Tag)
└── Nein → Feature vollständig deaktiviert
(Kamera-Buttons ausgeblendet, kein Consent-Dialog)
Schädlingserkennung (REQ-044)¶
Diese Variablen konfigurieren die optionale bildbasierte Schädlingserkennung. Das Feature ist standardmäßig deaktiviert — ohne gesetztes PEST_DETECTION_ENABLED=true ist der Button „Auf Schädlinge prüfen" ausgeblendet und die App voll funktionsfähig.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
PEST_DETECTION_ENABLED | false | Nein | Gesamtschalter. Auf true setzen, um die Funktion zu aktivieren. Muss auf Backend UND Celery-Worker gesetzt sein (alternativ INFERENCE_SERVICE_ENABLED), sobald ein Platform-Admin ein beigetragenes Schädlingsfoto zur Erkennungsbasis freigegeben hat — der Worker führt die planmäßige DSGVO-Art.-17-Löschung dieser Beiträge aus (Issue #1759); fehlt beides dort, hält der Worker fällige Löschungen als Konfigurationsfehler zurück und eine Mandantenlöschung antwortet mit HTTP 503. Details: Bilderkennung in Betrieb nehmen. |
PEST_DETECTION_SYMPTOM_ENABLED | true | Nein | Schadbild-/Symptom-Erkennung (Modus 2) ein/aus. Aktiv wenn PEST_DETECTION_ENABLED=true. |
PEST_DETECTION_DETECTOR_ENABLED | false | Nein | Direkt-Detektor (Modus 1, Phase 2) ein/aus. Erfordert trainierten ONNX-Detektor. |
PEST_DETECTION_DEMO_ENABLED | false | Nein | Demo-Adapter (kein externer Service, kein echtes Modell). Zeigt den kompletten UI-Ablauf mit klar gekennzeichneten Platzhalter-Befunden, während das trainierte Backend extern blockiert ist. Nur zur Vorschau — nicht für echte Entscheidungen. Aktiv, wenn zusätzlich PEST_DETECTION_ENABLED=true. |
PEST_DETECTION_CLOUD_ENABLED | false | Nein | Cloud-Adapter (Kindwise) ein/aus. Erfordert PEST_DETECTION_CLOUD_API_KEY. |
PEST_DETECTION_CLOUD_API_KEY | — | Nein | API-Key für Kindwise (Cloud-Erkennung). Ohne Key ist der Cloud-Adapter deaktiviert. |
PEST_DETECTION_PRIMARY_ADAPTER | local_pest_symptom | Nein | Bevorzugter Adapter. Mögliche Werte: local_pest_symptom, local_pest_detector (Phase 2), kindwise_pest, demo_pest. Ein anderer Wert — etwa ein Tippfehler — wird beim Start abgelehnt: API und Worker starten dann gar nicht erst, statt still einen anderen Anbieter zu verwenden. |
PEST_DETECTION_MAX_IMAGE_SIZE_MB | 8 | Nein | Maximale Bildgröße in Megabyte. Größere Bilder werden mit HTTP 400 abgelehnt. |
Self-Hosted-First
Der lokale Adapter (local_pest_symptom) benötigt keinen API-Key und erfordert keine Nutzereinwilligung. Cloud-Erkennung ist opt-in und einwilligungspflichtig (Consent-Zweck pest_detection_cloud).
CV-Krankheitsdiagnose (REQ-038)¶
Diese Variablen konfigurieren die optionale, self-hosted Foto-Diagnose für Krankheiten und Nährstoffmängel (abgegrenzt von der Schädlingserkennung oben). Das Feature ist standardmäßig deaktiviert; ohne CV_DIAGNOSIS_ENABLED=true bleibt der API-Endpunkt /status auf available: false, die App läuft uneingeschränkt weiter.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
CV_DIAGNOSIS_ENABLED | false | Nein | Gesamtschalter. Auf true setzen, um die Funktion zu aktivieren. |
CV_CLASSIFIER_CONFIDENCE_SHOW | 0.10 | Nein | Mindest-Konfidenz (0–1) für die Anzeige eines Treffers. Ergebnisse darunter werden verworfen. |
CV_CLASSIFIER_CONFIDENCE_HIGHLIGHT | 0.75 | Nein | Konfidenz-Schwelle (0–1), ab der ein Treffer visuell hervorgehoben wird. Löst kein automatisches Anlegen aus. |
CV_PHENOTYPE_ENABLED | true | Nein | PlantCV-Phänotyp-Kennzahlen (Blattfläche, Grün-Index, Verfärbungsanteil) im Inference-Service ein/aus. |
CV_DIAGNOSIS_MAX_IMAGE_SIZE_MB | 5 | Nein | Maximale Bildgröße in Megabyte. Größere Bilder werden mit HTTP 413 abgelehnt. |
Der Klassifikator läuft im bestehenden Inference-Service und nutzt die dort bereits konfigurierte Anbindung (INFERENCE_SERVICE_URL, INTERNAL_SERVICE_TOKEN) — es sind keine zusätzlichen Verbindungsvariablen nötig.
Self-Hosted, kein Cloud-Adapter
Anders als bei der Schädlingserkennung gibt es für die CV-Krankheitsdiagnose (Stand dieser Version) keinen Cloud-Adapter — Fotos verlassen die Instanz nie. Die Einwilligung plant_diagnosis ist trotzdem erforderlich (Voll-Modus), weil ein Foto verarbeitet wird (siehe Datenschutz & DSGVO).
Lizenzhinweise
Das Modell wird auf dem CC-BY-4.0-lizenzierten PlantDoc-Datensatz fine-getunt; die Phänotyp-Pipeline nutzt PlantCV (MPL-2.0). Vollständige Attributionen: NOTICE.md.
Browser Push / PWA (VAPID)¶
Diese Variablen aktivieren den Browser-Push-Benachrichtigungskanal (channel_key: "pwa"). Sind alle drei Variablen leer, ist der Kanal deaktiviert — die Anwendung bleibt vollständig funktionsfähig, Nutzer sehen dann die Meldung "Nicht konfiguriert" in den Benachrichtigungseinstellungen.
Schritt-für-Schritt-Anleitung
Der Guide Browser-Push einrichten führt durch das Erzeugen des Schlüsselpaars, das Eintragen in Docker Compose bzw. Kubernetes und die Verifikation der Einrichtung.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
VAPID_PUBLIC_KEY | — | Nein* | VAPID-Public-Key (Base64url, 87 Zeichen). Wird an den Browser übermittelt und in der PWA-Subscription verwendet. |
VAPID_PRIVATE_KEY | — | Nein* | VAPID-Private-Key (Base64url oder PEM). Nur serverseitig — niemals im Frontend oder in Logs ausgeben. |
VAPID_CONTACT_EMAIL | — | Nein* | Kontakt-E-Mail für den Push-Service (Format: mailto:admin@example.com). Von den Push-Diensten (FCM, APNS, Mozilla) bei Problemen genutzt. |
PWA_PUSH_ENDPOINT_ALLOWED_HOSTS | — (leer) | Nein | SSRF-Härtung (SEC-001): Kommagetrennte Liste erlaubter Host-Suffixe für Web-Push-Endpunkte, z. B. fcm.googleapis.com,updates.push.services.mozilla.com. Leer (Standard) fällt auf eine HTTPS-Pflicht plus Ablehnung privater IP-Adressen zurück, sodass selbst gehostete Push-Server weiterhin funktionieren. |
*Alle drei VAPID_*-Variablen müssen gesetzt sein, damit der Browser-Push-Kanal aktiv wird. Fehlt eine Variable, bleibt der Kanal deaktiviert. PWA_PUSH_ENDPOINT_ALLOWED_HOSTS ist unabhängig davon optional.
Schlüsselpaar generieren¶
Ausgabe:
Alternativ mit pywebpush (Python) — b64urlencode ist nötig, da v.public_key/v.private_key Schlüsselobjekte sind und erst die Serialisierung die Base64url-Strings liefert:
pip install pywebpush
python3 - <<'PY'
from py_vapid import Vapid
from py_vapid.utils import b64urlencode
from cryptography.hazmat.primitives import serialization
v = Vapid()
v.generate_keys()
pub_raw = v.public_key.public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
)
priv_raw = v.private_key.private_numbers().private_value.to_bytes(32, "big")
pub, priv = b64urlencode(pub_raw), b64urlencode(priv_raw)
assert pub_raw[0] == 0x04 and len(pub_raw) == 65 and len(pub) == 87, "invalid public key"
assert len(priv_raw) == 32 and len(priv) == 43, "invalid private key"
print("VAPID_PUBLIC_KEY =", pub)
print("VAPID_PRIVATE_KEY=", priv)
PY
Private Key serverseitig halten
Der VAPID_PRIVATE_KEY darf niemals im Frontend, in Logs oder in öffentlichen Konfigurationsdateien erscheinen. Speichere ihn als Kubernetes Secret oder Docker-Secret — analog zu JWT_SECRET_KEY.
Kubernetes Secret für VAPID
Saison- & Überwinterungs-Automatik¶
Diese Variablen steuern die Schwellwerte der automatischen Saison-/Überwinterungserkennung (siehe Saison-Automatik). Sie betreffen nur die Live- und Klimatologie-Stufen der Erkennungs-Kaskade — der Kalender-Fallback ist davon unabhängig.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
SEASON_PRE_WINTER_TEMP_C | 5.0 | Nein | Temperaturschwelle (°C) für den Übergang von „Wachstumsphase" zu „Winter kündigt sich an". |
SEASON_FROST_TEMP_C | 2.0 | Nein | Temperaturschwelle (°C) für den Übergang in die Winterruhe. |
SEASON_SPRING_TEMP_C | 10.0 | Nein | Temperaturschwelle (°C) für den Übergang in die Frühjahrs-Rückholung. |
SEASON_SIGNAL_THRESHOLD_DAYS | 3 | Nein | Anzahl aufeinanderfolgender Signaltage, bevor ein Übergang ausgelöst wird (Oszillationsschutz). |
SEASON_STATE_EVAL_ENABLED | true | Nein | Schalter für den täglichen Auswertungs-Task. Auf false setzen, um die Saison-Automatik komplett zu deaktivieren. |
Fehler-Tracking (optional)¶
Meldet Laufzeitfehler an einen Sentry-protokollkompatiblen Tracker (Referenz: GlitchTip). Ist SENTRY_DSN leer, passiert nichts — das SDK wird nie initialisiert, das Frontend lädt sein SDK-Bündel nicht einmal herunter. Mit DSN lädt das Frontend es erst, wenn die Person der Fehleranalyse zugestimmt hat. Ausführlich: Fehler-Tracking.
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
SENTRY_DSN | — (leer) | Nein | Ingest-URL des Trackers, Form https://<public-key>@host/<projekt-id>. Enthält nur einen öffentlichen Schlüssel, kein Geheimnis. Leer = abgeschaltet. |
SENTRY_ENVIRONMENT | development | Nein | Stufe aus dem festen Vokabular development, e2e, staging, production. Alarmregeln filtern auf genau diese Werte; ein abweichender Wert meldet trotzdem, wird aber protokolliert. |
SENTRY_RELEASE | Komponente + Version | Nein | Image-Tag oder Commit-SHA. Ohne sie sind Regressionserkennung und die Zuordnung „welches Deployment war es" unmöglich. |
SENTRY_SAMPLE_RATE | 1.0 | Nein | Anteil der gemeldeten Ereignisse (0–1). 1.0 ist eine bewusste Entscheidung für dieses Aufkommen, keine unangetastete Voreinstellung. Unlesbare Werte fallen auf 1.0 zurück. |
Alle vier Variablen gelten für Backend, Celery Worker und Beat, Inference- und Knowledge-Service sowie das Frontend. Im Frontend werden sie zur Laufzeit über runtime-config.js eingespeist, nicht ins Build gebacken — ein Image bedient damit alle Stufen.
Selbst gehosteter Tracker: NetworkPolicy nicht vergessen
Unter Kubernetes schließt die Egress-Regel des Backends die privaten Adressbereiche aus. Ein Tracker im eigenen Cluster oder LAN braucht deshalb eine zusätzliche Egress-Regel — sonst werden Ereignisse stillschweigend verworfen.
Vollständiges .env-Beispiel¶
# Datenbank
ARANGO_ROOT_PASSWORD=sicheres-root-passwort
ARANGODB_HOST=arangodb
ARANGODB_PORT=8529
ARANGODB_DATABASE=kamerplanter
ARANGODB_USERNAME=root
ARANGODB_PASSWORD=sicheres-root-passwort
# Cache / Queue
REDIS_URL=redis://valkey:6379/0
# Sicherheit (alle vier sind Pflicht-Secrets, Startup-Gate in Produktion)
JWT_SECRET_KEY=erzeugen-mit-openssl-rand-hex-32
FERNET_KEY=erzeugen-mit-Fernet.generate_key
ERASURE_TOMBSTONE_SALT=erzeugen-mit-openssl-rand-hex-32
LOG_PSEUDONYM_SALT=erzeugen-mit-openssl-rand-hex-32
# Default true; nur ohne ausgehenden Mailversand auf false setzen
REQUIRE_EMAIL_VERIFICATION=true
# Wer ein Konto anlegen darf: open | invite_only | closed (Default open)
REGISTRATION_MODE=open
# CORS
CORS_ORIGINS=["http://localhost:5173","http://localhost:3000"]
# Betriebsmodus
KAMERPLANTER_MODE=full
DEBUG=false
# E-Mail (Entwicklung)
EMAIL_ADAPTER=console
# mDNS Discovery (LAN-only, opt-in)
# MDNS_ENABLED=false
# INSTANCE_ID=
# Optionale externe APIs
PERENUAL_API_KEY=
HA_URL=
HA_ACCESS_TOKEN=
# Knowledge Service — Re-Ranking (leer = deaktiviert)
RERANKER_URL=
RERANKER_INITIAL_K=15
RERANKER_MAX_DOCUMENT_CHARS=500
RERANKER_TOP_K=5
# KI-Assistent (instanzweit deaktiviert, solange nicht explizit aktiviert)
AI_FEATURES_ENABLED=false
KNOWLEDGE_SERVICE_ENABLED=false
KNOWLEDGE_SERVICE_URL=http://knowledge-service:8000
# Foto-Identifikation (leer = Feature deaktiviert)
# PLANTNET_API_KEY=
# IDENTIFICATION_RATE_LIMIT_PER_USER_DAY=50
# Browser Push / PWA (leer = Kanal deaktiviert)
# VAPID_PUBLIC_KEY=
# VAPID_PRIVATE_KEY=
# VAPID_CONTACT_EMAIL=mailto:admin@example.com
Object Storage (NFR-013)¶
Diese Variablen konfigurieren den Storage-Adapter für Binärdaten (Fotos, Importe, Exporte). Das aktive Backend wird durch STORAGE_BACKEND bestimmt. Standardmäßig ist local-fs aktiv — keine weitere Konfiguration nötig.
Weitere Hintergrundinformationen: Speicher konfigurieren (Object Storage) und Helm Charts — Storage-Konfiguration.
Allgemeine Storage-Einstellungen¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
STORAGE_BACKEND | local-fs | Nein | Aktives Backend: local-fs oder s3. Ein anderer Wert wird beim Start abgelehnt. |
STORAGE_MAX_FILE_SIZE_MB | 25 | Nein | Maximale Upload-Größe in Megabyte (gilt für alle Kategorien, überschreibbar per Kategorie) |
STORAGE_PRESIGN_TTL_SECONDS | 900 | Nein | Gültigkeitsdauer von Pre-Signed URLs in Sekunden (max. 3600) |
UPLOAD_SPOOL_BUDGET_MB | 52 | Nein | Wie viele MiB gleichzeitige Uploads zusammen in /tmp zwischenspeichern dürfen. Gezählt werden die Bytes, die tatsächlich angekommen sind. Muss mindestens STORAGE_MAX_FILE_SIZE_MB + 1 sein, sonst startet das Backend nicht. Halte den Wert unter der Größe von /tmp im Backend-Pod (im Chart 64 MiB). Das Helm-Chart setzt ihn auf 52 oder, bei größerer Upload-Grenze, auf storage.maxFileSizeMb + 1. Er begrenzt auch die Upload-Grenze des Mandanten-Imports (TENANT_IMPORT_MAX_UPLOAD_MB): Für Archive bis N MiB hebst du ihn auf mindestens N + 1 an, zusammen mit /tmp und dem Speicherlimit (Issue #2138). |
UPLOAD_SPOOL_WAIT_SECONDS | 30 | Nein | Wie lange ein Upload auf freies Budget wartet. Danach antwortet das Backend mit 503 (UPLOAD_CAPACITY_EXHAUSTED) und Retry-After. |
STORAGE_ALLOWED_MIME_TYPES | (Liste) | Nein | Kommagetrennte globale Whitelist erlaubter MIME-Types |
STORAGE_ALLOWED_MIME_TYPES_<CATEGORY> | (pro Kategorie) | Nein | Kategorie-spezifische Whitelist, z. B. STORAGE_ALLOWED_MIME_TYPES_IMPORT=text/csv |
STORAGE_VIRUS_SCAN_ENABLED | false | Nein | Virenscan via ClamAV-REST-Wrapper aktivieren |
STORAGE_VIRUS_SCAN_ENDPOINT | (leer) | Nein | URL des ClamAV-REST-Wrappers |
STORAGE_STRIP_EXIF | true | Nein | Entfernt EXIF-/GPS-Metadaten aus Bild-Uploads global beim Speichern (NFR-013 §5.1). Es gibt keine Kategorie-spezifische Override-Variable — anders als bei den MIME-Whitelists ist dies ein einzelner, globaler Schalter. Solange er aktiv ist, lehnt der Upload Bildtypen ab, deren Metadaten die Bereinigung nicht entfernen kann (HEIC/HEIF, GIF), auch wenn eine MIME-Whitelist sie zulässt. Bilder über 40 Megapixel werden unabhängig von diesem Schalter mit 413 abgelehnt. |
STORAGE_TENANT_QUOTA_MB | 2048 | Nein | Speicherkontingent pro Mandant in Megabyte. 0 deaktiviert das Kontingent (unbegrenzt). |
STORAGE_MAX_PHOTOS_PER_INSTANCE | 50 | Nein | Maximale Anzahl Galerie-Fotos je Pflanzeninstanz (REQ-034). 0 deaktiviert das Limit. |
STORAGE_TASK_PHOTO_ORPHAN_HOURS | 0 | Nein | Wie lange ein Aufgabenfoto unreferenziert liegen darf, bevor der nächtliche Aufräum-Task es löscht (#1393). 0 schaltet ihn ab — und ist der Auslieferungswert: Der Job löscht Daten über eine Referenz-Historie, die jede Client-Version und eine manuelle Migration umfasst, und geht in seinem ersten Release bewusst nicht scharf. Zum Einschalten eine positive Stundenzahl setzen; jeder Upload ist kurzzeitig unreferenziert (der Anhang wird geschrieben, bevor das Formular ihn verknüpft), der Wert ist also ein weiter Sicherheitsabstand und kein Feineinstellungs-Regler — 48 ist die Größe, um die herum gebaut und getestet wurde. |
STORAGE_RECONCILE_DELETE_ENABLED | false | Nein | Schaltet das Löschen des nächtlichen Objekt-Abgleichs ein (#1834). Der Task reconcile_orphaned_storage_objects (täglich 04:10) listet den Object Store unter t/ und sucht Objekte, die kein Anhang-Datensatz mehr hält — Reste eines fehlgeschlagenen Storage-Aufrufs nach einer Löschung, eines Datensatzes im Fenster einer Kontolöschung oder einer Schädlingsreferenz in einem verlassenen Mandanten. Ausgelieferter Wert false: Der Lauf berichtet nur. Im Worker-Log steht je Lauf die Zeile reconcile_orphaned_storage_objects_audit mit scanned, found (kein Datensatz hält das Objekt), young (jünger als die Marge, bleibt), undated (kein lesbares Alter, bleibt), eligible, would_delete, deleted, failed, freed_bytes und truncated; sie enthält nie Mandanten-, Nutzerschlüssel oder Objektpfade. Erst wenn would_delete über mehrere Läufe nur Objekte beschreibt, die du erwartest, true setzen; dann löscht der Lauf genau diese Objekte samt Vorschaubildern und nie eines, das ein Datensatz hält. |
STORAGE_RECONCILE_MIN_AGE_HOURS | 24 | Nein | Mindestalter eines Objekts ohne Datensatz, bevor der Abgleich es beurteilt (#1834). Ein Upload schreibt das Objekt vor dem Datensatz, ein frisches Objekt ohne Datensatz ist also kurzzeitig normal. Die Untergrenze ist 1 (Request plus die Wiederholungen des Thumbnail-Tasks); kleinere Werte lehnt der Start ab. Ein Sicherheitsabstand, kein Regler. |
STORAGE_RECONCILE_MAX_OBJECTS_PER_RUN | 200000 | Nein | Höchstzahl der Objekte, die ein Lauf des Abgleichs prüft (Untergrenze 1000). Ein größerer Bestand wird über mehrere Nächte abgedeckt: truncated: true im Bericht, der nächste Lauf setzt an der gemerkten Stelle fort (Token in Valkey; fehlt er, beginnt der Lauf von vorn). |
STORAGE_RECONCILE_MAX_ORPHAN_FRACTION | 0.5 | Nein | Notbremse des Objekt-Abgleichs (#1834): Ist auf einer Seite (ab 20 Objekten) mehr als dieser Anteil der Objekte ohne Datensatz, löscht der Lauf dort nichts und meldet brake_tripped: true. Das fängt einen Katalog ab, der gültig, aber leer antwortet — falsche Datenbank über einem bestehenden Bucket, ein Restore, der älter als der Speicher ist, zwei Installationen auf einem Bucket —, bei dem keine Abfrage fehlschlägt und jedes Objekt „verwaist“ aussähe. Wertebereich 0 < x <= 1. Löst die Bremse aus, den Katalog prüfen, bevor du das Löschen weiter eingeschaltet lässt. |
Standard-MIME-Whitelist pro Kategorie:
| Kategorie | Erlaubte MIME-Types | Max-Größe |
|---|---|---|
diary, ipm, harvest, post_harvest, task, id_recognition, plant | image/jpeg, image/png, image/webp | 25 MB |
import | text/csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | 50 MB |
export | application/pdf, text/csv, application/zip | 200 MB |
tenant_export | application/zip | 5 GB |
Backend: Lokales Dateisystem (local-fs)¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
STORAGE_LOCAL_FS_ROOT | /data/attachments | Nein | Mount-Pfad innerhalb des Containers |
STORAGE_LOCAL_FS_PUBLIC_BASE_URL | (leer) | Ja* | Vollständige URL des Token-Download-Endpunkts, z. B. https://api.kamerplanter.example.com/api/v1/attachments/token. Muss auf https://<host>/api/v1/attachments/token zeigen. |
STORAGE_LOCALFS_SIGNING_SECRET | (ephemer) | Ja** | Geheimer Schlüssel für Token-Signaturen. Pflicht bei mehr als einer Replica, sonst können Tokens von anderen Pods nicht validiert werden. |
*Pflicht, damit local-fs Token-Downloads einlösen kann. **Pflicht bei Multi-Replica-Betrieb.
Signing-Secret als Kubernetes-Secret speichern
Der STORAGE_LOCALFS_SIGNING_SECRET ist ein kryptographisches Secret und darf nicht im Klartext in values.yaml oder Git committet werden. Anlegen als Kubernetes Secret:
Backend: S3-kompatibel (s3)¶
| Variable | Standard | Pflicht | Beschreibung |
|---|---|---|---|
STORAGE_S3_ENDPOINT_URL | (leer) | Ja | Vollständige Endpunkt-URL, z. B. https://s3.eu-central-1.amazonaws.com |
STORAGE_S3_REGION | (leer) | Ja | Region, z. B. eu-central-1 (auch bei MinIO erforderlich) |
STORAGE_S3_BUCKET | (leer) | Ja | Bucket-Name (muss vorab angelegt sein) |
STORAGE_S3_ACCESS_KEY_ID | (leer) | Ja | Access Key (aus External Secrets Operator — niemals im Klartext in Git) |
STORAGE_S3_SECRET_ACCESS_KEY | (leer) | Ja | Secret Access Key (aus External Secrets Operator — niemals im Klartext in Git) |
STORAGE_S3_USE_PATH_STYLE | false | Nein | true für MinIO und die meisten Nicht-AWS-Anbieter |
STORAGE_S3_FORCE_TLS | true | Nein | Plain-HTTP verbieten; in Dev-Umgebungen auf false setzen |
STORAGE_S3_KMS_KEY_ID | (leer) | Nein | Optionaler Customer-Managed Key für serverseitige Verschlüsselung (SSE-KMS) |
STORAGE_S3_ALLOW_PRIVATE_ENDPOINT | false | Nein | true für in-Cluster MinIO, das nicht öffentlich erreichbar ist |
S3-Credentials niemals in Git oder values.yaml
STORAGE_S3_ACCESS_KEY_ID und STORAGE_S3_SECRET_ACCESS_KEY sind Secrets und werden ausschließlich über den External Secrets Operator (ESO) oder Kubernetes Secrets bereitgestellt. Weitere Details: Helm Charts — Storage-Konfiguration.
Beispielkonfigurationen¶
STORAGE_BACKEND=s3
STORAGE_S3_ENDPOINT_URL=http://minio.kamerplanter.svc:9000
STORAGE_S3_REGION=us-east-1
STORAGE_S3_BUCKET=kamerplanter
STORAGE_S3_ACCESS_KEY_ID=<aus Secret>
STORAGE_S3_SECRET_ACCESS_KEY=<aus Secret>
STORAGE_S3_USE_PATH_STYLE=true
STORAGE_S3_FORCE_TLS=false
STORAGE_S3_ALLOW_PRIVATE_ENDPOINT=true
Häufige Fragen¶
Kann ich Umgebungsvariablen in Kubernetes als Secrets hinterlegen?
Ja. Verwenden Sie Kubernetes Secrets für sensible Werte (ARANGODB_PASSWORD, JWT_SECRET_KEY) und referenzieren Sie sie im Deployment-Manifest über valueFrom.secretKeyRef.
Wo kann ich prüfen, welche Werte das Backend tatsächlich verwendet?
Mit DEBUG=true loggt das Backend beim Start alle geladenen Einstellungen. Alternativ im Container:
Siehe auch¶
- Konfigurationsmatrix — Funktion → Dienste → Schalter → Pflicht-Secrets → Ressourcen in einer Tabelle
- Betriebsprofile — Empfohlene Komponenten-Bündel für typische Anwendungsfälle
- Lokales Setup
- Betriebs-Fehlerbehebung
- Deployment Kubernetes
- Wetterquellen je Standort — Benutzerhandbuch
- Benachrichtigungen: Frost-Frühwarnung — Benutzerhandbuch
- API-Referenz: CV-Krankheitsdiagnose
- MCP-Server
- Datenschutz & DSGVO — KI-Krankheitsdiagnose
- Gießprotokoll: Vorgeschlagene Gießmenge — Benutzerhandbuch
- Betriebsmittel & Inventar (InvenTree) — Benutzerhandbuch
- Umgebungssteuerung & Aktorik — Benutzerhandbuch
- API-Referenz: Umgebungssteuerung & Aktorik