Instalacja Nessie
Uruchom Nessie na infrastrukturze, którą wybierzesz: od jednej maszyny po zarządzaną chmurę.
Czego potrzeba przed rozpoczęciem
Nessie to monorepo pnpm złożone z usług Node oraz dwóch statycznych pakietów. Niezależnie od sposobu uruchomienia najpierw muszą istnieć te same cztery rzeczy.
- PostgreSQL z
pgvector. Nie jest opcjonalny i nie da się go zastąpić — migracje wykonująCREATE EXTENSION vectori tworzą kolumnyvector(1024). Produkcja działa napgvector/pgvector:pg17. - Node 22 i pnpm. Każdy Dockerfile w
infrastructure/docker/buduje się nanode:22-slim, a repozytorium przypina menedżera pakietów w głównympackage.json(pnpm@10.22.0). - Punkt końcowy modelu zgodny z OpenAI oraz klucz do niego. Przez niego przechodzą zarówno agenci, jak i embeddingi. Zobacz „Konfiguracja inferencji” poniżej.
- Magazyn obiektów zgodny z S3, w każdym wdrożeniu innym niż lokalne środowisko deweloperskie. Przesyłanie plików na lokalnym dysku jest odrzucane poza trybem
local, a takie odrzucenie kończy się błędem przy starcie, a nie uruchomieniem w ograniczonym trybie — zobacz „Jedna maszyna albo zarządzana chmura”.
Docker i odwrotny serwer proxy są potrzebne do ścieżki z kontenerami opisanej dalej, a domena i TLS do wszystkiego, co jest dostępne z internetu.
Wymagania sprzętowe — minimalna liczba procesorów, pamięć i dysk dla danej liczby osób — nie są podane nigdzie w repozytorium, więc ta strona też ich nie podaje.
Komponenty i sposób, w jaki współpracują
| Komponent | Czym jest | Dostęp z zewnątrz |
|---|---|---|
api/ | API HTTP/WebSocket oparte na Fastify. Każdy klient rozmawia wyłącznie z nim. | Tak |
worker/ | Orkiestracja agentów i zadania w tle. Odpytuje kolejkę zadań; nie otwiera żadnego portu. | Nie |
admin/ | Interfejs produktu — statyczny pakiet zbudowany przez Vite i serwowany przez nginx. | Tak |
web/ | Strona publiczna. Statyczna, opcjonalna i niezwiązana z uruchamianiem produktu. | Tak |
| PostgreSQL | Baza danych, kolejka zadań i magistrala czasu rzeczywistego, czyli wszystkie trzy funkcje naraz. | Nie |
| Magazyn obiektów | Załączniki, awatary i pliki bazy wiedzy. Zgodny z S3; w produkcji MinIO. | Nie |
gateway/ | Opcjonalny przekaźnik powiadomień push dla APNs i FCM. Wyłączony, dopóki nie zostanie świadomie włączony. | Tak |
API i worker to jeden obraz. Oba pakiety są ściśle powiązane, więc Dockerfile.app buduje cały zespół raz, a kontener workera nadpisuje polecenie na node worker/dist/index.js. W trybie local nie ma w ogóle kontenera workera: API uruchamia workera osadzonego we własnym procesie.
Nie ma Redis. Kolejka zadań i transport czasu rzeczywistego działają na Postgresie — z decyzji, a nie z zaniedbania. Drzewo Google Cloud usuwa moduły Pub/Sub i Redis z tego samego powodu.
Porty
W lokalnym środowisku deweloperskim API domyślnie działa na porcie 5454, a admin na 5455. Każdy checkout może przyjąć własną parę, ustawiając NESSIE_API_PORT i NESSIE_ADMIN_PORT w środowisku albo w pliku .env repozytorium; każde narzędzie w repozytorium, w tym zestawy testów przeglądarkowych, odczytuje te same dwie wartości. Wybierz parę przed startem i jej się trzymaj: przeniesienie działającej usługi na inny port sprawia, że admin rozmawia z niewłaściwym API.
Produkcja jest inna i ta zasada się na niej nie zmienia. Kontener API nasłuchuje wewnętrznie na porcie 5554 (przypięty przez NESSIE_API_PORT), a przekaźnik push na porcie 5556; oba działają za odwrotnym serwerem proxy.
Uruchamianie na jednej maszynie, w celach deweloperskich
Repozytorium zawiera plik Compose z samą bazą danych, co wystarcza lokalnej pętli pracy.
pnpm install
docker compose -f infrastructure/compose/docker-compose.yml up -d postgresTen kontener publikuje Postgresa na porcie 55432, a użytkownik, hasło i baza nazywają się wszystkie nessie. Ciąg połączenia umieść w pliku .env w katalogu głównym repozytorium — czyta go skrypt dev API — a następnie zastosuj migracje i zbuduj workera raz, ponieważ w trybie lokalnym API ładuje workera z jego zbudowanego katalogu dist:
DATABASE_URL=postgresql://nessie:nessie@localhost:55432/nessie
pnpm --filter @nessie/api prisma:migrate:deploy
pnpm --filter @nessie/worker build
pnpm devpnpm dev uruchamia API i admin równolegle, z hot reloadem, na portach 5454 i 5455. Sprawdź, czy API odpowiada na GET /api/health.
Postgres i wymóg pgvector
Trzy rozszerzenia tworzą same migracje — vector, pg_trgm i pgcrypto — więc rola, która je wykonuje, musi mieć uprawnienie do tworzenia rozszerzeń. W zarządzanym Postgresie to pierwsza rzecz do sprawdzenia.
Szerokość wektora jest częścią schematu, a nie ustawieniem. thoughts.embedding, thought_recalls.query_embedding i knowledge_page_chunks.embedding to kolumny vector(N), a N jest zapisane raz jako EMBEDDING_DIMENSIONS w packages/schemas/src/embedding.ts — obecnie 1024, natywna szerokość modelu osadzeń używanego w produkcji. Każde żądanie embeddingu wysyła tę szerokość, więc dostawca, który odpowiedziałby inną, zawiedzie głośno, zamiast zapisać wektory, których baza nie przyjmie. Przejście na model o innej szerokości wymaga zmiany tej stałej, napisania migracji, która zmienia typ trzech kolumn, i ponownego wyliczenia embeddingów: wektorów o różnych szerokościach nie da się przekonwertować, więc migracja zeruje je zamiast je obcinać.
Jeśli uruchamiasz Nessie obok innych aplikacji we wspólnej sieci Dockera, nadaj kontenerowi bazy danych odrębną nazwę. Usługa Compose projektu nazywa się nessie-postgres i nigdy postgres, ponieważ nazwa usługi staje się aliasem DNS we wspólnej sieci, a kolizja powoduje, że postgres:5432 rozdziela zapytania po kolei między dwie bazy danych.
Konfiguracja
Konfiguracja działania jest warstwowa: plik nessie.config.json montowany w trybie tylko do odczytu w API i workerze, a nad nim zmienne środowiskowe. Odwzorowanie między nimi to ConfigEnvMap w packages/config i to ono jest wiarygodną listą.
NESSIE_MODE wybiera model wdrożenia: local (domyślny i ścieżka dla deweloperów), hosted albo selfHosted — tego ustawia instalacja samodzielnie hostowana. Wyłącza logowanie deweloperskie i wymaga listy dozwolonych źródeł CORS.
To ustawienia, bez których wdrożenie selfHosted nie wystartuje albo nie będzie działać:
| Zmienna | Dlaczego |
|---|---|
DATABASE_URL / NESSIE_DB_URL | Ciąg połączenia z Postgresem. |
NESSIE_AUTH_SECRET | 32 bajty w postaci hex (openssl rand -hex 32). Podpisuje sesje i tokeny bootstrap. Start kończy się błędem bez tego poza trybem local; musi mieć tę samą wartość na każdej replice i między wdrożeniami, bo inaczej każda sesja wygasa przy każdym wydaniu. |
NESSIE_ENCRYPTION_KEY_RING, NESSIE_ENCRYPTION_ACTIVE_KEY_VERSION | Pierścień kluczy w formacie JSON oraz etykieta wskazująca aktywny klucz główny, wymagane poza trybem local. To osobny klucz główny względem sekretu podpisującego — pierścień szyfruje każdy trwały sekret, który Nessie przechowuje. Nie używaj jednego jako drugiego. |
NESSIE_STORAGE_PROVIDER | Musi mieć wartość s3. filesystem jest domyślny i jest odrzucany w trybach hosted i selfHosted, więc wdrożenie, które nie ustawia tu niczego, nie wystartuje, zamiast zapisywać przesyłane pliki na dysku, do którego nie dociera żaden inny kontener. |
NESSIE_CORS_ORIGINS | Origin panelu administracyjnego. Panel i API to osobne originy, a API musi umieścić na liście dozwolonych ten, który obsługuje. |
NESSIE_API_PUBLIC_URL, NESSIE_ADMIN_PUBLIC_URL | Służy do tworzenia URI przekierowań OAuth poza żądaniem HTTP. Bez pierwszego z nich API zgłasza błąd w czasie żądania. |
NESSIE_MODEL_PROVIDER, _BASE_URL, _API_KEY | Dokąd idzie inferencja. Zobacz niżej. |
infrastructure/compose/.env.prod.example zawiera dokładny kształt wszystkich tych zmiennych, z opcjonalnymi blokami — magazynem obiektów, sejfem sekretów, SSO i połączoną pocztą — zakomentowanymi w miejscu. Pełny katalog, łącznie z każdym opcjonalnym ustawieniem, znajduje się w docs/deployment/configuration.md.
Wdrażanie z Docker Compose
Skopiuj .env.prod.example do .env obok docker-compose.prod.yml i uzupełnij go, następnie uruchom bazę, zbuduj, wykonaj migracje i wystartuj usługi. Kolejność ma znaczenie: migracje i seedy uruchamiają się na bazie, która już działa, zanim jakakolwiek usługa zacznie obsługiwać ruch.
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 -dProdukcyjne Dockerfile uruchamiają lint każdego pakietu przed jego zbudowaniem, więc błąd lintu oznacza błąd budowania.
Krok reconcile jest obowiązkowy
reconcile wypełnia każdą organizację domyślnymi regułami polityki i uzupełnia brakujące przyznania narzędzi dla chronionych narzędzi MCP oraz asystenta osobistego. API robiło to kiedyś przy starcie na każdej replice; teraz tylko się łączy, nasłuchuje i nic więcej nie robi, więc bez tego kroku pierwsza organizacja nie ma reguł polityki i każde powiązanie agenta jest odrzucane. Krok jest idempotentny, więc uruchamianie go przy każdym wdrożeniu nic nie kosztuje — a aktualizacja wykonana ręcznie też musi go uruchomić.
Pierwsze konto właściciela
Świeża instalacja selfHosted nie ma żadnych użytkowników, więc API generuje jednorazowy token bootstrap i zapisuje w logu adres konfiguracji. Token znajduje się w Postgresie, a nie w procesie, więc każda replika zgadza się co do jego wartości.
docker compose -f infrastructure/compose/docker-compose.prod.yml logs nessie-api 2>&1 | grep bootstrapOtwórz https://<your-admin-host>/bootstrap?token=<token> i utwórz konto właściciela. Token wygasa po 15 minutach; aby wygenerować nowy, zrestartuj usługę API.
Jeśli skonfigurujesz SSO, w ogóle nie ma kroku bootstrap. Tryb bootstrap jest wyłączany w chwili włączenia zewnętrznego dostawcy uwierzytelniania — w przeciwnym razie przejąłby ekran logowania — a pierwsza osoba logująca się przez SSO tworzy domyślny zespół i zostaje jego właścicielem.
Konfiguracja inferencji
Czat jest zgodny z OpenAI. NESSIE_MODEL_BASE_URL to punkt końcowy, NESSIE_MODEL_API_KEY to token bearer, a NESSIE_MODEL_PROVIDER nazywa adapter — Nessie ma skompilowane adaptery dla openai, kimi i deepseek, a wszystko inne obsługuje ogólny łącznik zgodny z OpenAI. NESSIE_MODEL_NAME ustawia domyślny model wdrożenia, któremu wybór modelu przypisany do pojedynczego agenta wciąż ma pierwszeństwo.
Embeddingi są kierowane osobno, bo dostawca czatu może ich wcale nie obsługiwać — na przykład DeepSeek nie ma punktu końcowego embeddingów. Każde nieustawione pole NESSIE_EMBEDDING_* dziedziczy po dostawcy czatu, więc wdrożenie, które żadnego nie ustawi, generuje embeddingi przez punkt końcowy czatu. Ustaw NESSIE_EMBEDDING_PROVIDER, NESSIE_EMBEDDING_MODEL oraz — na proxy, które kieruje według usługi — segment usługi, gdy dostawca czatu nie może generować embeddingów. Błędna konfiguracja zawodzi po cichu, a nie głośno: pamięć przywoływania i wyszukiwanie w bazie wiedzy przechodzą na kanał leksykalny i działają dalej, zapisując w logach kb_search: query embedding failed.
Własne rozliczanie wydatków Nessie — rejestr tokenów na organizację i bramka budżetowa — nie zależy od tego, który punkt końcowy zostanie wskazany, i działa tak samo, niezależnie od tego, czy skonfigurowano podpisane przypisanie wywołań.
Logowanie
Nessie uwierzytelnia osoby przez UnlikeOtherAI, który zarządza też strukturą organizacji i zespołów. Strona logowania w panelu pokazuje jeden przycisk „Login / Register”.
Integracja działa jako przepływ config-JWT, a nie standardowe OIDC: API serwuje podpisany dokument konfiguracji RS256 pod GET /api/auth/sso/config oraz pasujący JWKS pod GET /.well-known/jwks.json, oba na hoście API. Uruchomienie to jednorazowa sekwencja: wygeneruj parę kluczy RSA-2048 i ustaw 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. Jednorazowe kliknięcie przycisku zgłasza prośbę o integrację; prośbę trzeba zatwierdzić, co wysyła na adres kontaktowy jednorazowy link zawierający sekret klienta. Następnie ustawia się UOA_CLIENT_SECRET i restartuje API. Wartość kid musi być unikalna dla każdej domeny.
Umieszczenie za domeną z TLS
Wdrożenie obsługuje trzy nazwy: stronę publiczną, panel administracyjny i API. Originy panelu i API nie są wymienne. Dockerfile.admin wkłada VITE_API_BASE_URL do pakietu w czasie budowania, więc każdy zbudowany artefakt panelu — w tym build desktopowy, który go osadza — musi być zbudowany względem originu API. Zbudowanie względem własnego originu panelu sprawia, że /api/auth/providers zwraca powłokę HTML panelu, a logowanie zawiesza się na „Loading providers...”.
Produkcja kończy TLS na Caddy, który uzyskuje certyfikaty dla poszczególnych nazw hostów przez HTTP-01 i do tego nie potrzebuje wtyczki dostawcy DNS. Skieruj proxy na kontenery panelu i strony na porcie 80, a na API na jego port wewnętrzny.
Ustaw NESSIE_API_TRUSTED_PROXY_HOPS na liczbę proxy przed API — 1 za pojedynczym odwrotnym proxy. Domyślnie wynosi 0, co całkowicie ignoruje X-Forwarded-For. To jedna decyzja z dwiema konsekwencjami: ustala też, według czego są liczone limity uwierzytelniania, więc przy zbyt niskiej wartości każdy klient dzieli kubełek proxy, a przy zbyt wysokiej klient może sfałszować własny adres. Lepiej zacząć od niskiej wartości i ją zmierzyć.
Obsługa każdej organizacji i zespołu pod własną subdomeną jest możliwa i domyślnie wyłączona. Steruje nią NESSIE_TEAM_HOST_BASE_DOMAIN: jeśli nie jest ustawiona, najemcy nie są w ogóle kierowani po nazwie hosta, a brama wydawania certyfikatów NESSIE_TLS_CHECK_KEY odrzuca wszystko, dopóki nie jest ustawiona — co jest właściwym domyślnym ustawieniem dla wdrożenia, które tej funkcji nie używa.
Sprawdzenia kondycji
| Punkt końcowy | Na jakie pytanie odpowiada |
|---|---|
GET /api/health | Czy ten proces działa? Zawodzi dopiero po rozpoczęciu wygaszania. |
GET /api/health/ready | Czy ta replika może przyjmować nowe żądania? To ten punkt końcowy, na który warto wskazać load balancer. |
GET /api/ops/health | Jak wdrożenie sobie radzi? Raport dla człowieka — heartbeaty workerów, długość kolejki, martwe zadania — a nie sonda. |
Po SIGTERM API wygasza się zamiast umierać w miejscu. NESSIE_SHUTDOWN_TIMEOUT_MS ogranicza to wygaszanie i domyślnie wynosi 25 sekund; utrzymuj ją poniżej okresu łaski, jaki orkiestrator daje kontenerowi, inaczej runtime zabije proces wcześniej i wygaszanie nic nie da.
Aktualizacja
Aktualizacja to prisma migrate deploy na istniejącej bazie, następnie seedy, a potem reconcile. Te same cztery polecenia co przy pierwszym wdrożeniu.
Ścieżka jest testowana dla trasy, którą naprawdę przechodzi użytkownik samodzielnego hostowania, a nie na świeżej bazie: CI przywraca migawkę pobraną dwadzieścia migracji wstecz, migruje ją do aktualnego head, weryfikuje schemat i uruchamia test dymny na podstawowych tabelach. Każde wydanie mieszczące się w tym ostatnim oknie jest sprawdzonym źródłem aktualizacji. Foldery migracji są niezmienne po zatwierdzeniu, a krok lintu przerywa budowanie, jeśli któryś zostanie przemianowany, przenumerowany, usunięty lub zmodyfikowany, bo wszystkie te operacje psują migrate deploy dla bazy, która już zapisała stary wiersz.
Jedna nieudana migracja blokuje każde następne wdrożenie. migrate deploy zatrzymuje się na pierwszym błędzie i odrzuca każdą późniejszą migrację kodem P3009, więc instalacja pozostaje na starym wydaniu, dopóki ktoś ręcznie tego nie wyczyści. Własna produkcja projektu przez cały dzień odrzucała w ten sposób każde wdrożenie. Wyczyszczenie wymaga potwierdzenia, że nieudana migracja naprawdę się wycofała — żaden z obiektów, które tworzy, nie powinien istnieć, a to, co zmienia, powinno mieć pierwotny kształt — i dopiero wtedy oznaczenia jej jako wycofanej. Migracja, która zastosowała się tylko częściowo, nie nadaje się do tego.
Kopia zapasowa i przywracanie dla wdrożenia Compose samodzielnie hostowanego nie są opisane w repozytorium, więc ta strona nie podaje dla nich procedury.
Jedna maszyna albo zarządzana chmura
Ścieżka Docker Compose na jednym hoście opisana wyżej jest tą sprawdzoną: na niej działa własna produkcja projektu, na jednym serwerze, z bazą danych, magazynem obiektów, API, workerem, panelem i stroną jako kontenerami za wspólnym odwrotnym proxy.
Skalowanie ponad jedną maszynę zmienia trzy rzeczy, wszystkie egzekwowane, a nie tylko zalecane. Poza trybem local Nessie uruchamia API i worker jako oddzielne procesy, w wielu kopiach każdego z nich, więc każda funkcja zakładająca, że jeden proces posiada dysk maszyny, jest odrzucana: magazyn obiektów na systemie plików (błąd przy starcie), szablony środowisk wykonawczych z dostawcą docker (bezczynne, a provisioning zawodzi głośno) oraz wbudowane narzędzia file_read, file_write i file_glob (nieudany wynik narzędzia, który agent może odczytać). Każda odmowa wskazuje ustawienie, tryb i to, czego użyć zamiast tego.
Dla zarządzanej chmury repozytorium zawiera drzewo Terraform w infrastructure/terraform/ przeznaczone dla Google Cloud: API jako usługę Cloud Run z minimum jedną instancją — trzyma stały klient LISTEN w Postgresie i wykonuje zadania konserwacyjne, więc skalowanie do zera zatrzymuje dostarczanie w czasie rzeczywistym — worker jako pulę workerów Cloud Run, ponieważ nie otwiera portu i zwykła usługa nigdy nie osiągnęłaby gotowości, zadanie migracji, które warunkuje każde wdrożenie, Cloud SQL for PostgreSQL 17 oraz bucket GCS dostępny przez backend zgodny z S3.
Ta ścieżka jest napisana, ale nie przetestowana na żywo. Repozytorium mówi o tym wprost i tak samo mówi ta strona: dla tej ścieżki nie istnieje projekt Google Cloud, nic w drzewie nie zostało zaplanowane ani zastosowane wobec prawdziwego projektu, ścieżka wieloczęściowego przesyłania do GCS nie była testowana na prawdziwym bucketcie, typ zasobu puli workerów nie został zweryfikowany, a liczba przeskoków zaufanego proxy jest tam placeholderem do zmierzenia, a nie pomiarem. Panel i publiczna strona w ogóle tam nie są modelowane. Traktuj to jako punkt wyjścia do weryfikacji, a nie jako wspierane wdrożenie.
Gdzie leżą dane wdrożenia, co oprogramowanie przechowuje, a czego świadomie nie przechowuje, opisuje osobna strona: Stworzone w UE i gdzie przechowywane są dane.