Instal·lar Nessie
Executa Nessie en la infraestructura que tu triïs, des d’una sola màquina fins a un núvol gestionat.
Què necessites abans de començar
Nessie és un monorepo de pnpm amb serveis de Node i dos paquets estàtics. Independentment de la manera com l’executis, primer han d’existir les mateixes quatre coses.
- PostgreSQL amb
pgvector. No és opcional i no es pot substituir: les migracions executenCREATE EXTENSION vectori creen columnesvector(1024). La producció executapgvector/pgvector:pg17. - Node 22 i pnpm. Tots els Dockerfile de
infrastructure/docker/es basen ennode:22-slim, i el repositori fixa el gestor de paquets alpackage.jsonarrel (pnpm@10.22.0). - Un endpoint de model compatible amb OpenAI i una clau per accedir-hi. Els agents i els embeddings passen tots dos per aquí. Consulta «Configuració de la inferència» més avall.
- Emmagatzematge d’objectes compatible amb S3, per a qualsevol desplegament que no sigui el de desenvolupament local. Les pujades al disc local es rebutgen fora del mode
local, i aquest rebuig fa fallar l’arrencada en lloc de deixar-la funcionant amb limitacions; consulta «Una sola màquina o un núvol gestionat».
Calen Docker i un proxy invers per al camí amb contenidors que es descriu més avall, i un domini i TLS per a qualsevol cosa accessible des d’internet.
La mida del maquinari (la CPU, la memòria i el disc mínims per a un nombre determinat de persones) no s’indica enlloc del repositori, per tant aquesta pàgina tampoc no ho indica.
Els components i com encaixen
| Component | Què és | Entrada |
|---|---|---|
api/ | L’API HTTP/WebSocket basada en Fastify. Tots els clients parlen amb aquesta i amb cap altra. | Sí |
worker/ | Orquestració d’agents i execucions en segon pla. Consulta la cua de tasques i no obre cap port. | No |
admin/ | La interfície del producte: un paquet estàtic creat amb Vite i servit per nginx. | Sí |
web/ | El lloc públic. És estàtic, opcional i no té relació amb l’execució del producte. | Sí |
| PostgreSQL | La base de dades, la cua de tasques i el bus de temps real, tots tres. | No |
| Emmagatzematge d’objectes | Adjunts, avatars i fitxers de la base de coneixement. Compatible amb S3; MinIO en producció. | No |
gateway/ | Un relé opcional de notificacions push per a APNs i FCM. Està desactivat tret que l’activis de manera deliberada. | Sí |
L’API i el worker són una sola imatge. Els dos paquets estan molt interrelacionats, així que Dockerfile.app compila tot l’equip una vegada i el contenidor del worker substitueix l’ordre per node worker/dist/index.js. En mode local no hi ha cap contenidor de worker: l’API executa el worker incrustat en el seu propi procés.
No hi ha Redis. La cua de tasques i el transport de temps real es basen tots dos en Postgres, per decisió i no per descuit: l’arbre de Google Cloud elimina els seus mòduls de Pub/Sub i de Redis pel mateix motiu.
Ports
En el desenvolupament local, l’API és per defecte al port 5454 i l’administració al 5455. Una còpia de treball pot tenir el seu propi parell definint NESSIE_API_PORT i NESSIE_ADMIN_PORT, a l’entorn o al .env del repositori; totes les eines del repositori, també els conjunts de proves de navegador, llegeixen els mateixos dos valors. Tria el parell abans d’engegar i mantén-lo: moure un servei en execució a un altre port fa que l’administració parli amb l’API equivocada.
La producció és una altra qüestió i no canvia per aquesta norma. El contenidor de l’API escolta internament al 5554 (fixat amb NESSIE_API_PORT) i el relé push al 5556, tots dos darrere del proxy invers.
Executar-ho en una sola màquina, per al desenvolupament
El repositori inclou un fitxer de Compose amb només una base de dades, que és tot el que necessita el bucle de desenvolupament local.
pnpm install
docker compose -f infrastructure/compose/docker-compose.yml up -d postgresAquest contenidor publica Postgres al 55432, amb l’usuari, la contrasenya i la base de dades anomenats nessie. Posa la cadena de connexió en un .env a l’arrel del repositori (el script de desenvolupament de l’API el llegeix), després aplica les migracions i compila el worker una vegada, perquè en mode local l’API carrega el worker des del seu dist compilat:
DATABASE_URL=postgresql://nessie:nessie@localhost:55432/nessie
pnpm --filter @nessie/api prisma:migrate:deploy
pnpm --filter @nessie/worker build
pnpm devpnpm dev executa l’API i el panell d’administració en paral·lel amb recàrrega en calent, als ports 5454 i 5455. Comprova que l’API respon amb GET /api/health.
Postgres i el requisit de pgvector
Les mateixes migracions creen tres extensions (vector, pg_trgm i pgcrypto), així que el rol que les executa ha de tenir permís per crear extensions. En un Postgres gestionat, és el primer que cal comprovar.
L’amplada dels vectors forma part de l’esquema, no és un paràmetre de configuració. thoughts.embedding, thought_recalls.query_embedding i knowledge_page_chunks.embedding són columnes vector(N), i N s’indica una sola vegada com a EMBEDDING_DIMENSIONS a packages/schemas/src/embedding.ts. Actualment és 1024, l’amplada nativa del model d’embeddings que fa servir la producció. Cada petició d’embedding envia aquesta amplada, de manera que un proveïdor que respongui amb una altra falla de manera explícita, en lloc d’escriure vectors que la base de dades rebutja. Canviar a un model d’embeddings d’una amplada diferent implica editar aquesta constant, escriure una migració que torni a definir el tipus de les tres columnes i tornar a calcular els embeddings: els vectors d’amplades diferents no es poden convertir, així que la migració els posa a nul en lloc de truncar-los.
Si executes Nessie al costat d’altres aplicacions en una xarxa Docker compartida, dona al contenidor de la base de dades un nom diferent. El servei de Compose del projecte s’anomena nessie-postgres i mai postgres, perquè el nom del servei esdevé un àlies DNS a la xarxa compartida, i una col·lisió fa que postgres:5432 alterni entre dues bases de dades.
Configuració
La configuració d’execució es organitza en capes: un fitxer nessie.config.json muntat en només lectura a l’API i al worker, amb les variables d’entorn per sobre. La correspondència entre tots dos és ConfigEnvMap a packages/config, que és la llista de referència.
NESSIE_MODE tria la forma del desplegament: local (el valor per defecte i el camí del desenvolupador), hosted o selfHosted, que és el que defineix una instal·lació autoallotjada. Desactiva l’inici de sessió de desenvolupament i exigeix una llista de CORS permesos.
Aquests són els paràmetres sense els quals un desplegament selfHosted no arrencarà o no funcionarà:
| Variable | Per què |
|---|---|
DATABASE_URL / NESSIE_DB_URL | La cadena de connexió a Postgres. |
NESSIE_AUTH_SECRET | 32 bytes en hexadecimal (openssl rand -hex 32). Signa les sessions i els tokens d’arrencada. Fora del mode local, l’arrencada falla sense aquest valor. Ha de ser el mateix a cada rèplica i entre desplegaments, o totes les sessions es perdran a cada publicació. |
NESSIE_ENCRYPTION_KEY_RING, NESSIE_ENCRYPTION_ACTIVE_KEY_VERSION | Un anell de claus en JSON i l’etiqueta que selecciona l’arrel activa, obligatoris fora del mode local. És una arrel independent del secret de signatura: l’anell xifra cada secret durador que Nessie emmagatzema. No els reutilitzis l’un com a l’altre. |
NESSIE_STORAGE_PROVIDER | Ha de ser s3. filesystem és el valor per defecte i es rebutja en mode hosted i selfHosted, de manera que un desplegament que no defineix res aquí no arrenca, en lloc d’escriure les pujades en un disc al qual cap altre contenidor no pot accedir. |
NESSIE_CORS_ORIGINS | L’origen del panell d’administració. El panell i l’API són orígens separats, i l’API ha de posar a la llista blanca l’origen que serveix. |
NESSIE_API_PUBLIC_URL, NESSIE_ADMIN_PUBLIC_URL | Serveix per generar els URI de redirecció d’OAuth fora d’una petició HTTP. L’API llança un error en el moment de la petició si falta aquest paràmetre. |
NESSIE_MODEL_PROVIDER, _BASE_URL, _API_KEY | On va la inferència. Consulta més avall. |
infrastructure/compose/.env.prod.example conté l’estructura exacta de totes, amb els blocs opcionals (emmagatzematge d’objectes, el magatzem de secrets, l’SSO i el correu connectat) comentats al seu lloc. El catàleg complet, amb cada paràmetre opcional, és docs/deployment/configuration.md.
Desplegament amb Docker Compose
Copia .env.prod.example a .env al costat de docker-compose.prod.yml i omple’l; després aixeca la base de dades, compila, migra i arrenca. L’ordre importa: les migracions i les dades inicials s’executen contra una base de dades que ja està en marxa, abans que cap servei respongui.
COMPOSE="docker compose -f infrastructure/compose/docker-compose.prod.yml"
bash infrastructure/compose/ensure-encryption-key-ring.sh infrastructure/compose/.env
$COMPOSE up -d nessie-postgres
$COMPOSE build nessie-api nessie-admin nessie-web
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api prisma:migrate:deploy
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api seed:connectors
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api seed:apps
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api reconcile
$COMPOSE up -dEls Dockerfile de producció executen el lint de cada paquet abans de compilar-lo, de manera que un error de lint és un error de compilació.
El pas de reconciliació no és opcional
reconcile crea les regles de política per defecte de cada organització i completa les concessions d’eines de MCP protegides i de l’Assistent personal. L’API ho feia a l’arrencada de cada rèplica; ara es connecta, escolta i no fa res més, de manera que sense aquest pas, la primera organització no té regles de política i cada vinculació d’agent és denegada. És idempotent, així que executar-lo a cada desplegament no té cap cost, i una actualització aplicada a mà també l’ha d’executar.
El compte del primer propietari
Una instal·lació selfHosted nova no té usuaris, així que l’API genera un token d’arrencada d’un sol ús i registra una URL de configuració. El token es guarda a Postgres i no en un procés, de manera que totes les rèpliques hi estan d’acord.
docker compose -f infrastructure/compose/docker-compose.prod.yml logs nessie-api 2>&1 | grep bootstrapObre https://<your-admin-host>/bootstrap?token=<token> i crea el compte del propietari. El token caduca als 15 minuts; reinicia el servei de l’API per generar-ne un de nou.
Si configures l’SSO, no hi ha cap pas d’arrencada. El mode d’arrencada es desactiva en el moment en què s’activa un proveïdor d’autenticació extern, perquè en cas contrari segrestaria la pantalla d’inici de sessió; i la primera persona que inicia sessió amb l’SSO crea l’equip per defecte i n’és la propietària.
Configuració de la inferència
El xat és compatible amb OpenAI. NESSIE_MODEL_BASE_URL és l’endpoint, NESSIE_MODEL_API_KEY el token bearer i NESSIE_MODEL_PROVIDER indica l’adaptador: Nessie inclou adaptadors per a openai, kimi i deepseek, i qualsevol altre proveïdor passa pel connector genèric compatible amb OpenAI. NESSIE_MODEL_NAME defineix el model per defecte del desplegament, que la selecció de model de cada agent individual encara té prioritat sobre ell.
Els embeddings s’encaminen per separat, perquè el proveïdor de xat pot no oferir-los en absolut: DeepSeek, per exemple, no té endpoint d’embeddings. Cada camp NESSIE_EMBEDDING_* que no es defineix hereta el proveïdor de xat, de manera que un desplegament que no n’defineix cap calcula els embeddings a través de l’endpoint del xat. Defineix NESSIE_EMBEDDING_PROVIDER, NESSIE_EMBEDDING_MODEL i, en un proxy que encamina per servei, el segment del servei, quan el proveïdor de xat no pot calcular embeddings. Si ho fas malament, falla sense fer soroll: la recuperació de memòria i la cerca a la base de coneixement passen a un canal lèxic i continuen, i registren kb_search: query embedding failed pel camí.
La comptabilitat de despesa pròpia de Nessie (el registre de tokens per organització i el control de pressupost) no depèn de l’endpoint al qual l’apuntis, i funciona exactament igual tant si hi ha configurada l’atribució signada per cada crida com si no.
Inici de sessió
Nessie autentica les persones a través d’UnlikeOtherAI, que també gestiona l’estructura d’organitzacions i equips. La pàgina d’inici de sessió del panell mostra un únic botó «Login / Register».
La integració és un flux de config-JWT i no un OIDC estàndard: l’API serveix un document de configuració RS256 signat a GET /api/auth/sso/config i el JWKS corresponent a GET /.well-known/jwks.json, tots dos al host de l’API. Configurar-ho és una seqüència d’una sola vegada: genera un parell de claus RSA-2048 i defineix UOA_CONFIG_JWT_PRIVATE_KEY_B64, UOA_CONFIG_JWT_KID, UOA_DOMAIN, UOA_CONFIG_URL, UOA_JWKS_URL, UOA_REDIRECT_URL i UOA_CONTACT_EMAIL; fes clic al botó una vegada, cosa que crea una sol·licitud d’integració; espera que l’aprovin, cosa que envia a l’adreça de contacte un enllaç d’un sol ús amb el secret de client; defineix UOA_CLIENT_SECRET i reinicia l’API. El kid ha de ser únic per domini.
Posar-ho darrere d’un domini, amb TLS
Un desplegament serveix tres noms: el lloc públic, el panell d’administració i l’API. El panell i els orígens de l’API no són intercanviables. Dockerfile.admin incrusta VITE_API_BASE_URL al paquet en el moment de compilar, de manera que cada artefacte del panell compilat (inclosa una versió d’escriptori que el porti incrustat) s’ha de compilar contra l’origen de l’API. Si el compiles contra l’origen propi del panell, /api/auth/providers es resol a la plantilla HTML del panell, i l’inici de sessió es queda a «Loading providers...».
En producció, TLS el termina Caddy, que obté certificats per nom d’host mitjançant HTTP-01 i no necessita un connector de DNS per a això. Apunta el proxy als contenidors del panell i del lloc al port 80, i a l’API al seu port intern.
Defineix NESSIE_API_TRUSTED_PROXY_HOPS amb el nombre de proxies davant de l’API: 1 darrere d’un únic proxy invers. Per defecte és 0, que ignora completament X-Forwarded-For. És una sola decisió amb dues conseqüències: també determina com s’agrupen els límits de freqüència d’autenticació. Si és massa baix, cada client comparteix el cub del proxy; si és massa alt, un client pot falsificar la seva pròpia adreça. Millor errar per baix i mesurar-ho.
Servir cada organització i equip en el seu propi subdomini és possible, però està desactivat per defecte. Ho regeix NESSIE_TEAM_HOST_BASE_DOMAIN: si no està definit, els inquilins no s’encaminen per nom d’host; i la porta d’emissió de certificats NESSIE_TLS_CHECK_KEY ho rebutja tot mentre no estigui definida, cosa que és el valor per defecte correcte per a un desplegament que no fa servir aquesta funció.
Comprovacions de salut
| Endpoint | Pregunta a la qual respon |
|---|---|
GET /api/health | Aquest procés està viu? Només falla quan ha començat a drenar. |
GET /api/health/ready | Pot aquesta rèplica acceptar noves peticions? És aquest el que cal indicar a un balancejador de càrrega. |
GET /api/ops/health | Com va el desplegament? Un informe per a una persona (pulsacions dels workers, profunditat de la cua, tasques mortes), no una sonda. |
En rebre SIGTERM, l’API drena les peticions en lloc de morir on és. NESSIE_SHUTDOWN_TIMEOUT_MS limita aquest drenatge i per defecte és de 25 segons. Mantén-lo per sota del període de gràcia que el teu orquestrador dona al contenidor, o l’entorn d’execució matarà el procés abans i el drenatge no servirà de res.
Actualitzar
Una actualització és prisma migrate deploy contra la base de dades existent, després les dades inicials i després reconcile. Les mateixes quatre ordres que la primera implantació.
El camí es prova per la ruta que realment fa un autoallotjat, en lloc de fer-ho contra una base de dades nova: el CI restaura una instantània presa vint migracions enrere, la migra fins al cap actual, valida l’esquema i executa una comprovació de funcionament sobre les taules principals. Qualsevol publicació dins d’aquesta finestra final és una font d’actualització provada. Les carpetes de migració són immutables un cop s’han afegit al control de versions, i un pas de lint fa fallar la compilació si una es reanomena, es renumera, s’esborra o es modifica, perquè les quatre coses trenquen migrate deploy per a una base de dades que ja ha registrat la fila antiga.
Una migració fallida bloqueja cada desplegament posterior. migrate deploy s’atura al primer error i rebutja totes les migracions posteriors amb P3009, de manera que la instal·lació es queda a la versió antiga fins que algú ho desbloquegi a mà. La producció del projecte va passar un dia rebutjant cada desplegament d’aquesta manera. Desbloquejar-ho vol dir confirmar que la migració fallida es va revertir de veritat (cap dels objectes que crea no hauria d’existir, i tot el que modifica hauria de mantenir la forma original) i només aleshores resoldre-la com a revertida. Una migració que s’ha aplicat a mitges no és candidata.
La còpia de seguretat i la restauració del desplegament autoallotjat amb Compose no estan documentades al repositori, per tant aquesta pàgina no descriu cap procediment per fer-ho.
Una sola màquina o un núvol gestionat
El camí de Docker Compose en una sola màquina, descrit més amunt, és el provat: és el que executa la producció del projecte, en un sol servidor, amb la base de dades, l’emmagatzematge d’objectes, l’API, el worker, el panell i el lloc com a contenidors darrere d’un proxy invers compartit.
Escalar més enllà d’una màquina canvia tres coses, i totes s’apliquen per la força en lloc de ser recomanacions. Fora del mode local, Nessie executa l’API i el worker com a processos separats i en diverses còpies de cadascun, de manera que qualsevol capacitat que assumeixi que un sol procés és propietari del disc de la màquina queda refusada: l’emmagatzematge d’objectes en sistema de fitxers (un error d’arrencada), les plantilles d’entorn d’execució amb el proveïdor docker (inerts, i l’aprovisionament falla de manera explícita) i les eines integrades file_read, file_write i file_glob (un resultat d’eina fallit que l’agent pot llegir). Cada rebuig indica el paràmetre, el mode i què cal usar en el seu lloc.
Per a un núvol gestionat, el repositori inclou un arbre de Terraform a infrastructure/terraform/ orientat a Google Cloud: l’API com a servei de Cloud Run amb un mínim d’una instància (manté un client LISTEN persistent de Postgres i executa tasques de manteniment, de manera que escalar-la a zero atura el temps real), el worker com a worker pool de Cloud Run, perquè no obre cap port i un servei mai no arribaria a estar llest; una tasca de migració que condiciona cada desplegament; Cloud SQL per a PostgreSQL 17, i un bucket de GCS accessible a través del backend compatible amb S3.
Aquest camí està escrit, però no provat. El repositori ho diu sense rodejos, i aquesta pàgina també: no existeix cap projecte de Google Cloud per a això, no s’ha planificat ni aplicat res de l’arbre contra un projecte real, el camí de pujada multipart a GCS no està provat contra un bucket real, el tipus de recurs del worker pool no està validat, i el nombre de salts de proxy de confiança és un marcador de posició que cal mesurar, no una mesura. El panell i el lloc públic no hi estan modelats en absolut. Tracta-ho com un punt de partida que has de verificar, no com un desplegament compatible.
On són les dades d’un desplegament, què guarda el programari i què deliberadament no guarda, és una pàgina a part: Fet a la UE i on són les teves dades.