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 migrate | Applica schema.sql e riporta il numero di istruzioni. |
| node scripts/db.mjs seed | Inserisce 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 reset | Solo 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 status | Stampa il motore, il numero di tabelle e il conteggio delle righe di ogni tabella non vuota. |
| node scripts/db.mjs demo-key | Emette 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 porte | APP_URL, API_URL, WEB_PORT, API_PORT, API_EMBED, PUBLIC_API_URL | La 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 dati | DATABASE_URL, REDIS_URL | DATABASE_URL seleziona l'archivio; REDIS_URL è riservato alla coda dei job e non viene letto dal percorso di codice v1. |
| Inferenza | INFERENCE_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. |
| Fatturazione | FX_EUR_USD, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Il registro è tenuto in micro USD e l’EUR è derivato per la visualizzazione a FX_EUR_USD. |
| Posta | 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 | Imposta MAIL_SMTP_HOST per SMTP, oppure MAIL_API_URL con MAIL_FROM per un relay HTTP, oppure nessuno dei due. |
| Sicurezza | NODE_ENV, LLMEU_IP_SALT, LLMEU_DEMO_PASSWORD, LLMEU_KEY_ENV | NODE_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_. |
| Varie | LOG_LEVEL | LOG_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.