Auto-găzduire
Node și un director în care se poate scrie sunt singura cerință. Afirmațiile de mai jos au fost citite din fișierele numite alături; tot ce nu a putut fi confirmat nu se află aici.
Puncte de intrare
- package.json necesită Node >= 22.5. Depozitul SQLite este modulul node:sqlite al Node.
- Nu există deloc o listă de dependențe: aplicațiile, pachetele, scripturile și testele rulează pe biblioteca standard a Node, iar Dockerfile nu instalează nimic.
- Există trei procese; fiecare deschide baza de date și rulează migrarea la pornire.
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
Depozit de date
DATABASE_URL=sqlite:./data/llmeu.db nu necesită server, iar directorul și fișierul sunt create la prima deschidere. Handle-ul rulează în modul WAL cu cheile străine activate și un timeout de ocupare de 5 s.
packages/shared/src/db/schema.sql este schema. Este doar de creare și idempotentă, astfel încât rerularea este sigură; o coloană adăugată la un tabel existent trebuie de asemenea declarată în pasul aditiv din packages/shared/src/db/index.mjs.
Un URL postgres:// urmează o ramură separată de adaptor. Acea ramură necesită pachetul pg, care nu este o dependență declarată, iar antetul schema.sql spune să tratați calea Postgres ca neverificată până când există un driver real și o migrare testată. SQLite este calea pe care rulează acest depozit.
CLI pentru operator
node scripts/db.mjs citește DATABASE_URL și nu necesită servicii. Comenzi:
| Comandă | Ce face |
|---|---|
| node scripts/db.mjs migrate | Aplică schema.sql și raportează numărul de instrucțiuni. |
| node scripts/db.mjs seed | Inserează sau reîmprospătează catalogul și organizația demo și scrie cheia demo în data/demo-key.txt cu modul 0600. |
| node scripts/db.mjs reset | Doar pentru SQLite: elimină fișierul bazei de date și însoțitorii săi -wal și -shm, apoi migrează și populează. Pe Postgres refuză. |
| node scripts/db.mjs status | Afișează motorul, numărul de tabele și numărul de rânduri al fiecărui tabel nevid. |
| node scripts/db.mjs demo-key | Emite o cheie nouă pentru organizația demo și o afișează o dată. |
| node scripts/db.mjs passwd <email> | Setează parola unui utilizator de pe server și șterge fiecare sesiune a acelui utilizator. Aceasta este calea de recuperare când nu este configurat un transport de email. Fără --password, generează una și o afișează o dată. |
Mediu
Grupate așa cum le grupează .env.example.
| Grup | Variabile | Note |
|---|---|---|
| URL-uri și porturi | APP_URL, API_URL, WEB_PORT, API_PORT, API_EMBED, PUBLIC_API_URL | Portul API implicit este 8088, nu 8080. API_EMBED=false este pentru o implementare care rulează API-ul ca proces propriu; PUBLIC_API_URL este baza afișată în fragmentele de documentație. |
| Depozit de date | DATABASE_URL, REDIS_URL | DATABASE_URL selectează depozitul; REDIS_URL este rezervat pentru coada de lucrări și nu este citit de calea de cod v1. |
| Inferență | INFERENCE_MOCK, GPU_ENABLED, VLLM_BASE_URL, VLLM_REGION, VLLM_MODEL_VERSION, VLLM_BASE_URL_<REGION>, DEFAULT_REGION, PARTNER_* | Vezi secțiunea de inferență de mai jos; PARTNER_* acoperă URL-ul de bază al partenerului, regiunea, clasa de suveranitate și cheia API. |
| Facturare | FX_EUR_USD, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Registrul este ținut în micro-USD, iar EUR este derivat pentru afișare la 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 | Setează MAIL_SMTP_HOST pentru SMTP, sau MAIL_API_URL cu MAIL_FROM pentru un releu HTTP, sau niciuna. | |
| Securitate | NODE_ENV, LLMEU_IP_SALT, LLMEU_DEMO_PASSWORD, LLMEU_KEY_ENV | NODE_ENV=production marchează cookie-urile de sesiune ca Secure. LLMEU_IP_SALT adaugă sare la IP-ul clientului hashuit și ar trebui schimbat pentru fiecare implementare. LLMEU_KEY_ENV prefixează cheile generate cu llmeu_live_ sau llmeu_test_. |
| Diverse | LOG_LEVEL | LOG_LEVEL stabilește nivelul minim de logare. |
Docker
Compose și imaginea sunt opționale: suita proprie a depozitului nu necesită Docker, iar calea principală este un proces local fără servicii.
- Profilul implicit rulează web: porturile 3000 și 8088, SQLite pe volumul llmeu-data la /app/data, API-ul încorporat și un healthcheck pe /healthz.
- Aceeași imagine rulează worker-ul cu node apps/worker/src/run.mjs, așteptând ca web să devină sănătos și partajând volumul llmeu-data.
- Două profiluri suplimentare sunt dezactivate implicit: postgres (postgres:16-alpine) și redis (redis:7-alpine, rezervat și necitit de calea de cod v1).
- Profilul gpu rulează vllm/vllm-openai cu o rezervare de dispozitiv NVIDIA și montează ponderile read-only de pe gazdă; nu este activat implicit, deoarece a pretinde inferență în UE fără un GPU ar fi fals.
- Dockerfile este FROM node:24-alpine cu tini, fără pas de build și fără instalare de dependențe, rulează ca utilizatorul node, scrie doar sub /app/data, expune 3000 și 8088 și pornește apps/web/server/main.mjs.
Copii de siguranță
node scripts/backup.mjs fără opțiuni scrie ./backups/llmeu-<stamp>.db. SQLite este copiat cu VACUUM INTO, niciodată cp, deoarece o bază de date WAL activă copiată cu cp poate produce o stare ruptă sau învechită.
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
Fiecare copie de siguranță SQLite este verificată înainte ca scriptul să raporteze succes: este restaurată într-o copie temporară, PRAGMA integrity_check trebuie să returneze ok, schema este reaplicată, iar numărătorile de rânduri pentru modele, organizații și utilizatori sunt afișate.
Cu un URL postgres://, scriptul folosește pg_dump --format=custom și necesită pg_dump în PATH; un dump nu poate fi deschis ca bază de date, astfel încât numărătorile sale de rânduri necesită o restaurare de probă.
E-mailul tranzacțional — resetarea parolei, verificarea adresei, invitațiile — folosește MAIL_SMTP_* sau MAIL_API_URL cu MAIL_FROM. Transportul este ales o dată la pornire și raportat pe fiecare pagină care are nevoie de e-mail.
O implementare fără niciunul este o stare suportată: înregistrarea, autentificarea, schimbarea parolei, echipele și invitațiile funcționează, dar e-mailurile de resetare a parolei și de verificare nu pot fi procesate acolo. Paginile spun acest lucru, iar node scripts/db.mjs passwd <email> este ruta de recuperare.
Backend-uri de inferență
Un rând de endpoint numește un runtime și o regiune, iar registrul de backend-uri alege adaptorul în funcție de regiune, astfel încât un endpoint poate fi răspuns doar de o mașină din regiunea pe care o numește.
- INFERENCE_MOCK=true păstrează backend-ul mock determinist, care nu are nevoie de GPU și nici de ponderi.
- GPU_ENABLED=false exclude backend-urile administrate; cu GPU_ENABLED=true, VLLM_BASE_URL se aplică singurei regiuni numite de VLLM_REGION.
- O implementare cu mai multe regiuni setează o variabilă pentru fiecare regiune, numită după slug-ul regiunii (de exemplu VLLM_BASE_URL_DE_FRA). Un endpoint dintr-o regiune fără backend configurat este refuzat, în loc să fie servit de o mașină din altă regiune.
- Backend-urile partenere provin din PARTNER_BASE_URL_<REGION> și folosesc PARTNER_SOVEREIGNTY și PARTNER_API_KEY; serverul permite utilizarea partenerilor când PARTNER_BASE_URL este setat.
Fără GPU, răspunde backend-ul mock. Regiunea sa este dev-mock, iar ieșirea sa spune că este backend-ul mock, că nu au rulat ponderi reale și că inferența first-party are nevoie de VLLM_BASE_URL și de flagul gpu_enabled. Un succes mock care ar părea real ar fi singurul lucru pe care acest produs nu trebuie să îl livreze niciodată.