docs: align update design with non-blocking two-phase model
Launcher only applies a staged update + launches (no download at startup); downloading happens in the app's settings dialog. docs/10 §3/§6/§8/§12/§16/§18 rewritten for the two-phase flow (installer.download_and_stage / apply_staged); docs/07 §4.3/§4.4 cover the gear dot + "检查并更新" button; tasks 17.21. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -76,24 +76,25 @@
|
|||||||
|
|
||||||
- `配置` 按钮**不是编号页签**,不参与 `1/2/3` 顺序;样式低调(图标 + 文字,悬停出强调色)。
|
- `配置` 按钮**不是编号页签**,不参与 `1/2/3` 顺序;样式低调(图标 + 文字,悬停出强调色)。
|
||||||
- 点击打开**设置对话框**(`QDialog`,见 4.4),不进入新的内容页(页签栏当前不切换内容面板)。
|
- 点击打开**设置对话框**(`QDialog`,见 4.4),不进入新的内容页(页签栏当前不切换内容面板)。
|
||||||
- 之所以现在提供集中设置入口:在线更新需要配置更新地址与凭据,属于「确有集中设置需求」的情形(区别于之前移除的点了无反应的占位按钮)。
|
- **有新版本角标**:启动时后台检查到新版,在 `配置` 按钮上点亮红点(文字加「●」、变红加粗),提示用户去设置里更新。**不弹阻塞横幅**(详见 `docs/10` §12)。
|
||||||
|
|
||||||
### 4.4 设置对话框
|
### 4.4 设置对话框
|
||||||
|
|
||||||
集中编辑在线更新相关配置,对应 `app_config.json` 的 `update_source` / `update_user` / `update_pass`(见 `docs/02-prd.md`、`docs/10-lan-update.md`)。
|
集中编辑在线更新配置并触发更新,对应 `app_config.json` 的 `update_source` / `update_user` / `update_pass`(见 `docs/02-prd.md`、`docs/10-lan-update.md`)。
|
||||||
|
|
||||||
字段与控件:
|
字段与控件:
|
||||||
|
|
||||||
- `更新地址`:`update_source` 的 HTTP(S) 地址。
|
- `更新地址`:`update_source` 的 HTTP(S) 地址。
|
||||||
- `账号` / `密码`:HTTP Basic Auth 凭据;密码框默认掩码,提供「显示」切换。
|
- `账号` / `密码`:HTTP Basic Auth 凭据;密码框默认掩码,提供「显示」切换。
|
||||||
- `测试连接`:点击用 `services.update_service.check_for_update` 实拉一次清单,反馈「已是最新 / 发现新版本 vX.Y.Z / 连接失败」,便于保存前验证。
|
- `测试连接`:用 `update_service.load_manifest` 实拉一次清单,反馈「已是最新 / 发现新版本 vX.Y.Z / 连接失败」,便于保存前验证。
|
||||||
- `当前版本`:只读显示 `APP_VERSION`。
|
- `当前版本` + 更新状态:只读显示 `APP_VERSION`,并提示是否有新版。
|
||||||
|
- `检查并更新`:点击在后台线程 `check_for_update` → `installer.download_and_stage` 下载并暂存到 `staging\app.new`,完成提示「已下载 vX,下次启动生效」。**下载期间主界面照常可用,不阻塞;切换由启动器下次启动完成**(运行中 `app\` 无法替换,见 `docs/10` §3/§8)。
|
||||||
- 一行小字安全提示:建议使用**只读账号**、生产环境走 HTTPS(凭据为明文存储)。
|
- 一行小字安全提示:建议使用**只读账号**、生产环境走 HTTPS(凭据为明文存储)。
|
||||||
|
|
||||||
行为与架构:
|
行为与架构:
|
||||||
|
|
||||||
- 对话框是**纯 UI**:打开时由 `main_window` 注入当前配置值;保存时把新值发回主窗口,由主窗口集中 `save_config`,UI 控件不直接读写配置文件(遵守 `docs/05` 4.12 与 `docs/04` 第 6 节)。
|
- 对话框是**纯 UI**(配置部分):打开时由 `main_window` 注入当前配置值;保存时把新值发回主窗口,由主窗口集中 `save_config`,UI 控件不直接读写配置文件(遵守 `docs/05` 4.12 与 `docs/04` 第 6 节)。下载/暂存经 `services` 层完成(与其它面板调用 service 一致)。
|
||||||
- 保存后主窗口重新触发一次在线更新检查(横幅按新配置刷新)。配置写入 `~/.cmbot/config/app_config.json`,启动器下次启动自然读到,两端一致。
|
- 保存后主窗口重新触发一次更新检查(角标按新配置刷新)。配置写入 `~/.cmbot/config/app_config.json`,启动器下次启动自然读到,两端一致。
|
||||||
- 取消不改动配置。
|
- 取消不改动配置。
|
||||||
|
|
||||||
## 5. 左侧素材栏
|
## 5. 左侧素材栏
|
||||||
|
|||||||
+64
-58
@@ -35,17 +35,20 @@
|
|||||||
|
|
||||||
## 3. 总体架构
|
## 3. 总体架构
|
||||||
|
|
||||||
核心约束:**运行中的 .exe 无法被覆盖。** 因此「替换」必须发生在主程序未运行时,由一个独立的**启动器(Launcher)**在主程序启动前完成。
|
核心约束:**运行中的 .exe 无法被覆盖**,且运行时也无法重命名它所在的 `app\` 文件夹。因此把更新拆成**两个阶段**,分别在「app 运行时」和「app 未运行时」执行:
|
||||||
|
|
||||||
采用「**固定 app 目录 + 启动器 + app.old 回滚目录**」方案:新版本先下载、校验并解压到 `staging\app.new\`,确认完整后再把当前 `app\` 改名为 `app.old\`,最后把 `app.new\` 改名为 `app\` 并启动。**不做边下载边覆盖,也不直接覆盖安装根目录。**
|
- **下载暂存(app 运行时)**:主程序内(手动「更新」按钮,未来可后台)下载新版 zip、校验 SHA-256、解压到 `staging\app.new\`。**只暂存,不切换**——此刻 `app\` 正被占用。
|
||||||
|
- **应用切换(启动时)**:下次启动,由独立的**启动器 `Launcher.exe`** 在拉起主程序之前,把 `staging\app.new\` 切换为 `app\`(旧的改名 `app.old\`)。此刻主程序尚未运行,可安全重命名目录。
|
||||||
|
|
||||||
|
所以分工是:**启动器只负责「应用已暂存好的更新」+ 启动,启动时不联网、不下载;下载发生在 app 内。**
|
||||||
|
|
||||||
优点:
|
优点:
|
||||||
|
|
||||||
- 避免文件锁冲突——启动器先更新,确认主程序未运行后才替换 `app\`。
|
- **启动永不阻塞**——启动器不下载,只做一次本地秒级目录切换(或直接启动)。
|
||||||
- 避免半覆盖——新版必须先完整落到 `staging\app.new\`,校验成功后才做目录切换。
|
- 避免文件锁冲突——切换发生在主程序未运行时,不就地覆盖运行中的文件。
|
||||||
- 回滚清晰——`app.old\` 保留上一个可用版本,切换失败时可改回 `app\`。
|
- 避免半覆盖——新版必须先完整落到 `staging\app.new\` 并校验通过,才在启动时切换。
|
||||||
- 目录更简洁——用户常见入口固定为 `Launcher.exe`,主程序固定在 `app\CMBot.exe`。
|
- 回滚清晰——`app.old\` 保留上一个可用版本,切换失败时可改回 `app\`。
|
||||||
- 与 `onedir` 天然契合——`app\` 是一份完整 `onedir` 产物(打成 zip 下载、解压后整体切换)。
|
- 与 `onedir` 天然契合——`app\` 是一份完整 `onedir` 产物(打成 zip 下载、解压后整体切换)。
|
||||||
|
|
||||||
## 4. 安装目录结构
|
## 4. 安装目录结构
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
<任意可写目录>\CMBot\ # 例如 D:\CMBot,InstallRoot = Launcher.exe 所在目录
|
<任意可写目录>\CMBot\ # 例如 D:\CMBot,InstallRoot = Launcher.exe 所在目录
|
||||||
Launcher.exe # 用户双击入口:检查更新 → 切换 → 启动 app
|
Launcher.exe # 用户双击入口:应用已暂存的更新(若有)→ 启动 app
|
||||||
app\ # 当前主程序目录,一份完整 onedir 产物
|
app\ # 当前主程序目录,一份完整 onedir 产物
|
||||||
CMBot.exe
|
CMBot.exe
|
||||||
version.txt # 当前 app 版本号,如 1.1.0
|
version.txt # 当前 app 版本号,如 1.1.0
|
||||||
@@ -75,7 +78,7 @@
|
|||||||
|
|
||||||
说明:
|
说明:
|
||||||
|
|
||||||
- `Launcher.exe`:版本检查、下载、校验、切换、启动主程序。**Python + PyInstaller onefile** 编译,逻辑稳定,本身不参与自更新(见第 11 节)。
|
- `Launcher.exe`:**只「应用已暂存的更新 + 启动」**——若 `staging\app.new\` 有就绪的新版则秒切,否则直接启动 `app\CMBot.exe`;启动时不下载、不联网。**Python + PyInstaller onefile** 编译,逻辑稳定,本身不参与自更新(见第 11 节)。下载由主程序内完成。
|
||||||
- `app\`:当前要启动的程序目录。更新成功后,新版目录整体替换为新的 `app\`。
|
- `app\`:当前要启动的程序目录。更新成功后,新版目录整体替换为新的 `app\`。
|
||||||
- `app\version.txt`:纯文本,仅存当前 `app\` 的版本号。由发布脚本从 `src/version.py` 写入,供启动器比较本地版本。
|
- `app\version.txt`:纯文本,仅存当前 `app\` 的版本号。由发布脚本从 `src/version.py` 写入,供启动器比较本地版本。
|
||||||
- `app\config\`:出厂默认配置/模板,仅作首次运行的播种来源,运行时不读写。
|
- `app\config\`:出厂默认配置/模板,仅作首次运行的播种来源,运行时不读写。
|
||||||
@@ -107,15 +110,15 @@
|
|||||||
|
|
||||||
## 6. 更新时机
|
## 6. 更新时机
|
||||||
|
|
||||||
仅在**启动时**由启动器检查并安装,不做运行期后台更新。
|
- **检查**:主程序**启动时**后台线程检查一次(非阻塞);发现新版只点亮设置入口的角标,不打断使用。
|
||||||
|
- **下载暂存**:用户在设置里点「更新」时进行(app 运行期);下载/校验不阻塞主界面,完成后暂存待应用。
|
||||||
|
- **应用切换**:仅在**下次启动**由启动器完成(主程序未运行时)。
|
||||||
|
|
||||||
理由:
|
理由:
|
||||||
|
|
||||||
- 桌面工具天然会重启,启动时更新覆盖绝大多数场景。
|
- 启动器只做秒级切换,**启动永不阻塞**;下载与启动解耦。
|
||||||
- 避免运行期替换带来的文件锁与状态一致性问题。
|
- 切换发生在主程序未运行时,避免文件锁与目录占用问题。
|
||||||
- 主程序无需内置更新逻辑,职责更清晰(呼应 `docs/04` 单一职责)。
|
- 检查失败/更新源不可达时静默跳过,不影响启动与使用。
|
||||||
|
|
||||||
更新检查必须**非阻塞要害路径**:更新源不可达时跳过更新,直接启动本地当前版本。
|
|
||||||
|
|
||||||
## 7. 更新源与版本清单
|
## 7. 更新源与版本清单
|
||||||
|
|
||||||
@@ -159,30 +162,30 @@ http://cm.xiapi.com/
|
|||||||
|
|
||||||
## 8. 更新流程
|
## 8. 更新流程
|
||||||
|
|
||||||
启动器每次启动执行:
|
分两阶段,对应两个执行体(`services/installer.py` 提供两个函数)。
|
||||||
|
|
||||||
1. **读取本地版本**:读 `app\version.txt`;不存在则视为无本地版本或旧版扁平结构。
|
### 8.1 阶段一:下载暂存(主程序内,`installer.download_and_stage`)
|
||||||
2. **读取远端清单**:HTTP GET `manifest.json`(带 Basic Auth)。
|
|
||||||
- 失败(网络不可达、4xx/5xx、JSON 格式错误)→ 记录日志,**跳过更新**,直接进入第 7 步启动本地当前版本。
|
|
||||||
3. **版本比较**:远端 `version` ≤ 本地版本 → 无需更新,进入第 7 步。
|
|
||||||
4. **下载 zip 到 staging**:HTTP GET `manifest.url` 下载到 `staging\<version>.zip`(带 Basic Auth)。
|
|
||||||
- 支持断点续传(更新源返回 `Accept-Ranges: bytes`)、失败重试。
|
|
||||||
- 下载期间展示简易进度/启动画面(见第 12 节)。
|
|
||||||
5. **校验并解压**:
|
|
||||||
- 校验下载文件的 SHA-256 与 `manifest.sha256` 一致(不一致 = 下载损坏或被篡改)。
|
|
||||||
- 解压到 `staging\app.new\`。
|
|
||||||
- 校验 `staging\app.new\CMBot.exe` 存在,且 `staging\app.new\version.txt` 与 `manifest.version` 一致。
|
|
||||||
- 任一步失败 → 删除临时文件/目录,记录日志,降级启动本地当前版本(若有)。
|
|
||||||
6. **目录级切换**:
|
|
||||||
1. 确认主程序未运行;如检测到 `CMBot.exe` 已运行,跳过更新并启动/提示现有版本。
|
|
||||||
2. 若 `app.old\` 存在,先删除;删除失败则跳过更新,避免无回滚目录。
|
|
||||||
3. 将当前 `app\` 重命名为 `app.old\`。
|
|
||||||
4. 将 `staging\app.new\` 重命名为 `app\`。
|
|
||||||
5. 若第 4 步失败,立即将 `app.old\` 改回 `app\`,记录日志并降级。
|
|
||||||
7. **启动主程序**:启动 `app\CMBot.exe`。数据根由主程序自身解析为 `~/.cmbot`(见第 5 节),启动器无需注入 `CMBOT_DATA_DIR`(如需指定可经该环境变量覆盖)。
|
|
||||||
8. **清理**:成功切换后清理 `staging\`(zip 与临时目录);`app.old\` 默认保留一个旧版本用于回滚(见第 13 节)。
|
|
||||||
|
|
||||||
任一步失败都不得使系统进入「无可启动版本」状态:只要本地存在一个可用版本,就以它启动。
|
主程序内点「更新」时执行:
|
||||||
|
|
||||||
|
1. **取清单**:HTTP GET `manifest.json`(带 Basic Auth),与 `APP_VERSION` 比较;非更新/不可达 → 提示「已是最新或连接失败」,不动 `app\`。
|
||||||
|
2. **可写性检测**:安装根不可写 → 报「安装目录不可写」并终止(不影响 app 继续使用)。
|
||||||
|
3. **下载**:GET `manifest.url` 到 `staging\<version>.zip`(带 Basic Auth)。
|
||||||
|
4. **校验**:`size`(若有)与 SHA-256 必须与清单一致,否则丢弃报错。
|
||||||
|
5. **解压 + 校验包**:解压,定位含 `CMBot.exe` 的包根,校验其 `version.txt == manifest.version`,移动为 `staging\app.new\`。
|
||||||
|
6. 成功 → 提示「已下载 vX,下次启动生效」。**全程不触碰 `app\`**,主程序照常使用。
|
||||||
|
|
||||||
|
### 8.2 阶段二:应用切换(启动器,`installer.apply_staged`)
|
||||||
|
|
||||||
|
下次启动 `Launcher.exe` 时执行(主程序未运行):
|
||||||
|
|
||||||
|
1. **首次播种**:`~/.cmbot/config` 缺文件时,从 `app\config\` 拷出厂默认。
|
||||||
|
2. **检查暂存**:`staging\app.new\` 无就绪包 → 跳过,直接启动当前 `app\`。
|
||||||
|
3. **新旧校验**:暂存版本 ≤ 当前 `app\version.txt` → 丢弃暂存(防降级/重复),直接启动。
|
||||||
|
4. **目录切换**:删除旧 `app.old\` → `app\` 改名为 `app.old\` → `staging\app.new\` 改名为 `app\`;第 3 步式的改名失败(如 exe 仍被占用)→ 立即把 `app.old\` 改回 `app\` 回滚。
|
||||||
|
5. **启动**:启动 `app\CMBot.exe`。数据根由主程序自身解析为 `~/.cmbot`(见第 5 节)。
|
||||||
|
|
||||||
|
任一步失败都不得使系统进入「无可启动版本」状态:只要本地存在一个可用 `app\`,就以它启动。
|
||||||
|
|
||||||
## 9. 安装位置与权限
|
## 9. 安装位置与权限
|
||||||
|
|
||||||
@@ -218,10 +221,11 @@ http://cm.xiapi.com/
|
|||||||
|
|
||||||
## 12. 用户体验
|
## 12. 用户体验
|
||||||
|
|
||||||
- 启动器在下载/安装期间显示轻量启动画面与进度,避免「双击无反应」。
|
- **发现新版**:主程序启动后台检查,有新版则在 `⚙ 配置` 按钮上点亮红点(不弹阻塞横幅、不打断工作)。
|
||||||
- 更新完成后正常进入主程序;可选地在主程序内展示一次本次更新说明(`manifest.notes`)。
|
- **手动更新**:用户打开设置 → 点「检查并更新」→ 下载在后台进行,完成后提示「已下载 vX,下次启动生效」。下载期间主界面照常可用。
|
||||||
- 更新源不可达时静默降级启动,不打断用户;仅记录日志。
|
- **应用**:下次双击 `Launcher.exe` 时秒切到新版,几乎无感;启动**永不因下载而卡住**。
|
||||||
- 文案遵循界面语气:说明发生了什么、下一步做什么,不做无谓道歉(呼应 `docs/07` 文案规范)。
|
- 更新源不可达 / 安装目录不可写时,给出明确文案并降级,不静默失败也不阻断使用。
|
||||||
|
- 文案遵循界面语气:说明发生了什么、下一步做什么,不做无谓道歉(呼应 `docs/07` 文案规范)。
|
||||||
|
|
||||||
## 13. 失败处理与回滚
|
## 13. 失败处理与回滚
|
||||||
|
|
||||||
@@ -252,25 +256,26 @@ HTTP 在线更新比内网共享面临更高风险,按以下层次防护:
|
|||||||
分阶段落地,每阶段可独立验证:
|
分阶段落地,每阶段可独立验证:
|
||||||
|
|
||||||
1. **地基**:数据目录分离(第 5 节)。任何更新方案的前提。✅ 已实现。
|
1. **地基**:数据目录分离(第 5 节)。任何更新方案的前提。✅ 已实现。
|
||||||
2. **只读通知**:启动时读取 `manifest.json` 比对版本,有新版仅提示。✅ 已实现——`services/update_service.py`(`check_for_update` / 版本比较,纯逻辑可测,支持 **`http(s)://` 源 + Basic Auth** 及本地路径;manifest 用 `utf-8-sig` 解码以容忍 BOM)+ 主窗口顶部通知横幅,后台线程检查(不可达不阻塞启动),更新源 / 凭据由 `update_source` / `update_user` / `update_pass` 配置。
|
2. **检测通知**:启动时比对版本,有新版提示。✅ 已实现——`services/update_service.py`(`check_for_update` / 版本比较,支持 **`http(s)://` 源 + Basic Auth** 及本地路径,manifest 用 `utf-8-sig` 容忍 BOM)+ 主窗口后台线程检查 → `⚙ 配置` 按钮点亮角标(非阻塞)。
|
||||||
3. **自动安装(PowerShell 原型,已退休)**:曾以 `scripts/update.ps1` + `scripts/install_local.ps1` 跑通整条流程(HTTP 下载 zip + SHA-256 + 解压 + `app/app.old` 切换),验证了设计可行。该原型已被阶段④取代并删除(git 历史可查)。
|
3. **非阻塞更新(当前方向)**:✅ 已实现——下载与应用拆开,**启动不阻塞**。
|
||||||
4. **`Launcher.exe` + `~/.cmbot`(当前方向)**:✅ 已实现——`src/launcher.py`(Python + PyInstaller onefile,复用 `services/update_service.py`:下载 zip + SHA-256 + 解压 + `app/app.old` 切换 + 安装根可写性检测 + 首次播种默认模板到 `~/.cmbot`);`scripts/build.ps1` 产出 `Launcher.exe` + 便携布局 + 两个 zip(自更新载荷 / 便携安装包)+ 无 BOM `manifest.json`;`get_data_dir()` 三级回退(见第 5 节)。已用本地 HTTP server 真实端到端验证(下载→校验→切换→播种)。**待做**:在 Windows 上真实 PyInstaller 构建 + 解压到 D 盘真机端到端实测。
|
- `services/installer.py`:`download_and_stage()`(app 内下载→SHA-256→解压→`staging\app.new`)、`apply_staged()`(启动器秒切 `app/app.old`,含「不比当前新则丢弃」「移动失败回滚」)、`staged_version()`。
|
||||||
5. **强制更新与保留策略**:补全 `mandatory` / `min_supported` 与 `app.old` 回滚策略。⛔ 未做。
|
- `src/launcher.py`:瘦身为 seed + `apply_staged` + 启动,**不联网**。
|
||||||
|
- `src/app/widgets/settings_dialog.py`:「检查并更新」按钮 → `download_and_stage` → 「下次启动生效」。
|
||||||
|
- `get_data_dir()` 三级回退(见第 5 节);`scripts/build.ps1` 产出 `Launcher.exe` + 便携布局 + 两个 zip + 无 BOM `manifest.json`。
|
||||||
|
- 已用本地 HTTP server 真实端到端验证(下载暂存 → app 不动 → 启动器秒切)。**待做**:Windows 真实 PyInstaller 构建 + 解压 D 盘真机实测。
|
||||||
|
4. **强制更新与保留策略**:补全 `mandatory` / `min_supported` 与 `app.old` 保留策略。⛔ 未做。
|
||||||
|
|
||||||
启动器(`Launcher.exe`,目标)要点:
|
关键约定:
|
||||||
|
|
||||||
- 安装根 = `Launcher.exe` 所在目录(便携,不限定 `%LOCALAPPDATA%`);`-NoLaunch` 等价开关用于测试。
|
- 安装根 = `Launcher.exe` 所在目录(便携,不限定 `%LOCALAPPDATA%`)。
|
||||||
- 从 `~/.cmbot/config/app_config.json` 读 `update_source` / `update_user` / `update_pass`,与 stage ② 同源(复用 `update_service`)。
|
- 配置 `update_source` / `update_user` / `update_pass` 从 `~/.cmbot/config/app_config.json` 读(app 与启动器同源)。
|
||||||
- 读取本地 `app\version.txt` → GET `manifest.json` → 比较版本 → 下载 `manifest.url` 到 `staging\<ver>.zip`(带 Basic Auth)→ 校验 SHA-256 → 解压到 `staging\app.new\` → 校验 `CMBot.exe` 与 `version.txt` → `app` 改名 `app.old` → `app.new` 改名 `app`。
|
- **启动器不下载**:只在 `staging\app.new\` 就绪时秒切,否则直接启动;首次播种 `app\config\` 默认模板到 `~/.cmbot`。
|
||||||
- 启动前检测安装根可写性;首次运行把 `app\config\` 默认配置/模板播种到 `~/.cmbot`。
|
- 任何失败都降级启动本地现版本,绝不进入「无可启动版本」;回滚 = 将 `app.old\` 改回 `app\`。
|
||||||
- 启动 `app\CMBot.exe`(主程序自行解析数据根为 `~/.cmbot`,无需注入 `CMBOT_DATA_DIR`)。
|
|
||||||
- 任何失败(网络不可达 / 4xx-5xx / SHA-256 不符 / 解压失败 / 坏 manifest / 安装根不可写)都降级启动本地现版本,绝不进入「无可启动版本」。
|
|
||||||
- 回滚 = 将 `app.old\` 改回 `app\`。
|
|
||||||
|
|
||||||
## 17. 暂不做
|
## 17. 暂不做
|
||||||
|
|
||||||
- 增量二进制差分更新。
|
- 增量二进制差分更新。
|
||||||
- 运行期后台静默更新。
|
- 运行期后台**静默下载**(当前为手动「更新」按钮触发;后台自动下载可后续加)。
|
||||||
- 按电脑名/用户的差异化版本下发。
|
- 按电脑名/用户的差异化版本下发。
|
||||||
- 更新源防篡改签名(Ed25519 签名 manifest,见第 14 节,作为后续加固项)。
|
- 更新源防篡改签名(Ed25519 签名 manifest,见第 14 节,作为后续加固项)。
|
||||||
|
|
||||||
@@ -278,9 +283,10 @@ HTTP 在线更新比内网共享面临更高风险,按以下层次防护:
|
|||||||
|
|
||||||
## 18. 验收要点
|
## 18. 验收要点
|
||||||
|
|
||||||
- 启动器能在有新版本时自动完成安装并启动新版本。
|
- 有新版时 `⚙ 配置` 按钮亮角标,启动不被打断。
|
||||||
- 安装后用户自定义模板与偏好(`data\`)完整保留。
|
- 设置里「检查并更新」能下载暂存,下载期间主界面照常可用,完成提示「下次启动生效」。
|
||||||
- 更新源不可达时仍能启动本地当前版本。
|
- 下载暂存期间 `app\` 不被改动;下次启动 `Launcher.exe` 秒切到新版,`app.old\` 保留旧版。
|
||||||
- 强制更新失败时不启动旧版本并给出提示。
|
- 安装(切换)后用户自定义模板与偏好(`~/.cmbot`)完整保留。
|
||||||
|
- 更新源不可达 / 安装目录不可写时给出明确提示并降级,仍能启动本地当前版本。
|
||||||
- 可通过把 `app.old\` 改回 `app\` 回滚到上一版本。
|
- 可通过把 `app.old\` 改回 `app\` 回滚到上一版本。
|
||||||
- 安装目录在非管理员权限下可正常更新。
|
- 安装目录在非管理员权限下可正常更新。
|
||||||
|
|||||||
@@ -967,6 +967,27 @@
|
|||||||
- [ ] GUI 实测:测试连接正确反馈三种结果
|
- [ ] GUI 实测:测试连接正确反馈三种结果
|
||||||
- [ ] GUI 实测:改完无需重启 launcher,app 内横幅按新配置刷新
|
- [ ] GUI 实测:改完无需重启 launcher,app 内横幅按新配置刷新
|
||||||
|
|
||||||
|
### 17.21 更新改为非阻塞:app 内下载 + 启动器只应用
|
||||||
|
|
||||||
|
前置阅读:
|
||||||
|
|
||||||
|
- `docs/10-lan-update.md`(§3 两阶段架构、§6 时机、§8 流程、§12 体验)
|
||||||
|
- `docs/07-ui-design.md`(4.3 角标、4.4 设置对话框「检查并更新」)
|
||||||
|
|
||||||
|
背景:
|
||||||
|
|
||||||
|
- 原启动器在启动时下载更新、阻塞进主界面(57MB 包看着像卡死)。改为「下载在 app 内、切换在启动器」,启动永不阻塞。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
- [x] `services/installer.py`:`download_and_stage()`(app 运行时下载→SHA-256→解压→`staging\app.new`)、`apply_staged()`(启动器秒切 `app/app.old`,含「不比当前新则丢弃」「移动失败回滚」)、`staged_version()`、`is_writable`
|
||||||
|
- [x] `launcher.py` 瘦身:seed + `apply_staged` + 启动,不联网;`tests/test_launcher.py` 重写
|
||||||
|
- [x] `main_window`:发现新版点亮 `⚙ 配置` 红点(删除旧阻塞横幅与「打开更新目录」)
|
||||||
|
- [x] `settings_dialog`:加「检查并更新」→ 下载暂存 → 「下次启动生效」
|
||||||
|
- [x] `tests/test_installer.py` 11 个单测 + 本地 HTTP server 真实端到端(下载暂存→app 不动→应用切换)
|
||||||
|
- [x] 文档:`docs/10` §3/§6/§8/§12/§16/§18、`docs/07` §4.3/§4.4 对齐
|
||||||
|
- [ ] GUI 实测:角标点亮 → 设置里更新 → 重启生效(真机)
|
||||||
|
|
||||||
## 18. 后续暂缓任务
|
## 18. 后续暂缓任务
|
||||||
|
|
||||||
以下任务第一阶段暂不做,后续需要时再新增设计文档:
|
以下任务第一阶段暂不做,后续需要时再新增设计文档:
|
||||||
|
|||||||
Reference in New Issue
Block a user