docs: 同步 P5 备份机制重写——DEPLOY §5 / API §13 / DATA_MODEL §7 / DEVELOPMENT §4.3
- DEPLOY §5.1: 说明 SQLite 现在是「备份交换格式」,恢复走单事务整库替换, 跨环境导入默认拒绝;§5.2 覆盖清单改为「由 metadata 派生」;§5.3 三个目录 支持环境变量覆盖及原因 - API §13: 导出/预览/应用三个接口的字段补齐(db_backend/deployment_env/ source_db_id/source_env/current_env、force_env_mismatch、pre_restore zip) - DATA_MODEL §7: pre_restore 改 .zip、补目录可覆盖说明 - DEVELOPMENT §4.3: 补三个备份目录环境变量
This commit is contained in:
+23
-11
@@ -610,37 +610,49 @@
|
||||
|
||||
### POST /api/system/backup/export
|
||||
|
||||
**JSON body**(不是表单):`{"include_apks": false}`。响应为 **zip 附件**:
|
||||
**JSON body**(不是表单):`{"include_apk": true}`(默认 true)。响应为 **zip 附件**:
|
||||
|
||||
```
|
||||
users.db # sqlite 在线备份 API 做的一致快照
|
||||
apks/*.apk # include_apks=true 时
|
||||
manifest.json # {format:"auto_control_backup", version, created_at, schema_version,
|
||||
# include_apk, tables:[{table,label,rows}], apks:[…], coverage_missing?}
|
||||
users.db # 当前库(MySQL 或回退 SQLite)整库一致快照成的 SQLite 归档
|
||||
apks/*.apk # include_apk=true 时
|
||||
manifest.json # {format:"auto_control_backup", version:2, created_at, schema_version,
|
||||
# db_backend, deployment_env, source_db_id, include_apk,
|
||||
# tables:[{table,label,rows}], apks:[…], coverage_missing?}
|
||||
```
|
||||
|
||||
> SQLite 在这里是**备份交换格式**而非运行时库:MySQL 下导出时把连接提到 REPEATABLE READ,
|
||||
> 保证 12 张表读的是同一时刻。
|
||||
|
||||
### POST /api/system/backup/preview
|
||||
|
||||
multipart 上传 `.zip` 或 `.db`(字段 `file`)→ 校验并暂存:
|
||||
|
||||
```json
|
||||
{"ok": true, "token": "e172dc75e2f7",
|
||||
"preview": {"integrity": "ok", "schema_version": 4, "current_schema_version": 4,
|
||||
"tables": [{"table":"agent_action","label":"动作库","rows":4}],
|
||||
"preview": {"integrity": "ok", "schema_version": 6, "current_schema_version": 6,
|
||||
"source_env": "dev", "current_env": "dev", "source_backend": "mysql",
|
||||
"source_db_id": "704fbbb0…",
|
||||
"tables": [{"table":"agent_action","label":"动作库","rows":5}],
|
||||
"missing_optional": [], "extra_tables": [],
|
||||
"warnings": ["备份为全量快照,含用户口令哈希、AI 控制台 API Key 等敏感信息…"]}}
|
||||
"warnings": ["备份为全量数据,含用户口令哈希、AI 配置里的 API Key 等敏感信息…"]}}
|
||||
```
|
||||
|
||||
- 完整性 `PRAGMA integrity_check` 必须 `ok`;缺必需表(`app_meta`/`user`/`task_job`/`device_group`)直接拒绝
|
||||
- `extra_tables` 非空 = 备份含**未登记进覆盖清单**的表(提示去登记)
|
||||
- `extra_tables` 非空 = 备份含**未登记的表**(提示去登记)
|
||||
- `source_env` ≠ `current_env` 时 `warnings` 会多一条跨环境告警
|
||||
- 暂存 **TTL 30 分钟**,过期自动清理
|
||||
- 文件非法 / 未选文件 → 400
|
||||
|
||||
### POST /api/system/backup/apply
|
||||
|
||||
`{"token":"…"}` → 先自动把当前库快照到 `data/backups/pre_restore_<ts>.db`(安全网),再把暂存库落到 `data/restore_pending/`。
|
||||
`{"token":"…", "force_env_mismatch": false}` → 先自动把当前库导出成
|
||||
`data/backups/pre_restore_<ts>.zip`(安全网,可直接再导入回来),再把暂存归档落到
|
||||
`data/restore_pending/`。
|
||||
|
||||
**必须重启服务才生效**(`web_server.py` 在 `init_db` 之前消费该目录)。token 无效/暂存缺失 → 400。
|
||||
- **必须重启服务才生效**:`web_server.py` 在 `init_db` 之后、`TaskManager` 之前消费该目录
|
||||
- 重启时**单事务整库替换**(DELETE 全表 + 分块 INSERT),失败自动回滚,当前数据不受影响
|
||||
- 备份来源环境与当前库不符时**默认拒绝**(400),要跨环境须显式传 `force_env_mismatch: true`
|
||||
- token 无效/暂存缺失 → 400
|
||||
|
||||
---
|
||||
|
||||
|
||||
+6
-2
@@ -271,9 +271,13 @@ SUMMARY_TABLES = tuple(sorted(t.name for t in db.metadata.tables.values()))
|
||||
|------|------|--------|
|
||||
| `data/users.db`(+`-wal`/`-shm`) | SQLite 主库(**仅回退模式用**;连 MySQL 时这些文件不被读写,可留作历史归档) | 否 |
|
||||
| `data/apks/*.apk` | 上传的 APK | 否 |
|
||||
| `data/backups/` | 导出临时 zip、`pre_restore_*.db`(导入前安全网)、`restore_failed_*` | 否 |
|
||||
| `data/backups/` | 导出临时 zip、`pre_restore_*.zip`(导入前安全网)、`restore_failed_*` | 否 |
|
||||
| `data/restore_staging/<token>/` | 导入暂存(TTL 1800s 自动清理) | 否 |
|
||||
| `data/restore_pending/` | 待生效恢复任务(重启时消费) | 否 |
|
||||
| `data/restore_pending/` | 待生效恢复任务(重启时单事务消费) | 否 |
|
||||
|
||||
> 后三个目录可用环境变量改到别处(`DATA_BACKUP_DIR` / `DATA_RESTORE_STAGING_DIR` /
|
||||
> `DATA_RESTORE_PENDING_DIR`)——自动化测试必须这么做,否则测试造的待生效恢复任务
|
||||
> 会被服务当成用户的操作在下次重启时消费掉。
|
||||
| `data/mcp_audit.log` | MCP 调用审计(路径由 `MCP_AUDIT_FILE` 指定) | 否 |
|
||||
| `data/uiauto.pid` | uiautodev 子进程 PID | 否 |
|
||||
| `data/*.json.migrated` | 旧 JSON 迁移归档 | 否 |
|
||||
|
||||
+42
-11
@@ -183,25 +183,56 @@ tail -20 logs/web.log # 无 ERROR/Traceback
|
||||
|
||||
**「系统 → 数据备份 / 导入恢复」**(仅管理员):
|
||||
|
||||
- **导出**:`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/` → **重启服务生效**
|
||||
- **导出**:`POST /api/system/backup/export` → 把当前库(MySQL 或回退模式的 SQLite)
|
||||
整库一致快照成一份 SQLite 归档 → 打包 zip(`users.db` + `manifest.json` + 可选 `apks/*.apk`)
|
||||
- **导入**:上传 zip/`.db` → 校验预览(完整性 / 必需表 / schema 版本 / 未登记表 / **来源环境**)
|
||||
→ 确认后自动把当前库导出一份 `data/backups/pre_restore_*.zip`(安全网,可再导入回来)
|
||||
→ 落 `data/restore_pending/` → **重启服务生效**
|
||||
|
||||
> **SQLite 在这里是"备份交换格式",不是运行时数据库。** 所以归档里永远是一个 `users.db`,
|
||||
> 无论平台连的是 MySQL 还是 SQLite —— 前端接口、校验逻辑、老备份的兼容性都不用变,
|
||||
> 也不依赖 `mysqldump` 这类外部二进制(容器是 `python:slim`,没有 MySQL 客户端)。
|
||||
|
||||
**恢复是怎么生效的**:重启时 `consume_pending_restore()` 在**单个事务内** `DELETE` 全表 +
|
||||
分块 `INSERT`,任何一步失败就回滚——当前数据保持原样,不会留下半新半旧的库。
|
||||
坏归档会被挪到 `data/backups/restore_failed_*/` 且不阻塞启动。
|
||||
|
||||
**跨环境导入默认拒绝**:备份的 manifest 里记了来源环境(`deployment_env`),
|
||||
若与当前库环境不一致(例如拿生产备份灌 dev 库),预览会弹黄框告警、直接应用会被拒绝;
|
||||
确需跨环境时在预览页勾选「允许跨环境导入」。
|
||||
|
||||
### 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。
|
||||
覆盖清单 = `core/system_backup.py` 的 `SUMMARY_TABLES`,**由模型元数据自动派生**:
|
||||
|
||||
> **新增任何持久化表,必须同步登记进该清单**——否则导出预览里看不到它,会被误判为"没有备份"(2026-09-10 动作库 `agent_action` 就踩过:数据其实在快照里,只是清单漏列)。
|
||||
> 导出侧有**覆盖自检**(登记表缺失 → `manifest.coverage_missing` + 日志告警);导入侧有**反向自检**(备份含未登记表 → 预览告警)。
|
||||
```python
|
||||
SUMMARY_TABLES = tuple(sorted(t.name for t in db.metadata.tables.values()))
|
||||
```
|
||||
|
||||
当前 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。
|
||||
|
||||
> **新增一张 ORM 表,就自动进了覆盖清单**,不可能再漏(2026-09-10 动作库 `agent_action`
|
||||
> 曾因手工维护漏登记,数据其实在快照里,只是清单没列 → 被误判为"没有备份")。
|
||||
> 还需要手工做的只有:给新表补 `TABLE_LABELS` 的中文标签。
|
||||
> 导出侧有**覆盖自检**(登记表缺失 → `manifest.coverage_missing` + 日志告警);
|
||||
> 导入侧有**反向自检**(备份含未登记表 → 预览告警)。
|
||||
|
||||
### 5.3 目录与手工备份
|
||||
|
||||
| 目录 | 用途 |
|
||||
|------|------|
|
||||
| `data/backups/` | 导出临时 zip、`pre_restore_*.db`、`restore_failed_*` |
|
||||
| `data/restore_staging/` | 导入暂存(TTL 30 分钟自动清理) |
|
||||
| `data/restore_pending/` | 待生效恢复任务(重启时消费) |
|
||||
| 目录 | 用途 | 可否用环境变量改 |
|
||||
|------|------|------------------|
|
||||
| `data/backups/` | 导出临时 zip、`pre_restore_*.zip`(导入前安全网)、`restore_failed_*` | `DATA_BACKUP_DIR` |
|
||||
| `data/restore_staging/` | 导入暂存(TTL 30 分钟自动清理) | `DATA_RESTORE_STAGING_DIR` |
|
||||
| `data/restore_pending/` | 待生效恢复任务(重启时消费) | `DATA_RESTORE_PENDING_DIR` |
|
||||
|
||||
手工整目录备份 `data/` 仍可作兜底,但**整库恢复建议走内置功能**(在线一致快照 + 预恢复备份 + 重启原子生效,避免手工替换被 WAL/占用文件破坏)。
|
||||
> 这三个环境变量主要给**自动化测试**用:测试必须把恢复目录指到临时位置,
|
||||
> 否则测试造出来的"待生效恢复任务"会在服务下次重启时被当成用户的操作消费掉。
|
||||
|
||||
连 MySQL 时 `data/users.db` 及其 `-wal`/`-shm` **不再被读写**(只有回退模式才用),
|
||||
可以留作历史归档。手工整目录备份 `data/` 仍可作兜底,但**整库恢复建议走内置功能**
|
||||
(一致快照 + 预恢复备份 + 重启时事务替换)。
|
||||
|
||||
> ⚠️ 备份 zip 含**用户口令哈希与 AI 控制台 API Key(明文)**,注意保管与传输。
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<密码> \
|
||||
|------|------|------|------|
|
||||
| `WEB_SECRET_KEY` | 未配置则随机 | `web_server.py` | 会话密钥,**生产必须固定** |
|
||||
| `DISABLE_SCHEDULER` | 未设 | `core/task_manager.py` | 设任意值则**不启动 cron 调度器**(测试用) |
|
||||
| `DATA_BACKUP_DIR` / `DATA_RESTORE_STAGING_DIR` / `DATA_RESTORE_PENDING_DIR` | `data/backups` 等 | `config.py` | 备份/恢复目录改到别处;**自动化测试必须指到临时目录**(否则测试造的待生效恢复任务会在下次重启被当成用户操作消费) |
|
||||
| `MCP_ENABLED` | `1` | `scripts/start.sh` | =0 则不后台拉起 MCP |
|
||||
| `MCP_ALLOW_WRITE` | `0`(脚本内强制 1) | `mcp_server/config.py` | 写操作总开关 |
|
||||
| `MCP_PLATFORM_URL` / `_USER` / `_PASS` | `http://127.0.0.1:18050` / `admin` / 空 | `mcp_server/config.py` | 登录平台的凭据(改过 admin 密码要同步) |
|
||||
|
||||
Reference in New Issue
Block a user