docs: doc/ 全量重整——按现状重写并建立文档索引;项目统一更名 auto_control

背景:文档长期落后于代码(Tab 数、任务类型、接口示例等多处与现状不符),
且信息分散重复。这次按当前代码状态逐篇重写,并建立统一的文档体系。

新增
- doc/README.md:文档总索引(文档地图 / 推荐阅读路径 / **文档维护约定**)
- doc/DATA_MODEL.md:数据模型(7 张模型表 + 5 张非模型表、迁移机制、app_meta 键、
  数据目录、备份覆盖清单与双向自检)
- doc/AI_CONSOLE.md:AI 控制台机制(会话与 SSE、经验库/动作库蒸馏与召回、巡检、
  Markdown 渲染、推理链、token 统计、故障排查)

重写(按现状,去掉过时与重复)
- README.md:7 个 Tab、18 种步骤、设备生命周期、调度/窗口语义、常见问题;修掉
  「6 个 Tab / 分组为顶级 Tab」等过时内容与损坏的目录树
- doc/ARCHITECTURE.md:补启动装配顺序(import 期副作用、A~G 七阶段)、线程与锁清单、
  设备状态机、调度全链路、前端结构与实时通道、设计决策、**已知缺陷与踩坑清单**、扩展点
- doc/API.md:按蓝图重建「接口总索引」(107 条路由含鉴权)+ 分域详细说明 +
  非 JSON 响应汇总 + 错误分支速查
- doc/TASK_DEV.md:18 种步骤全表(参数/默认值/语义)、容器与公共参数、
  选择器与 XPath 序号语义、抓取器建议规则、新增任务类型骨架
- doc/DEPLOY.md:容器入口 start.sh 三件事、发布流程与检查清单、备份覆盖红线、
  按现象分类的故障排查
- doc/DEVELOPMENT.md:流程/红线/本地开发/**测试与写测试的约定**/配置速查/文档同步
- doc/MCP.md:19 个工具的参数级清单、坐标空间、写门控三连、安全与审计
- doc/MCP_DESIGN.md、doc/AI_TASK_GEN.md:标注设计 vs 实现现状,补交叉链接
- doc/backlog/TODO.md:新增「已知缺陷」小节(含复现与影响)+ 已完成留档
- .env.example:按代码实际读取的键重写(补 USB/DISCOVERY/MCP/AGENT,删死配置)

其它
- 项目名统一 auto_control:README/文档/scripts/pack.py 产物名;代码内的
  doc 章节引用(templates/admin/monitor.html)同步更新
- 校验:16 篇文档 156 条相对链接全部可解析;文档中的关键数字与代码核对一致
  (19 个 MCP 工具 / 18 种步骤 / 12 张备份表 / 1 种任务类型)
This commit is contained in:
2026-09-10 22:19:18 +08:00
parent f4b5316436
commit 24d57d3b96
18 changed files with 2664 additions and 3667 deletions
+242 -324
View File
@@ -1,369 +1,287 @@
# 部署指南
# 部署与运维(DEPLOY)
本文介绍如何从零部署 `platform-tools` 设备自动化后台。
> 适用读者:部署与运维 `auto_control` 的人。
> 相关文档:[DEVELOPMENT.md](DEVELOPMENT.md)(开发流程/红线)、[DATA_MODEL.md](DATA_MODEL.md) §数据目录、[ARCHITECTURE.md](ARCHITECTURE.md) §启动装配。
---
## 1. 环境准备
### 1.1 Python 环境
### 1.1 主机与 Python
- **Python 3.10+**(推荐 3.12)
- 安装后确认 `python --version` 和 `pip` 可用
- Windows / Linux / macOS 均可;生产跑在 Linux 容器里
- `pip install -r requirements.txt`
```bash
# 验证
python --version # 应输出 3.10+
pip --version
```
项目自带的 **adb 二进制**在 `bin/adb/`(Windows 为 `adb.exe` + 依赖 dll;Linux/macOS 为 `adb`,需 `chmod +x`)。`config.py` 按平台自动选路径,换成自己的 adb 覆盖该目录即可。
### 1.2 设备池(已摘除 OpenSTF 依赖)
设备池由平台自身管理(SQLite `devices` 表 + 本机 adb 在线状态),**不再依赖 OpenSTF**。
新增设备在管理后台「工具 → 设备池管理」添加(serial 形如 `100.100.10.x:5555`)。
USB 设备(serial 无冒号)插在部署机(220)时,经 220 的 adb server 驱动:
- 220 的 adb 容器需为 **host 网络模式**(5037 已监听所有网卡,含 Tailscale)
- 平台配置 `USB_ADB_HOST`(默认 `100.100.10.1`)/ `USB_ADB_PORT`(默认 5037)
### 1.3 adb 工具
项目自带 adb 二进制在 `bin/adb/` 目录:
- **Windows**:`bin/adb/adb.exe` + 依赖 dll(已包含)
- **Linux**:`bin/adb/adb`(需 `chmod +x`)
- **macOS**:`bin/adb/adb`(需 `chmod +x`)
如需替换为自己的 adb 版本,把对应平台的 adb 放进 `bin/adb/` 即可,`config.py` 会自动识别操作系统。
### 1.4 设备准备
### 1.2 设备接入
设备需满足:
- 开启 **USB 调试**(设置 → 开发者选项)
- 转网络调试:USB 连上后 `adb -s <serial> tcpip 5555`(管理后台「adb 终端」有一键快捷命令)
- 设备加入 Tailscale(同一账号,获得 100.100.10.x IP)
- 在「工具 → 设备池管理」添加 `IP:5555`(自动尝试连接,任务运行时 u2 自动推送 atx-agent)
---
1. 开启 **USB 调试**
2. 转网络调试:USB 连上后 `adb -s <serial> tcpip 5555`(后台「工具 → adb 终端」有快捷命令)
3. 与平台**网络互通**(生产用 Tailscale,设备与部署机在同一 tailnet,serial 形如 `100.100.10.x:5555`)
4. 在「工具 → 设备池管理」加入设备池(**只连上 adb 不算入池,不参与调度**)
## 2. 安装部署
**USB 设备**(serial 无冒号)经部署机的 adb server 驱动:
### 2.1 获取代码
- adb 容器需 **host 网络模式**(5037 监听所有网卡,含 Tailscale)
- 平台通过 `USB_ADB_HOST`(默认 `100.100.10.1`)/ `USB_ADB_PORT`(默认 5037)访问它
```bash
# 方式一:直接拷贝项目目录
# 方式二:解压打包文件(python scripts/pack.py 生成的 zip)
```
> **adb key 必须沿用既有 key**(设备信任它)。换 key 会让全部设备变 `unauthorized`。
### 2.2 安装依赖
```bash
cd platform-tools
pip install -r requirements.txt
```
**依赖清单**(`requirements.txt`):
| 包 | 版本 | 用途 |
|----|------|------|
| Flask | >=2.3,<4.0 | Web 框架 |
| Flask-Login | >=0.6 | 用户认证 |
| Flask-SQLAlchemy | >=3.0,<4.0 | SQLite ORM |
| APScheduler | >=3.10,<4.0 | 定时调度 |
| requests | >=2.28 | HTTP 客户端 |
| uiautomator2 | >=3.0 | Android UI 自动化 |
| uiautodev | >=0.14 | UI 元素抓取 |
| pyaxmlparser | >=0.3.27 | APK 元信息解析 |
| rapidocr_onnxruntime | >=1.4 | 屏幕 OCR(条件判断的 OCR识别 选择器,中英文模型随包内置,跨平台) |
| fastmcp | >=2.0 | MCP Server 与 AI 控制台 Agent(Streamable HTTP 服务端/客户端) |
| paramiko | >=3.0 | SSH 运维预留(历史 STF SSH 通道退役;配置 `STF_SSH_PASSWORD` 时走密码认证,未配置则退回系统 ssh 免密) |
> 服务器(无显示器/Linux)环境建议把 opencv-python 换成 `opencv-python-headless`(rapidocr 依赖 cv2,两者取一)。
> uiautomator2 首次连接设备时会自动推送 atx-agent 到设备,无需手动安装。
### 2.3 修改配置
**密钥类配置统一放项目根目录 `.env`**(已被 `.gitignore` 排除,不会提交到 git;
`config.py` 不再内置任何密钥):
```ini
# .env 示例
WEB_SECRET_KEY=随机字符串(会话密钥,python -c "import secrets;print(secrets.token_hex(32))" 生成)
TAILSCALE_API_KEY=你的Tailscale_API_key
# USB_ADB_HOST=100.100.10.1 # 220 的 Tailscale IP(USB 设备远程 adb server,默认值已可用)
```
其他配置按需调整:
| 配置项 | 默认值 | 何时修改 |
|-------|--------|---------|
| `WEB_HOST` | `0.0.0.0` | 仅本机访问改为 `127.0.0.1` |
| `WEB_PORT` | `18050` | 端口冲突时修改 |
| `ADB_PATH` | 自动识别 | 用自定义 adb 时修改 |
| `USB_ADB_HOST` | `100.100.10.1` | USB 设备所在部署机(220)的 Tailscale IP |
| `USB_ADB_PORT` | `5037` | 220 adb 容器监听端口(host 网络模式) |
| `TAILSCALE_TAILNET` | 按邮箱前缀 | tailnet 名称/ID(个人账号一般为登录邮箱前缀) |
| `DISCOVERY_PORT` | `5555` | 设备自动发现扫描的 adb 端口(网段默认局域网 + Tailscale,可在设备池面板改) |
| `DISCOVERY_INTERVAL` | `60` | 自动发现扫描间隔(秒) |
> 配置来源区分:`WEB_HOST`/`WEB_PORT`/`ADB_PATH` 是 **config.py 常量**(直接改文件,不经 .env);
> `TAILSCALE_API_KEY`/`TAILSCALE_TAILNET`/`USB_ADB_HOST`/`USB_ADB_PORT`/`DISCOVERY_PORT`/`DISCOVERY_INTERVAL` 由 config.py 以环境变量读取,**可在 .env 覆盖**;
> `BACKUP_DIR`/`RESTORE_STAGING_DIR`/`RESTORE_PENDING_DIR` 是常量(硬编码到 `data/`,见 §3.5)。
> **未配置 `WEB_SECRET_KEY`**:启动时随机生成(每次重启登录态失效,生产务必配置固定值)。
> **工具页 Tailscale 管理前置**:`.env` 写入 `TAILSCALE_API_KEY` 后重启服务;
> 未配置时管理分区显示明确提示,不影响其他功能。
> (历史遗留的 `STF_URL`/`STF_TOKEN`/`STF_SSH_*` 等配置:`config.py` 仍读取但无任何功能使用,仅历史保留;生产 `.env` 可留空。)
### 2.4 启动服务
**命令行启动**
```bash
python web_server.py
```
(推荐配合 `scripts/supervise.sh` 进程守护,见 3.1 节)
**生产容器(220)入口 `scripts/start.sh`**(docker-compose 的 python-app 服务 command):
1. **依赖就绪守卫**:flask/u2/uiautodev/rapidocr/cv2/fastmcp 全可用则跳过安装;否则 `pip install -r requirements.txt` 并做 cv2 环境修复(卸 GUI opencv → 装 headless)
2. **后台拉起 MCP server**:`MCP_ENABLED` 默认 `1` 时执行 `python3 -m mcp_server.mcp_server`(`MCP_ALLOW_WRITE=1`、`MCP_PLATFORM_USER` 默认 admin、`MCP_PLATFORM_PASS` 兜底 `admin123`),日志 `/tmp/mcp_server.log`
3. **前台启动主服务**:`exec python -u web_server.py`
注意:
- `scripts/supervise.sh` **只守护 web_server,不拉起 MCP**——容器场景 MCP 由 start.sh 拉起,不要用 supervise.sh 替代容器入口
- **AI 控制台依赖 MCP**:`AGENT_MCP_URL` 默认 `http://127.0.0.1:8033/mcp`(`mcp_agent/config.py`)
- 手动起 web_server 需另启 MCP:`MCP_ALLOW_WRITE=1 python -m mcp_server.mcp_server`
- **改过 admin 密码务必同步 `MCP_PLATFORM_PASS`**(start.sh 兜底 `admin123` 会登录失败)
- 发布链路:dev 开发测试完成 → 负责人确认合并 main → 220 `git pull` → 重启 python-app 容器生效(详见 DEVELOPMENT §6)
### 2.5 验证部署
1. 控制台看到 `启动服务: http://localhost:18050/` 即成功
2. 浏览器访问 `http://localhost:18050/`
3. 用 `admin/admin123` 登录
4. 监控页应显示设备池中的设备(本机 adb 在线 + SQLite 配置清单,serial 形如 `100.100.10.x:5555`)
---
## 3. 生产部署建议
### 3.1 进程守护
用进程守护工具确保服务自动重启:
**Windows(NSSM)**:
```bat
nssm install platform-tools "C:\Python312\python.exe" "D:\platform-tools\web_server.py"
nssm start platform-tools
```
**Linux(systemd)**:
```ini
# /etc/systemd/system/platform-tools.service
[Unit]
Description=Platform Tools Web Server
After=network.target
[Service]
Type=simple
User=www
WorkingDirectory=/opt/platform-tools
ExecStart=/usr/bin/python3 web_server.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
**通用脚本(Linux/macOS,无需 systemd)**:
项目自带 `scripts/supervise.sh`:web_server 崩溃自动重启、带退避和重启上限(防崩溃死循环)。
```bash
# 用 venv 的 python 守护
PYTHON=./.venv/bin/python bash scripts/supervise.sh
```
> 进程守护只保证服务重启,不恢复已运行的任务(worker 状态在内存)。
>
> **崩溃后占用自愈(需人工核实)**:历史 `AUTO_RELEASE_STALE_OCCUPY` 机制已随 STF 摘除失效(全库代码不再读取该配置)。设备池的占用互斥仍在(任务 running/connecting 的设备会被锁),但崩溃后残留占用如何释放、是否自动清理,需按当前实现核实后补写本节。
**Docker(生产 python-app 容器)**:
docker-compose 已配置 `restart: unless-stopped`,web_server 进程退出 → 容器退出 → Docker 自动重启。建议再加健康检查,让编排感知服务存活:
```yaml
# docker-compose.yaml 的 python-app 服务下添加
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:18050/api/health', timeout=3)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
```
### 3.2 反向代理(可选)
如需 HTTPS 或 80 端口,用 Nginx 反向代理:
```nginx
server {
listen 80;
server_name auto.example.com;
location / {
proxy_pass http://127.0.0.1:18050;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
### 3.3 安全加固
- **修改默认密码**:登录后立即在"用户"Tab 修改 admin 密码
- **最小权限分配**:需要多人使用后台时,在"用户"Tab 创建普通用户并只勾选必要权限
(任务管理/设备控制/应用管理/日志查看),不要把 admin 密码共享出去
- **会话密钥 SECRET_KEY**:在 `.env` 写固定 `WEB_SECRET_KEY`(`python -c "import secrets;print(secrets.token_hex(32))"` 生成);未配置时启动随机生成(每次重启登录态失效,生产务必配置固定值)
- **限制访问**:生产环境把 `WEB_HOST` 改为 `127.0.0.1`,配合反向代理
- **防火墙**:只开放必要端口
### 3.4 日志管理
- 日志自动滚动(10MB 一份,保留 5 份)
- 日志目录 `logs/`,可在"日志"Tab 在线查看
- 长期运行建议定期清理或配置 logrotate
### 3.5 数据备份
**优先推荐平台功能「系统 → 数据备份导出/导入」(仅 admin)**:
- **导出**:`POST /api/system/backup/export` → 用 sqlite 在线备份 API 对 `data/users.db` 做一致快照,打包 zip(`users.db` + `manifest.json` + 可选 `apks/*.apk`)
- **导入**:上传 zip/`.db` → 校验预览(完整性/必需表/schema 版本告警)→ 确认后自动把当前库快照到 `data/backups/pre_restore_*.db`(安全网可回滚)→ 落 `data/restore_pending/` → **重启 web_server 生效**(web_server 在 `init_db` 前自动消费恢复任务)
**备份覆盖清单(红线)**:覆盖清单 = `core/system_backup.py` 的 `SUMMARY_TABLES`,当前包含
`app_meta`(系统配置) / `user` / `device_group` / `task_job` / `custom_action` / `apk_file` / `device` /
`pending_device` / `agent_conversation` / `agent_experience` / `experience_audit` / `agent_action`(动作库)。
> **新增任何持久化表,必须同步登记进该清单**——否则导出清单/预览里看不到它,会被误判为"没有备份"
> (2026-09-10 动作库 `agent_action` 即因此被误判:数据其实在 `users.db` 快照里,只是清单漏列)。
> 导出侧有**覆盖自检**(登记表若缺失 → `manifest.coverage_missing` + 日志告警);导入侧有**反向自检**
> (备份含未登记表 → 预览告警提示登记)。
目录与常量:
| 目录 | 用途 |
|---|---|
| `data/backups/` | 导出临时 zip + 恢复前快照 `pre_restore_*.db` + 校验失败的 `restore_failed_*` |
| `data/restore_staging/` | 导入暂存,TTL 30 分钟未应用自动清理 |
| `data/restore_pending/` | 待生效恢复任务(重启时消费) |
常量 `BACKUP_DIR` / `RESTORE_STAGING_DIR` / `RESTORE_PENDING_DIR`(config.py L63-68,非 .env,已 .gitignore)。
手工备份 `data/` 目录仍可作兜底,但**整库恢复建议走上述功能**(在线一致快照 + 预恢复备份 + 重启原子生效,避免手工替换被 WAL/占用文件破坏)。
---
## 4. 网络配置
### 4.1 端口说明
### 1.3 端口
| 端口 | 服务 | 说明 |
|------|------|------|
| 18050 | Web 后台 | 主服务端口(config.py 可改) |
| 20242 | uiautodev | 元素抓取服务(自动启动,固定端口) |
| 8033 | MCP | MCP 手机控制 Server(scripts/start.sh 自动拉起,`MCP_ENABLED=0` 可关) |
| 5555 | adb | 设备 adb 网络端口(设备端) |
### 4.2 Windows 端口问题(仅 Windows)
Windows 可能将某些端口范围划为动态排除范围,导致绑定失败(WinError 10013)。
```bash
# 查看排除的端口范围
netsh interface ipv4 show excludedportrange protocol=tcp
```
如果 18050 在排除范围内,修改 `config.py` 的 `WEB_PORT` 到一个不在排除范围内的端口。
### 4.3 局域网访问
- `WEB_HOST = "0.0.0.0"` 允许局域网访问
- 需要添加防火墙入站规则(Windows 手动添加,或参考 `netsh advfirewall firewall add rule`)
- 局域网其他机器访问 `http://部署机IP:18050/`
| 18050 | Web 后台 | `config.py` 的 `WEB_PORT`;绑定失败会自动回退候选端口 |
| 8033 | MCP Server | `MCP_HTTP_PORT`;由 `scripts/start.sh` 拉起 |
| 20242 | uiautodev | 元素抓取;`web_server.py` 启动时自动 Popen |
| 5555 | 设备 adb | 设备侧端口 |
---
## 5. 更新升级
## 2. 安装与启动
### 5.1 代码更新
### 2.1 获取代码与依赖
```bash
# 1. 停止服务
# 2. 替换代码文件(或解压新的 zip)
# 3. 重新安装依赖(如有新增)
cd auto_control
pip install -r requirements.txt
# 4. 启动服务
```
主要依赖:Flask / Flask-Login / Flask-SQLAlchemy / APScheduler / uiautomator2 / uiautodev / rapidocr_onnxruntime / **opencv-python-headless** / pyaxmlparser / fastmcp / paramiko。
> ⚠️ **服务器与容器环境必须用 `opencv-python-headless`**:GUI 版 `opencv-python` 依赖 X11 库,`python:slim` 容器里 `import cv2` 直接崩 → OCR 步骤抛异常 → 任务失败退出。版本锁 `<5`(5.x wheel 没有 cv2 模块)。`scripts/start.sh` 会自动做这个替换。
### 2.2 配置
**密钥类配置统一放项目根 `.env`**(不入 git,模板见 `.env.example`)。加载方式:逐行解析 + `os.environ.setdefault`(**真实环境变量优先**)。
生产至少配:
```ini
WEB_SECRET_KEY=<随机 64 hex> # 不配则每次重启登录态失效
# USB_ADB_HOST=100.100.10.1 # USB 设备所在的部署机(默认值通常可用)
# TAILSCALE_API_KEY=<...> # 需要 Tailscale 管理功能时
# MCP_PLATFORM_PASS=<admin 的密码> # 改过 admin 密码必须同步,否则 MCP 登录失败
```
其余配置项与默认值见 [DEVELOPMENT.md](DEVELOPMENT.md) §配置速查。
### 2.3 启动
```bash
python web_server.py
```
### 5.2 数据库迁移
启动成功日志:
- SQLite 表结构变化时,`init_db()` 会自动 `db.create_all()` 创建新表
- 旧 `groups.json` / `jobs.json` 首次启动自动迁移到 SQLite
- 迁移后 JSON 文件归档为 `.migrated`(保留备份,不再迁移)
```
[INFO] [core.worker] 心跳看门狗已启动
[INFO] [core.tm] 从数据库加载 X 个分组, X 个任务
[INFO] [web] uiautodev 服务已启动 (PID=...)
[INFO] [web] 管理后台: http://localhost:18050/ (admin/admin123)
[INFO] [web] 设备池预连接完成: X 台在线
[INFO] [web] 启动服务: http://localhost:18050/
```
### 5.3 注意事项
- **修改 `core/` 目录下的文件后必须重启 web_server**(`debug=False` 不热重载)
- **修改 `templates/` 下的 HTML 文件**:Flask 模板默认不缓存,但建议重启确保生效
- **修改 `tasks/` 下的文件后必须重启**(任务注册在启动时完成)
默认账号 **admin / admin123**(**登录后立即改密**)。
---
## 6. 故障排查
## 3. 生产部署(220 容器)
### 6.1 启动失败
生产环境固定为 **192.168.20.220** 的 `/mnt/data/openstf/auto_control`,由 `docker-compose` 的 **`python-app`** 容器运行:
| 现象 | 原因 | 解决 |
|------|------|------|
| `ModuleNotFoundError: No module named 'flask'` | 依赖未安装 | `pip install -r requirements.txt` |
| `WinError 10013` | 端口被排除/权限不足 | 改端口或用管理员运行 |
| `WinError 10048` | 端口被占用 | 改端口或杀占用进程 |
| 监控页设备列表为空 | 设备不在池中 / adb 连不上 | 「工具 → 设备池管理」确认已添加且在线,检查设备网络与 5555 端口 |
| AI 控制台报「MCP server(8033) 不可达」 | MCP Server 未启动/不可达 | 启动 MCP Server:本机 `MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<密码> MCP_AUDIT_FILE=data/mcp_audit.log python -m mcp_server.mcp_server`;220 容器由 `scripts/start.sh` 自动拉起(`MCP_ENABLED=0` 会关) |
| 项 | 值 |
|----|----|
| 宿主目录 | `/mnt/data/openstf/auto_control`(git 仓库,分支 `main`) |
| 容器挂载 | 宿主目录 → 容器 `/app` |
| 网络 | `network_mode: host` |
| 入口 | `scripts/start.sh`(`command`) |
| 重启策略 | `restart: unless-stopped` |
| 同级容器 | `adb`(USB 远程 adb server,5037)、以及该机器上其它无关服务 |
### 6.2 设备连接失败
> ⚠️ **`docker-compose.yaml` 不在仓库里**,只存在于 220 上;仓库里只有 `scripts/start.sh` 这一半。
| 现象 | 原因 | 解决 |
|------|------|------|
| `DeviceOfflineError` | u2 连接超时 / atx-agent 无响应 | 检查设备网络,重启设备或重新推送 atx-agent |
| `u2.connect 超时` | atx-agent 无响应 | 重启设备/重新推送 atx-agent |
| `adb connect failed` | 设备网络不通/端口未开放 | 检查设备 IP 和 5555 端口 |
| 设备显示离线 | 状态缓存/误报 | 管理后台设备池「重连」,或「扫描前台 App」复测 |
### 3.1 容器入口 `scripts/start.sh` 做三件事
### 6.3 任务不执行
1. **依赖就绪守卫**:检查 9 个模块 + `cv2` 可用性;全部可用就跳过安装
2. **依赖安装与修复**(仅在需要时):`pip install -r requirements.txt` → 卸载 GUI `opencv-python` → 装 `opencv-python-headless>=4.8,<5` → 再验 cv2,仍异常则 `--force-reinstall`
3. **拉起 MCP + 前台启动 Web**:
| 现象 | 原因 | 解决 |
|------|------|------|
| 任务列表有任务但不执行 | 任务未启用 / cron 未到点 | 检查 `enabled` 和 `schedule` |
| 立即执行无反应 | 无可用设备 | 检查设备池是否有空闲设备 |
| worker 状态 error | 查看日志的 `last_error` | 查看 `logs/core.log` 和 `logs/task.log` |
| 看门狗误杀 | 长操作未心跳 | 在长循环内加 `self.heartbeat()` |
```
MCP_ALLOW_WRITE=1 # 脚本内强制开启写操作
MCP_PLATFORM_USER=${…:-admin}
MCP_PLATFORM_PASS=${…:-admin123} # ← 改过 admin 密码必须覆盖它
MCP_AUDIT_FILE=${…:-/tmp/mcp_audit.log}
python3 -m mcp_server.mcp_server > /tmp/mcp_server.log 2>&1 &
exec python -u web_server.py
```
### 6.4 日志查看
> **`scripts/supervise.sh` 只守护 web_server,不拉起 MCP**——容器场景不要用它替代 `start.sh`。
### 3.2 发布流程
```bash
# 查看核心日志
# 方式一:Web 后台"日志"Tab
# 方式二:直接看文件
# logs/core.log — adb/worker/task_manager
# logs/task.log — 任务执行
# logs/web.log — Web 请求
# logs/action.log — 操作执行
cd /mnt/data/openstf/auto_control
git fetch origin && git checkout main && git pull --ff-only origin main
docker restart python-app
```
重启后验证:
```bash
curl -s http://127.0.0.1:18050/api/health # {"ok":true,"status":"up",...}
ss -ltnp | grep -E ':(18050|8033|20242)' # 三个端口都在
docker exec python-app tail -5 /tmp/mcp_server.log # MCP 已启动
tail -20 logs/web.log # 无 ERROR/Traceback
```
**发布前检查清单**:
- [ ] 本机 `dev` 已合并 `main` 并推送(且经负责人确认)
- [ ] 新增依赖已进 `requirements.txt`
- [ ] **新增持久化表已登记进备份覆盖清单**(`core/system_backup.py`,红线)
- [ ] 文档已同步(`doc/`)
- [ ] 若改过 admin 密码 → 同步容器环境变量 `MCP_PLATFORM_PASS`
- [ ] 启动日志里没有"任务类型已不存在"告警(有则说明库里有历史遗留任务,需人工处理)
> ⚠️ **重启会终止正在运行的任务**(worker 状态在内存)。选低峰期,或先在监控页「停止全部」。
---
## 4. 进程守护(非容器场景)
| 方式 | 用法 | 说明 |
|------|------|------|
| `scripts/supervise.sh` | `PYTHON=./.venv/bin/python bash scripts/supervise.sh` | 崩溃自动重启;300s 内最多重启 10 次;**不拉起 MCP** |
| systemd | `ExecStart=/usr/bin/python3 web_server.py` + `Restart=always` | Linux 通用 |
| NSSM | `nssm install auto_control <python> web_server.py` | Windows |
| Docker | `restart: unless-stopped` | 生产用法 |
> 进程守护只保证**服务重启**,不恢复已运行的任务。生产建议加健康检查打 `/api/health`(`interval 30s` / `timeout 5s` / `retries 3` / `start_period 10s`)。
---
## 5. 数据备份(重要)
### 5.1 平台内置(推荐)
**「系统 → 数据备份 / 导入恢复」**(仅管理员):
- **导出**:`POST /api/system/backup/export` → sqlite 在线备份 API 做一致快照 → 打包 zip(`users.db` + `manifest.json` + 可选 `apks/*.apk`)
- **导入**:上传 zip/`.db` → 校验预览(完整性 / 必需表 / schema 版本 / 未登记表告警)→ 确认后自动把当前库快照到 `data/backups/pre_restore_*.db`(安全网)→ 落 `data/restore_pending/` → **重启服务生效**
### 5.2 备份覆盖清单(红线)
覆盖清单 = `core/system_backup.py` 的 `SUMMARY_TABLES`,当前 12 张表:`app_meta` / `user` / `device_group` / `task_job` / `custom_action` / `apk_file` / `device` / `pending_device` / `agent_conversation` / `agent_experience` / `experience_audit` / `agent_action`。完整说明见 [DATA_MODEL.md](DATA_MODEL.md) §6。
> **新增任何持久化表,必须同步登记进该清单**——否则导出预览里看不到它,会被误判为"没有备份"(2026-09-10 动作库 `agent_action` 就踩过:数据其实在快照里,只是清单漏列)。
> 导出侧有**覆盖自检**(登记表缺失 → `manifest.coverage_missing` + 日志告警);导入侧有**反向自检**(备份含未登记表 → 预览告警)。
### 5.3 目录与手工备份
| 目录 | 用途 |
|------|------|
| `data/backups/` | 导出临时 zip、`pre_restore_*.db`、`restore_failed_*` |
| `data/restore_staging/` | 导入暂存(TTL 30 分钟自动清理) |
| `data/restore_pending/` | 待生效恢复任务(重启时消费) |
手工整目录备份 `data/` 仍可作兜底,但**整库恢复建议走内置功能**(在线一致快照 + 预恢复备份 + 重启原子生效,避免手工替换被 WAL/占用文件破坏)。
> ⚠️ 备份 zip 含**用户口令哈希与 AI 控制台 API Key(明文)**,注意保管与传输。
---
## 6. 安全加固
- **改默认密码**:登录后立即改 admin 密码;需要多人使用时在「用户」页建普通用户并只勾必要权限位
- **固定会话密钥**:`.env` 写 `WEB_SECRET_KEY`(`python -c "import secrets;print(secrets.token_hex(32))"`)
- **限制访问面**:`WEB_HOST` 改 `127.0.0.1` + Nginx 反代,或靠防火墙只放必要端口
- **MCP 端点(8033)自身无鉴权**:它只做**出站**登录平台,不对入站做校验 → **必须靠网络隔离**(同机/内网),不要直接暴露公网
- **写操作门控**:`MCP_ALLOW_WRITE=0`(默认)时 MCP 只读
- **HTTPS/80 端口**:用 Nginx 反代
```nginx
location / { proxy_pass http://127.0.0.1:18050; proxy_set_header Host $host; }
```
---
## 7. 升级与迁移
### 7.1 代码升级
```bash
git pull --ff-only origin main
docker restart python-app # 容器场景
# 或 systemd: systemctl restart auto_control
```
- **改 `core/`、`tasks/`、`templates/` 后必须重启**(`debug=False` 不热重载)
- 改前端 JS 后浏览器需**强刷**(Ctrl+Shift+R)
### 7.2 数据库迁移
- 表结构变化由 `init_db()` 自动处理:`create_all()` 建新表 + `SCHEMA_MIGRATIONS` 补列(幂等,失败不阻塞启动)
- 旧 `groups.json` / `jobs.json` 首次启动自动迁移并归档为 `.migrated`
- **跨版本恢复**:用「导入恢复」上传旧库 → 预览会提示 schema 版本差异 → 应用后重启(新版本会自动补迁移)
### 7.3 跨机迁移(换部署机)
1. 新机装依赖、放好 `bin/adb/` 与 **同一把 adb key**
2. 复制 `.env`
3. 老机「系统 → 数据备份」导出 zip → 新机「导入恢复」上传并应用
4. **重启新机服务**(恢复任务在 `init_db` 之前被消费)
5. 核对设备池在线状态与任务列表
---
## 8. 故障排查
### 8.1 启动类
| 现象 | 原因 / 处理 |
|------|------------|
| `ModuleNotFoundError: No module named 'flask'` | 依赖未装:`pip install -r requirements.txt` |
| `WinError 10013`(Windows) | 端口被排除/权限不足:`netsh interface ipv4 show excludedportrange protocol=tcp` 查看,或改 `WEB_PORT` |
| `WinError 10048` | 端口被占用:换端口或杀占用进程 |
| 启动日志只有一半 | 以 WSGI 方式 import 了模块(只跑了 import 期的装配,没跑 `__main__` 分支);直接 `python web_server.py` |
| 登录后立刻掉线 | 没配 `WEB_SECRET_KEY`(每次重启换密钥) |
| 容器里 `import cv2` 崩 | 装成了 GUI 版 opencv:换 `opencv-python-headless`(`start.sh` 会自动修) |
### 8.2 设备类
| 现象 | 处理 |
|------|------|
| 设备显示离线 | 「设备池管理 → 一键重连」;确认设备在线、网络互通(生产:同 tailnet) |
| 加了设备但不被调度 | 只连上 adb 不够,必须在**设备池**中且 `enabled=true` |
| `u2.connect 超时` | atx-agent 无响应:重启设备或重新推送 atx-agent(基类有 30s 超时保护) |
| 大量设备同时连接时超时 | Windows 端口耗尽(`WinError 10048`):代码会打 `[transient]` 并退避 120s 重试;减少并发或调整系统 TIME_WAIT |
| 设备 `unauthorized` | adb key 变了:恢复原 key |
### 8.3 任务类
| 现象 | 处理 |
|------|------|
| 任务不执行 | 检查 `enabled`、`schedule`、是否在运行窗口内、目标设备是否在线 |
| 立即执行无反应 | 无可用设备(`resolve_serials` 为空);看 `logs/core.log` |
| 任务"成功"但没做事 | 步骤类型未知/缺必填参数会被**告警跳过**;查 `logs/task.log` 的 WARNING |
| 执行报"任务类型 xxx 已不存在" | 库里残留了已删除类型的任务(如历史 `douyin_nurture`):在「任务」页删除或改用现有类型。启动日志也会点名列出 |
| 长任务被看门狗杀 | 120s 无心跳:业务循环里要周期性 `self.heartbeat()` |
| 任务卡在 running | 监控页「停止选中」;必要时重启服务(重启会清空内存状态) |
### 8.4 其它
| 现象 | 处理 |
|------|------|
| 元素抓取按钮不可用 | uiautodev(:20242)没起来;`web_server.py` 启动时自动拉起,手动可 `python -m uiautodev server --no-browser` |
| 抓元素超时 | 部分设备 dump 慢(~18s)超过平台超时(8s),见 [backlog/TODO.md](backlog/TODO.md) |
| AI 控制台报"MCP server(8033) 不可达" | MCP 没启动:容器由 `start.sh` 拉起,本机手动 `MCP_ALLOW_WRITE=1 python -m mcp_server.mcp_server` |
| AI 控制台报 401 | 模型 API Key 无效/过期:在「AI 控制台 ⚙ 配置」重填 |
| 备份导入后没变化 | **必须重启服务**,恢复任务在重启时才被消费 |
日志位置:`logs/core.log`(adb/调度)、`logs/task.log`(任务执行)、`logs/web.log`(Web/AI)、`logs/action.log`;容器内 MCP 日志 `/tmp/mcp_server.log`。