7.4 KiB
Operations
Environment
Copy .env.example to .env. Full list:
| Variable | Default | Purpose |
|---|---|---|
NODE_ENV |
development |
production under the NixOS module (and CI); gates the DRIVER_DEBUG default |
PORT / HOST |
3000 / 127.0.0.1 |
listen address (keep localhost behind nginx) |
APP_PASSWORD |
(required) | admin access key — generate with openssl rand -base64 24 |
SESSION_TTL_HOURS |
720 |
Bearer session lifetime (30 days) |
DATA_DIR |
./data |
holds kisassistant.db (SQLite) |
CDN_DIR |
./cdn |
local file CDN storage (avatars; NixOS: under state dir) |
OCR_LANG |
eng |
tesseract language(s) for the OCR tool |
OCR_MAX_CHARS |
100000 |
transcription cap (longer results are truncated + flagged) |
TESSERACT_BIN |
tesseract |
path to the tesseract binary (NixOS module puts it on PATH; missing binary cleanly disables OCR with 501) |
DRIVER_DEBUG |
true dev / false prod |
per-driver debug logging to stdout |
WIKI_URL |
https://git.kisakay.com/k/chat/wiki |
remote docs wiki — GET /wiki redirects (302) there |
SMTP_HOST / SMTP_PORT / SMTP_SECURE |
(empty) / 587 / false |
key-recovery mailer — empty host disables it (false = STARTTLS, true = implicit TLS/465) |
SMTP_USER / SMTP_PASS |
(empty) | SMTP auth (omit both for open relays / local catchers) |
SMTP_FROM |
KisAssistant <[email protected]> |
sender address |
APP_URL |
http://localhost:3000 |
public base URL used in reset links — must be the real origin in prod |
RESET_TTL_MIN |
60 |
reset-link lifetime (single use) |
RATE_LIMIT_MAX / RATE_LIMIT_WINDOW_MIN |
5 / 10 |
login rate limit per IP |
OLLAMA_HOST / OLLAMA_ENABLED |
http://10.66.66.4:11434 / true |
local-model driver |
MISTRAL_*, GLM_*, DEEPSEEK_*, ANTHROPIC_*, OPENAI_* |
disabled | API drivers: set *_ENABLED=true and *_API_KEY |
GLM_BASE_URL |
https://api.z.ai/api/paas/v4 |
Zhipu platform override (mainland China: https://open.bigmodel.cn/api/paas/v4) |
Local development
bun install
bun run dev # backend watch mode (restarts on edit), :3000
bun run dev:frontend # Vite HMR + /api proxy, :5173 (use this for UI work)
bun run dev:all # both at once
bun run build:frontend # production build into public/
Smoke tests: start the backend on a free non-3000 port with a throwaway
DATA_DIR (port 3000 is the owner's dev server — do not touch it), then drive
the API with curl/python: login → models → conversations → chat → share →
unshare. See AGENTS.md.
Managing accounts
- Log in as
adminwith theAPP_PASSWORDkey. - Open Accounts → New account → username (+ display name).
- Copy the one-time
ka_…key and hand it to the owner (chat, QR code, …). - Users set their display name, avatar (upload box in profile settings, or a
remote URL e.g. from
catbox.moe) and theme in the profile settings. - Rotate a compromised key with the regenerate button (old sessions die); delete removes the account with all its chats and shares.
Admin Center (/admin) & self-registration
The sidebar Admin Center button (admin only) opens the /admin page with
three tabs: Accounts (same manager as before, incl. the Ollama model
catalog), Features (server-side switches), Mail (SMTP status).
registration_enabled(default off): when on, the login page shows a working Register button — anyone can create an account (key shown once) viaPOST /api/auth/register. When off, the button is greyed with the tooltip "Registration currently disabled on this platform" and the endpoint answers 403. Flipped live, no restart.tools_ocr_enabled(default on): admin kill-switch for the OCR tool.- Flags live in the
settingsSQLite table, edited viaGET/PATCH /api/admin/settings(admin Bearer only).
Key recovery via email (optional)
Set the SMTP_* vars (and the real public APP_URL) to enable it; otherwise
the login page hides the "Forgot your access key?" link.
- Admin sets a recovery email at account creation (or the user sets their own in profile settings).
- User clicks the link on the login page and enters their username. The API
always answers
{ok:true}(no account enumeration) and, if the account has an email, sends a one-time/reset/<token>link (60 min, single use). - Opening the link shows a confirm page; confirming rotates the key
immediately — the new
ka_…key is displayed once, old sessions die. (adminitself is excluded: its key lives in.env.)
Local file CDN (avatars)
PUT /cdn/<ns>/<key> (Bearer) / GET /cdn/<ns>/<key>.<ext> (public), e.g.
/cdn/avatar/<account-id>.png. Namespaces live in CDN_NAMESPACES
(src/cdn.ts) — add one to extend (future usage).
- Allowed types only
jpg/png/webp, verified by magic bytes, never by extension (client filename is ignored; the stored extension comes from detection). Everything else →415, unknown namespace/ext →404. avatarrules: 5 MB max (413above), 5 uploads max per 2 h per account (429+Retry-After), ownership enforced (own id, or admin).- Served with fixed
Content-Type,nosniff,Cache-Control: public, max-age=3600. No listing, no traversal (strict charsets + SPA fallback).
Platform tools & attachments
Uploads always go through platform tools (GET /api/tools lists them with
availability). Today: OCR (POST /api/tools/ocr, tesseract).
- Images (jpg/png/webp, 5 MB max, magic-verified, 20 jobs/hour/account) are
transcribed on the backend to TXT — the model never receives image
bytes. Text files ride the
textCDN namespace (UTF-8 validated, 500 KB max, keys prefixed with the account id). - The UI (paperclip menu in the composer) explains this and always shows a pre-transcription preview: the user reviews/edits the text, then attaches it. Attachments travel as labeled text blocks inside the message, so they persist in history and work with every model.
NixOS deployment (chat.kisakay.com)
Full step-by-step guide: NIXOS-HOSTING.md.
flake.nix exposes packages.<system>.default (backend + prebuilt frontend,
frontend compiled offline from frontend/bun.lock via a fixed-output
bun install) and nixosModules.default.
imports = [ kisassistant.nixosModules.default ];
services.kisassistant = {
enable = true;
package = kisassistant.packages.${pkgs.system}.default; # required
domain = "chat.kisakay.com"; # legacy switch: implies nginx + ACME
enableNginx = true; # explicit; requires domain
passwordFile = "/run/secrets/kisassistant-password"; # agenix/sops-nix
ollamaHost = "http://10.66.66.4:11434";
extraEnv = { MISTRAL_ENABLED = "true"; }; # optional
};
security.acme.acceptTerms = true; # required for the ACME/TLS vhost
The module sets NODE_ENV=production, CDN_DIR=/var/lib/kisassistant/cdn
and DRIVER_DEBUG=false for the systemd service. enableNginx is a
tri-state: true/false force the bundled reverse proxy on/off, null
(the default) keeps the legacy behaviour (nginx iff domain is set).
Secrets (passwordFile, API keys) must come from files (agenix/sops-nix), never
baked into the Nix store. The service stores SQLite under
/var/lib/kisassistant/data; nginx terminates TLS and proxies to
127.0.0.1:3000.