# 摘除 STF 迁移计划 > **状态:历史迁移记录(2026-08)。** 本文档为当时摘除 STF 的计划与进度,不代表现状。 > 截至 dev HEAD,代码已不再依赖 STF(`core/stf_client.py` 等已删,`config.py` STF 键标注废弃)。 > "当前实现"以 [doc/ARCHITECTURE.md](ARCHITECTURE.md) / [doc/DEVELOPMENT.md](DEVELOPMENT.md) 为准; > 220 侧 STF 容器是否已 `docker stop` 见下文"待人工确认",需人工核实。 背景:全舰队设备为 Tailscale IP:5555 直连(adb key 沿用 STF 的),单实例部署。 STF 当前仅提供:occupy/release 互斥、present+ready 健康信号、设备池清单(与 220 的 connect_devices.sh 双维护)、remoteConnect 桥接(IP:port 已禁用)、网页看屏。 任务本体跑 uiautomator2(atx-agent),与 STF agent(jp.co.cyberagent.stf)无关。 摘除收益:告别 agent 安装死锁(MIUI 弹窗)、STF 容器重启风险、occupy 冲突悬空; 调度直接基于 adb 真实现状。风险点:多实例互斥(当前单实例无此需求,见阶段 3)。 ## 已确认的决策(2026-08-17) 1. **设备清单放本地 SQLite**(data/users.db 新增 devices 表),220 的 connect_devices.sh 退役(其"每 5 分钟补连"职责由平台定时器 + SSH 兜底接管,或直接依赖 adb 重试) 2. **USB 有线设备保留支持**——当前舰队 0 台 USB(全 IP:5555),但能力要保留。 实施中发现 **220 的 adb 容器是 host 网络模式、5037 已监听所有网卡(含 Tailscale 100.100.10.1),无需改动 220 任何配置**。平台用 `adb -H 100.100.10.1 -P 5037` 和 `adbutils.AdbClient(host=..., port=5037)`(u2 底层)驱动远端 USB 设备; 本机 USB 设备照旧走本地 adb。已验证(阶段 2 完成) 3. **网页远程看屏**——原计划 ws-scrcpy **不在 npm 分发**(仅 GitHub,国内下载不可靠), 改**自建**:MJPEG 流(u2/atx-agent minicap 截图,~5fps,实测 0.19s/帧)+ u2 触控 (tap/swipe/key/text),管理页新子分栏,零新依赖(阶段 2 完成) --- ## 迁移难度与程度评估(2026-08-17) ### 代码量 | 项 | 量级 | |---|---| | 删除 `core/stf_client.py` 及引用 | ~250 行 | | 新增 `core/device_pool.py`(清单+在线状态) | ~150 行 | | 新增 devices 表 CRUD(复用现有 SQLite 基建) | ~120 行 | | 修改 `task_manager`(resolve_serials/调度) | ~120 行 | | 重写 `device_worker.STFDevice`(去 occupy/release) | ~80 行 | | 修改 `web_server` 约 8 处调用点 | ~60 行 | | 改造 `stf_device_mgmt`(SSH 脚本 → SQLite 管理) | ~100 行 | | 前端 6 个文件文案/组件 | ~80 行 | 净效果:**-600 行 / +400 行 / 改 ~300 行**,集中在 task_manager 与 device_worker 两个核心文件。 ### 分项难度(单人) | 模块 | 难度 | 估算 | 关键点 | |---|---|---|---| | 设备池 SQLite + device_pool | 低 | 0.5-1 天 | 表结构 + CRUD + 管理页改数据源 | | 调度替换 | 中 | 1-2 天 | 内存锁已有(`_running[serial]`),换数据源 + 对照验证 | | web_server 调用点 | 低 | 0.5 天 | 8 处机械替换 | | USB 支持 | 中 | 1 天 | 220 容器发端口 + adb_helper 支持 `-H/-P` + u2 远程 server;**需一台真机验证** | | 网页看屏(ws-scrcpy) | 中 | 0.5-1 天 | 独立 Node 服务 + 内嵌页 + 认证联动 | | 界面清理 | 低 | 0.5 天 | 文案/按钮/列 | | 回归验证 | 中 | 1 天 | 调度/抢占/重试/离线跳过/亮屏/剪贴板/终端/看屏 | **总计约 4-6 人日**,分 4 个 commit 阶段,每阶段可独立回滚。 ### 风险 - **中**:USB 真机方案当前无设备可验,落地时需借一台 USB 设备 - **低**:离线跳过判定语义变化(STF 状态 → adb 状态),个别边界行为可能微变,需对照 - **低**:唯一动 220 的地方 = adb 容器发布 5037 端口(一次性) - **低**:ws-scrcpy 是新增 Node 依赖,需评估其维护性与内存占用(可先试用再定) ### 实施顺序(调整后) - **阶段 0**:SQLite 设备表 + `core/device_pool.py`(纯新增,STF 照常跑) - **阶段 1**:调度/生命周期替换(STF 保留运行对照验证) - **阶段 2**:USB 远程 adb server 打通 + ws-scrcpy 看屏部署 - **阶段 3**:界面清理 + 220 停 STF 容器(`docker stop` 不删,可回滚) --- ## 阶段 0:新建设备池模块(纯新增,不改行为) 新建 `core/device_pool.py` + devices 表(data/users.db): - devices 表:serial(主键)、name、enabled、note、created_at;管理页增删改 - `list_configured()` — 读 SQLite devices 表(取代 SSH 读 220 脚本) - `list_online()` — 本机 `adb devices` 中 state=device 的设备(实时) - `is_online(serial)` — 在 list_online 中;可选 `adb -s get-state` 兜底(带超时) - `list_ready()` — list_configured 与 list_online 的交集(取代 STF list_free_devices) - 全模块不 connect/不 kill-server/disconnect,遵守既有红线 - 迁移脚本:现有 8 台设备从 STF 池导入 devices 表 产出:`core/device_pool.py` + `doc/ARCHITECTURE.md` 设备章节初稿 + 数据迁移脚本。 ## 阶段 1:替换调度与生命周期(STF 保留运行,可对照验证) | 位置 | 现状 | 改为 | |---|---|---| | `task_manager.resolve_serials` (145-183) | `stf.list_free_devices()` / `list_all_devices()` | `device_pool.list_ready()`;preempt 模式 = 全部 list_online | | `resolve_serials` skip_offline 过滤 (166-183) | STF present/ready 判定 | `device_pool.is_online()`;跳过原因文案"离线/未连接" | | `device_worker.STFDevice.acquire` (45-70) | `stf.occupy()` + 直连/桥接 | 去掉 occupy(互斥由 `_running[serial]` 负责);**USB 桥接保留到阶段 2**(避免迁移期 USB 断档,阶段 2 换成 220 adb server);保留 adb_connect 重试 + 2s 等待 | | `STFDevice.release` (78-93) | `stf.release()` | 置空(仅保留 USB 隧道断开) | | `STFDevice._pick_free` (72-76) | `list_free_devices` | `device_pool.list_ready()` | | `_ForegroundScanner` (234-386) | `list_all_devices` / `list_my_devices` / remote_connect | `device_pool.list_online()`;桥接分支删除 | | `web_server.api_device_screen_all` (646) | STF present 列表 | `device_pool.list_online()` | | `web_server` 设备列表 (924 / 1011 / 1079) | STF 池合并 | SQLite devices 表 + 本地 adb(`_merged_device_list` 改数据源,保留 stf_not_ready 状态的等价物"未连接") | | `web_server.api_release` (740) / 启动清理 (1581) | release_all_mine | 删除或改为清理本实例 `_running` 状态(重启本就清零) | 验证清单(阶段 1 完成后全跑一遍): - [ ] 任务调度(serial/group/all 三种模式)+ 抢占/归还 + 重试 + 离线跳过 - [ ] 一键亮屏/息屏、剪贴板注入、应用版本查询、维护终端设备列表 - [ ] 前台 App 扫描 - [ ] 对照 STF 状态确认无行为差异 ## 阶段 2:USB 打通 + 网页看屏(阶段 1 稳定运行 ≥ 3 天后) - **USB 远程 adb server**: - 220 侧一次性改动:adb 容器发布 5037 端口(`docker run -p 5037:5037` 或 iptables), Tailscale 可达 - `core/adb_helper._adb` 支持 `-H <220-tailscale-ip> -P 5037`(仅 USB 序列号设备) - u2 连接:USB 设备走 `adbutils.Adb(host=220_ip, port=5037)` → `u2.Device(dev)` (已验证 u2 3.7.0 底层 adbutils 支持远程 adb server) - 本机 USB 设备照旧走本地 adb,零改动 - 需要一台真机 USB 设备验证 - **网页看屏(ws-scrcpy)**: - 平台机部署 ws-scrcpy(Node 服务,WebRTC 低延迟流 + 触控),本机 adb 为数据源 - 管理员页面新增「远程看屏」入口,iframe 内嵌;登录态复用(ws-scrcpy 自身鉴权 走 token 或仅内网/Tailscale 暴露) - 备选方案(若 ws-scrcpy 试用不达标):自建 minicap JPEG 流 + u2 触控注入(2-3 天) ## 阶段 3:界面清理 + STF 停用(已完成代码部分,2026-08-18) ✅ 已完成的代码部分(commit a5ce57b,-956 行): - 删「一键重启 STF 容器」区块、设备表 STF 占用列、强制释放占用按钮 - 删「STF 设备管理」子分栏(设备池管理面板接管)+ 卸载/检查 STF agent 按钮 - 删除 `core/stf_client.py`、`core/stf_device_mgmt.py` 及全部引用(含 web_server 全部 STF API、启动残留清理、设备池首导 seed) - 错误类(STFError/DeviceOfflineError)迁入 `core/device_worker.py`; `create_worker`/`BaseWorker` 去掉 stf 参数;`stf_occupied` 字段全链路移除 - 文案清理(tools/apps/editor/monitor)+ `config.py` STF/SSH 配置标注废弃 - 文档更新(ARCHITECTURE/DEPLOY) ⏳ 待人工确认的最后一步(220 侧): - **停 STF 容器**(`docker stop stf`,不删除,可 `docker start stf` 回滚) - 保留 adb 容器(host 网络 5037,USB 设备远程驱动依赖它); connect_devices.sh cron 可留可退役(对网络设备补连已无必要,无害) - 停容器前请确认:任务调度/看屏/设备池管理已稳定运行 ≥1 天 ## 阶段 4(可选,未来多实例/多机时) 跨实例互斥:SQLite 行锁替代 occupy—— ```sql UPDATE device_locks SET owner=?, ts=? WHERE serial=? AND (owner IS NULL OR owner=?) ``` --- ## 回滚方式 - 每个阶段一个 commit,问题可整体 `git revert`;STF 阶段 3 前一直运行,随时回切 - 阶段 3 的 STF 容器是 `docker stop` 而非删除,回滚 = `git revert` + `docker start stf` - 每个阶段 commit 后在 dev 观察 ≥3 天再进下一阶段