Selbst hosten

Node und ein beschreibbares Verzeichnis sind die ganze Voraussetzung. Die folgenden Aussagen stammen aus den daneben genannten Dateien; was nicht bestätigt werden konnte, steht nicht hier.

Einstiegspunkte

  • package.json verlangt Node >= 22.5. Der SQLite-Speicher ist Nodes eigenes node:sqlite-Modul.
  • Es gibt überhaupt keine Abhängigkeitsliste: Apps, Pakete, Skripte und Tests laufen mit Nodes Standardbibliothek, und das Dockerfile installiert nichts.
  • Drei Prozesse existieren; jeder öffnet die Datenbank und führt beim Start die Migration aus.
node apps/web/server/main.mjs                   # hub + console + embedded API
API_EMBED=false node apps/api/src/main.mjs    # API only, no pages
node apps/worker/src/run.mjs                  # probes, latency, rollup, housekeeping

Datenspeicher

DATABASE_URL=sqlite:./data/llmeu.db braucht keinen Server; Verzeichnis und Datei werden beim ersten Öffnen angelegt. Das Handle läuft im WAL-Modus mit aktivierten Fremdschlüsseln und 5 s busy_timeout.

packages/shared/src/db/schema.sql ist das Schema. Es ist ausschließlich anlegend und idempotent, erneutes Ausführen ist also sicher; eine Spalte, die einer bestehenden Tabelle hinzugefügt wird, muss zusätzlich im additiven Schritt in packages/shared/src/db/index.mjs stehen.

Eine postgres://-URL nimmt einen separaten Adapterzweig. Dieser Zweig benötigt das Paket pg, das keine deklarierte Abhängigkeit ist, und der Kopf von schema.sql sagt, der Postgres-Pfad sei als ungeprüft zu behandeln, bis ein echter Treiber und eine getestete Migration existieren. SQLite ist der Pfad, den dieses Repository ausführt.

Betreiber-CLI

node scripts/db.mjs liest DATABASE_URL und braucht keine Dienste. Befehle:

Befehl Wirkung
node scripts/db.mjs migrateWendet schema.sql an und meldet die Anzahl der Anweisungen.
node scripts/db.mjs seedFügt Katalog und Demo-Organisation ein oder aktualisiert sie und schreibt den Demo-Schlüssel mit Modus 0600 nach data/demo-key.txt.
node scripts/db.mjs resetNur für SQLite: entfernt die Datenbankdatei samt -wal und -shm, migriert und befüllt dann. Bei Postgres verweigert es.
node scripts/db.mjs statusGibt die Engine, die Tabellenzahl und die Zeilenzahl jeder nicht leeren Tabelle aus.
node scripts/db.mjs demo-keyStellt einen neuen Schlüssel für die Demo-Organisation aus und gibt ihn einmal aus.
node scripts/db.mjs passwd <email>Setzt das Passwort eines Nutzers vom Server aus und löscht jede Sitzung dieses Nutzers. Das ist der Wiederherstellungsweg, wenn kein Mail-Transport konfiguriert ist. Ohne --password wird eines erzeugt und einmal ausgegeben.

Umgebung

Gruppiert wie in .env.example.

Gruppe Variablen Hinweise
URLs und PortsAPP_URL, API_URL, WEB_PORT, API_PORT, API_EMBED, PUBLIC_API_URLDer API-Port ist standardmäßig 8088, nicht 8080. API_EMBED=false gilt für eine Bereitstellung, die die API als eigenen Prozess betreibt; PUBLIC_API_URL ist die in Doku-Beispielen gezeigte Basis.
DatenspeicherDATABASE_URL, REDIS_URLDATABASE_URL wählt den Speicher; REDIS_URL ist für die Job-Queue reserviert und wird vom v1-Codepfad nicht gelesen.
InferenzINFERENCE_MOCK, GPU_ENABLED, VLLM_BASE_URL, VLLM_REGION, VLLM_MODEL_VERSION, VLLM_BASE_URL_<REGION>, DEFAULT_REGION, PARTNER_*Siehe den Inferenzabschnitt unten; PARTNER_* umfasst Partner-Basis-URL, Region, Souveränitätsklasse und API-Schlüssel.
AbrechnungFX_EUR_USD, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETDas Ledger wird in USD-Mikros geführt; EUR wird zur Anzeige mit FX_EUR_USD abgeleitet.
MailMAIL_FROM, MAIL_SMTP_HOST, MAIL_SMTP_PORT, MAIL_SMTP_SECURE, MAIL_SMTP_USER, MAIL_SMTP_PASS, MAIL_TIMEOUT_MS, MAIL_API_URL, MAIL_API_KEYMAIL_SMTP_HOST für SMTP setzen, oder MAIL_API_URL mit MAIL_FROM für ein HTTP-Relay, oder nichts davon.
SicherheitNODE_ENV, LLMEU_IP_SALT, LLMEU_DEMO_PASSWORD, LLMEU_KEY_ENVNODE_ENV=production markiert Sitzungs-Cookies als Secure. LLMEU_IP_SALT salzt die gehashte Client-IP und sollte je Bereitstellung geändert werden. LLMEU_KEY_ENV stellt generierten Schlüsseln llmeu_live_ oder llmeu_test_ voran.
SonstigesLOG_LEVELLOG_LEVEL setzt die minimale Log-Stufe.

Docker

Compose und das Image sind optional: Die Testsuite des Repositories braucht kein Docker, und der primäre Weg ist ein lokaler Prozess ohne Dienste.

  • Das Standardprofil startet web: Ports 3000 und 8088, SQLite auf dem Volume llmeu-data unter /app/data, die eingebettete API und einen Healthcheck auf /healthz.
  • Dasselbe Image startet den Worker mit node apps/worker/src/run.mjs, wartet auf die Gesundheit von web und nutzt dasselbe Volume llmeu-data.
  • Zwei zusätzliche Profile sind standardmäßig aus: postgres (postgres:16-alpine) und redis (redis:7-alpine, reserviert und vom v1-Codepfad nicht gelesen).
  • Das Profil gpu startet vllm/vllm-openai mit NVIDIA-Gerätereservierung und bindet Gewichte schreibgeschützt vom Host ein; es ist nicht standardmäßig aktiv, denn EU-Inferenz ohne GPU zu behaupten wäre falsch.
  • Das Dockerfile ist FROM node:24-alpine mit tini, ohne Build-Schritt und ohne Abhängigkeitsinstallation, läuft als Benutzer node, schreibt nur unter /app/data, exponiert 3000 und 8088 und startet apps/web/server/main.mjs.

Backups

node scripts/backup.mjs ohne Flags schreibt ./backups/llmeu-<stamp>.db. SQLite wird mit VACUUM INTO kopiert, nie mit cp, denn eine laufende WAL-Datenbank kann mit cp einen zerrissenen oder veralteten Stand liefern.

node scripts/backup.mjs                                  # ./backups/llmeu-<stamp>.db
node scripts/backup.mjs --out /srv/backups/              # explicit destination
node scripts/backup.mjs --verify ./backups/llmeu-<stamp>.db
node scripts/backup.mjs --restore ./backups/llmeu-<stamp>.db --yes
node scripts/backup.mjs --list

Jedes SQLite-Backup wird geprüft, bevor das Skript Erfolg meldet: Es wird in eine temporäre Kopie wiederhergestellt, PRAGMA integrity_check muss ok liefern, das Schema wird erneut angewendet und die Zeilenzahlen für models, organizations und users werden ausgegeben.

Bei einer postgres://-URL nutzt das Skript pg_dump --format=custom und braucht pg_dump im PATH; ein Dump lässt sich nicht als Datenbank öffnen, seine Zeilenzahlen brauchen also eine Testwiederherstellung.

Mail

Transaktionsmail — Passwort-Zurücksetzen, Adressbestätigung, Einladungen — nutzt MAIL_SMTP_* oder MAIL_API_URL mit MAIL_FROM. Der Transport wird einmal beim Start gewählt und auf jeder Seite gemeldet, die Mail braucht.

Auch ohne beides ist ein unterstützter Zustand: Registrierung, Anmeldung, Passwortänderung, Teams und Einladungen funktionieren, aber Mail zum Zurücksetzen und zur Bestätigung kann die Bereitstellung nicht bearbeiten. Die Seiten sagen das, und node scripts/db.mjs passwd <email> ist der Wiederherstellungsweg.

Inferenz-Backends

Eine Endpunktzeile nennt Runtime und Region; die Backend-Registry wählt den Adapter nach Region, ein Endpunkt kann also nur von einer Maschine in der genannten Region beantwortet werden.

  • INFERENCE_MOCK=true behält das deterministische Mock-Backend, das keine GPU und keine Gewichte braucht.
  • GPU_ENABLED=false hält verwaltete Backends heraus; mit GPU_ENABLED=true gilt VLLM_BASE_URL für die eine Region, die VLLM_REGION nennt.
  • Eine Bereitstellung mit mehreren Regionen setzt eine Variable je Region, benannt nach dem Regions-Slug (zum Beispiel VLLM_BASE_URL_DE_FRA). Ein Endpunkt in einer Region ohne konfiguriertes Backend wird abgelehnt statt von einer Maschine in einer anderen Region bedient.
  • Partner-Backends stammen aus PARTNER_BASE_URL_<REGION> und übernehmen PARTNER_SOVEREIGNTY und PARTNER_API_KEY; der Server erlaubt Partner-Nutzung, wenn PARTNER_BASE_URL gesetzt ist.

Ohne GPU antwortet das Mock-Backend. Seine Region ist dev-mock, und seine Ausgabe sagt, dass es das Mock-Backend ist, dass keine echten Gewichte liefen und dass First-Party-Inferenz VLLM_BASE_URL und das Flag gpu_enabled braucht. Ein Mock-Erfolg, der echt aussieht, wäre das eine, was dieses Produkt nie ausliefern darf.