Zelfhosting
Node en een beschrijfbare map zijn alles wat nodig is. De onderstaande uitspraken zijn gelezen uit de bestanden die ernaast staan genoemd; alles wat niet kon worden bevestigd, staat hier niet.
Toegangspunten
- package.json vereist Node >= 22.5. De SQLite-opslag is Node's eigen node:sqlite-module.
- Er is helemaal geen afhankelijkhedenlijst: apps, pakketten, scripts en tests draaien op Node's standaardbibliotheek, en de Dockerfile installeert niets.
- Er bestaan drie processen; elk opent de database en voert de migratie bij het opstarten uit.
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
Datastore
DATABASE_URL=sqlite:./data/llmeu.db heeft geen server nodig, en de map en het bestand worden bij de eerste opening aangemaakt. De handle draait in WAL-modus met foreign keys aan en een busy timeout van 5 s.
packages/shared/src/db/schema.sql is het schema. Het is alleen aanmakend en idempotent, dus opnieuw uitvoeren is veilig; een kolom die aan een bestaande tabel wordt toegevoegd, moet ook in de additieve stap in packages/shared/src/db/index.mjs worden gedeclareerd.
Een postgres://-URL neemt een aparte adaptertak. Die tak vereist het pg-pakket, dat geen gedeclareerde dependency is, en de header van schema.sql zegt dat het Postgres-pad als ongeverifieerd moet worden behandeld totdat er een echte driver en een geteste migratie bestaan. SQLite is het pad dat deze repository draait.
Operator-CLI
node scripts/db.mjs leest DATABASE_URL en heeft geen services nodig. Opdrachten:
| Opdracht | Wat het doet |
|---|---|
| node scripts/db.mjs migrate | Past schema.sql toe en rapporteert het aantal statements. |
| node scripts/db.mjs seed | Voegt de catalogus en de demo-organisatie in of ververst deze, en schrijft de demo-sleutel naar data/demo-key.txt met modus 0600. |
| node scripts/db.mjs reset | Alleen voor SQLite: verwijdert het databasebestand en de bijbehorende -wal- en -shm-bestanden, en migreert en seedt daarna. Op Postgres weigert het. |
| node scripts/db.mjs status | Drukt de engine, het aantal tabellen en het rijenaantal van elke niet-lege tabel af. |
| node scripts/db.mjs demo-key | Geeft een nieuwe sleutel uit voor de demo-organisatie en drukt deze één keer af. |
| node scripts/db.mjs passwd <email> | Stelt het wachtwoord van een gebruiker in vanaf de server en verwijdert elke sessie van die gebruiker. Dit is het herstelpad wanneer geen mailtransport is geconfigureerd. Zonder --password genereert het er een en drukt deze één keer af. |
Omgeving
Gegroepeerd zoals .env.example ze groepeert.
| Groep | Variabelen | Opmerkingen |
|---|---|---|
| URL's en poorten | APP_URL, API_URL, WEB_PORT, API_PORT, API_EMBED, PUBLIC_API_URL | De API-poort staat standaard op 8088, niet 8080. API_EMBED=false is voor een deployment die de API als eigen proces draait; PUBLIC_API_URL is de basis die in documentatiefragmenten wordt getoond. |
| Datastore | DATABASE_URL, REDIS_URL | DATABASE_URL selecteert de opslag; REDIS_URL is gereserveerd voor de takenwachtrij en wordt niet gelezen door het v1-codepad. |
| Inferentie | INFERENCE_MOCK, GPU_ENABLED, VLLM_BASE_URL, VLLM_REGION, VLLM_MODEL_VERSION, VLLM_BASE_URL_<REGION>, DEFAULT_REGION, PARTNER_* | Zie de inferentiesectie hieronder; PARTNER_* dekt de partner-basis-URL, regio, soevereiniteitsklasse en API-sleutel. |
| Facturatie | FX_EUR_USD, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Het grootboek wordt bijgehouden in USD-micro's en EUR wordt voor weergave afgeleid tegen FX_EUR_USD. |
| MAIL_FROM, MAIL_SMTP_HOST, MAIL_SMTP_PORT, MAIL_SMTP_SECURE, MAIL_SMTP_USER, MAIL_SMTP_PASS, MAIL_TIMEOUT_MS, MAIL_API_URL, MAIL_API_KEY | Stel MAIL_SMTP_HOST in voor SMTP, of MAIL_API_URL met MAIL_FROM voor een HTTP-relay, of geen van beide. | |
| Beveiliging | NODE_ENV, LLMEU_IP_SALT, LLMEU_DEMO_PASSWORD, LLMEU_KEY_ENV | NODE_ENV=production markeert sessiecookies als Secure. LLMEU_IP_SALT voorziet het gehashte client-IP van een salt en moet per deployment worden gewijzigd. LLMEU_KEY_ENV geeft gegenereerde sleutels het voorvoegsel llmeu_live_ of llmeu_test_. |
| Overig | LOG_LEVEL | LOG_LEVEL bepaalt het minimale logniveau. |
Docker
Compose en de image zijn optioneel: de eigen suite van de repository vereist geen Docker, en het primaire pad is een lokaal proces zonder services.
- Het standaardprofiel draait web: poorten 3000 en 8088, SQLite op het llmeu-data-volume op /app/data, de embedded API, en een healthcheck op /healthz.
- Dezelfde image draait de worker met node apps/worker/src/run.mjs, wacht tot web healthy is en deelt het llmeu-data-volume.
- Twee extra profielen staan standaard uit: postgres (postgres:16-alpine) en redis (redis:7-alpine, gereserveerd en niet gelezen door het v1-codepad).
- Het gpu-profiel draait vllm/vllm-openai met een NVIDIA-apparaatreservering en mount gewichten read-only vanaf de host; het is niet standaard ingeschakeld, omdat het claimen van EU-inference zonder GPU onwaar zou zijn.
- De Dockerfile is FROM node:24-alpine met tini, zonder buildstap en zonder dependency-installatie, draait als de node-gebruiker, schrijft alleen onder /app/data, exposeert 3000 en 8088, en start apps/web/server/main.mjs.
Back-ups
node scripts/backup.mjs zonder flags schrijft ./backups/llmeu-<stamp>.db. SQLite wordt gekopieerd met VACUUM INTO, nooit cp, omdat een live WAL-database die met cp wordt gekopieerd een verscheurde of verouderde staat kan opleveren.
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
Elke SQLite-back-up wordt geverifieerd voordat het script succes meldt: deze wordt in een tijdelijke kopie hersteld, PRAGMA integrity_check moet ok teruggeven, het schema wordt opnieuw toegepast, en rijenaantallen voor modellen, organisaties en gebruikers worden afgedrukt.
Met een postgres://-URL gebruikt het script pg_dump --format=custom en heeft het pg_dump in PATH nodig; een dump kan niet als database worden geopend, dus de rijenaantallen ervan vereisen een scratch-restore.
Transactionele e-mail — wachtwoordherstel, adresverificatie, uitnodigingen — gebruikt MAIL_SMTP_* of MAIL_API_URL met MAIL_FROM. Het transport wordt één keer bij het opstarten gekozen en gemeld op elke pagina die e-mail nodig heeft.
Een deployment met geen van beide is een ondersteunde toestand: registratie, inloggen, wachtwoordwijziging, teams en uitnodigingen werken, maar wachtwoordherstel en verificatie-e-mail kunnen daar niet worden verwerkt. De pagina's vermelden dit, en node scripts/db.mjs passwd <email> is de herstelroute.
Inferentiebackends
Een endpointrij noemt een runtime en een regio, en het backendregister kiest de adapter op basis van regio, dus een endpoint kan alleen worden beantwoord door een machine in de regio die het noemt.
- INFERENCE_MOCK=true behoudt de deterministische mockbackend, die geen GPU en geen gewichten nodig heeft.
- GPU_ENABLED=false houdt beheerde backends buiten; met GPU_ENABLED=true geldt VLLM_BASE_URL voor de enkele regio die door VLLM_REGION wordt genoemd.
- Een deployment met meerdere regio's stelt één variabele per regio in, genoemd naar de regioslug (bijvoorbeeld VLLM_BASE_URL_DE_FRA). Een endpoint in een regio zonder geconfigureerde backend wordt geweigerd, in plaats van te worden bediend vanaf een machine in een andere regio.
- Partnerbackends komen van PARTNER_BASE_URL_<REGION> en nemen PARTNER_SOVEREIGNTY en PARTNER_API_KEY; de server staat partnergebruik toe wanneer PARTNER_BASE_URL is ingesteld.
Zonder GPU antwoordt de mockbackend. De regio is dev-mock, en de uitvoer ervan zegt dat het de mockbackend is, dat er geen echte gewichten zijn uitgevoerd, en dat first-party-inferentie VLLM_BASE_URL en de gpu_enabled-vlag nodig heeft. Een mocksucces dat echt leek, zou het enige zijn dat dit product nooit mag uitbrengen.