update
This commit is contained in:
+53
-9
@@ -6,12 +6,22 @@ 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_*`, `DEEPSEEK_*`, `ANTHROPIC_*`, `OPENAI_*` | disabled | API drivers: set `*_ENABLED=true` **and** `*_API_KEY` |
|
||||
@@ -41,8 +51,21 @@ unshare. See `AGENTS.md`.
|
||||
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)
|
||||
## 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.
|
||||
|
||||
1. Admin sets a recovery email at account creation (or the user sets their own
|
||||
in profile settings).
|
||||
2. 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).
|
||||
3. Opening the link shows a confirm page; confirming **rotates the key
|
||||
immediately** — the new `ka_…` key is displayed once, old sessions die.
|
||||
(`admin` itself 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).
|
||||
@@ -55,26 +78,47 @@ unshare. See `AGENTS.md`.
|
||||
- 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 `text` CDN 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`)
|
||||
|
||||
`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`.
|
||||
Full step-by-step guide: **[NIXOS-HOSTING.md](./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`.
|
||||
|
||||
```nix
|
||||
imports = [ kisassistant.nixosModules.default ];
|
||||
services.kisassistant = {
|
||||
enable = true;
|
||||
package = kisassistant.packages.${pkgs.system}.default;
|
||||
domain = "chat.kisakay.com"; # nginx + ACME
|
||||
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
|
||||
# secrets for API drivers: pass via environment files / extraEnv, never the store
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user