Self-hosting

Node e una directory scrivibile sono l'unico requisito. Le affermazioni seguenti sono state lette dai file indicati accanto; tutto ciò che non è potuto essere confermato non è qui.

Punti di ingresso

  • package.json richiede Node >= 22.5. L'archivio SQLite è il modulo node:sqlite di Node stesso.
  • Non c'è alcun elenco di dipendenze: app, pacchetti, script e test girano sulla libreria standard di Node, e il Dockerfile non installa nulla.
  • Esistono tre processi; ciascuno apre il database ed esegue la migrazione all'avvio.
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 non richiede alcun server, e la directory e il file vengono creati alla prima apertura. L’handle viene eseguito in modalità WAL con chiavi esterne attive e un timeout di occupato di 5 s.

packages/shared/src/db/schema.sql è lo schema. È solo di creazione e idempotente, quindi rieseguirlo è sicuro; una colonna aggiunta a una tabella esistente deve anche essere dichiarata nel passaggio additivo in packages/shared/src/db/index.mjs.

Un URL postgres:// segue un ramo separato dell’adattatore. Quel ramo richiede il pacchetto pg, che non è una dipendenza dichiarata, e l’intestazione di schema.sql dice di trattare il percorso Postgres come non verificato finché non esistono un driver reale e una migrazione testata. SQLite è il percorso che questo repository esegue.

CLI dell’operatore

node scripts/db.mjs legge DATABASE_URL e non richiede servizi. Comandi:

Comando Cosa fa
node scripts/db.mjs migrateApplica schema.sql e riporta il numero di istruzioni.
node scripts/db.mjs seedInserisce o aggiorna il catalogo e l’organizzazione demo, e scrive la chiave demo in data/demo-key.txt con modalità 0600.
node scripts/db.mjs resetSolo per SQLite: rimuove il file del database e i suoi file companion -wal e -shm, poi migra e popola. Su Postgres rifiuta.
node scripts/db.mjs statusStampa il motore, il numero di tabelle e il conteggio delle righe di ogni tabella non vuota.
node scripts/db.mjs demo-keyEmette una nuova chiave per l’organizzazione demo e la stampa una volta.
node scripts/db.mjs passwd <email>Imposta la password di un utente dal server ed elimina ogni sessione di quell’utente. Questo è il percorso di recupero quando non è configurato alcun trasporto mail. Senza --password ne genera una e la stampa una volta.

Ambiente

Raggruppate come le raggruppa .env.example.

Gruppo Variabili Note
URL e porteAPP_URL, API_URL, WEB_PORT, API_PORT, API_EMBED, PUBLIC_API_URLLa porta dell'API è per impostazione predefinita 8088, non 8080. API_EMBED=false serve per una distribuzione che esegue l'API come processo a sé; PUBLIC_API_URL è la base mostrata negli esempi della documentazione.
Archivio datiDATABASE_URL, REDIS_URLDATABASE_URL seleziona l'archivio; REDIS_URL è riservato alla coda dei job e non viene letto dal percorso di codice v1.
InferenzaINFERENCE_MOCK, GPU_ENABLED, VLLM_BASE_URL, VLLM_REGION, VLLM_MODEL_VERSION, VLLM_BASE_URL_<REGION>, DEFAULT_REGION, PARTNER_*Vedi la sezione Inferenza di seguito; PARTNER_* copre l'URL di base del partner, la regione, la classe di sovranità e la chiave API.
FatturazioneFX_EUR_USD, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETIl registro è tenuto in micro USD e l’EUR è derivato per la visualizzazione a FX_EUR_USD.
PostaMAIL_FROM, MAIL_SMTP_HOST, MAIL_SMTP_PORT, MAIL_SMTP_SECURE, MAIL_SMTP_USER, MAIL_SMTP_PASS, MAIL_TIMEOUT_MS, MAIL_API_URL, MAIL_API_KEYImposta MAIL_SMTP_HOST per SMTP, oppure MAIL_API_URL con MAIL_FROM per un relay HTTP, oppure nessuno dei due.
SicurezzaNODE_ENV, LLMEU_IP_SALT, LLMEU_DEMO_PASSWORD, LLMEU_KEY_ENVNODE_ENV=production contrassegna i cookie di sessione come Secure. LLMEU_IP_SALT applica un salt all'IP client con hash e va cambiato per ogni distribuzione. LLMEU_KEY_ENV antepone alle chiavi generate il prefisso llmeu_live_ o llmeu_test_.
VarieLOG_LEVELLOG_LEVEL imposta il livello minimo di log.

Docker

Compose e l’immagine sono opzionali: la suite del repository stesso non richiede Docker, e il percorso principale è un processo locale senza servizi.

  • Il profilo predefinito esegue web: porte 3000 e 8088, SQLite sul volume llmeu-data in /app/data, l’API incorporata e un healthcheck su /healthz.
  • La stessa immagine esegue il worker con node apps/worker/src/run.mjs, attendendo che web diventi healthy e condividendo il volume llmeu-data.
  • Due profili extra sono disattivati per impostazione predefinita: postgres (postgres:16-alpine) e redis (redis:7-alpine, riservato e non letto dal percorso di codice v1).
  • Il profilo gpu esegue vllm/vllm-openai con una prenotazione di dispositivo NVIDIA e monta i pesi in sola lettura dall’host; non è abilitato per impostazione predefinita, perché affermare inferenza UE senza una GPU sarebbe falso.
  • Il Dockerfile è FROM node:24-alpine con tini, nessuno step di build e nessuna installazione di dipendenze, viene eseguito come utente node, scrive solo sotto /app/data, espone 3000 e 8088 e avvia apps/web/server/main.mjs.

Backup

node scripts/backup.mjs senza flag scrive ./backups/llmeu-<stamp>.db. SQLite viene copiato con VACUUM INTO, mai con cp, perché un database WAL attivo copiato con cp può produrre uno stato incoerente o obsoleto.

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

Ogni backup SQLite viene verificato prima che lo script segnali successo: viene ripristinato in una copia temporanea, PRAGMA integrity_check deve restituire ok, lo schema viene riapplicato e vengono stampati i conteggi di righe per models, organizations e users.

Con un URL postgres:// lo script usa pg_dump --format=custom e richiede pg_dump nel PATH; un dump non può essere aperto come database, quindi i suoi conteggi di righe richiedono un ripristino temporaneo.

Posta

La posta transazionale — reimpostazione password, verifica dell'indirizzo, inviti — usa MAIL_SMTP_* o MAIL_API_URL con MAIL_FROM. Il trasporto viene scelto una volta all'avvio ed è indicato su ogni pagina che richiede posta.

Una distribuzione senza né l'uno né l'altro è uno stato supportato: registrazione, accesso, cambio password, team e inviti funzionano, ma la posta di reimpostazione password e di verifica non può essere elaborata lì. Le pagine lo dicono, e node scripts/db.mjs passwd <email> è la via di recupero.

Backend di inferenza

Una riga endpoint indica un runtime e una regione, e il registro dei backend sceglie l'adattatore in base alla regione, quindi a un endpoint può rispondere solo una macchina nella regione che indica.

  • INFERENCE_MOCK=true mantiene il backend mock deterministico, che non richiede GPU né pesi.
  • GPU_ENABLED=false tiene fuori i backend gestiti; con GPU_ENABLED=true, VLLM_BASE_URL si applica all'unica regione indicata da VLLM_REGION.
  • Una distribuzione con più regioni imposta una variabile per regione, denominata secondo lo slug della regione (ad esempio VLLM_BASE_URL_DE_FRA). Un endpoint in una regione senza backend configurato viene rifiutato, anziché essere servito da una macchina in un'altra regione.
  • I backend partner provengono da PARTNER_BASE_URL_<REGION> e usano PARTNER_SOVEREIGNTY e PARTNER_API_KEY; il server consente l'uso dei partner quando PARTNER_BASE_URL è impostato.

Senza GPU risponde il backend mock. La sua regione è dev-mock e il suo output dichiara che è il backend mock, che non sono stati eseguiti pesi reali e che l'inferenza first-party richiede VLLM_BASE_URL e il flag gpu_enabled. Un successo mock che sembrasse reale sarebbe l'unica cosa che questo prodotto non deve mai rilasciare.