Zum Inhalt springen
openplate

Die App

Self-Hosting

Compose-Anleitungen, das erste Konto, Betrieb ohne Docker, erster Start, HTTPS, Backups, Upgrades

Diese Seite wurde maschinell aus der englischen Dokumentation übersetzt.

openplate wird als vorgefertigtes Multi-Arch-Docker-Image (linux/amd64 + linux/arm64; Rechner der Raspberry-Pi-Klasse sind ein vorrangiges Ziel) ausgeliefert und in der GitHub Container Registry veröffentlicht. Der Tag latest ist die neueste Version. Jede Version hat zudem ihren eigenen Versions-Tag. Der Tag main folgt jeder Änderung auf dem Main-Branch. Es müssen keine Geheimnisse erzeugt werden, und außer deinem eigenen Schlüssel für einen KI-Anbieter musst du dich nirgends registrieren.

Hier gibt es keine abgespeckte Version. Lokales Erfassen, BYOK-Tellererkennung per KI, PWA-Installation, JSON-Export/-Import, die gesamte Oberfläche: identisch mit der gehosteten Instanz, weil es dasselbe Image ist. Das Einzige, was das gehostete Setup hinzufügt, ist der optionale Sync-Dienst, den wir für dich betreiben, und auch der ist quelloffen, damit du ihn selbst betreiben kannst.

Was du ausführen kannst

  • Die App allein. Ergänzt einen Container. Du erhältst ein schnelles Setup ohne Datenbank oder Secrets, die du verwalten musst. Du riskierst den Verlust deines Tagebuchs, wenn du den Browser leerst, es gibt keinen geräteübergreifenden Sync und Scans erfordern einen Cloud-KI-Schlüssel.
  • Die App plus der Core-Server. Ergänzt openplate-core und Postgres. Du erhältst verschlüsselten Sync über Geräte hinweg und eine optionale gemeinsame KI-Abrechnung. Du riskierst Datenverlust, wenn du die Datenbank, das Secret und deinen Wiederherstellungsschlüssel nicht sicherst.
  • Die App samt selbst gehosteter Inferenz. Ergänzt openplate-inference. Du erhältst lokale Teller-Scans ohne Cloud-Konto und ohne dass Fotos dein Netzwerk verlassen. Du riskierst Hardware-Belastung, zudem muss jeder Browser den Inferenz-Container direkt erreichen.
  • Alles. Sync und Inferenz zusammen, insgesamt vier Container. Du erhältst vollständigen Datenschutz bei geräteübergreifendem Sync. Du riskierst den höchsten Betriebsaufwand und die stärkste Ressourcenlast.

Jede Ausprägung entspricht einer Compose-Datei unter docker/topologies/; topologies.md erklärt die Auswahl.

Bevor du beginnst

Installiere Docker. Ein neuer Server hat es nicht. Folge der Docker-Anleitung für deine Distribution unter docs.docker.com/engine/install. Unter Ubuntu 24.04 funktionieren auch die Distributionspakete:

bash
sudo apt-get update
sudo apt install docker.io docker-compose-v2
sudo usermod -aG docker "$USER"   # then log out and back in, to use docker without sudo

Podman funktioniert ebenfalls. Lies podman.md für die Unterschiede.

Wähle einen dauerhaften Ordner. Jede Anleitung unten beginnt mit mkdir -p ~/openplate && cd ~/openplate. Die Compose-Datei und ihre .env liegen dort. Führe jeden späteren Befehl von dort aus, einschließlich Upgrades, Backups und Logs. Verwende nicht /tmp. Ein Neustart kann das Verzeichnis leeren, und deine .env mit ihren Secrets verschwindet.

Wenn du dies für deine Familie betreibst, erledige zwei Dinge frühzeitig. - Sichere .env und die Datenbank. Kopiere .env an einen sicheren Ort, vor allem die Zeile SERVER_SECRET. Ohne sie öffnet eine wiederhergestellte Datenbank kein Konto. Sichere die Datenbank danach nach Zeitplan. Backups enthält die Befehle. - Lass andere Geräte nur den HTTPS-Port erreichen. Viele Server starten ohne Firewall, sodass jeder von einem Container veröffentlichte Port in deinem Netzwerk offen ist. Belasse die Container-Ports auf 127.0.0.1, wie im Abschnitt HTTPS gezeigt, damit nur dein Reverse-Proxy sie erreicht. Eine Firewall wie ufw schließt sie nicht für dich, da Docker seine Ports daran vorbei veröffentlicht. ufw schließt dennoch alles andere auf dem Server. Erlaube zuerst SSH, dann HTTPS: ``bash sudo ufw allow OpenSSH sudo ufw allow 443/tcp sudo ufw allow 8443/tcp # the core server's HTTPS port, in the recipes below sudo ufw enable ` With a domain name, Caddy also needs port 80 for its certificate: sudo ufw allow 80/tcp`.

Die App allein

Container-Tool
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/compose.yml
docker compose -f compose.yml up -d
Öffne es als http://localhost:3000 auf dem Server selbst oder über HTTPS. Von einem anderen Gerät aus zeigt http://<the server's address>:3000 das Tagebuch an. Die Installation der App, die Offline-Nutzung und die Ein-Klick-Verbindung zu OpenRouter funktionieren dort nicht. Siehe HTTPS.

Podman führt diese Dateien mit podman compose aus. Unter Ubuntu benötigt dieser Unterbefehl das installierte Paket podman-compose als Anbieter: siehe podman.md.

Du erhältst einen Container, keine Datenbank, keinen .env-Schritt und kein Secret, das du generieren musst. Die App ist unter http://localhost:3000 erreichbar.

Der Port wird auf der Schnittstelle jeder veröffentlicht. Die App ist unter der LAN-Adresse dieses Rechners erreichbar, sobald up -d zurückkehrt. Ändere die Zeile ports: in einem geteilten Netzwerk zu '127.0.0.1:3000:3000' und erreiche sie stattdessen über einen Reverse-Proxy.

TRUST_PROXY legt fest, wie viele Reverse-Proxys vor der App stehen. Die Compose-Datei verwendet standardmäßig 1. Das ist hinter einem Proxy wie Caddy oder nginx korrekt. Ohne dies sieht die CSRF-Prüfung der App die falsche Adresse und Formularübertragungen schlagen fehl. Setze es ohne vorgeschalteten Proxy auf 0:

bash
echo "TRUST_PROXY=0" >> .env
docker compose -f compose.yml up -d

Ohne Proxy funktionieren Seiten bei beiden Werten. Allerdings erlaubt 1 einem Besucher, seine Adresse in X-Forwarded-For zu fälschen und das Limit für Lebensmittelabfragen pro Adresse zu umgehen.

Baue das Image selbst

Um aus dem Quellcode zu bauen, statt das veröffentlichte Image herunterzuladen, kommentiere image: in docker/compose.yml aus, hebe die Auskommentierung von build: auf und führe dies vom Root-Verzeichnis des Repositories aus, da der Build-Kontext relativ zu dieser Datei ist:

Container-Tool
docker compose --project-directory . -f docker/compose.yml build
docker compose --project-directory . -f docker/compose.yml up -d

Für den Betrieb ganz ohne Container siehe Ohne Docker.

Jedes andere Setup (Sync, selbst gehostete Inferenz oder beides) ist eine separate Datei unter docker/topologies/. Siehe topologies.md, um eines auszuwählen.

Die App plus dein eigener Core-Server

docker/topologies/compose.core.yml ist die Referenz-Bereitstellung für die App, den Core-Server und die Postgres-Datenbank, die sync benötigt. Die App verbindet sich weiterhin mit keiner eigenen Datenbank. (Für Self-Hosting der Inferenz siehe Die App plus Sync und selbst gehostete Inferenz unten. Dieses Setup nutzt dieselbe Abgleich-Konfiguration, ergänzt um die Modell-Runtime.)

bash
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.core.yml

# The core server needs exactly one secret. Generate it and keep it with your backups.
echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env

# Your key to the admin API. You need it to create the first account.
echo "ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env

# The URLs a BROWSER will use to reach each service. Skip these two for a test
# on this machine, or through the ssh tunnel in the HTTPS section.
echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env

# 1 behind one reverse proxy, 0 with none.
echo "TRUST_PROXY=1" >> .env

docker compose -f compose.core.yml up -d
bash
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.core.yml

echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env
echo "ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env

echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env
echo "TRUST_PROXY=1" >> .env

podman compose -f compose.core.yml up -d
Konten erfordern eine sichere Seite. Anmeldung, Registrierung und das Öffnen eines Einladungslinks schlagen über reines http://<the server's address> fehl: Der Browser blockiert die Kryptografie, die sie nutzen. Liefere beide Adressen über HTTPS aus oder teste über localhost. Siehe HTTPS.

Lies README von openplate-core, bevor du diese letzte Zeile auf einem Rechner ausführst, den andere erreichen können. Beide Dienste veröffentlichen ihre Ports auf jeder Schnittstelle. Der Kontodienst ist ab dem Moment des Starts erreichbar, und der Betrieb eines Kontodienstes ist ein größeres Unterfangen als der Betrieb der App.

Die Datei ist Zeile für Zeile kommentiert, einschließlich der beiden Einstellungen, die bei falscher Konfiguration Probleme verursachen (SERVER_SECRET und TRUST_PROXY). TRUST_PROXY gilt für beide Dienste, da sie hinter demselben Proxy oder hinter gar keinem liegen. Die Datei übergibt jede Variable, die die Dienste aus .env lesen, an deren Container. environment-variables.md listet sie alle auf. Unter sync.md findest du Erklärungen dazu, was Sync ist und wie der Client ihn erreicht.

Das erste Konto erstellen

Niemand kann sich selbst registrieren. Ein Konto entsteht durch das Öffnen einer Einladung, die an eine E-Mail-Adresse gerichtet ist. Die erste Einladung für dich selbst erzeugst du auf dem Server mit ADMIN_TOKEN. Führe dies in ~/openplate mit deiner eigenen Adresse aus. Port 3001 ist der Port, auf dem compose.core.yml und compose.full.yml den Core-Server veröffentlichen. Die eigene Compose-Datei des Core-Servers in apps/core nutzt stattdessen Port 3000:

bash
ADMIN_TOKEN=$(grep '^ADMIN_TOKEN=' .env | cut -d= -f2)
curl -s -X POST http://127.0.0.1:3001/v1/admin/invites \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com","displayName":"You","role":"admin"}'

Die Antwort ist eine Zeile JSON. Der relevante Teil sieht so aus:

json
"emailed":false,"link":"https://openplate.example.com/join#server=https%3A%2F%2Fsync.example.com&invite=si_..."
  • Kein Mailversand konfiguriert (Standard): "emailed": false. Es wurde niemand angeschrieben. Kopiere die link und öffne sie selbst.
  • Mailversand konfiguriert (siehe E-Mail): "emailed": true. Derselbe Link ist auch per E-Mail an diese Adresse unterwegs.

Öffne den Link im Browser über eine sichere Seite, wähle ein Passwort, und das Konto existiert. Ein Smartphone oder zweites Gerät kann sich erst anmelden, nachdem du HTTPS eingerichtet hast, da der SSH-Tunnel und localhost nur einen einzelnen Computer bedienen. Der Link funktioniert einmal und läuft nach sieben Tagen ab. "role":"admin" macht dieses erste Konto zum Administrator. Von da an lädst du Personen direkt in der App unter /admin ein. Auf einer Instanz ohne E-Mail zeigt sie jeden neuen Link an. Für ein normales Mitglied lässt du role weg. E-Mail erklärt beide Wege: jeden Link manuell weiterzugeben oder den Core-Server den Versand per E-Mail übernehmen zu lassen.

Füge auf einer verwalteten Instanz, auf der der Core-Server für die Scans aller Nutzer zahlt, "dailyAiLimit":200 in den Body ein, um dem Konto 200 KI-Anfragen pro Tag zu gewähren. Der Standardwert ist 0. Eine verwaltete Instanz benötigt vier weitere Zeilen in .env. Scans starten ohne Modell nicht, da die App kein Modell auf deine Rechnung auswählt:

bash
echo "INSTANCE_MODE=managed" >> .env
echo "UPSTREAM_BASE_URL=https://openrouter.ai/api/v1" >> .env
echo "UPSTREAM_API_KEY=sk-or-..." >> .env
echo "AI_ADVERTISED_MODEL=vendor/model-name" >> .env

Ersetze vendor/model-name durch das Modell deines Anbieters, genau so geschrieben, wie der Anbieter es schreibt. Statt AI_ADVERTISED_MODEL kannst du AI_TIERS_FILE=bundled setzen, dann übernimmt der Core-Server das Modell aus seiner Tier-Datei, ai-tiers.json. Siehe configuration.md.

Wenn jemand das Passwort vergisst

Wenn Mailversand konfiguriert ist, versendet Passwort vergessen in der App einen Link zum Zurücksetzen, und das Tagebuch ist nach dem Zurücksetzen wieder da. Ohne Mailversand kann die App nichts senden, und die Seite zum Zurücksetzen weist den Benutzer an, den Administrator zu fragen. Du erstellst den Link so:

  • In der App: Verwaltung, öffne unter Personen die Person und wähle Link zum Zurücksetzen senden. Ohne Mailversand zeigt die Seite den Link an. Teile ihn so, wie du auch ein Passwort teilen würdest.
  • Auf dem Server: ermittle die id des Kontos und fordere dann einen Link dafür an. Der Port ist wieder 3001, wie in den Compose-Dateien der Topologie.
bash
ADMIN_TOKEN=$(grep '^ADMIN_TOKEN=' .env | cut -d= -f2)
curl -s http://127.0.0.1:3001/v1/admin/accounts -H "Authorization: Bearer $ADMIN_TOKEN"
curl -s -X POST http://127.0.0.1:3001/v1/admin/accounts/1/reset-mail \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Der zweite Aufruf antwortet mit {"emailed":false,"link":"https://openplate.example.com/reset#server=...&token=sr_..."}. Ein Link zum Zurücksetzen funktioniert einmal und läuft nach einer Stunde ab.

E-Mail

Der Core-Server kann Einladungs- und Passwort-Reset-Mails senden. Er muss es nicht. Eine Familien-Instanz läuft ganz ohne E-Mail-Setup, und das ist der einfachste Weg.

Keine E-Mail

Lass alle E-Mail-Einstellungen leer. Der Core-Server verschickt keine Mails. Er zeigt dir stattdessen jeden Link an, und du gibst ihn wie ein Passwort weiter.

  • Eine Einladung: öffne Verwaltung unter /admin und wähle Jemanden einladen. Gib die Adresse ein und wähle Einladung senden. Die Seite meldet Einladung bereit für diese Adresse und zeigt den Link. Wähle Link kopieren und sende ihn an die Person, zum Beispiel in einer privaten Nachricht. Wer den Link besitzt, kann das Konto eröffnen.
  • Ein vergessenes Passwort: öffne unter Personen die Person und wähle Link zum Zurücksetzen senden. Die Seite zeigt den Link. Gib ihn auf demselben Weg weiter. Er funktioniert einmal, innerhalb einer Stunde.
  • Eine verlorene Einladung: wähle unter Einladungen neben der Adresse Erneut senden. Dadurch wird ein neuer Link erzeugt und der alte ungültig. Die Seite zeigt den neuen Link zum Kopieren an. Wähle Zurück zur Liste, um zu deinen offenen Einladungen zurückzukehren.

Verwendet der Link eine andere Adresse als dein Browser, erscheint darunter eine Warnung. Setze PUBLIC_APP_URL und PUBLIC_SYNC_URL auf die Adressen, die deine Familie nutzt, und erzeuge den Link erneut. Die Seite warnt dich außerdem, wenn der Link die Seite zwar öffnet, die App aber zu einem Core-Server unter localhost oder einer einfachen http://-Adresse leitet, die andere Geräte nicht erreichen können. Setze PUBLIC_SYNC_URL auf die https://-Adresse, die deine Familie verwendet, und erzeuge den Link erneut.

OPEN_SIGNUP=true erlaubt Fremden, ein Konto anzufragen. Ohne konfigurierte E-Mail verweigert der Core-Server mit dieser Einstellung den Start. Eine Familien-Instanz lässt sie deaktiviert, und sie bleibt aus, bis du sie setzt. Registrierung mit Turnstile erklärt die Einstellung und ihr Captcha.

SMTP

Jedes Standard-E-Mail-Konto kann die E-Mails über SMTP versenden. Füge diese Zeilen zu .env hinzu:

  • SMTP_HOST: der Servername, ohne Schema und ohne Port.
  • SMTP_PORT: 587, wenn du es weglässt.
  • SMTP_USER und SMTP_PASSWORD: die Zugangsdaten. Setze beide oder lass beide leer für einen Server, der keine Anmeldung erfordert.
  • SMTP_FROM: die Absenderadresse, als reine Adresse oder Name <address>.
  • MAIL_OPERATOR_EMAIL: deine eigene Adresse. Sie empfängt deine Kopie einer Kündigung oder eines Widerrufs. Beide Transportwege erfordern sie.

Der Port entscheidet über den Verschlüsselungsmodus. Port 465 nutzt TLS von Beginn an. Jeder andere Port muss über STARTTLS hochstufen, und der Dienst sendet keine E-Mails an einen Server, dem dies fehlt. Klartext ist nur erlaubt, wenn SMTP_HOST eine Loopback-Adresse wie localhost ist, für einen lokalen Fänger wie Mailpit (siehe Mit Mailpit testen). Der Dienst prüft Zertifikate immer.

Ein Gmail-Konto benötigt ein App-Passwort. Google erzeugt dieses nur für Konten mit aktivierter Bestätigung in zwei Schritten. Googles SMTP-Einstellungen gib smtp.gmail.com und Port 587 an:

bash
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=family.openplate@gmail.com
SMTP_PASSWORD="the app password"
SMTP_FROM="openplate <family.openplate@gmail.com>"
MAIL_OPERATOR_EMAIL=you@example.org

Amazon SES benötigt Für SES erstellte SMTP-Zugangsdaten, die sich von deinem AWS-Zugriffsschlüssel unterscheiden, sowie eine verifizierte Absendeadresse. Der Host nennt deine AWS-Region. Einzelheiten findest du in der Endpunktliste. Solange dein Konto in der SES-Sandbox bleibt, stellt SES nur an verifizierte Empfängeradressen zu.

bash
SMTP_HOST=email-smtp.eu-central-1.amazonaws.com
SMTP_PORT=587
SMTP_USER=<SES SMTP user name>
SMTP_PASSWORD=<SES SMTP password>
SMTP_FROM="openplate <noreply@example.org>"
MAIL_OPERATOR_EMAIL=you@example.org

Erstelle den Core-Server nach jeder Änderung an .env mit docker compose -f <your file> up -d neu.

Eine HTTP-Mail-API

Ein E-Mail-Dienst mit HTTP-API funktioniert ebenso. Setze alle drei Einstellungen MAIL_API_URL, MAIL_API_KEY und MAIL_API_FROM, zusätzlich MAIL_OPERATOR_EMAIL. Der Core-Server sendet jede Mail als JSON-POST-Anfrage an MAIL_API_URL, mit MAIL_API_KEY als Bearer-Token, im von der Resend-API erwarteten Format. Resend ist ein kompatibler Dienst. Bei Resend ist MAIL_API_URL gleich https://api.resend.com/emails.

Konfiguriere nur einen Übertragungsweg. Wenn du sowohl SMTP als auch die E-Mail-API setzt, verweigert der Core-Server den Start.

Mail benötigt die öffentlichen Adressen

Jede Mail enthält einen Link, und dieser Link muss sich auf dem Smartphone des Empfängers öffnen lassen. Wenn du SMTP oder eine E-Mail-API konfigurierst, setze PUBLIC_APP_URL und PUBLIC_SYNC_URL auf die https://-Adressen, die deine Familie verwendet. Verwendet eine der Einstellungen einfaches http:// oder eine Loopback-Adresse wie localhost, verweigert der Core-Server den Start. Sein Protokoll nennt jeden zu korrigierenden Wert in einer Nachricht, die so beginnt:

Mail is configured. Its messages would carry links that recipients cannot open.

Die Protokollmeldung bezeichnet diese Werte als CLIENT_BASE_URL und SERVER_PUBLIC_URL. Das sind die internen Namen, die der Core-Server liest, und die Compose-Dateien bilden sie aus PUBLIC_APP_URL und PUBLIC_SYNC_URL ab. Sieh dir die Meldung mit docker compose -f <your file> logs core an.

Mail-Funktion prüfen

Sende in /admin eine Einladung an eine zweite eigene Adresse. Die Seite sollte Einladung gesendet an für diese Adresse anzeigen und die E-Mail sollte in deinem Posteingang ankommen. Zeigt die Seite Einladung bereit für an und gibt einen Link aus, ist die Zustellung fehlgeschlagen. Der Link bleibt gültig. Prüfe das Core-Server-Protokoll auf eine Zeile mit Mail send failed, um den Grund zu sehen. Wenn du mit dem Testen fertig bist, wähle unter Einladungen für die Testeinladung Zurückziehen.

Mit Mailpit testen

Mailpit fängt jede Mail ab und zeigt sie auf einer Webseite an, sodass du E-Mails ohne E-Mail-Konto testen kannst. Führe es als Sidecar aus, der das Netzwerk des Core-Containers teilt. Der Core-Server erreicht ihn dann unter localhost, wo Klartext erlaubt ist. Speichere diese Datei neben deiner Compose-Datei als compose.mailpit.yml:

yaml
# compose.mailpit.yml: a mail catcher for testing, next to your compose file
services:
  core:
    ports:
      - '127.0.0.1:8025:8025' # Mailpit's web page, on this machine only
  mailpit:
    image: docker.io/axllent/mailpit:latest
    restart: unless-stopped
    network_mode: 'service:core'

Füge diese Zeilen zu .env hinzu. Mailpit nimmt E-Mails auf Port 1025 entgegen:

bash
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_FROM="openplate <test@example.org>"
MAIL_OPERATOR_EMAIL=you@example.org

Starte beide Dateien zusammen, sende dann eine Einladung und lies sie unter http://localhost:8025 auf dem Server:

bash
docker compose -f compose.core.yml -f compose.mailpit.yml up -d

Die Regel in Mail benötigt die öffentlichen Adressen gilt weiterhin. Setze zuerst PUBLIC_APP_URL und PUBLIC_SYNC_URL auf https://-Adressen, sonst startet der Core-Server nicht.

Ein separater Mailpit-Container, der über den Dienstnamen wie etwa SMTP_HOST=mailpit angesprochen wird, funktioniert nicht. Der Core-Server sendet Klartext nur an diese Maschine. Ein Dienstname gilt als anderer Host. Der Dienst verlangt STARTTLS, Mailpit bietet keins, und jede Mail schlägt mit Mail send failed im Protokoll fehl. Das geteilte Netzwerk platziert Mailpit auf derselben Maschine.

Wenn du mit dem Testen fertig bist, entferne die vier Zeilen aus .env. Entferne Mailpit danach mit docker compose -f compose.core.yml up -d --remove-orphans.

Ein Relay mit einer privaten Zertifizierungsstelle

Der Core-Server prüft das Zertifikat jedes E-Mail-Servers. Ein Relay in einem Firmennetzwerk nutzt möglicherweise ein Zertifikat, das von einer privaten Zertifizierungsstelle signiert ist. Node.js vertraut dieser Zertifizierungsstelle standardmäßig nicht. Übergib dem Core-Server das Zertifikat dieser Zertifizierungsstelle als PEM-Datei. Mounte die Datei in den Container und setze NODE_EXTRA_CA_CERTS auf ihren Pfad. Zum Beispiel in einer compose.ca.yml neben deiner Compose-Datei:

yaml
# compose.ca.yml: trust a private certificate authority for the mail relay
services:
  core:
    volumes:
      - ./relay-ca.pem:/etc/openplate/relay-ca.pem:ro
bash
echo "NODE_EXTRA_CA_CERTS=/etc/openplate/relay-ca.pem" >> .env
docker compose -f compose.core.yml -f compose.ca.yml up -d

Node.js liest die Datei einmalig beim Start. Das Zertifikat wird zu denen hinzugefügt, denen Node.js bereits vertraut, sodass öffentliche E-Mail-Server weiterhin funktionieren.

Die App plus Self-Hosting-Inferenz

docker/topologies/compose.inference.yml führt die App neben openplate-inference aus. Tellerfotos werden auf deiner eigenen Hardware ausgewertet, und jeder Besucher erhält per Fingertipp den Hinweis „Dieses openplate stellt seine eigene KI bereit“. Lies zuerst den Hardware-Abschnitt von topologies.md. Das kleine Modell lite benötigt auf einer CPU etwa 1,6 GB RAM und wenige Sekunden bis eine Minute pro Teller.

bash
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.inference.yml

# One key, generated here on the server. The inference service accepts it and
# the app hands it to every browser.
echo "INFERENCE_API_KEY=opk_$(openssl rand -hex 24)" >> .env

# The two URLs a BROWSER will use. Replace 192.168.1.20 with this machine's
# address, or with the names your reverse proxy serves.
echo "PUBLIC_APP_URL=http://192.168.1.20:3000" >> .env
echo "PUBLIC_INFERENCE_URL=http://192.168.1.20:8300/v1" >> .env

# 0 with no reverse proxy, 1 behind one.
echo "TRUST_PROXY=0" >> .env

docker compose -f compose.inference.yml up -d
docker compose -f compose.inference.yml logs -f inference
Einfaches HTTP genügt für Scans, HTTPS erfordert durchgehend HTTPS. Über einfaches http://<the server's address> erreicht ein Tellerfoto den Inferenz-Container, aber die Installation der App funktioniert nicht. Sobald die App über https:// läuft, muss auch die Inferenz-Adresse https:// sein. Andernfalls blockiert der Browser den Aufruf von der sicheren Seite. Siehe HTTPS.

PUBLIC_INFERENCE_URL muss eine Adresse sein, die ein Browser öffnen kann, da das Foto vom Smartphone direkt an den Inferenz-Container gesendet wird. http://inference:8300/v1, der Name, den die Container untereinander verwenden, funktioniert dort nicht. Behalte das /v1 am Ende bei.

Der erste Start lädt etwa 2 GiB an Gewichten (1,96 GiB) in ein benanntes Volume herunter, was in unseren Tests sechs bis sieben Minuten dauerte, und lädt anschließend das Modell. Das Protokoll zeigt jeden Schritt an. Die App ist sofort einsatzbereit; die Ein-Klick-KI funktioniert, sobald dies mit 200 antwortet:

bash
curl -s http://127.0.0.1:8300/readyz

Der Schlüssel steht im Quelltext der Seite, die jeder Browser lädt, sodass ihn jeder lesen kann, der die App öffnen kann. In einem Heimnetzwerk oder Tailnet ist das in Ordnung, auf einer öffentlich über das Internet erreichbaren Instanz jedoch falsch. configuration.md enthält die vollständige Regel. Der Inferenz-Container belegt bis auf zwei alle CPU-Kerne; passe LLAMA_THREADS in .env an, um das zu ändern.

Die App plus Sync und selbst gehostete Inferenz

docker/topologies/compose.full.yml betreibt vier Container: die App, den Core-Server, Postgres und die Inferenz per Self-Hosting. Lies zuerst Die App plus dein eigener Core-Server und Die App plus Self-Hosting-Inferenz. Dieser Abschnitt behandelt nur, was sich ändert, wenn alle Teile zusammen laufen.

bash
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.full.yml

# The core server needs exactly one secret. Generate it and keep it with your backups.
echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env

# Your key to the admin API. You need it to create the first account.
echo "ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env

# One key for the inference service, which the app hands to every browser.
echo "INFERENCE_API_KEY=opk_$(openssl rand -hex 24)" >> .env

# The URLs a BROWSER will use to reach each service. PUBLIC_APP_URL and
# PUBLIC_SYNC_URL default to localhost, so skip both for a test on this
# machine. PUBLIC_INFERENCE_URL has no such default: set it even for a
# local test, for example to http://localhost:8300/v1.
echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env
echo "PUBLIC_INFERENCE_URL=https://ai.example.com/v1" >> .env

# 1 behind one reverse proxy, 0 with none.
echo "TRUST_PROXY=1" >> .env

docker compose -f compose.full.yml up -d

Erstelle das erste Konto genauso wie unter oben beschrieben. Beim ersten Start werden auch die Inferenz-Gewichte heruntergeladen, etwa 2 GiB. Das Protokoll zum Modell-Laden und die Bereitschaftsprüfung funktionieren wie unter Die App plus Self-Hosting-Inferenz. Für echte Geräte statt eines Testbetriebs solltest du alle drei Adressen hinter HTTPS betreiben. Siehe HTTPS.

Ohne Docker

Die App ist ein einzelnes Node.js-Programm. Sie läuft direkt aus einem Checkout. So betreibst du sie auf Ubuntu 24.04 mit systemd, damit sie über Logouts und Neustarts hinweg aktiv bleibt.

Node.js 24 oder neuer. Ubuntu 24.04 liefert nodejs Version 18 mit, was zu alt ist. Installiere 24 aus NodeSource:

bash
curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs git
node --version      # v24.x

nvm funktioniert ebenfalls, legt Node aber in deinem Home-Ordner ab. Die systemd-Unit unten muss dann diesen Pfad verwenden (command -v node gibt ihn aus).

pnpm in der vom Repository vorgegebenen Version. corepack wird mit Node 24 mitgeliefert. Es lädt genau das pnpm herunter, das im Feld packageManager der package.json der App hinterlegt ist, aber nur innerhalb von apps/app. Außerhalb dieses Ordners greift pnpm standardmäßig auf das zurück, was corepack wählt. Führe jeden Befehl für pnpm innerhalb von apps/app aus. Bestätige beim ersten Durchlauf die Download-Aufforderung.

bash
sudo corepack enable
git clone https://github.com/LowCarbCheck/openplate.git ~/openplate-src
cd ~/openplate-src/apps/app
pnpm install --frozen-lockfile
pnpm build

Die Einstellungen. Der Server liest .env aus dem Ordner, in dem er ausgeführt wird. In Produktionsumgebungen startet er ohne APP_URL nicht:

bash
cat > .env <<'EOF'
NODE_ENV=production
PORT=3000
APP_URL=http://localhost:3000
TRUST_PROXY=0
EOF

Setze APP_URL auf die Adresse, die Personen aufrufen, und TRUST_PROXY=1, sobald ein Reverse-Proxy davor steht. Jede andere Variable von die App gehört in dieselbe Datei. HOST=127.0.0.1 sorgt dafür, dass der Server nur auf dieser Maschine lauscht, was hinter einem Proxy auf derselben Box gewünscht ist.

Eine systemd-Unit, damit die App beim Systemstart anläuft und nach dem Abmelden weiterläuft. Die Werte für $USER und $HOME unten werden beim Einfügen eingesetzt:

bash
sudo tee /etc/systemd/system/openplate.service > /dev/null <<EOF
[Unit]
Description=openplate
After=network-online.target
Wants=network-online.target

[Service]
User=$USER
WorkingDirectory=$HOME/openplate-src/apps/app
Environment=NODE_ENV=production
ExecStart=/usr/bin/node --import tsx ./server.ts
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now openplate
curl -s http://127.0.0.1:3000/healthcheck

sudo journalctl -u openplate -f zeigt das Protokoll. Ziehe für ein Upgrade den Code erneut, baue das Projekt und starte anschließend neu:

bash
cd ~/openplate-src/apps/app
git pull
pnpm install --frozen-lockfile
pnpm build
sudo systemctl restart openplate

Der Abschnitt HTTPS gilt hier unverändert: Leite den Reverse-Proxy auf Port 3000.

Erster Start

  1. Öffne die App und folge der kurzen Einführung. Keine Registrierung, kein Login: Wer die App auf einem Gerät öffnet, ist der Nutzer dieses Geräts.
  2. Gehe zu Einstellungen → KI und verbinde einen KI-Anbieter über deinen eigenen API-Schlüssel (OpenRouter, Mistral, einen eigenen OpenAI-kompatiblen Endpunkt oder Anthropic). Unter configuration.md findest du den Klick-Ablauf für OpenRouter und die Anleitung, wie du stattdessen einen instanzeigenen Endpunkt bereitstellst.
  3. Erstelle frühzeitig ein Backup: Einstellungen → Daten & Backup → Alles herunterladen (JSON). Dein Tagebuch liegt im Speicher dieses Browsers, daher ist ein Backup die einzige Kopie, die das Löschen der Websitedaten oder einen Wechsel auf ein neues Gerät übersteht.
  4. Wenn mehr als eine Person auf dieser Instanz scannt, erstelle einen kostenlosen Lebensmitteldatenbank-Schlüssel unter lowcarbcheck.org/developers, trage ihn in .env als FOOD_DB_API_KEY=... ein und führe docker compose -f <your file> up -d erneut aus. Ohne Schlüssel teilen sich alle Nutzer der Instanz ein einziges kleines anonymes Kontingent. Siehe configuration.md. Mit einem Schlüssel kannst du auch FOOD_DB_BACKFILL=true aktivieren, wodurch die von Nutzern aus einer KI-Antwort übernommenen Lebensmittel als Vorschläge an LowCarbCheck gesendet werden. Siehe configuration.md.

HTTPS

Browser beschränken etliche Funktionen auf einen sicheren Kontext. Ein sicherer Kontext ist eine Seite, die über https:// oder über localhost auf der lokalen Maschine bereitgestellt wird. openplate setzt für mehrere Kernfunktionen einen sicheren Kontext voraus:

  • Konten, Anmeldung und Sync. Registrierung, Anmeldung sowie das Öffnen von Einladungs- oder Wiederherstellungs-Links leiten Schlüssel über die Web Crypto API (crypto.subtle) ab. Browser schalten diese API auf einfachen http://-Seiten ab. Unter http://192.168.1.20:3000 schlagen diese Ansichten fehl. Auch das Teilen und die Forschungskonsole funktionieren dann nicht, da sie auf dieselbe API zurückgreifen.
  • Mit OpenRouter verbinden, die 1-Klick-Anmeldung bei OpenRouter, aus demselben Grund. Das manuelle Einfügen eines Schlüssels funktioniert immer.
  • Installation der App und Offline-Nutzung (der Service-Worker).

Über einfaches HTTP von einem anderen Gerät aus bleiben das Tagebuch, manuelle Einträge, Datensicherungen und Tellerfotos weiterhin nutzbar. Die Foto-Schaltfläche übergibt die Aufnahme über einen Dateidialog an die Kamera des Smartphones, was keine sichere Verbindung verlangt. Die Schnellstart-Anleitung funktioniert auf dem Server selbst ohne Änderungen, da localhost als sicher gilt.

Deine Geräte benötigen eine sichere Adresse. Du kannst eine auf vier Arten einrichten: ein SSH-Tunnel für einen schnellen Test, Caddy mit einem Domainnamen, Caddy in einem Heimnetzwerk ohne Domainnamen oder Tailscale.

Ein schneller Test von einem Computer aus: ein SSH-Tunnel

Dies ist ein Test für einen einzelnen Computer, kein Setup. Der Tunnel bedient nur den Computer, auf dem er läuft, und nur während der Befehl ausgeführt wird. Ein Telefon oder ein zweites Gerät kann sich darüber nicht anmelden. Richte für diese HTTPS mit Caddy weiter unten ein.

Um Konten und Abgleich vor der Zertifikatskonfiguration zu testen, leite die beiden Ports an deinen Computer weiter. Lass PUBLIC_APP_URL und PUBLIC_SYNC_URL ungesetzt, damit beide ihre Standardwerte localhost behalten. Lass E-Mail hier ebenfalls ungesetzt. Ist E-Mail konfiguriert, verweigert der Core-Server den Start, solange seine Link-Adressen localhost lauten. Ohne E-Mail startet er, und du kopierst jeden Link selbst. Führe diesen Befehl auf deinem Rechner aus, nicht auf dem Server:

bash
ssh -N -L 3000:localhost:3000 -L 3001:localhost:3001 you@192.168.1.20

Öffne http://localhost:3000 in deinem Browser, während der Befehl läuft. Dies gilt als sichere Seite, die Anmeldung funktioniert also. Ein Einladungslink vom Server (http://localhost:3000/join#...) öffnet sich dort ebenfalls. Füge -L 8300:localhost:8300 für den Inferenz-Container hinzu.

Ein Domainname: Caddy

Caddy ruft ein Let's-Encrypt-Zertifikat automatisch ab und erneuert es. Es erfordert einen Domainnamen, der auf deinen Server verweist. Außerdem müssen die Ports 80 und 443 für die Zertifikatsprüfung aus dem Internet erreichbar sein.

# Caddyfile
openplate.example.com {
    reverse_proxy localhost:3000
}

Setze als Nächstes APP_URL=https://openplate.example.com in .env. Setze TRUST_PROXY=1, was der Produktionsstandard ist. Erstelle den Dienst app mit docker compose -f compose.yml up -d neu. Ein einfaches docker compose restart liest .env nicht neu ein. Es startet nur den vorhandenen Container neu, sodass neue Werte nie geladen werden.

Gib dem Core-Server für den Abgleich einen eigenen Domänennamen. Setze beide öffentlichen URLs anstelle von APP_URL:

# Caddyfile
openplate.example.com {
    reverse_proxy localhost:3000
}
sync.example.com {
    reverse_proxy localhost:3001
}
bash
PUBLIC_APP_URL=https://openplate.example.com
PUBLIC_SYNC_URL=https://sync.example.com
TRUST_PROXY=1

Ändere die Zeile ports: in '127.0.0.1:3000:3000', und verwende '127.0.0.1:3001:3000' für den Sync. Wende die Änderung mit docker compose -f <your file> up -d an. Ein Reverse Proxy hebt die Veröffentlichung von Container-Ports nicht auf. Wenn du es bei '3000:3000' belässt, liefert die App neben der HTTPS-Adresse weiterhin einfaches HTTP auf Port 3000 über dein lokales Netzwerk aus.

Podman erstellt den Dienst mit podman compose -f compose.yml up -d auf dieselbe Weise neu. Beachte ein Rootless-Detail, bevor du den Reverse Proxy weglässt: Ein Rootless-Podman-Container kann ohne zusätzliche Konfiguration keinen Host-Port unter 1024 binden. Die direkte Veröffentlichung auf Port 80 oder 443 erfordert zuerst sudo sysctl net.ipv4.ip_unprivileged_port_start=80. Siehe podman.md.

Kein Domainname, nur Heimnetzwerk: Caddy mit lokalem Zertifikat

Ohne Domainnamen kann Caddy dennoch HTTPS in deinem Heimnetzwerk bereitstellen. Es erstellt eine eigene Zertifizierungsstelle und signiert damit ein Zertifikat für die Adresse des Servers. Jedes Telefon und jeder Computer, der openplate öffnet, muss dieser Stelle einmalig vertrauen. Danach erhalten die Telefone der Familie eine sichere Seite. Anmeldung, Sync und die Installation der App funktionieren über https://.

Gib dem Server eine feste Adresse. Reserviere im Router die aktuelle Adresse des Servers, zum Beispiel 192.168.1.20, damit sie sich nie ändert. Das Zertifikat und beide öffentlichen Adressen verweisen darauf.

Installiere Caddy und richte es auf die Adresse aus. Unter Ubuntu installiert sudo apt install caddy Caddy als Dienst. Ersetze /etc/caddy/Caddyfile durch Folgendes und setze dabei die Adresse deines Servers ein. tls internal weist Caddy an, das Zertifikat selbst zu signieren:

# /etc/caddy/Caddyfile
https://192.168.1.20 {
    tls internal
    reverse_proxy localhost:3000
}
https://192.168.1.20:8443 {
    tls internal
    reverse_proxy localhost:3001
}
bash
sudo systemctl reload caddy

Setze dieselben Adressen in .env, erstelle die Container dann neu mit docker compose -f <your file> up -d:

bash
PUBLIC_APP_URL=https://192.168.1.20
PUBLIC_SYNC_URL=https://192.168.1.20:8443
TRUST_PROXY=1

Für die App allein setze stattdessen APP_URL=https://192.168.1.20. Lass den zweiten Block aus dem Caddyfile weg. Ändere wie im Rezept oben die ports:-Zeilen zu '127.0.0.1:3000:3000' und '127.0.0.1:3001:3000', damit nur Caddy die Container erreicht.

Kopiere Caddys Root-Zertifikat vom Server herunter. Caddys Ubuntu-Paket speichert es unter /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt. Das Zertifikat ist öffentlich. Sein privater Schlüssel liegt im selben Ordner und darf den Server niemals verlassen, kopiere also nur root.crt:

bash
sudo cp /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt ~/openplate-root.crt
sudo chown "$USER" ~/openplate-root.crt

Hole es auf deinen Computer mit scp you@192.168.1.20:openplate-root.crt .. Schicke es dann an jedes Smartphone, zum Beispiel als E-Mail-Anhang an dich selbst oder per AirDrop.

Vertraue ihm einmalig auf jedem Gerät. Das ist der Schritt, der dafür sorgt, dass die Smartphones der Familie über https:// funktionieren.

  • iPhone und iPad: Öffne die Datei und erlaube den Download. Tippe in Einstellungen oben auf Profil geladen oder suche es unter Allgemein > VPN und Geräteverwaltung, und installiere es. Aktiviere anschließend das volle Vertrauen dafür unter Einstellungen > Allgemein > Info > Zertifikatsvertrauenseinstellungen. Ohne diesen letzten Schritt verweigert der Browser weiterhin die Seite.
  • Android: Speichere die Datei auf dem Smartphone. Öffne Einstellungen > Sicherheit > Verschlüsselung & Anmeldedaten > Zertifikat installieren > CA-Zertifikat, bestätige die Warnung und wähle die Datei aus. Auf neueren Geräten beginnt der Pfad bei Sicherheit und Datenschutz > Weitere Sicherheitseinstellungen, und die Bezeichnungen unterscheiden sich je nach Hersteller etwas. Wenn das Smartphone keine Bildschirmsperre hat, fordert Android dich auf, eine einzurichten.
  • Ein Computer: Füge es zu den Zertifikaten des Systems hinzu. Manche Browser führen eine eigene Liste und brauchen es auch dort.

Öffne https://192.168.1.20 auf einem Smartphone. Die Seite sollte ohne Warnung laden, und die Anmeldung sollte funktionieren. Die Adresse funktioniert nur in deinem Heimnetzwerk. Sichere Caddys Datenordner in deinen Backups. Eine Neuinstallation von Caddy erstellt eine neue Zertifizierungsstelle, und jedes Gerät muss der neuen dann erneut vertrauen.

Jeder andere Reverse-Proxy

nginx, Traefik oder ein anderer Proxy kann Caddy ersetzen. Wir haben keinen davon getestet, daher ist dies eine Checkliste, keine Anleitung. Der Proxy muss all dies leisten:

  • Zwei https://-Adressen. Die App und der Core-Server erhalten jeweils eine eigene.
  • Dieselben Adressen in .env. Setze PUBLIC_APP_URL und PUBLIC_SYNC_URL genau auf diese Adressen. Für die App allein ist das APP_URL.
  • TRUST_PROXY=1, oder die Anzahl der Proxys in der Kette.
  • Host und X-Forwarded-Proto erreichen die App. Reiche den Host-Header des Browsers unverändert durch, oder setze X-Forwarded-Host darauf. Setze X-Forwarded-Proto auf https. Die CSRF-Prüfung der App baut daraus die eigene Adresse der Seite und vergleicht sie mit dem Origin des Browsers. Stimmen sie nicht, schlagen Formularübertragungen fehl.
  • X-Forwarded-For erreicht beide Dienste. Deren Limits pro Adresse lesen sie aus.
  • Die Container-Ports bleiben auf 127.0.0.1. Behalte '127.0.0.1:3000:3000' und '127.0.0.1:3001:3000' bei, damit am Proxy vorbei nichts zu ihnen gelangt.

Kein Domainname: Tailscale Serve

Tailscale gibt jeder Maschine in deinem Tailnet eine HTTPS-Adresse unter ts.net. Du benötigst keinen Domänennamen, keine offenen Ports und keine manuelle Zertifikatsverwaltung. Tailscale Serve schaltet diese Adresse vor einen Port auf dieser Maschine. openplate mit Abgleich benötigt zwei Adressen. Du stellst zwei Ports unter demselben Maschinennamen bereit: die App auf 443 und den Core-Server auf 8443.

Bevor du beginnst:

  • MagicDNS und HTTPS-Zertifikate aktivieren für dein Tailnet auf der DNS-Seite der Tailscale-Admin-Konsole. Tailscales HTTPS-Leitfaden enthält die Schritte. Der Rechnername erscheint in einem öffentlichen Zertifikats-Log, wähle also einen Namen, der nichts Privates verrät.
  • Jedes Familienmitglied nutzt Tailscale auf jedem Gerät, das openplate öffnet. Jede Person muss in deinem Tailnet sein oder diesen Rechner für sich freigegeben haben.
  • Die Container-Ports auf 127.0.0.1 belassen: '127.0.0.1:3000:3000' für die App und '127.0.0.1:3001:3000' für Sync. Tailscale Serve erreicht sie auf diesem Rechner. Nichts anderes benötigt Zugriff.

Gib dann beide Ports frei. --bg lässt sie im Hintergrund weiterlaufen, und Tailscale gibt sie nach einem Neustart wieder frei:

bash
tailscale serve --bg --https=443 3000
tailscale serve --bg --https=8443 3001
tailscale serve status

Tailscale stellt das Zertifikat aus und erneuert es. Setze beide Adressen in .env mit deinen eigenen Rechner- und Tailnet-Namen:

bash
PUBLIC_APP_URL=https://<machine-name>.<tailnet>.ts.net
PUBLIC_SYNC_URL=https://<machine-name>.<tailnet>.ts.net:8443
TRUST_PROXY=1

Tailscale Serve ist ein Reverse-Proxy, TRUST_PROXY bleibt also bei 1. Erstelle den Stack mit docker compose -f <your file> up -d neu. Wenn du die App alleine betreibst, gib nur Port 3000 frei und setze APP_URL auf die erste Adresse.

Diesen Pfad haben wir nicht durchgängig getestet. Die tailscale serve-Referenz dokumentiert die obigen Flags, und Tailscales Dokumentation beschreibt tailscale serve. Die obige Caddy-Anleitung ist diejenige, die wir überprüft haben.

Headscale, ein selbst gehosteter Tailscale-Control-Server, stellt keine HTTPS-Zertifikate aus. Ein Headscale-Tailnet benötigt stattdessen die Caddy-Anleitung mit einer Domain.

Backups

Auf dem App-Server gibt es nichts zu sichern. Er enthält keine Datenbank und schreibt keinen Zustand: Ein zerstörter App-Container verliert nichts.

Der gerätespezifische JSON-Export ist das entscheidende Backup: Einstellungen → Daten & Backup → Alles herunterladen (JSON). Diese Datei ist die Kopie, die einen gelöschten Browser oder ein defektes Telefon übersteht. Die App blendet ein Erinnerungsbanner ein, wenn ein Gerät Daten enthält, die du noch nie oder schon länger nicht exportiert hast.

Behandle den Export genauso vertraulich wie das Tagebuch. Sie enthält jeden Eintrag im Klartext, und sie enthält außerdem den privaten Schlüssel der Freigabe-Identität dieses Geräts sowie die Wurzel, aus der das Forschungs-Pseudonym abgeleitet wird. Wer die Datei besitzt, kann ein Tagebuch öffnen, das für diese Person freigegeben wurde, und die Studienbeiträge zu dieser Person zurückverfolgen. Zwei Dinge bleiben außen vor: der Schlüssel des KI-Anbieters und die Tellerfotos, die das Gerät nie verlassen.

Wenn du auch den Core-Server betreibst, lohnt sich für sein Postgres ein regelmäßiger Dump, zusammen mit dem SERVER_SECRET, der ohne die Datenbank nutzlos ist und umgekehrt:

Container-Tool
docker compose -f compose.core.yml exec postgres \
  pg_dump -U openplate openplate_sync > sync-backup.sql

Aktualisierung

Container-Tool
docker compose -f compose.yml pull
docker compose -f compose.yml up -d

Verwende dieselbe -f-Datei, mit der du bereitgestellt hast. Wenn du eine Topologie aus docker/topologies/ gestartet hast, gib stattdessen diese Datei an, zum Beispiel docker compose -f compose.core.yml pull. Ein bloßes docker compose pull neben compose.core.yml schlägt mit no configuration file provided: not found fehl.

Die Compose-Dateien verwenden das Tag latest, welches die neueste Version ist, also bringt dich pull dorthin. Um den Zeitpunkt deines Upgrades selbst zu bestimmen, pinne eine Version in der Zeile image:, zum Beispiel ghcr.io/lowcarbcheck/openplate:0.54.0. Ändere die Nummer, wenn du die nächste Version möchtest. Der Core-Server und der Inferenzdienst haben eigene Versionsnummern, pinne also jedes Image auf seine eigene. Das Tag main folgt jeder Änderung auf dem Haupt-Branch. Es ist zum Testen gedacht, nicht für einen Server, den deine Familie nutzt.

Es gibt nichts zu migrieren: Der App-Container speichert keinen Zustand, daher ersetzt ein neues Image einfach das alte. Wenn du den gesamten Stack betreibst, führt der Core-Server seine eigenen Migrationen beim Start aus.

Aktualisierung von einem Image vor 0.1.x (einmalig)

Ältere Images betrieben ein eigenes Kontosystem und ein eigenes Postgres. Beides ist weg. Das Upgrade löscht die Tabelle users und alles, was daran hängt: Konten, Sitzungen, Bestätigungs- und Reset-Tokens. Nichts von dem, was du protokolliert hast, ist betroffen: Die Daten des Trackers waren bereits auf das Gerät gewandert. Die Änderung ist unumkehrbar, also:

  1. Erstelle zuerst eine Sicherung. Erstelle ein pg_dump der alten App-Datenbank, wenn die Kontenzeilen wiederherstellbar bleiben sollen, und lass jede Person auf jedem Gerät einen JSON-Export über Profil → Deine Daten erstellen. Dieser Export ist die Kopie, die ihr Tagebuch enthält.
  2. Führe das Upgrade durch. Aktualisiere zuerst die Zeile image: auf ghcr.io/lowcarbcheck/openplate:latest, das neueste Release: Vor-0.1.x-Images wurden unter ghcr.io/sprqvntrs/openplate veröffentlicht, und ein Pull ohne diese Änderung holt bloß das alte Image erneut. Die Migration läuft dann beim Starten des Containers. Danach gibt es keine Anmeldeseite mehr: Jedes Gerät, auf dem bereits Daten liegen, behält sie und hört einfach auf zu fragen, wer du bist.
  3. Bereinige deine .env. Die Variablen für Session-Secret, Encryption-Key, Signup-Gate und Seeded-Superadmin existieren nicht mehr, ebenso wenig wie DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME und die Variablen zur Pool-Optimierung. Nichts liest sie mehr aus. Sie gesetzt zu lassen ist harmlos, aber sie sind Ballast.
  4. Altes Volume löschen, sobald du zufrieden bist. Ältere Releases legten keinen Compose-Projektnamen fest, daher stammte das Präfix aus dem Verzeichnis, in dem du gearbeitet hast (prüfe zuerst den tatsächlichen Namen): docker volume ls | grep pg-data, dann docker compose down && docker volume rm <that name>.

Wenn zwei Konten im selben Browserprofil angemeldet waren, beachte, dass der Gerätespeicher immer gerätebezogen war: Ihre Daten teilten sich bereits einen Speicher und das bleibt so. Getrennte Browserprofile bleiben der Weg, um die Tagebücher zweier Personen auf einem Gerät auseinanderzuhalten.

Diese Seite auf GitHub bearbeiten