Memasang Nessie
Jalankan Nessie di infrastruktur pilihan Anda, dari satu mesin hingga layanan cloud terkelola.
Yang perlu disiapkan sebelum memulai
Nessie adalah monorepo pnpm berisi layanan Node serta dua bundel statis. Apa pun cara menjalankannya, empat hal berikut harus sudah ada lebih dulu.
- PostgreSQL dengan
pgvector. Tidak opsional, dan tidak bisa diganti — migrasi menjalankanCREATE EXTENSION vectordan membuat kolomvector(1024). Produksi menjalankanpgvector/pgvector:pg17. - Node 22 dan pnpm. Setiap Dockerfile di
infrastructure/docker/dibangun di atasnode:22-slim, dan repositori mengunci versi pengelola paketnya dipackage.jsonroot (pnpm@10.22.0). - Endpoint model yang kompatibel dengan OpenAI beserta kuncinya. Agen dan embedding sama-sama melewatinya. Lihat “Mengonfigurasi inferensi” di bawah.
- Penyimpanan objek yang kompatibel dengan S3, untuk deployment apa pun yang bukan deployment pengembangan lokal. Unggahan di disk lokal ditolak di luar mode
local, dan penolakan itu berupa kegagalan saat start, bukan boot dengan fungsi terbatas — lihat “Satu mesin, atau cloud terkelola”.
Docker dan reverse proxy diperlukan untuk jalur container yang dijelaskan di bagian bawah, dan domain beserta TLS untuk apa pun yang dapat diakses dari internet.
Ukuran perangkat keras — CPU, memori, dan disk minimum untuk jumlah orang tertentu — tidak tercantum di mana pun dalam repositori, jadi halaman ini juga tidak mencantumkannya.
Komponen-komponennya, dan cara kerjanya bersama
| Komponen | Apa itu | Ingress |
|---|---|---|
api/ | API HTTP/WebSocket berbasis Fastify. Setiap klien berbicara dengan API ini dan tidak dengan yang lain. | Ya |
worker/ | Orkestrasi agen dan eksekusi latar belakang. Melakukan polling ke antrean job; tidak membuka port. | Tidak |
admin/ | Antarmuka produk — bundel statis yang dibangun oleh Vite dan disajikan oleh nginx. | Ya |
web/ | Situs publik. Statis, opsional, dan tidak berkaitan dengan menjalankan produk. | Ya |
| PostgreSQL | Basis data, antrean job, dan bus realtime — ketiganya sekaligus. | Tidak |
| Penyimpanan objek | Lampiran, avatar, dan berkas basis pengetahuan. Kompatibel dengan S3; di produksi menggunakan MinIO. | Tidak |
gateway/ | Relay push opsional untuk APNs dan FCM. Nonaktif kecuali Anda mengaktifkannya dengan sengaja. | Ya |
API dan worker adalah satu image. Kedua paket ini saling terkait erat, sehingga Dockerfile.app membangun seluruh tim sekali, dan container worker menimpa perintahnya menjadi node worker/dist/index.js. Dalam mode local tidak ada container worker sama sekali: API menjalankan worker secara tertanam di dalam prosesnya sendiri.
Tidak ada Redis. Antrean job maupun transport realtime sama-sama berbasis Postgres. Itu keputusan yang disengaja, bukan karena terlewat — pohon Google Cloud menghapus modul Pub/Sub dan Redis-nya dengan alasan yang sama.
Port
Dalam pengembangan lokal, secara bawaan API berada di 5454 dan admin di 5455. Sebuah checkout dapat memakai pasangan port sendiri dengan menetapkan NESSIE_API_PORT dan NESSIE_ADMIN_PORT, di lingkungan atau di .env repositori; setiap alat di repositori, termasuk rangkaian uji browser, membaca dua nilai yang sama. Pilih pasangannya sebelum memulai dan pertahankan: memindahkan layanan yang sedang berjalan ke port lain membuat admin berbicara dengan API yang salah.
Produksi berbeda, dan aturan itu tidak berubah di sana. Container API mendengarkan secara internal di 5554 (dipatok dengan NESSIE_API_PORT) dan relay push di 5556, keduanya di belakang reverse proxy.
Menjalankan di satu mesin, untuk pengembangan
Repositori menyertakan berkas Compose yang hanya berisi basis data, dan itulah satu-satunya yang dibutuhkan loop lokal.
pnpm install
docker compose -f infrastructure/compose/docker-compose.yml up -d postgresContainer itu mempublikasikan Postgres di 55432 dengan user, password, dan basis data yang semuanya bernama nessie. Letakkan connection string di .env di root repositori — skrip dev API membacanya — lalu terapkan migrasi dan bangun worker sekali, karena dalam mode lokal API memuat worker dari dist hasil build:
DATABASE_URL=postgresql://nessie:nessie@localhost:55432/nessie
pnpm --filter @nessie/api prisma:migrate:deploy
pnpm --filter @nessie/worker build
pnpm devpnpm dev menjalankan API dan admin secara paralel dengan hot reload, di port 5454 dan 5455. Pastikan API menjawab dengan GET /api/health.
Postgres, dan syarat pgvector
Tiga ekstensi dibuat oleh migrasi itu sendiri — vector, pg_trgm, dan pgcrypto — sehingga peran yang menjalankannya harus diizinkan membuat ekstensi. Pada Postgres terkelola, itulah yang perlu diperiksa sebelum hal lain.
Lebar vektor adalah bagian dari skema, bukan pengaturan. thoughts.embedding, thought_recalls.query_embedding, dan knowledge_page_chunks.embedding adalah kolom vector(N), dan N dinyatakan sekali sebagai EMBEDDING_DIMENSIONS di packages/schemas/src/embedding.ts — saat ini 1024, lebar asli dari model embedding yang dipakai produksi. Setiap permintaan embed mengirim lebar itu, sehingga penyedia yang menjawab dengan lebar lain akan gagal dengan jelas, alih-alih menulis vektor yang ditolak basis data. Berpindah ke model embedding dengan lebar berbeda berarti mengubah konstanta itu, menulis migrasi yang mengetik ulang ketiga kolom, lalu meng-embed ulang: vektor dengan lebar berbeda tidak dapat dikonversi, sehingga migrasi mengosongkannya (null) alih-alih memotongnya.
Jika Anda menjalankan Nessie berdampingan dengan aplikasi lain di jaringan Docker bersama, beri container basis datanya nama yang berbeda. Service Compose proyek ini bernama nessie-postgres dan tidak pernah postgres, karena nama service menjadi alias DNS di jaringan bersama, dan tabrakan membuat postgres:5432 bergantian secara round-robin di antara dua basis data.
Konfigurasi
Konfigurasi runtime berlapis: berkas nessie.config.json dipasang secara read-only ke API dan worker, dengan variabel lingkungan di atasnya. Pemetaan antara keduanya adalah ConfigEnvMap di packages/config, dan itulah daftar resminya.
NESSIE_MODE memilih bentuk deployment: local (default, dan jalur pengembang), hosted, atau selfHosted — yang diatur oleh instalasi hosting sendiri. Mode ini menonaktifkan login pengembang dan mewajibkan daftar izin CORS.
Ini adalah pengaturan yang tanpanya deployment selfHosted tidak akan berjalan, atau tidak akan berfungsi:
| Variabel | Alasan |
|---|---|
DATABASE_URL / NESSIE_DB_URL | Connection string Postgres. |
NESSIE_AUTH_SECRET | 32 byte dalam format hex (openssl rand -hex 32). Menandatangani sesi dan token bootstrap. Start-up gagal tanpanya di luar mode local, dan nilainya harus sama di setiap replika dan di seluruh deployment, atau setiap sesi berakhir pada setiap rilis. |
NESSIE_ENCRYPTION_KEY_RING, NESSIE_ENCRYPTION_ACTIVE_KEY_VERSION | Key ring JSON dan label yang memilih root aktif, wajib di luar mode local. Ini adalah root terpisah dari secret penandatanganan — key ring ini mengenkripsi setiap secret permanen yang disimpan Nessie. Jangan gunakan salah satunya sebagai pengganti yang lain. |
NESSIE_STORAGE_PROVIDER | Harus s3. filesystem adalah default dan ditolak dalam mode hosted dan selfHosted, sehingga deployment yang tidak mengatur apa pun di sini gagal dijalankan, alih-alih menulis unggahan ke disk yang tidak bisa dibaca container lain. |
NESSIE_CORS_ORIGINS | Origin admin. Admin dan API adalah origin yang terpisah, dan API harus memasukkan dalam daftar izin origin yang dilayaninya. |
NESSIE_API_PUBLIC_URL, NESSIE_ADMIN_PUBLIC_URL | Digunakan untuk membuat URI redirect OAuth di luar permintaan HTTP. API melempar error saat ada permintaan jika variabel pertama tidak diatur. |
NESSIE_MODEL_PROVIDER, _BASE_URL, _API_KEY | Ke mana inferensi dikirim. Lihat di bawah. |
infrastructure/compose/.env.prod.example memiliki bentuk persis dari semuanya, dengan blok opsional — penyimpanan objek, brankas secret, SSO, dan email terhubung — diberi komentar di tempatnya. Katalog lengkap, termasuk setiap pengaturan opsional, ada di docs/deployment/configuration.md.
Deployment dengan Docker Compose
Salin .env.prod.example menjadi .env di sebelah docker-compose.prod.yml dan isi, lalu jalankan basis data, build, migrasi, dan start. Urutannya penting: migrasi dan seed dijalankan terhadap basis data yang sudah berjalan, sebelum layanan mana pun melayani.
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 produksi menjalankan lint setiap paket sebelum membangunnya, sehingga kegagalan lint berarti kegagalan build.
Langkah reconcile tidak opsional
reconcile menanam aturan kebijakan default setiap organisasi dan melengkapi grant alat MCP yang dilindungi serta Personal Assistant yang belum ada. API dulu melakukan ini saat boot di setiap replika; sekarang API hanya terhubung, mendengarkan, dan tidak melakukan apa pun lagi, sehingga tanpa langkah ini, organisasi pertama tidak memiliki aturan kebijakan dan setiap bind agen akan ditolak. Langkah ini idempoten, sehingga menjalankannya di setiap deployment tidak memakan biaya — dan upgrade yang diterapkan secara manual juga harus menjalankannya.
Akun pemilik pertama
Instalasi selfHosted yang baru tidak memiliki pengguna, sehingga API membuat satu token bootstrap sekali pakai dan mencatat URL pengaturan. Token itu disimpan di Postgres, bukan di dalam proses, sehingga setiap replika sepakat tentangnya.
docker compose -f infrastructure/compose/docker-compose.prod.yml logs nessie-api 2>&1 | grep bootstrapBuka https://<your-admin-host>/bootstrap?token=<token> dan buat akun pemilik. Token berakhir setelah 15 menit; mulai ulang layanan API untuk membuat token baru.
Jika Anda mengonfigurasi SSO, tidak ada langkah bootstrap sama sekali. Mode bootstrap dinonaktifkan begitu penyedia autentikasi eksternal diaktifkan — jika tidak, mode ini akan membajak layar login — dan orang pertama yang masuk melalui SSO akan membuat tim default dan menjadi pemiliknya.
Mengonfigurasi inferensi
Chat kompatibel dengan OpenAI. NESSIE_MODEL_BASE_URL adalah endpoint, NESSIE_MODEL_API_KEY bearer-nya, dan NESSIE_MODEL_PROVIDER menamai adapter — Nessie menyertakan adapter untuk openai, kimi, dan deepseek, dan menjangkau yang lain melalui konektor umum yang kompatibel dengan OpenAI. NESSIE_MODEL_NAME mengatur model default deployment, yang tetap lebih diutamakan daripada pilihan model masing-masing agen.
Embedding dirutekan secara terpisah, karena penyedia chat mungkin sama sekali tidak melayaninya — DeepSeek, misalnya, tidak memiliki endpoint embedding. Setiap field NESSIE_EMBEDDING_* yang tidak diatur mewarisi penyedia chat, sehingga deployment yang tidak mengatur satu pun dari keduanya akan melakukan embedding lewat endpoint chat. Atur NESSIE_EMBEDDING_PROVIDER, NESSIE_EMBEDDING_MODEL dan, pada proxy yang merutekan berdasarkan layanan, segmen layanannya, saat penyedia chat tidak dapat melakukan embedding. Jika keliru, kegagalannya tidak terdengar: recall memori dan pencarian basis pengetahuan turun ke kanal leksikal dan tetap berjalan, sambil mencatat kb_search: query embedding failed.
Akuntansi pengeluaran Nessie sendiri — buku besar token per organisasi dan gerbang anggaran — tidak terpengaruh oleh endpoint mana pun yang Anda arahkan, dan bekerja sama persis baik atribusi per panggilan yang ditandatangani dikonfigurasi maupun tidak.
Masuk
Nessie mengautentikasi orang melalui UnlikeOtherAI, yang juga mengelola struktur organisasi dan tim. Halaman login admin menampilkan satu tombol “Login / Register”.
Integrasinya adalah alur config-JWT, bukan OIDC standar: API menyajikan dokumen konfigurasi RS256 yang ditandatangani di GET /api/auth/sso/config dan JWKS yang sesuai di GET /.well-known/jwks.json, keduanya di host API. Menyiapkannya adalah urutan sekali jalan — buat keypair RSA-2048 dan atur UOA_CONFIG_JWT_PRIVATE_KEY_B64, UOA_CONFIG_JWT_KID, UOA_DOMAIN, UOA_CONFIG_URL, UOA_JWKS_URL, UOA_REDIRECT_URL, dan UOA_CONTACT_EMAIL; klik tombol sekali, yang akan mengajukan permintaan integrasi; setujui permintaan itu, yang akan mengirim tautan sekali pakai ke alamat kontak berisi client secret; lalu atur UOA_CLIENT_SECRET dan mulai ulang API. kid harus unik per domain.
Menempatkan di balik domain, dengan TLS
Deployment melayani tiga nama: situs publik, admin, dan API. Origin admin dan origin API tidak dapat saling menggantikan. Dockerfile.admin menanamkan VITE_API_BASE_URL ke dalam bundle saat build, sehingga setiap artefak admin yang dibangun — termasuk build desktop yang menyematkannya — harus dibangun terhadap origin API. Jika dibangun terhadap origin admin sendiri, /api/auth/providers akan mengarah ke shell HTML admin, dan login macet di “Loading providers...”.
Produksi mengakhiri TLS di Caddy, yang memperoleh sertifikat per hostname melalui HTTP-01 dan tidak memerlukan plugin penyedia DNS untuk itu. Arahkan proxy ke container admin dan web di port 80, dan ke API di port internalnya.
Atur NESSIE_API_TRUSTED_PROXY_HOPS ke jumlah proxy di depan API — 1 di belakang satu reverse proxy. Defaultnya 0, yang mengabaikan X-Forwarded-For sepenuhnya. Ini satu keputusan dengan dua akibat: nilai ini juga yang menentukan batas laju autentikasi, sehingga jika terlalu rendah setiap klien berbagi bucket proxy, dan jika terlalu tinggi klien dapat memalsukan alamatnya sendiri. Pilih yang rendah lalu ukur.
Melayani setiap organisasi dan tim di subdomainnya sendiri dimungkinkan dan nonaktif secara default. Ini diatur oleh NESSIE_TEAM_HOST_BASE_DOMAIN; jika tidak diatur, tenant tidak dirutekan berdasarkan hostname sama sekali. Gerbang penerbitan sertifikat NESSIE_TLS_CHECK_KEY menolak semuanya selama tidak diatur — yang merupakan default yang tepat untuk deployment yang tidak memakai fitur ini.
Pemeriksaan kesehatan
| Endpoint | Pertanyaan yang dijawabnya |
|---|---|
GET /api/health | Apakah proses ini masih hidup? Hanya gagal setelah mulai dikosongkan. |
GET /api/health/ready | Bolehkah replika ini menerima permintaan baru? Inilah yang sebaiknya dijadikan target load balancer. |
GET /api/ops/health | Bagaimana kondisi deployment? Laporan untuk manusia — heartbeat worker, kedalaman antrean, job yang mati — bukan probe. |
Pada SIGTERM, API mengosongkan antrean (drain) alih-alih mati di tempatnya. NESSIE_SHUTDOWN_TIMEOUT_MS membatasi drain itu dan defaultnya 25 detik; pastikan nilainya di bawah grace period apa pun yang diberikan orkestrator kepada container, atau runtime akan mematikan proses lebih dulu dan drain tidak ada gunanya.
Upgrade
Upgrade adalah prisma migrate deploy terhadap basis data yang sudah ada, lalu seed, lalu reconcile. Empat perintah yang sama dengan deployment pertama.
Jalur ini diuji untuk rute yang benar-benar ditempuh oleh orang yang menjalankan Nessie di hosting sendiri, bukan terhadap basis data baru: CI memulihkan snapshot yang diambil dua puluh migrasi di belakang, memigrasikannya ke head terkini, memvalidasi skema, dan menjalankan pemeriksaan asap pada tabel inti. Rilis mana pun di dalam jendela itu adalah sumber upgrade yang sudah terbukti. Folder migrasi bersifat tetap begitu di-commit, dan langkah lint menggagalkan build jika salah satunya diganti nama, diberi nomor ulang, dihapus, atau diubah, karena semuanya merusak migrate deploy untuk basis data yang sudah mencatat baris lama.
Satu migrasi yang gagal menghentikan setiap deployment setelahnya. migrate deploy berhenti pada kegagalan pertama dan menolak setiap migrasi berikutnya dengan P3009, sehingga instalasi tetap berada di rilis lama sampai seseorang membersihkannya secara manual. Produksi proyek ini sendiri pernah menghabiskan satu hari menolak setiap deployment dengan cara ini. Membersihkannya berarti memastikan migrasi yang gagal benar-benar sudah di-rollback — tidak boleh ada objek yang dibuatnya, dan apa pun yang diubahnya harus masih dalam bentuk aslinya — dan hanya setelah itu menandainya sebagai di-rollback. Migrasi yang separuh diterapkan bukan kandidat.
Backup dan restore untuk deployment Compose hosting sendiri tidak didokumentasikan di repositori, sehingga halaman ini tidak menjelaskan prosedur untuknya.
Satu mesin, atau cloud terkelola
Jalur Docker Compose satu host di atas adalah jalur yang sudah terbukti: inilah yang dijalankan produksi proyek ini sendiri, di satu server, dengan basis data, penyimpanan objek, API, worker, admin, dan situs sebagai container di belakang reverse proxy bersama.
Melampaui satu mesin mengubah tiga hal, dan semuanya ditegakkan, bukan sekadar disarankan. Di luar mode local, Nessie menjalankan API dan worker sebagai proses terpisah, dan sebagai beberapa salinan masing-masing, sehingga kapabilitas apa pun yang mengasumsikan satu proses memiliki disk mesin itu ditolak: penyimpanan objek berbasis filesystem (kegagalan saat start), template lingkungan eksekusi dengan provider docker (tidak berfungsi, dan provisioning gagal dengan keras), serta alat builtin file_read, file_write, dan file_glob (hasil alat yang gagal, yang dapat dibaca agen). Setiap penolakan menyebutkan pengaturannya, modenya, dan apa yang harus dipakai sebagai gantinya.
Untuk cloud terkelola, repositori menyertakan pohon Terraform di infrastructure/terraform/ yang menargetkan Google Cloud: API sebagai layanan Cloud Run dengan minimum satu instans — ia memegang klien LISTEN Postgres yang persisten dan menjalankan sweep pemeliharaan, sehingga menskalakannya ke nol menghentikan pengiriman realtime — worker sebagai worker pool Cloud Run, karena ia tidak membuka port dan sebuah Service tidak akan pernah siap; sebuah migrate job yang menjadi gerbang setiap rollout; Cloud SQL for PostgreSQL 17; dan bucket GCS yang diakses melalui backend kompatibel S3.
Jalur itu sudah ditulis, tetapi belum pernah dijalankan dari awal sampai akhir. Repositori mengatakannya dengan jelas, dan halaman ini juga: belum ada proyek Google Cloud untuknya, tidak ada yang di dalam pohon itu yang sudah direncanakan atau diterapkan ke proyek nyata, jalur unggah multipart ke GCS belum diuji terhadap bucket nyata, jenis resource worker-pool belum divalidasi, dan jumlah hop trusted-proxy di dalamnya adalah placeholder yang harus diukur, bukan hasil pengukuran. Admin dan situs publik sama sekali tidak dimodelkan di sana. Anggap ini sebagai titik awal yang harus Anda verifikasi, bukan deployment yang didukung.
Di mana data sebuah deployment berada, apa yang disimpan perangkat lunak, dan apa yang sengaja tidak disimpannya, dijelaskan di halaman tersendiri: Dibuat di UE, dan di mana data Anda berada.