Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
167 lines
13 KiB
Markdown
167 lines
13 KiB
Markdown
# 开发环境运行手册
|
|
|
|
## 前置
|
|
|
|
- Deno 2.9.7(锁定;`curl -fsSL https://deno.land/install.sh | sh -s v2.9.7`)、Docker。不需要 Node.js / pnpm。
|
|
- 端口:web 5180、API 4390、PG 55433。4380 留给本机部署版(Caddy),本机 3000/5175/5176/55432 已被其他项目占用,不要改用。
|
|
|
|
## 首次启动
|
|
|
|
```sh
|
|
cp .env.example .env # 按需修改;server 与 db 脚本都读仓库根 .env
|
|
deno install # 按 deno.lock 装依赖到 node_modules(成员包软链到 node_modules/@umt/*)
|
|
deno task dev:up # 起开发库 postgres:18.6(docker-compose.dev.yml)并迁移到最新;拉代码后也跑一次
|
|
deno task dev # 同时起 server(4390)与 web(5180)
|
|
```
|
|
|
|
单独起:`deno task dev:server`(`deno run --watch`,默认 `--role all`,API + worker 同进程)、`deno task dev:web`。
|
|
|
|
## 验证
|
|
|
|
```sh
|
|
curl localhost:4390/api/v1/health
|
|
# 开发模式可用 .env 的 DEV_ADMIN_KEY 作 admin
|
|
curl -N -H "Authorization: Bearer umt-dev-admin-key" "localhost:4390/api/v1/events?after=0" # SSE 流(控制台用)
|
|
curl -H "Authorization: Bearer umt-dev-admin-key" "localhost:4390/api/v1/events?after=0&limit=50" # JSON 补读页(API Key 调用者用)
|
|
```
|
|
|
|
- OpenAPI:http://localhost:4390/api/openapi.json,文档页 http://localhost:4390/api/docs
|
|
- 控制台:http://localhost:5180,右上角钥匙按钮设置 API Key(存 localStorage `umt-api-key`)
|
|
|
|
## 常用命令
|
|
|
|
| 命令 | 作用 |
|
|
|---|---|
|
|
| `deno task ci` | typecheck(tsc 7.0.2 + deno check)→ lint(biome)→ test(vitest)→ check:tokens → build,提交前必须全绿 |
|
|
| `deno task typecheck` | `deno types` 生成 `types/deno.d.ts` 后,逐个成员跑 tsc 7.0.2,再跑 `deno check`(Deno 内置 TS 6.0.3) |
|
|
| `deno task lint` / `deno task fmt` | biome 检查 / 自动修复(含 import 排序;相对 import 必须带扩展名) |
|
|
| `deno task test` | 逐个成员跑 vitest(`deno run -A npm:vitest run`);单包:`cd packages/<x> && deno task test` |
|
|
| `deno task db:reset` | 清空 public schema 并重新迁移(生产环境拒绝执行) |
|
|
| `deno task gen:types` | 从已迁移的库反射生成 `packages/db/src/types.ts` |
|
|
| `deno task gen:tokens` | 改了 app.css 后重新生成 token 登记表 |
|
|
| `deno task build` | web 构建到 `apps/web/build/client` + server 编译为单文件 `dist/umt-server` |
|
|
| `cd apps/server && deno task start` | 以受限权限(net/env/sys/read)直接跑源码 |
|
|
|
|
依赖:npm 包版本只在根 `deno.json` 的 `imports` 里登记(web 的前端依赖仍在 `apps/web/package.json`)。改完跑 `deno install` 更新 `deno.lock`。有安装脚本的新依赖需要 `deno approve-scripts` 显式批准。
|
|
|
|
## 进程角色
|
|
|
|
`umt-server --role api|worker|worker:agent|all`(源码:`deno run ... apps/server/src/main.ts --role ...`)。
|
|
|
|
| 角色 | 引擎 worker | OutboxRelay / 模块后台循环 |
|
|
|---|---|---|
|
|
| `api` | 不起(只入队/信号/取消) | 不跑 |
|
|
| `worker`(io 组) | 除 agent 道外的全部任务,分文件 / 网络 / 维护三条道(见下) | 跑 |
|
|
| `worker:agent` | 只跑 agent 道(`runAgent`/`resolveDecision`),并发 `WORKER_AGENT_CONCURRENCY`(缺省 2) | 不跑 |
|
|
| `all` | io 三条道 + agent 共四个池(开发缺省) | 跑 |
|
|
|
|
分组 worker 只认领自己的任务(`@umt/tasks` 按任务名过滤认领),Agent 长会话不占文件复制的槽,反之亦然。
|
|
worker 里有事件循环延迟探针:偏差超过 5s 打 `worker 事件循环延迟超过阈值` warn,次数随心跳事件(`loopLagWarnings`)发出;
|
|
每 15s 的进程心跳另记延迟与峰值(`process_heartbeat` / `process_heartbeat_sample`),运行状态页可看历史。
|
|
|
|
### IO Worker 分道(WK-1)
|
|
|
|
io worker 进程内按任务声明的 `lane` 起三个引擎 worker,各自独立并发、各自排队,一条道占满不影响另一条道领取:
|
|
|
|
| 道 | 缺省并发 | 放什么 |
|
|
|---|---|---|
|
|
| 文件 `file` | 2 | 整理(复制/硬链接/移动)、ffprobe、校验、接管整理、下载盘阈值清理 |
|
|
| 网络 `network` | 4 | 站点快照、RSS、TMDB、元数据刷新、搜索、下载器提交、识别(单站限流照旧另计,见 sites.md) |
|
|
| 维护 `maintenance` | 1 | 回收站清理、资产对账、调度器里内联的清理 / 回填 / 保留期裁剪;**空闲时跑** |
|
|
|
|
- **声明**:任务在 `defineTask({ lane })` 里显式声明(`@umt/tasks` 的 `TASK_LANES`)。没声明的任务运行时归网络道并在启动日志里
|
|
warn「这些任务没有声明 lane」;ci 里 `apps/server/test/worker-lanes.test.ts` 会失败(注册表与下表逐项核对,新增任务必须同时改这里)。
|
|
选"ci 报错 + 运行时兜底网络道"而不是"启动即失败":漏声明不该让生产进程起不来,但不能悄悄进 main。
|
|
- **维护道让路**:维护道每次领取前查全库文件道 + 网络道有没有已到点排队或正在运行的 run(1s 缓存),有就不领;已经在跑的维护任务
|
|
不打断。连续让路超过 10 分钟放行一个(日志「维护道让路超时」),防止网络道长期有活时维护永远跑不上。实现是给维护道 worker 的
|
|
认领加闸门(`StartWorkerOptions.claimGate`),不改 OpenWorkflow;取舍:按"全库有无活"判断,多个 io 实例时别的实例忙也会让路,
|
|
判断有 1s 滞后,够用且简单。
|
|
- **调度器内联的维护任务**:`ScheduledJobDef.lane = "maintenance"` 的定时任务(下表)在调度器里直接执行,与维护道共用并发与让路规则:
|
|
进不了维护道就保持到期、下一轮再试;在计划页点「立即执行」不让路。其余定时任务只入队或很快结束,不占道。
|
|
- **并发配置**:设置 → 系统 →「后台任务并发」(`settings.automation.workerLanes`),保存后约 5 秒内热生效(每道开 16 个槽,
|
|
按设置值收)。环境变量 `WORKER_FILE_CONCURRENCY` / `WORKER_NETWORK_CONCURRENCY` / `WORKER_MAINTENANCE_CONCURRENCY` 设了就以变量为准
|
|
(运行状态页悬停提示"由环境变量指定")。旧变量 `WORKER_CONCURRENCY` 映射到**网络道**(`WORKER_NETWORK_CONCURRENCY` 未设时):它原来
|
|
是 io 组总并发,缺省 4,与网络道缺省相同;文件 / 维护道按设置页走。compose 不再给 `WORKER_CONCURRENCY` 缺省值。
|
|
- **观测**:运行状态页「进程」表的并发列,io 进程按道显示「运行中/并发 · 排队 N」,维护道让路时标「让路中」;数据在心跳行
|
|
`concurrency.{file,network,maintenance}` 与 `extra.lanes`(排队是全库数,运行中是本进程数)。
|
|
- **拆进程的触发条件**(未实施):整理期间心跳事件循环延迟峰值持续 > 200ms,或整理需要贴近存储部署到另一台机器。
|
|
|
|
#### 任务归属表
|
|
|
|
ci 逐行核对这张表与任务注册表(`apps/server/test/worker-lanes.test.ts`),改任务的 lane 或新增任务时同步改。
|
|
|
|
| 任务 | 道 | 说明 |
|
|
|---|---|---|
|
|
| `verifyAndImport` | file | 下载完成后的校验、ffprobe、复制/硬链接/移动入库(含接管整理) |
|
|
| `cleanupDownloadCache` | file | 下载盘到暂停线时的清理;要尽快腾空间,不放维护道 |
|
|
| `scrapeMedia` | file | 刮削:写 NFO 与图片到媒体库目录(入库后自动 / 作品详情 / 文件管理),图片下载限并发 4 |
|
|
| `ingestFeed` | network | RSS 抓取 |
|
|
| `resolveObservation` | network | 标题识别 + TMDB |
|
|
| `enrichPoolEntry` | network | 未识别资源的元数据补全 |
|
|
| `refreshMetadata` | network | TMDB 元数据刷新 |
|
|
| `searchSubscription` | network | 订阅搜索 |
|
|
| `snapshotSite` | network | 站点快照 |
|
|
| `refreshSiteAccount` | network | 站点登录 / 用户数据刷新 |
|
|
| `evaluateAcquisition` | network | 获取策略评估(查库为主,随上游链路放网络道) |
|
|
| `submitDownload` | network | 提交下载器 |
|
|
| `identifyRemote` | network | 文件管理「识别」的 TMDB 查询 |
|
|
| `refreshPiTokens` | network | 模型服务 OAuth 令牌刷新 |
|
|
| `reconcileLibrary` | maintenance | 资产对账(扫库) |
|
|
| `purgeTrash` | maintenance | 回收站到期清除 |
|
|
| `scanOrganizeSource` | maintenance | 整理来源基线与增量快照,候选整理进入文件道 |
|
|
| `runAgent` | agent | Agent 会话(worker:agent) |
|
|
| `resolveDecision` | agent | 决策处理(worker:agent) |
|
|
|
|
调度器内联、走维护道的定时任务:`cache.sweep`、`cache.images_clean`、`cache.images_lru`、`auth.cleanup`、`system.heartbeat_cleanup`、
|
|
各保留期裁剪(`system/retention.ts` 的 `RETENTION_JOBS`)、`metadata.backfill_languages`(元数据语言回刷)。
|
|
只入队的定时任务(如 `trash.purge`、`cache.threshold`、`library.reconcile`、`pi.token_refresh`)按所入队任务的道执行。
|
|
|
|
启动时 `bootstrap()` 装配 `apps/server/src/modules/` 下的四个业务模块(见 apps/server/README.md),并连接任务引擎。数据库不可达时进程启动失败并退出。
|
|
|
|
acquisition-organize 的后台循环只做编排:qB 同步(含阈值暂停/恢复)一个定时器;另一个定时器按阈值入队 `cleanupDownloadCache`、
|
|
巡检补投 `submitDownload`/`verifyAndImport`、每 6h 入队 `reconcileLibrary`,重活都在任务里跑。
|
|
`POST /download-cache/cleanup`、`POST /library/reconcile` 同样只入队,返回 `{taskId, runId}`(202)。
|
|
HTTP 调用外部 IO 型能力(`release.search` 实时搜索、`release.inspect` 刷新详情、`identity.resolve`、`catalog.lookup` includeRemote)
|
|
限时 15s,站点并发 2、TMDB 并发 4,超时 502 / 排队满 429。
|
|
|
|
一次性子命令(容器入口用):`--role migrate`(迁移到最新,迁移文件已编进二进制;随后迁移任务引擎的 openworkflow schema)、`--role preflight`(启动自检:生产环境拒绝占位符密钥与 root、DB 可达、`WEB_ROOT/index.html` 存在)、`--role probe`(打本机 `/api/v1/health`,HEALTHCHECK 用)。
|
|
|
|
设置 `WEB_ROOT` 后 API 进程在同一端口托管 web 构建产物:存在的文件直接返回,其余非 `/api` 路径回退 `index.html`。
|
|
|
|
## Docker 单容器部署
|
|
|
|
```sh
|
|
export SECRET_MASTER_KEY=$(openssl rand -hex 32) POSTGRES_PASSWORD=$(openssl rand -hex 16)
|
|
docker compose up -d --build # 服务:app(4380)+ postgres(数据卷 umt-pg-data)
|
|
curl localhost:4380/api/v1/health
|
|
```
|
|
|
|
- 镜像:`denoland/deno:2.9.7` 构建 web 与 `deno compile` 出二进制,运行时 `debian:trixie-slim` + gosu + postgresql-client + 静态 ffprobe,root 启动修 `/data` 属主后降权到 PUID(缺省 10001)。
|
|
- 入口 `docker/entrypoint.sh`:迁移 → 自检 → 同时起 api、worker、worker:agent 三个进程;任一退出则停掉其余并以非 0 退出,由 `restart: unless-stopped` 拉起。
|
|
- 二进制编译时固定权限:`--allow-net --allow-env --allow-sys --allow-read --allow-write --allow-run=ffprobe,sha256sum`;镜像带静态 ffprobe;运行期写入落在 `DATA_DIR`(容器内 `/data`)。完整部署说明见 [deploy.md](deploy.md)。
|
|
- 构建版本号:`APP_VERSION=v0.1.0 docker compose build`(写入 web 的 `version.json`)。
|
|
|
|
## 测试数据库
|
|
|
|
需要 PG 的测试(db、tasks)默认连 `DATABASE_URL` 或 55433 开发库,不可达时跳过。tasks 包可用 `TASKS_TEST_DATABASE_URL` 指定独立库;每个测试文件使用带 pid+随机后缀的独立 schema,结束时删除,多份测试并发跑也不冲突。
|
|
|
|
测试被中断(超时、kill、并发跑挂掉)时 schema 不会删,开发库会越积越多。清理脚本:
|
|
|
|
```sh
|
|
deno run -A scripts/dev-db-clean.ts # dry-run:只打印清单
|
|
deno run -A scripts/dev-db-clean.ts --verbose # 同上,逐个列出 schema 名
|
|
deno run -A scripts/dev-db-clean.ts --yes # 真正删除
|
|
```
|
|
|
|
- 只在 `DATABASE_URL`(缺省 `postgres://umt:umt@localhost:55433/umt`)指向 localhost:55433 时运行,否则直接拒绝。
|
|
- 删除的 schema:`tasks_test_`、`test_`、`e2e_`、`umt_test_`、`ao_test_`、`fm_test_`、`w2a_test_`、`w2b_`、`w7a_mig_`、`w9g_search_`、`server_test_` 前缀。`public`、`openworkflow` 不动;有其它会话持锁的 schema(测试正在跑)跳过,但最好在没有测试运行时执行。
|
|
- 删除的 public 数据:名字以 `w32-`、`w33-`、`fe4-`、`qa-` 开头的作品(title)、站点(name)、订阅(title_query),以及按 restrict 外键挂在它们下面的行,在一个事务里删。`admin_user`(`w31-admin`)、`pi_upstream`、没有这些前缀的站点(fake 上游)和手工登记的数据不动;要牵连到受保护表时脚本中止。
|
|
|
|
## 排障
|
|
|
|
- `配置无效`:按提示检查 `.env`,`SECRET_MASTER_KEY` 至少 16 字符,`DATABASE_URL` 必须是 postgres://。
|
|
- health 返回 `db: "down"`:`docker compose -f docker-compose.dev.yml ps` 看容器健康状态。
|
|
- `deno check` 报找不到 `Deno`:检查根目录是否误放了 `tsconfig.json`(tsc 基座在 `config/tsconfig.base.json`)。
|
|
- tsc 报 `Deno` 未定义:先跑 `deno task types` 生成 `types/deno.d.ts`(已 gitignore)。
|
|
- 日志中的 passkey/token/cookie/authorization/password 字段与 URL query 参数会被替换为 `[REDACTED]`(含 `logger.child()` 绑定字段与 `err` 的 message/stack);若发现明文,属于缺陷。
|