Files
creator-hub/docs/architecture/gateway-linux-runtime-plan.md

264 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gateway Linux Xvfb:规划、设计与验收
> 状态:设计草案,尚未实现、尚未通过运行验收。现状核对基线:`main@977e541`。
> 已确认范围:仅编写文档;保留 Docker,新增 Linux 物理机通过 Xvfb 虚拟屏幕启动浏览器。Linux 真实桌面、Windows/macOS 不在本次范围。`XFVB` 按 `Xvfb` 理解。
> 本文补充 [plan01](../plan01.md) 的环境运行能力,不替代平台业务验收;现有部署步骤仍以 [deployment](../deployment.md) 为准。下文配置、接口增量和时限均是待实现设计,不是当前可用功能。
## 1. 目标与完成标准
同一套 Python gateway 能管理 Docker 浏览器和 Linux 本机浏览器;本机浏览器仅通过 Xvfb 运行,不依赖显示器或图形登录会话。使用者仍从环境页面创建、启动、停止和回收,不能靠管理员手工启动 Chrome 后填写 CDP 地址冒充完成。
交付分为两个独立结论:
- **文档完成**:范围、现状差距、设计选择、接口影响、实施顺序、逐项验收步骤与失败判定完整;相对链接有效,未把目标描述成现有能力。
- **功能完成(后续开发)**:Docker 回归通过,Linux 本机 Xvfb 完成创建到回收的真实闭环;人工登录、会话保存、并发与故障恢复均有证据;业务接口复用通过,不以 Mock 或健康接口代替浏览器与平台证据。
首轮验收基线为 Linux x86_64、Python 3.12+、可运行受控 Chromium 的发行版;测试记录必须填写发行版、内核、systemd、X server 和浏览器精确版本。ARM、原生 Wayland 启动、跨宿主机 Profile 搬迁不在本轮。
部署支持一个控制面中 Docker 环境和本机 Xvfb 环境并存;同一个 Linux 本机 gateway 下多个 Xvfb 环境可同时运行。单个 gateway 部署实例选择一种运行后端,不在同一进程内混管 Docker 和宿主机进程。
## 2. 当前能力与缺口
| 核对位置 | 当前实现 | 本次目标的缺口 |
| --- | --- | --- |
| `cmd/docker_gateway/gateway.py``Gateway``run` | 生命周期围绕 DockerClient、容器标签和网络代际;入口构造 DockerClient | 没有本机浏览器的创建、启停、回收、进程归属管理 |
| 同文件 `external_cdp` / `BROWSER_CDP_URL` | 指定一个外部浏览器地址与 alias;列表返回 `external`;平台读取/动作可连接该浏览器 | 是接入已有浏览器,不负责启动;不能作为托管本机环境的实现,也不能用固定 runtime ID 证明新进程身份 |
| `docker/browser-wrapper/docker-entrypoint.sh` | 容器内启动固定 `:99` Xvfb、x11vnc、CDP 转发,再启动 Chromium | 固定显示号仅在容器隔离下适用;不能原样搬到多环境宿主机;现有入口不等于本机服务 |
| `cmd/control-plane/hub.go``internal/hub` | 创建参数、版本管理、运行识别、租约与清理包含镜像/容器/网络假设 | 需要明确本机运行信息,不能用假镜像、假 Docker network ID 填字段 |
| `cmd/docker_gateway/douyin.py``xiaohongshu.py` | 浏览器动作通过受控 CDP 连接执行,含账号与运行代际核验 | 应复用业务逻辑,只替换受控浏览器定位与生命周期,不复制平台连接器 |
特别说明:现有 `external_cdp` 列表的 `proxy_ready=True` 并不能证明本机启动或代理就绪;`/healthz` 可用也不能证明浏览器存在。
## 3. 方案选择
| 方案 | 优点 | 缺点 | 结论 |
| --- | --- | --- | --- |
| 只使用外部 CDP 接入 | 代码少,适合临时诊断 | 不拥有生命周期,无法保证回收与会话独占 | 不满足需求 |
| 每个 gateway 选择 Docker 或本机后端;本机仅使用 Xvfb | 部署边界明确,可复用现有业务与控制面;本机不依赖 Docker | 需调整生命周期契约与本机进程管理 | **推荐,本文采用** |
| 一个 gateway 进程同时管理容器和本机进程 | 注册入口少 | 同进程权限、依赖、故障边界混杂,没有当前必要性 | 不采用 |
不增加插件框架、调度集群、消息队列或另一套 HTTP 服务。用现有 Python HTTP 服务承载路由;内部按启动配置选择两个明确的执行实现,仅提取真正共享的生命周期操作与浏览器地址解析。
### 3.1 成熟模式与采用边界
- Playwright 的 headed Linux 测试使用 Xvfb:虚拟屏幕承载正常有界面浏览器,不等于 `headless`;不自动获得可供人操作的远程桌面。
- Chromium 用独立 `user-data-dir` 保存 Profile;同一个目录不能被多个浏览器实例同时使用,不能拿用户日常 Chrome 默认目录作为托管 Profile。
- Xvfb 提供 X server;以实际 X client 连接确认就绪,而不是只看进程、socket 文件或固定等待。浏览器的 DISPLAY 与必要授权由本环境 Xvfb 启动流程提供,不继承宿主机桌面会话。
- systemd 的服务/cgroup 用于托管本机进程树和重启后的归属核验;不以 PID 文件或进程名匹配作为终止依据。
- 仅借鉴公开文档中的运行模式,不复制上游代码。采用的资料链接与许可证边界见第 10 节。
## 4. 运行与配置设计
```text
控制面(Go,环境/账号/策略/运行记录)
├─ Docker gatewayPython,现有部署) → 容器 → Xvfb + 浏览器
└─ 本机 gateway(同一 Python 服务代码,Linux 用户服务)
├─ 环境 A:专属进程组/cgroup + Xvfb + 浏览器 + 人工操作入口
└─ 环境 B:专属进程组/cgroup + Xvfb + 浏览器 + 人工操作入口
```
Docker gateway 保持现有部署及 socket 归属;本机 gateway 不要求 Docker socket、镜像或 Docker 网络存在。宿主机无 Docker daemon 时,本机模式也必须完整启动。控制面通过注册地址访问 gateway;部署需验证容器到宿主机的实际连通地址,不能把控制面容器内的 `127.0.0.1` 当成宿主机。
### 4.1 拟新增配置
所有配置先校验再创建目录、显示服务或浏览器;未知模式、缺少必要字段直接报错,不尝试切换另一模式。
| 配置/字段 | 所属层 | 规则 |
| --- | --- | --- |
| `BROWSER_RUNTIME=docker\|native` | gateway 启动配置 | 显式选择;Docker 现有部署在实现时写明 `docker` |
| 本机浏览器版本 → 绝对可执行文件路径 | 本机 gateway 配置 | 有限版本表;文件存在、可执行、版本可探测;API 不能指定任意命令或 shell 文本 |
| 本机 Profile 根目录、运行状态目录 | 本机 gateway 配置 | 绝对路径、目录可写;Profile 长期保存,临时状态与其分离;禁止路径越界 |
| Xvfb 屏幕参数 | 本机 gateway 配置 | 初始固定为 `1920x1080x24`;确有不同尺寸需求再扩展环境参数 |
环境选择 gateway 后由 gateway 能力决定运行后端,不允许请求声称 `native` 却发给 Docker gateway。本机版本来自部署者明确安装的受控浏览器,版本与二进制校验值进入验收记录;不因启动失败自动下载替代浏览器,不假设普通 Chrome 能等价支持现有指纹参数。
本机显示固定使用 Xvfb,不新增显示模式选择参数。DISPLAY 与必要的 Xauthority 由 gateway 为每个实例生成、传递和清理,不读取宿主机图形会话作为替代。
### 4.2 Xvfb
- 每个运行实例独立 Xvfb,动态分配显示号;采用 X server 支持的分配机制并验证目标发行版能力,不扫描出一个空闲号后无保护地占用。
- 启动顺序:运行锁和 Profile 锁 → 持久保存启动归属记录 → 创建环境 unit → Xvfb → X client 连接探测 → 按平台出口配置准备请求中转 → 浏览器 → CDP 与目标页探测 → 人工操作入口探测 → 持久保存启动完成记录 → running。
- 不复用容器入口中的固定 `:99`,不删除不属于当前实例的 `/tmp/.X*-lock` 或 X11 socket。
- Xvfb 被杀、显示断开时停止本环境浏览器与操作入口,标记失败并保留 Profile;不得切换 headless,也不得自动重放业务发送。
- Xvfb 本身不是远程桌面。复用项目现有浏览器操作链路,提供该显示实例专属的 x11vnc/RFB 入口供人工登录、验证码和确认;只有画面可见而不能输入不算通过。不新增第二套远程桌面产品;需在实施第一阶段核对现有入口能否无损接入本机运行地址。
### 4.3 本机进程归属与重启
每个运行实例使用独立的 systemd 用户 transient service/cgroup;由环境 runner 启动并监督本环境的 Xvfb、浏览器和 RFB 子进程,设 `KillMode=control-group`,禁止失败自动重启浏览器。主 gateway 重启与环境子服务分离,避免主服务退出就误杀仍需核验的环境。
创建 unit 前必须先持久保存启动归属记录,包含 alias、binding version、随机生成的 runtime ID、唯一 unit 名、Profile 路径、operation ID 与 `starting` 阶段;记录保存失败不得创建 unit。unit 必须携带可与记录核对的运行身份。随后将本次分配的 Xvfb 显示号、CDP 端点、中转监听地址与启动时间补入记录;所有就绪检查通过并持久保存启动完成记录后,才报告 running、允许业务动作和订阅。记录更新必须原子替换并确保落盘,不能以仅写入进程内存作为完成依据。
PID 仅用于诊断;重启时同时核对 unit 的运行身份、受控路径、运行记录和实际 CDP。Profile 独占锁在环境存活期间由环境 runner 持有,不因主 gateway 退出而释放;交接期间不得出现可被另一实例抢占的窗口。
重启恢复分支按以下顺序处理,恢复检查完成前不得报告 running 或接收业务动作:
1. 元数据、unit 或 Profile 所有权无法确认或不一致 → 进入 `unknown`,阻止同 Profile 再启动,不认领、不终止可疑进程;输出可定位原因,人工核验后显式回收。发现缺少记录的疑似托管 unit 也按此处理,不忽略残留。
2. 归属记录仍为 `starting`,尚未持久保存启动完成 → 不续跑启动、不自动重启;核对归属后,仅清理本次 unit 与临时资源,保留 Profile。即使浏览器已能连接,也不视为启动完成。确认资源已释放后记录 `failed`;清理结果无法确认则为 `unknown`。记录存在但 unit 尚未创建时同样收敛为失败,不创建新 unit。
3. 已完成启动的 unit 已退出 → 记录停止或失败,确认子进程已回收后释放锁与租约。
4. 已完成启动且归属相符的 unit 仍在运行 → 按第 5.4 节恢复请求中转,检查显示、CDP、所选出口及人工操作入口;人工入口须验证画面与输入能力,与首次启动标准一致。全部通过后才恢复运行报告,并在账号身份核验通过后恢复事件订阅,不新建浏览器、不重放业务动作。
5. 上述存活实例的任一就绪检查失败 → 不恢复业务与订阅,记录具体失败原因;归属已确认时停止本次运行并保留 Profile,清理完成为 `failed`,清理结果无法确认则为 `unknown`。不得以人工入口不可用、出口配置暂不可取等理由跳过检查。
采用 `systemctl --user` 管理现有用户服务,不引入 Python systemd 库。无用户服务管理器时启动配置失败,不回退到无人管理的后台进程。无图形登录的服务器重启后运行需由部署者配置用户服务驻留(linger);浏览器只依赖本环境 Xvfb,不要求已有桌面会话。
## 5. 生命周期、数据与接口
### 5.1 共享语义
| 动作 | 目标行为 |
| --- | --- |
| create | 保存已校验的环境配置并准备持久 Profile,返回停止态;不提前启动浏览器 |
| start | 按 alias 串行;获取跨进程 Profile 独占锁;分配新 runtime ID;按第 4.3 节先保存归属记录,再准备显示、请求中转与浏览器并逐项验证。相同配置已就绪则返回当前实例,不再启动第二个 |
| stop | 核对运行归属;先停接收新业务动作、停止订阅,再关闭浏览器,随后 RFB/Xvfb;保留 Profile |
| recycle | 只回收本次受控运行资源与临时记录;保留环境、绑定和 Profile,供以后重新启动 |
| upgrade | 停止旧实例并保留 Profile;使用已安装且启用的目标浏览器版本重新启动;不自动降级或回滚;失败不声称升级成功 |
| rebind | 延续停止且无执行中任务的要求;更新出口绑定后,下次启动应用;不在运行中切代理 |
`stopped → starting → running → stopping → stopped` 为正常路径;确认失败进入 `failed`,结果无法确认进入 `unknown`。这些是设计状态,实施时需在控制面、gateway 和页面一致表达,不把“进程存在”当成 running。对外动作结果不明时先查询核验,不自动重发业务写操作。
启动总时限初始定为 60 秒;停止给予浏览器 15 秒优雅退出,之后终止该 unit 的剩余进程树,总时限 30 秒。超时返回明确错误或 `unknown`,必须保留原始失败步骤和清理结果。强制退出需提示会话最后一次写入可能丢失。启动中途失败按创建顺序逆向清理,仅删除本次临时资源,绝不删除 Profile。
### 5.2 Profile 与并发
Profile 固定绑定环境与宿主机:`<profile_root>/<alias>`,不同 alias 不共享目录。使用操作系统文件锁与 Chromium 自有锁共同防止重复启动;只检查目录存在或 Python 线程锁不够。两个 gateway 进程争抢同一 Profile 必须只有一个成功。
停止、回收、主 gateway 重启均不删除 Profile;升级也不搬迁 Profile。版本不兼容时明确失败,不自动清空登录数据。登录保存仅指保留平台允许持久保存的会话,不承诺平台不会撤销或要求重新登录。切换 gateway/宿主机视为新部署,禁止把跨机复制 Profile 包含在本轮。
### 5.3 契约增量(待实现评审)
保持现有 `/api/browsers``/v1/browsers` 的领域入口及 Docker 动作含义;本机增加条件字段,不把本机浏览器伪装成 Docker 容器。以下是拟定契约,不是可直接调用的现有接口:
| 位置 | 增量与约束 |
| --- | --- |
| gateway 能力查询(拟 `GET /v1/capabilities` | 返回 `runtime_backend`、可用浏览器版本、人工操作能力;native 显示方式固定为 Xvfb;缺失或查询失败显示不可用,不猜测能力 |
| gateway 注册信息 | 保存/展示后端能力;请求与当前能力不符拒绝;不依赖名称中是否带 docker 判断 |
| 环境创建 | Docker 继续使用 `image_version`native 使用 `browser_version`,不接受显示模式选择字段;两类版本字段互斥,错误组合返回 400 |
| native 启动载荷 | 受校验 alias、binding version、浏览器版本、指纹与出口;不接收任意 executable、Profile 路径、DISPLAY、shell 或 CDP URL |
| 运行记录/列表 | 明确后端、runtime ID、就绪状态与失败原因;native 不要求容器 ID 格式,也不伪造 `network_id` |
| 代际校验 | 共享使用 alias、binding version、runtime IDDocker 额外保留真实 network ID 校验;native 核对受控 unit 和进程归属 |
| upgrade | 保留 `{version}` 动作形态,根据已确定后端查镜像版本或本机浏览器版本,不跨后端升级 |
| 页面 | 按 gateway 能力展示对应版本;本机显示固定标注 Xvfb,不提供显示模式选择;错误、禁用原因可见,显式启停回收仍是独立动作 |
HTTP 设计:输入错误 400;不存在的环境 404;运行归属、Profile 占用或版本冲突 409;显示/浏览器依赖不可用 503;下游执行失败 502;启动或清理超时 504。错误体保留现有可读错误信息并增加稳定错误码和 operation ID;不在错误体输出秘密。Docker 已有状态码不在本次顺带重写。具体字段、Go/Python 解析器、数据库约束和前端类型必须在同一实施变更中更新并测试。
开发阶段允许重建开发数据库,不做旧运行数据回填、双写或假字段兼容。已有 Docker 对外语义继续维护,不等于保留已经废弃的内部假设。`external_cdp` 不接入托管环境创建流程;其是否仍有独立诊断用途需检查调用方,废弃时直接删除,不成为启动失败的回退路径。
### 5.4 代理、平台动作和监听
代理及其出口配置、凭据和环境绑定由平台管理;gateway 只按平台下发的配置中转请求,不创建、启停或自行选择上游代理。复用现有浏览器侧无需凭据的本地转发器;本机浏览器使用本机可达的转发地址,不能复用只在 Docker 网络内可达的 gateway 地址。出口凭据不进入浏览器命令行、配置文件或日志。明确直连与绑定代理是不同选择:绑定代理不可用必须失败,不能改为直连。
现有中转器运行于 gateway 进程内,主进程退出后不能假定中转仍可用。恢复时由平台依据 alias、binding version、runtime ID 核对当前运行及出口绑定,并重新下发所需中转配置和凭据;gateway 不从本地运行记录恢复秘密,也不自行更换出口。对仍存活的浏览器,须重建其原中转监听地址和端口,并通过实际浏览器请求确认所选出口可用后才能报告 running。平台无法提供配置、绑定不符、原端口被其他服务占用或出口验证失败时,执行第 4.3 节失败分支,不切直连、不换端口冒充恢复成功。重复下发同一运行配置应幂等,旧运行配置须拒绝;该恢复交互的具体接口纳入 P0 契约说明。
本机模式本轮不新增宿主机防火墙或网络隔离,不能宣称与现有 Docker 隔离等价。验收需证明实际浏览器请求经过选定代理、失败可见;不宣称已实现操作系统级防泄漏。
抖音/小红书继续使用现有页面内业务动作与身份校验;runtime 改变时旧订阅停止,新实例重新核验身份后建立订阅。人工发送仍逐次确认,自动响应仍由策略与 UID 冷却决定。平台通知保持事件监听;进程健康探测不是业务事件轮询的替代方案。断线期间平台若无法补齐事件必须显示缺口,不承诺无证据的无丢失恢复。
## 6. 日志与失败可见性
关键节点均记录开始、完成或失败:配置校验、Profile 加锁、启动归属记录落盘、unit 创建、显示就绪、请求中转就绪、浏览器启动、CDP 就绪、人工入口就绪、启动完成记录落盘、恢复配置核对、中转重建、未完成启动清理、订阅恢复、停止、强杀与回收。
稳定字段:`service``operation_id``alias``runtime_backend``binding_version``runtime_id``stage``duration_ms``outcome``error_code`。进程退出补充 unit、退出码/信号;异常保留经过脱敏且有长度上限的 stderr。Profile 仅记录环境内相对路径;不输出 Cookie、密码、代理凭据、Xauthority 内容或完整 CDP WebSocket 秘密地址。
`/healthz` 只证明 gateway HTTP 进程存活;能力可用、环境 running、平台账号可用分别验证。失败清理也失败时,两条事实都要保留,不能只留下“启动失败”而隐藏残留进程。
## 7. 分阶段实施计划
每阶段先写会失败的最小回归测试,再实现;前一阶段具备真实端到端证据后再扩展。开发前新建功能分支,本文提交不启动重构。
| 阶段 | 交付物 | 退出条件 |
| --- | --- | --- |
| P0 契约与能力核对 | 最小接口/schema 变更说明;锁定本机浏览器;证明 systemd 用户服务、Xvfb 分配、现有人工操作链路能使用 | 无假容器/网络字段;本机 Xvfb 方案实际可行;依赖来源与版本明确 |
| P1 本机 Xvfb 最小闭环 | 一条从页面 create/start/stop/recycle 到 Xvfb 浏览器的链路;独立 Profile;人工操作入口;显示失败可见 | A01–A08、A10、A14 通过;Docker 最小回归通过 |
| P2 多环境与故障处理 | 多个 Xvfb 实例并存;显示、端口冲突与进程故障清理 | A09、A11–A13 通过,无显示号或 Profile 冲突 |
| P3 恢复与业务回归 | 重启调和(含未完成启动清理)、旧代际保护、请求中转恢复、平台读取与事件连接、完整日志 | A15 的重启会话保存及全部恢复分项通过;第 8 节全部必测用例与自动检查通过;真实平台结论与范围明确 |
优先复用 `gateway.py`、现有 DockerClient/代理/CDP 连接器;新增本机生命周期模块及测试。控制面、`internal/hub`、环境页面只增加实际需要的后端能力字段,不另建产品管理框架。实现时核对精确文件职责,不按本计划盲目拆文件。
## 8. 验收方案
### 8.1 场地和记录要求
准备一台没有图形登录/物理显示器的 Linux 主机验证本机 Xvfb;另保留现有 Docker 部署作回归。资源受限时,可用独立无显示会话的 Linux VM 验证,但不能用容器内已有 Xvfb 代替本机验收。无需准备真实桌面测试环境。
准备已锁定浏览器版本、专用测试 Profile、测试账号、可控代理、可发送测试事件的对端账号。真实发送仅用测试对象并遵守人工确认/自动策略。故障注入仅针对本次受控 unit,不使用 `pkill chrome` 等全局命令。
实施 PR 必须补齐可执行命令清单:安装依赖、创建用户服务、设置 gateway 配置、注册能力、调用生命周期接口、定位日志及故障注入。本文不提供尚未实现的虚假启动脚本。下面步骤通过现有页面动作描述,新增字段/操作入口待实现后必须可由测试人员直接执行。
每例记录:用例 ID、代码 commit、机器及依赖版本、运行后端、开始结束时间、输入摘要、operation/runtime ID、预期与实际、脱敏日志/截图路径、通过/失败/阻塞。没有证据一律不填“通过”。
### 8.2 必测用例
| ID | 执行步骤 | 通过标准 |
| --- | --- | --- |
| A01 无 Docker 本机启动 | 在无 Docker daemon/socket 的主机配置 native,启动 gateway;查询健康与能力 | HTTP 可用,能力明确 native;不尝试连接 Docker;能继续创建环境 |
| A02 参数校验 | 分别提交未知后端、版本不存在、非法显示模式字段、越界路径/非法 alias、混用两类版本字段 | 分别明确拒绝;没有新增 unit、浏览器或遗留临时文件;Profile 不被删除 |
| A03 创建不启动 | 创建一个 native 环境;查看进程与页面状态 | 环境停止态;Profile 归属正确,无提前启动的浏览器/Xvfb |
| A04 显示依赖校验 | 分别移除 Xvfb 可执行文件、使 X client 无法连接;再设置继承 DISPLAY 为其他显示服务地址并启动 | 依赖失败明确报错且清理本次资源,不切换 headless;正常启动只使用自建 Xvfb,不连接继承的显示地址 |
| A05 停止/回收后会话保存(P1) | 人工登录测试账号,记录身份;依次 stop/start、recycle/startgateway 重启恢复另见 A15P3 | Profile 保留;平台未主动撤销时身份一致;若平台要求再登录则如实记录原因,不能用该情况掩盖 Profile 丢失 |
| A06 无屏启动 | 无桌面主机清空继承 DISPLAY,启动 native 环境并使用人工入口访问、点击和输入 | 新建专属 Xvfb;浏览器 headed;远程画面和输入均正常;不需要物理屏幕 |
| A07 人工登录 | Xvfb 人工入口完成平台登录,遇到验证码由人处理;读取自身身份 | 无自动注入密码/验证码;身份匹配;无法完成登录记阻塞,不以静态截图代替 |
| A08 停止与回收 | 对本机 Xvfb 执行 stop 两次、start、recycle 两次;观察 unit/端口/显示/目录 | 动作幂等;受控进程树与监听端口已释放;Profile 保留;不关闭其他显示服务 |
| A09 多环境 | 同一 native gateway 同时启动 Xvfb 环境 A、B、C;分别改变页面,再停止 B | 三个 Profile、runtime、显示号独立;B 停止不影响 A/C;Docker 环境同时正常 |
| A10 同 Profile 争用 | 并发发送同 alias start;再让另一 gateway 实例争抢同一 Profile | 第一次最多一个浏览器;重复同请求返回已有实例或明确冲突;跨进程冲突 409;无 Profile 损坏 |
| A11 显示与端口冲突 | 占用候选显示号;制造 CDP/RFB 端口占用;使 X client 连接失败 | 仅在正常分配阶段另选未占用资源或明确失败;不连接别人的服务;失败有阶段与清理证据 |
| A12 子进程故障 | 分别终止受控 Xvfb、浏览器、人工操作服务;再次执行健康/状态查询 | 浏览器或显示失效不得仍报 running;必需操作入口失效可见;清理仅限本环境;没有自动重发动作 |
| A13 启动/停止超时 | 让受控浏览器在启动阶段无响应、在停止阶段不退出 | 60 秒启动上限后失败并清理;停止 30 秒内终止受控进程树或标记 unknown;原始失败和清理结果可查 |
| A14 不误杀 | 同用户另启非托管测试进程和独立 Xvfb 浏览器;启动/停止/回收托管环境;模拟记录 PID 被复用 | 非托管进程、独立显示服务与其他环境不受影响;所有权不符拒绝终止并报告原因 |
| A15 gateway 重启与崩溃恢复(P3) | 按下方 A15 分项分别测试正常重启、运行中崩溃、启动中断、请求中转恢复、人工入口失效和归属异常 | 每个分项分别留证;匹配且就绪的已完成实例才恢复;未完成启动只清理,不新开浏览器;Profile 保留,残留资源和失败原因可查 |
| A16 主机重启 | 保留 Profile,重启无图形登录主机,恢复用户服务并启动环境 | 不把旧 runtime/PID 当新实例;旧实例已退出;经显式 start 新建 Xvfb 运行实例,无需桌面会话 |
| A17 代理与断线 | 本机 Xvfb 分别选直连、选测试代理;通过浏览器请求检查出口;再停代理 | 直连按选择工作;代理模式流量到达所选出口;失效可见且无自动直连;日志/参数无凭据 |
| A18 旧代际请求 | 重启生成新 runtime;对新环境提交旧 stop/业务动作/订阅请求 | 409 或对应明确代际冲突;新实例未被操作,旧监听不继续发送 |
| A19 版本升级 | 用已安装目标版本升级;再选不存在/不兼容版本;检查 Profile 和恢复操作 | 成功使用目标版本且保留 Profile;不存在版本在启动前拒绝;不兼容如实失败,无清空或降级 |
| A20 平台链路 | 本机 Xvfb 先验证抖音自身身份和读取;由测试对端产生互动事件;按 plan01 验证确认/策略;随后同样核对小红书 | 真实事件来自当前账号/实例,无旧订阅重复执行;写前核对身份,人工逐次确认,自动遵守策略;平台缺口明确列为阻塞 |
| A21 Docker 回归 | 原部署重复 create/start/stop/recycle/upgrade、出口故障、租约恢复和人工操作 | 原 Docker 行为不变;native 依赖不成为 Docker 启动条件;没有假 native 数据进入 Docker 解析 |
| A22 页面与诊断 | 两类 gateway 切换创建环境,查看失败详情、禁用项、运行模式;按 operation ID 查完整启动失败链 | 只展示当前能力允许的字段;失败可定位;不展示秘密;服务健康与浏览器状态不混淆 |
A15 必须分别记录以下结果,不得只验证运行中的直连环境:
- **已完成实例与会话**:人工登录后分别正常重启、强制终止并重启主 gateway;原 runtime 不变、不新开浏览器,Profile 保留。平台未撤销会话时身份一致;平台要求重新登录时记录原因,不能掩盖 Profile 丢失。
- **启动中断**:分别在归属记录落盘前后、unit 创建前后、启动完成记录落盘前后终止 gateway。归属记录未落盘不得已有 unit;完成记录未落盘的本次资源仅清理、不续跑;完成记录已落盘的实例按完整就绪检查恢复。每次均核对进程、端口、显示、锁与 Profile,不能留下无法解释的残留,也不能重复启动。
- **归属与清理异常**:分别测试 unit 已退出、记录缺失或不符、Profile 所有权不符、清理失败;已退出如实停止,无法确认归属或清理结果时进入 unknown,阻止同 Profile 再启动,不误杀其他进程。
- **请求中转恢复**:使用平台绑定的测试代理,在浏览器运行时终止 gateway;重启后由平台重新下发匹配配置,验证原中转地址恢复且实际浏览器出口正确。另测配置不可取、绑定不符、原端口被占用、上游不可用;均不得恢复 running 或改为直连。重复下发不创建重复中转,旧运行配置明确拒绝。
- **人工入口失效**:保持 unit、显示、CDP 和出口可用,仅使 RFB 入口不可见或无法输入,再重启 gateway;不得恢复 running 或事件订阅,按失败分支清理并保留 Profile。
A20 的真实平台验收按 plan01 顺序执行:先抖音,后小红书。平台自身能力未验证不得阻挡对“本机运行层”作单独结论,但不得将运行层通过写成“两平台业务全部通过”。所有跳过项必须列出,不能默认视为通过。
### 8.3 自动化检查与门槛
实现必须保留最小可复现回归测试:模式校验、条件载荷、Profile 跨进程锁、超时清理、归属与旧代际拒绝、重启恢复、秘密脱敏。用真实进程/文件锁验证生命周期关键边界;Mock 只用于可控错误注入,不代替真机显示验收。
- Python`python -m unittest discover -s cmd`;覆盖率检查必须覆盖 native 生命周期与 gateway 集成,相关模块和整体单元测试覆盖率均不低于 65%。覆盖率工具加入开发依赖锁定文件,实施 PR 提供实际非交互命令及报告。
- Go`go test ./...``go vet ./...``go build ./cmd/control-plane`;涉及运行状态/并发,必须加 `go test -race ./...`
- 前端:`npm --prefix web ci`、仓库实际非交互测试命令、`npm --prefix web run build`;新增能力选择、失败/禁用状态有交互测试。
- Docker`docker compose config --quiet`,构建、启动、健康检查及 A21;不得仅凭配置解析通过宣称运行通过。
- 本机:Xvfb 安装/启动命令、用户 unit 配置、故障注入步骤和 A01–A22 证据一并交付。
文档任务本身不需要运行上述功能测试;当前没有 native 实现,运行旧测试也不能证明它存在。
## 9. 风险、排除项与交付判定
- **显示归属**:浏览器必须连接当前实例的 Xvfb,不能继承宿主机 DISPLAY 或误连其他显示服务;以连接探测与归属核验验证,不以进程存在代替。
- **浏览器发行版差异**:CDP、指纹参数和 Profile 升级行为需用所选二进制验证;不承诺任意浏览器兼容。Chrome 136 起针对默认数据目录的远程调试参数受到额外限制,专用 Profile 是必要设计,仍需验证所选 Chromium 派生版本。
- **同用户多显示会话**:Chromium 官方资料提示多图形 session 可能存在 D-Bus 串扰;不同 Profile 不足以证明多个 Xvfb 实例并发可用。P0 必须实测 D-Bus、密钥环与登录持久性;若需要独立 session bus,优先验证系统已有 `dbus-run-session`,证据不足时阻塞,不提前承诺隔离已完成。A09 除窗口互不影响外,还必须核对各自账号身份与重启后的持久数据。
- **远程人工操作**:Xvfb 能启动不代表登录可完成;A06/A07 是必测,不允许缩减为 headless 截图。
- **系统依赖**systemd 用户服务与 Xvfb/x11vnc 为本机部署依赖;无此依赖不是“部分成功”,必须阻断并提示安装要求。
- **业务结果不确定**:gateway 崩溃后浏览器可能仍执行最后一次动作,恢复仅核验结果,不自动重放发送。
- **不新增**Linux 真实桌面支持、Windows/macOS 支持、跨机器 Profile 迁移、任意 CDP/命令服务、自动登录、平台风控规避、认证系统或本机网络隔离。
功能验收结论必须分别填写:Docker 回归、本机 Xvfb、抖音业务、小红书业务。任一核心生命周期/归属/Profile 测试失败,本机 Xvfb 支持不能标为完成。真实账号或机器条件缺失时写“阻塞及所需条件”,不能写“预计通过”。
## 10. 官方资料
下列为设计依据,不是本仓库已完成证明;引用运行约定,不复制实现代码。实际引入软件时登记所选版本、来源和许可证;Playwright 为 Apache-2.0Chromium 使用 BSD 风格及第三方许可证,X.Org 组件以各包许可清单为准,systemd 以其发行包许可清单为准。
- Playwright CI / headed Linux 与 Xvfb<https://playwright.dev/python/docs/ci>
- Playwright persistent context / Profile 独占:<https://playwright.dev/python/docs/api/class-browsertype#browser-type-launch-persistent-context>
- Chromium User Data Directory<https://chromium.googlesource.com/chromium/src/+/main/docs/user_data_dir.md>
- X.Org Xvfb 手册:<https://www.x.org/releases/current/doc/man/man1/Xvfb.1.xhtml>
- systemd transient service<https://www.freedesktop.org/software/systemd/man/latest/systemd-run.html>
- systemd 进程组终止规则:<https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html>
- systemd 服务停止与清理:<https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html>
- X.Org 显示连接检查与授权:<https://www.x.org/releases/current/doc/man/man1/xdpyinfo.1.xhtml>、<https://www.x.org/releases/current/doc/man/man7/Xsecurity.7.xhtml>
- Debian `xvfb-run`(包含 `--wait` 已忽略的说明,不能照旧教程使用):<https://manpages.debian.org/bookworm/xvfb/xvfb-run.1.en.html>
- Chrome 远程调试参数变化:<https://developer.chrome.com/blog/remote-debugging-port>
- Patchright Python 上游(Apache-2.0headed 示例不等于锁定版本兼容保证):<https://github.com/Kaliiiiiiiiii-Vinyzu/patchright-python>