feat(browser): ship camofox v1.17.0 in compose, verified on a real instance
- compose: camofox service on by default (internal only, volume umt-camofox, healthcheck, CAMOFOX_REPLICAS=0 to skip); app depends on it with service_started/required=false and auto-registers camofox-env from BROWSER_PROVIDER_URL / BROWSER_ACCESS_KEY / BROWSER_ENABLED - docker/camofox/Dockerfile: builds upstream tag v1.17.0 (389c996); fixes the upstream Dockerfile (missing postinstall.js before npm ci, sh -c not forwarding SIGTERM so logins were never persisted on restart) - adapter fixes from the real container: coordinate click via evaluate + selector, focus typing with mode=keyboard, password values redacted from ARIA snapshots/extract, downloads field names, 410 after browser restart treated as lost tab; navigate reopens a tab in the same session - verified-capability table keyed by upstream version (1.17.0 all ten); other versions stay unverified; probe reports auth failures - settings: browser providers block (create/edit/delete/probe, capability table); API POST/PATCH/DELETE /browser/providers, POST .../probe - scripts/browser-verify: umtbr stack + mock NexusPHP run of REQ 4.7.4 items - docs: runbook browser §1/§7 (real-instance record), deploy §2.1 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b4c9b1ead2
commit
40dd0e69e4
29 files changed
+1661
-73
No files matched your search
+100
-29
@@ -4,33 +4,64 @@
|
||||
代码:`packages/adapters/src/browser/**`(适配器)、`apps/server/src/modules/browser/**`(会话/接管/能力)、
|
||||
`apps/web/app/components/browser/**`(原站视图)。
|
||||
|
||||
> 当前状态:全部能力只对 testkit 的 mock camofox 验证过,**没有连接过真实实例**。界面上"画面与输入未对真实实例核验"
|
||||
> 这个标记会一直显示,直到按第 6 节完成核验并写入 `config.verified`。
|
||||
> 当前状态:已对真实 camofox-browser **v1.17.0**(jo-inc,commit `389c996`)容器逐项核验(§7),十项能力全部通过;
|
||||
> 探测到其它版本 / fork 时一律显示"未核验",需按 §6 重新核验。
|
||||
|
||||
## 1. 登记浏览器服务
|
||||
## 1. 浏览器服务从哪来
|
||||
|
||||
设置页还没有浏览器服务的表单(留给 C1 之后)。当前用 API + SQL 登记:
|
||||
### 1.1 部署自带(缺省)
|
||||
|
||||
`docker compose up -d` 同时起 `camofox` 服务(`docker-compose.yml`,只在内网、不映射端口,卷 `umt-camofox`)。
|
||||
app 的 `BROWSER_PROVIDER_URL` 缺省 `http://camofox:9377`,启动后第一次用到浏览器时登记为 **`camofox-env`**
|
||||
(`browser_provider.config.managedBy = "env"`,访问凭据取 `BROWSER_ACCESS_KEY` / `CAMOFOX_ACCESS_KEY`,只在进程内,不入库),
|
||||
并在后台探测一次能力。地址与凭据只能改部署配置;设置页只能改它的启用。
|
||||
|
||||
```sh
|
||||
KEY="Authorization: Bearer <admin API Key 或 DEV_ADMIN_KEY>"
|
||||
# 1) 访问凭据(camofox 的 CAMOFOX_ACCESS_KEY)存进凭证库,拿到 secret id
|
||||
curl -s -XPOST localhost:4380/api/v1/secrets -H "$KEY" -H 'content-type: application/json' \
|
||||
-d '{"purpose":"other","name":"camofox-access-key","plaintext":"<CAMOFOX_ACCESS_KEY>"}'
|
||||
# 2) 登记服务(secret_ref 用上一步的 id;没设访问凭据就留 null)
|
||||
psql "$DATABASE_URL" -c "insert into browser_provider (name, kind, base_url, secret_ref, enabled)
|
||||
values ('camofox', 'camofox', 'http://camofox:9377', '<secret id>', true)"
|
||||
# 3) 探测:读 /health 与 /openapi.json,写回 capabilities / health_status
|
||||
curl -s "localhost:4380/api/v1/browser/providers?probe=true" -H "$KEY"
|
||||
# .env(部署目录)
|
||||
CAMOFOX_ACCESS_KEY=$(openssl rand -hex 24) # camofox 与 app 共用
|
||||
docker compose --env-file .env.deploy up -d --build
|
||||
docker compose ps camofox # healthy 即可
|
||||
```
|
||||
|
||||
设置页将来要做的字段:`base_url`、访问凭据(写 `secret_ref`)、`enabled`、只读的能力表(supported / verified / 上游版本)、
|
||||
下面的 `config` 可调项。
|
||||
上游不发布镜像,compose 用 `docker/camofox/Dockerfile` 从 git tag `v1.17.0` 构建(修了上游 Dockerfile 的两个问题,见文件头)。
|
||||
首次构建要从 GitHub 拉源码与约 630 MB 的 Camoufox 浏览器包;网络不好时先在本机构建同名镜像,compose 会直接用:
|
||||
|
||||
```sh
|
||||
git clone --depth 1 --branch v1.17.0 https://github.com/jo-inc/camofox-browser.git /tmp/camofox-src
|
||||
docker build -t camofox-browser:1.17.0 --build-arg APT_MIRROR=mirrors.ustc.edu.cn \
|
||||
--build-context upstream=/tmp/camofox-src docker/camofox
|
||||
# 走宿主代理时加 --network host --build-arg https_proxy=http://127.0.0.1:7890 --build-arg http_proxy=...
|
||||
```
|
||||
|
||||
### 1.2 接自有实例
|
||||
|
||||
`.env` 里 `BROWSER_PROVIDER_URL=http://<host>:9377`、`BROWSER_ACCESS_KEY=<它的 CAMOFOX_ACCESS_KEY>`、`CAMOFOX_REPLICAS=0`
|
||||
(不再起自带容器)。或者不设环境变量,在 **设置 → 站点默认 → 浏览器服务** 里"添加浏览器服务":名称、服务地址、访问凭据
|
||||
(加密存凭证库,只写不回显)、启用。自有实例的版本不是 1.17.0 时能力一律显示"未核验"。
|
||||
|
||||
### 1.3 不用浏览器
|
||||
|
||||
`CAMOFOX_REPLICAS=0` + `BROWSER_ENABLED=false`:`camofox-env` 停用,原站视图显示"没有启用的浏览器服务"。
|
||||
|
||||
### 1.4 设置页与 API
|
||||
|
||||
设置 → 站点默认 → 浏览器服务:列表(地址、来源、凭据是否设置、启用、健康、核验)、测试连通(探测 `/health` 与
|
||||
`/openapi.json`,凭据错误显示"访问凭据不对")、编辑、删除(有未关闭会话时拒绝;`camofox-env` 只能停用);
|
||||
点行打开的抽屉里是逐项能力表(不可用 / 未核验 / 已核验 + 说明)。
|
||||
|
||||
| 端点 | 说明 |
|
||||
| --- | --- |
|
||||
| `GET /browser/providers[?probe=true]` | 列表;`probe=true` 探测全部启用的服务并写回 |
|
||||
| `POST /browser/providers` | 登记 `{name, baseUrl, accessKey?, enabled?}`;`camofox-env` 名称保留 |
|
||||
| `PATCH /browser/providers/{id}` | `{baseUrl?, accessKey?(null 清除), enabled?}` |
|
||||
| `POST /browser/providers/{id}/probe` | 测试连通(停用的也可以测) |
|
||||
| `DELETE /browser/providers/{id}` | 删除 |
|
||||
|
||||
`browser_provider.config` 可调项(都可省略):
|
||||
|
||||
| 键 | 缺省 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| `verified` | `{}` | 人工核验通过的能力,如 `{"snapshot": true, "screenshot": true}`;探测永远不会把它置 true |
|
||||
| `verified` | `{}` | 逐项覆盖核验结果,如 `{"takeover": false}`;缺省取适配器内置的真机核验表(§7,按上游版本);探测永远不会把它置 true |
|
||||
| `navigationTimeoutMs` | 30000 | 单次导航超时 |
|
||||
| `maxRetries` | 2 | 导航只重试超时 / 网络 / 5xx / 503;401、白名单、控制权冲突不重试 |
|
||||
| `snapshotMaxBytes` | 524288 | 快照文本上限(超出截断并标 truncated) |
|
||||
@@ -90,13 +121,23 @@ execute 类需要持续授权(`automation_grant`)或审批。元素引用只来
|
||||
| 502 浏览器服务不可用 | camofox `/health`(503 = 恢复中);容器日志 |
|
||||
| 409 先接管会话再操作 | 界面点"接管";或租约已被他人持有 / 已过期 |
|
||||
| "连接中断" | 截图请求失败;看 API 日志 `component: browser` 的方法 / 路径 / 状态(日志不含正文与凭据) |
|
||||
| 测试连通提示"访问凭据不对" | `/health` 免认证所以能连上,但 `/openapi.json` 401:`BROWSER_ACCESS_KEY` 与 camofox 的 `CAMOFOX_ACCESS_KEY` 不一致 |
|
||||
| 409"标签页已被浏览器服务回收" | camofox 重启(旧标签页返回 410)或会话空闲回收(404);Agent 再次 `browser.navigate` 会在同一会话开新标签页 |
|
||||
| 重启 camofox 后要重新登录 | 容器是被 SIGKILL / OOM 杀掉的(`docker inspect` 看 `OOMKilled`、退出码 137):只有 SIGTERM 正常停止、会话空闲回收、关闭会话时才落盘登录状态;加大 `CAMOFOX_MEM_LIMIT` |
|
||||
| 设置页显示"未核验" | 探测到的上游版本不在核验表里(不是 1.17.0,或 fork);按 §6 核验后在适配器 `CAMOFOX_VERIFIED_RELEASES` 加一行,或临时写 `config.verified` |
|
||||
|
||||
## 6. 真实实例核验清单(REQ §4.7.4)
|
||||
|
||||
先固定用户现有 camofox-browser 的**仓库、版本(`/openapi.json` 的 `info.version`)、启动方式与认证配置**,记在本节末尾。
|
||||
每项通过后把对应能力写进 `browser_provider.config.verified` 并更新 `verified_at`;不通过的保持 false,界面继续标"未核验"。
|
||||
先固定 camofox-browser 的**仓库、版本(`/openapi.json` 的 `info.version`)、启动方式与认证配置**,记在本节末尾。
|
||||
通过的能力登记进适配器的真机核验表 `CAMOFOX_VERIFIED_RELEASES`(`packages/adapters/src/browser/camofox/capabilities.ts`,
|
||||
按上游版本,只在探测到完全相同的版本时生效,并把 `verified_at` 写成核验日期);个别部署要撤销某项写
|
||||
`browser_provider.config.verified = {"<键>": false}`。不通过的保持 false,界面继续标"未核验"。
|
||||
不得用另一个浏览器窗口代替后宣称通过。
|
||||
|
||||
自动化核验脚本:`scripts/browser-verify/run.sh`(临时栈 `umtbr`:postgres 127.0.0.1:55447、camofox 127.0.0.1:49377、
|
||||
compose 网络里的 mock NexusPHP 站 `mocknexus:8080`;服务层在宿主进程里装配,与生产同一套代码;
|
||||
阶段之间重启 / SIGKILL camofox;结果写 `.browser-verify-out/results-*.json` 与截图)。跑完 `docker compose -p umtbr down -v`。
|
||||
|
||||
| # | 项目 | 做法 | 通过标准 | 对应 verified 键 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | 登录保持 | 观众 / 馒头 / HHClub / CHD 各一账号:原站视图接管登录;闲置超过 `SESSION_TIMEOUT_MS` 后再 `browser.snapshot` | 不需重新登录;过期时落到登录页、`loginRequired = true`,而不是空列表 | `sessions`、`persistentProfile` |
|
||||
@@ -106,17 +147,47 @@ execute 类需要持续授权(`automation_grant`)或审批。元素引用只来
|
||||
| 5 | 重启后会话 | 分别触发 `BROWSER_IDLE_TIMEOUT_MS` 回收与强制重启 camofox 容器 | 持久化 profile 恢复登录;旧 tabId 识别为 `lost`,不重放旧引用 | `persistentProfile`、`tabs` |
|
||||
| 6 | 人与 Agent 同标签页连续操作 | Agent 打开详情页 → 原站视图接管,在**同一标签页**点击、输入 → 交还 → Agent 重新 `snapshot` | 人看到的是同一会话同一页面;交还后 Agent 读到人工操作后的状态;下载不重复提交 | `takeover`、`interact` |
|
||||
|
||||
本适配器依赖、但 1.17.0 OpenAPI 未写明的行为,核验时一并确认(不符就改适配器,不改合同):
|
||||
|
||||
- `POST /tabs/{id}/click` 接受 `{x, y}` 视口坐标(原站视图点击);不支持时需要改走 VNC 插件或上游新增端点。
|
||||
- `POST /tabs/{id}/type` 不带 `ref`/`selector` 时输入到当前焦点;`submit` 与 README 的 `pressEnter` 哪个生效。
|
||||
- `POST /tabs/{id}/press`、`/scroll`、`/back`、`/forward`、`/refresh` 是否存在及请求体字段。
|
||||
- `DELETE /tabs/{id}` 的 `userId` 放 query 还是 body;`DELETE /sessions/{userId}` 是否关闭该 profile 全部标签。
|
||||
- `userId` 允许的字符与长度(`umt-site-<uuid>-acct-<uuid>`,约 90 字符)。
|
||||
- 性能:四站并发 1 / 2 / 4 会话的启动延迟、截图往返(原站视图 1.5 秒刷新是否跟得上)、内存与失败恢复时间。
|
||||
|
||||
核验记录(填写):
|
||||
核验记录:
|
||||
|
||||
| 日期 | 仓库 / fork | 版本 | 启动方式 / 认证 | 通过项 | 备注 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| | | | | | |
|
||||
| 2026-09-28 | jo-inc/camofox-browser | v1.17.0(`389c996`),Camoufox 152.0.4-beta.28 | `docker/camofox/Dockerfile` + compose;`CAMOFOX_ACCESS_KEY` Bearer | 十项全部 | mock NexusPHP 站 + example.com;四个真实站点未测,见 §7 |
|
||||
|
||||
## 7. 真机核验(版本 1.17.0)
|
||||
|
||||
环境:`camofox-browser:1.17.0` 容器(本机构建,镜像 3.6 GB,空闲内存约 200 MB,打开 2 个会话后约 390 MB),
|
||||
`CAMOFOX_ACCESS_KEY` 已设置,`SESSION_TIMEOUT_MS=60000`、`BROWSER_IDLE_TIMEOUT_MS=30000`(空闲回收阶段用短超时);
|
||||
目标页:testkit mock NexusPHP(观众风格,`mocknexus:8080`,与 camofox 同一 compose 网络)与公开静态页 `https://example.com/`。
|
||||
浏览器服务经环境变量登记为 `camofox-env`。每项的原始输出见 `scripts/browser-verify/run.sh` 的结果文件。
|
||||
|
||||
| # | 项目 | 结果 | 实际观测 |
|
||||
| --- | --- | --- | --- |
|
||||
| 0 | 认证 / 探测 | 通过 | 无 Bearer 请求 401;`/openapi.json` `info.version = 1.17.0`,十项能力的端点都在 |
|
||||
| 1 | 登录保持 | 通过 | 新 profile 打开 `torrents.php` → 302 到 `login.php?returnto=…`,`loginRequired = true`;人工接管登录后 Agent 直接打开受保护列表页,不再登录(站点 takelogin 只收到 1 次);Agent 用服务端凭证注入登录第二个账号也通过,快照与返回值里没有密码 |
|
||||
| 2 | 截图 / 结构化快照 | 通过(修了遮蔽) | 截图直接返回 `image/png` 二进制(不是 OpenAPI 写的 JSON base64),视口随指纹变化(实测 1433×1145、1691×867),截图像素 = 点击坐标;登录页 `modelSafe = false`。快照是 Playwright ARIA 格式(`- textbox [e2]: 值`,引用写作 `[eN]`),**密码框与普通文本框同形且带明文值**,原有按标签名的遮蔽规则抓不到 → 已修(见下);列表页 45 个引用、8.6 KB,未触发分页 |
|
||||
| 3 | 列表 / 详情提取 | 通过 | 列表页快照含 fixture 的标题、副标题、大小、做种数(5 条中 4 条标题完整出现,1 条是站点截断显示);`POST /extract` 按 `x-ref` 取回标题;详情页取回下载链接文字。与 HTTP 适配器同源 fixture 对照,未逐字段比对促销标记 |
|
||||
| 4 | 下载捕获 | 通过(修了字段名) | Agent 点详情页 `.torrent` 链接 → `downloads` 返回 `{id, url, suggestedFilename, mimeType, bytes, createdAt, failure, dataBase64}`;sha256 与直接 HTTP 下载一致(`9fcf61f7…`),infohash 与站点一致并匹配到本地资源,约 2 秒;`consume=true` 后不再返回同一文件 |
|
||||
| 5 | 重启后会话 | 通过(修了 3 处),有已知限制 | SIGTERM 重启:旧标签页返回 **410** `browser_restarted`(不是 404)→ 已按 lost 处理;登录状态恢复,Agent 再次导航在同一会话开新标签页(约 1.3 秒,含浏览器冷启动)。会话空闲回收(60 秒 + 上游 60 秒扫描周期):旧标签页 404 → lost,重新打开仍登录(约 0.4 秒)。**SIGKILL**:上次落盘之后的新登录丢失(需重新登录),这是上游落盘时机决定的(只在会话空闲回收、关闭、SIGTERM 停止时写),界面照常识别为登录页 |
|
||||
| 6 | 人与 Agent 同标签页 | 通过(修了坐标点击与焦点输入) | Agent 打开登录页(上游 tabId T)→ 接管:自动化输入被拒(409)、只读快照允许 → 截图坐标点击用户名框 → 焦点输入 → 点密码框 → 输入 → 回车 → 交还;交还前的旧引用 `e1` 被拒("先重新读取页面");Agent 重新读取,**仍是 tabId T**,看到人工登录后的页面。后退 / 前进 / 刷新 / 滚动也在同一标签页通过。核验走原站视图调用的同一组 HTTP 端点,没有在浏览器里点原站视图界面 |
|
||||
|
||||
对接中发现并已修的差异:
|
||||
|
||||
1. **截图格式**:1.17.0 直接返回 `image/png`;适配器原本两种都兼容,无需改。
|
||||
2. **坐标点击**:`POST /click` 只认 `ref` / `selector`,不接受 `{x, y}` → 适配器先 `POST /evaluate` 用 `elementFromPoint` 给该点元素打
|
||||
`data-umt-pt` 标记,再按属性选择器点击(仍走上游的完整鼠标事件序列与下载守卫)。坐标处没有元素报 400。
|
||||
3. **焦点输入**:`POST /type` 缺省 `mode=fill` 必须带 `ref`/`selector` → 无目标时发 `mode=keyboard`;回车用 `/press`。
|
||||
4. **密码框遮蔽**:快照里出现带值的输入框时,经 `evaluate` 取页面上 `input[type=password]` 的当前值按字面量遮蔽;
|
||||
`evaluate` 失败时遮蔽全部输入框的值。`extract` 结果同样处理。
|
||||
5. **下载字段**:`suggestedFilename`、`bytes`(大小)、`dataBase64`、`dataSkipped`(超 `maxBytes`)、`failure`。
|
||||
6. **410**:浏览器重启后 5 分钟内旧标签页返回 410,与 404 同样按"标签页已不存在"对账为 lost;Agent 下一次导航在同一会话开新标签页。
|
||||
7. **上游镜像**:① v1.17.0 的 Dockerfile 在 `npm ci` 前没复制 `postinstall.js`,构建必然失败;② `CMD sh -c "node …"`
|
||||
不转发 SIGTERM,`docker stop/restart` 等满宽限期后 SIGKILL,**关机落盘不执行,重启后登录全丢**(修前实测)。
|
||||
`docker/camofox/Dockerfile` 两处都改了(`exec node`)。
|
||||
8. 其它:关掉上游缺省开启的崩溃报告外发(`CAMOFOX_CRASH_REPORT_ENABLED=false`)与每次启动从 addons.mozilla.org 下载
|
||||
uBlock(`CAMOFOX_DISABLE_DEFAULT_ADDONS=true`,内网下载失败会拖慢启动)。
|
||||
|
||||
`userId`(`umt-site-<uuid>-acct-<uuid>`,约 90 字符)上游原样使用(持久化目录按 SHA-256 取名),无字符限制问题;
|
||||
`DELETE /tabs/{id}` 的 `userId` query / body 都认;`DELETE /sessions/{userId}` 关闭该 profile 全部标签页并落盘。
|
||||
|
||||
没有核验的:四个真实站点(观众 / 馒头 / HHClub / CHD)的登录保持、Cloudflare / 验证码挑战、真实站点页面上的提取对比;
|
||||
并发 1 / 2 / 4 会话的性能与资源;原站视图界面本身在浏览器里的点击体验(1.5 秒截图刷新);VNC 插件(镜像已装,未启用、未接入)。
|
||||
Reference in new issue
Block a user