Auto-hospedagem
Node e um diretório com permissão de escrita são o requisito completo. As afirmações abaixo foram lidas dos ficheiros nomeados ao lado; tudo o que não pôde ser confirmado não está aqui.
Pontos de entrada
- package.json requer Node >= 22.5. O armazenamento SQLite é o módulo node:sqlite do próprio Node.
- Não existe qualquer lista de dependências: aplicações, pacotes, scripts e testes executam na biblioteca padrão do Node, e o Dockerfile não instala nada.
- Existem três processos; cada um abre a base de dados e executa a migração no arranque.
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
Armazenamento de dados
DATABASE_URL=sqlite:./data/llmeu.db não precisa de servidor, e o diretório e o ficheiro são criados na primeira abertura. O handle é executado em modo WAL com chaves externas ativadas e um timeout de ocupado de 5 s.
packages/shared/src/db/schema.sql é o esquema. É apenas de criação e idempotente, pelo que executá-lo novamente é seguro; uma coluna adicionada a uma tabela existente também tem de ser declarada no passo aditivo em packages/shared/src/db/index.mjs.
Um URL postgres:// segue um ramo de adaptador separado. Esse ramo requer o pacote pg, que não é uma dependência declarada, e o cabeçalho do schema.sql diz para tratar o caminho Postgres como não verificado até existir um driver real e uma migração testada. O SQLite é o caminho que este repositório executa.
CLI do operador
node scripts/db.mjs lê a DATABASE_URL e não precisa de serviços. Comandos:
| Comando | O que faz |
|---|---|
| node scripts/db.mjs migrate | Aplica o schema.sql e reporta o número de declarações. |
| node scripts/db.mjs seed | Insere ou atualiza o catálogo e a organização de demonstração, e escreve a chave de demonstração em data/demo-key.txt com modo 0600. |
| node scripts/db.mjs reset | Apenas para SQLite: remove o ficheiro da base de dados e os seus ficheiros -wal e -shm associados, depois migra e semeia. No Postgres, recusa. |
| node scripts/db.mjs status | Imprime o motor, o número de tabelas e a contagem de linhas de cada tabela não vazia. |
| node scripts/db.mjs demo-key | Emite uma chave nova para a organização de demonstração e imprime-a uma vez. |
| node scripts/db.mjs passwd <email> | Define a palavra-passe de um utilizador a partir do servidor e elimina todas as sessões desse utilizador. Este é o caminho de recuperação quando não está configurado nenhum transporte de correio. Sem --password, gera uma e imprime-a uma vez. |
Ambiente
Agrupados como o .env.example os agrupa.
| Grupo | Variáveis | Notas |
|---|---|---|
| URLs e portas | APP_URL, API_URL, WEB_PORT, API_PORT, API_EMBED, PUBLIC_API_URL | A porta da API tem como padrão 8088, não 8080. API_EMBED=false é para uma implementação que executa a API como processo próprio; PUBLIC_API_URL é a base mostrada em trechos de documentação. |
| Armazenamento de dados | DATABASE_URL, REDIS_URL | DATABASE_URL seleciona o armazenamento; REDIS_URL é reservado para a fila de tarefas e não é lido pelo caminho de código v1. |
| Inferência | INFERENCE_MOCK, GPU_ENABLED, VLLM_BASE_URL, VLLM_REGION, VLLM_MODEL_VERSION, VLLM_BASE_URL_<REGION>, DEFAULT_REGION, PARTNER_* | Consulte a secção de inferência abaixo; PARTNER_* cobre o URL base do parceiro, região, classe de soberania e chave de API. |
| Faturação | FX_EUR_USD, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | O registo é mantido em micros USD e o EUR é derivado para apresentação à taxa 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 | Defina MAIL_SMTP_HOST para SMTP, ou MAIL_API_URL com MAIL_FROM para um relay HTTP, ou nenhum. | |
| Segurança | NODE_ENV, LLMEU_IP_SALT, LLMEU_DEMO_PASSWORD, LLMEU_KEY_ENV | NODE_ENV=production marca os cookies de sessão como Secure. LLMEU_IP_SALT adiciona salt ao IP do cliente com hash e deve ser alterado por implementação. LLMEU_KEY_ENV prefixa as chaves geradas com llmeu_live_ ou llmeu_test_. |
| Diversos | LOG_LEVEL | LOG_LEVEL define o nível mínimo de log. |
Docker
O Compose e a imagem são opcionais: a própria suite do repositório não requer Docker, e o caminho principal é um processo local sem serviços.
- O perfil predefinido executa web: portas 3000 e 8088, SQLite no volume llmeu-data em /app/data, a API incorporada, e um healthcheck em /healthz.
- A mesma imagem executa o worker com node apps/worker/src/run.mjs, à espera que o web fique saudável e partilhando o volume llmeu-data.
- Dois perfis extra estão desativados por predefinição: postgres (postgres:16-alpine) e redis (redis:7-alpine, reservado e não lido pelo caminho de código v1).
- O perfil gpu executa vllm/vllm-openai com uma reserva de dispositivo NVIDIA e monta os pesos em modo só de leitura a partir do anfitrião; não está ativado por predefinição, porque afirmar inferência na UE sem uma GPU seria falso.
- O Dockerfile é FROM node:24-alpine com tini, sem passo de compilação e sem instalação de dependências, é executado como o utilizador node, escreve apenas em /app/data, expõe 3000 e 8088, e inicia apps/web/server/main.mjs.
Cópias de segurança
node scripts/backup.mjs sem flags escreve ./backups/llmeu-<stamp>.db. O SQLite é copiado com VACUUM INTO, nunca com cp, porque uma base de dados WAL em funcionamento copiada com cp pode originar um estado rasgado ou desatualizado.
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
Cada cópia de segurança SQLite é verificada antes de o script reportar sucesso: é restaurada para uma cópia temporária, o PRAGMA integrity_check tem de devolver ok, o esquema é reaplicado e são impressas as contagens de linhas para models, organizations e users.
Com um URL postgres://, o script usa pg_dump --format=custom e precisa de pg_dump no PATH; um dump não pode ser aberto como base de dados, pelo que as suas contagens de linhas precisam de um restauro numa base de dados de teste.
Correio
O correio transacional — reposição de palavra-passe, verificação de endereço, convites — usa MAIL_SMTP_* ou MAIL_API_URL com MAIL_FROM. O transporte é escolhido uma vez no arranque e reportado em todas as páginas que precisam de correio.
Uma implementação sem nenhum é um estado suportado: registo, início de sessão, alteração de palavra-passe, equipas e convites funcionam, mas o correio de reposição de palavra-passe e de verificação não pode ser processado aí. As páginas dizem-no, e node scripts/db.mjs passwd <email> é a rota de recuperação.
Backends de inferência
Uma linha de endpoint nomeia um runtime e uma região, e o registo de backend escolhe o adaptador por região, pelo que um endpoint só pode ser respondido por uma máquina na região que nomeia.
- INFERENCE_MOCK=true mantém o backend mock determinístico, que não precisa de GPU nem de pesos.
- GPU_ENABLED=false mantém os backends geridos de fora; com GPU_ENABLED=true, VLLM_BASE_URL aplica-se à única região nomeada por VLLM_REGION.
- Uma implementação com várias regiões define uma variável por região, nomeada segundo o slug da região (por exemplo VLLM_BASE_URL_DE_FRA). Um endpoint numa região sem backend configurado é recusado, em vez de ser servido a partir de uma máquina noutra região.
- Os backends parceiros vêm de PARTNER_BASE_URL_<REGION> e usam PARTNER_SOVEREIGNTY e PARTNER_API_KEY; o servidor permite o uso de parceiros quando PARTNER_BASE_URL está definido.
Sem GPU, o backend mock responde. A sua região é dev-mock, e a sua saída indica que é o backend mock, que nenhuns pesos reais foram executados, e que a inferência de primeira parte precisa de VLLM_BASE_URL e da flag gpu_enabled. Um sucesso mock que parecesse real seria a única coisa que este produto nunca deve lançar.