Instalarea Nessie
Rulați Nessie pe infrastructura pe care o alegeți, de la o singură mașină până la un cloud gestionat.
Ce trebuie să aveți înainte să începeți
Nessie este un monorepo pnpm de servicii Node, plus două pachete statice. Indiferent de felul în care îl rulați, aceleași patru lucruri trebuie să existe mai întâi.
- PostgreSQL cu
pgvector. Nu este opțional și nu poate fi înlocuit — migrările ruleazăCREATE EXTENSION vectorși creează coloanevector(1024). Producția ruleazăpgvector/pgvector:pg17. - Node 22 și pnpm. Fiecare Dockerfile din
infrastructure/docker/se bazează penode:22-slim, iar depozitul își fixează managerul de pachete înpackage.json-ul din rădăcină (pnpm@10.22.0). - Un endpoint de model compatibil OpenAI și o cheie pentru el. Agenții și embedding-urile trec amândoi prin el. Vedeți „Configurarea inferenței” mai jos.
- Stocare de obiecte compatibilă S3, pentru orice implementare care nu este cea locală de dezvoltare. Încărcările pe discul local sunt refuzate în afara modului
local, iar acest refuz este o eroare la pornire, nu o pornire degradată — vedeți „Pe o singură mașină sau într-un cloud administrat”.
Docker și un proxy invers sunt necesare pentru calea cu containere descrisă mai jos, iar un domeniu și TLS pentru orice este accesibil din internet.
Dimensionarea hardware — numărul minim de procesoare, memorie și spațiu pe disc pentru un anumit număr de persoane — nu este menționată nicăieri în depozit, deci nici această pagină nu o menționează.
Componentele și modul în care se îmbină
| Componentă | Ce este | Acces din exterior |
|---|---|---|
api/ | API-ul HTTP/WebSocket Fastify. Fiecare client vorbește doar cu acesta. | Da |
worker/ | Orchestrarea agenților și rulările în fundal. Interoghează coada de sarcini și nu leagă niciun port. | Nu |
admin/ | Interfața produsului — un pachet static construit cu Vite și servit de nginx. | Da |
web/ | Site-ul public. Static, opțional și neimplicat în rularea produsului. | Da |
| PostgreSQL | Baza de date, coada de sarcini și magistrala în timp real — toate trei. | Nu |
| Stocare de obiecte | Atașamente, avatare și fișiere din baza de cunoștințe. Compatibilă S3; în producție se folosește MinIO. | Nu |
gateway/ | Un releu opțional de notificări push pentru APNs și FCM. Este oprit, dacă nu îl activați în mod deliberat. | Da |
API-ul și worker-ul sunt o singură imagine. Cele două pachete sunt strâns legate, deci Dockerfile.app construiește întreaga echipă o singură dată, iar containerul worker suprascrie comanda cu node worker/dist/index.js. În modul local nu există deloc container worker: API-ul rulează worker-ul încorporat în propriul proces.
Nu există Redis. Coada de sarcini și transportul în timp real se bazează pe Postgres, din decizie, nu din omisiune — arborele Google Cloud șterge modulele Pub/Sub și Redis din același motiv.
Porturi
În dezvoltarea locală, API-ul rulează implicit pe 5454, iar interfața de administrare pe 5455. O copie de lucru își poate lua propria pereche setând NESSIE_API_PORT și NESSIE_ADMIN_PORT, în mediu sau în fișierul .env al depozitului; toate uneltele din depozit, inclusiv suitele de teste în browser, citesc aceleași două valori. Alegeți perechea înainte de pornire și păstrați-o: mutarea unui serviciu aflat în execuție pe alt port face ca interfața de administrare să comunice cu un API greșit.
Producția este altceva și nu este modificată de această regulă. Containerul API ascultă intern pe 5554 (fixat cu NESSIE_API_PORT), iar releul push pe 5556, ambele în spatele proxy-ului invers.
Rularea pe o singură mașină, pentru dezvoltare
Depozitul include un fișier Compose cu doar o bază de date, iar atât îi trebuie bucla de lucru locală.
pnpm install
docker compose -f infrastructure/compose/docker-compose.yml up -d postgresAcest container publică Postgres pe 55432, cu utilizatorul, parola și baza de date numite toate nessie. Puneți șirul de conectare într-un .env în rădăcina depozitului — scriptul de dezvoltare al API-ului îl citește — apoi aplicați migrările și construiți worker-ul o dată, pentru că în modul local API-ul încarcă worker-ul din dist-ul construit:
DATABASE_URL=postgresql://nessie:nessie@localhost:55432/nessie
pnpm --filter @nessie/api prisma:migrate:deploy
pnpm --filter @nessie/worker build
pnpm devpnpm dev rulează API-ul și interfața de administrare în paralel, cu reîncărcare la cald, pe porturile 5454 și 5455. Verificați că API-ul a răspuns la GET /api/health.
Postgres și cerința pgvector
Trei extensii sunt create chiar de migrări — vector, pg_trgm și pgcrypto — deci rolul care le rulează trebuie să aibă voie să creeze extensii. Pe un Postgres administrat, acesta este lucrul de verificat înaintea tuturor celorlalte.
Lățimea vectorilor face parte din schemă, nu este o setare. thoughts.embedding, thought_recalls.query_embedding și knowledge_page_chunks.embedding sunt coloane vector(N), iar N este declarat o singură dată ca EMBEDDING_DIMENSIONS în packages/schemas/src/embedding.ts — în prezent 1024, lățimea nativă a modelului de embedding folosit în producție. Fiecare cerere de embedding trimite această lățime, deci un furnizor care ar răspunde cu o altă lățime eșuează zgomotos, în loc să scrie vectori pe care baza de date îi respinge. Trecerea la un model de embedding de altă lățime înseamnă editarea acestei constante, scrierea unei migrări care redefinește tipul celor trei coloane și recalcularea embedding-urilor: vectorii de lățimi diferite nu se pot converti, deci migrarea îi setează pe NULL în loc să îi trunchieze.
Dacă rulați Nessie lângă alte aplicații pe o rețea Docker comună, dați containerului bazei de date un nume distinct. Serviciul Compose al proiectului se numește nessie-postgres și niciodată postgres, pentru că numele serviciului devine un alias DNS pe rețeaua comună, iar o coliziune face ca postgres:5432 să distribuie în rotație între două baze de date.
Configurarea
Configurarea la rulare este stratificată: un fișier nessie.config.json montat în mod read-only în API și în worker, peste care se suprapun variabilele de mediu. Corespondența dintre cele două este ConfigEnvMap din packages/config, care este lista de referință.
NESSIE_MODE alege forma implementării: local (implicit și calea dezvoltatorului), hosted sau selfHosted — valoarea setată de o instalare găzduită de dvs. Dezactivează autentificarea de dezvoltare și cere o listă permisă CORS.
Acestea sunt setările fără de care o implementare selfHosted nu pornește sau nu funcționează:
| Variabilă | De ce |
|---|---|
DATABASE_URL / NESSIE_DB_URL | Șirul de conectare la Postgres. |
NESSIE_AUTH_SECRET | 32 de octeți în hex (openssl rand -hex 32). Semnează sesiunile și tokenurile de inițializare. Pornirea eșuează fără el în afara modului local, iar trebuie să fie aceeași valoare pe fiecare replică și între implementări, altfel fiecare sesiune moare la fiecare lansare. |
NESSIE_ENCRYPTION_KEY_RING, NESSIE_ENCRYPTION_ACTIVE_KEY_VERSION | Un inel de chei JSON și eticheta care selectează rădăcina activă, obligatorii în afara modului local. Este o rădăcină separată de secretul de semnare — inelul criptează fiecare secret durabil pe care îl stochează Nessie. Nu le folosiți una drept cealaltă. |
NESSIE_STORAGE_PROVIDER | Trebuie să fie s3. filesystem este implicit și este refuzat în modurile hosted și selfHosted, deci o implementare care nu setează nimic aici nu pornește, în loc să scrie încărcările pe un disc la care niciun alt container nu are acces. |
NESSIE_CORS_ORIGINS | Originea interfeței de administrare. Administrarea și API-ul sunt origini separate, iar API-ul trebuie să includă în lista permisă originea pe care o servește. |
NESSIE_API_PUBLIC_URL, NESSIE_ADMIN_PUBLIC_URL | Folosite pentru a emite URI-urile de redirecționare OAuth în afara unei cereri HTTP. Fără prima, API-ul aruncă o eroare în momentul cererii. |
NESSIE_MODEL_PROVIDER, _BASE_URL, _API_KEY | Unde merge inferența. Vedeți mai jos. |
infrastructure/compose/.env.prod.example are forma exactă a tuturor, cu blocurile opționale — stocarea de obiecte, seiful de secrete, SSO, e-mailul conectat — comentate la locul lor. Catalogul complet, cu fiecare setare opțională, se află în docs/deployment/configuration.md.
Implementarea cu Docker Compose
Copiați .env.prod.example în .env lângă docker-compose.prod.yml și completați-l, apoi porniți baza de date, construiți, migrați și porniți serviciile. Ordinea contează: migrările și datele inițiale (seed) rulează pe o bază de date deja pornită, înainte ca vreun serviciu să deservească.
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 -dDockerfile-urile de producție rulează lint-ul fiecărui pachet înainte să îl construiască, deci o eroare de lint este o eroare de construire.
Pasul reconcile nu este opțional
reconcile populează regulile de politică implicite ale fiecărei organizații și completează retroactiv permisiunile pentru instrumentele MCP protejate și pentru Personal Assistant. API-ul făcea asta înainte la pornire, pe fiecare replică; acum se conectează, ascultă și nu face nimic altceva, deci fără acest pas, prima organizație nu are reguli de politică, iar fiecare legare a unui agent este refuzată. Este idempotent, deci rularea lui la fiecare implementare nu costă nimic — iar o actualizare aplicată manual trebuie să-l ruleze și ea.
Primul cont de proprietar
O instalare selfHosted nouă nu are utilizatori, deci API-ul generează un token de inițializare de unică folosință și înregistrează un URL de configurare. Tokenul trăiește în Postgres, nu într-un proces, astfel încât fiecare replică este de acord asupra lui.
docker compose -f infrastructure/compose/docker-compose.prod.yml logs nessie-api 2>&1 | grep bootstrapDeschideți https://<your-admin-host>/bootstrap?token=<token> și creați contul de proprietar. Tokenul expiră după 15 minute; reporniți serviciul API ca să generați unul nou.
Dacă configurați SSO, nu există deloc pas de inițializare. Modul de inițializare este suprimat în momentul în care este activat un furnizor extern de autentificare — altfel ar prelua ecranul de autentificare — iar prima persoană care se autentifică prin SSO inițializează echipa implicită și devine proprietarul ei.
Configurarea inferenței
Chatul este compatibil OpenAI. NESSIE_MODEL_BASE_URL este endpoint-ul, NESSIE_MODEL_API_KEY tokenul de autentificare, iar NESSIE_MODEL_PROVIDER numește adaptorul — Nessie are adaptoare compilate pentru openai, kimi și deepseek, iar orice altceva trece prin conectorul generic compatibil OpenAI. NESSIE_MODEL_NAME setează modelul implicit al implementării, pe care îl depășește selecția de model a fiecărui agent.
Embedding-urile sunt rutate separat, pentru că furnizorul de chat s-ar putea să nu le ofere deloc — DeepSeek, de exemplu, nu are endpoint pentru embedding-uri. Fiecare câmp NESSIE_EMBEDDING_* nesetat moștenește furnizorul de chat, deci o implementare care nu setează niciunul calculează embedding-urile prin endpoint-ul de chat. Setați NESSIE_EMBEDDING_PROVIDER, NESSIE_EMBEDDING_MODEL și, pe un proxy care rutează după serviciu, segmentul de serviciu, atunci când furnizorul de chat nu poate calcula embedding-uri. Greșeala aici eșuează discret, nu zgomotos: recuperarea din memorie și căutarea în baza de cunoștințe trec pe un canal lexical și își văd mai departe de treabă, iar în jurnal apare kb_search: query embedding failed.
Contabilizarea proprie a cheltuielilor Nessie — registrul de tokeni pe organizație și poarta de buget — nu depinde de endpoint-ul către care îl îndreptați și funcționează identic indiferent dacă atribuirea semnată per apel este configurată sau nu.
Autentificarea
Nessie autentifică persoanele prin UnlikeOtherAI, care administrează și structura organizațiilor și a echipelor. Pagina de autentificare din administrare afișează un singur buton „Login / Register”.
Integrarea este un flux bazat pe config-JWT, nu pe OIDC standard: API-ul servește un document de configurare JWT semnat RS256 la GET /api/auth/sso/config și JWKS-ul corespunzător la GET /.well-known/jwks.json, ambele pe gazda API-ului. Punerea în funcțiune este o secvență făcută o singură dată — generați o pereche de chei RSA-2048 și setați 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; apăsați o dată butonul, ceea ce deschide o cerere de integrare; așteptați aprobarea, care trimite un link de unică folosință la adresa de contact, cu secretul clientului; setați UOA_CLIENT_SECRET și reporniți API-ul. kid trebuie să fie unic pentru fiecare domeniu.
Punerea în spatele unui domeniu, cu TLS
O implementare servește trei nume: site-ul public, interfața de administrare și API-ul. Originile administrării și ale API-ului nu sunt interschimbabile. Dockerfile.admin încorporează VITE_API_BASE_URL în pachet la momentul construirii, deci fiecare artefact de administrare construit — inclusiv o versiune desktop care îl include — trebuie construit pentru originea API-ului. Dacă îl construiți pentru originea proprie a administrării, /api/auth/providers se rezolvă către shell-ul HTML al administrării, iar autentificarea rămâne blocată la „Loading providers...”.
Producția termină TLS la Caddy, care obține certificate pentru fiecare nume de gazdă prin HTTP-01 și nu are nevoie de un plugin de furnizor DNS pentru asta. Îndreptați proxy-ul către containerele de administrare și de site pe portul 80 și către API pe portul său intern.
Setați NESSIE_API_TRUSTED_PROXY_HOPS la numărul de proxy-uri din fața API-ului — 1 în spatele unui singur proxy invers. Implicit este 0, care ignoră complet X-Forwarded-For. Aceasta este o singură decizie cu două consecințe: ea stabilește și cheia limitelor de rată pentru autentificare, deci o valoare prea mică face ca fiecare client să împartă găleata proxy-ului, iar una prea mare permite unui client să-și falsifice propria adresă. Greșiți spre valori mici și măsurați.
Servirea fiecărei organizații și fiecărei echipe pe propriul subdomeniu este posibilă și dezactivată implicit. Este reglementată de NESSIE_TEAM_HOST_BASE_DOMAIN: dacă nu este setată, organizațiile nu sunt rutate deloc după numele gazdei, iar poarta de emitere a certificatelor, NESSIE_TLS_CHECK_KEY, refuză totul cât timp nu este setată — ceea ce este setarea corectă implicită pentru o implementare care nu folosește această funcție.
Verificări de sănătate
| Adresă | Întrebarea la care răspunde |
|---|---|
GET /api/health | Procesul este în funcțiune? Eșuează doar după ce a început golirea. |
GET /api/health/ready | Poate această replică să primească cereri noi? Aceasta este cea spre care trebuie îndreptat un echilibrator de sarcină. |
GET /api/ops/health | Cum merge implementarea? Un raport pentru o persoană — semnalele heartbeat ale worker-ilor, adâncimea cozii, sarcinile eșuate definitiv — nu o sondă. |
La SIGTERM, API-ul își încheie cererile în curs, în loc să se oprească pe loc. NESSIE_SHUTDOWN_TIMEOUT_MS limitează această golire și este implicit 25 de secunde; păstrați-o sub perioada de grație pe care o oferă orchestratorul containerului, altfel runtime-ul oprește procesul mai întâi și golirea nu folosește la nimic.
Actualizarea
O actualizare înseamnă prisma migrate deploy pe baza de date existentă, apoi datele inițiale (seed), apoi reconcile. Aceleași patru comenzi ca la prima implementare.
Calea este testată pe traseul pe care îl parcurge de fapt cine își găzduiește singur Nessie, nu pe o bază de date nouă: CI restaurează o copie luată cu douăzeci de migrări în urmă, o migrează până la versiunea curentă, validează schema și rulează o verificare de funcționare (smoke) pe tabelele de bază. Orice versiune din fereastra de compatibilitate de după ea este o sursă de actualizare dovedită. Folderele de migrare sunt imutabile după ce au fost înregistrate, iar un pas de lint oprește build-ul dacă unul dintre ele este redenumit, renumerotat, șters sau modificat, pentru că toate patru strică migrate deploy pentru o bază care a înregistrat deja rândul vechi.
O migrare eșuată blochează fiecare implementare care urmează. migrate deploy se oprește la primul eșec și refuză fiecare migrare ulterioară cu P3009, deci instalarea rămâne pe versiunea veche până când cineva o deblochează manual. Propria producție a proiectului a respins o zi întreagă fiecare implementare în acest fel. Deblocarea presupune confirmarea faptului că migrarea eșuată chiar s-a derulat înapoi — niciunul dintre obiectele pe care le creează nu ar trebui să existe, iar ce modifică ar trebui să fie încă în forma originală — și abia apoi marcarea ei drept derulată înapoi. O migrare aplicată pe jumătate nu este un candidat.
Backup-ul și restaurarea pentru implementarea Compose găzduită de dvs. nu sunt documentate în depozit, deci această pagină nu descrie o procedură pentru ele.
Pe o singură mașină sau într-un cloud administrat
Calea Docker Compose pe o singură gazdă, descrisă mai sus, este cea dovedită: aceasta rulează producția proprie a proiectului, pe un singur server, cu baza de date, stocarea de obiecte, API-ul, worker-ul, administrarea și site-ul ca containere în spatele unui proxy invers comun.
Trecerea dincolo de o singură mașină schimbă trei lucruri, toate impuse, nu doar recomandate. În afara modului local, Nessie rulează API-ul și worker-ul ca procese separate și în mai multe copii ale fiecăruia, deci orice capabilitate care presupune că un proces deține discul mașinii este refuzată: stocarea de obiecte pe sistemul de fișiere (eroare la pornire), șabloanele de mediu de execuție cu furnizorul docker (fără efect, iar provizionarea eșuează zgomotos) și instrumentele încorporate file_read, file_write și file_glob (un rezultat de instrument eșuat, pe care agentul îl poate citi). Fiecare refuz numește setarea, modul și ce să folosiți în schimb.
Pentru un cloud administrat, depozitul conține un arbore Terraform în infrastructure/terraform/, destinat Google Cloud: API-ul ca serviciu Cloud Run cu un minim de o instanță — ține un client persistent Postgres LISTEN și rulează sweep-uri de întreținere, deci scalarea la zero oprește livrarea în timp real — worker-ul ca pool de worker-i Cloud Run, pentru că nu leagă niciun port și un Service nu ar deveni niciodată gata, un job de migrare care condiționează fiecare lansare, Cloud SQL pentru PostgreSQL 17 și un bucket GCS accesat prin backend-ul compatibil S3.
Calea aceea este scrisă, dar nu a fost parcursă. Depozitul o spune direct, și o spune și această pagină: nu există un proiect Google Cloud pentru ea, nimic din arbore nu a fost planificat sau aplicat pe un proiect real, calea de încărcare multiparte către GCS nu este testată pe un bucket real, tipul de resursă pool de worker-i nu este validat, iar numărul de salturi de proxy de încredere din ea este o valoare provizorie, care trebuie măsurată, nu o măsurătoare. Administrarea și site-ul public nu sunt deloc modelate acolo. Tratați-o ca pe un punct de plecare pe care îl verificați, nu ca pe o implementare suportată.
Unde trăiesc datele unei implementări, ce păstrează software-ul și ce nu păstrează în mod deliberat face obiectul unei pagini separate: Realizat în UE și locul unde se află datele dvs..