Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
@@ -0,0 +1,147 @@
# API Overview
NomiFun's backend (`nomifun-app`, binary `nomicore`) exposes a single axum HTTP server. The SPA, the desktop shell, and any external integration all talk to it the same way: JSON over HTTP for command/query, WebSocket for streaming events.
This page is an **orientation**, not an exhaustive endpoint reference. The full surface lives in the route modules under `crates/backend/`; the source is the canonical reference. Group base paths and the routers that own each one are listed below — start there.
## Base URL
| Host | Default base URL | Notes |
|---|---|---|
| `nomifun-desktop` | `http://127.0.0.1:<picked-port>` | Picks a free localhost port at startup. The renderer learns the port over IPC and uses `/api` and `/ws` against it. |
| `nomifun-web` | `http://<host>:<port>` (`http://127.0.0.1:8787` by default) | Same backend, served alongside the SPA on one port. |
| `nomicore` standalone | `http://127.0.0.1:25808` | Backend run on its own — useful for debugging. |
The SPA uses **relative paths** (`/api/...`, `/ws`). There is no separate API server to point clients at — the SPA and the API are co-located.
## Authentication model
NomiFun runs under one of three auth policies, decided at startup:
### Authenticated mode (default for `nomifun-web`)
- Login via `POST /login` returns a session JWT in both a cookie (`nomifun-session`, `HttpOnly`) and the JSON body. Subsequent requests authenticate via the cookie or an `Authorization: Bearer …` header.
- State-changing requests must additionally include the CSRF header `x-csrf-token` matching the `nomifun-csrf-token` cookie (Double Submit Cookie pattern). Safe methods (`GET`, `HEAD`, `OPTIONS`) bypass CSRF; the login/setup/qr-login endpoints are exempt because they have no session yet.
- WebSocket upgrades carry the same JWT — typically via `Sec-WebSocket-Protocol`, fetched from `GET /api/ws-token`. The `/ws` route is exempt from CSRF (no cookie-based double-submit on a WebSocket upgrade) but is otherwise authenticated.
- Rate limiters apply per-client to login attempts, general API traffic, and authenticated state-changing actions.
### Desktop local-trust mode (`nomifun-desktop`)
- The embedded backend uses `AuthPolicy::TrustLocalToken`.
- The desktop WebView receives a per-boot secret (`window.__nomiLocalTrust`) and
presents it on HTTP/WebSocket requests.
- Other clients, even on the same machine, are not trusted unless they have a
normal authenticated session. This is what makes WebUI remote access safe to
expose behind login.
### No-auth local mode (`--local` on `nomicore`, or `--insecure-no-auth` on the web host)
- Authentication and CSRF are turned off entirely. Every request acts as `system_default_user`.
- A permissive CORS layer is added so the desktop WebView (and tooling) can call the API freely.
- Local-only routes such as `/api/auth/internal/*` and `/api/webui/*` become reachable.
The trust boundary in local mode is the network — only ever expose it on loopback or a fully trusted private network. The web host loudly logs a warning if `--insecure-no-auth` is combined with a non-loopback bind.
## Body size and limits
- The default request body limit is **10 MiB** (`BODY_LIMIT` in `nomifun-common`). Routes that legitimately need more (file upload, ZIP creation, …) install their own larger limit — `/api/fs/upload` accepts up to 30 MiB.
- Remote images downloaded on behalf of the user are capped at 5 MiB and follow at most 5 redirects.
## Route groups
Each group is owned by a specific crate. The base path is the actual URL prefix mounted into the app router; auth applies in authenticated mode and desktop local-trust mode.
| Group | Base path | Auth | Owning crate / file |
|---|---|---|---|
| Health | `/health` | public | [`router/health.rs`](../../crates/backend/nomifun-app/src/router/health.rs) |
| Auth — login / setup / status / refresh | `/login`, `/logout`, `/api/auth/*`, `/api/ws-token`, `/qr-login` | mixed (login/setup/qr-login: public; rest: authenticated) | [`nomifun-auth/src/routes.rs`](../../crates/backend/nomifun-auth/src/routes.rs) |
| Auth — local-only admin/internal | `/api/webui/*`, `/api/auth/internal/*` | local mode only | same as above |
| Conversations | `/api/conversations/*`, `/api/messages/search` | authenticated | [`nomifun-conversation/src/routes.rs`](../../crates/backend/nomifun-conversation/src/routes.rs), [`routes_aux.rs`](../../crates/backend/nomifun-conversation/src/routes_aux.rs) |
| Agents (local CLI agents) | `/api/agents/*` | authenticated | [`nomifun-ai-agent/src/routes/agent.rs`](../../crates/backend/nomifun-ai-agent/src/routes/agent.rs) |
| Remote agents | `/api/remote-agents/*` | authenticated | [`nomifun-ai-agent/src/routes/remote.rs`](../../crates/backend/nomifun-ai-agent/src/routes/remote.rs) |
| Assistants | `/api/assistants/*` | authenticated | [`nomifun-assistant/src/routes.rs`](../../crates/backend/nomifun-assistant/src/routes.rs) |
| Assistant tags | `/api/assistant-tags/*` | authenticated | same as above |
| MCP servers | `/api/mcp/*` | authenticated | [`nomifun-mcp/src/routes.rs`](../../crates/backend/nomifun-mcp/src/routes.rs) |
| Skills | `/api/skills/*` | authenticated | [`nomifun-extension/src/skill_routes.rs`](../../crates/backend/nomifun-extension/src/skill_routes.rs) |
| Extensions | `/api/extensions/*` | authenticated | [`nomifun-extension/src/routes.rs`](../../crates/backend/nomifun-extension/src/routes.rs) |
| Hub (extension marketplace) | `/api/hub/*` | authenticated | [`nomifun-extension/src/hub_routes.rs`](../../crates/backend/nomifun-extension/src/hub_routes.rs) |
| Cron jobs | `/api/cron/*` | authenticated | [`nomifun-cron/src/routes.rs`](../../crates/backend/nomifun-cron/src/routes.rs) |
| Channels (IM bridges) | `/api/channel/*` | authenticated | [`nomifun-channel/src/routes.rs`](../../crates/backend/nomifun-channel/src/routes.rs) |
| Webhooks + tag settings | `/api/webhooks/*`, `/api/tags/{tag}/settings` | authenticated | [`nomifun-webhook/src/routes.rs`](../../crates/backend/nomifun-webhook/src/routes.rs) |
| Requirements (project board) | `/api/requirements/*` | authenticated | [`nomifun-requirement/src/routes.rs`](../../crates/backend/nomifun-requirement/src/routes.rs) |
| AutoWork / IDMM | `/api/idmm/*`, `/api/requirements/autowork*` | authenticated | [`nomifun-idmm/src/routes.rs`](../../crates/backend/nomifun-idmm/src/routes.rs) |
| Teams (backend implementation surface; no current frontend guide route) | `/api/teams/*` | authenticated | [`nomifun-team/src/routes.rs`](../../crates/backend/nomifun-team/src/routes.rs) |
| Terminals | `/api/terminals/*` | authenticated | [`nomifun-terminal/src/routes.rs`](../../crates/backend/nomifun-terminal/src/routes.rs) |
| Terminal knowledge registration helpers | `/api/terminals/mcp-register-template`, `/api/terminals/register-knowledge*`, `/api/terminals/knowledge-global-status` | authenticated | [`router/health.rs`](../../crates/backend/nomifun-app/src/router/health.rs) |
| Knowledge bases | `/api/knowledge/*` | authenticated | [`nomifun-knowledge/src/routes.rs`](../../crates/backend/nomifun-knowledge/src/routes.rs) |
| Companion | `/api/companion/*` | authenticated | [`nomifun-companion/src/routes.rs`](../../crates/backend/nomifun-companion/src/routes.rs) |
| Companion access tokens for WebUI/public capability use | `/api/webui/companions/{id}/access-token` | authenticated/local WebUI admin flow | [`router/companion_token_routes.rs`](../../crates/backend/nomifun-app/src/router/companion_token_routes.rs) |
| Browser-use secrets | `/api/browser-secrets/*` | authenticated | [`nomifun-secret/src/routes.rs`](../../crates/backend/nomifun-secret/src/routes.rs) |
| Filesystem | `/api/fs/*` | authenticated | [`nomifun-file/src/routes.rs`](../../crates/backend/nomifun-file/src/routes.rs) |
| Office preview | `/api/word-preview/*`, `/api/excel-preview/*`, `/api/ppt-preview/*`, `/api/document/convert`, `/api/preview-history/*`, `/api/star-office/detect` | authenticated | [`nomifun-office/src/routes.rs`](../../crates/backend/nomifun-office/src/routes.rs) |
| Office iframe proxies | `/api/ppt-proxy/*`, `/api/office-watch-proxy/*` | public (serve iframe content; no auth) | same as above |
| Settings + providers + system info | `/api/settings`, `/api/providers/*`, `/api/system/*` | authenticated | [`nomifun-system/src/routes.rs`](../../crates/backend/nomifun-system/src/routes.rs) |
| Global model failover queue | `/api/agent/model-failover` | authenticated | [`router/model_failover.rs`](../../crates/backend/nomifun-app/src/router/model_failover.rs) |
| Connection probes (Bedrock, …) | `/api/bedrock/test-connection` | authenticated | [`nomifun-system/src/bedrock_probe/routes.rs`](../../crates/backend/nomifun-system/src/bedrock_probe/routes.rs) |
| Shell helpers + STT | `/api/shell/*`, `/api/stt` | authenticated | [`nomifun-shell/src/routes.rs`](../../crates/backend/nomifun-shell/src/routes.rs) |
| Public assets (logos) | `/api/assets/logos/*` | public | [`nomifun-assets/src/routes.rs`](../../crates/backend/nomifun-assets/src/routes.rs) |
| Public MCP front door | `/mcp/*` | companion-token / configured public auth | [`nomifun-public/src/router.rs`](../../crates/backend/nomifun-public/src/router.rs) |
| Public MCP agent front door | `/mcp-agent/*` | companion-token / configured public auth | [`nomifun-public/src/router.rs`](../../crates/backend/nomifun-public/src/router.rs) |
| Remote capability REST API | `/v1/*` | companion-token | [`nomifun-public/src/rest.rs`](../../crates/backend/nomifun-public/src/rest.rs) |
| Realtime WebSocket | `/ws` | authenticated (token in `Sec-WebSocket-Protocol` or query) | [`nomifun-realtime/src/handler.rs`](../../crates/backend/nomifun-realtime/src/handler.rs) |
For the exact set of methods on each route, read the corresponding `routes.rs` file — every router declares its routes inline.
### Selected auth endpoints
These are the auth endpoints clients are most likely to interact with directly:
| Method + path | Purpose |
|---|---|
| `POST /login` | Username + password login. Returns `{success, user, token}` and sets the session cookie. CSRF-exempt. Rate-limited. |
| `POST /api/auth/setup` | One-time first-run admin creation on a fresh install. Atomic; concurrent callers race on a conditional UPDATE so only one wins (the others get `409 Conflict`). CSRF-exempt. |
| `POST /logout` | Blacklists the current token; clears the session cookie. |
| `GET /api/auth/status` | Public — reports `{needs_setup, user_count, is_authenticated}`. Useful as a liveness/health probe. |
| `GET /api/auth/user` | Returns the current `{id, username}`. |
| `POST /api/auth/change-password` | Changes the current user's password and rotates the JWT secret (invalidating every other session). |
| `POST /api/auth/refresh` | Refreshes a token that is still valid but near expiry. |
| `GET /api/ws-token` | Returns the token to use for the WebSocket upgrade. |
| `POST /api/auth/qr-login` | Consume a one-shot QR-login token (issued via the WebUI remote-access flow). |
| `GET /qr-login` | Static HTML page that completes a QR login redirect from a phone scanner. |
## WebSocket event model
`/ws` is the single bidirectional channel for streaming updates: agent token streams, terminal output, requirement / cron / team state changes, etc.
- Authentication: a JWT obtained from `GET /api/ws-token`, sent in the WebSocket `Sec-WebSocket-Protocol` header (or `Authorization`). Invalid or expired token → server sends an `auth-expired` event and closes with code `1008`. No token at all → close with `1008`, reason `"no token provided"`.
- After a successful upgrade, every message is a JSON object with a `type` and a `payload`. Messages are pushed by the server when domain events occur (a new agent token, a terminal byte, a requirement transition); clients usually do not need to send anything back. The server multiplexes a single `BroadcastEventBus` to every connected client.
- Heartbeats: ping every 30s, timeout at 60s (`HEARTBEAT_INTERVAL_MS` / `HEARTBEAT_TIMEOUT_MS`).
- Close codes: `1000` for a normal close, `1008` for policy violations (auth failure, invalid token).
The set of `type` values is open-ended — extensions and feature modules emit their own. Treat unknown types as forward-compatible: ignore them.
## Response envelope
Most JSON responses use the same shape (`ApiResponse<T>` from `nomifun-api-types`):
```json
{ "success": true, "data": { ... } }
```
Errors are returned with the appropriate HTTP status and a body like:
```json
{ "success": false, "error": "Invalid username or password" }
```
The login/setup/refresh handlers return slightly enriched envelopes (`LoginResponse`, `RefreshResponse`) — they include the token or user object inline.
## Source-of-truth pointers
The list above is meant to get you to the right module. From there, read the source — every router declares its routes in one place, and every handler is in the same file or the next one over. The router assembly itself is in [`crates/backend/nomifun-app/src/router/routes.rs`](../../crates/backend/nomifun-app/src/router/routes.rs); the middleware stack (CSRF, security headers, body limit, optional CORS) is also there.
## See also
- [Configuration Reference](./configuration.md) — flags, env vars, the auth secret resolution order.
- [Troubleshooting](./troubleshooting.md) — common API and WebSocket failure modes.
- [Web Server Deployment](../guides/web-server-deployment.md) — exposing the API over the network behind TLS.
@@ -0,0 +1,183 @@
# API 概览
NomiFun 的后端(`nomifun-app`,二进制 `nomicore`)对外暴露的是单一的
axum HTTP 服务。SPA、桌面外壳,以及任何外部集成,与它沟通的方式都一样:
HTTP 上的 JSON 用于命令/查询,WebSocket 用于流式事件。
本页是一份**导览**,不是穷尽式的端点参考。完整的接口面位于
`crates/backend/` 下的各路由模块;源码即权威参考。下方列出了各分组的
基础路径与对应的路由 owner——请从那里开始查阅。
## Base URL
| 宿主 | 默认 base URL | 备注 |
|---|---|---|
| `nomifun-desktop` | `http://127.0.0.1:<picked-port>` | 启动时挑选一个空闲的 localhost 端口。渲染端通过 IPC 获知端口号,并以此向 `/api``/ws` 发起调用。 |
| `nomifun-web` | `http://<host>:<port>`(默认 `http://127.0.0.1:8787`) | 同一个后端,与 SPA 一并在同一个端口上提供。 |
| `nomicore` 独立运行 | `http://127.0.0.1:25808` | 单独运行后端——便于调试。 |
SPA 使用**相对路径**`/api/...``/ws`)。客户端不需要指向另一台 API
服务——SPA 与 API 同址。
## 鉴权模型
NomiFun 启动时进入三种鉴权策略之一:
### 已鉴权模式(`nomifun-web` 默认)
- 通过 `POST /login` 登录,返回一个会话 JWT,同时写入 cookie
`nomifun-session``HttpOnly`)与 JSON body。后续请求依靠该 cookie
`Authorization: Bearer …` 请求头进行鉴权。
- 状态变更类请求还必须附带 CSRF 请求头 `x-csrf-token`,其值需与
`nomifun-csrf-token` cookie 匹配(Double Submit Cookie 模式)。安全
方法(`GET``HEAD``OPTIONS`)跳过 CSRF;登录/设置/二维码登录端点
因尚无会话被豁免。
- WebSocket 升级携带同一份 JWT——通常通过 `Sec-WebSocket-Protocol`
传输,可由 `GET /api/ws-token` 获取。`/ws` 路由对 CSRF 豁免(在
WebSocket 升级中无法做基于 cookie 的双提交),但仍需鉴权。
- 限流器分别按客户端作用于登录尝试、一般 API 流量与已鉴权的状态变更
动作。
### 桌面本地信任模式(`nomifun-desktop`
- 嵌入式后端使用 `AuthPolicy::TrustLocalToken`
- 桌面 WebView 会得到每次启动生成的 secret(`window.__nomiLocalTrust`),并在 HTTP/WebSocket 请求中呈递它。
- 其他客户端即使在同一台机器上,也不会因为来自 loopback 自动受信任;除非它拥有正常登录会话。这也是 WebUI 远程访问可以放在登录后的原因。
### 无鉴权本地模式(`nomicore --local`,或 Web 宿主 `--insecure-no-auth`
- 鉴权与 CSRF 完全关闭。每个请求都以 `system_default_user` 身份执行。
- 加入一层宽松的 CORS,使桌面 WebView(以及工具)可以自由调用 API。
- 仅本地可达的路由(如 `/api/auth/internal/*``/api/webui/*`)变为
可达。
本地模式下的信任边界是网络——只能将其暴露在 loopback 或完全受信任的
私有网络上。Web 宿主在 `--insecure-no-auth` 与非 loopback 绑定同时使用
时会大声地打印警告日志。
## 请求体大小与上限
- 请求体的默认大小上限是 **10 MiB**`nomifun-common` 中的
`BODY_LIMIT`)。确实需要更大的路由(文件上传、ZIP 创建等)会安装自己
的更大限制——`/api/fs/upload` 接受最大 30 MiB。
- 代用户下载的远程图片上限为 5 MiB,最多跟随 5 次重定向。
## 路由分组
每个分组归属一个特定的 crate。下表中的基础路径就是挂载到 app router 中
的实际 URL 前缀;鉴权在已鉴权模式和桌面本地信任模式下生效。
| 分组 | 基础路径 | 鉴权 | 归属 crate / 文件 |
|---|---|---|---|
| 健康检查 | `/health` | 公共 | [`router/health.rs`](../../crates/backend/nomifun-app/src/router/health.rs) |
| 鉴权 —— 登录 / 设置 / 状态 / 刷新 | `/login``/logout``/api/auth/*``/api/ws-token``/qr-login` | 混合(登录/设置/qr-login:公共;其余:已鉴权) | [`nomifun-auth/src/routes.rs`](../../crates/backend/nomifun-auth/src/routes.rs) |
| 鉴权 —— 仅本地 admin/internal | `/api/webui/*``/api/auth/internal/*` | 仅本地模式 | 同上 |
| 会话 | `/api/conversations/*``/api/messages/search` | 已鉴权 | [`nomifun-conversation/src/routes.rs`](../../crates/backend/nomifun-conversation/src/routes.rs)、[`routes_aux.rs`](../../crates/backend/nomifun-conversation/src/routes_aux.rs) |
| 智能体(本地 CLI 智能体) | `/api/agents/*` | 已鉴权 | [`nomifun-ai-agent/src/routes/agent.rs`](../../crates/backend/nomifun-ai-agent/src/routes/agent.rs) |
| 远程智能体 | `/api/remote-agents/*` | 已鉴权 | [`nomifun-ai-agent/src/routes/remote.rs`](../../crates/backend/nomifun-ai-agent/src/routes/remote.rs) |
| 助手 | `/api/assistants/*` | 已鉴权 | [`nomifun-assistant/src/routes.rs`](../../crates/backend/nomifun-assistant/src/routes.rs) |
| 助手标签 | `/api/assistant-tags/*` | 已鉴权 | 同上 |
| MCP 服务 | `/api/mcp/*` | 已鉴权 | [`nomifun-mcp/src/routes.rs`](../../crates/backend/nomifun-mcp/src/routes.rs) |
| 技能 | `/api/skills/*` | 已鉴权 | [`nomifun-extension/src/skill_routes.rs`](../../crates/backend/nomifun-extension/src/skill_routes.rs) |
| 扩展 | `/api/extensions/*` | 已鉴权 | [`nomifun-extension/src/routes.rs`](../../crates/backend/nomifun-extension/src/routes.rs) |
| Hub(扩展市场) | `/api/hub/*` | 已鉴权 | [`nomifun-extension/src/hub_routes.rs`](../../crates/backend/nomifun-extension/src/hub_routes.rs) |
| 计划任务 | `/api/cron/*` | 已鉴权 | [`nomifun-cron/src/routes.rs`](../../crates/backend/nomifun-cron/src/routes.rs) |
| 频道(IM 桥) | `/api/channel/*` | 已鉴权 | [`nomifun-channel/src/routes.rs`](../../crates/backend/nomifun-channel/src/routes.rs) |
| Webhook + 标签设置 | `/api/webhooks/*``/api/tags/{tag}/settings` | 已鉴权 | [`nomifun-webhook/src/routes.rs`](../../crates/backend/nomifun-webhook/src/routes.rs) |
| 需求(项目看板) | `/api/requirements/*` | 已鉴权 | [`nomifun-requirement/src/routes.rs`](../../crates/backend/nomifun-requirement/src/routes.rs) |
| AutoWork / IDMM | `/api/idmm/*``/api/requirements/autowork*` | 已鉴权 | [`nomifun-idmm/src/routes.rs`](../../crates/backend/nomifun-idmm/src/routes.rs) |
| 团队(后端实现面;当前没有对应用户指南路由) | `/api/teams/*` | 已鉴权 | [`nomifun-team/src/routes.rs`](../../crates/backend/nomifun-team/src/routes.rs) |
| 终端 | `/api/terminals/*` | 已鉴权 | [`nomifun-terminal/src/routes.rs`](../../crates/backend/nomifun-terminal/src/routes.rs) |
| 终端 knowledge 注册辅助 | `/api/terminals/mcp-register-template``/api/terminals/register-knowledge*``/api/terminals/knowledge-global-status` | 已鉴权 | [`router/health.rs`](../../crates/backend/nomifun-app/src/router/health.rs) |
| 知识库 | `/api/knowledge/*` | 已鉴权 | [`nomifun-knowledge/src/routes.rs`](../../crates/backend/nomifun-knowledge/src/routes.rs) |
| 伙伴 | `/api/companion/*` | 已鉴权 | [`nomifun-companion/src/routes.rs`](../../crates/backend/nomifun-companion/src/routes.rs) |
| WebUI/public 能力 companion token | `/api/webui/companions/{id}/access-token` | 已鉴权 / 本地 WebUI admin 流 | [`router/companion_token_routes.rs`](../../crates/backend/nomifun-app/src/router/companion_token_routes.rs) |
| Browser-use secrets | `/api/browser-secrets/*` | 已鉴权 | [`nomifun-secret/src/routes.rs`](../../crates/backend/nomifun-secret/src/routes.rs) |
| 文件系统 | `/api/fs/*` | 已鉴权 | [`nomifun-file/src/routes.rs`](../../crates/backend/nomifun-file/src/routes.rs) |
| Office 预览 | `/api/word-preview/*``/api/excel-preview/*``/api/ppt-preview/*``/api/document/convert``/api/preview-history/*``/api/star-office/detect` | 已鉴权 | [`nomifun-office/src/routes.rs`](../../crates/backend/nomifun-office/src/routes.rs) |
| Office iframe 代理 | `/api/ppt-proxy/*``/api/office-watch-proxy/*` | 公共(提供 iframe 内容;不鉴权) | 同上 |
| 设置 + 提供商 + 系统信息 | `/api/settings``/api/providers/*``/api/system/*` | 已鉴权 | [`nomifun-system/src/routes.rs`](../../crates/backend/nomifun-system/src/routes.rs) |
| 全局模型故障转移队列 | `/api/agent/model-failover` | 已鉴权 | [`router/model_failover.rs`](../../crates/backend/nomifun-app/src/router/model_failover.rs) |
| 连接探测(Bedrock 等) | `/api/bedrock/test-connection` | 已鉴权 | [`nomifun-system/src/bedrock_probe/routes.rs`](../../crates/backend/nomifun-system/src/bedrock_probe/routes.rs) |
| Shell 辅助 + STT | `/api/shell/*``/api/stt` | 已鉴权 | [`nomifun-shell/src/routes.rs`](../../crates/backend/nomifun-shell/src/routes.rs) |
| 公共资源(logo | `/api/assets/logos/*` | 公共 | [`nomifun-assets/src/routes.rs`](../../crates/backend/nomifun-assets/src/routes.rs) |
| Public MCP front door | `/mcp/*` | companion-token / 已配置 public auth | [`nomifun-public/src/router.rs`](../../crates/backend/nomifun-public/src/router.rs) |
| Public MCP agent front door | `/mcp-agent/*` | companion-token / 已配置 public auth | [`nomifun-public/src/router.rs`](../../crates/backend/nomifun-public/src/router.rs) |
| Remote capability REST API | `/v1/*` | companion-token | [`nomifun-public/src/rest.rs`](../../crates/backend/nomifun-public/src/rest.rs) |
| 实时 WebSocket | `/ws` | 已鉴权(token 通过 `Sec-WebSocket-Protocol` 或查询串传递) | [`nomifun-realtime/src/handler.rs`](../../crates/backend/nomifun-realtime/src/handler.rs) |
如需各路由具体支持的方法,请阅读对应的 `routes.rs` 文件——每个 router
都在源文件内联声明自身的路由。
### 选取的鉴权端点
下面这些是客户端最常直接交互的鉴权端点:
| 方法 + 路径 | 用途 |
|---|---|
| `POST /login` | 用户名 + 密码登录。返回 `{success, user, token}` 并设置会话 cookie。CSRF 豁免。带限流。 |
| `POST /api/auth/setup` | 全新安装上的一次性首位管理员创建。原子操作;并发调用通过条件 UPDATE 竞争,只有一个会赢(其余得到 `409 Conflict`)。CSRF 豁免。 |
| `POST /logout` | 将当前 token 加入黑名单;清除会话 cookie。 |
| `GET /api/auth/status` | 公共——返回 `{needs_setup, user_count, is_authenticated}`。可作为 liveness/health 探针。 |
| `GET /api/auth/user` | 返回当前 `{id, username}`。 |
| `POST /api/auth/change-password` | 修改当前用户密码并轮换 JWT 密钥(使其他会话全部失效)。 |
| `POST /api/auth/refresh` | 刷新仍然有效但接近过期的 token。 |
| `GET /api/ws-token` | 返回用于 WebSocket 升级的 token。 |
| `POST /api/auth/qr-login` | 消费一次性的二维码登录 token(由 WebUI 远程访问流程下发)。 |
| `GET /qr-login` | 静态 HTML 页面,用于完成来自手机扫码的二维码登录跳转。 |
## WebSocket 事件模型
`/ws` 是用于流式更新的单一双向通道:智能体 token 流、终端输出,
需求/计划任务/团队的状态变化等等。
- 鉴权:通过 `GET /api/ws-token` 获得的 JWT,放在 WebSocket 的
`Sec-WebSocket-Protocol` 请求头中(或 `Authorization`)。token 无效或
过期 → 服务端发出 `auth-expired` 事件并以 `1008` 关闭。完全没有
token → 以 `1008` 关闭,原因为 `"no token provided"`
- 升级成功后,每条消息都是带 `type``payload` 的 JSON 对象。当域内
事件发生时(新的智能体 token、一个终端字节、需求状态切换),由
服务端推送;客户端通常无需回送任何内容。服务端把单一的
`BroadcastEventBus` 多路复用给所有已连接客户端。
- 心跳:每 30 秒 ping 一次,60 秒超时(`HEARTBEAT_INTERVAL_MS` /
`HEARTBEAT_TIMEOUT_MS`)。
- 关闭码:`1000` 表示正常关闭;`1008` 表示策略违规(鉴权失败、token
无效)。
`type` 取值集合是开放的——扩展与功能模块会发出各自的类型。请把未知
类型当作向前兼容的:忽略它们即可。
## 响应包络
绝大多数 JSON 响应使用同一种形状(来自 `nomifun-api-types`
`ApiResponse<T>`):
```json
{ "success": true, "data": { ... } }
```
错误使用恰当的 HTTP 状态码返回,body 形如:
```json
{ "success": false, "error": "Invalid username or password" }
```
登录/设置/刷新这几个 handler 会返回略微富化的包络
`LoginResponse``RefreshResponse`)——它们会把 token 或 user 对象
内联在响应中。
## 真值来源指引
上面的列表只是为了把你引导到对的模块。到达后请阅读源码——每个 router
在一处声明全部路由,每个 handler 都在同一个文件或紧挨着的下一个文件
里。Router 装配本身位于
[`crates/backend/nomifun-app/src/router/routes.rs`](../../crates/backend/nomifun-app/src/router/routes.rs)
中间件栈(CSRF、安全响应头、请求体上限、可选的 CORS)也在那里。
## 另见
- [配置参考](./configuration.zh.md) —— 参数、环境变量、鉴权密钥解析顺序。
- [疑难排查](./troubleshooting.zh.md) —— 常见的 API 与 WebSocket 故障
形态。
- [Web 服务部署](../guides/web-server-deployment.md) —— 在 TLS 之后把
API 暴露到网络上。
@@ -0,0 +1,144 @@
# Configuration Reference
Every flag and environment variable NomiFun reads, with defaults and the file that owns each one. Values are taken from the source — if a setting is not in this page it does not exist.
NomiFun ships **one** Rust backend (`nomifun-app`, binary `nomicore`) and two hosts that embed it:
- `nomifun-desktop` — the Tauri desktop shell. Boots the backend under `AuthPolicy::TrustLocalToken` on a chosen loopback port and injects a per-boot trust secret into its own WebView.
- `nomifun-web` — the standalone web/server host. Boots the same backend in **authenticated** mode by default and serves the SPA on the same port.
Both hosts share the same configuration surface for the backend; the per-host CLIs only override the bits each one owns.
## `nomifun-web` flags and environment variables
Source: [`apps/web/src/main.rs`](../../apps/web/src/main.rs).
| Flag | Env var | Default | Purpose |
|---|---|---|---|
| `--host` | `NOMIFUN_WEB_HOST` | `127.0.0.1` | IP to bind on. `0.0.0.0` accepts LAN/VPN/public traffic; pre-seed or complete first-run setup before broad exposure. Hostnames are not parsed; bad input fails fast at startup. |
| `--port` | `NOMIFUN_WEB_PORT` | `8787` | TCP port. Serves the API, the WebSocket at `/ws`, and the SPA from one socket. |
| `--data-dir` | `NOMIFUN_DATA_DIR` | per-user app-data dir | Backend data directory (SQLite database, agent state, logs, Bun cache). Defaults to the per-user location shared by every host — `%LOCALAPPDATA%\NomiFun\Nomi` on Windows, `~/Library/Application Support/NomiFun/Nomi` on macOS, `$XDG_DATA_HOME/NomiFun/Nomi` on Linux. Override with the flag or `NOMIFUN_DATA_DIR` (taken literally, no suffix); use an absolute path in production. |
| `--dist` | `NOMIFUN_WEB_DIST` | `../../ui/dist` | Directory containing the built SPA. Set this explicitly when deploying outside the repo. |
| `--admin-user` | `NOMIFUN_ADMIN_USERNAME` | `admin` | Username used when pre-seeding the first admin. Ignored once an admin exists. |
| `--admin-password` | `NOMIFUN_ADMIN_PASSWORD` | — | Pre-seeds the first admin password at boot, skipping interactive setup. Ignored once an admin exists. |
| `--insecure-no-auth` | `NOMIFUN_WEB_INSECURE_NO_AUTH` | `false` | DANGER. Disables authentication entirely (desktop-style local mode). Only use on loopback or a fully trusted private network. |
Boolean envs accept `1`, `true`, `yes`, `on` (case-insensitive).
## `nomicore` (backend) flags
Source: [`crates/backend/nomifun-app/src/cli.rs`](../../crates/backend/nomifun-app/src/cli.rs).
These are the flags exposed by the standalone `nomicore` binary. The two hosts construct a defaulted `Cli` and override only what they own — so the same flags apply when the backend is run on its own.
| Flag | Default | Purpose |
|---|---|---|
| `--host` | `127.0.0.1` (`DEFAULT_HOST`) | Host address to listen on. |
| `--port` | `25808` (`DEFAULT_PORT`) | Port to listen on. |
| `--data-dir` | per-user app-data dir | Database + file storage root. Bound to the `NOMIFUN_DATA_DIR` env (literal value) via clap; with neither set it resolves `default_data_dir()` — the same per-user location all hosts share. |
| `--work-dir` | (none) | Working directory for conversation workspaces. Falls back to `NOMIFUN_WORK_DIR` env, then to the data dir itself. |
| `--app-version` | crate version | Host application version reported to the extension engine for compatibility checks. |
| `--local` | `false` | No-auth local mode for standalone `nomicore`. `nomifun-web --insecure-no-auth` maps to the same policy. The desktop shell does not use this flag; it uses `TrustLocalToken` instead. |
| `--log-dir` | `<data-dir>/logs` | Directory for rolling daily log files. |
| `--log-level` | `info` | Log level filter. Supports per-target overrides — e.g. `info,nomifun_mcp=trace`. |
Subcommands (used internally by the agent CLI bridge and for diagnostics):
| Subcommand | Purpose |
|---|---|
| `mcp-requirement-stdio` | MCP stdio server for AutoWork requirement declaration tools. |
| `mcp-knowledge-stdio` | MCP stdio server for per-session knowledge search. |
| `mcp-gateway-stdio` | MCP stdio server for Desktop Gateway tools. |
| `mcp-open-stdio` | MCP stdio server exposing a reliable OS `open` tool. |
| `mcp-computer-stdio` | MCP stdio server exposing desktop computer-use tools. |
| `mcp-browser-stdio` | MCP stdio server exposing browser-use tools. |
| `terminal-hook --event <kind>` | One-shot terminal lifecycle hook relay. |
| `doctor` | Self-check: hydrate the agent registry, probe every CLI on `$PATH`, print a per-agent availability table. |
| `tools` | List public Remote capability names and descriptions as JSON. |
| `call <name> [json-args]` | Invoke a public Remote capability on a running instance via `/v1`. |
| `agent "<goal>"` | Convenience wrapper over the `nomi_agent_run` capability. |
## Shared environment variables
These are read by the backend regardless of which host embeds it.
| Env var | Read by | Effect |
|---|---|---|
| `NOMIFUN_DATA_DIR` | all hosts | Source of truth for the backend data directory when the host wants to honour it. The desktop shell appends `/Nomi`: with the env set the dir is `$NOMIFUN_DATA_DIR/Nomi`; with it unset the dir is the per-user app-data default (see [below](#data-directory-and-work-directory-semantics)). The standalone web host and the `nomicore` binary use it literally as the default for `--data-dir` (no extra suffix). |
| `NOMIFUN_WORK_DIR` | `nomicore` | Fallback for `--work-dir` (per-conversation workspace root). |
| `JWT_SECRET` | `nomifun-app` | Secret used to sign session JWTs. See [Auth secret resolution](#auth-secret-resolution) for the resolution order. |
| `NOMIFUN_HTTPS` | `nomifun-auth::CookieConfig` | When truthy, session and CSRF cookies get the `Secure` flag and `SameSite=Strict`. Set it whenever the app is reached over HTTPS (TLS reverse proxy, etc.). Default is `false` → no `Secure` flag, `SameSite=Lax`. |
| `SHELL` | agent engine (Linux/macOS) | Shell used when the agent engine spawns child processes. On Linux servers under systemd, set this explicitly (the system account often has no `$SHELL`). |
| `NOMIFUN_URL` | `nomicore call`, `nomicore agent` | Base URL for a running instance when invoking Remote capabilities. |
| `NOMIFUN_COMPANION_TOKEN` | `nomicore call`, `nomicore agent` | Companion access token used against `/v1` Remote capability routes. |
There is no `SENTRY_DSN` integration: the codebase does not read that environment variable.
## Backend constants
Source: [`crates/backend/nomifun-common/src/constants.rs`](../../crates/backend/nomifun-common/src/constants.rs). These are compile-time values, not environment variables — they are listed here so operators know the limits.
| Constant | Value | Used for |
|---|---|---|
| `DEFAULT_HOST` | `127.0.0.1` | Default `--host` for `nomicore`. |
| `DEFAULT_PORT` | `25808` | Default `--port` for `nomicore`. (The web host overrides this to `8787`.) |
| `BODY_LIMIT` | `10 MiB` | Default request body limit applied to every route. Routes that need more (e.g. `/api/fs/upload`) install their own larger limit. |
| `UPLOAD_MAX_SIZE` | `30 MiB` | Cap for the file upload route (`/api/fs/upload`). |
| `REMOTE_IMAGE_MAX_SIZE` | `5 MiB` | Cap for downloading a remote image referenced in chat. |
| `COOKIE_NAME` | `nomifun-session` | Session cookie. |
| `CSRF_COOKIE_NAME` | `nomifun-csrf-token` | CSRF cookie (NOT HttpOnly — JavaScript reads it). |
| `CSRF_HEADER_NAME` | `x-csrf-token` | Header that mirrors the CSRF cookie value (Double Submit Cookie). |
| `COOKIE_MAX_AGE_DAYS` | `30` | Cookie `Max-Age`. |
| `SESSION_EXPIRY` | `24h` | JWT validity window before refresh is required. |
| `HEARTBEAT_INTERVAL_MS` / `HEARTBEAT_TIMEOUT_MS` | `30000` / `60000` | WebSocket heartbeat ping/pong. |
## Data directory and work directory semantics
- `data-dir` holds the SQLite database (`nomifun-backend.db*`), per-agent state, the Bun cache, log files, and any embedded extension data. Treat it like any other database — back it up and restrict permissions. Sharing it between two running backends is prevented mechanically (see the server lock below).
- All three hosts (`nomifun-desktop`, `nomifun-web`, the standalone `nomicore` binary) resolve the **same default** data dir via `nomifun_app::cli::default_data_dir()`: `%LOCALAPPDATA%\NomiFun\Nomi` on Windows, `~/Library/Application Support/NomiFun/Nomi` on macOS, `$XDG_DATA_HOME/NomiFun/Nomi` on Linux (usually `~/.local/share/NomiFun/Nomi`), resolved via the `dirs` crate, with `<system temp>/nomifun-data/Nomi` as the extreme fallback when the OS reports no user directory. One default for every host is deliberate: the dev loops (`bun run serve:web`, `dev:web`, `dev`) and the installed desktop app read and write the same state — a provider or companion configured once is testable everywhere, and troubleshooting only ever has one directory to look at. For an isolated sandbox, point `NOMIFUN_DATA_DIR` or `--data-dir` somewhere else.
- At startup (before the database is opened) the backend takes an OS-level **exclusive lock** on `{data_dir}/server.lock`. A second backend process on the same data dir fails fast with an error naming the holder (pid + executable) and the two ways out: close the other instance, or give this one its own directory via `NOMIFUN_DATA_DIR` / `--data-dir`. The lock is advisory (`flock` / `LockFileEx` via `fs2`) and is released by the OS when the process exits or crashes — a leftover `server.lock` file is harmless. `nomicore doctor` and the `mcp-*` stdio subcommands do not take the lock (doctor is designed to run alongside a live server).
- `work-dir` holds per-conversation workspaces. When unset, it resolves in this order: `--work-dir` → non-empty `NOMIFUN_WORK_DIR` env → the data dir itself. Conversations create subdirectories under `<work-dir>/conversations/`; deleting a conversation deletes its workspace.
- The desktop shell uses the shared default above. With `NOMIFUN_DATA_DIR` set, the dir becomes `$NOMIFUN_DATA_DIR/Nomi` — the override semantics are unchanged. Older builds defaulted to `<system temp>/nomifun-data/Nomi`; on first launch with the new default, an existing temp-rooted install is relocated automatically (one-shot, the legacy dir is kept as a backup and absolute paths stored in the database are rewritten).
- The web host applies the value literally — `--data-dir` (or `NOMIFUN_DATA_DIR`) is used as given, with no `/Nomi` suffix — so Docker (`/data`) and systemd (`/var/lib/nomifun`) deployments are unaffected. With neither set it falls back to the shared per-user default; the old relative `data` default is gone.
## Auth secret resolution
`JwtService` is constructed from a single secret; `AppServices::from_config` resolves it in this order:
1. `JWT_SECRET` environment variable, if set.
2. Otherwise, the value persisted in the system user row (`system_default_user.jwt_secret`).
3. Otherwise, a fresh cryptographically random secret is generated and **persisted to the database** for future boots.
The change-password flow rotates the JWT secret as a side effect, invalidating every existing session.
The same secret is also used to derive an encryption key (`derive_encryption_key`) for at-rest encryption of secrets stored in the database (provider keys, MCP OAuth tokens, etc.).
## TLS / HTTPS cookie handling
NomiFun does not terminate TLS itself — put a TLS-terminating reverse proxy (Caddy, nginx, …) in front. When you do:
- Set `NOMIFUN_HTTPS=true` so cookies are flagged `Secure` and `SameSite=Strict`. Without this, browsers reject `Secure` cookies on HTTPS responses, and login appears to silently fail.
- The WebSocket upgrade at `/ws` passes through any standards-compliant proxy without extra headers; Caddy handles it out of the box.
See [`guides/web-server-deployment.md`](../guides/web-server-deployment.md) for a worked Caddy + Docker setup.
## Logging
- All logs go to both stdout (so `journalctl`/`docker logs` capture them) and a daily-rolling file at `<log-dir>/nomicore.log`.
- `--log-level` accepts a full [`tracing` `EnvFilter`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html) directive: a global level, or a comma-separated list of per-target overrides.
Examples:
- `info` — global info.
- `debug` — global debug. Verbose; useful for short reproductions.
- `info,nomifun_mcp=trace` — info everywhere, trace for the MCP module.
- `warn,nomifun_conversation=info,nomifun_terminal=debug` — quieter overall, normal for the conversation engine, debug for terminals.
There is no separate `RUST_LOG` plumbing — `--log-level` (or its env-driven equivalent in the running host) is the single switch.
## See also
- [Web Server Deployment](../guides/web-server-deployment.md) — running `nomifun-web` with Docker, systemd, Caddy.
- [Running NomiFun as a Desktop App](../guides/desktop-app.md) — desktop-specific configuration.
- [API Overview](./api-overview.md) — what the backend exposes once it is configured and running.
- [Troubleshooting](./troubleshooting.md) — symptoms and fixes when configuration ends up wrong at runtime.
@@ -0,0 +1,194 @@
# 配置参考
NomiFun 读取的每一个参数与环境变量、它们的默认值,以及各自归属的文件。
所有取值都直接来自源码——本页没有列出的设置就不存在。
NomiFun 交付的是**一个**统一的 Rust 后端(`nomifun-app`,二进制
`nomicore`),以及两个嵌入它的宿主:
- `nomifun-desktop` —— Tauri 桌面外壳。在选定的 loopback 端口上以
`AuthPolicy::TrustLocalToken` 启动后端,并把每次启动生成的本地信任 secret
注入自己的 WebView。
- `nomifun-web` —— 独立的 Web/服务端宿主。默认以**已鉴权**模式启动同一
个后端,并在同一端口上提供 SPA。
两个宿主共享后端的同一组配置面;各自的 CLI 仅会覆盖它们自己拥有的那几项。
## `nomifun-web` 参数与环境变量
来源:[`apps/web/src/main.rs`](../../apps/web/src/main.rs)。
| 参数 | 环境变量 | 默认值 | 用途 |
|---|---|---|---|
| `--host` | `NOMIFUN_WEB_HOST` | `127.0.0.1` | 绑定的 IP。`0.0.0.0` 会接受 LAN/VPN/公网流量;大范围暴露前请先预置或完成首次设置。不解析主机名;非法输入将在启动阶段直接失败。 |
| `--port` | `NOMIFUN_WEB_PORT` | `8787` | TCP 端口。在同一个 socket 上提供 API、`/ws` WebSocket 与 SPA。 |
| `--data-dir` | `NOMIFUN_DATA_DIR` | 按用户的应用数据目录 | 后端数据目录(SQLite 数据库、智能体状态、日志、Bun 缓存)。默认是所有宿主共享的按用户位置——Windows 上是 `%LOCALAPPDATA%\NomiFun\Nomi`macOS 上是 `~/Library/Application Support/NomiFun/Nomi`Linux 上是 `$XDG_DATA_HOME/NomiFun/Nomi`。可用本参数或 `NOMIFUN_DATA_DIR`(按字面值,不附加后缀)覆盖;生产环境请使用绝对路径。 |
| `--dist` | `NOMIFUN_WEB_DIST` | `../../ui/dist` | 已构建 SPA 所在目录。在仓库之外部署时务必显式指定。 |
| `--admin-user` | `NOMIFUN_ADMIN_USERNAME` | `admin` | 预置首位管理员时使用的用户名。管理员存在后将被忽略。 |
| `--admin-password` | `NOMIFUN_ADMIN_PASSWORD` | — | 在启动时预置首位管理员密码,跳过交互式设置。管理员存在后将被忽略。 |
| `--insecure-no-auth` | `NOMIFUN_WEB_INSECURE_NO_AUTH` | `false` | 危险。完全禁用鉴权(桌面式本地模式)。仅可用于 loopback 或完全受信任的私有网络。 |
布尔环境变量接受 `1``true``yes``on`(不区分大小写)。
## `nomicore`(后端)参数
来源:[`crates/backend/nomifun-app/src/cli.rs`](../../crates/backend/nomifun-app/src/cli.rs)。
下面是独立 `nomicore` 二进制对外暴露的参数。两个宿主会构造一个带默认值
`Cli`,仅覆盖各自拥有的那部分——所以单独运行后端时这些参数同样适用。
| 参数 | 默认值 | 用途 |
|---|---|---|
| `--host` | `127.0.0.1``DEFAULT_HOST` | 监听的主机地址。 |
| `--port` | `25808``DEFAULT_PORT` | 监听端口。 |
| `--data-dir` | 按用户的应用数据目录 | 数据库 + 文件存储根目录。通过 clap 绑定 `NOMIFUN_DATA_DIR` 环境变量(按字面值);两者都未设置时解析 `default_data_dir()`——所有宿主共享的那个按用户位置。 |
| `--work-dir` | (无) | 会话工作区目录。回退顺序:`NOMIFUN_WORK_DIR` 环境变量 → 数据目录本身。 |
| `--app-version` | crate 版本 | 报告给扩展引擎用于做兼容性检查的宿主应用版本。 |
| `--local` | `false` | 独立 `nomicore` 的无鉴权本地模式。`nomifun-web --insecure-no-auth` 映射到同一策略。桌面外壳不使用该 flag,而是使用 `TrustLocalToken`。 |
| `--log-dir` | `<data-dir>/logs` | 滚动日志的目录。 |
| `--log-level` | `info` | 日志级别过滤。支持按 target 覆盖——例如 `info,nomifun_mcp=trace`。 |
子命令(供智能体 CLI 桥与诊断使用):
| 子命令 | 用途 |
|---|---|
| `mcp-requirement-stdio` | AutoWork requirement 声明工具的 MCP stdio server。 |
| `mcp-knowledge-stdio` | 每会话 knowledge search 的 MCP stdio server。 |
| `mcp-gateway-stdio` | Desktop Gateway 工具的 MCP stdio server。 |
| `mcp-open-stdio` | 暴露可靠 OS `open` 工具的 MCP stdio server。 |
| `mcp-computer-stdio` | 暴露 desktop computer-use 工具的 MCP stdio server。 |
| `mcp-browser-stdio` | 暴露 browser-use 工具的 MCP stdio server。 |
| `terminal-hook --event <kind>` | 一次性 terminal 生命周期 hook relay。 |
| `doctor` | 自检:填充智能体注册表,逐个探测 `$PATH` 上的每个 CLI,并打印一张按智能体维度的可用性表格。 |
| `tools` | 以 JSON 列出 Remote 能力名称与描述。 |
| `call <name> [json-args]` | 通过 `/v1` 调用运行中实例上的 Remote 能力。 |
| `agent "<goal>"` | `nomi_agent_run` 能力的便捷包装。 |
## 共享环境变量
下列变量由后端读取,不论被哪个宿主嵌入。
| 环境变量 | 读取方 | 作用 |
|---|---|---|
| `NOMIFUN_DATA_DIR` | 所有宿主 | 当宿主选择遵循该值时,作为后端数据目录的真值来源。桌面外壳会附加 `/Nomi`:设置该环境变量时目录为 `$NOMIFUN_DATA_DIR/Nomi`;未设置时目录为按用户的应用数据默认值(见[下文](#数据目录与工作目录的语义))。独立 Web 宿主与 `nomicore` 二进制则按字面值将其作为 `--data-dir` 的默认值(不附加任何后缀)。 |
| `NOMIFUN_WORK_DIR` | `nomicore` | `--work-dir`(按会话区分的工作区根)的回退值。 |
| `JWT_SECRET` | `nomifun-app` | 用于签发会话 JWT 的密钥。解析顺序见 [鉴权密钥解析](#鉴权密钥解析)。 |
| `NOMIFUN_HTTPS` | `nomifun-auth::CookieConfig` | 取真值时,会话与 CSRF cookie 会带上 `Secure` 标记和 `SameSite=Strict`。当应用通过 HTTPS 暴露(TLS 反向代理等)时请打开。默认 `false` → 不带 `Secure` 标记,`SameSite=Lax`。 |
| `SHELL` | 智能体引擎(Linux/macOS) | 智能体引擎派生子进程时使用的 shell。在 systemd 下的 Linux 服务器上请显式设置(系统账户通常没有 `$SHELL`)。 |
| `NOMIFUN_URL` | `nomicore call`, `nomicore agent` | 调用 Remote capability 时使用的运行中实例 base URL。 |
| `NOMIFUN_COMPANION_TOKEN` | `nomicore call`, `nomicore agent` | 访问 `/v1` Remote capability 路由的 companion access token。 |
代码库不集成 `SENTRY_DSN`:这个环境变量并未被读取。
## 后端常量
来源:[`crates/backend/nomifun-common/src/constants.rs`](../../crates/backend/nomifun-common/src/constants.rs)。
这些是编译期值,不是环境变量——列在这里只是为了让运维方了解相关上限。
| 常量 | 取值 | 用途 |
|---|---|---|
| `DEFAULT_HOST` | `127.0.0.1` | `nomicore` 的默认 `--host`。 |
| `DEFAULT_PORT` | `25808` | `nomicore` 的默认 `--port`。(Web 宿主将其覆写为 `8787`。) |
| `BODY_LIMIT` | `10 MiB` | 应用于每条路由的默认请求体大小限制。需要更大的路由(例如 `/api/fs/upload`)会安装自己的更大限制。 |
| `UPLOAD_MAX_SIZE` | `30 MiB` | 文件上传路由(`/api/fs/upload`)的上限。 |
| `REMOTE_IMAGE_MAX_SIZE` | `5 MiB` | 下载聊天中引用的远程图片时的上限。 |
| `COOKIE_NAME` | `nomifun-session` | 会话 cookie。 |
| `CSRF_COOKIE_NAME` | `nomifun-csrf-token` | CSRF cookie(不是 HttpOnly——JavaScript 需要读取它)。 |
| `CSRF_HEADER_NAME` | `x-csrf-token` | 与 CSRF cookie 值对应的请求头(Double Submit Cookie 模式)。 |
| `COOKIE_MAX_AGE_DAYS` | `30` | Cookie 的 `Max-Age`。 |
| `SESSION_EXPIRY` | `24h` | JWT 在需要刷新前的有效期。 |
| `HEARTBEAT_INTERVAL_MS` / `HEARTBEAT_TIMEOUT_MS` | `30000` / `60000` | WebSocket 的心跳 ping/pong。 |
## 数据目录与工作目录的语义
- `data-dir` 存放 SQLite 数据库(`nomifun-backend.db*`)、各智能体状态、
Bun 缓存、日志文件,以及任何嵌入式扩展数据。把它当成普通数据库来
对待——做好备份、限制权限。两个同时运行的后端共享它的情况已被机制
性地阻止(见下面的服务器锁)。
- 三个宿主(`nomifun-desktop``nomifun-web`、独立的 `nomicore`
二进制)通过 `nomifun_app::cli::default_data_dir()` 解析出**同一个
默认**数据目录:Windows 上是 `%LOCALAPPDATA%\NomiFun\Nomi`macOS
上是 `~/Library/Application Support/NomiFun/Nomi`Linux 上是
`$XDG_DATA_HOME/NomiFun/Nomi`(通常为 `~/.local/share/NomiFun/Nomi`),
`dirs` crate 解析;当 OS 报告不出用户目录时的极端回退是
`<system temp>/nomifun-data/Nomi`。所有宿主共用一个默认值是有意为
之:开发循环(`bun run serve:web``dev:web``dev`)与已安装
的桌面应用读写同一份状态——配置一次提供商或伙伴,处处可测;排查
问题也只需要看一个目录。想要隔离的沙箱时,把 `NOMIFUN_DATA_DIR`
`--data-dir` 指到别处即可。
- 后端启动时(早于打开数据库)会对 `{data_dir}/server.lock` 取一把
OS 级**排他锁**。同一数据目录上的第二个后端进程会快速失败,错误
信息会指出持有者(pid + 可执行文件名)并给出两条出路:关掉另一个
实例,或用 `NOMIFUN_DATA_DIR` / `--data-dir` 给这一个指一个独立
目录。锁是 advisory 的(经 `fs2``flock` / `LockFileEx`),进程
退出或崩溃时由 OS 自动释放——残留的 `server.lock` 文件无害。
`nomicore doctor``mcp-*` stdio 子命令不取这把锁(doctor 设计上
就允许与运行中的服务器并存)。
- `work-dir` 存放按会话区分的工作区。未设置时按以下顺序解析:
`--work-dir` → 非空的 `NOMIFUN_WORK_DIR` 环境变量 → 数据目录本身。
会话会在 `<work-dir>/conversations/` 下创建子目录;删除会话同时
删除其工作区。
- 桌面外壳使用上述共享默认值。设置 `NOMIFUN_DATA_DIR` 后会附加
`/Nomi`:目录变为 `$NOMIFUN_DATA_DIR/Nomi`——覆盖语义不变。旧版
构建默认在 `<system temp>/nomifun-data/Nomi`;首次以新默认启动时,
既有的 temp 根安装会被自动搬迁(一次性;旧目录保留为备份,数据库
中存储的绝对路径会被改写)。
- Web 宿主按字面值使用该值——`--data-dir`(或 `NOMIFUN_DATA_DIR`
原样生效,不附加 `/Nomi` 后缀——因此 Docker`/data`)与 systemd
`/var/lib/nomifun`)部署不受影响。两者都未设置时,回退到同一个
共享的按用户默认目录;旧的相对 `data` 默认值已不复存在。
## 鉴权密钥解析
`JwtService` 由单一密钥构造;`AppServices::from_config` 按以下顺序解析它:
1. 若已设置,使用 `JWT_SECRET` 环境变量。
2. 否则,使用系统用户行(`system_default_user.jwt_secret`)中持久化的值。
3. 否则,生成一个全新的强随机密钥,并**持久化到数据库**供后续启动使用。
修改密码流会顺带轮换 JWT 密钥,使所有现有会话失效。
同一密钥还会用于派生加密钥(`derive_encryption_key`),用于对存储在
数据库中的机密(提供商密钥、MCP OAuth token 等)做静态加密。
## TLS / HTTPS Cookie 处理
NomiFun 自身不做 TLS 终止——请在前面放一个负责 TLS 终止的反向代理
Caddy、nginx 等)。届时:
- 设置 `NOMIFUN_HTTPS=true`,使 cookie 带上 `Secure` 标记和
`SameSite=Strict`。否则浏览器会在 HTTPS 响应上拒收 `Secure` cookie
登录看似会无声失败。
- `/ws` 上的 WebSocket 升级无需额外的请求头即可穿过任何符合标准的
代理;Caddy 开箱即用。
可参考 [`guides/web-server-deployment.md`](../guides/web-server-deployment.md)
中完整的 Caddy + Docker 示例。
## 日志
- 所有日志同时写入 stdout(让 `journalctl`/`docker logs` 能捕捉到)以及
`<log-dir>/nomicore.log` 上的按日滚动文件。
- `--log-level` 接受完整的 [`tracing` `EnvFilter`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html)
指令:一个全局级别,或一组以逗号分隔的按 target 覆盖项。
示例:
- `info` —— 全局 info。
- `debug` —— 全局 debug。较啰嗦;适合短时复现。
- `info,nomifun_mcp=trace` —— 默认 infoMCP 模块为 trace。
- `warn,nomifun_conversation=info,nomifun_terminal=debug` —— 整体
更安静;会话引擎为 normal/info;终端为 debug。
不存在另一套 `RUST_LOG` 通路——`--log-level`(或宿主中等价的环境驱动
开关)是唯一的总开关。
## 另见
- [Web 服务部署](../guides/web-server-deployment.md) —— 用 Docker、
systemd、Caddy 运行 `nomifun-web`
- [作为桌面应用运行 Nomi](../guides/desktop-app.md) —— 桌面端专属配置。
- [API 概览](./api-overview.zh.md) —— 配置完成并启动后,后端对外暴露
了什么。
- [疑难排查](./troubleshooting.zh.md) —— 配置在运行时出错时的症状与
修复方法。
+114
View File
@@ -0,0 +1,114 @@
# FAQ
Honest, short answers to questions that come up over and over. For deeper explanations follow the links.
## What is the difference between NomiFun and nomifun?
**NomiFun** is the open-source project and the user-facing product name: the desktop app, WebUI surface, codebase, workspace, GitHub repository, and brand all use this spelling.
In this codebase the lowercase form `nomifun` shows up only as a literal technical identifier — package names (`nomifun-app`, `nomifun-web`, …), the desktop bundle id `com.nomifun.desktop`, environment variables prefixed `NOMIFUN_`, repository directories. Anywhere it is shown to a human as the app or project brand, use "NomiFun".
## Is there a hosted version?
No. NomiFun is a self-host project. There is no SaaS instance, no managed login at a `nomifun.com` URL, no central account system to sign up for. The two ways to use it are:
- Install the desktop app and run it locally — `nomifun-desktop`.
- Deploy `nomifun-web` on a server you control. See [Web Server Deployment](../guides/web-server-deployment.md).
You can expose your desktop install temporarily to other devices (your phone, your laptop) using [WebUI Remote Access](../guides/webui-remote-access.md), but that is a per-instance feature, not a hosted service.
## Does the desktop app require login?
No. The desktop WebView is trusted through a per-boot local trust token injected
by the Tauri shell. There is no login screen for the desktop window, but the
embedded backend is not a blanket no-auth localhost server.
The web host is the opposite: it requires login by default. Mixing the two is
intentional — the desktop shell can trust its own WebView, while a
network-reachable host needs an actual auth boundary.
## Where is my data stored?
In the **data directory**. Its location depends on which host you are running:
- **Desktop**: defaults to the **per-user application-data dir**`%LOCALAPPDATA%\NomiFun\Nomi` on Windows, `~/Library/Application Support/NomiFun/Nomi` on macOS, `$XDG_DATA_HOME/NomiFun/Nomi` (usually `~/.local/share/NomiFun/Nomi`) on Linux. Set `NOMIFUN_DATA_DIR=<absolute path>` and the dir becomes `$NOMIFUN_DATA_DIR/Nomi` (unchanged override semantics). Older builds stored data under `<system temp>/nomifun-data/Nomi`; if such an install exists it is relocated to the new location automatically on launch, and the old dir is kept as a backup.
- **Web (`nomifun-web`)**: whatever you pass to `--data-dir` (or `NOMIFUN_DATA_DIR`), taken literally — no `/Nomi` suffix. With neither set it defaults to the **same per-user dir as the desktop app**, so a dev `bun run serve:web` and the installed app see one shared state.
- **Docker**: the named volume defined in the compose file (`nomifun-data` mounted at `/data`).
The data directory contains the SQLite database (`nomifun-backend.db*`), per-agent state, the Bun cache, log files, and any embedded extension data. Back it up like a database. Because every host defaults to the same directory, the backend guards it with an exclusive `server.lock` — a second backend instance on the same data dir fails fast instead of corrupting state.
For the full lifecycle and `work-dir` semantics, see [Configuration Reference](./configuration.md#data-directory-and-work-directory-semantics).
## Which agents and providers are supported?
The "Agent CLIs" NomiFun runs as ACP (Agent Client Protocol) backends include `claude`, `codex`, `gemini`, `nomi`, `codebuddy`, `qwen`, and `opencode`. Each one is a separate CLI you install on your system; NomiFun discovers them on `PATH` and the registry hydrates from there. Run `nomicore doctor` to see what your install detects.
For raw model access (e.g. provider keys, custom OpenAI-compatible endpoints), the system supports configurable providers via `/api/providers/*` and the in-app settings UI. You bring the API keys; NomiFun stores them encrypted at rest in the data directory.
There is no built-in agent that calls out to a hosted NomiFun endpoint — there is no such endpoint. Every agent / provider you configure is something you control.
## Is NomiFun really local-only?
The application logic and your data are local. The agents you connect to may not be — most CLI agents make outbound calls to their respective providers (Anthropic, OpenAI, Google, …). That is between you and the agent.
What NomiFun itself does over the network:
- Optional update checks (system info / check-update endpoint).
- Extension marketplace (`/api/hub/*`) — only if you actively use it.
- Whatever your configured agents and providers do — typically API calls to LLM providers.
There is no telemetry pipeline, no analytics SDK, no `SENTRY_DSN` integration in the binary. The backend does not phone home on its own.
## What about extensions and skills — what runs them?
Extensions (themes, assistants, channel plugins, settings tabs) are loaded by `nomifun-extension` from the data directory. Skills are bundles of prompts/instructions resolved into the agent's context per-conversation. Both are local files under your data dir; the marketplace flow simply downloads them into that directory.
The agent CLI binaries are not extensions — they are external CLIs that NomiFun launches as child processes via the ACP protocol.
## Can I run agents on a different machine from the UI?
Yes — that is exactly what `nomifun-web` is for. Deploy the web host on the machine where you want the agents (and their CLIs, and their network access) to live, and access the SPA from any browser. See [Web Server Deployment](../guides/web-server-deployment.md).
For lighter-weight remote access from a phone or another laptop without spinning up a separate server, [WebUI Remote Access](../guides/webui-remote-access.md) exposes an existing desktop install over the LAN.
## What is the licence?
**Apache-2.0**, declared in the workspace `Cargo.toml`. You can use, modify, redistribute, and bundle the code under the standard Apache-2.0 terms — including in commercial products — provided you keep the licence and notice intact.
## Are there prebuilt installers?
Not yet. Desktop bundles can be built locally, macOS Developer ID signing is
scripted through `bun run build:signed`, and updater artifacts can be generated
with `bun run build:updater`; there is not yet an official public release
channel or registry-backed installer feed. Until then, the supported install
paths are:
- **Desktop**: `bun install && bun run build:ui && cargo run -p nomifun-desktop` (or `cargo build --release -p nomifun-desktop`).
- **Server**: build from source (`cargo build --release -p nomifun-web`) or `docker compose up -d --build`.
When prebuilt installers ship, they will be linked from the project README and the [getting-started guide](../getting-started/).
## I lost my admin password
On `nomifun-web`, the in-band recovery flow is the local-only WebUI route (`POST /api/webui/reset-password`) — you can hit it from the same machine the server runs on, and it generates a fresh random password and prints it. From a remote machine you cannot recover the password through the API.
The fallback is to stop the server, edit the database directly (the `system_default_user.password_hash` column), and restart. The simplest reset is to set the hash to an empty string — the next boot then treats the install as needing first-run setup again, and the next visitor can claim the admin.
For desktop installs there is no password for the local WebView. WebUI remote
access has its own admin password because it is reachable from another browser.
## Is there a "single binary" build?
Yes for the server: `nomifun-web` is one statically-linked Rust binary plus the `ui/dist/` directory it serves. SQLite is statically linked, TLS uses rustls — there is no `libsqlite`, no `openssl` dependency at runtime. Build with `cargo build --release -p nomifun-web`.
The agent runtime (bun) is *not* embedded by default; install it system-wide or use the `NOMIFUN_EMBED_BUN=1` build flag to bundle it into the binary. See the [bun-on-PATH](../guides/web-server-deployment.md#bun-must-be-on-the-system-path) section.
The desktop shell also produces a single binary (`nomifun-desktop`), but for distribution you typically want the platform-native packaging via `bun run build` once installer signing is set up.
## See also
- [Configuration Reference](./configuration.md)
- [API Overview](./api-overview.md)
- [Troubleshooting](./troubleshooting.md)
- [Web Server Deployment](../guides/web-server-deployment.md)
- [Running NomiFun as a Desktop App](../guides/desktop-app.md)
+173
View File
@@ -0,0 +1,173 @@
# 常见问题
针对反复被问到的问题给出直白、简短的回答。如需更深入的解释,请顺着
链接前往对应文档。
## NomiFun 与 nomifun 有什么区别?
**NomiFun** 是这个开源项目与面向用户的产品名:桌面应用、WebUI 界面、
代码库、工作区、GitHub 仓库和品牌都使用这个写法。
在本代码库里,小写形式 `nomifun` 仅作为字面意义上的技术标识符出现
——包名(`nomifun-app``nomifun-web` 等)、桌面 bundle id
`com.nomifun.desktop`、以 `NOMIFUN_` 为前缀的环境变量、仓库目录。
任何作为应用或项目品牌展示给人看的地方,都使用 "NomiFun"。
## 有托管版本吗?
没有。NomiFun 是一个自托管项目。不存在 SaaS 实例、不存在
`nomifun.com` 上的托管登录、不存在你可以注册的中心账户系统。使用它的
两种方式是:
- 安装桌面应用并在本地运行——`nomifun-desktop`
- 在你自己控制的服务器上部署 `nomifun-web`。参见
[Web 服务部署](../guides/web-server-deployment.md)。
你可以使用 [WebUI 远程访问](../guides/webui-remote-access.md) 把桌面
安装临时暴露给其他设备(手机、笔记本),但这是一项按实例的功能,
不是托管服务。
## 桌面应用需要登录吗?
不需要。桌面 WebView 通过 Tauri 外壳注入的每启动本地信任 token 被信任。
桌面窗口没有登录界面,但嵌入式后端并不是一个对所有 localhost 调用者都无鉴权
开放的服务。
Web 宿主与之相反:默认要求登录。两者的差异是有意的——桌面外壳可以信任
自己的 WebView,但网络可达的宿主必须有真正的鉴权边界。
## 我的数据存在哪里?
存在**数据目录**里。具体位置取决于你运行的是哪种宿主:
- **桌面端**:默认在**按用户的应用数据目录**——Windows 上是
`%LOCALAPPDATA%\NomiFun\Nomi`macOS 上是
`~/Library/Application Support/NomiFun/Nomi`Linux 上是
`$XDG_DATA_HOME/NomiFun/Nomi`(通常为 `~/.local/share/NomiFun/Nomi`)。
设置 `NOMIFUN_DATA_DIR=<absolute path>` 后目录会变成
`$NOMIFUN_DATA_DIR/Nomi`(覆盖语义不变)。旧版构建把数据存在
`<system temp>/nomifun-data/Nomi` 下;若存在这样的安装,启动时会
自动搬迁到新位置,旧目录保留为备份。
- **Web`nomifun-web`**:你传给 `--data-dir`(或
`NOMIFUN_DATA_DIR`)的任何位置,按字面值生效——不附加 `/Nomi`
后缀。两者都未设置时,默认与桌面应用是**同一个按用户目录**,因此
开发中的 `bun run serve:web` 与已安装的应用看到的是同一份状态。
- **Docker**compose 文件中定义的命名卷(`nomifun-data`,挂载到
`/data`)。
数据目录里有 SQLite 数据库(`nomifun-backend.db*`)、各智能体状态、
Bun 缓存、日志文件,以及任何嵌入式扩展数据。请像对待数据库一样备份
它。由于所有宿主默认指向同一个目录,后端用一把排他的 `server.lock`
守住它——同一数据目录上的第二个后端实例会快速失败,而不是悄悄破坏
状态。
完整生命周期与 `work-dir` 语义请参见
[配置参考](./configuration.zh.md#数据目录与工作目录的语义)。
## 支持哪些智能体与提供商?
NomiFun 作为 ACPAgent Client Protocol)后端运行的"智能体 CLI"
包括 `claude``codex``gemini``nomi``codebuddy``qwen`
`opencode`。每一个都是一个独立的 CLI,需要你自己在系统上安装;
NomiFun 会从 `PATH` 上发现它们,并据此填充注册表。运行
`nomicore doctor` 即可看到你的安装能识别到哪些。
至于裸模型访问(例如提供商密钥、自定义的 OpenAI 兼容端点),系统
通过 `/api/providers/*` 与应用内的设置 UI 支持可配置的提供商。API
密钥由你提供;NomiFun 会以静态加密的方式把它们存到数据目录。
并不存在一个会调用某个托管 NomiFun 端点的内置智能体——根本就没有
这样的端点。你配置的每一个智能体 / 提供商都由你自己控制。
## NomiFun 真的纯本地吗?
应用逻辑与你的数据是本地的。但你接入的智能体未必——大多数 CLI 智能体
都会向各自的提供商(Anthropic、OpenAI、Google 等)发起出站调用。那
是你与该智能体之间的事。
NomiFun 自身在网络上做的事:
- 可选的更新检查(system info / check-update 端点)。
- 扩展市场(`/api/hub/*`)——仅在你主动使用时。
- 你已配置的智能体与提供商所做的——通常是去 LLM 提供商的 API 调用。
二进制中没有遥测管道、没有 analytics SDK,也没有 `SENTRY_DSN` 集成。
后端不会自行回传任何数据。
## 扩展与技能——它们由谁来运行?
扩展(主题、助手、频道插件、设置标签页)由 `nomifun-extension`
数据目录加载。技能则是一组提示词/指令,按会话被解析进智能体的上下文。
两者都是数据目录下的本地文件;市场流程只是把它们下载到该目录。
智能体 CLI 二进制不属于扩展——它们是 NomiFun 通过 ACP 协议作为子
进程派生的外部 CLI。
## 我能在与 UI 不同的机器上跑智能体吗?
可以——这正是 `nomifun-web` 的用途。把 Web 宿主部署到你希望智能体
(及其 CLI 和它们的网络访问)所在的机器上,然后从任何浏览器访问
SPA。参见 [Web 服务部署](../guides/web-server-deployment.md)。
如果只是想从手机或另一台笔记本做更轻量的远程访问,又不想另起一台
服务,[WebUI 远程访问](../guides/webui-remote-access.md) 可以把
现有的桌面安装暴露到 LAN 上。
## 许可证是什么?
**Apache-2.0**,在工作区 `Cargo.toml` 中声明。你可以在标准的
Apache-2.0 条款下使用、修改、再分发,并将代码打包——包括用于商业
产品——只要保留许可证与声明完整即可。
## 有预构建安装包吗?
还没有官方公开发布渠道。桌面包可以本地构建,macOS Developer ID 签名已通过
`bun run build:signed` 脚本接好,updater 产物可用 `bun run build:updater`
生成;但还没有官方 installer feed 或公开 registry。当前受支持的安装路径是:
- **桌面端**`bun install && bun run build:ui && cargo run -p
nomifun-desktop`(或 `cargo build --release -p nomifun-desktop`)。
- **服务端**:从源码构建(`cargo build --release -p nomifun-web`
或 `docker compose up -d --build`。
预构建安装包发布后,会从项目 README 与
[新手入门指南](../getting-started/) 链接出来。
## 我把管理员密码搞丢了
在 `nomifun-web` 上,带内的恢复流程是仅本地的 WebUI 路由
`POST /api/webui/reset-password`)——你可以从运行该服务的同一台
机器上调用它,它会生成一个全新的随机密码并打印出来。从远程机器
无法通过 API 恢复密码。
兜底方案是停止服务、直接编辑数据库(`system_default_user.password_hash`
列),然后重启。最简单的重置就是把哈希设为空字符串——下次启动
就会把当前安装当作需要重新进行首次启动设置,下一个访问者就可以
认领管理员。
桌面本地 WebView 没有密码。WebUI 远程访问有自己的管理员密码,因为它会被其他
浏览器访问。
## 有"单二进制"构建吗?
服务端有:`nomifun-web` 是一个静态链接的 Rust 二进制,再加上它要
提供的 `ui/dist/` 目录。SQLite 是静态链接的,TLS 用的是 rustls——
运行时没有 `libsqlite`、也没有 `openssl` 依赖。用
`cargo build --release -p nomifun-web` 即可构建。
智能体运行时(bun)默认*不*嵌入;请把它装到系统级,或者用
`NOMIFUN_EMBED_BUN=1` 构建标记把它打包进二进制。参见
[bun-on-PATH](../guides/web-server-deployment.md#bun-must-be-on-the-system-path)
小节。
桌面外壳同样可以产出单一二进制(`nomifun-desktop`),但用于分发时,
通常你想要的是 `bun run build` 通过平台原生打包流程产出的
产物(前提是安装包签名已配置好)。
## 另见
- [配置参考](./configuration.zh.md)
- [API 概览](./api-overview.zh.md)
- [疑难排查](./troubleshooting.zh.md)
- [Web 服务部署](../guides/web-server-deployment.zh.md)
- [作为桌面应用运行 NomiFun](../guides/desktop-app.zh.md)
@@ -0,0 +1,172 @@
# Troubleshooting
Symptoms you might hit running NomiFun, and the actual mechanism behind each one. If you find a problem that is not on this list, the source is the fastest reference — every behaviour described below is grounded in a specific file in `crates/backend/`.
## Backend port / connection problems
### `nomifun-web: invalid --host '<value>'`
The host argument must parse as an IP address (`127.0.0.1`, `0.0.0.0`, an explicit interface IP). Hostnames like `localhost` are not parsed — `nomifun-web` fails fast with this message rather than producing a cryptic socket error later. Pass an IP literal.
### `address already in use` on the configured port
Another process is bound to the same port. The web host uses `8787` by default (`NOMIFUN_WEB_PORT`). The desktop shell does not have this problem because it asks the OS for a free localhost port at startup and then tells the renderer over IPC.
To find the offender on Linux/macOS: `lsof -i :8787`. On Windows: `Get-NetTCPConnection -LocalPort 8787`. Either kill it, or change `--port` / `NOMIFUN_WEB_PORT`.
### Browser cannot reach the server on a non-loopback address
`nomifun-web` defaults to binding `127.0.0.1`. To accept LAN/VPN traffic, either pass `--host 0.0.0.0` or set `NOMIFUN_WEB_HOST=0.0.0.0`. Pre-seed the admin or complete first-run setup before doing this on a broadly reachable host. On Windows / macOS hosts also check the firewall — the OS may silently drop the connection.
If the goal is remote access from a phone or another device on the LAN, [WebUI Remote Access](../guides/webui-remote-access.md) usually wants less configuration than full server deployment.
## First-run admin and login problems
### `GET /api/auth/status` returns `needs_setup: true` after I started the server
This is the expected state on a fresh install when `NOMIFUN_ADMIN_PASSWORD` is not set. The first browser visitor's username + password become the admin via `POST /api/auth/setup`. Open the URL, fill in the form, and you are logged in.
If you want to close this window before the server is publicly reachable, set `NOMIFUN_ADMIN_PASSWORD` (and optionally `NOMIFUN_ADMIN_USERNAME`) before the first boot.
### `409 Conflict` on `/api/auth/setup`
An admin already exists. The setup endpoint is one-time only. Use `POST /login` instead, or — if the password has been lost on a self-hosted instance — recover via the local-only WebUI flow described in [WebUI Remote Access](../guides/webui-remote-access.md).
### Login appears to succeed but the next request gets `401`
Almost always a cookie problem behind a TLS proxy. The `Secure` flag is added to cookies only when `NOMIFUN_HTTPS=true`. On HTTPS responses without that flag, browsers reject the cookie outright and the next request has no session. Set `NOMIFUN_HTTPS=true` and reload.
A second cause: clock skew on the server. If the system clock is far off, the JWT may be considered expired by the same server that signed it. Make sure NTP is running.
### `Current password is incorrect` on change-password despite a correct password
The endpoint runs a constant-time bcrypt compare against the stored hash. If you suspect data corruption: stop the server, back up the data dir, and inspect the `system_default_user.password_hash` column. A surgical fix is possible (`/api/auth/internal/users/{id}/password` in local mode) but the easiest path is to restore from backup or re-bootstrap.
### "Username/password rejected" with a vaguely worded validation error
The validators reject a small set of obvious patterns — passwords under 8 characters, common dictionary entries, usernames outside `[a-zA-Z0-9_-]` or starting/ending with `-`/`_`. Pick something else.
## CSRF errors
### `403 CSRF token validation failed` on a POST/PUT/PATCH/DELETE
The `nomifun-csrf-token` cookie value must match the `x-csrf-token` request header. The middleware sets the cookie automatically on the first response that does not have one, so a freshly-loaded SPA acquires it on its first GET. This usually breaks for one of these reasons:
- The client assumes no-auth local mode while the server is running in authenticated mode (or vice versa). `nomicore --local` and `nomifun-web --insecure-no-auth` skip CSRF; normal `nomifun-web` requires it. The desktop shell uses `TrustLocalToken`, so its own WebView should not see CSRF failures unless the injected trust header/cookie flow is broken.
- A reverse proxy is stripping cookies or rewriting `Set-Cookie`. The standard Caddy/nginx configurations leave them alone; custom rewrite rules can break them.
- The browser has third-party cookie blocking that affects the deployment domain.
`/login`, `/api/auth/setup`, and `/api/auth/qr-login` are CSRF-exempt; CSRF only applies to the *post-login* state-changing routes.
## WebSocket disconnects
### Connection closes immediately with code 1008
Code 1008 is "policy violation" — the server uses it for two specific cases:
- "no token provided" — the WebSocket upgrade request did not carry a JWT.
- An `auth-expired` event followed by close — the token was present but invalid or expired.
Both are usually caused by a stale token. Refresh the token via `GET /api/ws-token` and reconnect. If you see this immediately after login, check that cookies are flowing correctly (see the cookies-don't-stick case above) and that `Sec-WebSocket-Protocol` (or whichever header you use) is reaching the server unmodified.
### WebSocket connects then quietly stops receiving events
The server pings every 30 s and considers a client dead at 60 s. If the network drops a connection silently (mobile NAT, captive portals, a flaky proxy), the client side still appears connected until the server prunes it. The client is expected to reconnect; the SPA does this automatically. If you wrote a custom client, implement an exponential-backoff reconnect on the close event.
## "Agent CLI not found" and bun problems
### Conversation fails immediately with "agent not available" / "command not found"
The agent engine spawns ACP agent CLIs (`claude`, `codex`, `gemini`, `nomi`, `codebuddy`, …) and they must be on the **process** `PATH`. The process PATH is enhanced at startup (`nomifun_runtime::enhance_process_path`) but if the binary lives somewhere unusual it can still be missed.
Run the doctor:
```bash
nomicore doctor
```
This hydrates the agent registry and probes every CLI on `$PATH`, printing a per-agent availability table. Run it from the same shell that launched the app to see exactly what the app sees. If an agent is missing, install its CLI or add its bin directory to `PATH` and restart.
### Under systemd: `bun: command not found`
The agent engine requires **`bun ≥ 1.3.13`**. A `nologin` system account does not see `~/.bun/bin/`; install bun system-wide (`sudo install ~/.bun/bin/bun /usr/local/bin/bun`) or build with `NOMIFUN_EMBED_BUN=1` so bun is bundled into the binary and self-extracts into the data dir on first run. See [Web Server Deployment](../guides/web-server-deployment.md#bun-must-be-on-the-system-path) for the worked recipe.
Verify with `sudo -u nomifun -s -- which bun` after installing.
### "bun runtime extraction" log line followed by no agent activity
The embedded-bun build extracts bun into the data directory on first run. If extraction fails (typically permissions), the agent engine has no runtime. Check the data-dir for the bun binary, ensure the service user owns the data dir, and look in the log for the actual extraction error.
## Office preview
### Word/Excel/PPT preview returns "LibreOffice not detected"
The `/api/star-office/detect` route probes the system for a LibreOffice install. The Office preview features (`/api/word-preview/*`, `/api/excel-preview/*`, `/api/ppt-preview/*`, `/api/document/convert`) need LibreOffice to render documents.
- Linux: `apt install libreoffice` (or distribution equivalent).
- macOS: `brew install --cask libreoffice`.
- Windows: install from libreoffice.org.
After installing, restart the backend so it re-detects.
### Preview iframe stays blank
The Office preview routes spawn LibreOffice subprocesses and proxy them via `/api/ppt-proxy/*` and `/api/office-watch-proxy/*`. These proxy routes are **public** (no auth) on purpose — the iframe content needs to load without sending the SPA's session cookie. If your reverse proxy strips the URL path components or applies auth at the edge to `/api/*`, exempt the proxy paths.
## Data directory permissions
### Server starts but database writes fail / "unable to open database file"
The configured data directory must be writable by the process. Common cases:
- Running under systemd with `User=nomifun` but a data dir owned by another user. Fix: `chown -R nomifun:nomifun /var/lib/nomifun`.
- A read-only mount (`RootDirectory=`, `ProtectHome=yes`, …) covering the data path. Drop the over-broad sandbox; keep the moderate hardening from the shipped unit (`NoNewPrivileges=yes`, `PrivateTmp=yes`).
- On Docker, mounting a host directory whose UID does not match the container's. Use a named volume instead, or `chown` the host directory to the right UID.
The desktop shell's default data dir is the **per-user application-data location** (`%LOCALAPPDATA%\NomiFun\Nomi` on Windows, `~/Library/Application Support/NomiFun/Nomi` on macOS, `$XDG_DATA_HOME/NomiFun/Nomi` on Linux), which is writable by the launching user by construction. Set `NOMIFUN_DATA_DIR=<absolute path>` and the dir becomes `$NOMIFUN_DATA_DIR/Nomi`. Legacy installs under `<system temp>/nomifun-data/Nomi` are relocated to the new default automatically on launch (the old dir is kept as a backup); if the relocation cannot complete, the app keeps starting from the legacy dir and retries next launch.
### `data directory ... is already in use by another running NomiFun backend`
Every host (desktop shell, `nomifun-web`, the `nomicore` binary) defaults to the **same** per-user data directory, and the backend takes an OS-level exclusive lock on `{data_dir}/server.lock` at startup — a second backend on the same directory fails fast with this message instead of silently corrupting shared state. The classic trigger: the desktop app is still running and you start `bun run serve:web` / `dev:web` (or vice versa). Two ways out: close the other instance (the message names the holder's pid and executable), or give the new one its own directory via `NOMIFUN_DATA_DIR` / `--data-dir`. The lock is released by the OS when the holder exits or crashes; a leftover `server.lock` file is harmless. `nomicore doctor` and the `mcp-*` stdio subcommands do not take the lock, so they are unaffected.
## Docker specifics
### `docker compose up` builds, starts, then exits immediately
Read the logs (`docker compose logs nomifun`). The most common causes are:
- The data volume is empty *and* `NOMIFUN_ADMIN_PASSWORD` is missing — the server runs fine, but you have no way in until you complete first-run setup over HTTP. This is not actually a failure; it is a state.
- The `--dist` directory inside the image points at the wrong path. The shipped Dockerfile copies `ui/dist` to `/opt/nomifun/web` and the `CMD` references that — only an issue if you have customised the Dockerfile.
- A bind-mounted data dir that the container user cannot write to.
### Logs say `nomifun-web: embedded backend + SPA on one port` but the browser cannot connect
Confirm the port mapping (`docker compose ps`). The default compose file publishes `8787:8787`; if you put Caddy in front, it should be `expose: ["8787"]` instead. Connecting to the wrong port is the usual culprit.
### Build is slow or fails behind a corporate proxy
Pass a cargo registry mirror at build time:
```bash
docker build --build-arg CARGO_REGISTRY_MIRROR=https://rsproxy.cn/index/ -t nomifun-web:local .
```
(Or whichever mirror your environment uses.)
## Logging
The backend writes to **both** stdout and a daily-rolling file at `<log-dir>/nomicore.log` (default `<data-dir>/logs`). When something is going wrong:
- The `journalctl -u nomifun-web` / `docker compose logs nomifun` view shows the last few minutes.
- The rotating file under `<log-dir>` keeps history.
- Crank up the level for the affected module: `--log-level info,nomifun_mcp=trace` for MCP issues, `info,nomifun_terminal=debug` for terminals, `info,nomifun_conversation=debug` for agent conversations.
## When all else fails
Read the source. Every route handler is in the `routes.rs` (or `routes/`) file of its owning crate; the assembly is in `crates/backend/nomifun-app/src/router/routes.rs`. The error messages thrown by handlers are the literal strings that appear in HTTP responses, so a quick `grep` for the exact message usually lands you on the offending check in seconds.
## See also
- [Configuration Reference](./configuration.md) — every flag and env var.
- [API Overview](./api-overview.md) — orientation to routes, auth, and the WebSocket model.
- [FAQ](./faq.md) — short answers to the most common "is X true?" questions.
@@ -0,0 +1,270 @@
# 疑难排查
运行 NomiFun 时可能遇到的症状,以及它们背后真实的成因。如果你遇到的
问题不在本表里,源码就是最快的参考——本页描述的每一个行为都对应
`crates/backend/` 下的某个具体文件。
## 后端端口 / 连接问题
### `nomifun-web: invalid --host '<value>'`
host 参数必须能被解析为 IP 地址(`127.0.0.1``0.0.0.0`、某个具体
网卡的 IP)。`localhost` 这样的主机名不会被解析——`nomifun-web`
直接以这条信息快速失败,而不是稍后再抛出晦涩的 socket 错误。请传入
一个 IP 字面量。
### 配置端口上出现 `address already in use`
有别的进程占着同一个端口。Web 宿主默认使用 `8787`
`NOMIFUN_WEB_PORT`)。桌面外壳没有这个问题——它启动时会向 OS 索要
一个空闲的 localhost 端口,然后通过 IPC 告知渲染端。
在 Linux/macOS 上找出占用者:`lsof -i :8787`。Windows 上:
`Get-NetTCPConnection -LocalPort 8787`。要么把它杀掉,要么修改
`--port` / `NOMIFUN_WEB_PORT`
### 浏览器无法在非 loopback 地址上访问服务
`nomifun-web` 默认绑定 `127.0.0.1`。要接受 LAN/VPN 流量,需要传入
`--host 0.0.0.0` 或设置 `NOMIFUN_WEB_HOST=0.0.0.0`。如果该主机可被较大范围访问,
请先预置管理员或完成首次设置。在 Windows / macOS 宿主上还要检查防火墙——OS
可能默默丢弃连接。
如果目标只是从手机或同一 LAN 上的其他设备进行远程访问,
[WebUI 远程访问](../guides/webui-remote-access.md) 通常比完整的服务
部署更省事。
## 首次启动管理员与登录问题
### 启动服务后 `GET /api/auth/status` 返回 `needs_setup: true`
这是全新安装且没有设置 `NOMIFUN_ADMIN_PASSWORD` 时的预期状态。第一个
访问浏览器的人输入的用户名 + 密码会通过 `POST /api/auth/setup` 成为
管理员。打开 URL,填写表单,登录即可完成。
如果希望在服务对外可达之前关闭这个时间窗口,请在首次启动前设置
`NOMIFUN_ADMIN_PASSWORD`(可选地再设置 `NOMIFUN_ADMIN_USERNAME`)。
### `/api/auth/setup` 返回 `409 Conflict`
管理员已经存在了。setup 端点只能调用一次。请改用 `POST /login`
如果在自托管实例上忘了密码,可通过
[WebUI 远程访问](../guides/webui-remote-access.md) 描述的仅本地 WebUI
流程恢复。
### 登录看起来成功,但下一个请求返回 `401`
绝大多数情况是 TLS 反向代理后的 cookie 问题。`Secure` 标记只在
`NOMIFUN_HTTPS=true` 时才会被加到 cookie 上。在没有该标记的 HTTPS
响应中,浏览器会直接拒收 cookie,下一个请求于是没有会话。设置
`NOMIFUN_HTTPS=true` 然后重新加载即可。
第二个原因:服务器时钟漂移。如果系统时钟差得太远,签发它的同一台
服务器都会把 JWT 当作过期。请确认 NTP 在跑。
### 修改密码时密码明明对,却报 `Current password is incorrect`
该端点会用恒定时间的 bcrypt 比对存储的哈希。如果你怀疑数据损坏:
停止服务、备份数据目录,然后查看 `system_default_user.password_hash`
列。可以做精细的修复(在本地模式下使用
`/api/auth/internal/users/{id}/password`),但最简单的路径是从备份
恢复或重新初始化。
### 用一个含糊的校验错误拒绝"用户名/密码"
校验器拒绝一小撮显而易见的模式——长度小于 8 的密码、常见词典词、
不在 `[a-zA-Z0-9_-]` 中的用户名,或者以 `-`/`_` 开头/结尾的用户名。
请换一个。
## CSRF 错误
### 在 POST/PUT/PATCH/DELETE 上出现 `403 CSRF token validation failed`
`nomifun-csrf-token` cookie 的值必须与 `x-csrf-token` 请求头匹配。
中间件会在第一个没带它的响应中自动设置该 cookie,因此一个新加载的
SPA 会在它的第一个 GET 请求中拿到。这通常因为以下几种原因失败:
- 客户端假设服务处于无鉴权本地模式,但服务实际运行在已鉴权模式下
(或反过来)。`nomicore --local``nomifun-web --insecure-no-auth`
会跳过 CSRF;普通 `nomifun-web` 需要它。桌面外壳使用 `TrustLocalToken`
自己的 WebView 正常不应看到 CSRF 失败,除非注入的本地信任 header/cookie
流程被破坏。
- 反向代理剥掉了 cookie 或重写了 `Set-Cookie`。Caddy/nginx 的标准配置
不会动它们;自定义 rewrite 规则可能会。
- 浏览器的第三方 cookie 屏蔽影响到了部署域名。
`/login``/api/auth/setup``/api/auth/qr-login` 对 CSRF 豁免;CSRF
仅作用于*登录后*的状态变更类路由。
## WebSocket 断连
### 连接立刻以 1008 关闭
1008 是"策略违规"——服务端在两种特定情况下使用:
- "no token provided" —— WebSocket 升级请求没有携带 JWT。
- 一条 `auth-expired` 事件后接关闭——token 存在但无效或已过期。
这两种通常都是 token 过期导致。通过 `GET /api/ws-token` 刷新 token
后重连即可。如果你在登录后立刻看到这一现象,请确认 cookie 是否被
正常透传(参见上面 cookie 不生效的小节),并确认
`Sec-WebSocket-Protocol`(或你使用的请求头)原样到达了服务端。
### WebSocket 连上之后悄悄不再收到事件
服务端每 30 秒 ping 一次,60 秒未响应则视为客户端已死。如果网络静默
地断掉了连接(移动 NAT、强制门户、不靠谱的代理),在服务端清理它
之前客户端侧仍显示已连接。客户端应当负责重连;SPA 会自动重连。如果
你写了自己的客户端,请在 close 事件上实现指数退避重连。
## "Agent CLI not found" 与 bun 相关问题
### 会话立即以 "agent not available" / "command not found" 失败
智能体引擎会派生 ACP 智能体 CLI(`claude``codex``gemini`
`nomi``codebuddy` 等),它们必须出现在**进程**的 `PATH` 里。
进程 PATH 在启动时被增强(`nomifun_runtime::enhance_process_path`),
但若二进制位置不寻常,仍可能被错过。
跑一下 doctor
```bash
nomicore doctor
```
它会填充智能体注册表,逐个探测 `$PATH` 上的每个 CLI,并打印一张按
智能体维度的可用性表格。务必在与启动应用相同的 shell 中运行它,
以看到应用真正看到的内容。如果某个智能体缺失,请安装其 CLI 或把它
的 bin 目录加入 `PATH` 后重启。
### 在 systemd 下:`bun: command not found`
智能体引擎要求 **`bun ≥ 1.3.13`**。一个 `nologin` 的系统账户看不到
`~/.bun/bin/`;请把 bun 装到系统级(`sudo install ~/.bun/bin/bun
/usr/local/bin/bun`),或用 `NOMIFUN_EMBED_BUN=1` 构建以将 bun 打包
进二进制——它会在首次运行时把自己解压到数据目录。已写好的食谱见
[Web 服务部署](../guides/web-server-deployment.md#bun-must-be-on-the-system-path)。
安装后用 `sudo -u nomifun -s -- which bun` 验证。
### 看到 "bun runtime extraction" 日志后再无智能体活动
嵌入式 bun 构建会在首次运行时把 bun 解压到数据目录。若解压失败
(通常是权限问题),智能体引擎就没有运行时。请检查数据目录中是否
存在 bun 二进制,确认服务用户拥有数据目录,并在日志里查看真实的
解压错误。
## Office 预览
### Word/Excel/PPT 预览返回 "LibreOffice not detected"
`/api/star-office/detect` 路由会在系统中探测 LibreOffice。Office 预览
功能(`/api/word-preview/*``/api/excel-preview/*`
`/api/ppt-preview/*``/api/document/convert`)需要 LibreOffice 才能
渲染文档。
- Linux`apt install libreoffice`(或同等发行版命令)。
- macOS`brew install --cask libreoffice`
- Windows:从 libreoffice.org 安装。
安装后请重启后端,让它重新探测。
### 预览 iframe 一直空白
Office 预览路由会派生 LibreOffice 子进程,并通过
`/api/ppt-proxy/*``/api/office-watch-proxy/*` 代理它们。这些代理
路由是有意**公共**(不鉴权)的——iframe 内容必须在不带 SPA 会话
cookie 的情况下加载。如果你的反向代理剥掉了 URL 路径段,或在边缘对
`/api/*` 全部加了鉴权,请把这些 proxy 路径豁免出去。
## 数据目录权限
### 服务起来了,但数据库写入失败 / "unable to open database file"
配置的数据目录必须对进程可写。常见情况:
- 在 systemd 下以 `User=nomifun` 运行,但数据目录的所属者是另一个
用户。修复:`chown -R nomifun:nomifun /var/lib/nomifun`
- 一个只读挂载(`RootDirectory=``ProtectHome=yes` 等)覆盖了数据
路径。请去掉过宽的沙箱;保留官方 unit 中的中度加固
`NoNewPrivileges=yes``PrivateTmp=yes`)。
- 在 Docker 下,把宿主目录挂载进来但 UID 与容器不一致。请改用命名
卷,或将宿主目录 `chown` 到正确的 UID。
桌面外壳的默认数据目录是**按用户的应用数据目录**(Windows 上是
`%LOCALAPPDATA%\NomiFun\Nomi`macOS 上是
`~/Library/Application Support/NomiFun/Nomi`Linux 上是
`$XDG_DATA_HOME/NomiFun/Nomi`),它天然对启动应用的用户可写。设置
`NOMIFUN_DATA_DIR=<absolute path>` 后目录会变成
`$NOMIFUN_DATA_DIR/Nomi`。位于 `<system temp>/nomifun-data/Nomi`
遗留安装会在启动时被自动搬迁到新默认位置(旧目录保留为备份);若
搬迁未能完成,应用会继续从遗留目录启动,并在下次启动时重试。
### `data directory ... is already in use by another running NomiFun backend`(数据目录被占用)
所有宿主(桌面外壳、`nomifun-web``nomicore` 二进制)默认使用
**同一个**按用户的数据目录,而后端启动时会对 `{data_dir}/server.lock`
取一把 OS 级排他锁——同一目录上的第二个后端会以这条信息快速失败,
而不是悄悄破坏共享状态。最经典的触发方式:桌面应用还开着,又去跑
`bun run serve:web` / `dev:web`(或反过来)。两条出路:关掉另一个实例
(错误信息会写明持有者的 pid 与可执行文件名),或用
`NOMIFUN_DATA_DIR` / `--data-dir` 给新实例指一个独立目录。持有者
退出或崩溃时锁由 OS 自动释放;残留的 `server.lock` 文件无害。
`nomicore doctor``mcp-*` stdio 子命令不取这把锁,因此不受影响。
## Docker 专项
### `docker compose up` 完成构建并启动后立刻退出
请阅读日志(`docker compose logs nomifun`)。最常见的原因有:
- 数据卷为空*而且*没有设置 `NOMIFUN_ADMIN_PASSWORD`——服务运行
正常,但你必须先通过 HTTP 完成首次启动设置才能进得去。这其实不是
失败,而是一种状态。
- 镜像内 `--dist` 路径指错。官方 Dockerfile 把 `ui/dist` 复制到
`/opt/nomifun/web``CMD` 也据此引用——只在你定制了 Dockerfile
时才会出问题。
- 一个被 bind 挂载的、容器用户无写权限的数据目录。
### 日志显示 `nomifun-web: embedded backend + SPA on one port` 但浏览器无法连接
请确认端口映射(`docker compose ps`)。默认 compose 文件发布
`8787:8787`;如果你在前面放了 Caddy,应改为 `expose: ["8787"]`
连错端口是最常见的元凶。
### 在企业代理后构建很慢或失败
构建时传入 cargo 注册表镜像:
```bash
docker build --build-arg CARGO_REGISTRY_MIRROR=https://rsproxy.cn/index/ -t nomifun-web:local .
```
(或者你环境中使用的任意镜像。)
## 日志
后端**同时**写入 stdout 与 `<log-dir>/nomicore.log` 上的按日滚动文件
(默认 `<data-dir>/logs`)。出问题时:
- `journalctl -u nomifun-web` / `docker compose logs nomifun` 视图能
看到最近几分钟的日志。
- `<log-dir>` 下的滚动文件保存了历史。
- 把受影响模块的级别拉高:MCP 问题用
`--log-level info,nomifun_mcp=trace`,终端用
`info,nomifun_terminal=debug`,智能体会话用
`info,nomifun_conversation=debug`
## 当一切都不奏效
读源码。每个路由 handler 都在其归属 crate 的 `routes.rs`(或
`routes/`)文件里;装配在
`crates/backend/nomifun-app/src/router/routes.rs`。handler 抛出的错误
信息就是 HTTP 响应里那串字面量字符串,所以一次精确 grep 通常几秒就
能落到出错的检查上。
## 另见
- [配置参考](./configuration.zh.md) —— 每个参数与环境变量。
- [API 概览](./api-overview.zh.md) —— 路由、鉴权与 WebSocket 模型导览。
- [常见问题](./faq.zh.md) —— 那些最常见的"X 是这样吗?"的简短回答。