Files
chat/docs/OPERATIONS.md
T
2026-09-21 12:01:35 +02:00

3.7 KiB

Operations

Environment

Copy .env.example to .env. Full list:

Variable Default Purpose
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)
WIKI_URL https://git.kisakay.com/k/chat/wiki remote docs wiki — GET /wiki redirects (302) there
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_*, DEEPSEEK_*, ANTHROPIC_*, OPENAI_* disabled API drivers: set *_ENABLED=true and *_API_KEY

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

  1. Log in as admin with the APP_PASSWORD key.
  2. Open Accounts → New account → username (+ display name).
  3. Copy the one-time ka_… key and hand it to the owner (chat, QR code, …).
  4. 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.
  5. Rotate a compromised key with the regenerate button (old sessions die); delete removes the account with all its chats and shares.

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.
  • avatar rules: 5 MB max (413 above), 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).

NixOS deployment (chat.kisakay.com)

flake.nix exposes packages.<system>.default (backend + prebuilt frontend) and nixosModules.default. The frontend is compiled offline with buildNpmPackage from frontend/package-lock.json — commit that file, and on a hash mismatch paste the hash Nix reports into npmDepsHash.

imports = [ kisassistant.nixosModules.default ];
services.kisassistant = {
  enable = true;
  package = kisassistant.packages.${pkgs.system}.default;
  domain = "chat.kisakay.com";                          # nginx + ACME
  passwordFile = "/run/secrets/kisassistant-password";  # agenix/sops-nix
  ollamaHost = "http://10.66.66.4:11434";
  extraEnv = { MISTRAL_ENABLED = "true"; };             # optional
  # secrets for API drivers: pass via environment files / extraEnv, never the store
};

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.