Installation af Nessie
Kør Nessie på den infrastruktur, du vælger, fra en enkelt maskine til en administreret sky.
Hvad du skal have, før du går i gang
Nessie er et pnpm-monorepo med Node-tjenester og to statiske bundles. Uanset hvordan du kører det, skal de samme fire ting være på plads først.
- PostgreSQL med
pgvector. Ikke valgfrit, og det kan ikke erstattes — migreringerne kørerCREATE EXTENSION vectorog oprettervector(1024)-kolonner. I produktion brugespgvector/pgvector:pg17. - Node 22 og pnpm. Alle Dockerfiles i
infrastructure/docker/bygger pånode:22-slim, og repositoriet låser sin pakkemanager i root-package.json(pnpm@10.22.0). - Et OpenAI-kompatibelt modelendpoint og en nøgle til det. Agenter og embeddings går begge igennem det. Se »Konfiguration af inferens« nedenfor.
- S3-kompatibel objektlagring, til enhver deployment, der ikke er den lokale udviklerinstallation. Uploads på lokal disk afvises uden for
local-tilstand, og den afvisning er en opstartsfejl og ikke en nedsat opstart — se »Én maskine eller en administreret cloud«.
Docker og en reverse proxy er nødvendige for containerstien, der beskrives længere nede, og et domæne plus TLS til alt, der kan nås fra internettet.
Hardwaredimensionering — minimum CPU, hukommelse og disk for et givet antal personer — står ikke noget sted i repositoriet, så denne side angiver det heller ikke.
Komponenterne, og hvordan de hænger sammen
| Komponent | Hvad det er | Indgang |
|---|---|---|
api/ | Den Fastify HTTP/WebSocket-API. Alle klienter taler med denne og ingen andre. | Ja |
worker/ | Agentorkestrering og baggrundskørsler. Poller job-køen; binder ingen port. | Nej |
admin/ | Produktgrænsefladen — en statisk bundle bygget med Vite og serveret af nginx. | Ja |
web/ | Den offentlige hjemmeside. Statisk, valgfri og uafhængig af, at produktet kører. | Ja |
| PostgreSQL | Databasen, job-køen og realtidsbussen — alle tre. | Nej |
| Objektlagring | Vedhæftede filer, avatarer og vidensbasefiler. S3-kompatibel; MinIO i produktion. | Nej |
gateway/ | Et valgfrit push-relæ til APNs og FCM. Slået fra, medmindre du aktiverer det bevidst. | Ja |
API'et og workeren er ét image. De to pakker er tæt forbundet, så Dockerfile.app bygger hele holdet én gang, og workercontaineren overskriver kommandoen til node worker/dist/index.js. I local-tilstand findes der slet ingen workercontainer: API'et kører workeren indlejret i sin egen proces.
Der er ingen Redis. Job-køen og realtidstransporten er begge baseret på Postgres — et bevidst valg og ikke en udeladelse. Google Cloud-træet sletter sine Pub/Sub- og Redis-moduler af samme grund.
Porte
I lokal udvikling er API'et som standard på 5454 og admin på 5455. Et checkout kan få sit eget par ved at sætte NESSIE_API_PORT og NESSIE_ADMIN_PORT, i miljøet eller i repositoriets .env; alt værktøj i repositoriet, browsertestsuiterne inklusive, læser de samme to værdier. Vælg parret, før du starter, og hold fast i det: flytter du en kørende tjeneste til en anden port, taler admin med det forkerte API.
Produktion er en anden sag og er uændret af den regel. API-containeren lytter internt på 5554 (fastsat med NESSIE_API_PORT), og push-relæet på 5556 — begge bag reverse proxyen.
Kør det på én maskine, til udvikling
Repositoriet indeholder en Compose-fil med kun en database i, og det er alt, den lokale udviklingsløkke har brug for.
pnpm install
docker compose -f infrastructure/compose/docker-compose.yml up -d postgresDen container publicerer Postgres på 55432, med bruger, adgangskode og database alle navngivet nessie. Læg forbindelsesstrengen i en .env i repositoriets rod — API'ets dev-script læser den — kør derefter migreringerne og byg workeren én gang, fordi API'et i lokal tilstand indlæser workeren fra sin byggede dist:
DATABASE_URL=postgresql://nessie:nessie@localhost:55432/nessie
pnpm --filter @nessie/api prisma:migrate:deploy
pnpm --filter @nessie/worker build
pnpm devpnpm dev kører API'et og admin parallelt med hot reload, på 5454 og 5455. Tjek, at API'et svarer på GET /api/health.
Postgres og kravet om pgvector
Tre udvidelser oprettes af selve migreringerne — vector, pg_trgm og pgcrypto — så den rolle, der kører dem, skal have lov til at oprette udvidelser. På en administreret Postgres er det det første, du skal tjekke.
Vektorbredden er en del af skemaet, ikke en indstilling. thoughts.embedding, thought_recalls.query_embedding og knowledge_page_chunks.embedding er vector(N)-kolonner, og N er angivet én gang som EMBEDDING_DIMENSIONS i packages/schemas/src/embedding.ts — lige nu 1024, den oprindelige bredde for den embeddingmodel, produktionen bruger. Hver embedding-forespørgsel sender den bredde, så en udbyder, der svarer med en anden, fejler højlydt i stedet for at skrive vektorer, som databasen afviser. Skifter du til en embeddingmodel med en anden bredde, skal du ændre den konstant, skrive en migrering, der retyper de tre kolonner, og lave re-embedding: vektorer med forskellige bredder kan ikke konverteres, så migreringen nuller dem i stedet for at trunkere dem.
Kører du Nessie ved siden af andre applikationer på et delt Docker-netværk, skal dens databasecontainer have et særskilt navn. Projektets egen Compose-tjeneste hedder nessie-postgres og aldrig postgres, fordi tjenestenavnet bliver et DNS-alias på det delte netværk, og en kollision gør, at postgres:5432 skiftevis peger på to databaser.
Konfiguration
Runtime-konfigurationen er lagdelt: en nessie.config.json-fil, der er monteret skrivebeskyttet i API'et og workeren, med miljøvariabler ovenpå. Sammenhængen mellem de to er ConfigEnvMap i packages/config, som er den autoritative liste.
NESSIE_MODE vælger deployment-formen: local (standard, og udviklerstien), hosted eller selfHosted — hvilket en selvhostet installation sætter. Den slår dev-login fra og kræver en CORS-allowlist.
Det er de indstillinger, en selfHosted-deployment ikke vil starte med, eller ikke vil fungere uden:
| Variabel | Hvorfor |
|---|---|
DATABASE_URL / NESSIE_DB_URL | Postgres-forbindelsesstrengen. |
NESSIE_AUTH_SECRET | 32 bytes hex (openssl rand -hex 32). Signerer sessioner og bootstrap-tokens. Opstarten fejler uden den uden for local-tilstand, og den skal være den samme værdi på hver replika og på tværs af deployments, ellers dør alle sessioner ved hver udgivelse. |
NESSIE_ENCRYPTION_KEY_RING, NESSIE_ENCRYPTION_ACTIVE_KEY_VERSION | En JSON-nøglering og den etiket, der vælger den aktive rod, påkrævet uden for local-tilstand. Dette er en separat rod fra signeringshemmeligheden — nøgleringen krypterer hver varig hemmelighed, Nessie gemmer. Brug ikke den ene som den anden. |
NESSIE_STORAGE_PROVIDER | Skal være s3. filesystem er standard og afvises i hosted- og selfHosted-tilstand, så en deployment, der ikke sætter noget her, fejler ved opstart i stedet for at skrive uploads til en disk, ingen anden container kan læse. |
NESSIE_CORS_ORIGINS | Admin-origin. Admin og API er separate origins, og API'et skal have den på sin allowlist, som det serverer. |
NESSIE_API_PUBLIC_URL, NESSIE_ADMIN_PUBLIC_URL | Bruges til at udstede OAuth-redirect-URI'er uden for en HTTP-forespørgsel. API'et kaster en fejl på forespørgselstidspunktet uden den første. |
NESSIE_MODEL_PROVIDER, _BASE_URL, _API_KEY | Hvor inferensen går hen. Se nedenfor. |
infrastructure/compose/.env.prod.example har den nøjagtige form for alle dem, med de valgfri blokke — objektlagring, hemmelighedsvault, SSO, tilsluttet mail — kommenteret ind på deres plads. Den fulde katalog, inklusive hver valgfri indstilling, er docs/deployment/configuration.md.
Deployment med Docker Compose
Kopiér .env.prod.example til .env ved siden af docker-compose.prod.yml og udfyld den, og start derefter databasen, byg, migrer og start. Rækkefølgen betyder noget: migreringerne og seeds kører mod en database, der allerede er oppe, før nogen tjeneste svarer.
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 -dProduktions-Dockerfiles kører hver pakkes lint, før de bygger den, så en lint-fejl er en build-fejl.
Reconcile-trinnet er ikke valgfrit
reconcile seeder hver organisations standardpolitikregler og udfylder de beskyttede MCP- og Personal Assistant-værktøjstilladelser. API'et gjorde dette ved opstart på hver replika; nu forbinder, lytter og gør det ikke andet, så uden dette trin har den første organisation ingen politikregler, og hver agentbinding afvises. Det er idempotent, så det er gratis at køre ved hver deployment — og en opgradering, der er udført manuelt, skal også køre det.
Den første ejerkonto
En frisk selfHosted-installation har ingen brugere, så API'et udsteder et engangs-bootstrap-token og logger en opsætningsadresse. Tokenet ligger i Postgres og ikke i en proces, så alle replikaer er enige om det.
docker compose -f infrastructure/compose/docker-compose.prod.yml logs nessie-api 2>&1 | grep bootstrapÅbn https://<your-admin-host>/bootstrap?token=<token> og opret ejerkontoen. Tokenet udløber efter 15 minutter; genstart API-tjenesten for at udstede et nyt.
Hvis du opsætter SSO, er der slet intet bootstrap-trin. Bootstrap-tilstanden undertrykkes i det øjeblik, en ekstern login-udbyder er aktiveret — ellers ville den kapre login-skærmen — og den første person, der logger ind via SSO, opretter standardteamet og bliver dets ejer.
Konfiguration af inferens
Chat er OpenAI-kompatibel. NESSIE_MODEL_BASE_URL er endpointet, NESSIE_MODEL_API_KEY bearer-nøglen, og NESSIE_MODEL_PROVIDER navngiver adapteren — Nessie har kompilerede adaptere til openai, kimi og deepseek, og når alt andet gennem den generiske OpenAI-kompatible connector. NESSIE_MODEL_NAME sætter deploymentets standardmodel, som en enkelt agents egen modelvalg stadig har forrang over.
Embeddings er routet separat, fordi chatudbyderen måske slet ikke leverer dem — DeepSeek har for eksempel intet embedding-endpoint. Hvert ikke-angivet NESSIE_EMBEDDING_*-felt arver chatudbyderen, så en deployment, der ikke sætter nogen af dem, laver embeddings via chat-endpointet. Sæt NESSIE_EMBEDDING_PROVIDER, NESSIE_EMBEDDING_MODEL og — på en proxy, der router efter tjeneste — tjenestesegmentet, når chatudbyderen ikke kan lave embeddings. At få det forkert fejler stille i stedet for højlydt: hukommelsesgenkaldelse og vidensbasesøgning degraderer til en leksikal kanal og fortsætter, og logger kb_search: query embedding failed undervejs.
Nessies egen forbrugsbogføring — token-ledgeret pr. organisation og budgetspærren — er upåvirket af, hvilket endpoint du peger den på, og virker ens, uanset om signeret attribution pr. kald er konfigureret eller ej.
Log ind
Nessie autentificerer personer via UnlikeOtherAI, som også administrerer organisations- og teamstrukturen. Admin-loginsiden viser en enkelt knap med »Login / Register«.
Integrationen er en config-JWT-flow snarere end standard OIDC: API'et serverer et signeret RS256-konfigurationsdokument på GET /api/auth/sso/config og det tilsvarende JWKS på GET /.well-known/jwks.json, begge på API-værten. Opsætningen er et engangsforløb — generer et RSA-2048-nøglepar og sæt UOA_CONFIG_JWT_PRIVATE_KEY_B64, UOA_CONFIG_JWT_KID, UOA_DOMAIN, UOA_CONFIG_URL, UOA_JWKS_URL, UOA_REDIRECT_URL og UOA_CONTACT_EMAIL; klik på knappen én gang, hvilket opretter en integrationsforespørgsel; få den godkendt, hvilket sender et engangslink til kontaktadressen med klienthemmeligheden; sæt UOA_CLIENT_SECRET og genstart API'et. kid skal være unik pr. domæne.
At sætte den bag et domæne, med TLS
En deployment serverer tre navne: den offentlige side, admin og API'et. Admin- og API-origins er ikke udskiftelige. Dockerfile.admin indbygger VITE_API_BASE_URL i bundlen på build-tidspunktet, så hver bygget admin-artefakt — inklusive en desktop-build, der indlejrer den — skal bygges mod API-origin. Bygger du den mod adminens egen origin, løses /api/auth/providers til admin-HTML-skallen, og login hænger fast på »Loading providers...«.
I produktion afslutter TLS hos Caddy, som henter certifikater pr. værtsnavn over HTTP-01 og ikke kræver et DNS-udbyder-plugin til det. Peg proxyen mod admin- og web-containerne på port 80 og mod API'et på dets interne port.
Sæt NESSIE_API_TRUSTED_PROXY_HOPS til antallet af proxyer foran API'et — 1 bag en enkelt reverse proxy. Standarden er 0, som ignorerer X-Forwarded-For helt. Det er én beslutning med to konsekvenser: den styrer også autentificeringens rate limits, så er den for lav, deler alle klienter proxyens bucket, og er den for høj, kan en klient forfalske sin egen adresse. Vælg lavt, og mål det.
At servere hver organisation og hvert team på sit eget subdomæne er muligt og slået fra som standard. Det styres af NESSIE_TEAM_HOST_BASE_DOMAIN; er den ikke sat, routes lejere slet ikke efter værtsnavn, og certifikatudstedelsesgaten NESSIE_TLS_CHECK_KEY afviser alt, mens den ikke er sat — hvilket er den rigtige standard for en deployment, der ikke bruger funktionen.
Sundhedstjek
| Endpoint | Spørgsmål, det besvarer |
|---|---|
GET /api/health | Er denne proces i live? Fejler kun, når den er begyndt at dræne. |
GET /api/health/ready | Må denne replika tage nye forespørgsler? Det er den, du skal pege en load balancer mod. |
GET /api/ops/health | Hvordan har deployment det? En rapport til en person — worker-heartbeats, kølængde, døde jobs — ikke en probe. |
Ved SIGTERM dræner API'et i stedet for at dø, hvor det står. NESSIE_SHUTDOWN_TIMEOUT_MS begrænser dræningen og er som standard 25 sekunder. Hold den under den nedlukningsfrist, din orkestrator giver containeren, ellers dræber runtime'en processen først, og dræningen gør ingen forskel.
Opgradering
En opgradering er prisma migrate deploy mod den eksisterende database, derefter seeds, derefter reconcile. De samme fire kommandoer som ved den første deployment.
Stien er testet for den vej, en selvhoster faktisk tager, snarere end mod en frisk database: CI gendanner et snapshot taget tyve migreringer bagud, migrerer det til det aktuelle head, validerer skemaet og kører et røgtest over kernetabellerne. Enhver udgivelse inden for det efterfølgende vindue er en bevist opgraderingskilde. Migreringsmapper er uforanderlige, når de er committet, og et lint-trin fejler buildet, hvis en bliver omdøbt, omnummereret, slettet eller ændret, fordi alle fire ødelægger migrate deploy for en database, der allerede har registreret den gamle række.
Én fejlet migrering sætter alle efterfølgende deployments på pause. migrate deploy stopper ved den første fejl og afviser hver senere migrering med P3009, så installationen bliver på den gamle udgivelse, indtil nogen rydder op manuelt. Projektets egen produktion brugte en dag på at afvise hver deployment på den måde. At rydde op betyder at bekræfte, at den fejlede migrering faktisk blev rullet tilbage — ingen af de objekter, den opretter, må findes, og alt, den ændrer, skal stadig have sin oprindelige form — og først derefter markere den som rullet tilbage. En migrering, der er halvt anvendt, er ikke en kandidat.
Backup og gendannelse for den selvhostede Compose-deployment er ikke dokumenteret i repositoriet, så denne side beskriver heller ikke en procedure for det.
Én maskine eller en administreret cloud
Den enkelte Docker Compose-vært ovenfor er den, der er bevist: det er det, projektets egen produktion kører, på en enkelt server, med database, objektlagring, API, worker, admin og side som containere bag en delt reverse proxy.
At skalere ud over én maskine ændrer tre ting, og alle tre håndhæves frem for blot anbefales. Uden for local-tilstand kører Nessie API'et og workeren som separate processer og i flere kopier af hver, så enhver funktion, der forudsætter, at én proces ejer maskinens disk, afvises: filsystembaseret objektlagring (en opstartsfejl), execution-environment-skabeloner med docker-udbyderen (inaktive, og provisionering fejler højlydt), og de indbyggede værktøjer file_read, file_write og file_glob (et fejlet værktøjsresultat, som agenten kan læse). Hver afvisning nævner indstillingen, tilstanden og hvad du skal bruge i stedet.
Til en administreret cloud indeholder repositoriet et Terraform-træ i infrastructure/terraform/, rettet mod Google Cloud: API'et som en Cloud Run-tjeneste med minimum én instans — det holder en vedvarende Postgres-LISTEN-klient og kører vedligeholdelsessweeps, så skalering til nul stopper realtidslevering — workeren som en Cloud Run worker pool, fordi den ikke binder nogen port, og en Service aldrig ville blive klar, et migreringsjob, der styrer hver udrulning, Cloud SQL for PostgreSQL 17, og en GCS-bucket nået gennem den S3-kompatible backend.
Den sti er skrevet, ikke gennemgået. Repositoriet siger det klart, og det gør denne side også: der findes intet Google Cloud-projekt til den, intet i træet er planlagt eller anvendt mod et rigtigt projekt, multipart-uploadstien til GCS er utestet mod en rigtig bucket, worker-pool-ressourcetypen er uvalideret, og antallet af betroede proxy-hops deri er en pladsholder, der skal måles, ikke en måling. Admin og den offentlige side er slet ikke modelleret der. Behandl det som et udgangspunkt, du verificerer, ikke som en understøttet deployment.
Hvor en deployments data ligger, hvad softwaren holder, og hvad den bevidst ikke holder, står på en separat side: EU-udviklet, og hvor dine data ligger.