refactor: migrate browser gateway to native xvfb
This commit is contained in:
@@ -1,86 +1,83 @@
|
||||
# 浏览器容器控制面
|
||||
# 宿主机浏览器控制面
|
||||
|
||||
> 当前实现说明,核对基线 `main@1fbf126`;不是新业务完成证明。目标范围与验收以 [plan01](../plan01.md) 为准,运行步骤见[部署说明](../deployment.md)。现状限制不自动成为新产品约束。
|
||||
> 当前实现说明,基于已同步的 `main@7e3808cf4ce453b3583079680a3b59ca2ed64ad4`。单节点 native browser + Xvfb 已实现;真实平台、代理、LAN 和多节点能力仍以[验证文档](../native-browser-verification.md)为准。业务范围以 [plan01](../plan01.md) 为准,部署步骤见[部署说明](../deployment.md)。
|
||||
|
||||
## 技术选型
|
||||
|
||||
- 前端:React 19 + Vite 8 + Refine + shadcn/ui + Tailwind CSS,图标 RemixIcon;项目自有 Layout 与 HashRouter,当前页面见 [main.jsx](../../web/src/main.jsx),依赖见 [package.json](../../web/package.json)。
|
||||
- 后端:Go 模块化单体,Fiber v3 提供 HTTP 路由,Viper 读取并校验启动配置,Logrus 输出 JSON 结构化日志,Cobra 保持当前两个服务入口。控制面提供同源 API 和静态文件,并编排网关;受限网关单独封装 Docker Engine API,是纯执行器。
|
||||
- 数据:环境配置(别名、中文名、网关、镜像版本、指纹参数)持久化在 Postgres,列表/详情读取持久运行记录,不触发网关实时探测;后台独立维护运行租约;Profile 使用命名卷持久化;阶段 A 账号、凭据引用、确认、任务、尝试和审计实体同样由控制面持久化到 Postgres。
|
||||
- 部署:Docker Compose 启动控制面和受限网关;浏览器容器由网关按平台下发的镜像引用动态创建,缺失时自动拉取。
|
||||
- 前端:React 19 + Vite 8 + Refine + shadcn/ui + Tailwind CSS,图标 RemixIcon;项目自有 Layout 与 HashRouter。
|
||||
- 控制面:Go、Fiber v3、Viper、Logrus、Cobra。控制面编排浏览器 gateway、环境、账号、任务和审计,不直接启动浏览器。
|
||||
- 浏览器 gateway:Python 3.12+ 标准库 HTTP/进程/文件/socket 能力;以非 root `systemctl --user` 服务运行,在宿主机管理 Xvfb、native/fingerprint browser、Profile、CDP、代理和运行代次。
|
||||
- 数据:PostgreSQL 保存网关登记、浏览器版本、环境绑定、运行实例、清理状态、租约、账号、任务、结果和审计。`browser_version.browser_path` 是宿主机已安装的可执行文件路径,不是镜像引用。
|
||||
- 部署:Compose 只提供控制面和 PostgreSQL;gateway 与 Xvfb 在宿主机运行。控制面在 Compose 中通过 `host.docker.internal:8081` 访问 gateway,裸机运行使用 `127.0.0.1:8081`。
|
||||
|
||||
## 调用链与契约
|
||||
## 调用链与职责
|
||||
|
||||
```text
|
||||
React ── /api/* (Basic Auth) ──> control-plane
|
||||
├─ /v1/browsers (Bearer token) ──> docker-gateway ──> docker.sock
|
||||
│ └─> browser container
|
||||
├─ /v1/browsers/.../douyin/events (Bearer token) ──> docker-gateway ──> browser event stream
|
||||
└─ 账号/环境/任务/互动事件持久记录 ──> PostgreSQL
|
||||
React ── /api/* (Basic Auth) ──> control-plane ──> PostgreSQL
|
||||
│
|
||||
└─ /v1/browsers (Bearer token)
|
||||
└─ host-native browser gateway
|
||||
├─ Xvfb display
|
||||
├─ fingerprint browser + Profile
|
||||
├─ CDP endpoint
|
||||
└─ loopback proxy / event stream
|
||||
```
|
||||
|
||||
控制面是唯一事实源:网关不持有镜像清单和业务规则,镜像引用、启动命令和卷名均随请求下发。
|
||||
控制面是业务事实源,gateway 是本机浏览器资源事实源。gateway 不接受任意 Profile 路径、不暴露通用 CDP、不创建 Docker 资源;Profile 由 gateway 根据 `profile_id` 映射到 gateway 自己管理的目录。
|
||||
|
||||
当前生命周期契约集中如下(代码:[控制面](../../cmd/control-plane/hub.go)、[环境存储](../../internal/hub/environment.go)):
|
||||
## 生命周期契约
|
||||
|
||||
生命周期操作都必须带当前 `binding_version`,并在需要时带 `runtime_id`、`network_id` 和 `runtime_instance_id`。服务端以 alias 串行化操作,拿锁后重新读取账号、环境、版本和出口。
|
||||
|
||||
| 接口/动作 | 当前行为与失败语义 |
|
||||
| --- | --- |
|
||||
| `POST /api/browsers` / create | 严格接收 `{alias, name, gateway, image_version, fingerprint, account_id, network_exit_id}`,未知字段拒绝;`account_id` 必填,`network_exit_id` 可空表示明确选择直连。新绑定要求账号 authorized/paused、镜像启用,指定出口须健康并再次核验。先保存环境与稳定 binding,创建停止态容器;不是创建即启动。相同绑定及配置可复用,冲突拒绝;已存在且账号可运行的环境可调和/恢复运行。网关失败不删除已保存环境/binding,保留以供核验/恢复 |
|
||||
| `GET /api/browsers`、`GET /api/browsers/{alias}` | 只读数据库环境/运行记录;列表的 running 表示已记录运行实例,不保证实时存活。页面可直接展示返回 status,不主动探测或轮询。后台租约调和与列表读取独立 |
|
||||
| `POST /api/browsers/{alias}/start` | 要求账号可运行;有出口时重新核验,不健康则失败而非直连。当前匹配且就绪的运行容器可复用;停止/缺失容器按当前 binding 重建并激活租约;支持预先选定的直连 |
|
||||
| `POST /api/browsers/{alias}/stop` | 停止容器并处理租约/清理状态;失败或结果不明可见,不据请求发出即声称已停止 |
|
||||
| `POST /api/browsers/{alias}/upgrade` | 接收 `{version}`,要求启用镜像;删除旧容器并保留 Profile,更新版本后按原指纹/binding 重建,账号可运行才启动,否则为停止态。无自动回滚;失败保留结果,人工重试进入现有调和流程。当前实现无条件核验出口,空出口的直连环境不能据 create/start 成功推断 upgrade 可用 |
|
||||
| `POST /api/browsers/{alias}/rebind` | 接收 `{network_exit_id}`;要求 paused、无 executing task、无活动 runtime,验证目标出口后变更。当前要求有效出口 ID,不支持以空值切回直连 |
|
||||
| `DELETE /api/browsers/{alias}` / recycle | 回收容器、处理运行记录,但保留环境、稳定 binding 与命名 Profile 卷;后续 create/start 复用。不是永久删除账号/环境或素材;没有新增永久删除 API |
|
||||
| `POST /api/browsers` | 接收 `alias`、`name`、`gateway`、`browser_version`、`fingerprint`、`account_id`、`network_exit_id`。版本必须启用且路径为绝对可执行路径;账号必须满足授权/暂停规则,指定出口必须健康。先保存环境和稳定 binding,再向 gateway 创建停止态 runtime;失败保留环境、operation 和清理状态,不伪造成功。 |
|
||||
| `GET /api/browsers`、`GET /api/browsers/{alias}` | 返回数据库状态与 gateway 可达时的 runtime 快照,包括 `node_id`、generation、ready、运行状态、cleanup 状态和失败原因。不可达、版本缺失、Profile 占用和待清理均保持可见;读取不把未知状态改成 stopped。 |
|
||||
| `POST /api/browsers/{alias}/start` | 重新核对账号、版本、Profile、出口和 binding。匹配的 ready runtime 可复用;缺失或不匹配时按当前 binding 创建新代次并激活。必需代理失败时不得直连。 |
|
||||
| `POST /api/browsers/{alias}/stop` | 以当前 generation 停止浏览器、Xvfb、代理和订阅,并记录独立 cleanup 状态。结果未知、强杀或清理失败返回可见失败/待清理状态,而不是请求发出即成功。 |
|
||||
| `POST /api/browsers/{alias}/upgrade` | 接收 `{version}`,要求目标 browser version 启用;停止旧代次、保留 Profile、按新路径创建并按需启动。无隐式回退和自动回滚,失败保留证据供重试。 |
|
||||
| `POST /api/browsers/{alias}/rebind` | 接收有效 `network_exit_id`,要求没有执行中的 runtime-use 或业务任务;成功后递增 binding version。空值切直连不通过此接口隐式完成。 |
|
||||
| `DELETE /api/browsers/{alias}` | 回收当前 runtime 和本次临时资源,保留环境、稳定 binding、账号 Profile、正式结果和长期监听。旧 generation 的迟到清理不会触碰 successor。 |
|
||||
|
||||
`name` 是环境展示名,`alias` 限 `^[a-z0-9][a-z0-9-]{0,31}$`;容器名 `creatorhub-browser-<alias>`,Profile 卷 `creatorhub-profile-<alias>`。指纹中的代理字段拒绝,使用所选出口;已绑定代理失败不能静默直连。create/start/stop/upgrade/recycle 写同一 operation ID 的 requested/finished 审计对,网关断连且无法调和时 outcome 为 `unknown`;幂等/重试是生命周期核验,不等于 plan01 中人工/自动业务发送可重发。
|
||||
每个 runtime 独立拥有:`runtime_id`、`runtime_instance_id`、`generation`、`node_id`、owner、Profile、display、CDP 端口、loopback proxy 端口、browser/Xvfb unit、日志目录和 cleanup 状态。runtime 结束后,任务目录按结果语义清理;正式素材和结果目录不由通用 runtime 清理逻辑删除。
|
||||
|
||||
- `GET/POST /api/browser-images` 维护可用镜像版本(版本号不可改,`PUT /{version}` 仅接受 `image_ref/note/enabled`);仅启用版本可用于创建与升级;被环境引用时拒绝删除。
|
||||
- `GET/POST /api/gateways` 注册网关(`POST` 可携带令牌,否则平台生成 48 位十六进制令牌并明文存储),`DELETE /api/gateways/{name}` 删除;仍被环境引用时拒绝删除。
|
||||
- 别名唯一约束由数据库保证;控制面不自动重试结果不明的用户生命周期请求,后台租约恢复的现状另见下文。
|
||||
## 浏览器版本、网关与 Profile
|
||||
|
||||
## docker.sock 安全边界
|
||||
- `GET/POST /api/browser-versions` 管理 `{version, browser_path, note, enabled}`;版本号不可改,只有启用版本可创建或升级环境。旧迁移 SQL 中出现的 `browser_image`/`image_ref` 仅是 PostgreSQL 前向迁移的历史表名,当前 API、模型、页面和运行时均使用 browser version/path。
|
||||
- `GET/POST /api/gateways` 注册稳定 gateway endpoint、node name 和令牌;gateway `/v1/info` 返回稳定 `node_id`、版本和能力。当前目标只调度单 gateway,不执行跨机迁移。
|
||||
- `profile_id` 由控制面绑定账号和环境,gateway 只接受已登记的标识并生成目录。Profile 目录权限为 owner-only;同一 Profile 的第二个 runtime 在资源锁阶段拒绝。
|
||||
- 每个执行使用独立的 `.runs/<execution_id>` 目录和不可变 token。业务结果引用相对 work root 的正式产物;路径穿越、绝对路径、跨 work 目录和临时文件均拒绝。
|
||||
|
||||
将 socket 以只读文件挂载**不会**限制 Docker API 的写操作;拥有 socket 等价于拥有宿主机 root 权限。因此:
|
||||
## generation 与 runtime-use lease
|
||||
|
||||
- 只有 `docker-gateway` 挂载 socket,控制面和浏览器容器均不可见;网关加入 control 并按需接入浏览器隔离网络,浏览器在代理模式只拿到无凭据的内存转发代理地址,`/v1` 仍必须通过容器内不可见的网关令牌;
|
||||
- 网关只暴露面向领域的路由,不提供通用 Docker 代理;`/v1` 全部接口校验 `Authorization: Bearer <GATEWAY_TOKEN>`(常数时间比较),令牌由部署者在网关环境变量与平台注册表中保持一致;
|
||||
- 网关直连的 `POST /v1/browsers/{alias}/start|stop` 仅供内部维护使用,必须提交并精确匹配容器标签中的 `{binding_version,runtime_id,network_id}`;直连 start 拒绝空 `network_id`,generation 不匹配返回 `409`,控制面生命周期编排不依赖无 fence 的直连 start;
|
||||
- 网关恢复内存代理时会同时 fence 隔离网络成员及网关成员 IPv4,地址变化返回 `409` 并关闭刚恢复的监听;代理移除或代际替换会立即关闭所有已 hijack 的 CONNECT 双向连接,任一端先关闭也会关闭隧道两端,不等待优雅 drain;
|
||||
- 网关固定命令、网络、挂载和资源限制;外部输入是受校验的别名,以及平台下发的镜像引用、启动参数和卷名——镜像引用来自平台维护的版本表,新增/变更由人工在页面审核启用,不再写死在代码中;
|
||||
- 启停和删除前必须同时匹配固定名称前缀及 `io.creatorhub.managed`、`io.creatorhub.runtime-id` 标签;
|
||||
- 动态容器使用只读根文件系统、非 root `1000:1000` 与固定镜像入口、全部 capability drop、`no-new-privileges`、CPU/内存/PID 限制,且无宿主机端口和目录挂载;
|
||||
- 控制面发布到宿主机所有网卡;控制网络为固定名称的 Compose 网络;浏览器 bridge 按 ownership、role、driver、Internal 失败关闭校验,且拒绝复用 control 网络;
|
||||
- Compose 基础镜像锁定 digest;浏览器镜像推荐使用 `@sha256:` 摘要引用以获得不可变性,tag 引用由部署者自行把控。
|
||||
- 激活 runtime 时保存 owner、node、binding version、runtime/network ID 和 runtime instance ID。所有 gateway 写操作核对这些值;旧代次收到迟到请求时返回冲突,不删除 successor。
|
||||
- 业务任务和长期监听不共享“环境存在”这一隐含占用。`runtime_use_lease` 默认 60 秒,20 秒续租;任务、素材处理、平台读取、会话历史和监听均显式 acquire/renew/release。
|
||||
- lease 续租失败会取消使用上下文并保留失败原因;释放操作幂等。gateway 重启后先按 runtime instance 恢复或标记 cleanup pending,再允许新 lease。
|
||||
- 采集 task lease 与合法长期监听 lease 分开;监听重连会核对 node/generation/binding,事件去重和 UID 冷却仍由 creator 数据层负责。
|
||||
|
||||
网关自身一旦被攻破,socket 仍允许接管宿主机;应用内校验不能消除这个平台级风险。开发目标不新增认证或访问限制,安全由部署者自行把控;但当前代码仍强制 HTTP Basic Auth(除 `/healthz` 外的 API 与静态页面),Compose 仍要求用户名/密码。见 [main.go](../../cmd/control-plane/main.go) 与 [compose.yaml](../../compose.yaml);本轮未移除现有策略,也不新增 RBAC 或认证 profile。
|
||||
## 代理边界
|
||||
|
||||
## 运行
|
||||
控制面只把经过校验的出口配置交给 gateway。gateway 代理绑定 `127.0.0.1` 动态端口,并以 alias、binding version、runtime/network generation 绑定;代理恢复或移除失败必须可见。必需代理启动失败时不切换直连,不把不确定的写结果转换成成功。
|
||||
|
||||
使用[部署说明](../deployment.md)中的完整变量及启动命令(含 Basic Auth、凭据主密钥与 socket GID),不维护另一套省略必填配置的命令。首次注册网关和镜像、创建账号、再创建停止态环境;恢复账号后显式启动。网关拉取镜像上限约 10 分钟;失败查看审计和保留的环境,不能按“数据库已回滚”直接假定没有资源。
|
||||
出口凭据只存在于控制面单次请求和 gateway 内存代理,不能进入 URL、日志、数据库、浏览器命令行或 API 响应。代理检查、重启恢复和清理均按当前 generation fence。
|
||||
|
||||
## 现有账号与阶段 A 离线闭环
|
||||
## 非 root 与系统边界
|
||||
|
||||
本节描述已运行的基础及历史 schema,不规定 plan01 的新业务范围。旧阶段 A 的 Mock 任务验证不等于 G0 平台能力或 G1 抖音完整验收;当前抖音受限读取连接器也未接成完整竞品/监听/发送流程。
|
||||
- gateway 由宿主机非 root 用户的 `systemctl --user` 服务运行;Xvfb、browser 和 Profile 的 unit 都属于该用户。
|
||||
- native gateway 不读取 Docker socket,不创建/删除浏览器 container、image、volume 或 network,也不保留 Docker fallback。
|
||||
- 启动命令拒绝 `--no-sandbox` 等越权参数;无法满足 sandbox、磁盘、路径、端口、权限或版本条件时直接失败并记录原因。
|
||||
- `/healthz` 用于存活;其它 gateway API 使用 Bearer token。控制面继续按现有部署策略提供 Basic Auth;本目标不新增 RBAC。
|
||||
|
||||
`POST /api/phase-a/accounts` 只接受 `{name, platform, platform_account_key, tags, cookies}`;`platform` 限定为 `douyin`、`xiaohongshu`、`wechat-official`、`kuaishou`,`cookies` 可空,非空时须为浏览器 Cookie Header 格式。控制面通过持久 provider bridge 安全写入凭据:部署侧 Secret Manager/OS Keyring 注入 32 字节主密钥,独立凭据卷只保存 AES-GCM 密文,数据库只记录凭据引用;外部 API 不返回 Cookies、provider 或 `reference_key`。数据库明确回滚时清理凭据,提交结果未知时保留凭据并返回 `account_creation_result_unknown`,不自动破坏可能已提交的账号。内部账号 ID 由服务端生成,新账号默认 `paused`,`(platform, platform_account_key)` 全局唯一。pause/revoke 会递增账号版本并将 queued 任务置为 `policy_hold`,resume 要求稳定 binding,若绑定出口则需 healthy,并满足无活动 runtime/待清理等条件;未绑定出口的显式直连不要求出口记录。账号与浏览器环境通过一对一 `environment_binding` 关联,出口可复用;运行实例保留历史,并以 binding 和外部 runtime id 的部分唯一索引限制活动实例。
|
||||
## 运行与恢复
|
||||
|
||||
草稿经 `POST /api/phase-a/confirmations` 显式确认后才可投递到 `/api/phase-a/tasks`。任务由幂等键去重;`POST /api/phase-a/mock/execute` 使用 `FOR UPDATE SKIP LOCKED` 领取一分钟租约,执行前统一核对账号、草稿和确认版本。缺少确认或版本不一致会进入 `needs_confirmation`,暂停账号或 Mock 策略结果会进入 `policy_hold`,不确定结果与过期租约进入 `needs_confirmation`;这些状态都不会自动重试。`GET /api/phase-a/audit` 只导出账号、确认版本、尝试和结果等非秘密证据。
|
||||
使用[部署说明](../deployment.md)中的变量和命令。首次部署时先安装已批准的浏览器版本和 Xvfb,再以 `scripts/install-native-browser-gateway.sh` 安装 gateway user service,随后在控制面注册 gateway、browser version、账号和出口。
|
||||
|
||||
启动时控制面先应用 Phase A v1,再由 Hub runner 顺序应用 v2 至 v14;CreatorHub 业务迁移继续顺序应用至 v26(竞品同步 lease token、事件消息正文);每一步都在事务和 advisory lock 下前向执行。v3 保留旧表、列和历史记录,旧账号回填为 `platform=mock` 并暂停,仅账号 ID 与环境 alias 相同的记录自动建立 binding;v4 追加环境动作审计字段与索引,v5 清理持久 fingerprint 中的旧代理字段,v6 增加可重试的 runtime cleanup 状态,v7 为 runtime lease 增加 binding version 并回填可确定的既有记录,v8 至 v10 补齐 cleanup/runtime 的不可变 generation 与兼容约束,v11、v12 增加任务恢复状态并修复兼容约束,v13 增加账号名称和 TAGS;v14 仅前向修复旧 PR v13 的空数据 schema。若旧 v13 已产生空引用或明文 Cookies,v14 会在删除前阻断启动,必须先将凭据迁入 provider。其余记录等待显式绑定。本阶段不提供破坏性自动回滚。
|
||||
启动、停止、回收和升级分别写 requested/finished operation;gateway 不可达、返回未知、cleanup pending、Profile 被占用和版本缺失都在页面和 API 显示。gateway 重启只恢复仍匹配当前代次的 runtime;机器重启不自动恢复已过期短任务。控制面后台 heartbeat 续租 running runtime 并调和 cleanup,读取列表不会用轮询伪装业务事件监听。
|
||||
|
||||
`POST /api/network-exits` 只接受协议、主机、端口、已有 `credential_reference: {id}` 和预期出口身份;新出口为 `unchecked`,由 `POST /api/network-exits/:id/check` 经实际代理链路变为 `healthy` 或 `unhealthy`,`disable` 不可被检查重新启用。credential reference 的 `reference_key` 不出现在 API、日志或审计中;OS Keyring/Secret Manager bridge 在控制面进程启动前注入 `CREATORHUB_CREDENTIAL_<SHA256(reference_key)>`(大写十六进制),值为请求期解析的 `username:password`,控制面不持久化解析值。
|
||||
## 阶段 A 与 Creator 业务边界
|
||||
|
||||
环境创建/启动/回收及直连边界以上方生命周期表为唯一说明。已配置代理时由控制面下发代理信息与 `disable_non_proxied_udp`;直连并非代理失败后的替代路径。
|
||||
阶段 A 账号、凭据引用、确认、任务和审计继续由 PostgreSQL 持久化。账号身份在每次写操作前由 gateway 返回的实际 UID 与已授权 `platform_account_key` 核对;Cookie、密码、token 和 provider reference 不回显。
|
||||
|
||||
解析后的出口凭据只存在于控制面单次请求和网关内存转发器中;Docker inspect、容器环境、标签、挂载、`Config.Cmd` 与进程参数只包含 `docker-gateway` 的无凭据本地代理地址。网关内存代理以 alias、binding version 和 exit ID 共同标识 generation;生命周期操作按 alias 串行,重启恢复或重建必须重新核对该 generation,旧出口代理不能被新容器复用。
|
||||
Creator 采集流程使用独立 runtime-use lease:登录二维码、身份核对、抖音读取、评论/私信历史、素材下载、音频提取和指标处理完成后才释放浏览器使用权。结果保存与资源清理分开记录;结果未知时保留执行目录和证据,不能重复写入或删除可能仍被引用的素材。
|
||||
|
||||
控制面后台每 20 秒调和网关([runtimeLeaseHeartbeat](../../cmd/control-plane/main.go)),续租 running runtime、释放 stopped/missing runtime;列表/详情读取不触发调和,过期 lease 也会在绑定事务中回收。控制面还按账号调和抖音事件监听([creator_events.go](../../cmd/control-plane/creator_events.go)):每个已授权且有有效运行代际的账号只有一个监听,网关断连或代际改变时停止旧监听并退避重连;事件通知在控制面转换为互动事件后进入 `ProcessAutomaticEvent`,数据库的事件唯一键负责去重,写入结果不明不会由监听器重复发送。当前通知边界、平台事件游标/基线连续性和真实写操作仍需真机证据,不能把网关轮询队列视为平台监听验收。控制面用 PostgreSQL advisory transaction lock 按 alias 协调多副本;每个 Store 最多允许 5 个锁会话占用 10 连接池的一半,为锁内数据库调用保留连接。create 同时锁定账号 ID、alias、请求出口和请求镜像;start、reconcile/rebuild、rebind 和 upgrade 锁定 alias、当前出口及当前镜像(upgrade 还锁目标镜像),拿锁后重新读取出口与镜像版本。账号 pause/resume/revoke 使用账号 ID 与当前 binding alias 加入同一协调域;镜像禁用、引用更新或账号状态变更不能穿透在途生命周期。
|
||||
|
||||
非法 upgrade/rebind 目标在进入 advisory lock key 前按公开格式校验;审计仅保留环境原有的非秘密资源关联,并以 `upgrade_input_rejected` / `rebind_input_rejected` 写同一 operation ID 的 requested/finished 对。reconcile 恢复或重建后会重新读取 context,finished 事件关联实际激活的 runtime instance、binding version 与出口;后台释放 runtime 的成功或失败也写独立的 `reconcile` 审计对。
|
||||
|
||||
## 与新业务计划的边界
|
||||
|
||||
- 当前受限抖音读取仅允许自身身份与 `count=20/max_cursor=0` 首批作品,JSON 响应限制为 1 MiB,见 [douyin.py](../../cmd/docker_gateway/douyin.py)。竞品同步仍需外部平台证据;媒体通过 `POST /api/creator/works/:id/material/process` 下载到持久卷、用 FFmpeg 提取音轨,再调用显式配置的 `CREATOR_TRANSCRIPTION_BIN`。未配置转写入口时明确记为 failed,不接受手写 succeeded 或二进制塞入通用 JSON。
|
||||
- 当前账号凭据入口处理 Cookie;登录密码可按现有凭据保存流程保存,读取不回显、不进入日志,但绝不由系统自动注入或用于绕过人工登录。
|
||||
- 新业务平台监听、前端业务推送和现有后台运行租约是三件事;前两者要求见 plan01 A6,列表不主动探测的约定不禁止业务事件推送。现有生命周期/租约可能核验出口,不应误写成已完成 plan01 的手动代理管理目标。
|
||||
当前仍需真实账号、真实代理和人工 LAN 验收的项目见[验证记录](../evidence/native-browser-verification-2026-09-18.md)。多节点、A/B、跨机故障恢复和性能对比明确移出本目标。
|
||||
|
||||
+107
-477
@@ -1,525 +1,155 @@
|
||||
# CreatorHub 部署
|
||||
# CreatorHub 单节点部署
|
||||
|
||||
本文档按当前发布提交说明单台 Linux 主机 Docker Compose 部署及离线检查,不证明新产品功能已实现。[plan01](plan01.md) 是业务范围与验收依据;离线结果不能替代抖音/小红书最终真机验收。
|
||||
本文档描述当前单节点方案:control-plane 管理业务和数据库,宿主机上的 native browser gateway 管理 Xvfb、预安装浏览器、Profile、CDP 与代理。Docker 只可作为 PostgreSQL 的独立部署方式,不参与浏览器生命周期。
|
||||
|
||||
当前控制面仍使用单用户 HTTP Basic Auth(除 `/healthz` 外,包括静态页面),不提供 RBAC 或多租户隔离。开发目标不新增认证/访问限制,但本轮未删除现有代码或配置;以下变量仍须填写,不新增认证 profile。
|
||||
## 服务与数据边界
|
||||
|
||||
## 部署内容
|
||||
| 服务 | 位置 | 责任 |
|
||||
| --- | --- | --- |
|
||||
| `creator-hub` | 宿主机或 Compose | Go control-plane 与前端静态文件 |
|
||||
| native browser gateway | 宿主机 systemd user service | Xvfb、浏览器、Profile、runtime、CDP、代理 |
|
||||
| PostgreSQL | 宿主机或 Compose | 业务、绑定、runtime generation、cleanup 和 lease 状态 |
|
||||
|
||||
`compose.yaml` 会启动以下服务:
|
||||
gateway 以非 root 用户运行。每个 runtime 有独立的 Xvfb display、CDP/代理端口、systemd transient unit、临时目录、owner、generation 和日志;账号 Profile、正式素材和长期监听不属于任务临时清理对象。
|
||||
|
||||
- `creator-hub`:Go 1.26 控制面,同时提供 React 19/Vite 8 构建的静态页面;
|
||||
- `docker-gateway`:受限 Docker API 网关,是唯一挂载 `/var/run/docker.sock` 的服务;
|
||||
- `postgres`:PostgreSQL 17,数据保存在 `creatorhub_postgres` 命名卷;
|
||||
- 浏览器容器:由网关按需创建,镜像引用由平台「镜像版本」页配置(缺失时网关自动拉取),Profile 保存在 `creatorhub-profile-<别名>` 命名卷。
|
||||
|
||||
控制面发布到宿主机所有网卡,局域网内可直接访问;浏览器容器可访问外网。
|
||||
`creator-hub` 会等待 `docker-gateway` 健康检查通过后再启动。
|
||||
当前目标是单机单节点。多节点调度、A/B 环境和跨机恢复不属于本轮部署步骤。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- Linux 主机;
|
||||
- Docker Engine 26 或兼容版本;
|
||||
- Docker Compose v2;
|
||||
- 当前用户可访问 Docker daemon;
|
||||
- 可访问镜像仓库(如 `git.ipao.vip`),并已完成登录(如仓库要求认证);镜像也可不在宿主机预拉取,网关会在缺失时按引用自动拉取;
|
||||
- `curl`、`jq`、`openssl` 和 GNU `stat`,用于启动与业务验证命令;
|
||||
- 若验证真实浏览器运行环境:Linux x86_64、一个可访问的固定 HTTP/HTTPS/SOCKS 出口,以及可被 Docker daemon 拉取的、以 immutable digest 固定的 `fingerprint-chromium` 镜像。镜像来源必须登记仓库地址、构建提交和 digest,禁止使用 `latest` 或未登记 tag;Xvfb/CDP 包装入口的可复现源码见 [`docker/browser-wrapper`](../docker/browser-wrapper/)。
|
||||
宿主机必须具备:
|
||||
|
||||
在仓库根目录执行预检:
|
||||
- Linux、systemd user session 和可用的 `systemd-run --user`;
|
||||
- Xvfb、`flock`、`curl`、`jq`、`openssl`;
|
||||
- 合法的 fingerprint Chromium 及其绝对路径;浏览器以普通用户运行并保留 sandbox,禁止 `--no-sandbox`;
|
||||
- 至少 20 GB 可用磁盘,Profile 缓存上限 20 GB,runtime 日志上限 1 GB;
|
||||
- 如使用代理,准备真实可访问的 HTTP/HTTPS/SOCKS4/SOCKS5 出口及认证失败测试条件;
|
||||
- PostgreSQL 17。Docker Compose 仅用于单独运行 PostgreSQL 时才需要 Docker。
|
||||
|
||||
预检:
|
||||
|
||||
```bash
|
||||
test -S /var/run/docker.sock
|
||||
docker info >/dev/null
|
||||
docker compose version
|
||||
: "${DOCKER_GID:?export DOCKER_GID=$(stat -c '%g' /var/run/docker.sock) in this shell}"
|
||||
: "${CONTROL_PLANE_USERNAME:?export CONTROL_PLANE_USERNAME in this shell}"
|
||||
: "${CONTROL_PLANE_PASSWORD:?export CONTROL_PLANE_PASSWORD in this shell}"
|
||||
: "${CREATORHUB_CREDENTIAL_MASTER_KEY:?export CREATORHUB_CREDENTIAL_MASTER_KEY in this shell}"
|
||||
docker compose config --quiet
|
||||
id -u
|
||||
systemctl --user is-system-running
|
||||
command -v systemd-run systemctl Xvfb flock
|
||||
systemd-run --user --wait --pipe --unit=creatorhub-preflight-$$ -- /usr/bin/true
|
||||
```
|
||||
|
||||
## 首次部署
|
||||
## 安装 native gateway
|
||||
|
||||
以下命令应在同一个 shell、仓库根目录执行。本说明采用显式导出模式:Compose 启动和手工验证必须读取当前 shell 中导出的同一组变量;验证脚本不会自行读取 `.env`,也不会使用 Compose 默认值。若变量只写在 `.env` 中,请先将相同值 export 到当前 shell。`DOCKER_GID` 必须与宿主机 Docker socket 的组一致;`CREATORHUB_PORT` 控制控制面的宿主机端口。
|
||||
复制配置并填写真实浏览器路径。配置文件只允许 gateway 用户读取:
|
||||
|
||||
```bash
|
||||
install -d -m 0700 ~/.config/creatorhub
|
||||
cp deploy/browser-gateway.env.example ~/.config/creatorhub/browser-gateway.env
|
||||
chmod 600 ~/.config/creatorhub/browser-gateway.env
|
||||
$EDITOR ~/.config/creatorhub/browser-gateway.env
|
||||
```
|
||||
|
||||
关键配置:
|
||||
|
||||
- `LISTEN_ADDR=0.0.0.0:8081`:允许局域网 control-plane 联调;防火墙只放行受信网络;
|
||||
- `GATEWAY_TOKEN`:至少 16 个字符,和控制面登记值完全一致;
|
||||
- `BROWSER_PATH`、`BROWSER_VERSION`:预安装浏览器的绝对路径和版本;
|
||||
- `BROWSER_STATE_DIR`:runtime 清单、锁和日志目录;
|
||||
- `BROWSER_PROFILE_ROOT`:持久账号 Profile 根目录;不得指向临时目录;
|
||||
- `RUNTIME_MIN_FREE_BYTES`、`RUNTIME_LOG_MAX_BYTES`、`PROFILE_CACHE_MAX_BYTES`:资源硬限制。
|
||||
|
||||
安装并启动 user service:
|
||||
|
||||
```bash
|
||||
scripts/install-native-browser-gateway.sh
|
||||
curl --fail --silent --show-error http://127.0.0.1:8081/healthz
|
||||
curl --fail --silent --show-error -H "Authorization: Bearer ${GATEWAY_TOKEN}" http://127.0.0.1:8081/v1/info
|
||||
systemctl --user --no-pager status creatorhub-browser-gateway.service
|
||||
```
|
||||
|
||||
若 user service 不随登录启动,按主机运行规范启用 lingering;不要把 gateway 改成 root service:
|
||||
|
||||
```bash
|
||||
loginctl enable-linger "$(id -un)"
|
||||
```
|
||||
|
||||
## 运行 control-plane
|
||||
|
||||
### Compose 运行 control-plane 和 PostgreSQL
|
||||
|
||||
Compose 不再创建 gateway 服务,也不挂载 Docker socket:
|
||||
|
||||
```bash
|
||||
export DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)"
|
||||
export CREATORHUB_PORT=8080
|
||||
export GATEWAY_TOKEN="$(openssl rand -hex 24)"
|
||||
export CONTROL_PLANE_USERNAME=creatorhub
|
||||
export CONTROL_PLANE_PASSWORD="$(openssl rand -hex 24)"
|
||||
export CREATORHUB_CREDENTIAL_MASTER_KEY="$(openssl rand -base64 32)"
|
||||
|
||||
docker compose config --quiet
|
||||
docker compose up --detach --build
|
||||
```
|
||||
|
||||
网关配置了 45 秒停止宽限期,并在收到 SIGTERM/SIGINT 时停止接收请求、排空有限期限内的在途请求,再关闭事件订阅和代理;本地测试覆盖该顺序,但部署验证仍须记录正常退出码、实际耗时和没有 SIGKILL,不能仅以“等待了 45 秒”证明优雅退出。
|
||||
|
||||
控制面启动时会连接 PostgreSQL,并在事务和 advisory lock 保护下自动执行前向迁移。迁移失败时控制面会退出,由 Compose 按 `restart: unless-stopped` 重启;先检查日志,不要删除数据卷。
|
||||
|
||||
### 首次配置
|
||||
|
||||
服务起来后打开 <http://127.0.0.1:${CREATORHUB_PORT}>:
|
||||
|
||||
1. 「网关管理」页注册网关:名称如 `gw-main`,Endpoint `http://docker-gateway:8081`,令牌填 `GATEWAY_TOKEN` 的值(即 `openssl rand -hex 24` 生成的值)。
|
||||
2. 按 [`docker/browser-wrapper/README.md`](../docker/browser-wrapper/README.md) 用已登记 base digest 构建包装镜像,再在「镜像版本」页添加发布 digest。生产与验收必须填写 `git.ipao.vip/rogee/creatorhub-browser-wrapper@sha256:<已登记摘要>`;`148.0.7778.215` 只作为浏览器版本元数据,不能单独作为不可变引用。登记内容同时包含 base 镜像源仓库、base digest、构建提交、Dockerfile 路径和发布摘要。
|
||||
3. 「社媒账号」页先创建账号(默认暂停),再在「运行环境」选该账号、网关、镜像,填写中文名、小写别名及指纹参数。代理可选;指定代理须先手动检测为健康,留空是明确直连,不是失败回退。
|
||||
4. 创建得到停止态容器;在账号页恢复账号后回环境页显式启动。容器名 `creatorhub-browser-<别名>`,Profile 卷 `creatorhub-profile-<别名>`。回收只删除容器、保留环境/binding/Profile;完整契约见[架构说明](architecture/container-control.md)。当前直连 create/start 不代表 upgrade/rebind 已支持空出口。
|
||||
|
||||
### 人工登录二维码
|
||||
|
||||
抖音账号页的「显示登录二维码」由 CreatorHub 通过已绑定 gateway 请求登录画面,并直接展示在后台;它不会注入密码、Cookie 或自动完成登录。登录画面只在内存中返回,前端显示两分钟有效期;完成扫码或验证码后,点击「核验浏览器身份」确认 UID 与账号绑定一致。gateway 无法取得二维码时,后台展示实际登录/验证码画面并要求人工处理,不把不确定结果标记为成功。
|
||||
|
||||
对应接口为 `POST /api/creator/accounts/<account_id>/login-qr`,仅接受已授权、已运行的抖音账号;响应中的 `image_base64` 只用于当前页面展示,不应写入日志、数据库或备份。
|
||||
|
||||
## 部署验证
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error \
|
||||
--retry 30 --retry-delay 2 --retry-connrefused \
|
||||
--output /dev/null "http://127.0.0.1:${CREATORHUB_PORT}/healthz"
|
||||
|
||||
curl --fail --silent --show-error \
|
||||
--retry 30 --retry-delay 2 --retry-connrefused \
|
||||
--output /dev/null "http://127.0.0.1:${CREATORHUB_PORT}/readyz"
|
||||
|
||||
curl --fail --silent --show-error \
|
||||
--retry 30 --retry-delay 2 --retry-connrefused \
|
||||
--user "${CONTROL_PLANE_USERNAME}:${CONTROL_PLANE_PASSWORD}" \
|
||||
"http://127.0.0.1:${CREATORHUB_PORT}/api/browsers" >/dev/null
|
||||
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 32;' \
|
||||
| grep -qx 1
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 35;' \
|
||||
| grep -qx 1
|
||||
|
||||
curl --fail --silent --show-error "http://127.0.0.1:${CREATORHUB_PORT}/healthz"
|
||||
curl --fail --silent --show-error "http://127.0.0.1:${CREATORHUB_PORT}/readyz"
|
||||
docker compose ps
|
||||
docker compose logs --tail=200 creator-hub postgres
|
||||
```
|
||||
|
||||
健康检查应成功,浏览器列表接口应返回 JSON,迁移查询当前应输出 `1`,三个 Compose 服务应为运行状态。`CREATORHUB_CREDENTIAL_MASTER_KEY` 必须由部署侧 Secret Manager/OS Keyring 持久保存并在每次启动时注入同一值;账号凭据以 AES-GCM 密文写入独立 `creatorhub_credentials` 卷,轮换主密钥前必须先迁移已有凭据。然后访问 <http://127.0.0.1:8080>;修改过 `CREATORHUB_PORT` 时使用对应端口。
|
||||
Compose 中的 control-plane 访问宿主机 gateway 时,网关 Endpoint 登记为 `http://host.docker.internal:8081`。裸机运行 control-plane 时登记为 `http://127.0.0.1:8081`。两种情况下都必须使用同一个 `GATEWAY_TOKEN`。
|
||||
|
||||
旧实验版本的数据库异常应先记录版本、备份并核实,不把历史文档中的建议迁移当成本期开发要求;本期不新增兼容迁移、回填或双写。下文更新/恢复命令仅描述现有部署的数据操作,不改变 plan01 验收范围。
|
||||
|
||||
排障时读取结构化服务日志:
|
||||
### 裸机开发
|
||||
|
||||
```bash
|
||||
docker compose logs --tail=200 creator-hub docker-gateway postgres
|
||||
npm --prefix web ci
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 手工业务验证(阶段 A Mock)
|
||||
`pnpm dev:backend` 只启动 PostgreSQL 依赖,并在启动 control-plane 前检查宿主机 gateway;它不会创建 browser runtime 或替代 gateway。
|
||||
|
||||
本节的旧阶段 A 业务执行器是 Mock,不向真实社交平台发送;容器、数据库、网关和可选代理则是实际运行资源。最小业务路径是:账号 → 草稿 → 显式确认 → 任务入队 → Mock 执行 → 审计回溯。
|
||||
默认联调地址:
|
||||
|
||||
页面路径如下:
|
||||
| 服务 | 地址 |
|
||||
| --- | --- |
|
||||
| Vite | `http://127.0.0.1:5173` |
|
||||
| control-plane | `http://0.0.0.0:8082` |
|
||||
| native gateway | `http://0.0.0.0:8081` |
|
||||
| PostgreSQL | `127.0.0.1:5432` |
|
||||
|
||||
1. 登录后在「网关管理」注册 <http://docker-gateway:8081>,令牌必须等于 GATEWAY_TOKEN。
|
||||
2. 在「镜像版本」添加并启用一个可拉取的 fingerprint-chromium 镜像。
|
||||
3. 可选:在「网络出口」创建出口并点击「检测」,健康状态必须为「健康」;这里只填写凭据引用 ID,不填写密码、Cookie 或 token。留空则使用网关所在机器的网络出口直连。
|
||||
4. 在「社媒账号」创建平台为抖音的账号;创建后默认暂停,后续仍可使用 Mock 执行器验证离线闭环。
|
||||
5. 在「运行环境」选择该账号、镜像和可选的健康出口,使用正整数 Fingerprint Seed 创建环境;随后在账号详情点击「恢复账号」,再在「运行环境」点击「启动」。
|
||||
6. 在账号详情创建文本草稿,点击「核对草稿」,勾选“我已核对当前账号、草稿内容、运行环境和固定出口”,依次执行「确认当前快照」→「保存确认」→「加入队列」。
|
||||
7. Mock 执行器没有独立页面,使用下面的 POST /api/phase-a/mock/execute,再到「任务中心」和「审计」核对结果。
|
||||
## 首次配置与生命周期
|
||||
|
||||
以下命令可在已经启动的仓库根目录直接执行,并且必须在启动 Compose 的同一个 shell 中执行。开始前确认 `CREATORHUB_PORT`、`GATEWAY_TOKEN`、`CONTROL_PLANE_USERNAME` 和 `CONTROL_PLANE_PASSWORD` 都已 export;脚本不会读取未 export 的 `.env` 值或 Compose 默认值。出口参数必须替换成已批准且可从 creator-hub 容器访问的无认证代理;若代理需要认证,先按部署规范准备 credential_reference,不要把秘密值写进命令或仓库。镜像引用也可改成已发布的 registry 引用;本地 smoke test 可以使用宿主机上已有的镜像标签。
|
||||
1. 在「网关管理」登记 native gateway;读取 `/v1/info` 核对稳定 `node_id` 和浏览器版本能力。
|
||||
2. 在「浏览器版本」登记 gateway 上存在的版本和绝对路径;版本缺失时创建动作必须失败,不得自动拉取或改用其它版本。
|
||||
3. 创建账号和环境,选择账号、gateway、浏览器版本及可选网络出口。
|
||||
4. 显式启动环境;gateway 返回 runtime ID、network ID、node ID、generation、display、CDP/代理端口和 readiness。
|
||||
5. 登录二维码只在内存响应中短暂展示;完成人工登录后执行账号身份核验。
|
||||
6. 采集和人工写操作必须通过账号身份、binding version、runtime generation 和 runtime use lease 检查。
|
||||
7. 停止或失败时,业务结果和 cleanup 状态分别记录。cleanup 失败显示 `pending` 并可重试;不得把 pending 伪装为已停止。
|
||||
8. 重新启动使用相同 Profile 根目录,核对浏览器版本和账号身份,不删除登录资料。
|
||||
|
||||
~~~bash
|
||||
set -Eeuo pipefail
|
||||
gateway 的 runtime 清单、Profile 和日志目录不得由 `docker compose down` 删除。只有明确授权时才执行账号 Profile 或正式素材删除。
|
||||
|
||||
: "${CREATORHUB_PORT:?export CREATORHUB_PORT (same value used by Compose)}"
|
||||
: "${CONTROL_PLANE_USERNAME:?set CONTROL_PLANE_USERNAME}"
|
||||
: "${CONTROL_PLANE_PASSWORD:?set CONTROL_PLANE_PASSWORD}"
|
||||
: "${GATEWAY_TOKEN:?export GATEWAY_TOKEN (same value registered in the gateway)}"
|
||||
: "${PROXY_HOST:?set PROXY_HOST to an approved fixed proxy host}"
|
||||
: "${PROXY_PORT:?set PROXY_PORT to an approved fixed proxy port}"
|
||||
BASE_URL="http://127.0.0.1:${CREATORHUB_PORT}"
|
||||
## 验证与故障处理
|
||||
|
||||
api() {
|
||||
curl --fail-with-body --silent --show-error \
|
||||
--user "${CONTROL_PLANE_USERNAME}:${CONTROL_PLANE_PASSWORD}" \
|
||||
-H 'Content-Type: application/json' "$@"
|
||||
}
|
||||
|
||||
[[ "$PROXY_PORT" =~ ^[0-9]+$ ]] && (( PROXY_PORT >= 1 && PROXY_PORT <= 65535 ))
|
||||
|
||||
run_id="manual-$(date +%s)"
|
||||
gateway_name="gw-${run_id}"
|
||||
image_version="${IMAGE_VERSION:-148.0.7778.215}"
|
||||
: "${IMAGE_REF:?IMAGE_REF must be an immutable browser-wrapper @sha256 reference}"
|
||||
case "$IMAGE_REF" in
|
||||
*@sha256:*) image_ref="$IMAGE_REF" ;;
|
||||
*) echo "IMAGE_REF must contain @sha256:" >&2; exit 2 ;;
|
||||
esac
|
||||
exit_protocol="${PROXY_PROTOCOL:-http}"
|
||||
account_key="${run_id}-platform"
|
||||
env_alias="${run_id}-env"
|
||||
|
||||
gateway_json="$(api -X POST "$BASE_URL/api/gateways" --data "$(jq -n \
|
||||
--arg name "$gateway_name" --arg token "$GATEWAY_TOKEN" \
|
||||
'{name:$name,endpoint:"http://docker-gateway:8081",token:$token}')")"
|
||||
jq -e --arg name "$gateway_name" '.name == $name' <<<"$gateway_json" >/dev/null
|
||||
|
||||
image_json="$(api -X POST "$BASE_URL/api/browser-images" --data "$(jq -n \
|
||||
--arg version "$image_version" --arg ref "$image_ref" \
|
||||
'{version:$version,image_ref:$ref,note:"manual validation",enabled:true}')")"
|
||||
jq -e --arg version "$image_version" '.version == $version and .enabled == true' <<<"$image_json" >/dev/null
|
||||
|
||||
exit_json="$(api -X POST "$BASE_URL/api/network-exits" --data "$(jq -n \
|
||||
--arg protocol "$exit_protocol" --arg host "$PROXY_HOST" --argjson port "$PROXY_PORT" \
|
||||
'{protocol:$protocol,host:$host,port:$port}')")"
|
||||
exit_id="$(jq -er '.id' <<<"$exit_json")"
|
||||
checked_exit="$(api -X POST "$BASE_URL/api/network-exits/$exit_id/check")"
|
||||
jq -e '.health_status == "healthy"' <<<"$checked_exit" >/dev/null
|
||||
|
||||
account_json="$(api -X POST "$BASE_URL/api/phase-a/accounts" --data "$(jq -n \
|
||||
--arg name "手工验证账号" --arg key "$account_key" --arg cookies "${ACCOUNT_COOKIES:-sessionid=manual-test}" \
|
||||
'{name:$name,platform:"douyin",platform_account_key:$key,tags:["manual"],cookies:$cookies}')")"
|
||||
account_id="$(jq -er '.id' <<<"$account_json")"
|
||||
jq -e '.authorization_status == "authorized" and .runtime_status == "paused"' <<<"$account_json" >/dev/null
|
||||
|
||||
created_env="$(api -X POST "$BASE_URL/api/browsers" --data "$(jq -n \
|
||||
--arg alias "$env_alias" --arg name "手工验证环境" --arg gateway "$gateway_name" \
|
||||
--arg version "$image_version" --arg account "$account_id" --arg exit "$exit_id" \
|
||||
'{alias:$alias,name:$name,gateway:$gateway,image_version:$version,account_id:$account,network_exit_id:$exit,fingerprint:{seed:1000}}')")"
|
||||
jq -e --arg alias "$env_alias" '.alias == $alias' <<<"$created_env" >/dev/null
|
||||
|
||||
api -X POST "$BASE_URL/api/phase-a/accounts/$account_id/resume" >/dev/null
|
||||
api -X POST "$BASE_URL/api/browsers/$env_alias/start" >/dev/null
|
||||
|
||||
for _ in $(seq 1 30); do
|
||||
runtime_json="$(api "$BASE_URL/api/browsers/$env_alias")"
|
||||
if jq -e '.runtime_id != "" and .runtime_instance_id != ""' <<<"$runtime_json" >/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
jq -e '.runtime_id != "" and .runtime_instance_id != ""' <<<"$runtime_json" >/dev/null
|
||||
|
||||
account_json="$(api "$BASE_URL/api/phase-a/accounts/$account_id")"
|
||||
account_version="$(jq -er '.version' <<<"$account_json")"
|
||||
draft_json="$(api -X POST "$BASE_URL/api/phase-a/drafts" --data "$(jq -n \
|
||||
--arg account "$account_id" '{account_id:$account,content:"阶段 A 手工验证内容"}')")"
|
||||
draft_id="$(jq -er '.id' <<<"$draft_json")"
|
||||
draft_version="$(jq -er '.version' <<<"$draft_json")"
|
||||
|
||||
confirmation_json="$(api -X POST "$BASE_URL/api/phase-a/confirmations" --data "$(jq -n \
|
||||
--arg draft "$draft_id" --argjson account_version "$account_version" --argjson draft_version "$draft_version" \
|
||||
'{draft_id:$draft,account_version:$account_version,draft_version:$draft_version}')")"
|
||||
confirmation_id="$(jq -er '.id' <<<"$confirmation_json")"
|
||||
|
||||
task_json="$(api -X POST "$BASE_URL/api/phase-a/tasks" --data "$(jq -n --arg confirmation "$confirmation_id" '{confirmation_id:$confirmation}')")"
|
||||
task_id="$(jq -er '.id' <<<"$task_json")"
|
||||
jq -e '.state == "queued"' <<<"$task_json" >/dev/null
|
||||
duplicate_task_json="$(api -X POST "$BASE_URL/api/phase-a/tasks" --data "$(jq -n --arg confirmation "$confirmation_id" '{confirmation_id:$confirmation}')")"
|
||||
jq -e --arg task_id "$task_id" '.id == $task_id' <<<"$duplicate_task_json" >/dev/null
|
||||
|
||||
execution_json="$(api -X POST "$BASE_URL/api/phase-a/mock/execute" --data \
|
||||
'{"worker_id":"manual-worker","outcome":"succeeded"}')"
|
||||
jq -e '.was_claimed == true and .state == "succeeded"' <<<"$execution_json" >/dev/null
|
||||
|
||||
task_detail="$(api "$BASE_URL/api/phase-a/tasks/$task_id")"
|
||||
jq -e '(.state == "succeeded") and ((.attempts | length) >= 1) and (.attempts[0].evidence.mock_outcome == "succeeded")' <<<"$task_detail" >/dev/null
|
||||
|
||||
audit_json="$(api "$BASE_URL/api/phase-a/audit?task_id=$task_id&page_size=100")"
|
||||
for event_type in task_queued task_claimed task_finished; do
|
||||
jq -e --arg event_type "$event_type" '.data | any(.[]; .event_type == $event_type)' <<<"$audit_json" >/dev/null
|
||||
done
|
||||
|
||||
printf 'PASS account=%s environment=%s task=%s state=succeeded audit=task_queued,task_claimed,task_finished\n' \
|
||||
"$account_id" "$env_alias" "$task_id"
|
||||
~~~
|
||||
|
||||
通过标准:/healthz 返回 204;受保护的 /api/browsers 返回 JSON;网关、控制面、PostgreSQL 均为运行状态;运行环境有 runtime_id 和 runtime_instance_id;任务最终为 succeeded,且执行尝试的脱敏证据为 mock_outcome=succeeded;审计至少包含 task_queued、task_claimed、task_finished。重复提交同一确认时应返回同一任务 ID,不应产生第二条任务。
|
||||
|
||||
验证完成后的容器回收(保留账号、环境/binding、PostgreSQL 数据与 Profile 卷,不是永久删除环境):
|
||||
|
||||
~~~bash
|
||||
api() {
|
||||
curl --fail-with-body --silent --show-error \
|
||||
--user "${CONTROL_PLANE_USERNAME}:${CONTROL_PLANE_PASSWORD}" \
|
||||
-H 'Content-Type: application/json' "$@"
|
||||
}
|
||||
api -X POST "$BASE_URL/api/browsers/$env_alias/stop" >/dev/null
|
||||
api -X DELETE "$BASE_URL/api/browsers/$env_alias" >/dev/null
|
||||
docker compose stop
|
||||
~~~
|
||||
|
||||
## 常见故障排查
|
||||
|
||||
| 现象 | 先检查 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| docker compose config 报 required | CONTROL_PLANE_USERNAME、CONTROL_PLANE_PASSWORD、CREATORHUB_CREDENTIAL_MASTER_KEY 是否在当前 shell 非空 | 重新 export 三个变量;用户名不能含冒号,密码至少 6 字节,主密钥必须为 32 字节的 base64 |
|
||||
| 手工验证脚本在 `:?` 处退出 | CREATORHUB_PORT、GATEWAY_TOKEN、CONTROL_PLANE_USERNAME、CONTROL_PLANE_PASSWORD、CREATORHUB_CREDENTIAL_MASTER_KEY 是否都已 export | 在启动 Compose 的同一个 shell 中 export 完整变量集;不要只依赖 `.env` 或 Compose 默认值 |
|
||||
| creator-hub 未启动 | docker compose ps、docker compose logs --tail=200 postgres docker-gateway creator-hub | 先确认 PostgreSQL 与网关 health 为 healthy;网关需能访问 /var/run/docker.sock,DOCKER_GID 使用 stat -c '%g' /var/run/docker.sock 的实际值 |
|
||||
| API 返回 401 | curl 是否带 --user CONTROL_PLANE_USERNAME:CONTROL_PLANE_PASSWORD | /healthz 不需要认证,其余 /api/* 需要控制面 Basic Auth |
|
||||
| 生命周期动作报网关不可用 | 网关注册的 Endpoint、令牌与 Compose 的 GATEWAY_TOKEN | Endpoint 应为 <http://docker-gateway:8081>;令牌必须完全一致。列表/详情只读持久记录,其成功不能证明网关在线 |
|
||||
| 出口一直是 unchecked/unhealthy | 出口协议、主机、端口;控制面容器到代理的连通性;last_check_reason | 先用无认证代理完成最小验证;有认证时只提供已配置的凭据引用,不把认证值放到请求、日志或文档 |
|
||||
| 创建环境时报 image_unavailable 或拉取超时 | image_ref 格式、镜像架构、Docker daemon 的 registry 登录和网络 | 版本表中的镜像必须可被 Docker daemon 拉取,最长约 10 分钟;已保存环境/binding 不会因网关失败自动删掉,核对记录后按原配置恢复 |
|
||||
| 恢复/入队返回 503 | readiness、GET /api/browsers/<alias>、GET /api/network-exits/<id> | binding_missing、network_exit_unhealthy、runtime_missing 表示固定资源未就绪;先修复出口并启动原环境,不要换出口重试 |
|
||||
| Mock 执行没有领取任务 | GET /api/phase-a/tasks/<id> 的 state、hold_reason | POST /api/phase-a/mock/execute 只领取满足账号、确认、已绑定出口健康(显式直连无此项)、活动 runtime 和租约条件的 queued 任务;policy_hold/needs_confirmation 不会自动重试 |
|
||||
| 停止 Compose 后浏览器仍在运行 | docker ps --filter 'name=^creatorhub-browser-' | 动态浏览器不由 Compose 管理;先通过「运行环境」停止/回收,再 docker compose stop,不要误删 Profile 卷 |
|
||||
|
||||
不要执行 docker compose down --volumes 作为普通排障手段;它会删除 PostgreSQL 数据卷。不要把控制面密码、网关令牌、代理密码、Cookie 或 token 写入仓库、截图、日志或审计查询。
|
||||
|
||||
## 配置
|
||||
|
||||
Compose 部署时通常只需设置以下宿主机变量:
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `CREATORHUB_PORT` | `8080` | 控制面宿主机端口,局域网可访问 |
|
||||
| `DOCKER_GID` | 无(必填) | Docker socket 的宿主机组 ID;必须按实际值设置 |
|
||||
| `GATEWAY_TOKEN` | `dev-creatorhub-gateway-token` | Compose 未提供变量时的默认值;本说明要求显式 export 随机值,并同步填入网关注册表单 |
|
||||
| `CONTROL_PLANE_USERNAME` | 无(必填) | 控制面唯一用户;不能包含冒号 |
|
||||
| `CONTROL_PLANE_PASSWORD` | 无(必填) | 控制面密码,至少 6 字节;使用随机值 |
|
||||
| `CREATORHUB_CREDENTIAL_MASTER_KEY` | 无(必填) | 32 字节 base64;由部署 Secret Manager/OS Keyring 持久注入,重启后必须保持一致 |
|
||||
| `BAILIAN_API_KEY` | 空(按需) | 已批准的生产 AI API key;为空时文本 AI 动作明确返回 unavailable,不使用 Mock |
|
||||
| `BAILIAN_BASE_URL` | 客户端默认值 | 无用户信息的 HTTP(S) 地址;供应商变更前需完成审批和脱敏样本验证 |
|
||||
| `CREATOR_MEDIA_DIR` | `/var/lib/creatorhub/materials` | 持久媒体目录;必须挂载持久卷,不能使用临时目录替代 |
|
||||
| `CREATOR_TRANSCRIPTION_BIN` | 空(按需) | 可执行的本地转写入口;为空时转写明确失败,不伪造 succeeded |
|
||||
|
||||
服务本身支持并校验以下环境变量;`compose.yaml` 会在控制面凭据缺失或为空时拒绝渲染。下表中的 Compose 默认值不由手工验证脚本隐式读取;手工验证沿用上文的显式 export 要求:
|
||||
|
||||
| 服务 | 变量 | 当前 Compose 值 |
|
||||
| --- | --- | --- |
|
||||
| `creator-hub` | `LISTEN_ADDR` | 默认 `:8080` |
|
||||
| `creator-hub` | `WEB_DIR` | 镜像内固定为 `/app/web` |
|
||||
| `creator-hub` | `DATABASE_URL` | `postgres://creatorhub@postgres/creatorhub?sslmode=disable` |
|
||||
| `creator-hub` | `LOG_LEVEL` | 默认 `info` |
|
||||
| `creator-hub` | `CONTROL_PLANE_USERNAME` | 必填;HTTP Basic Auth 用户名 |
|
||||
| `creator-hub` | `CONTROL_PLANE_PASSWORD` | 必填且至少 6 字节;不会写入日志或响应 |
|
||||
| `creator-hub` | `CREATORHUB_CREDENTIAL_MASTER_KEY` | 必填;解密独立凭据卷,不写入数据库、日志或响应 |
|
||||
| `creator-hub` | `CREATORHUB_CREDENTIAL_STORE_DIR` | 默认 `/var/lib/creatorhub/credentials`;必须为绝对路径且持久可写 |
|
||||
| `creator-hub` | `BAILIAN_API_KEY` | 可选;仅用于已批准的文本 AI,缺失时不回退 Mock |
|
||||
| `creator-hub` | `BAILIAN_BASE_URL` | 可选;HTTP(S) 供应商地址,禁止携带用户信息 |
|
||||
| `docker-gateway` | `LISTEN_ADDR` | 默认 `:8081` |
|
||||
| `docker-gateway` | `DOCKER_SOCKET` | 默认值和 Compose 挂载均固定为 `/var/run/docker.sock`;不能只覆盖环境变量 |
|
||||
| `docker-gateway` | `BROWSER_NETWORK` | `creatorhub_browser` |
|
||||
| `docker-gateway` | `GATEWAY_TOKEN` | 与平台注册值一致,长度 ≥16;`/v1` 全部接口校验 Bearer 令牌 |
|
||||
| `docker-gateway` | `BROWSER_CDP_URL` | 空;仅本地联调时连接外部 CDP,例如 `http://host.docker.internal:9222` |
|
||||
| `docker-gateway` | `BROWSER_CDP_TARGET_ID` | 空;外部 CDP 多页面时必须指定抖音页面 ID |
|
||||
| `docker-gateway` | `LOG_LEVEL` | 默认 `info` |
|
||||
|
||||
不要把凭据写入仓库或 Compose 文件。
|
||||
|
||||
### 本地 9222 CDP 联调
|
||||
|
||||
网关支持显式连接已经登录的本地 CDP,不创建或回收 Docker 浏览器。仅用于开发和真实平台链路测试,不应在生产 Compose 中设置。先从 `http://127.0.0.1:9222/json/list` 选择唯一的抖音页面 `id`,再在宿主机启动网关:
|
||||
代码检查:
|
||||
|
||||
```bash
|
||||
export LISTEN_ADDR=127.0.0.1:18081
|
||||
export GATEWAY_TOKEN=0123456789abcdef
|
||||
export BROWSER_CDP_URL=http://127.0.0.1:9222
|
||||
export BROWSER_CDP_TARGET_ID=<已登录抖音页面的 target id>
|
||||
export BROWSER_CDP_ALIAS=local-cdp
|
||||
export BROWSER_CDP_NETWORK_ID=local-cdp
|
||||
python3 -m cmd.docker_gateway.gateway
|
||||
```
|
||||
|
||||
该模式的 generation 固定为 `binding_version=1`、`runtime_id=64 个 0`、`network_id=local-cdp`。生命周期接口明确不可用于此浏览器;身份、受限读取、事件订阅和动作接口仍要求完整 generation,并继续执行 UID 核对。`BROWSER_CDP_TARGET_ID` 必须固定到抖音页面,不能把包含多个网站的 CDP 页面列表交给自动猜测。
|
||||
|
||||
### P0-lite 停机通知
|
||||
|
||||
当前只使用 `creator-hub` 的专用 Logrus JSON logger 作为通知渠道;它固定输出警告,不继承业务 `LOG_LEVEL`。选择它是因为 Compose 已可靠收集服务日志,不需要新增外部账号、凭据、网络重试或通知依赖。仅 `policy_hold` 和 `needs_confirmation` 会产生 `operator attention required`,字段限定为 `event_type`、`reason_code`、账号/任务 ID;不会包含请求头、Secret 引用或凭据值。运维可用下列命令接入现有日志采集或人工查看:
|
||||
|
||||
```bash
|
||||
docker compose logs creator-hub | grep 'operator attention required'
|
||||
```
|
||||
|
||||
该渠道是单实例 P0-lite 能力,不保证外部送达、升级或确认回执;只有出现明确的多渠道/送达需求时才增加 webhook 或消息平台。
|
||||
|
||||
## 更新与回滚
|
||||
|
||||
更新前记录当前版本并备份数据库:
|
||||
|
||||
```bash
|
||||
set -Eeuo pipefail
|
||||
git rev-parse HEAD
|
||||
umask 077
|
||||
export BACKUP_FILE="$(pwd)/creatorhub-$(date +%Y%m%d-%H%M%S).dump"
|
||||
docker compose exec -T postgres \
|
||||
pg_dump -U creatorhub -d creatorhub --format=custom \
|
||||
> "$BACKUP_FILE"
|
||||
test -s "$BACKUP_FILE"
|
||||
docker compose exec -T postgres pg_restore --list \
|
||||
< "$BACKUP_FILE" >/dev/null
|
||||
```
|
||||
|
||||
PostgreSQL dump 之外,发布备份必须同时包含 credentials、materials 和动态 Profile 卷;这些归档存放在部署侧受限目录,不放进仓库或共享聊天。示例(`BACKUP_DIR` 使用独立磁盘或 Secret Manager 的受控挂载目录):
|
||||
|
||||
```bash
|
||||
set -Eeuo pipefail
|
||||
umask 077
|
||||
BACKUP_DIR=/srv/creatorhub-backups/$(date +%Y%m%d-%H%M%S)
|
||||
mkdir -p "$BACKUP_DIR/profiles"
|
||||
# PostgreSQL dump
|
||||
pg_dump_file="$BACKUP_DIR/creatorhub.dump"
|
||||
docker compose exec -T postgres pg_dump -U creatorhub -d creatorhub --format=custom > "$pg_dump_file"
|
||||
test -s "$pg_dump_file"
|
||||
# Credential and material volumes
|
||||
for volume in creatorhub_credentials creatorhub_materials; do
|
||||
docker run --rm -v "$volume:/data:ro" -v "$BACKUP_DIR:/backup" alpine:3.22 \
|
||||
tar czf "/backup/${volume}.tar.gz" -C /data .
|
||||
done
|
||||
# Dynamic browser Profile volumes; absence is an explicit empty set.
|
||||
for volume in $(docker volume ls -q --filter name='^creatorhub-profile-'); do
|
||||
docker run --rm -v "$volume:/data:ro" -v "$BACKUP_DIR/profiles:/backup" alpine:3.22 \
|
||||
tar czf "/backup/${volume}.tar.gz" -C /data .
|
||||
done
|
||||
find "$BACKUP_DIR" -type f -exec sha256sum {} + > "$BACKUP_DIR/SHA256SUMS"
|
||||
```
|
||||
|
||||
`CREATORHUB_CREDENTIAL_MASTER_KEY` 不写入上述归档;部署管理员必须在独立的 Secret Manager/OS Keyring 保留一份受访问控制的密钥托管记录,并确认恢复主机能注入同一值。恢复前核对 `SHA256SUMS`、主密钥记录和备份目录权限;恢复后在隔离 Compose 项目中还原四类卷,检查账号凭据可解密、材料文件可读、Profile 可挂载,再切换服务。任何一类缺失都判定为恢复失败,不把应用健康检查当作数据恢复证据。
|
||||
|
||||
拉取已审核版本后,重新执行部署和验证:
|
||||
|
||||
```bash
|
||||
set -Eeuo pipefail
|
||||
git pull --ff-only
|
||||
export DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)"
|
||||
export CREATORHUB_PORT=8080
|
||||
python3 -m unittest discover -s cmd/browser_gateway -t cmd -p 'test_*.py' -q
|
||||
python3 -m coverage run --source=cmd/browser_gateway --branch -m unittest discover -s cmd/browser_gateway -t cmd -p 'test_*.py' -q
|
||||
python3 -m coverage report --omit='cmd/browser_gateway/test_*.py' --fail-under=65
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go build ./cmd/control-plane
|
||||
go test -race ./...
|
||||
npm --prefix web ci
|
||||
npm --prefix web test -- --run
|
||||
npm --prefix web run build
|
||||
docker compose config --quiet
|
||||
docker compose up --detach --build
|
||||
```
|
||||
|
||||
镜像版本升级在页面「运行环境 → 升级」完成:删除旧容器并保留 Profile,用新镜像与原指纹/binding 重建,账号可运行才启动,否则保持停止态;无自动回滚。失败先查看记录,人工重试由现有流程调和,不承诺所有失败都可直接重试消除。当前 upgrade 无条件要求有效代理出口,直连环境会失败;rebind 也不支持空出口切回直连。此为现状限制,不是新产品范围裁决。
|
||||
资源和失败场景必须由人工按 [native-browser-verification.md](native-browser-verification.md) 留证:Profile 占用、版本缺失、代理认证/网络失败、Xvfb 或浏览器启动失败、取消、超时、gateway 重启、重复清理、磁盘不足、旧 generation、监听与采集并行,以及真实抖音登录和采集。单元测试、Fake systemd 和 Mock gateway 不能替代真实平台证据。
|
||||
|
||||
数据库迁移只支持安全前进,不提供自动破坏性回滚。需要同时恢复旧代码和更新前数据库时,修改下面两个变量后**整块执行一次**;不要逐行或拆块执行。预检、恢复演练、动态容器停止、停服、主库恢复、提交切换和启动都位于同一个 fail-fast subshell 中。
|
||||
常用诊断:
|
||||
|
||||
```bash
|
||||
(
|
||||
set -Eeuo pipefail
|
||||
|
||||
BACKUP_FILE=/absolute/path/to/creatorhub-YYYYmmdd-HHMMSS.dump
|
||||
RESTORE_REV=PREVIOUS_REVIEWED_COMMIT_SHA
|
||||
: "${BACKUP_FILE:?set BACKUP_FILE to the absolute archive path}"
|
||||
: "${RESTORE_REV:?set RESTORE_REV to the previous reviewed commit SHA}"
|
||||
test -r "$BACKUP_FILE"
|
||||
test -s "$BACKUP_FILE"
|
||||
git cat-file -e "${RESTORE_REV}^{commit}"
|
||||
docker compose exec -T postgres pg_restore --list \
|
||||
< "$BACKUP_FILE" >/dev/null
|
||||
|
||||
docker compose exec -T postgres \
|
||||
dropdb --if-exists --force -U creatorhub creatorhub_restore_check
|
||||
docker compose exec -T postgres \
|
||||
createdb -U creatorhub creatorhub_restore_check
|
||||
docker compose exec -T postgres \
|
||||
pg_restore -U creatorhub -d creatorhub_restore_check \
|
||||
--exit-on-error --no-owner --no-privileges \
|
||||
< "$BACKUP_FILE"
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub_restore_check -v ON_ERROR_STOP=1 -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 32;' \
|
||||
| grep -qx 1
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub_restore_check -v ON_ERROR_STOP=1 -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 35;' \
|
||||
| grep -qx 1
|
||||
docker compose exec -T postgres \
|
||||
dropdb --force -U creatorhub creatorhub_restore_check
|
||||
|
||||
list_running_browsers() {
|
||||
docker ps --quiet \
|
||||
--filter 'name=^creatorhub-browser-' \
|
||||
--filter 'label=io.creatorhub.managed=true' \
|
||||
--filter 'label=io.creatorhub.runtime-id'
|
||||
}
|
||||
|
||||
browser_ids="$(list_running_browsers)" || exit 1
|
||||
for browser_id in $browser_ids; do
|
||||
if ! docker stop "$browser_id"; then
|
||||
docker rm --force "$browser_id"
|
||||
fi
|
||||
done
|
||||
|
||||
browser_ids="$(list_running_browsers)" || exit 1
|
||||
test -z "$browser_ids"
|
||||
docker compose stop creator-hub docker-gateway
|
||||
|
||||
umask 077
|
||||
PRE_ROLLBACK_BACKUP="$(pwd)/creatorhub-pre-rollback-$(date +%Y%m%d-%H%M%S).dump"
|
||||
docker compose exec -T postgres \
|
||||
pg_dump -U creatorhub -d creatorhub --format=custom \
|
||||
> "$PRE_ROLLBACK_BACKUP"
|
||||
test -s "$PRE_ROLLBACK_BACKUP"
|
||||
docker compose exec -T postgres pg_restore --list \
|
||||
< "$PRE_ROLLBACK_BACKUP" >/dev/null
|
||||
|
||||
test -r "$BACKUP_FILE"
|
||||
test -s "$BACKUP_FILE"
|
||||
git cat-file -e "${RESTORE_REV}^{commit}"
|
||||
docker compose exec -T postgres pg_restore --list \
|
||||
< "$BACKUP_FILE" >/dev/null
|
||||
|
||||
docker compose exec -T postgres \
|
||||
dropdb --if-exists --force -U creatorhub creatorhub
|
||||
docker compose exec -T postgres createdb -U creatorhub creatorhub
|
||||
docker compose exec -T postgres \
|
||||
pg_restore -U creatorhub -d creatorhub \
|
||||
--exit-on-error --no-owner --no-privileges \
|
||||
< "$BACKUP_FILE"
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub -v ON_ERROR_STOP=1 -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 32;' \
|
||||
| grep -qx 1
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub -v ON_ERROR_STOP=1 -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 35;' \
|
||||
| grep -qx 1
|
||||
|
||||
git switch --detach "$RESTORE_REV"
|
||||
if ! docker compose up --detach --build; then
|
||||
docker compose stop creator-hub docker-gateway || true
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
systemctl --user --no-pager --full status creatorhub-browser-gateway.service
|
||||
journalctl --user -u creatorhub-browser-gateway.service --since=-30m
|
||||
find ~/.local/state/creatorhub/browser-gateway -maxdepth 3 -type f -name runtime.json -print
|
||||
curl --fail --silent --show-error http://127.0.0.1:8081/v1/browsers \
|
||||
-H "Authorization: Bearer ${GATEWAY_TOKEN}"
|
||||
```
|
||||
|
||||
任何命令失败时 subshell 立即退出;若失败发生在主库 `dropdb` 之后,应用保持停止。`docker compose up` 自身失败时也会显式停回应用服务。成功后重新执行“部署验证”。不要直接删除数据卷或手工改写迁移记录。
|
||||
|
||||
## 停止与数据保留
|
||||
|
||||
`docker compose stop/down` 不管理网关动态创建的浏览器容器;直接执行会留下运行中的浏览器和活动会话。先严格按固定容器名前缀及两个管理标签筛选并停止;只有单个容器停止失败或超时时才强制删除。命令不带 `--volumes`,因此 Profile 卷仍会保留:
|
||||
|
||||
```bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
list_running_browsers() {
|
||||
docker ps --quiet \
|
||||
--filter 'name=^creatorhub-browser-' \
|
||||
--filter 'label=io.creatorhub.managed=true' \
|
||||
--filter 'label=io.creatorhub.runtime-id'
|
||||
}
|
||||
|
||||
browser_ids="$(list_running_browsers)" || exit 1
|
||||
for browser_id in $browser_ids; do
|
||||
if ! docker stop "$browser_id"; then
|
||||
docker rm --force "$browser_id"
|
||||
fi
|
||||
done
|
||||
|
||||
browser_ids="$(list_running_browsers)" || exit 1
|
||||
test -z "$browser_ids"
|
||||
|
||||
docker compose stop
|
||||
```
|
||||
|
||||
恢复服务使用 `docker compose up --detach`。需要删除服务容器和网络但保留数据时使用:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
不要执行 `docker compose down --volumes`:它会删除 PostgreSQL 数据卷。浏览器 Profile 卷不属于 Compose 声明卷,删除浏览器容器或执行 `docker compose down` 时仍会保留;可用以下命令核对:
|
||||
|
||||
```bash
|
||||
docker volume ls --filter name=creatorhub-profile-
|
||||
```
|
||||
|
||||
`docker.sock` 即使以只读文件方式挂载,也仍允许 Docker API 写操作,等价于宿主机 root 权限。完整安全边界、API 契约和失败语义见[《浏览器容器控制面》](architecture/container-control.md)。
|
||||
强杀或网络中断后,不要重复发送真实平台写操作。先查询 control-plane 的运行、结果未知和 cleanup 状态,再按 generation 执行显式恢复或清理。
|
||||
|
||||
+13
-106
@@ -3,111 +3,18 @@ set -Eeuo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
extract_block() {
|
||||
awk -v wanted="$1" '
|
||||
/^```bash$/ {
|
||||
in_code = 1
|
||||
strict = ""
|
||||
wrapper = ""
|
||||
next
|
||||
}
|
||||
in_code && /^\($/ { wrapper = $0 }
|
||||
in_code && /^[[:space:]]*set -Eeuo pipefail$/ { strict = $0 }
|
||||
/^[[:space:]]*list_running_browsers\(\) \{/ {
|
||||
count++
|
||||
capture = count == wanted
|
||||
if (capture) {
|
||||
if (wrapper) print wrapper
|
||||
print strict
|
||||
}
|
||||
}
|
||||
capture { print }
|
||||
capture && /^[[:space:]]*docker compose stop/ {
|
||||
capture = 0
|
||||
if (!wrapper) exit
|
||||
closing = 1
|
||||
}
|
||||
closing && /^\)$/ { print; exit }
|
||||
/^```$/ { in_code = 0 }
|
||||
' docs/deployment.md
|
||||
}
|
||||
# Deployment documentation must describe only the host-native browser lifecycle.
|
||||
grep -Fq 'systemctl --user' docs/deployment.md
|
||||
grep -Fq 'creatorhub-browser-gateway.service' docs/deployment.md
|
||||
grep -Fq 'BROWSER_PROFILE_ROOT' docs/deployment.md
|
||||
grep -Fq 'Compose 不再创建 gateway 服务' docs/deployment.md
|
||||
! grep -Fq 'docker-gateway' docs/deployment.md
|
||||
! grep -Fq 'DOCKER_SOCKET' docs/deployment.md
|
||||
! grep -Fq 'docker.sock' docs/deployment.md
|
||||
|
||||
docker() {
|
||||
printf '%s\n' "$*" >>"$calls"
|
||||
case "$1" in
|
||||
ps)
|
||||
[[ "$*" == 'ps --quiet --filter name=^creatorhub-browser- --filter label=io.creatorhub.managed=true --filter label=io.creatorhub.runtime-id' ]] || return 64
|
||||
query_count=0
|
||||
[[ ! -e "$state/query-count" ]] || read -r query_count <"$state/query-count"
|
||||
((query_count += 1))
|
||||
printf '%s\n' "$query_count" >"$state/query-count"
|
||||
[[ "$mode:$query_count" != query_failure:1 && "$mode:$query_count" != recheck_failure:2 ]] || return 42
|
||||
[[ -e "$state/stopped" || -e "$state/removed" ]] || printf '%s\n' browser-1
|
||||
;;
|
||||
stop)
|
||||
case "$mode" in
|
||||
stop_success) : >"$state/stopped" ;;
|
||||
stop_failure) return 1 ;;
|
||||
stop_timeout) return 124 ;;
|
||||
esac
|
||||
;;
|
||||
rm)
|
||||
[[ "$*" == 'rm --force browser-1' ]] || return 64
|
||||
: >"$state/removed"
|
||||
;;
|
||||
compose)
|
||||
[[ "$*" == 'compose stop' || "$*" == 'compose stop creator-hub docker-gateway' ]] || return 64
|
||||
;;
|
||||
esac
|
||||
}
|
||||
export -f docker
|
||||
# The install unit must remain a user service and must not run the gateway as root.
|
||||
grep -Fq 'EnvironmentFile=%h/.config/creatorhub/browser-gateway.env' deploy/creatorhub-browser-gateway.service.in
|
||||
grep -Fq 'cmd.browser_gateway.gateway' deploy/creatorhub-browser-gateway.service.in
|
||||
! grep -Fq 'User=root' deploy/creatorhub-browser-gateway.service.in
|
||||
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
query='ps --quiet --filter name=^creatorhub-browser- --filter label=io.creatorhub.managed=true --filter label=io.creatorhub.runtime-id'
|
||||
scenario_count=0
|
||||
|
||||
for block_number in 1 2; do
|
||||
block="$(extract_block "$block_number")"
|
||||
bash -n <<<"$block"
|
||||
[[ "$block" == *'set -Eeuo pipefail'* ]]
|
||||
if [[ $block_number -eq 1 ]]; then
|
||||
[[ "${block%%$'\n'*}" == '(' && "${block##*$'\n'}" == ')' ]]
|
||||
compose='compose stop creator-hub docker-gateway'
|
||||
else
|
||||
[[ "${block%%$'\n'*}" == 'set -Eeuo pipefail' ]]
|
||||
compose='compose stop'
|
||||
fi
|
||||
|
||||
for mode in stop_success stop_failure stop_timeout query_failure recheck_failure; do
|
||||
state="$work/$block_number-$mode"
|
||||
calls="$state/calls"
|
||||
mkdir "$state"
|
||||
export mode state calls
|
||||
|
||||
status=0
|
||||
bash -c "$block" || status=$?
|
||||
|
||||
case "$mode" in
|
||||
stop_success)
|
||||
[[ $status -eq 0 ]]
|
||||
diff -u <(printf '%s\n' "$query" 'stop browser-1' "$query" "$compose") "$calls"
|
||||
;;
|
||||
stop_failure | stop_timeout)
|
||||
[[ $status -eq 0 ]]
|
||||
diff -u <(printf '%s\n' "$query" 'stop browser-1' 'rm --force browser-1' "$query" "$compose") "$calls"
|
||||
;;
|
||||
query_failure)
|
||||
[[ $status -eq 1 ]]
|
||||
diff -u <(printf '%s\n' "$query") "$calls"
|
||||
;;
|
||||
recheck_failure)
|
||||
[[ $status -eq 1 ]]
|
||||
diff -u <(printf '%s\n' "$query" 'stop browser-1' "$query") "$calls"
|
||||
;;
|
||||
esac
|
||||
scenario_count=$((scenario_count + 1))
|
||||
done
|
||||
done
|
||||
|
||||
[[ $scenario_count -eq 10 ]]
|
||||
echo 'native browser deployment documentation checks passed'
|
||||
|
||||
+74
-74
@@ -1,10 +1,10 @@
|
||||
# 当前系统全量功能测试计划(不含小红书)
|
||||
# 当前系统全量功能测试计划(单节点 native browser;抖音主流程与小红书读取)
|
||||
|
||||
## 1. 基线、目标与执行边界
|
||||
|
||||
- 源码基线:`main`,HEAD `4d1dd37438b7c7001f572695feb5e1b56b9fc78d`。编写前父会话已同步远程且工作树干净。本文件是测试计划,**所有用例初始状态均为“未执行”**,不是测试报告。
|
||||
- 目标:验证当前页面、公开 API、Docker/浏览器 gateway、持久数据及实际抖音链路;以当前源码为准,不将 `docs/plan01.md` 的未实现目标列为既有功能。
|
||||
- 本次只编写并静态核验文档;不运行测试、不启动或停止服务、不改配置、不访问外部平台、不提交或推送。下文命令和故障操作均供**后续获准执行**,本次未执行。
|
||||
- 源码基线:已同步的 `main` / `origin/main`,HEAD `7e3808cf4ce453b3583079680a3b59ca2ed64ad4`。本文件是测试计划;已执行项目和真实平台阻塞项目以 [验证记录](evidence/native-browser-verification-2026-09-18.md) 为准。
|
||||
- 目标:验证当前页面、公开 API、host-native browser gateway、持久数据及实际抖音链路;以当前源码为准,不将 `docs/plan01.md` 的未实现目标列为既有功能。
|
||||
- 自动检查和本地 native gateway smoke 已执行并记录;真实平台、代理、LAN、Profile 占用和破坏性资源用例仍须后续由授权操作者逐项执行。下文未执行项目不得以测试替身或内部函数补齐。
|
||||
- 排除:小红书全部业务测试;未实现的自动密码登录、真实文章/视频发布、自动聊天、附件发送、运营工作台新建私信会话、永久删除账号/环境/出口、RBAC/多租户及性能压测。公众号/快手仅覆盖现有通用账号建档,不测试其采集或平台动作。
|
||||
- 不排除:已有代码但尚无真实验收证据的抖音读取、通知监听、点赞、回复、关注、私信、素材处理。外部条件不具备时标“阻塞”,不得改为“不适用”以提高通过率。
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
### 1.2 核对来源与纠偏
|
||||
|
||||
主要来源:`web/src/main.jsx`、`web/src/dataProvider.js`、`web/src/*Page.jsx`、`web/src/Layout.jsx`、`web/src/lib/ui.jsx`;`cmd/control-plane/{main,hub,phasea,creator,creator_events,creator_material}.go`;`internal/creator/{models,accounts,actions,collection,content,metrics,rules,settings,bailian}.go`;`internal/hub/{environment,fingerprint}.go`;`internal/douyin/creator_collector.go`;`cmd/docker_gateway/{gateway,douyin,proxy,docker_client}.py`;`compose.yaml`、`compose.dev.yaml`、`Dockerfile`、`docker/browser-wrapper/`。
|
||||
主要来源:`web/src/main.jsx`、`web/src/dataProvider.js`、`web/src/*Page.jsx`、`web/src/Layout.jsx`、`web/src/lib/ui.jsx`;`cmd/control-plane/{main,hub,phasea,creator,creator_events,creator_material}.go`;`internal/creator/{models,accounts,actions,collection,content,metrics,rules,settings,bailian}.go`;`internal/hub/{environment,fingerprint}.go`;`internal/douyin/creator_collector.go`;`cmd/browser_gateway/{gateway,douyin,proxy,runtime}.py`;`compose.yaml`、`compose.dev.yaml`、`Dockerfile`、`deploy/`。
|
||||
|
||||
其他运行文档只作操作参考:其中“代理仅凭据引用”“仿写自动生成”“抖音仅首批读取”等旧表述不能覆盖当前路由和源码。当前出口表单/API 接收用户名与密码,列表/详情会明文展示;证据必须脱敏。仿写确认只保存要求与确认时间,**没有 AI 生成调用或按钮**。`decodeCreator` 直接读 JSON body,不依据 Content-Type 选择解码,故保存仿写缺 header 不等于已经证实失败。
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
|
||||
### 2.1 环境
|
||||
|
||||
1. 使用专用、可销毁的 Linux Docker 测试主机;禁止与现有业务共用固定 `creatorhub_control` 网络、浏览器 alias 或数据卷。只修改 Compose project 名不足以隔离固定网络名。
|
||||
2. 由执行负责人按 `docs/deployment.md` 准备一次完整 Compose 部署,记录控制面/gateway 构建提交、镜像 digest、Docker/Compose/浏览器版本、操作系统、数据库版本、时区、测试起止 UTC;另用独立时段验证 `compose.dev.yaml` 的开发入口。不得一边测试一边自动升级源码。
|
||||
3. 准备 `curl`、`jq`、Docker CLI、浏览器 DevTools;媒体核验使用当前镜像内的 `ffprobe`/`ffmpeg`。真实浏览器镜像使用 `docker/browser-wrapper/README.md` 中已登记来源与 digest 的包装镜像,准备两个可用版本和一个不存在引用。
|
||||
4. 配置必须来自测试凭据保管渠道:`CONTROL_PLANE_USERNAME`、`CONTROL_PLANE_PASSWORD`、32 字节 Base64 `CREATORHUB_CREDENTIAL_MASTER_KEY`、`DOCKER_GID`、`GATEWAY_TOKEN`。不得将实际密码、Cookie、token、证件号贴入文档/缺陷/日志截图。
|
||||
5. 记录控制面 `LISTEN_ADDR`、`WEB_DIR`、`DATABASE_URL`、`CREATORHUB_CREDENTIAL_STORE_DIR`、`LOG_LEVEL`;gateway 的 `LISTEN_ADDR`、`DOCKER_SOCKET`、`BROWSER_NETWORK`。媒体目录 `CREATOR_MEDIA_DIR` 可写;如测转写,`CREATOR_TRANSCRIPTION_BIN` 必须是已安装的真实供应商适配程序,接受音轨路径参数、stdout 输出正文;不能用 `echo` 冒充真实转写。
|
||||
1. 使用专用、可销毁的 Linux 主机;gateway 以非 root `systemctl --user` 服务运行。禁止与现有业务共用固定 browser alias、Profile 目录或数据库 schema;不得删除现有 Docker PostgreSQL 之外的业务服务。
|
||||
2. 由执行负责人按 `docs/deployment.md` 准备控制面/PostgreSQL Compose 与 host-native gateway,记录构建提交、浏览器版本/path、systemd/Xvfb/操作系统、数据库版本、时区、测试起止 UTC;另用独立时段验证 `compose.dev.yaml` 的开发入口。不得一边测试一边自动升级源码。
|
||||
3. 准备 `curl`、`jq`、必要时 Docker CLI(仅用于 PostgreSQL Compose)、`systemctl --user`、浏览器 DevTools;媒体核验使用宿主机 `ffprobe`/`ffmpeg`。准备两个已安装且可执行的 browser version/path 和一个不存在或不可执行的 path。
|
||||
4. 配置必须来自测试凭据保管渠道:`CONTROL_PLANE_USERNAME`、`CONTROL_PLANE_PASSWORD`、32 字节 Base64 `CREATORHUB_CREDENTIAL_MASTER_KEY`、`GATEWAY_TOKEN`。gateway 还需合法 `BROWSER_STATE_DIR`、`BROWSER_PROFILE_ROOT`、`NODE_ID` 和 browser version/path。不得将实际密码、Cookie、token、证件号贴入文档/缺陷/日志截图。
|
||||
5. 记录控制面 `LISTEN_ADDR`、`WEB_DIR`、`DATABASE_URL`、`CREATORHUB_CREDENTIAL_STORE_DIR`、`LOG_LEVEL`;gateway 的 `LISTEN_ADDR`、`BROWSER_STATE_DIR`、`BROWSER_PROFILE_ROOT`、`NODE_ID`、browser version/path。媒体目录 `CREATOR_MEDIA_DIR` 可写;如测转写,`CREATOR_TRANSCRIPTION_BIN` 必须是已安装的真实供应商适配程序,接受音轨路径参数、stdout 输出正文;不能用 `echo` 冒充真实转写。
|
||||
6. 如测 AI:准备已审批的 Bailian 账号、配额、`BAILIAN_API_KEY`,可选 `BAILIAN_BASE_URL`;页面 provider 填 `bailian`,model 填实际批准模型。勾选“已完成审批”并不验证密钥。转写页面元数据也不替代可执行程序配置。
|
||||
7. 对所有代理协议 http/https/socks4/socks5 分别准备真实可控测试端点;至少一个无认证端点、一个带认证端点、两个不同公网 IP、一个不可达端点,保存实际地区与供应商定义。没有某协议服务时该行阻塞,不能用另一协议代替。
|
||||
8. 记录观察窗口:普通 API 30 秒;创建/升级含拉镜像最多按控制面 11 分钟预算;运行租约调和每 20 秒、采集调度每 30 秒、监听绑定调和每 10 秒;事件长等待至多 25 秒,重连退避 1–30 秒。超时记录请求/日志,不自动重复写。
|
||||
8. 记录观察窗口:普通 API 30 秒;创建/升级含拉browser version/path最多按控制面 11 分钟预算;运行租约调和每 20 秒、采集调度每 30 秒、监听绑定调和每 10 秒;事件长等待至多 25 秒,重连退避 1–30 秒。超时记录请求/日志,不自动重复写。
|
||||
|
||||
### 2.2 账号、目标与数据
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
| C | 已同意的竞品测试账号,必须与采集执行账号不同;有可人工核对的近期/历史作品、分页及一级评论 |
|
||||
| W / K | 从真实采集得到的内部作品 ID / 评论 ID;另存平台 work_key/comment_key、作者 UID、原页面链接和时间证据 |
|
||||
| M1 / M2 / M3 | 有明确授权的带语音视频、无音轨视频、音轨无语音视频;保存原片时长与参考文稿,不把网页 HTML 当媒体 |
|
||||
| G / V1 / V2 | 测试网关名、两个镜像版本;升级使用第二个已启用版本 |
|
||||
| G / V1 / V2 | 测试网关名、两个已安装 browser version/path;升级使用第二个已启用版本 |
|
||||
| E0 / E1 / P1 / P2 | 直连环境、代理环境、两个已健康出口;每个环境固定不同账号 |
|
||||
| T / D / Q / AT | 本轮生成的任务、草稿、确认、Attempt ID;不得拿旧轮次同名结果充数 |
|
||||
|
||||
@@ -90,13 +90,13 @@ curl --silent --show-error --config "$API_AUTH_FILE" \
|
||||
| --- | --- | --- |
|
||||
| F1 浏览器请求失败 | DevTools → Network request blocking 添加准确 URL(如 `*/api/creator/rules*`),启用后刷新对应页;网络断开用 Network Offline | 记录报错、写请求数;取消规则/恢复 Online,只刷新读取;不能把页面断网当后端平台失败 |
|
||||
| F2 慢响应/页面竞争 | DevTools Network 选 Slow 3G、勾 Preserve log,快速切账号/素材/会话并观察完成顺序 | 恢复 No throttling;若没有形成倒序响应,记该竞争子项未触发,不宣称已验证 |
|
||||
| F3 gateway 故障 | 专用主机执行 `docker compose stop docker-gateway`;持久化错误场景可在生命周期操作已发出时停止,记时刻 | 读取控制面错误/审计/cleanup 状态;`docker compose start docker-gateway`,先查询环境与 Docker 实物,再按提示显式恢复。不得重发真实平台动作 |
|
||||
| F3 gateway 故障 | 专用主机执行 `systemctl --user stop creatorhub-browser-gateway.service`;持久化错误场景可在生命周期操作已发出时停止,记时刻 | 读取控制面错误/审计/cleanup 状态;`systemctl --user start creatorhub-browser-gateway.service`,先查询 runtime 实物和 generation,再按提示显式恢复。不得重发真实平台动作 |
|
||||
| F4 控制面中断 | 在目标 operation 为 processing 时执行 `docker compose stop creator-hub`;崩溃专项才使用 `docker compose kill -s SIGKILL creator-hub` | `docker compose start creator-hub`,记录自动重启竞争;只读核对旧操作、租约及平台结果;不重复 execute。优雅与强杀分别记录 |
|
||||
| F5 PostgreSQL 故障 | `docker compose stop postgres`,发一次指定读取/保存;记录是否已发出保存请求 | `docker compose start postgres`,待 `pg_isready` 后重读;不盲目补写。数据库不可用使多个后台报错是预期故障影响 |
|
||||
| F6 实际代理断连 | 对专用测试代理容器 `docker stop "$TEST_PROXY_CONTAINER"`(先核验其归属);或使用供应商已批准的暂停端点动作 | 显式检测出口/访问测试目标,确认未回退直连;`docker start "$TEST_PROXY_CONTAINER"` 或恢复供应商端点后重新检测健康 |
|
||||
| F7 实际浏览器丢失/断线 | 先核验 `docker inspect creatorhub-browser-$ALIAS` 的 RUN 对应别名/managed 标签,再 `docker stop`;仅丢失容器专项用 `docker rm -f`,不带 `-v` | 观察后台调和及动作审计;控制面重新启动环境,不直接复用旧 generation;确认 Profile 仍在 |
|
||||
| F8 媒体本地故障 | 专用测试部署中,读取已选素材 `video_reference` 并核验属于 RUN;在 control-plane 停止后由卷维护人员将该文件重命名为 `.e2e-backup`,再启动并 process;或对下一条全新素材临时撤掉真实转写程序配置 | 记录原文件/配置;停止控制面,按原路径恢复备份/原 `CREATOR_TRANSCRIPTION_BIN` 并重建该服务;清除临时文件。禁止修改 step 状态冒充成功;不存在可安全操作的独立卷则阻塞 |
|
||||
| F9 服务启动配置 | 不动运行中服务,用 `docker compose run --rm --no-deps -e NAME=VALUE creator-hub` 启动一次性命令;仅对预校验会失败的变量使用。gateway 以 `docker-gateway` 服务同法 | 校验失败应无新监听、无新文件/数据库/Docker 副作用;一次性容器退出即恢复。不将有效未知配置指向真实环境 |
|
||||
| F7 实际浏览器丢失/断线 | 先核验 `systemctl --user` 中 alias 对应的 browser/Xvfb unit 和 runtime metadata,再只停止或强杀该 runtime 的 unit;不得删除 Profile 目录 | 观察后台调和及动作审计;控制面重新启动环境,不直接复用旧 generation;确认 Profile 仍在 |
|
||||
| F8 媒体本地故障 | 专用测试部署中,读取已选素材 `video_reference` 并核验属于 RUN;停止 control-plane 后由负责人将该文件重命名为 `.e2e-backup`,再启动并 process;或对下一条全新素材临时撤掉真实转写程序配置 | 记录原文件/配置;停止控制面,按原路径恢复备份/原 `CREATOR_TRANSCRIPTION_BIN` 并重建该服务;清除临时文件。禁止修改 step 状态冒充成功;不存在可安全操作的独立目录则阻塞 |
|
||||
| F9 服务启动配置 | 不动运行中服务,用 control-plane 的一次性裸启动或 `systemd-run --user --wait` gateway 进程验证预校验会失败的变量;仅对预校验会失败的变量使用 | 校验失败应无新监听、无新 Profile/runtime/数据库副作用;进程退出即恢复。不将有效未知配置指向真实环境 |
|
||||
| F10 UI 异常响应 | 浏览器 Local Overrides 对指定 GET 保存脱敏副本,仅改待测字段,例如任务 `hold_reason` 与 `allowed_action`;记录原响应与改动 | 仅作 UI 注入,不算服务端 E2E;禁止用该响应点击实际执行/恢复写按钮,观察禁用状态即可;关闭 Overrides 并重新读取 |
|
||||
|
||||
F8 的可执行操作补充(只供后续获准的专用部署;`SOURCE_PATH`必须由本轮material.video_reference定位到媒体目录下的普通文件,人工核对不是目录/其他账号文件;操作时无其他素材处理):
|
||||
@@ -128,7 +128,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
| --- | --- | --- |
|
||||
| 1 基础部署与配置 | `/healthz`,Compose、Cobra、配置、持久化、日志、静态资源 | SYS-01~SYS-06 |
|
||||
| 2 登录/导航/公共组件 | `/#/login`、`/#/`、Layout、全部页面共用控件 | UI-01~UI-07 |
|
||||
| 3 网关与镜像 | `/#/gateways`、`/#/browser-images` | GW-01~GW-05、IMG-01~IMG-04 |
|
||||
| 3 网关与浏览器版本 | `/#/gateways`、`/#/browser-versions` | GW-01~GW-05、VER-01~VER-04 |
|
||||
| 4 出口 | `/#/network-exits`、`/#/network-exits/:id` | NET-01~NET-06 |
|
||||
| 5 账号/环境 | `/#/accounts`、`/#/accounts/:id`、`/#/browsers`、`/#/browsers/new`、`/#/browsers/:id` | AC-01~AC-07、ENV-01~ENV-10 |
|
||||
| 6 gateway 领域能力 | `/v1/browsers`、代际/代理/CDP/抖音受限路由 | API 附录 G、GATE-01~GATE-06 |
|
||||
@@ -146,8 +146,8 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
|
||||
依赖执行顺序(从干净环境开始):
|
||||
|
||||
1. 完成 SYS-01~03、SYS-06 及 UI 登录/导航,创建 GW-01 网关、IMG-01 镜像、NET-01 出口、AC-01 账号与 ENV 创建/启动所需记录;公共组件的有数据/错误状态随对应模块补测。
|
||||
2. 取得环境引用后执行 GW-02 的引用冲突分支及其他资源关联检查;删除/停用/令牌失效等分支使用独立资源,或延后至这些资源的所有依赖用例完成。不得提前删掉后续真实测试使用的网关、镜像或出口。
|
||||
1. 完成 SYS-01~03、SYS-06 及 UI 登录/导航,创建 GW-01 网关、VER-01 browser version/path、NET-01 出口、AC-01 账号与 ENV 创建/启动所需记录;公共组件的有数据/错误状态随对应模块补测。
|
||||
2. 取得环境引用后执行 GW-02 的引用冲突分支及其他资源关联检查;删除/停用/令牌失效等分支使用独立资源,或延后至这些资源的所有依赖用例完成。不得提前删掉后续真实测试使用的网关、browser version/path或出口。
|
||||
3. 完成 AC-06 登录检查及非破坏性环境/gateway 检查;创建 Phase A 草稿并完成任务流程;配置采集设置与账号资料/关系。策略可保存,但自动写入保持关闭。
|
||||
4. 完成竞品采集,再以真实作品/评论执行素材、规则/线索和人工操作。完成前述数据创建后,才执行 SYS-04 持久化检查:账号、环境、草稿、规则、素材均实际存在;保存各项证据后重启。不能为满足前置而改库造数据。
|
||||
5. 人工动作留证后执行 EVT 自动响应,再立即停用策略;审计用例在所需动作/任务记录产生后执行。API-only 导入可在合法账号/来源就绪后独立执行,但修改全局指标设置的 API-02 应在 REAL 指标观察完成后执行并恢复设置。
|
||||
@@ -165,35 +165,35 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
|
||||
- 前置:专用干净主机,2.1 配置齐备;允许后续部署。
|
||||
- 操作:
|
||||
1. 按部署说明启动本轮三个服务并记录 ps、镜像 digest
|
||||
1. 按部署说明启动 control-plane、native gateway 和前端,并记录进程、browser version/path digest
|
||||
2. GET 控制面和 gateway 的 /healthz;GET 带认证的 /api/gateways
|
||||
3. 打开 BASE/,再打开实际 JS/CSS 资源;刷新 /#/accounts 及 /#/browsers/new
|
||||
- 预期:两个健康接口 204;数据库健康、服务持续运行;API 返回 JSON,页面/资源可加载。healthz 不等于数据库、平台或全部依赖已通过。
|
||||
|
||||
#### SYS-02 控制面配置与命令校验 [API/FAULT]
|
||||
|
||||
- 前置:SYS-01 镜像;采用 F9,禁止改运行配置。
|
||||
- 前置:SYS-01 browser version/path;采用 F9,禁止改运行配置。
|
||||
- 操作:
|
||||
1. 分别单项设置 LISTEN_ADDR=:0、LOG_LEVEL=bogus、WEB_DIR仅三个空格、DATABASE_URL=`http://example.invalid/db`
|
||||
2. 分别设置用户名仅三个空格或含冒号、密码不足6字节、主密钥非Base64或解码长度不为32、凭据目录为相对路径、BAILIAN_BASE_URL 含 userinfo
|
||||
3. 一次性容器运行 control-plane 的 --help;另传不支持的位置参数
|
||||
- 预期:非法输入非零退出、日志清楚指出字段且不泄露值;配置校验先于数据库/凭据/Docker副作用。只有当前最小根命令,无编造子命令。help 不启动服务;未支持参数拒绝。Viper可能将空环境变量视为未设置并采用默认值,因此空白字符串校验与“未设置/空变量默认值”另行记录,不能混为必然启动失败。
|
||||
- 预期:非法输入非零退出、日志清楚指出字段且不泄露值;配置校验先于数据库/凭据/runtime/systemd 副作用。只有当前最小根命令,无编造子命令。help 不启动服务;未支持参数拒绝。Viper可能将空环境变量视为未设置并采用默认值,因此空白字符串校验与“未设置/空变量默认值”另行记录,不能混为必然启动失败。
|
||||
|
||||
#### SYS-03 gateway 配置与依赖错误 [API/FAULT]
|
||||
|
||||
- 前置:SYS-01 镜像;F9 独立命令。
|
||||
- 前置:SYS-01 browser version/path;F9 独立命令。
|
||||
- 操作:
|
||||
1. 单项试 LISTEN_ADDR=:0、GATEWAY_TOKEN=short、DOCKER_SOCKET=空、BROWSER_NETWORK=creatorhub_control 或非法名称
|
||||
2. 恢复有效配置;在专用测试实例令 DOCKER_SOCKET 指向不存在路径,GET /v1/browsers
|
||||
1. 单项试 LISTEN_ADDR=:0、GATEWAY_TOKEN=short、BROWSER_STATE_DIR=空、BROWSER_PROFILE_ROOT=非 owner-only 或 browser path 不存在
|
||||
2. 恢复有效配置;在专用测试实例令 browser path 指向不存在路径,GET /v1/browsers
|
||||
3. 结束一次性实例并回读原服务列表
|
||||
- 预期:非法配置明确失败;缺 Docker socket 的真实领域请求报错、不返回假空成功;原环境不受影响。健康接口能返回不证明 Docker socket 可用。
|
||||
- 预期:非法配置明确失败;缺 browser path、Profile 权限或 systemd/Xvfb 条件的真实领域请求报错、不返回假空成功;原环境不受影响。健康接口能返回不证明 runtime 可用。
|
||||
|
||||
#### SYS-04 持久化与重启 [UI/FAULT]
|
||||
|
||||
- 前置:已有账号/资料密码、环境、草稿、规则、素材至少各一条;主密钥备份。
|
||||
- 操作:
|
||||
1. 记录各 ID、版本、非秘密字段和 Profile 内自建 RUN 书签
|
||||
2. 正常重启 creator-hub、docker-gateway,保留 PostgreSQL/凭据/素材/Profile卷
|
||||
2. 正常重启 control-plane、native gateway,保留 PostgreSQL/凭据/素材/Profile目录
|
||||
3. 重新登录并逐页读取;浏览器启动后核对书签;用原真实会话执行一次已授权只读身份检查
|
||||
- 预期:记录、版本链及文件不丢;原主密钥可解密既有凭据。启动不得无限复制记录;事件恢复另按 EVT-03。无主密钥不做破坏性换密钥实验。
|
||||
|
||||
@@ -211,7 +211,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
- 前置:可切换独立测试时段;本轮资源已登记。
|
||||
- 操作:
|
||||
1. 正常停止 gateway,记录耗时/退出码/日志并检查无 SIGKILL,随后恢复
|
||||
2. 核对只有 gateway 挂载 Docker socket,浏览器由其创建且命名与标签可追踪
|
||||
2. 核对只有 host-native gateway 管理 browser/Xvfb unit;runtime metadata、owner、node 和 generation 可追踪
|
||||
3. 完整部署结束后按 compose.dev.yaml 单独启动依赖与开发进程;打开5173入口并观察 /api 到8082、网关8081映射
|
||||
- 预期:正常关停在45秒宽限内完成或明确失败;代理/事件订阅不悬挂。生产8080与开发5173/8082入口均可用,错误网关地址不能假成功;两种模式不共用活动测试实例。
|
||||
|
||||
@@ -255,7 +255,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
|
||||
#### UI-05 弹窗取消、忙碌和重复点击 [UI/FAULT]
|
||||
|
||||
- 前置:镜像或账号普通创建弹窗、出口停用确认框;F2。
|
||||
- 前置:browser version/path或账号普通创建弹窗、出口停用确认框;F2。
|
||||
- 操作:
|
||||
1. 普通创建输入后分别用取消、关闭图标、Escape、遮罩退出再打开
|
||||
2. 确认框先取消一次,检查无写请求
|
||||
@@ -266,7 +266,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
|
||||
- 前置:干净数据时留存各页空态;F1可用。
|
||||
- 操作:
|
||||
1. 分别阻断 accounts、browsers、gateways、browser-images、network-exits、creator各列表、drafts、audit、attempt详情 GET
|
||||
1. 分别阻断 accounts、browsers、gateways、browser-versions、network-exits、creator各列表、drafts、audit、attempt详情 GET
|
||||
2. 逐页检查加载结束后错误区、操作禁用及是否显示假空态
|
||||
3. 取消阻断,只有存在重试按钮的页面点击重试,其余重新进入页面
|
||||
- 预期:失败与空数据可区分,不能隐藏依赖错误;没有重试按钮不写“重试成功”。特别记录账号草稿、环境创建依赖、规则列表异常呈现;不得自动重做先前写请求。
|
||||
@@ -280,7 +280,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
3. 复制可显示的CDP和网关token到本地私密临时区,随后撤销剪贴板权限再试
|
||||
- 预期:核心内容可读、禁用可辨;复制内容与来源一致但不要求当前不存在的成功提示。焦点圈定/自动聚焦、aria-describedby缺口如实报,不把缺失能力当支持;截图无秘密。
|
||||
|
||||
### 5.3 网关和镜像
|
||||
### 5.3 网关和browser version/path
|
||||
|
||||
#### GW-01 注册、令牌与列表 [UI]
|
||||
|
||||
@@ -316,7 +316,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
1. 点击删除先取消,确认列表仍在且无DELETE
|
||||
2. 再打开确认删除并确认一次,观察Network、Console与页面
|
||||
3. 刷新列表读取实际记录
|
||||
- 预期:预期删除应发送领域DELETE并移除记录;当前 provider.deleteOne 仅支持browser-images,预计阻断/报错,证实则记失败。不得使用后端删除成功掩盖UI失败;后续清理另用GW-05。
|
||||
- 预期:预期删除应发送领域DELETE并移除记录;browser version 被环境引用时必须拒绝删除。不得使用后端删除成功掩盖UI失败;runtime 回收另用 GW-05。
|
||||
|
||||
#### GW-05 网关删除 API 与引用冲突 [API]
|
||||
|
||||
@@ -327,16 +327,16 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
3. DELETE仍被环境引用的G,回读G和环境
|
||||
- 预期:未引用项成功删除;不存在返回明确结果、不影响其他项;引用中的网关409拒绝且环境引用不丢。此用例独立于UI删除缺陷。
|
||||
|
||||
#### IMG-01 创建镜像版本与字段边界 [UI/API]
|
||||
#### VER-01 创建browser version/path与字段边界 [UI/API]
|
||||
|
||||
- 前置:两个可拉取的已登记镜像摘要。
|
||||
- 前置:两个可拉取的已登记browser version/path。
|
||||
- 操作:
|
||||
1. 创建启用V1,检查版本/引用/备注;再创建V2
|
||||
2. 重复V1;试非法版本首字符、65字符版本、空引用、非法引用字符、超长备注
|
||||
3. F1阻断一次创建,恢复后只读查重
|
||||
- 预期:成功持久化,重复/非法值不覆盖已有版本;失败不清表单或误报成功。仅登记版本不证明镜像可运行。
|
||||
- 预期:成功持久化,重复/非法值不覆盖已有版本;失败不清表单或误报成功。仅登记版本不证明browser version/path可运行。
|
||||
|
||||
#### IMG-02 编辑、禁用和跨页候选 [UI]
|
||||
#### VER-02 编辑、禁用和跨页候选 [UI]
|
||||
|
||||
- 前置:V1/V2已登记,V1被E1引用。
|
||||
- 操作:
|
||||
@@ -345,7 +345,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
3. 再启用V2;编辑时清空引用点击保存
|
||||
- 预期:停用版本不出现在新建/升级候选,已有环境不静默换版;空引用应阻止请求或被后端拒绝;错误在编辑框可见。
|
||||
|
||||
#### IMG-03 删除引用与取消 [UI]
|
||||
#### VER-03 删除引用与取消 [UI]
|
||||
|
||||
- 前置:V1被引用,另有未引用临时版本。
|
||||
- 操作:
|
||||
@@ -354,12 +354,12 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
3. 恢复临时登记项以供下一用例或登记已删除
|
||||
- 预期:未引用删除成功;取消无DELETE;引用中拒绝且保留信息,不移除V1或破坏E1。
|
||||
|
||||
#### IMG-04 镜像不可拉取与动作忙碌 [UI/FAULT]
|
||||
#### VER-04 browser version/path不可拉取与动作忙碌 [UI/FAULT]
|
||||
|
||||
- 前置:临时版本指向不存在的仓库引用,独立暂停账号。
|
||||
- 操作:
|
||||
1. 新建环境选该版本并提交,记录拉取过程、按钮禁用和超时
|
||||
2. 查看环境列表、审计和Docker实际容器,不假定登记已回滚
|
||||
2. 查看环境列表、审计和gateway runtime 实物,不假定登记已回滚
|
||||
3. 修改该临时版本为正确测试digest,核验残留后显式恢复环境一次
|
||||
- 预期:失败可见,有requested/finished证据;没有假running;已保存环境/binding按实物核验恢复,不能重复生成绑定。
|
||||
|
||||
@@ -491,16 +491,16 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
- 前置:已授权暂停账号、G、启用V1。
|
||||
- 操作:
|
||||
1. 打开创建环境填写名称、alias、seed1000、账号/G/V1,出口选直连,提交
|
||||
2. 读取列表/详情及Docker实例状态
|
||||
2. 读取列表/详情及gateway runtime状态
|
||||
3. 去账号详情恢复账号,回环境列表显式启动,复制CDP并按已批准连接方式打开浏览器
|
||||
- 预期:创建先停止态,固定绑定正确;恢复账号和启动分离;运行有实际容器/租约,直连无出口也可用。CDP展示/复制不代表公网端口已发布。
|
||||
- 预期:创建先停止态,固定绑定正确;恢复账号和启动分离;运行有实际 browser/Xvfb unit 与 lease,直连无出口也可用。CDP展示/复制不代表公网端口已发布。
|
||||
|
||||
#### ENV-02 代理环境和高级指纹 [UI/REAL]
|
||||
|
||||
- 前置:另一暂停账号、healthy P1、G/V1。
|
||||
- 操作:
|
||||
1. 创建E1选择P1;高级字段依次填windows/linux/macos中的一个、Chrome/Edge/Opera/Vivaldi中的一个、版本、CPU、语言、时区与伪装禁用项
|
||||
2. 恢复账号并启动,Docker inspect核对argv、Profile卷和无凭据代理地址
|
||||
2. 恢复账号并启动,Docker inspect核对argv、Profile目录和无凭据代理地址
|
||||
3. 浏览器查看navigator语言/CPU等及真实公网IP;停启后重新核对
|
||||
- 预期:选择参数保留且argv独立传递;实际IP匹配P1,凭据不出现在命令/标签。OS/brand其余合法选项分轮独立记录,不要求同一环境改指纹入口。
|
||||
|
||||
@@ -508,7 +508,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
|
||||
- 前置:空依赖与齐备依赖分时段;附录D。
|
||||
- 操作:
|
||||
1. 缺网关/镜像/可绑账号/健康出口时打开表单查看准备提示;F1阻断依赖GET后再开
|
||||
1. 缺网关/browser version/path/可绑账号/健康出口时打开表单查看准备提示;F1阻断依赖GET后再开
|
||||
2. 分别试alias非法/超32、seed0/2147483648/小数、CPU负数/129、非法语言/时区、指纹内proxy_server
|
||||
3. 修正后创建;并用API重复同alias不同账号或已绑定账号的新alias
|
||||
- 预期:默认只选合法依赖;错误读取不伪装业务缺项;字段越界后端拒绝。相同资源配置重复按幂等核验,冲突配置409;一账号不出现两个活动绑定。
|
||||
@@ -554,9 +554,9 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
- 前置:仅E1测试账号;两个API客户端,不发平台业务动作。
|
||||
- 操作:
|
||||
1. 两端同时POST start,保存每个响应与operation_id
|
||||
2. 在一次启动/升级进行时另端pause账号或disable目标镜像
|
||||
3. 读取账号、镜像、environment及Docker实物,按最终有效配置恢复
|
||||
- 预期:不会双runtime或使用已失效配置绕过锁;完成审计与实际generation匹配;冲突/失败明确,未批准状态不能偷偷启动。取消禁用镜像或恢复账号只在核验后显式执行。
|
||||
2. 在一次启动/升级进行时另端pause账号或disable目标browser version/path
|
||||
3. 读取账号、browser version/path、environment及runtime/systemd 实物,按最终有效配置恢复
|
||||
- 预期:不会双 runtime 或使用已失效配置绕过锁;完成审计与实际generation匹配;冲突/失败明确,未批准状态不能偷偷启动。取消禁用browser version/path或恢复账号只在核验后显式执行。
|
||||
|
||||
#### ENV-09 gateway失联、清理待定与租约 [UI/FAULT]
|
||||
|
||||
@@ -564,7 +564,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
- 操作:
|
||||
1. F3停gateway后停止或回收环境一次,记录cleanup_pending/unknown/错误
|
||||
2. 恢复gateway,只读观察至少两个20秒调和周期及审计
|
||||
3. 另轮F7移除容器,不删除Profile,观察missing/runtime释放,再显式启动
|
||||
3. 另轮 F7 停止/强杀 browser unit,不删除 Profile,观察missing/runtime释放,再显式启动
|
||||
- 预期:失败不能假已停止/无绑定;后台恢复有requested/finished证据,租约不长期显示可调度假running;新旧generation不混用,Profile不丢。
|
||||
|
||||
#### ENV-10 非法动作与资源失效 [API]
|
||||
@@ -572,9 +572,9 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
- 前置:附录D;测试alias及不存在alias。
|
||||
- 操作:
|
||||
1. POST现有alias/unknown-action;给upgrade增加未知字段或非法version
|
||||
2. 给create未知字段、已停用镜像或未授权账号;GET不存在alias
|
||||
2. 给create未知字段、已停用browser version/path或未授权账号;GET不存在alias
|
||||
3. 回读原环境和动作审计
|
||||
- 预期:非法操作400/冲突404等明确,不修改旧环境;升级/重绑拒绝输入的审计不泄露输入秘密;没有通用PUT更新或任意Docker操作入口。
|
||||
- 预期:非法操作400/冲突404等明确,不修改旧环境;升级/重绑拒绝输入的审计不泄露输入秘密;没有通用 PUT 更新或任意 browser/CDP 操作入口。
|
||||
|
||||
### 5.7 gateway 领域 API
|
||||
|
||||
@@ -585,16 +585,16 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
1. 分别无token/错误token GET /v1/browsers,再用正确Bearer读取
|
||||
2. POST create提交未知字段、非对象JSON、缺body及超过1MiB body
|
||||
3. 访问非领域路径与不存在alias,最后回读正常列表
|
||||
- 预期:除healthz外领域请求校验token;非法body400,未知路径404,不提供通用Docker或任意CDP代理;列表未夹带其他非managed容器。
|
||||
- 预期:除healthz外领域请求校验token;非法body400,未知路径404,不提供通用 CDP 代理;列表未夹带其他非本 gateway runtime。
|
||||
|
||||
#### GATE-02 创建/启停/删除与代际 [API]
|
||||
|
||||
- 前置:单独的gateway实验alias,不对应活动控制面环境;附录G创建样例及已批准V1镜像。
|
||||
- 前置:单独的gateway实验alias,不对应活动控制面环境;附录G创建样例及已批准V1browser version/path。
|
||||
- 操作:
|
||||
1. POST /v1/browsers 创建停止态实验容器,GET列表提取实际runtime/network/binding
|
||||
1. POST /v1/browsers 创建停止态实验 runtime,GET列表提取实际runtime/network/binding
|
||||
2. 按附录G正确generation进行stop/remove;重复remove核对幂等或明确冲突
|
||||
3. 另轮G-create改stopped=false创建运行态实验容器,GET取得真实network与runtime后用正确generation依次stop/start,核对实际状态;再用旧runtime_id或错误binding执行start/stop/remove并读取Docker实物
|
||||
- 预期:合法操作可观察,创建返回201;过期代际409且不操作新容器;不匹配managed标签的对象不可删除。实验资源按END单独回收,不交给控制面冒充业务环境。
|
||||
3. 另轮G-create改stopped=false创建运行态实验 runtime,GET取得真实network与runtime后用正确generation依次stop/start,核对实际状态;再用旧runtime_id或错误binding执行start/stop/remove并读取runtime/systemd 实物
|
||||
- 预期:合法操作可观察,创建返回201;过期代际409且不操作新 runtime;不匹配 owner/node/generation 的对象不可删除。实验资源按END单独回收,不交给控制面冒充业务环境。
|
||||
|
||||
#### GATE-03 代理恢复与隧道生命周期 [API/REAL/FAULT]
|
||||
|
||||
@@ -1273,7 +1273,7 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
1. 在各类POST/PUT复制合法body分别加入unknown字段、尾随第二对象、错误类型;试空body与超过1MiB
|
||||
2. GET各详情不存在ID,试POST路径拼错/不支持方法,记录实际HTTP和body
|
||||
3. 正确body再次只读核验原记录,检查服务日志无Cookie/token/账号密码
|
||||
- 预期:字段错误400、资源不存在404、冲突409、依赖不可用503等按对应handler语义可辨;不把HTML SPA兜底当API成功。gateway Docker依赖可能502,不能强求所有模块统一503。
|
||||
- 预期:字段错误400、资源不存在404、冲突409、依赖不可用503等按对应handler语义可辨;不把HTML SPA兜底当API成功。gateway native gateway 依赖可能502,不能强求所有模块统一503。
|
||||
|
||||
### 5.18 清理
|
||||
|
||||
@@ -1290,27 +1290,27 @@ OP-08的UI注入在浏览器Console执行 `Object.defineProperty(crypto, 'random
|
||||
|
||||
- 前置:END-01;所有备份和RUN资源清单。
|
||||
- 操作:
|
||||
1. 按3.2恢复全部网络/配置/文件/Overrides;回收测试浏览器容器,核对Profile保留
|
||||
2. 删除未引用镜像/网关(网关用API);停用测试出口;恢复原settings/策略/业务状态并只读核对
|
||||
3. 需彻底销毁时,先停止专用Compose部署,由负责人逐一核对RUN容器/网络/卷后删除本轮数据卷和Profile;导出脱敏报告
|
||||
- 预期:只清本轮资源,禁止全局docker prune或不核对就down -v;回收后仍保留环境binding会阻止删除被引用网关/镜像,应登记保留或在获批整套测试库销毁时一起清理,不伪造永久删除API。原始敏感证据受限,最终报告脱敏。
|
||||
1. 按3.2恢复全部网络/配置/文件/Overrides;回收测试browser runtime,核对Profile保留
|
||||
2. 删除未引用browser version/path/网关(网关用API);停用测试出口;恢复原settings/策略/业务状态并只读核对
|
||||
3. 需彻底销毁时,先停止专用Compose部署,由负责人逐一核对RUN runtime/Profile/数据库临时资源后删除本轮数据卷和Profile;导出脱敏报告
|
||||
- 预期:只清本轮资源,禁止全局docker prune或不核对就down -v;回收后仍保留环境binding会阻止删除被引用网关/browser version/path,应登记保留或在获批整套测试库销毁时一起清理,不伪造永久删除API。原始敏感证据受限,最终报告脱敏。
|
||||
|
||||
## 6. API 覆盖及请求样例
|
||||
|
||||
以下目录是第5节用例的执行附录,不额外计算用例数。每个写接口必须使用指定用例的前置与授权,不能把目录当成可直接批量运行的脚本。`无`表示无请求 body。所有对象样例均为合法 JSON 结构,里面的 A/S1/C/W/K/R 等必须替换为账本真实内部 ID;平台数字 UID、作品 key 和 comment key另取真实值。返回字段不要全量复制进严格输入接口。除明确 API 补充数据外,优先使用真实采集/监听输出;标记 `RUN-api-only` 的样例绝不能进入 REAL 通过统计。
|
||||
|
||||
### 附录 A:网关登记与镜像
|
||||
### 附录 A:网关登记与browser version/path
|
||||
|
||||
| 方法 / 路径 | body或查询样例 | 入口 / 用例 |
|
||||
| --- | --- | --- |
|
||||
| GET `/api/gateways` | 无 | UI、GW-01 |
|
||||
| POST `/api/gateways` | `{"name":"e2e-gw","endpoint":"http://docker-gateway:8081","token":""}`;显式token从私密文件替换 | UI、GW-01/03 |
|
||||
| PUT `/api/gateways/{G}` | `{"name":"e2e-gw-renamed","endpoint":"http://docker-gateway:8081","token":""}` | UI、GW-02 |
|
||||
| POST `/api/gateways` | `{"name":"e2e-gw","endpoint":"http://host.docker.internal:8081","token":""}`(裸机改为 `http://127.0.0.1:8081`);显式 token 从私密文件替换 | UI、GW-01/03 |
|
||||
| PUT `/api/gateways/{G}` | `{"name":"e2e-gw-renamed","endpoint":"http://host.docker.internal:8081","token":""}`(裸机改为 `http://127.0.0.1:8081`) | UI、GW-02 |
|
||||
| DELETE `/api/gateways/{G}` | 无;只删除未引用项 | **API可用,UI接线失败待复现**,GW-04/05 |
|
||||
| GET `/api/browser-images` | 无 | UI、IMG-01 |
|
||||
| POST `/api/browser-images` | `{"version":"1-e2e","image_ref":"registry.example.invalid/test/browser:approved","note":"RUN","enabled":true}`;image_ref必须换已登记真实digest | UI、IMG-01 |
|
||||
| PUT `/api/browser-images/{V2}` | `{"image_ref":"registry.example.invalid/test/browser:approved","note":"RUN编辑","enabled":false}`;不传version | UI、IMG-02 |
|
||||
| DELETE `/api/browser-images/{V2}` | 无 | UI、IMG-03 |
|
||||
| GET `/api/browser-versions` | 无 | UI、VER-01 |
|
||||
| POST `/api/browser-versions` | `{"version":"148.0.7778.215","browser_path":"/absolute/path/to/chrome","note":"RUN","enabled":true}`;path必须换本机已核验的可执行文件 | UI、VER-01 |
|
||||
| PUT `/api/browser-versions/{V2}` | `{"browser_path":"/absolute/path/to/chrome","note":"RUN编辑","enabled":false}`;不传 version | UI、VER-02 |
|
||||
| DELETE `/api/browser-versions/{V2}` | 无 | UI、VER-03 |
|
||||
|
||||
### 附录 B:网络出口
|
||||
|
||||
@@ -1530,18 +1530,18 @@ F-message(仅隔离API存储,platform_message_key带API标记;message_at
|
||||
|
||||
这些接口不经 `/api`;独立实验用例才直接调用,正常业务优先从控制面进入。`GATEWAY_BASE` 的可达方式见3.1。除 `GET /v1/browsers` 外,领域GET/DELETE也要提交 JSON body及Content-Length,不能照普通GET省略。
|
||||
|
||||
**代际获取**:控制面 `GET /api/browsers/{E1}` 返回对象内运行记录的 binding_version(先按实际JSON层级读取),与 gateway `GET /v1/browsers` 中同alias的 `id`、`network_id`、`network_exit_id` 共同核验;`id`是Docker runtime ID,不是控制面 runtime_instance_id。实际字段回读与Docker标签一致后再填请求。不存在时不得随意填 `1`/假ID来做成功测试。
|
||||
**代际获取**:控制面 `GET /api/browsers/{E1}` 返回对象内运行记录的 binding_version(先按实际JSON层级读取),与 gateway `GET /v1/browsers` 中同alias的 `id`、`network_id`、`network_exit_id` 共同核验;`id`是gateway runtime ID,不是控制面 runtime_instance_id。实际字段回读与 runtime metadata 一致后再填请求。不存在时不得随意填 `1`/假ID来做成功测试。
|
||||
|
||||
G-generation(start/stop/remove仅这三字段,不能混入network_exit_id):
|
||||
|
||||
```json
|
||||
{"binding_version":1,"runtime_id":"实际Docker容器ID","network_id":"实际Docker网络ID"}
|
||||
{"binding_version":1,"runtime_id":"实际 runtime ID","network_id":"实际 network ID"}
|
||||
```
|
||||
|
||||
G-douyin-base(cookies/get/identity/action/events均在此对象上加各自字段;直连出口为空,但仍须真实network_id):
|
||||
|
||||
```json
|
||||
{"binding_version":1,"runtime_id":"实际Docker容器ID","network_id":"实际Docker网络ID","network_exit_id":"P1"}
|
||||
{"binding_version":1,"runtime_id":"实际 runtime ID","network_id":"实际 network ID","network_exit_id":"P1"}
|
||||
```
|
||||
|
||||
| 方法 / 路径 | 请求样例 / 在base上追加字段 | 用例 |
|
||||
@@ -1569,7 +1569,7 @@ G-douyin-base(cookies/get/identity/action/events均在此对象上加各自字
|
||||
- 一级评论:`https://www.douyin.com/aweme/v1/web/comment/list/?aweme_id=实际平台work_key&count=20&cursor=0`;下一页同理。
|
||||
- 不允许其他host、额外参数、任意CDP或脚本。源码当前允许非负数字分页,不采用旧文档“只能max_cursor=0”的描述。
|
||||
|
||||
G-create(独立实验alias,不与ENV共用;镜像换真实摘要。`stopped=true`仅允许直连;运行态实验需要受控网络并明确授权):
|
||||
G-create(独立实验alias,不与ENV共用;browser version/path换真实摘要。`stopped=true`仅允许直连;运行态实验需要受控网络并明确授权):
|
||||
|
||||
```json
|
||||
{"alias":"e2e-gate-only","name":"RUN gateway实验","image":"registry.example.invalid/test/browser:approved","cmd":["--fingerprint=1000","about:blank"],"volume":"creatorhub-profile-e2e-gate-only","binding_version":1,"network_exit_id":"","network_exit":{},"stopped":true}
|
||||
@@ -1578,13 +1578,13 @@ G-create(独立实验alias,不与ENV共用;镜像换真实摘要。`stoppe
|
||||
G-proxy:
|
||||
|
||||
```json
|
||||
{"binding_version":1,"runtime_id":"实际Docker容器ID","network_id":"实际Docker网络ID","network_exit_id":"P1","network_exit":{"protocol":"socks5","host":"proxy.example.invalid","port":1080,"username":"","password":""}}
|
||||
{"binding_version":1,"runtime_id":"实际 runtime ID","network_id":"实际 network ID","network_exit_id":"P1","network_exit":{"protocol":"socks5","host":"proxy.example.invalid","port":1080,"username":"","password":""}}
|
||||
```
|
||||
|
||||
G-action(gateway字段使用**平台**标识,不是内部K/W;仅预览):
|
||||
|
||||
```json
|
||||
{"binding_version":1,"runtime_id":"实际Docker容器ID","network_id":"实际Docker网络ID","network_exit_id":"P1","expected_uid":"真实发送账号UID","action":"reply_comment","target_uid":"真实作者UID","target_comment_id":"真实平台comment_key","target_work_id":"真实平台work_key","text":"RUN已批准文本","confirm":false}
|
||||
{"binding_version":1,"runtime_id":"实际 runtime ID","network_id":"实际 network ID","network_exit_id":"P1","expected_uid":"真实发送账号UID","action":"reply_comment","target_uid":"真实作者UID","target_comment_id":"真实平台comment_key","target_work_id":"真实平台work_key","text":"RUN已批准文本","confirm":false}
|
||||
```
|
||||
|
||||
GET带body调用示例(仅在后续执行时,`BODY_FILE`为替换后的G-douyin-base,认证配置含Bearer):
|
||||
@@ -1603,7 +1603,7 @@ curl --silent --show-error --config "$GATEWAY_AUTH_FILE" \
|
||||
| 项目 | 基线事实 / 风险 | 对应判定 |
|
||||
| --- | --- | --- |
|
||||
| 首次已验证登录闭环 | 新账号unknown;公开login-result拒绝logged_in;采集/写/监听又要求logged_in。人工浏览器登录到控制面资料的可达路径需验证 | AC-06;不能改库解决测试前置 |
|
||||
| 网关UI删除 | provider仅允许删除browser-images;页面网关删除未正确接通 | GW-04失败与GW-05 API成功分开 |
|
||||
| 网关 UI 删除 | gateway 删除必须只回收 runtime,保留 Profile 与正式结果;页面状态和 API 结果分开核对 | GW-04 失败与 GW-05 API 成功分开 |
|
||||
| 线索人工联系 | 线索页未渲染回复编辑区、没有自动切评论页 | LEAD-06,不能把手动绕路当通过 |
|
||||
| 规则错误区、依赖失败 | 多处辅助列表失败可能退化空数据/准备提示 | UI-06、LEAD-02、ENV-03 |
|
||||
| 仿写 | 确认只保存要求;PUT缺Content-Type,但服务端直接JSON解码;切换/确认后表单一致性需实测 | MAT-05/06,不期待自动生成 |
|
||||
@@ -1621,7 +1621,7 @@ curl --silent --show-error --config "$GATEWAY_AUTH_FILE" \
|
||||
|
||||
复制下表,每例及每个参数化子项各一行。不要把多协议、四种Mock结果、六类动作合并成一个“通过”。
|
||||
|
||||
| RUN / 用例 / 子项 | 层级 | HEAD / 镜像digest | 执行人 / UTC起止 | 前置账号与资源ID | 授权单 | 实际有序操作 | HTTP/operation/event/Attempt ID | 实际UI与平台结果 | 证据路径 | 状态 | 缺陷/阻塞及恢复清理 |
|
||||
| RUN / 用例 / 子项 | 层级 | HEAD / browser version/path | 执行人 / UTC起止 | 前置账号与资源ID | 授权单 | 实际有序操作 | HTTP/operation/event/Attempt ID | 实际UI与平台结果 | 证据路径 | 状态 | 缺陷/阻塞及恢复清理 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 待填写 | UI/API/REAL/MOCK/FAULT | 待填写 | 待填写 | 脱敏ID | 无写填不适用 | 记录实际步骤,不只复制计划 | 无则说明原因 | 不以“正常”代替事实 | 脱敏截图/响应/日志索引 | 未执行 | 待填写 |
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Native Browser P0 本机前置核验记录
|
||||
|
||||
- 核验时间:2026-09-17T07:51:43Z 起,按单节点范围复核至 2026-09-17
|
||||
- 状态:**P0 已通过,放行本机单节点实现;真实平台验收证据待 P6 补齐**
|
||||
- 依据:`docs/native-browser-change-review.md`、`docs/native-browser-implementation-plan.md`、`docs/native-browser-verification.md`
|
||||
- 本目标范围:本机单节点;多节点控制、A/B 环境、跨机恢复与多节点性能对比移至后续目标。
|
||||
|
||||
## Git 基线与隔离
|
||||
|
||||
- 同步时间:2026-09-17T08:23:28Z
|
||||
- 同步源:`main` 与 `origin/main` 均为 `7e3808cf4ce453b3583079680a3b59ca2ed64ad4`
|
||||
- worktree 同步前提交:`1bf905599cb843959d6b1d4d64918771a0f87aa7`
|
||||
- worktree 同步后提交:`7e3808cf4ce453b3583079680a3b59ca2ed64ad4`
|
||||
- 实施分支:`refactor/native-browser-xvfb-impl`
|
||||
- 实施 worktree:`/home/rogee/Workspace/creator-hub-native-browser`
|
||||
- 采用 fast-forward 同步,未使用强制 reset。原工作目录由其它线程使用,本次未对其 stash、reset、删除或覆盖;其既有修改和未跟踪文件继续保留。
|
||||
- 本 worktree 尚未修改业务代码;未执行 Docker 删除、远程服务修改、数据库重建或浏览器资源回收。
|
||||
|
||||
## 本机能力
|
||||
|
||||
| 项目 | 证据 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 操作系统/用户 | Linux `home-dev`,uid 1000 `rogee` | 通过:gateway 可按非 root 用户运行 |
|
||||
| Python/Go/Node | Python 3.13.5、Go 1.26.4、Node v22.21.0 | 通过:满足项目版本要求 |
|
||||
| systemd | systemd 257.9;system/user manager 可访问,但总体状态 `degraded`,失败项为既有桌面/Compose 服务 | 部分通过:可继续核验,不能把既有服务当作 CreatorHub native gateway |
|
||||
| Xvfb | 已安装;隔离 `-displayfd` 启动/退出探针成功 | 通过:可按 runtime 分配 display;不得复用固定 `:99` |
|
||||
| 磁盘 | `/` 使用率 66%,`/tmp` 使用率 16%;用户已批准最低可用磁盘 20 GB | 通过:实现和验收必须在低于下限前拒绝新 runtime 并保留清理证据 |
|
||||
| 网络 | `ens18` 为 `10.1.1.104/24`,默认路由为 `10.1.1.10` | 通过:局域网监听/联调具备基础条件;实际代理和平台访问仍需单独授权核验 |
|
||||
| sandbox / fingerprint 浏览器探针 | uid 1000 使用独立临时 Profile、未传 `--no-sandbox` 启动 fingerprint Chromium 148.0.7778.215;`unshare -Ur` 与 `unshare -Urnm` 成功,指纹参数使 UA/platform/cores/lang/timezone 生效 | 通过:可使用 Linux user-namespace sandbox;不继承现有普通 Chrome 或 Docker 进程 |
|
||||
|
||||
## 浏览器与 Profile 边界
|
||||
|
||||
- 已按用户指定的上游仓库安装 fingerprint Chromium:<https://github.com/adryfish/fingerprint-chromium>。
|
||||
- 上游版本:tag `148.0.7778.215`,源码仓库提交 `3f61b0dfa665e883da8824b1450601fc529dd006`;发布资产为 `ungoogled-chromium-148.0.7778.215-1-x86_64_linux.tar.xz`,GitHub API 提供的 SHA-256 为 `70d239830332e5820aa34dfcb284161cac0429eee25da642830afe04bda717f4`。
|
||||
- 本机安装路径:`/home/rogee/.local/share/creatorhub/browsers/fingerprint-chromium/148.0.7778.215/chrome`;安装二进制 SHA-256 为 `abd700e6015e259a00f1a31e99ad16f99f63365222328937454e8f603f575284`,权限为 `rogee:rogee 0755`。
|
||||
- 上游 `LICENSE` 为 BSD-3-Clause;仓库 README 说明该构建基于 Ungoogled Chromium,并公开上述指纹参数。许可证和来源已记录,未把其代码复制进本项目。
|
||||
- 当前看到的 `/opt/chromium/chrome` 只存在于另一进程的 mount namespace,主机路径不存在;该进程命令行带 `--no-sandbox`,不能作为 native gateway 的浏览器或 sandbox 证据。
|
||||
- `~/.chrome/profile` 当前为活动浏览器 Profile(权限 775),不能被新 runtime 抢占或改写;`~/.config/google-chrome` 与 `~/.config/chromium` 为现有用户目录,也不能直接作为 CreatorHub 账号 Profile 根目录。
|
||||
- 已创建独立的 CreatorHub Profile 根目录:`/home/rogee/.local/share/creatorhub/browser-profiles`,权限 `rogee:rogee 0700`。每个账号/运行代次必须由 gateway 依据内部 ID 生成子目录并独占锁定;禁止请求方传入任意删除路径,禁止触碰现有用户 Profile。
|
||||
- 当前已有 `:99`/5900/9222 浏览器会话,证明不能假定固定 display、VNC 或 CDP 端口;runtime 必须做冲突安全分配。
|
||||
|
||||
## 已批准的单节点验证参数
|
||||
|
||||
用户已批准以下初始参数,复杂业务采集最大时长仍按具体场景单独配置,不因清理预算强行截断:
|
||||
|
||||
| 参数 | 初始值 |
|
||||
| --- | --- |
|
||||
| 短任务清理预算 `T_cleanup` | 30 秒 |
|
||||
| 服务恢复预算 `T_recover` | 60 秒 |
|
||||
| 验证并发 | 1(基线)/2(压力) |
|
||||
| runtime lease | 60 秒过期,20 秒续租 |
|
||||
| 最低可用磁盘 | 20 GB |
|
||||
| 单 runtime 日志上限 | 1 GB |
|
||||
| Profile 缓存上限 | 20 GB |
|
||||
| 业务任务最大时长 | 按场景配置;不设全局强制截断 |
|
||||
|
||||
## 仍需在后续真实验收时形成的证据(不阻塞本机代码实现)
|
||||
|
||||
1. gateway 使用固定 fingerprint 版本和指纹参数的持久化清单,以及浏览器升级对照记录。
|
||||
2. 真实代理端点/认证授权(若执行代理用例);直连只能作为明确的无代理配置,不得作为代理失败回退。
|
||||
3. 抖音测试账号、登录二维码、UID 身份核对及真实采集/写操作的逐项人工授权;不得把密码、Cookie、验证码或 token 写入记录。
|
||||
4. 旧 Docker 对照是否实际执行;没有可信旧基线时性能改善必须标记未验证。
|
||||
|
||||
## 结论
|
||||
|
||||
本机 Linux、非 root 用户、Python、systemd、Xvfb、磁盘、基础网络、已安装的 fingerprint Chromium sandbox 探针和独立 Profile 根目录已具备;用户已批准单节点验证参数,P0 放行本机单节点实现。真实平台写操作、代理用例和性能对照仍须在验收阶段按授权执行。不得切回 Docker、使用普通 Chrome 冒充 fingerprint browser、传入 `--no-sandbox`、抢占现有 Profile,或删除未确认的旧 Docker 资源。
|
||||
@@ -0,0 +1,20 @@
|
||||
# Native browser 旧 Docker 引用审计
|
||||
|
||||
- 日期:2026-09-18
|
||||
- 范围:仓库文本引用;排除 `.git`、`node_modules`、构建产物和 source map。
|
||||
|
||||
## 生产路径结果
|
||||
|
||||
`cmd/`、`internal/`、`web/src/` 未发现 `docker.sock`、`DockerClient`、浏览器容器/镜像/卷/网络生命周期、browser wrapper 或 Docker fallback 引用。
|
||||
|
||||
## 保留引用分类
|
||||
|
||||
| 文件 | 分类 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| `docs/plans/2026-09-15-douyin-release-remediation.md` | 历史计划 | 记录旧发布方案中的 wrapper/schema 风险,不是当前部署入口。 |
|
||||
| `docs/native-browser-change-review.md` | 迁移审计 | 对照记录旧 `cmd/docker_gateway` 和旧 wrapper,并明确当前已删除。 |
|
||||
| `docs/deployment_stop_test.sh` | 检查规则 | 以 `docker.sock` 作为禁止旧浏览器链路的负向断言。 |
|
||||
| `docs/native-browser-verification.md` | 验收规则 | 定义 production static audit 必须排除 Docker 浏览器生命周期。 |
|
||||
| `docs/python-gateway-branch-review.md` | 历史评审 | 记录迁移前分支的缺陷和旧设计,不是可执行代码。 |
|
||||
|
||||
审计结论:剩余引用均属于历史、审计或负向检查文档;未发现当前浏览器生产路径的隐式 Docker 回退。PostgreSQL 独立 Compose 资产不在本审计的删除范围内。
|
||||
@@ -0,0 +1,126 @@
|
||||
# 单节点 native browser 验证记录
|
||||
|
||||
- 执行时间:2026-09-18(Asia/Shanghai)
|
||||
- 执行者:本 worktree 的开发检查
|
||||
- worktree:`/home/rogee/Workspace/creator-hub-native-browser`
|
||||
- 分支:`refactor/native-browser-xvfb-impl`
|
||||
- 基线:`7e3808cf4ce453b3583079680a3b59ca2ed64ad4`
|
||||
- 远程同步:执行时 `main` 与 `origin/main` 均为 `7e3808cf4ce453b3583079680a3b59ca2ed64ad4`;当前 worktree 未改写原工作目录。
|
||||
|
||||
## 前置条件
|
||||
|
||||
| 项目 | 实际记录 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| Linux 用户 | `rogee`,UID 1000,非 root | 通过 |
|
||||
| Xvfb | `/usr/bin/Xvfb` | 通过 |
|
||||
| systemd | `systemd-run` 可用,`systemctl --user` 可用 | 通过 |
|
||||
| 浏览器 | fingerprint Chromium `148.0.7778.215`,可执行文件存在且可执行 | 通过 |
|
||||
| Profile/runtime | 使用专用临时目录;目录权限为 0700 | 通过 |
|
||||
| 数据库 | PostgreSQL `127.0.0.1:15432`;每次集成检查使用临时 schema | 通过 |
|
||||
| 真实平台账号/真实代理 | 用户已提供抖音账号并完成只读身份/作品读取;合法代理仍未提供 | 部分通过 |
|
||||
|
||||
## 自动检查
|
||||
|
||||
| 检查 | 实际结果 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| Go PostgreSQL 集成测试 | `go test -p 1 -count=1 ./...`,全部通过 | 通过 |
|
||||
| Go 覆盖率 | 总覆盖率 `65.01%`;creator `65.9%`、hub `65.8%`、phasea `68.1%` | 通过 |
|
||||
| `go vet ./...` | 通过 | 通过 |
|
||||
| control-plane 构建 | `go build ./cmd/control-plane`,通过 | 通过 |
|
||||
| Go race | `go test -race ./...`,全部通过 | 通过 |
|
||||
| Python gateway | 69 tests 通过;生产文件覆盖率 `67%–76%` | 通过 |
|
||||
| Frontend | 101 tests 通过;覆盖率 `66.31%`;Vite build 通过 | 通过 |
|
||||
| Compose 配置 | PostgreSQL-only Compose 配置检查通过 | 通过 |
|
||||
| 静态旧路径检查 | production gateway/control-plane 无 Docker socket、container/image/volume/network 生命周期或 Docker fallback | 通过 |
|
||||
|
||||
## Real native gateway smoke
|
||||
|
||||
### 正常生命周期
|
||||
|
||||
- 临时 gateway 以 UID 1000 启动,监听 `127.0.0.1:28183`,稳定 `node_id=native-smoke-node`。
|
||||
- `/healthz` 返回 `204`。
|
||||
- `/v1/info` 返回 `200`,列出浏览器版本 `148.0.7778.215`。
|
||||
- 创建 `smoke-runtime` 返回 `201`,`state=running`、`status=ready`、`ready=true`,返回独立 display、CDP 端口、runtime/network ID 和 `proxy_ready=true`。
|
||||
- stop 返回 `204`,delete 返回 `204`;查询结果为 `state=released`、`cleanup_state=cleaned`。
|
||||
- 清理后 `systemctl --user list-units 'creatorhub-browser-*'` 为 0;临时 smoke 目录已删除。
|
||||
|
||||
### gateway 强制重启恢复
|
||||
|
||||
- 临时 gateway 监听 `127.0.0.1:28184`,创建一个 ready runtime 后对 gateway 进程执行 `SIGKILL`。
|
||||
- 使用相同 state/profile 根目录和稳定 node ID 重启 gateway;重新查询得到同一 runtime,`state=running`、`ready=true`、`node_id=native-restart-node`。
|
||||
- stop/delete 均返回 `204`,清理后受管 systemd unit 数量为 0。
|
||||
|
||||
上述 smoke 使用专用临时目录;为避免测试盘空间限制,仅该 smoke 将 `RUNTIME_MIN_FREE_BYTES` 覆盖为 0,不能替代 20 GB 磁盘阈值用例。
|
||||
|
||||
## 局域网裸启动 smoke
|
||||
|
||||
- 使用临时 PostgreSQL schema 启动 control-plane `LISTEN_ADDR=0.0.0.0:28082`,`/healthz` 返回 `204`;使用 Vite `--host 0.0.0.0 --port 5175`,首页返回 `200`。
|
||||
- `ss` 确认两个监听分别为 `0.0.0.0:28082` 和 `0.0.0.0:5175`;本机 LAN 地址为 `10.1.1.104`,Vite 输出该地址。
|
||||
- smoke 结束后停止临时进程并删除临时 schema/目录;原工作目录中已存在的服务和端口未触碰。
|
||||
|
||||
## 33 项单节点记录
|
||||
|
||||
| ID | 记录结果 | 证据/阻塞原因 |
|
||||
| --- | --- | --- |
|
||||
| A01 | 通过 | 独立 native runtime 新 Profile 通过 VNC 手工登录;control plane 身份核对返回 `logged_in`,Profile 未接管用户的 `chrome.service` |
|
||||
| A02 | 阻塞 | 真实二维码、Profile 登录保持和版本升级未执行 |
|
||||
| A03 | 阻塞 | 缺少真实竞品账号与采集授权 |
|
||||
| A04 | 通过 | 独立 native runtime 通过 control plane scheduler 完成真实 Douyin works/comments checkpoint;当前账号无公开作品,保存 0 条,不伪造作品数据;证据见 `native-control-plane-real-douyin-2026-09-18.md` |
|
||||
| A05 | 阻塞 | 同节点双账号真实采集未执行 |
|
||||
| A06 | 阻塞 | 同 Profile 并发/人工登录占用未执行 |
|
||||
| A07 | 阻塞 | 长期监听与采集并行缺少真实账号 |
|
||||
| A08 | 阻塞 | 长期 holder 借用场景未执行 |
|
||||
| A09 | 阻塞 | 缺少合法 HTTP/HTTPS/SOCKS4/SOCKS5 代理 |
|
||||
| A10 | 阻塞 | 真实人工发送授权未提供 |
|
||||
| B01 | 阻塞 | 真实采集取消未执行;代码取消路径有回归测试 |
|
||||
| B02 | 阻塞 | 真实超时任务未执行 |
|
||||
| B03 | 阻塞 | 真实代理失败与无效 browser path 未逐项手工执行 |
|
||||
| B04 | 阻塞 | 真实浏览器/Xvfb 故障注入未执行 |
|
||||
| B05 | 阻塞 | 真实目录清理失败未执行 |
|
||||
| B06 | 阻塞 | 20 GB 阈值/磁盘写满未执行;smoke 覆盖了非阈值生命周期 |
|
||||
| B07 | 通过 | native stop/delete 重复清理回归测试和 smoke 通过 |
|
||||
| B08 | 通过 | gateway 强杀后 generation 恢复 smoke 通过 |
|
||||
| B09 | 阻塞 | 真实长任务续租及旧 lease 迟到未执行;确定性 lease 测试通过 |
|
||||
| B10 | 阻塞 | 真实结果保存失败/响应丢失未执行 |
|
||||
| C01 | 通过 | native gateway SIGKILL 后同 runtime/node/ready 恢复 smoke 通过 |
|
||||
| C02 | 阻塞 | 真实事件、冷却和订阅恢复未执行;事件去重/lease 自动测试通过 |
|
||||
| C03 | 阻塞 | 真实短任务最长寿命观察未执行 |
|
||||
| C04 | 阻塞 | control-plane 强杀期间素材处理未执行 |
|
||||
| C05 | 阻塞 | 真实旧执行与新执行竞争未执行;资源归属测试通过 |
|
||||
| C06 | 阻塞 | 真实机器重启未执行 |
|
||||
| C07 | 通过 | 旧 generation/无关资源保护有确定性回归测试;真实目录手工核对未执行 |
|
||||
| D01 | 未执行 | 多节点后续目标 |
|
||||
| D02 | 未执行 | 多节点后续目标 |
|
||||
| D03 | 未执行 | 多节点后续目标 |
|
||||
| D04 | 未执行 | 多节点后续目标 |
|
||||
| D05 | 未执行 | 多节点后续目标 |
|
||||
| D06 | 未执行 | 多节点后续目标 |
|
||||
|
||||
## 尚未执行/阻塞的真实验收
|
||||
|
||||
- Douyin 独立 native runtime 登录、身份核对、只读采集和空结果 checkpoint 已通过;Xiaohongshu、长期监听、自动响应、写操作仍未执行,分别因范围/授权/真实平台条件标记未执行或阻塞。
|
||||
- HTTP/HTTPS/SOCKS4/SOCKS5 真实出口及认证失败:缺少合法代理端点,阻塞。
|
||||
- 另一台设备的 LAN 手工访问与前端完整点击流程:未执行,需联调操作者完成;本机 `0.0.0.0` 裸启动 smoke 已通过。
|
||||
- 磁盘不足、真实 Profile 占用、清理失败和真实平台写操作:真实破坏性/写入场景未执行;确定性测试、真实 stop/repeat-stop/delete 和 runtime cleanup 已通过,不能把它们替代为全部真机故障证据。
|
||||
- 多节点、A/B、跨机恢复、性能对比:按目标边界延期,不属于本目标。
|
||||
|
||||
本记录没有保存密码、Cookie、token、真实账号标识或平台数据。
|
||||
|
||||
## 补充:用户授权的 service-managed Chrome 真实读取
|
||||
|
||||
初始记录完成后,用户授权使用当前由 `chrome.service` 管理的 Chrome,仅通过本机 CDP `127.0.0.1:9222` 连接测试交互;没有让 native gateway 接管该 Chrome,也没有改变原有 `:99` Xvfb/VNC 启动方式。详细记录见 [`native-douyin-service-browser-2026-09-18.md`](native-douyin-service-browser-2026-09-18.md)。
|
||||
|
||||
- 抖音身份核对:通过,用户提供的账号标识与平台返回 UID 匹配。
|
||||
- 抖音资料读取:通过,UID 与 `sec_uid` 两种查询均返回 `status_code=0`。
|
||||
- 抖音作品第一页读取:通过,返回 13 条作品并标记存在下一页。
|
||||
- 独立 native runtime 的 control-plane works/comments checkpoint 已通过;自动响应、人工发送、代理和真实资源故障场景仍未执行,不能用只读读取结果替代。
|
||||
- 代理阻塞确认:2026-09-18 用户确认不提供合法 HTTP/HTTPS/SOCKS4/SOCKS5 代理端点;相关用例继续标记为“阻塞”,不宣称单节点目标完成。
|
||||
|
||||
## 补充:变更后自动检查复跑
|
||||
|
||||
- Python gateway:69 tests 通过;生产文件覆盖率分别为 `douyin.py 67%`、`gateway.py 68%`、`proxy.py 68%`、`runtime.py 76%`,均达到 65% 要求。
|
||||
- Go:`go test -p 1 -count=1 ./...`、`go test -race -p 1 -count=1 ./...`、`go vet ./...`、`go build ./cmd/control-plane` 通过。
|
||||
- Frontend:Vitest 101 tests 通过;coverage statements `66.31%`、lines `68.73%`;Vite build 通过。默认 worker 在机器高负载时曾启动超时,改用单 worker threads pool 后通过,未修改测试代码。
|
||||
- 部署:`git diff --check`、`docs/deployment_stop_test.sh` 通过。
|
||||
- 外部显示人工观察:临时 native gateway 复用已运行的 Xvfb `:99`,runtime 在 VNC `5900` 可见;停止/删除后恢复用户的 `chrome.service`,原 Profile 未被覆盖。该模式仅用于单节点人工观察,默认仍是每 runtime 独立 Xvfb。
|
||||
- 独立 native gateway + control plane 的真实抖音闭环记录见 [`native-control-plane-real-douyin-2026-09-18.md`](native-control-plane-real-douyin-2026-09-18.md):runtime 启停、身份核对、works/comments checkpoint 均已通过;当前账号无公开作品,因此保存 0 条作品,不把空结果伪造为有数据。
|
||||
@@ -0,0 +1,29 @@
|
||||
# Native gateway + control plane 抖音单节点真实闭环
|
||||
|
||||
- 日期:2026-09-18
|
||||
- 验证范围:独立 native gateway、独立 Profile、Xvfb、control plane、抖音真实账号只读采集。
|
||||
- 用户边界:未停止或接管用户当前 `chrome.service`、原 Profile、`:99` Xvfb 或 VNC `5900`。
|
||||
- 独立 control plane:`0.0.0.0:28082`,使用隔离 PostgreSQL schema。
|
||||
- 独立 gateway:`127.0.0.1:28187`,display `:122`,CDP `19000`,Profile 为独立目录。
|
||||
|
||||
## 结果
|
||||
|
||||
| 检查 | 结果 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| control plane 创建 gateway/browser version/account/environment | API 返回成功;native browser version 使用 fingerprint Chromium `148.0.7778.215` | 通过 |
|
||||
| 显式启停 | `POST /api/browsers/independent-douyin/start` 返回 204;runtime 保持 running/ready | 通过 |
|
||||
| runtime 身份绑定 | control plane 记录 runtime ID、network ID、runtime instance 和 ready schedule | 通过 |
|
||||
| 抖音账号核对 | control plane verify 返回 `logged_in`;账号标识不写入仓库 | 通过 |
|
||||
| 抖音作品采集 | control plane scheduler 调用 native gateway,作品页返回 `status_code=0`;当前账号无公开作品,保存 0 条作品 | 通过 |
|
||||
| 采集 checkpoint | works 与 comments 均为 `succeeded`,写入 `last_completed_at`,无错误 | 通过 |
|
||||
| 资源状态 | runtime 运行期间为 running/ready,gateway 与 control plane 的 runtime lease 状态可见 | 通过 |
|
||||
| 资源释放 | control plane stop 返回 204;重复 stop 返回 204;删除环境返回 204;停止后 cleanup_pending=false | 通过 |
|
||||
|
||||
## 实施中发现并修复的问题
|
||||
|
||||
1. control plane scheduler 与手工启动同时绑定同一 runtime 时,旧逻辑会把并发胜出的 runtime 清理掉;`activateGatewayRuntime` 现会识别“同一 generation 已被另一执行者绑定”的情况并复用它。新增回归测试覆盖该竞态。
|
||||
2. fingerprint Chromium 在 Xvfb 下需要 `--disable-gpu` 与 `--disable-gpu-compositing`;否则 GPU 子进程会退出并造成 runtime 假 ready。启动命令已补齐参数。
|
||||
3. 抖音真实 works 接口对 `has_more` 返回数字 `0/1`;connector 现同时接受 JSON boolean 与 `0/1`,并新增解析测试。
|
||||
4. 第一次真实采集曾因身份核对与 scheduler 同时占用 alias 返回 `browser alias is busy`;该次被记录为失败并重试,未被伪造为成功。
|
||||
|
||||
原始账号标识、Cookie、密码、gateway token 和数据库凭据未写入仓库。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 抖音真实浏览器连接验证记录
|
||||
|
||||
- 日期:2026-09-18(Asia/Shanghai)
|
||||
- worktree:`/home/rogee/Workspace/creator-hub-native-browser`
|
||||
- 浏览器归属:用户明确要求由 `chrome.service` 管理;本次不停止、不启动、不删除该 Chrome,仅通过本机 CDP 连接测试交互。
|
||||
- 显示:原有 Xvfb `:99` 与 VNC `5900` 保持不变。
|
||||
- CDP:`127.0.0.1:9222`。
|
||||
- 浏览器版本:fingerprint Chromium `148.0.7778.215`。
|
||||
|
||||
## 结果
|
||||
|
||||
| 检查 | 结果 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| 抖音登录身份核对 | 用户提供的授权账号标识与 `/user/profile/self/` 返回的 UID 匹配;返回 `sec_uid`、昵称和平台时间 | 通过 |
|
||||
| 抖音账号资料读取 | `/user/profile/other/` 使用 UID 与 `sec_uid` 均返回 HTTP 200、`status_code=0`、包含用户资料 | 通过 |
|
||||
| 抖音作品第一页读取 | `/aweme/post/` 返回 HTTP 200、`status_code=0`、13 条作品、`has_more=1`,存在下一页游标 | 通过 |
|
||||
| 写操作/自动响应 | 本次未执行 | 未执行 |
|
||||
| 结果持久化 | 本次仅验证真实浏览器与平台读取,未伪造控制面持久化成功 | 未执行 |
|
||||
|
||||
原始账号标识、Cookie、密码和 token 不写入仓库。native gateway 临时 runtime 已停止并删除;用户的 `chrome.service` 已恢复运行。
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
## 1. 状态与结论
|
||||
|
||||
- 日期:2026-09-17。
|
||||
- 评审基线:`main` / `1f63b5b`,以及评审时工作区的现有未提交改动;不能将本文当作该提交单独具备的能力证明。
|
||||
- 用户已确认:本轮只交付评审、实施计划与验证文档,不修改业务代码。
|
||||
- 日期:2026-09-18。
|
||||
- 评审基线:已同步的 `main` / `origin/main` / `7e3808cf4ce453b3583079680a3b59ca2ed64ad4`;评审时的旧 Docker 入口仅作为历史对照,不能将本文当作真实平台能力证明。
|
||||
- 评审阶段的契约已在独立实施 worktree 执行。当前状态以实施计划、验证文档和证据记录为准。
|
||||
- **方向成立,但不是把 Docker 启动命令换成 Xvfb 命令即可。**必须同时替换进程生命周期、运行代次、Profile 路径、端口分配、代理接入及版本管理。
|
||||
- 下文“目标/推荐”是待实施方案,不表示已实现或已通过真机验证。详细执行顺序见[实施计划](native-browser-implementation-plan.md),验收见[验证文档](native-browser-verification.md)。
|
||||
- 下文保留必要的风险、边界和历史对照;不表示真实平台、真实代理或 LAN 已全部验收。详细实现范围见[实施计划](native-browser-implementation-plan.md),验收见[验证文档](native-browser-verification.md)。
|
||||
|
||||
### 已确认的需求
|
||||
|
||||
@@ -29,19 +29,19 @@
|
||||
|
||||
| 现有事实/入口 | 对本次变更的影响 |
|
||||
| --- | --- |
|
||||
| `internal/hub/store.go` 的 `Gateway` 保存名称、Endpoint;`Env` 保存 `Gateway`、`ImageVersion` | 多机器注册与路由已存在,应复用,不新增第二套节点管理 |
|
||||
| `internal/hub/store.go` 的 `Gateway` 保存名称、Endpoint;`Env` 保存 `Gateway`、`BrowserVersion` | 多机器注册与路由已存在,应复用,不新增第二套节点管理 |
|
||||
| `cmd/control-plane/hub.go` 的 `startBrowserRuntime`、`createGatewayRuntime`、`removeGatewayRuntime` 管理启停与清理 | 必须整体改造生命周期,不能仅替换 Python 中一个 Docker 调用 |
|
||||
| `gatewayCreatePayload` 下发 `creatorhub-profile-{alias}` 卷名、指纹、代理和绑定版本 | 卷改成 gateway 管理的持久目录;控制面不能下发任意宿主机路径 |
|
||||
| `runtimeCleanupStore` 及启动前清理逻辑已有 cleanup-pending 概念 | 保留“未清理完成不能冒充已停止”的语义,改用本机资源身份,不再依赖容器/网络 ID |
|
||||
| `cmd/docker_gateway/gateway.py` 初始化 `DockerClient`;`load_config` 读取 `DOCKER_SOCKET`、`BROWSER_NETWORK` | 当前正常入口仍依赖 Docker。外部 CDP 配置并不等于完整的原生浏览器生命周期 |
|
||||
| `docker/browser-wrapper/docker-entrypoint.sh` 包含 Xvfb、x11vnc、socat、Chromium,固定 `:99` 和容器内端口,Profile 为 `/data` | 已有容器内 Xvfb 脚本,但 gateway 强制入口与 wrapper 入口不一致,不能证明实际镜像执行了它。宿主机并发不能复用固定 display/端口 |
|
||||
| 同一脚本传入 `--no-sandbox` | **当前脚本并未启用 Chromium sandbox。**不能以“浏览器本身就是沙盒”解释现有安全边界或直接照搬启动参数 |
|
||||
| 历史 `cmd/docker_gateway/gateway.py` 曾初始化 `DockerClient` 并读取 `DOCKER_SOCKET`、`BROWSER_NETWORK` | 已由 `cmd/browser_gateway/gateway.py` 的 native runtime 管理替换;当前浏览器入口不依赖 Docker。该行只保留迁移前事实 |
|
||||
| 历史 `docker/browser-wrapper/docker-entrypoint.sh` 曾固定容器内 Xvfb/端口/Profile | 浏览器 wrapper 已从生产入口删除;native gateway 为每个 runtime 独立分配 display、端口和 Profile,历史行不构成当前能力证明 |
|
||||
| 历史 wrapper 曾传入 `--no-sandbox` | native gateway 拒绝该参数并以非 root 用户启动;无法满足 sandbox 时直接失败,不回退旧 wrapper |
|
||||
| `cmd/control-plane/creator.go` 的采集读取已有运行账号,没有按任务自动创建 runtime;`creator_events.go` 独立运行监听 | 需要新增任务使用权;现有 source lease 不是浏览器使用权,直接在采集末尾调用 stop 会误停共享会话 |
|
||||
| `creator.go` 的临时下载、`creator_material.go` 的 `audio.wav.tmp` 依赖正常退出删除;素材先写固定路径再提交数据库 token | 控制面本机也需要崩溃清理;旧执行可能覆盖新产物,必须先隔离执行目录与产物发布,不能只增加目录扫描 |
|
||||
| `internal/creator/source_lease.go` / `content.go` 的来源 lease 为固定十分钟;部分失败收尾仍使用已取消的 context | 长任务可能重复领取,取消后仍显示 running;资源使用权必须覆盖真实执行期并有独立收尾上下文 |
|
||||
| `internal/hub/store.go` 的 gateway endpoint 可更新,环境按 gateway 名称重新解析地址 | 不能把名称当稳定机器身份;原 owner 和待清理目标必须保留,禁止把仍有绑定资源的 gateway 改指另一机器 |
|
||||
| `internal/hub/store.go` 的 `Image`、前端镜像页、指纹/版本字段依赖 Docker 镜像语义 | 浏览器版本来源必须替换;只改界面名称会留下无效配置 |
|
||||
| `scripts/dev-backend.mjs` / `compose*.yaml` 启动 Docker gateway | 联调脚本和部署说明必须同步;不能让“原生模式”后台仍偷偷启动旧 gateway |
|
||||
| `scripts/dev-backend.mjs` / `compose*.yaml` 现在只保留 PostgreSQL(Compose)和 host-native gateway 注册 | 联调脚本和部署说明已同步;原生模式不启动 Docker browser gateway |
|
||||
| `requirements-gateway.lock` 当前锁定 `websocket-client` | 优先保留已有 CDP/WebSocket 与平台适配器,不因取消 Docker 就新增 Playwright/Patchright |
|
||||
|
||||
## 3. 方案选择
|
||||
@@ -143,21 +143,21 @@ Xvfb 只负责显示;gateway 负责启动、就绪确认、终止、回收与
|
||||
|
||||
## 6. 改造评审结论与放行项
|
||||
|
||||
**建议采用:每台 gateway 管理本机独立 Xvfb/runtime,保留账号 Profile,采集结束回收任务资源,复用现有多机路由及业务层。**先形成一个可手工验收的抖音端到端切片,再完成多机故障与全业务回归。
|
||||
**已采用:每台 gateway 管理本机独立 Xvfb/runtime,保留账号 Profile,采集结束回收任务资源,复用现有多机路由及业务层。**单节点代码、自动检查和 native gateway smoke 已完成;真实账号、代理、LAN、多节点和性能验收按验证文档分开记录。
|
||||
|
||||
代码实施前须确认:
|
||||
|
||||
- [ ] 目标机器为满足 systemd、Xvfb、sandbox 条件的 Linux,原生指纹浏览器可合法部署且能力相同。
|
||||
- [ ] 接受删除 Docker 浏览器代码路径、镜像字段/接口及相关部署配置;开发数据可以受控重建,但删除用户数据仍需逐次授权。
|
||||
- [ ] 确认 Profile 不随采集删除、机器不自动切换、任务不得抢占人工登录/长期监听;gateway 重启按独立 runtime 核对恢复,机器重启不自动恢复已过期短任务。
|
||||
- [ ] 确认本次不把完整网页远程桌面当作已有能力;若要求补齐,应另列明确验收范围。
|
||||
- [ ] 批准[验证文档](native-browser-verification.md)中的资源预算与性能放行标准;没有旧基线时不能宣称已降低开销。
|
||||
- [x] 目标机器为满足 systemd、Xvfb、sandbox 条件的 Linux,原生指纹浏览器可合法部署且能力相同;已完成非 root native smoke。
|
||||
- [x] 已接受删除 Docker 浏览器代码路径、镜像字段/接口及相关部署配置;开发数据可受控重建,用户数据删除仍需逐次授权。
|
||||
- [x] Profile 不随采集删除、机器不自动切换、任务不得抢占人工登录/长期监听;gateway 重启按独立 runtime 核对恢复。
|
||||
- [x] 本次不把完整网页远程桌面当作已有能力;若要求补齐,另列明确验收范围。
|
||||
- [x] 验证文档中的资源预算已固定;性能改善和真实平台能力未以无旧基线的推测代替实测。
|
||||
|
||||
以上是实施放行清单,不是本轮要求用户补齐运行环境后才能拿到文档。
|
||||
以上是已执行的实施放行记录;真实平台、代理、LAN 和破坏性资源用例仍需授权操作者按验证文档补齐,未执行项不视为通过。
|
||||
|
||||
## 7. 专项评审处理记录
|
||||
|
||||
本轮进行了两项独立只读代码评审:gateway/多机生命周期,以及采集/素材资源清理。它们审查的是现有实现与改造风险,不是新实现的测试通过证明;本方案由主审汇总,仍待用户批准实施。
|
||||
本轮进行了两项独立只读代码评审:gateway/多机生命周期,以及采集/素材资源清理。它们审查的是实现与改造风险,不是新实现的真实平台证明;自动检查和 native smoke 已记录,真实手工验收仍按验证文档执行。
|
||||
|
||||
| 发现 | 处理 |
|
||||
| --- | --- |
|
||||
@@ -168,7 +168,7 @@ Xvfb 只负责显示;gateway 负责启动、就绪确认、终止、回收与
|
||||
| gateway 名称/Endpoint 可变,旧清理可能指向新机器 | 固定节点身份与资源 owner,阻止有资源时更换机器 |
|
||||
| 中央出口检查不能证明远端浏览器出网正确 | 从 A/B 实际执行路径分别验证,不复用中央结果冒充 |
|
||||
|
||||
尚待 P0 补齐的不是代码小修,而是原生浏览器发行物、目标机器条件、契约批准和性能基线。未执行真实平台或故障测试的项目全部保留为未验证。
|
||||
P0 的发行物、目标机器条件、契约和资源参数已记录;性能基线以及未执行的真实平台/代理/故障测试仍全部保留为未验证。
|
||||
|
||||
## 8. 成熟方案参考与证据边界
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# 原生浏览器环境:评审后的实施计划
|
||||
|
||||
> 状态:待批准实施。仅文档,本轮未修改业务代码、数据库或运行环境。
|
||||
> 依据:[变更评审](native-browser-change-review.md)、[业务基线](plan01.md)。验收:[验证文档](native-browser-verification.md)。
|
||||
> 状态:实施 worktree 已按本计划完成 native gateway、控制面契约、runtime-use lease、前端和单机部署代码改造;自动检查与真实平台/代理/LAN 验收分开记录,缺少授权或真实资源时标为阻塞。
|
||||
> 依据:[变更评审](native-browser-change-review.md)、[业务基线](plan01.md)。验收:[验证文档](native-browser-verification.md);记录:[单节点验证记录](evidence/native-browser-verification-2026-09-18.md)。
|
||||
|
||||
## 1. 完成定义
|
||||
|
||||
满足以下条件才算完成改造,而不是“可以启动 Chromium”就结束:
|
||||
|
||||
- 各 gateway 不访问 Docker socket,不创建浏览器容器、镜像层、卷或网络;每个 runtime 在本机运行 Xvfb 与指定版本浏览器。
|
||||
- 通过现有控制面管理至少两台机器;账号、运行代次、代理、Profile 与实际节点一致,不自动跨机重建。
|
||||
- 本目标先通过现有控制面管理单台机器;账号、运行代次、代理、Profile 与实际节点一致,不自动跨机重建。多节点仅保留稳定契约字段,另立目标。
|
||||
- 采集任务可按需启停;任务自建进程和临时文件在所有终态回收。清理失败真实可见并可重试。
|
||||
- 持久登录、Profile、稳定指纹及账号身份在重启和普通浏览器升级后保持;二维码登录、采集结果及逐次确认的人工发送不退化(A02、A10;AC-E1)。
|
||||
- gateway、控制面重启及监听重连不清除自动响应的事件去重、基线或 UID 冷却;旧事件不重放,迟到事件默认只记录、不自动补发(C02、D04;AC-A9、AC-B1)。
|
||||
@@ -20,11 +20,10 @@
|
||||
|
||||
```text
|
||||
React → Go control-plane(账号/任务/绑定/业务结果/素材发布)
|
||||
├─ gateway A(已有节点登记与路由)
|
||||
│ ├─ 预安装浏览器版本(所有任务共用只读二进制)
|
||||
│ ├─ 账号 A 的持久 Profile
|
||||
│ └─ runtime generation:Xvfb + 浏览器 + 临时目录
|
||||
└─ gateway B(同样结构,独立本地资源)
|
||||
└─ native gateway G(单节点;保留后续节点登记契约)
|
||||
├─ 预安装浏览器版本(所有任务共用只读二进制)
|
||||
├─ 账号的持久 Profile
|
||||
└─ runtime generation:Xvfb + 浏览器 + 临时目录
|
||||
```
|
||||
|
||||
- 继续使用 Go 控制面、Python gateway、PostgreSQL 与现有 CDP/WebSocket。
|
||||
@@ -35,7 +34,7 @@ React → Go control-plane(账号/任务/绑定/业务结果/素材发布)
|
||||
|
||||
## 3. 必要数据和接口变化
|
||||
|
||||
以下为**契约变更提案**,不是已存在的新字段/API。P0 批准后才能实施。
|
||||
以下为本实施 worktree 已采用的**单节点契约**;多节点字段保留稳定命名,但跨机调度与恢复不在本目标内。
|
||||
|
||||
| 概念 | 变更 | 约束 |
|
||||
| --- | --- | --- |
|
||||
@@ -65,8 +64,8 @@ React → Go control-plane(账号/任务/绑定/业务结果/素材发布)
|
||||
交付:
|
||||
|
||||
1. 确认当前未提交工作由谁负责,将基线固定到可复现提交;在干净、明确基线之上新建实施分支,不擅自 stash/reset 用户改动。
|
||||
2. 核实两台 Linux 机器的 systemd、用户服务、Xvfb、字体、浏览器依赖、sandbox、可用磁盘;取得合法原生指纹浏览器并固定版本,指定用于 A02 普通升级验证的源版本与目标版本。
|
||||
3. 记录旧方式性能/磁盘基线。默认不运行 Docker;如无历史可信数据,需要用户另行批准旧方式对照运行。
|
||||
2. 核实单台 Linux 机器的 systemd、用户服务、Xvfb、字体、浏览器依赖、sandbox、可用磁盘;取得合法原生指纹浏览器并固定版本,指定用于 A02 普通升级验证的源版本与目标版本。
|
||||
3. 记录可用的旧方式性能/磁盘基线。默认不运行 Docker;如无历史可信数据,性能改善明确标记未验证,不用新建旧浏览器对照冒充基线。
|
||||
4. 列出受影响状态码、载荷、字段、配置和移除项,批准第三节契约。明确 gateway 重启后受管 runtime 的恢复行为。
|
||||
5. 固定验证参数:任务最长时间、清理时间预算、任务使用权续租/过期时间、磁盘下限、并发数、日志保留及资源对比指标。
|
||||
|
||||
@@ -127,23 +126,23 @@ React → Go control-plane(账号/任务/绑定/业务结果/素材发布)
|
||||
- 扩展 P2 的 Refine data provider 和交互测试,覆盖 P3 完整资源归属及多节点场景;保留业务结果、清理状态、错误和禁用原因分别可见,不把首个闭环必需的界面工作留到本阶段。
|
||||
- 增加可重复部署的 gateway/原生 runner 用户服务配置和本机浏览器安装说明。跨重启保留节点 ID、Profile、版本配置和运行清单。
|
||||
- 若 runtime 允许跨 gateway 重启存活,其临时目录必须由 runtime unit 归属,不能放在 gateway 重启就被 systemd 删除的 `RuntimeDirectory` 中。大型缓存/下载明确使用配置的数据磁盘,不因“临时”二字默认放进 `/run` 的 tmpfs 而转为大量内存占用。
|
||||
- 修改 `scripts/dev-backend.mjs`、开发脚本、Dockerfile/Compose 的旧 gateway 相关配置;默认裸启动 Go/Python/Vite,不悄悄 `compose up docker-gateway`。
|
||||
- 修改 `scripts/dev-backend.mjs`、开发脚本、Dockerfile/Compose 的旧 gateway 相关配置;默认裸启动 Go/Python/Vite,不悄悄启动 Docker browser gateway。
|
||||
- 删除 Docker browser wrapper、拉镜像/构建/网络/卷管理及相关测试、配置;PostgreSQL 等仍使用 Docker 的独立部署资产可保留,逐项说明,不做无关清理。
|
||||
- `AGENTS.md`、README、部署说明、架构说明、E2E 文档全部对齐。删除旧入口,不保留“失败就回退 Docker”。
|
||||
|
||||
放行:验证文档全部必测项有证据;静态检查中浏览器链路没有 Docker 依赖;两节点局域网手工验收通过。
|
||||
放行:验证文档全部适用项有证据;静态检查中浏览器链路没有 Docker 依赖;单节点局域网手工验收通过。多节点项记录为后续目标。
|
||||
|
||||
## 5. 改动定位与顺序约束
|
||||
|
||||
| 范围 | 主要定位 | 必须一起变化的内容 |
|
||||
| --- | --- | --- |
|
||||
| gateway 执行 | `cmd/docker_gateway/gateway.py`、`proxy.py`、CDP/平台模块及其测试 | 原生 runner、版本/端口/Profile、unit 生命周期、snapshot、代理恢复 |
|
||||
| gateway 执行 | `cmd/browser_gateway/gateway.py`、`runtime.py`、`proxy.py`、CDP/平台模块及其测试 | 原生 runner、版本/端口/Profile、unit 生命周期、snapshot、代理恢复 |
|
||||
| 控制面 lifecycle | `cmd/control-plane/hub.go`、`main.go`、hub tests | generation、owner、清理/心跳、多 gateway 错误隔离 |
|
||||
| 采集与监听 | `creator.go`、`creator_events.go`、`internal/creator/source_lease.go`、`content.go` | 使用权、续租、独立收尾、事件边界与身份核对 |
|
||||
| 素材 | `creator_material.go`、`internal/creator/material.go`、素材测试 | 临时目录、不可变发布、token、崩溃清理 |
|
||||
| 数据模型 | `internal/hub/`、`internal/creator/` | 删除 Docker 字段,明确开发重建;业务结果与清理状态分离 |
|
||||
| 前端 | 现有 gateway/环境/镜像/任务相关组件及 data provider | 对应契约、失败/禁用交互;不重做导航和视觉体系 |
|
||||
| 部署/文档 | `scripts/`、`compose*.yaml`、Dockerfile、`docker/browser-wrapper/`、README、`docs/` | 原生安装和联调;旧路径移除;手工验收地址 |
|
||||
| 部署/文档 | `scripts/`、`compose*.yaml`、Dockerfile、`deploy/`、README、`docs/` | 原生安装和联调;旧路径移除;手工验收地址 |
|
||||
|
||||
顺序限制:产物执行隔离先于孤儿文件清理;本地单任务闭环先于多机压测;所有权和代次先于通用自动清理;不能先删 Docker 实现再留下无法登录的中间交付。
|
||||
|
||||
@@ -151,9 +150,9 @@ React → Go control-plane(账号/任务/绑定/业务结果/素材发布)
|
||||
|
||||
- 每个非平凡行为改动先有会在旧实现下失败的测试;单元覆盖率至少 65%。Go 全量测试、vet、build,生命周期/并发相关变更跑 race;Python 非交互测试与覆盖率;前端从 lockfile 安装并测试、构建。
|
||||
- 更改 Docker/Compose 文件执行 `docker compose config --quiet`;不默认构建/运行任何 Docker 镜像。旧基线测试需另行得到用户同意。
|
||||
- 功能验收不使用浏览器自动化或批量 API 代替用户操作。提供 `0.0.0.0` 监听的本地环境和两台 gateway 地址,由用户按验证文档手工确认。
|
||||
- 功能验收不使用浏览器自动化或批量 API 代替用户操作。提供 `0.0.0.0` 监听的本地环境和单节点 gateway 地址,由用户按验证文档手工确认。
|
||||
- 资源观测、日志读取和单元测试可以自动执行,但不能因此将真实登录、采集、发送或多机故障验收标为通过。
|
||||
- 本轮不会启动服务、重建数据库或删除遗留资源。实施完成后才启动可联调环境,并报告实测局域网地址。
|
||||
- 本文不把服务启动、数据库重建或遗留资源删除当作自动交付动作;需要真实联调时按部署和验证文档由授权操作者启动,并报告当次局域网地址。
|
||||
|
||||
## 7. 切换与失败恢复
|
||||
|
||||
@@ -163,13 +162,13 @@ React → Go control-plane(账号/任务/绑定/业务结果/素材发布)
|
||||
4. 如新环境验收失败,停止新任务、保留证据、修复再验。紧急恢复旧版只能是人工恢复到明确版本和匹配数据,须取得授权;不是运行时隐式回退。
|
||||
5. 验收后,经授权回收旧浏览器容器/卷/网络与镜像。只删除确认属于旧 CreatorHub 浏览器的资源,禁止全机 `docker system prune` 或按相似名称批量删除。
|
||||
|
||||
## 8. 实施前批准清单
|
||||
## 8. 实施与验收记录
|
||||
|
||||
- [ ] Linux/systemd 与原生指纹浏览器部署条件满足。
|
||||
- [ ] 独立 runtime 跨 gateway 重启核对恢复、短任务有界存活策略获准。
|
||||
- [ ] 账号 Profile 保留、采集独占/借用边界、无自动跨机迁移获准。
|
||||
- [ ] API/schema/配置的破坏性变更及数据重建范围获准。
|
||||
- [ ] 明确完整网页远程桌面是否另立需求,不能把 wrapper 内有 x11vnc 当已具备该功能。
|
||||
- [ ] 验证参数、旧方式对照是否允许运行 Docker、实测性能放行标准获准。
|
||||
- [x] Linux/systemd、Xvfb 与原生指纹浏览器部署条件满足;非 root gateway 已完成 smoke。
|
||||
- [x] 独立 runtime 的 gateway 重启恢复和短任务有界清理已通过自动/真实 smoke;正式参数为租约 60 秒、续租 20 秒、清理 30 秒、恢复 60 秒。
|
||||
- [x] 账号 Profile 保留、采集独占/借用边界、无自动跨机迁移已写入契约;同 Profile 真实并发仍需授权操作者手工验收。
|
||||
- [x] API/schema/配置的破坏性开发切换范围已固定;不保留旧 Docker 浏览器兼容路径。
|
||||
- [x] 完整网页远程桌面未作为本目标能力;需要时另立需求,不能把 gateway 的 Xvfb 误称为远程桌面。
|
||||
- [x] 20 GB 磁盘下限、1 GB runtime 日志、20 GB Profile 缓存、并发 1/2 和 `0.0.0.0:8082` 已配置并记录。
|
||||
|
||||
在上述批准之前,这份文档只作为实施依据,不构成已经完成的改造。
|
||||
自动检查结果见 [单节点验证记录](evidence/native-browser-verification-2026-09-18.md)。真实账号、代理、LAN 和破坏性资源用例仍须由授权操作者完成;未执行项不视为通过。
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
# 原生浏览器环境:验证与手工验收
|
||||
|
||||
> **当前状态:仅验证计划,下面所有功能、故障、性能用例均未执行。**
|
||||
> 本轮未构建镜像、启动服务、操作真实账号、终止进程或删除数据。
|
||||
> 方案:[变更评审](native-browser-change-review.md);阶段:[实施计划](native-browser-implementation-plan.md)。
|
||||
> **当前状态:单节点代码改造与自动检查通过;真实平台、代理、LAN 和资源验收仍须由授权操作者手工完成。**
|
||||
> 已完成 native gateway、控制面契约、runtime-use lease、Compose/P4 文档和本地单元测试;本文件只记录可复现的验收步骤,不把测试替身当作真实平台证据。
|
||||
> 方案:[变更评审](native-browser-change-review.md);阶段:[实施计划](native-browser-implementation-plan.md);当次记录:[单节点验证记录](evidence/native-browser-verification-2026-09-18.md)。
|
||||
|
||||
## 1. 验证边界
|
||||
|
||||
- 开发者负责单元/契约检查、构建和启动可联调环境;真实功能由用户手工验证,不使用浏览器自动化或脚本代点网页。
|
||||
- 读取日志、目录占用、进程/端口和系统指标可以使用命令。自动测试中的替身只证明代码行为,不能证明平台登录、监听、发送或真实代理能力。
|
||||
- 默认不构建/运行 Docker。旧方式对照、旧资源删除、故障注入必须单独取得用户同意。
|
||||
- 默认不构建/运行 Docker;Compose 仅可作为 PostgreSQL 独立依赖。旧方式对照、资源删除和故障注入必须单独取得用户同意。
|
||||
- 测试专用账号、Profile、gateway、数据目录及出口;不能强杀用户正在使用的长期账号或清空全机 Docker 资源。
|
||||
- 任何结果使用“未执行 / 通过 / 失败 / 阻塞”,不得把文档中的期望填成实际结果。
|
||||
- 若被测 Chrome 由用户的 `chrome.service` 管理,验证只能通过其已开放的 CDP 连接执行只读/交互检查,不得停止、启动、替换或接管该实例;这类结果不能替代 native gateway 的 runtime 生命周期、控制面持久化和资源清理验收。
|
||||
|
||||
## 2. 前置条件和记录表
|
||||
|
||||
@@ -19,21 +20,21 @@
|
||||
| 项目 | 必填记录 |
|
||||
| --- | --- |
|
||||
| 软件基线 | Git 提交、未提交改动清单、Go/Python/Node/systemd/Xvfb/浏览器版本 |
|
||||
| 机器 | 控制面 C、gateway A、gateway B 的稳定节点 ID、IP、CPU、内存、磁盘、发行版 |
|
||||
| 机器 | 单节点控制面 C 与 native gateway G 的稳定节点 ID、IP、CPU、内存、磁盘、发行版 |
|
||||
| 账号 | 专用采集账号、自有监听账号;预期平台 UID,勿记录密码/Cookie |
|
||||
| 浏览器 | 原生发行物来源、许可证、校验值、支持的指纹参数和 sandbox 证据;A02 普通升级的已安装源版本/目标版本 |
|
||||
| 路径 | 各机器 Profile 根目录、runtime 根目录、运行清单、日志、控制面素材目录 |
|
||||
| 网络 | A/B 各节点的 HTTP/HTTPS/SOCKS4/SOCKS5 测试出口、协议支持的认证配置、预期出口 IP、CDP 可用情况;只记录配置标识,不记录密码 |
|
||||
| 网络 | 单节点的 HTTP/HTTPS/SOCKS4/SOCKS5 测试出口、协议支持的认证配置、预期出口 IP、CDP 可用情况;只记录配置标识,不记录密码 |
|
||||
| 操作许可 | 是否允许旧 Docker 对照、普通浏览器升级、gateway/控制面重启、任务取消、磁盘故障和测试数据删除;C02/D04 自动响应的测试策略、可控互动/接收账号及授权范围,A10 人工发送另行逐次确认 |
|
||||
| 时限 | task 最大执行时间、续租间隔/过期时间、清理预算 `T_cleanup`、恢复预算 `T_recover` |
|
||||
| 资源预算 | 并发数、最低可用磁盘、日志上限;启动 p50/p95 和总内存目标,Profile 缓存预算 |
|
||||
| 时限 | task 最大执行时间、续租间隔 20 秒、租约 60 秒、清理预算 30 秒、恢复预算 60 秒 |
|
||||
| 资源预算 | 并发数 1/2、最低可用磁盘 20 GB、日志上限 1 GB;启动 p50/p95 和总内存目标,Profile 缓存预算 20 GB |
|
||||
| 证据位置 | 独立于 runtime 清理目录的本地证据目录,不提交账号敏感数据 |
|
||||
|
||||
建议首轮使用并发 1 和 2、短任务正常清理预算 30 秒、服务恢复预算 60 秒;这些是待批准的测试参数,不是已实现默认配置。复杂平台采集的最大时长单独确定,不能因为清理预算而强行截断正常业务。
|
||||
首轮使用并发 1 和 2、短任务正常清理预算 30 秒、服务恢复预算 60 秒。复杂平台采集的最大时长单独确定,不能因为清理预算而强行截断正常业务。
|
||||
|
||||
## 3. 开发者检查命令
|
||||
|
||||
以下是**实施完成后的命令**,不是本轮执行记录。gateway 重命名为 `cmd/browser_gateway/` 后才使用该路径;最终脚本需与实际交付一致,不允许测试目录为空仍算通过。
|
||||
以下命令是交付检查命令;执行结果另记在证据文件,不把未运行的命令当作通过。
|
||||
|
||||
### Go
|
||||
|
||||
@@ -53,10 +54,10 @@ go tool cover -func=/tmp/creatorhub-go.cover
|
||||
```bash
|
||||
python3 -m venv .venv-gateway
|
||||
.venv-gateway/bin/python -m pip install -r requirements-gateway-dev.lock
|
||||
.venv-gateway/bin/python -m unittest discover -s cmd/browser_gateway -p 'test_*.py'
|
||||
.venv-gateway/bin/python -m unittest discover -s cmd/browser_gateway -t cmd -p 'test_*.py'
|
||||
.venv-gateway/bin/python -m coverage erase
|
||||
.venv-gateway/bin/python -m coverage run --source=cmd/browser_gateway \
|
||||
-m unittest discover -s cmd/browser_gateway -p 'test_*.py'
|
||||
.venv-gateway/bin/python -m coverage run --source=cmd/browser_gateway --branch \
|
||||
-m unittest discover -s cmd/browser_gateway -t cmd -p 'test_*.py'
|
||||
.venv-gateway/bin/python -m coverage report --omit='*/test_*.py' --fail-under=65
|
||||
```
|
||||
|
||||
@@ -79,22 +80,22 @@ npm --prefix web run build
|
||||
docker compose config --quiet
|
||||
```
|
||||
|
||||
仅在改动 Docker/Compose 时做配置校验,不执行 build/up;有保留的独立开发 Compose 文件时也逐份验证。另行检查浏览器 runtime 路径已无 DockerClient/socket、容器/镜像/卷/网络创建、容器 wrapper 入口、旧 image 字段和“原生失败回退 Docker”;PostgreSQL 的独立部署配置不算漏删。
|
||||
仅在改动 Compose 时做配置校验,不执行 build/up;保留的 PostgreSQL 开发 Compose 文件也逐份验证。另行检查浏览器 runtime 路径已无 DockerClient/socket、容器/镜像/卷/网络创建、容器 wrapper 入口、旧 image 字段和“原生失败回退 Docker”;PostgreSQL 的独立部署配置不算漏删。
|
||||
|
||||
## 4. 联调启动与访问
|
||||
|
||||
### 当前可确认的设置
|
||||
### 已交付设置
|
||||
|
||||
- Go 使用 `LISTEN_ADDR`;联调须设置 `0.0.0.0:8082`。
|
||||
- Vite 当前 `/api` 代理到 `127.0.0.1:8082`,端口 5173。Vite 必须加 `--host 0.0.0.0`,不能只给 localhost 地址。
|
||||
- Python gateway 使用 `LISTEN_ADDR`,多机控制测试可采用各节点 `0.0.0.0:8081`;现有凭据约束保持不变。
|
||||
- 当前 `scripts/dev-backend.mjs` 会启动 Compose 依赖,因此在 P4 替换之前,**不能把现有 `pnpm dev` 当作已支持原生 gateway 的命令。**
|
||||
- native Python gateway 使用 `LISTEN_ADDR=0.0.0.0:8081`,由非 root systemd user service 运行;稳定 `node_id` 从配置或主机身份加载。
|
||||
- `scripts/dev-backend.mjs` 只启动 PostgreSQL 并检查 `NATIVE_GATEWAY_ENDPOINT`,不会启动浏览器容器。
|
||||
|
||||
### 实施完成后
|
||||
### 单节点执行步骤
|
||||
|
||||
1. 按最终交付的原生安装说明,在 A/B 安装同一已确认浏览器、Xvfb 和 gateway 用户服务。服务名建议固定为 `creatorhub-gateway.service`;它目前尚未交付。
|
||||
1. 按部署说明在单节点安装已确认浏览器、Xvfb 和 `creatorhub-browser-gateway.service`。
|
||||
2. 使用已有、经验证的 PostgreSQL 与凭据配置。不要重新生成现有主密钥;不要把秘密值粘贴到验收报告。
|
||||
3. 按交付脚本启动 A/B 的原生 gateway;在控制面登记真实 A/B 地址,并核对返回的节点身份。
|
||||
3. 按交付脚本启动单节点 native gateway;在控制面登记真实地址,并核对 `/v1/info` 返回的稳定节点身份。
|
||||
4. 启动控制面及前端:
|
||||
|
||||
```bash
|
||||
@@ -104,9 +105,9 @@ LISTEN_ADDR=0.0.0.0:8082 go run ./cmd/control-plane
|
||||
npm --prefix web run dev -- --host 0.0.0.0
|
||||
```
|
||||
|
||||
命令启动后,用 `ip -brief -4 addr` 记录当次实际局域网 IP。用户在另一台设备访问 `http://<C-IP>:5173`;开发者可查看 `http://<C-IP>:8082/healthz`。报告列出 A/B 的实际 Endpoint。
|
||||
命令启动后,用 `ip -brief -4 addr` 记录当次实际局域网 IP。用户在另一台设备访问 `http://<C-IP>:5173`;开发者可查看 `http://<C-IP>:8082/healthz`。报告列出单节点 gateway 的实际 Endpoint。
|
||||
|
||||
本轮宿主机曾观察到 `10.1.1.104`,只可作为候选地址,**没有服务已启动的含义**;实施当天重新核实,不预先宣称该地址可用。
|
||||
历史观察到的地址不构成可用性证据;实施当天必须重新核实 IP、监听地址和防火墙。
|
||||
|
||||
## 5. 手工功能与清理矩阵
|
||||
|
||||
@@ -116,7 +117,7 @@ npm --prefix web run dev -- --host 0.0.0.0
|
||||
|
||||
| ID | 手工步骤 | 通过条件 |
|
||||
| --- | --- | --- |
|
||||
| A01 | 在无 Docker daemon/socket 可用的测试节点创建账号环境,选择原生版本,启动 | 正确 Xvfb/浏览器就绪;未登录时可进入人工登录,不永久停在“正在启动”;无镜像下载、容器、卷、Docker 网络操作 |
|
||||
| A01 | 在 gateway 节点确认 Docker daemon/socket 不参与运行,创建账号环境,选择已安装 native 版本并启动 | 正确 Xvfb/浏览器就绪;未登录时可进入人工登录,不永久停在“正在启动”;无镜像下载、容器、卷或 Docker 浏览器网络操作 |
|
||||
| A02 | 人工完成二维码登录,记录账号、Profile 标识和指纹;先关闭再启动,再停止并从界面普通升级至另一已安装且获准的版本后启动 | 两个子项均保持原 Profile、指纹和登录身份,无意外重新登录;升级后实际浏览器版本等于所选目标版本,不通过重建环境/重新登录冒充保持(AC-E1) |
|
||||
| A03 | 浏览器停止时,从界面启动一次竞品账号采集,等待结束 | 自动创建任务 runtime,结果保存;`T_cleanup` 内进程、display、端口和临时文件释放 |
|
||||
| A04 | 连续采集作品/评论/线索,再打开结果查看 | 数据完整且仍可读;不能因 runtime 清理丢失业务结果或已发布素材 |
|
||||
@@ -127,9 +128,9 @@ npm --prefix web run dev -- --host 0.0.0.0
|
||||
| A09 | 按下方代理矩阵分别配置四类代理及其支持的认证配置,改变代理或账号绑定后启动采集 | 每个配置下实际浏览器出口、代理版本、指纹时区/语言等正确;身份不符时拒绝动作;不能用单一无认证代理的成功代替其他配置(AC-E3) |
|
||||
| A10 | 仅在用户准备真实接收账号并逐次确认时验证人工回复/私信 | 保持原有确认与身份核对;重复结果不触发重复发送,不确定状态不冒充成功 |
|
||||
|
||||
A02 验证的是原生环境内的普通版本升级,不是首次 Docker 切换或 Profile 迁移;缺少获准版本时标为阻塞。
|
||||
A02 验证的是 native 环境内的普通版本升级,不是 Profile 迁移;缺少获准版本时标为阻塞。
|
||||
|
||||
代理矩阵适用于 A09/B03/D05:A/B 每台节点分别列出 HTTP、HTTPS、SOCKS4、SOCKS5,按协议允许的配置验证无认证(如支持)及支持的认证方式;A09/D05 记录成功与实际浏览器出口,B03 记录认证失败(适用时)与网络失败,均不得静默直连或轮换出口。协议本身不支持的认证项说明依据,不作为通过项;缺少测试代理、凭据或尚未实现的承诺能力标为阻塞,不能免测。证据不得包含密码/Cookie。
|
||||
代理矩阵适用于 A09/B03:单节点分别列出 HTTP、HTTPS、SOCKS4、SOCKS5,按协议允许的配置验证无认证(如支持)及支持的认证方式;A09 记录成功与实际浏览器出口,B03 记录认证失败(适用时)与网络失败,均不得静默直连或轮换出口。协议本身不支持的认证项说明依据,不作为通过项;缺少测试代理、凭据或尚未实现的承诺能力标为阻塞,不能免测。证据不得包含密码/Cookie。
|
||||
|
||||
A10 不能由脚本自动发送,也不能因为只修改 runtime 就免除必要回归;人工逐次确认不能代替 C02/D04 的自动策略验收。
|
||||
|
||||
@@ -139,7 +140,7 @@ A10 不能由脚本自动发送,也不能因为只修改 runtime 就免除必
|
||||
| --- | --- | --- |
|
||||
| B01 | 任务运行中点击取消 | 业务不永久 running;独立清理继续,`T_cleanup` 内完成或明确待清理 |
|
||||
| B02 | 让测试任务超过已批准最大时长 | 超时可见;lease 释放,任务进程/目录回收,不能继续后台写入 |
|
||||
| B03 | 按代理矩阵逐节点、逐协议分别制造认证失败(协议支持时)和网络失败;另测无效浏览器依赖启动 | 每个适用子项明确失败,无静默直连或出口轮换;已创建的 Xvfb/端口/临时目录逆序回收(AC-E3) |
|
||||
| B03 | 按代理矩阵逐协议分别制造认证失败(协议支持时)和网络失败;另测无效浏览器路径启动 | 每个适用子项明确失败,无静默直连或出口轮换;已创建的 Xvfb/端口/临时目录逆序回收(AC-E3) |
|
||||
| B04 | 经授权终止该测试 runtime 的浏览器,另测 Xvfb 退出 | 任务失败可见;其余子进程终止;其他 runtime 不受影响 |
|
||||
| B05 | 对测试专用目录制造无法删除条件后结束任务 | 显示“业务结果 + 清理失败”,保留可追溯记录;恢复条件后重试只清资源,不重跑业务 |
|
||||
| B06 | 测试磁盘低于启动阈值;用隔离测试盘验证写满 | 启动前拒绝或运行中明确失败;不删除登录资料/正式素材腾空间;收尾状态可靠 |
|
||||
@@ -169,16 +170,16 @@ C02/D04 必测恢复子项:
|
||||
|
||||
C05/B09 的精确竞态先用确定性单元测试证明;真机无法可重复制造时标记该子项未执行,不能用“人工未复现”替代回归测试。测试不得添加面向生产的任意执行接口。
|
||||
|
||||
### D. 多机控制
|
||||
### D. 多机控制(后续目标,全部未执行)
|
||||
|
||||
| ID | 步骤 | 通过条件 |
|
||||
| --- | --- | --- |
|
||||
| D01 | A/B 各创建合法唯一环境,分别启动;停止/清理一侧并观察另一侧 | 按稳定节点和代次执行,不误清另一机器;节点间资源 ID 碰撞另用契约测试覆盖,不为测试放松名称唯一性 |
|
||||
| D02 | A 失联,B 正常采集/续租/监听 | B 不被 A 的超时拖住;A 显示不可达,不把 A 的账号迁到 B |
|
||||
| D03 | A 仍有 Profile、运行实例或待清理资源时,尝试把登记地址改成 B | 明确阻止改变机器归属;旧 cleanup 目标仍是 A |
|
||||
| D04 | A 恢复,旧停止响应/事件迟到,期间已有新代次;按 C02 恢复子项核对既有自动响应与冷却记录 | 旧响应不能关闭/污染新实例;事件和身份边界重新确认;原 UID 冷却保留,重复旧事件及迟到事件不触发额外动作,B 的记录与正常监听不受影响(AC-A9、AC-B1) |
|
||||
| D05 | A/B 配置不同出口,按代理矩阵逐节点检查四类代理及支持的认证配置下目标浏览器的实际出网 | 各适用配置均有对应 gateway 的浏览器出口证据,不能只显示控制面测得 IP;失败子项与 B03 对照,无直连或轮换(AC-E3) |
|
||||
| D06 | B 未安装账号指定浏览器版本 | 明确显示不可用,不自动换普通 Chromium、其他版本或另一节点 |
|
||||
| D01 | 后续目标:两台节点各创建合法唯一环境,分别启动;停止/清理一侧并观察另一侧 | 本目标不执行;保留稳定节点和代数字段,后续目标按契约验收 |
|
||||
| D02 | 后续目标:一台节点失联,另一台正常采集/续租/监听 | 本目标不执行;不得以单节点重启替代跨机证据 |
|
||||
| D03 | 后续目标:带 Profile、运行实例或待清理资源时改变机器归属 | 本目标不执行;不得删除原节点资源 |
|
||||
| D04 | 后续目标:跨节点恢复时旧响应/事件迟到且已有新代次 | 本目标不执行;单节点旧代次保护由 C07 和自动测试覆盖 |
|
||||
| D05 | 后续目标:多节点配置不同出口并按代理矩阵核验 | 本目标不执行;单节点代理适用项在 A09/B03 验收 |
|
||||
| D06 | 后续目标:另一节点未安装账号指定浏览器版本 | 本目标不执行;单节点版本缺失在 A01/B03 验收 |
|
||||
|
||||
抖音先完成全部适用用例;小红书按相同生命周期复验。尚未实现的平台能力记为阻塞,不用 Mock、轮询或另一平台成功代替。
|
||||
|
||||
@@ -187,10 +188,10 @@ C05/B09 的精确竞态先用确定性单元测试证明;真机无法可重复
|
||||
### 6.1 测量方法
|
||||
|
||||
1. 固定机器、浏览器版本、指纹、账号、代理、任务内容、并发和依赖状态;除生命周期方式外尽量保持一致。不要拿不同浏览器或不同网络的结果相减。
|
||||
2. 冷启动指本次机器/服务启动后的第一次浏览器启动,和后续热启动分开记录;不擅自全机清理 page cache。安装、镜像拉取或下载浏览器耗时另列,不混入日常任务启动。
|
||||
2. 冷启动指本次机器/服务启动后的第一次浏览器启动,和后续热启动分开记录;不擅自全机清理 page cache。安装或下载浏览器耗时另列,不混入日常任务启动。
|
||||
3. 每种方式在并发 1 下至少 20 次有效启动,并在并发 2 下至少 10 组。按相同节奏执行,遵守平台限制。遇验证码/限流单独记录,不删去异常后只展示好看的数值。
|
||||
4. 时间从用户启动请求被接收到 CDP+代理+账号就绪;另外记录完整任务、终止到资源清理完成耗时。不能只测进程创建或 Xvfb 启动时间。
|
||||
5. 内存统计整个 runtime 的浏览器子进程、Xvfb、可选桌面,以及 gateway 增量;旧方案同样统计完整进程树及 Docker/代理相关增量。优先使用 cgroup 峰值或一致口径的 PSS;RSS 合计会重复计算共享页,必须注明。
|
||||
5. 内存统计整个 runtime 的浏览器子进程、Xvfb、可选桌面,以及 gateway 增量;如有获准的旧基线,旧方案也必须按完整进程树和代理增量的同一口径统计。优先使用 cgroup 峰值或一致口径的 PSS;RSS 合计会重复计算共享页,必须注明。
|
||||
6. 记录每轮之前、业务结束、清理完成三个时刻:活跃进程/unit、端口、runtime/Profile/日志/素材字节数。使用 `ps`、`ss`、`systemctl show`、`du -sb` 等只读工具;共享内存和 X socket 也须核对。
|
||||
7. 原生路径连续完成至少 30 个短任务,覆盖取消和失败;再观察一轮长期监听与采集并行。平台受限时可用明确标识的本地测试页面单独测生命周期,但不得拿它替代真实平台功能验收。
|
||||
|
||||
|
||||
+2
-2
@@ -44,9 +44,9 @@
|
||||
| 页面入口 | 账号、任务、审计、环境、代理、镜像、网关等页面 | 竞品池、作品、素材仿写、评论线索、响应策略、私信会话尚无完整入口 | [页面路由](../web/src/main.jsx) |
|
||||
| 账号 | 平台账号 ID、标签、Cookie、暂停/恢复等 | 实名资料、登录用户名/密码、备注、人工业务状态、人工登录与大小号关系 | [账号页面](../web/src/AccountsPage.jsx)、[账号存储](../internal/phasea/store.go)、[账号接口](../cmd/control-plane/phasea.go) |
|
||||
| 平台范围 | 登记项还包含公众号、快手 | 新业务仅承诺抖音、小红书;其他登记项不是业务能力验收结果,也不因此要求删除无关已有功能 | [账号页面](../web/src/AccountsPage.jsx) |
|
||||
| 作品读取 | 有抖音连接器、指标字段与测试 | 连接器只核验登录者自身、读取首批 20 条,未接成运行中的竞品采集链路;需目标账号解析、分页、保存、查询和定时更新 | [抖音连接器](../internal/douyin/connector.go)、[连接器测试](../internal/douyin/connector_test.go)、[网关读取限制](../cmd/docker_gateway/douyin.py) |
|
||||
| 作品读取 | 有抖音连接器、指标字段与测试 | 连接器只核验登录者自身、读取首批 20 条,未接成运行中的竞品采集链路;需目标账号解析、分页、保存、查询和定时更新 | [抖音连接器](../internal/douyin/connector.go)、[连接器测试](../internal/douyin/connector_test.go)、[网关读取限制](../cmd/browser_gateway/douyin.py) |
|
||||
| 指纹与环境 | 结构化指纹、独立 Profile、启动/停止/升级及代理接入 | 默认固定 seed 不是自动分配;需首次自动生成、地区匹配及稳定性验证 | [环境页面](../web/src/BrowsersPage.jsx)、[指纹参数](../internal/hub/fingerprint.go)、[环境存储](../internal/hub/environment.go) |
|
||||
| 代理 | 添加、列表、手动检测、停用、实际转发 | 补齐编辑、删除、重新启用及引用约束;不增加自动轮换 | [代理页面](../web/src/NetworkExitsPage.jsx)、[代理转发](../cmd/docker_gateway/proxy.py) |
|
||||
| 代理 | 添加、列表、手动检测、停用、实际转发 | 补齐编辑、删除、重新启用及引用约束;不增加自动轮换 | [代理页面](../web/src/NetworkExitsPage.jsx)、[代理转发](../cmd/browser_gateway/proxy.py) |
|
||||
| 任务与发送 | 有草稿确认、任务记录、Mock 执行 | 不能作为真实回复、私信、点赞、关注、转发成功的证据;需接通实际平台执行及结果核验 | [任务页面](../web/src/TasksPage.jsx)、[任务接口](../cmd/control-plane/phasea.go) |
|
||||
| 事件、线索、私信、AI | 未发现完整可运行链路 | 均需新增业务能力,不能把旧文档、类型定义或模拟返回记为已实现 | [页面路由](../web/src/main.jsx)、[抖音连接器](../internal/douyin/connector.go) |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Python 网关分支审查与修正清单
|
||||
|
||||
日期:2026-09-13。状态:**审查与代码修正已完成;本地门禁已通过,真实平台及供应商证据仍按外部验收项单独保留。**
|
||||
日期:2026-09-13。状态:**历史分支审查已完成;本文保留迁移前 `cmd/docker_gateway`、Docker wrapper 和容器生命周期路径作为问题证据,不是当前生产入口。当前入口与状态以 `cmd/browser_gateway`、部署说明和 native 验证记录为准。**
|
||||
|
||||
## 1. 范围、结论与证据边界
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ CreatorHub 不应承诺“稳定”“不封号”或“无法识别”,也不
|
||||
- `internal/creator/actions.go`:已有事件永久去重、策略顺序、同 UID 冷却,以及 AI 失败/结果不明不补发。
|
||||
- `cmd/control-plane/creator.go`:已有写操作前身份核对、目标映射和结果证据保存。
|
||||
- `cmd/control-plane/creator_material.go`:已有人工选取、原子媒体文件、大小/HTML 校验、ffprobe/ffmpeg 和显式转写失败步骤。
|
||||
- `cmd/control-plane/creator_events.go` 与 `cmd/docker_gateway/douyin.py`:已有监听代际、baseline、gap、断连恢复、delivery ACK/retry 的抖音实现;小红书不能直接沿用为已验证能力。
|
||||
- `cmd/control-plane/creator_events.go` 与 `cmd/browser_gateway/douyin.py`:已有监听代际、baseline、gap、断连恢复、delivery ACK/retry 的抖音实现;小红书不能直接沿用为已验证能力。
|
||||
- `docs/plan01.md`:已明确先抖音后小红书、逐平台真实验收,以及读取/写入/事件的成功、失败、不明边界。
|
||||
|
||||
### 5.2 当前小红书缺口
|
||||
|
||||
Reference in New Issue
Block a user