xb-init 2.2.3__tar.gz → 2.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. {xb_init-2.2.3 → xb_init-2.2.4}/PKG-INFO +5 -1
  2. {xb_init-2.2.3 → xb_init-2.2.4}/README.md +4 -0
  3. {xb_init-2.2.3 → xb_init-2.2.4}/pyproject.toml +1 -1
  4. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/root/AGENTS.md.j2 +3 -1
  5. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/root/README.md.j2 +3 -0
  6. xb_init-2.2.4/xb/templates/root/docs/images/feishu-folder.png +0 -0
  7. xb_init-2.2.4/xb/templates/root/docs/images/feishu-permission.png +0 -0
  8. xb_init-2.2.4/xb/templates/root/docs/images/release-upload.png +0 -0
  9. xb_init-2.2.4/xb/templates/root/docs/images/update-dialog.png +0 -0
  10. xb_init-2.2.4/xb/templates/root/docs//350/207/252/345/212/250/346/233/264/346/226/260/350/257/264/346/230/216.md +322 -0
  11. {xb_init-2.2.3 → xb_init-2.2.4}/xb/utils/template_engine.py +5 -0
  12. {xb_init-2.2.3 → xb_init-2.2.4}/.gitignore +0 -0
  13. {xb_init-2.2.3 → xb_init-2.2.4}/docs/screenshot.png +0 -0
  14. {xb_init-2.2.3 → xb_init-2.2.4}/tests/test_cli.py +0 -0
  15. {xb_init-2.2.3 → xb_init-2.2.4}/tests/test_update_templates.py +0 -0
  16. {xb_init-2.2.3 → xb_init-2.2.4}/tests/test_upgrade.py +0 -0
  17. {xb_init-2.2.3 → xb_init-2.2.4}/uv.lock +0 -0
  18. {xb_init-2.2.3 → xb_init-2.2.4}/xb/__init__.py +0 -0
  19. {xb_init-2.2.3 → xb_init-2.2.4}/xb/cli.py +0 -0
  20. {xb_init-2.2.3 → xb_init-2.2.4}/xb/commands/__init__.py +0 -0
  21. {xb_init-2.2.3 → xb_init-2.2.4}/xb/commands/build.py +0 -0
  22. {xb_init-2.2.3 → xb_init-2.2.4}/xb/commands/dev.py +0 -0
  23. {xb_init-2.2.3 → xb_init-2.2.4}/xb/commands/doctor.py +0 -0
  24. {xb_init-2.2.3 → xb_init-2.2.4}/xb/commands/init.py +0 -0
  25. {xb_init-2.2.3 → xb_init-2.2.4}/xb/commands/upgrade.py +0 -0
  26. {xb_init-2.2.3 → xb_init-2.2.4}/xb/commands/version.py +0 -0
  27. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/api/__init__.py.j2 +0 -0
  28. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/api/config.py.j2 +0 -0
  29. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/api/ports.py.j2 +0 -0
  30. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/api/terminal/__init__.py.j2 +0 -0
  31. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/api/terminal/routes.py.j2 +0 -0
  32. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/api/update/__init__.py.j2 +0 -0
  33. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/app_version.py.j2 +0 -0
  34. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/backend_build.py.j2 +0 -0
  35. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/main.py.j2 +0 -0
  36. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/managers/__init__.py.j2 +0 -0
  37. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/managers/logger_manager.py.j2 +0 -0
  38. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/managers/path_manager.py.j2 +0 -0
  39. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/managers/secret_obfuscator.py.j2 +0 -0
  40. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/managers/sudoers_manager.py.j2 +0 -0
  41. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/requirements.txt.j2 +0 -0
  42. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/services/__init__.py.j2 +0 -0
  43. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/services/feishu_client.py.j2 +0 -0
  44. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/backend/services/updater.py.j2 +0 -0
  45. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/configs/config_changes.json.j2 +0 -0
  46. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/configs/global_config.yaml.j2 +0 -0
  47. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/configs/secrets.yaml.example.j2 +0 -0
  48. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/configs/secrets.yaml.j2 +0 -0
  49. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/launcher.js.j2 +0 -0
  50. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/main.js.j2 +0 -0
  51. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/package.json.j2 +0 -0
  52. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/port_diagnostics.js.j2 +0 -0
  53. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/preload.js.j2 +0 -0
  54. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/prepare.js.j2 +0 -0
  55. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/resources/postinst.py.j2 +0 -0
  56. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/resources/postrm.py.j2 +0 -0
  57. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/electron/resources/uninstaller.nsh.j2 +0 -0
  58. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/index.html.j2 +0 -0
  59. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/package.json.j2 +0 -0
  60. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/App.vue.j2 +0 -0
  61. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/ConfigSetup.vue.j2 +0 -0
  62. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/DashboardHome.vue.j2 +0 -0
  63. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/FileManager.vue.j2 +0 -0
  64. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/GitVersionBadge.vue.j2 +0 -0
  65. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/RefreshButton.vue.j2 +0 -0
  66. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/TerminalPanel.vue.j2 +0 -0
  67. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/TerminalView.vue.j2 +0 -0
  68. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/ThemeToggle.vue.j2 +0 -0
  69. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/version/CommitHistoryPanel.vue.j2 +0 -0
  70. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/components/version/UpdatePanel.vue.j2 +0 -0
  71. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/composables/useTerminalSessions.js.j2 +0 -0
  72. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/composables/useUpdate.js.j2 +0 -0
  73. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/main.js.j2 +0 -0
  74. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/src/style.css.j2 +0 -0
  75. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/frontend/vite.config.js.j2 +0 -0
  76. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/root/.gitignore.j2 +0 -0
  77. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/root/build.py.j2 +0 -0
  78. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/root/dev.py.j2 +0 -0
  79. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/root/pyproject.toml.j2 +0 -0
  80. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/scripts/example.py.j2 +0 -0
  81. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/scripts/feishu_upload.py.j2 +0 -0
  82. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/version/hooks/pre-commit.py.j2 +0 -0
  83. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/version/scripts/gui_version_manager.py.j2 +0 -0
  84. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/version/scripts/install_hooks.py.j2 +0 -0
  85. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/version/scripts/version_manager.py.j2 +0 -0
  86. {xb_init-2.2.3 → xb_init-2.2.4}/xb/templates/version/scripts/web_version_manager.py.j2 +0 -0
  87. {xb_init-2.2.3 → xb_init-2.2.4}/xb/utils/__init__.py +0 -0
  88. {xb_init-2.2.3 → xb_init-2.2.4}/xb/utils/click_helpers.py +0 -0
  89. {xb_init-2.2.3 → xb_init-2.2.4}/xb/utils/validators.py +0 -0
  90. {xb_init-2.2.3 → xb_init-2.2.4}/xb/utils/version_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: xb-init
3
- Version: 2.2.3
3
+ Version: 2.2.4
4
4
  Summary: Project management tool for UV + FastAPI + Vue3 + Electron desktop applications
5
5
  Project-URL: Homepage, https://github.com/xuebli/xb
6
6
  Project-URL: Documentation, https://github.com/xuebli/xb#readme
@@ -159,6 +159,10 @@ xb init demo --icon ./my-icon.png
159
159
 
160
160
  ## 应用内自动更新(`--update`)
161
161
 
162
+ > 详细原理与 AI 开发者注意事项见模板内
163
+ > **[xb/templates/root/docs/自动更新说明.md](xb/templates/root/docs/自动更新说明.md)**
164
+ > (生成的项目会自带 `docs/自动更新说明.md` 与配图)。
165
+
162
166
  `xb init demo --update` 会让生成的项目内置与 [leo_stm32_board_test](../leo_stm32_board_test) 同源的自动更新能力,
163
167
  以飞书云盘的一个文件夹作为发布"货架":
164
168
 
@@ -130,6 +130,10 @@ xb init demo --icon ./my-icon.png
130
130
 
131
131
  ## 应用内自动更新(`--update`)
132
132
 
133
+ > 详细原理与 AI 开发者注意事项见模板内
134
+ > **[xb/templates/root/docs/自动更新说明.md](xb/templates/root/docs/自动更新说明.md)**
135
+ > (生成的项目会自带 `docs/自动更新说明.md` 与配图)。
136
+
133
137
  `xb init demo --update` 会让生成的项目内置与 [leo_stm32_board_test](../leo_stm32_board_test) 同源的自动更新能力,
134
138
  以飞书云盘的一个文件夹作为发布"货架":
135
139
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "xb-init"
7
- version = "2.2.3"
7
+ version = "2.2.4"
8
8
  description = "Project management tool for UV + FastAPI + Vue3 + Electron desktop applications"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -491,7 +491,9 @@ Windows 下服务不会继承当前 PowerShell 的 stdin,也不会弹出后台
491
491
 
492
492
  ### 条目格式速查
493
493
 
494
- 顶层 key = **变更生效前用户所处的最后版本号**(即本次变更 commit **之前** `electron/package.json` 里的版本;不确定时问用户)。应用条件是 `用户当前版本 <= 块版本 < 要安装的包版本`(见 `backend/services/updater.py` 的 `filter_config_changes`),**不要**写成"引入变更的新版本号"——否则从上一旧版本直升引入版本的更新会被条件二挡掉,迁移失效。例:块 `"1.0.0"` 表示 1.0.0 及更早的用户配置还是旧结构,1.0.0 升 1.1.0 会应用,1.1.0 升 1.2.0 不再应用:
494
+ 顶层 key = **变更生效前用户所处的最后版本号**(即本次变更 commit **之前** `electron/package.json` 里的版本;不确定时问用户)。应用条件是 `用户当前版本 <= 块版本 < 要安装的包版本`(见 `backend/services/updater.py` 的 `filter_config_changes`),**不要**写成"引入变更的新版本号"——否则从上一旧版本直升引入版本的更新会被条件二挡掉,迁移失效。例:块 `"1.0.0"` 表示 1.0.0 及更早的用户配置还是旧结构,1.0.0 升 1.1.0 会应用,1.1.0 升 1.2.0 不再应用。
495
+
496
+ **文件维护约定**:顶层版本块按版本号**从高到低排列**(新条目插在文件最上方,便于快速定位最新迁移);追加条目时用版本号元组倒序重排整个文件,**不要**直接 append 到末尾。(运行时 `filter_config_changes` 会自行按块版本升序应用,文件顺序只影响可读性,但仍必须遵守。)
495
497
 
496
498
  ```json
497
499
  {
@@ -278,6 +278,9 @@ git commit -m "feat: add new feature"
278
278
  应用内置「检查更新 → 下载校验 → 备份配置 → 自动安装」的完整闭环,
279
279
  以飞书云盘的一个文件夹作为发布"货架"。
280
280
 
281
+ > 完整原理、发版/升级全流程截图与配置迁移详解见
282
+ > **[docs/自动更新说明.md](docs/自动更新说明.md)**。
283
+
281
284
  ### 发版(开发电脑)
282
285
 
283
286
  1. 在 `configs/secrets.yaml` 填好 `feishu.app_id` / `app_secret` /
@@ -0,0 +1,322 @@
1
+ # 软件自动更新说明(xb `--update` 功能详解)
2
+
3
+ > 本文详细解释 `xb init demo --update` 生成的**应用内自动更新**功能
4
+ > (对应 xb 仓库提交 `e3d2c72`)。
5
+ > 看完这篇你就明白:新版本是怎么"上架"的、用户的软件是怎么"发现并装上"新版本的、
6
+ > 用户配置为什么不会丢、飞书那边要准备什么、以及**作为 AI 开发者你在这个机制下
7
+ > 必须遵守的三条纪律**。
8
+
9
+ ---
10
+
11
+ ## 一句话总览
12
+
13
+ ```
14
+ 开发电脑把新版本"寄存"到飞书网盘 → 用户的软件自己去飞书"查快递"
15
+ → 发现新版本就"签收下载" → 自动"换新装"(安装) → 重新打开就是新版
16
+ ```
17
+
18
+ 整个过程用户的**配置、密钥、数据都不会丢**(第六节细说为什么)。
19
+
20
+ ---
21
+
22
+ ## 一、这个功能是什么、生成了什么
23
+
24
+ `xb init demo --update` 会在标准脚手架之上额外生成一套完整更新链路,
25
+ 并默认附带 `--sudoers`(Ubuntu 免密安装 deb 用;Windows 会跳过密码输入,
26
+ 运行时 sudo 相关能力自动 no-op)。
27
+
28
+ ### 生成的文件与职责
29
+
30
+ | 位置 | 职责 |
31
+ |------|------|
32
+ | `backend/api/update/__init__.py` | 更新接口:检查 / 信息 / 下载 / 进度 / 安装 |
33
+ | `backend/services/updater.py` | 核心逻辑:版本比较、信息拉取、并发下载、SHA256 校验、配置备份与迁移、双平台安装编排 |
34
+ | `backend/services/feishu_client.py` | 飞书开放平台封装:token 缓存、文件夹列举、4 线程分块下载 |
35
+ | `backend/managers/secret_obfuscator.py` | 敏感值混淆(`obf1:` 前缀,防围观级别) |
36
+ | `backend/app_version.py` | 后端运行时版本号(打包态读不到 package.json,靠它) |
37
+ | `scripts/feishu_upload.py` | 发版端:安装包/版本信息上传飞书、旧文件清理 |
38
+ | `configs/config_changes.json` | **配置迁移清单**(初始为 `{}`,见第五节) |
39
+ | `frontend/src/components/version/UpdatePanel.vue` | 更新弹窗(版本信息 / 更新说明 / 迁移明细 / 进度条) |
40
+ | `frontend/src/components/version/CommitHistoryPanel.vue` | 版本弹窗里的提交历史 |
41
+ | `frontend/src/composables/useUpdate.js` | 前端更新逻辑:启动/每小时定时检查、下载进度轮询 |
42
+ | `GitVersionBadge.vue`(改造) | 顶栏版本号徽章 = 更新入口:点开是"版本弹窗"(提交历史 + 检查更新按钮) |
43
+
44
+ 另外 `build.py` 会多出发布编排(上传询问、进度渲染、`--upload-only`),
45
+ `version_manager` 会在版本 bump 时多同步两个文件(共 4 个)。
46
+
47
+ ---
48
+
49
+ ## 二、角色分工
50
+
51
+ | 角色 | 干什么 | 用到的工具 |
52
+ |------|--------|-----------|
53
+ | **发版者**(开发电脑) | 打包 → 传飞书 → 写"货架标签" | `./build.py -a` 一条命令 |
54
+ | **用软件的人**(产线电脑) | 什么都不用干,软件自己检查、下载、装 | 顶栏版本号徽章 |
55
+
56
+ **比喻**:飞书云盘的一个文件夹就是"货架",安装包(`.deb`/`.exe`)是"货",
57
+ 版本信息文件(`update_ubuntu.json` / `update_windows.json`)是"货架标签"。
58
+ **客户端永远只看标签决定有没有新货。**
59
+
60
+ ---
61
+
62
+ ## 三、飞书侧准备(发版者一次性配置)
63
+
64
+ 1. 在 [飞书开放平台](https://open.feishu.cn) 创建**企业自建应用**,得到
65
+ `app_id` 和 `app_secret`;
66
+ 2. 开通 **`drive:drive`** 权限(最容易漏的一步!):
67
+ - 没有它:上传成功但列举/删除/下载全失败,客户端永远"没有新版本";
68
+ - **开通后必须"创建版本并发布"**,管理员审核通过才生效——只开通不发布 = 没开通;
69
+
70
+ ![飞书开放平台开通 drive:drive 权限](images/feishu-permission.png)
71
+
72
+ 3. 在飞书云盘建一个文件夹当"货架",复制浏览器**完整链接**:
73
+ `https://xxx.feishu.cn/drive/folder/一串字符`;
74
+ **并把第 1 步创建的自建应用添加为该文件夹的协作者**(应用对文件夹没有
75
+ 协作权限时,token 再对也访问不了里面的文件):
76
+
77
+ ![货架文件夹:将自建应用添加为协作者](images/feishu-folder.png)
78
+ 4. 三样东西填进项目的 `configs/secrets.yaml`(已 gitignore,永不进仓库):
79
+
80
+ ```yaml
81
+ feishu:
82
+ app_id: "cli_xxx"
83
+ app_secret: "xxx"
84
+ update_folder_url: "https://xxx.feishu.cn/drive/folder/一串字符"
85
+ ```
86
+
87
+ ---
88
+
89
+ ## 四、发版流程(开发电脑)
90
+
91
+ 一条命令:`./build.py -a`(构建 + 询问上传 + 写标签),
92
+ 或 `./build.py --upload-only` 只传已有安装包。
93
+ 全流程实录(分片上传进度 → 上传结果 → 输入更新说明 → 版本信息上传 → 旧标签清理):
94
+
95
+ ![发版上传全流程](images/release-upload.png)
96
+
97
+ ### 关键机制 1:凭证自动同步与混淆(发版者可以"开箱填明文")
98
+
99
+ 1. 构建开始时,`build.py` 把你本机 `configs/secrets.yaml` 的内容与
100
+ `configs/secrets.yaml.example` 做**深度合并**(真实值覆盖写入,
101
+ **example 独有的占位键原样保留**);
102
+ 2. Electron 打包前,把 example 里的敏感键(键名含 secret/password/token)
103
+ 混淆成 `obf1:xxxx` 密文——打进安装包的就是密文,用户**开箱即用**;
104
+ 3. `sudo_password` 是唯一例外:**无论何时强制留空**。它是你本机的管理员密码,
105
+ 混淆也不允许随包分发,客户端用配置界面设置自己的密码;
106
+ 4. 构建结束(无论成败)example 自动恢复为留空模板——**真实凭证永远只在你本机**。
107
+
108
+ > 混淆是"防围观"级别(XOR+Base64,可被软件自动还原),防好奇不防逆向。
109
+
110
+ ### 关键机制 2:上传与"货架标签"
111
+
112
+ - 安装包大于 20MB 自动走**分片上传**(4MB/片,失败重试),终端按 5 秒一行
113
+ 显示带时间戳的进度,上传结束立即补打 100%(不拖到提示语之后);
114
+ - **清理策略**:`update_*.json` 同名旧文件自动删除(防止客户端误读残缺旧标签);
115
+ **安装包只记录不删除**——货架上保留全部历史版本,方便回滚;
116
+ - 更新说明直接回车 = 采用最新一条 git 提交信息;
117
+ - 标签内容:版本号、发布日期、更新说明(按行数组)、安装包的
118
+ `file_token`(应用内下载唯一入口)/ URL(仅展示)/ SHA256 / 大小,
119
+ 以及第五节的 `config_changes` 全文。
120
+
121
+ ---
122
+
123
+ ## 五、配置迁移清单 `configs/config_changes.json`(AI 开发者重点!)
124
+
125
+ **这是整个功能里唯一需要你持续手工维护的文件,也是新手最容易写错的地方。**
126
+
127
+ ### 5.1 它解决什么问题
128
+
129
+ 用户机器上的配置文件是"存量"——新版本代码改了配置结构(新增键/删除键/改默认值),
130
+ 用户升级后配置文件**不会自动重写**。迁移清单就是告诉安装器:
131
+ "从版本 X 升上来的用户,配置要怎么改"。
132
+
133
+ ### 5.2 顶层 key 的语义(⚠️ 最容易写错)
134
+
135
+ **顶层 key = 变更生效前用户所处的最后版本号**(即引入变更的那次 commit **之前**
136
+ `electron/package.json` 里的版本),**不是**"引入变更的新版本号"。
137
+
138
+ 运行时应用条件(`backend/services/updater.py` 的 `filter_config_changes`):
139
+
140
+ ```
141
+ 用户当前版本 <= 块版本 < 要安装的包版本
142
+ ```
143
+
144
+ 为什么不能写新版本号:假设变更随 1.1.0 发布、块却写成 `"1.1.0"`,
145
+ 用户从 1.0.0 **直升 1.1.0** 时 `目标(1.1.0) > 块(1.1.0)` 不成立 → 被条件二挡掉 →
146
+ **迁移失效**。写成 `"1.0.0"` 则:1.0.0 升 1.1.0 应用 ✓,1.1.0 升 1.2.0 不再应用 ✓。
147
+
148
+ ### 5.3 条目格式
149
+
150
+ ```json
151
+ {
152
+ "1.0.0": {
153
+ "summary": "secrets.yaml 新增 test.test 占位键(随 1.1.0 发布)",
154
+ "files": [
155
+ {
156
+ "filename": "secrets.yaml",
157
+ "action": "merge",
158
+ "description": "新增 secrets 键 test.test(值取自随包模板,留空)",
159
+ "add_items": [{ "key": "test.test" }]
160
+ }
161
+ ]
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### 5.4 三种 action
167
+
168
+ | action | 用途 | 说明 |
169
+ |--------|------|------|
170
+ | `merge` | YAML 增量合并(最常用) | 配合 `add_items`(补缺失键)/ `overwrite_items`(覆盖值或删除键)/ `delete_items`(删键)/ `keep_items`(保护名单,绝不触碰) |
171
+ | `create` | 新建配置文件 | 已存在则跳过,绝不覆盖用户文件 |
172
+ | `overwrite` | 整文件覆盖 | csv 等"程序自带数据"专用;启动时还会由 `sync_declared_overwrites` 自动对齐,覆盖前留 `.bak` |
173
+
174
+ 要点:
175
+
176
+ - **没被清单提到的键一律原样保留**(以用户现有配置为基底做增量修改);
177
+ - `add_items` / `overwrite_items` **不带 value** 时,值从安装包内
178
+ `<文件名>.example` 模板取——这是 secrets 类条目的**规范写法**(真实值不进仓库);
179
+ 业务 yaml(无 example)必须显式带 value;
180
+ - 落盘前键名含 secret/password/token 的明文值自动混淆;
181
+ - 用户已有该键时**跳过不覆盖**(幂等,不碰用户已填的值)。
182
+
183
+ ### 5.5 文件维护约定
184
+
185
+ - 顶层版本块按版本号**从高到低**排列:新条目插在文件**最上方**;
186
+ - 追加条目时用版本号元组倒序**重排整个文件**,不要直接 append 到末尾;
187
+ (运行时会自行按块版本升序应用,文件顺序只影响可读性,但必须遵守。)
188
+
189
+ ### 5.6 双保险机制(为什么"安装时跳过"不等于失败)
190
+
191
+ | 层 | 时机 | 读的 example | 负责 |
192
+ |----|------|-------------|------|
193
+ | 安装时迁移 | 新包安装**前** | **旧**包内的 example | 改值/删除/覆盖(必须在换文件前做) |
194
+ | 启动补齐 | 新包**首次启动** | **新**包内的 example | 新增键类(旧包 example 天然没有新键) |
195
+
196
+ 所以:新增占位键类条目在安装时**静默跳过属正常现象**(日志刻意不打),
197
+ 升级后首次启动会以 `[补齐] secrets.yaml: test.test` 的形式自动补上。
198
+ 想让它安装时就生效,显式带 `"value": ""` 即可。
199
+
200
+ ---
201
+
202
+ ## 六、客户端全流程与数据安全
203
+
204
+ ### 流程
205
+
206
+ 1. **检查**:启动时 / 每小时 / 点版本号徽章 → 版本弹窗 → 「🔄 检查更新」;
207
+ 远程信息缓存 5 分钟(`?force=true` 跳过);
208
+ 2. **下载**:4 线程分块并发(8MB/块)+ `.part` 临时文件防半截包 + SHA256 校验,
209
+ 进度条与预计剩余时间实时显示。检查到新版本时会弹出更新弹窗
210
+ (版本信息 / 更新说明 / 配置迁移明细):
211
+
212
+ ![客户端更新弹窗](images/update-dialog.png)
213
+ 3. **安装**(点「立即安装更新」):
214
+ - 备份整个配置目录到 `datas/config_backups/版本号_时间戳/`;
215
+ - 应用第五节的迁移;
216
+ - Ubuntu:趁应用存活用免密 `sudo dpkg -i`(免密规则只放行
217
+ `datas/updates/*.deb`,且启动发证、退回收证);Windows:等应用退出后
218
+ `setup.exe /S` 静默安装(运行中的 exe 被锁,只能退出再装);
219
+ - 安装日志:`datas/logs/update_install.log`;
220
+ 4. **首次启动**:`init_user_configs` 用新包 example 深合并补齐用户新增键
221
+ (日志 `[补齐] secrets.yaml: test.test`),已有值一律不覆盖。
222
+
223
+ ### 数据安全三重保护
224
+
225
+ 1. 升级前整体备份(保底);
226
+ 2. 迁移只动清单上写的键,其余设置/密钥一个字符都不碰;覆盖前留 `.bak`;
227
+ 3. 迁移失败软件照常启动(配置按默认值运行),不会"升级完打不开"。
228
+
229
+ ---
230
+
231
+ ## 七、API 速查(`/api/update/*`)
232
+
233
+ | 方法 | 路径 | 说明 |
234
+ |------|------|------|
235
+ | GET | `/api/update/check` | 检查更新(`?force=true` 跳过 5 分钟缓存) |
236
+ | GET | `/api/update/info` | 完整版本信息(含 source: remote/local/none) |
237
+ | POST | `/api/update/download` | 后台线程开始下载 |
238
+ | GET | `/api/update/download/progress` | 下载进度(status/progress/downloaded/total) |
239
+ | POST | `/api/update/install` | 备份 → 迁移 → 拉起安装器(body: `{"apply_config": true}`) |
240
+
241
+ 开发模式(源码运行)下所有接口直接返回 `packaged: false`,不支持应用内更新——
242
+ 刻意设计:对源码装 deb 没有意义。
243
+
244
+ ---
245
+
246
+ ## 八、AI 开发者三条纪律(与 AGENTS.md §H 一致,违反会直接造成线上事故)
247
+
248
+ ### 纪律 1:改了 `configs/` 就要问迁移
249
+
250
+ commit 前检查暂存区(`git status --short -- configs/`),只要配置文件有修改,
251
+ **必须询问用户**是否需要在 `config_changes.json` 补迁移条目;用户授权过
252
+ "默认直接维护"则直接补并在回复中列出条目。
253
+
254
+ ### 纪律 2:块版本号写"变更前",别写"新版本"
255
+
256
+ 见 5.2。写错的后果是**部分升级路径迁移静默失效**,测试时很难发现。
257
+
258
+ ### 纪律 3:真实密钥绝不进仓库
259
+
260
+ 迁移条目、`config_changes.json`、`config_changes` 的 summary 里都不能出现
261
+ 真实密钥——secrets 类条目一律不带 value(从 example 模板取);
262
+ `build.py` 会把 example 同步/混淆后随包分发并在结束后恢复留空模板;
263
+ `sudo_password` 在 example 中永远为空。
264
+
265
+ ### 快速自检清单
266
+
267
+ - [ ] 块版本号 = 引入变更 commit 之前的 package.json 版本?
268
+ - [ ] 新块插在文件最上方(版本号从高到低)?
269
+ - [ ] secrets 类条目不带 value?
270
+ - [ ] 占位键的键已经同步加进 `configs/secrets.yaml.example`?
271
+ - [ ] 改动的配置文件只动了清单上声明的键?
272
+
273
+ ---
274
+
275
+ ## 九、FAQ
276
+
277
+ **Q:客户端永远显示"没有新版本"?**
278
+ 九成是飞书 `drive:drive` 权限没开通或没发布审核。此时发版端清理不了旧文件、
279
+ 客户端用本地兜底信息。确认权限**已发布生效**。
280
+
281
+ **Q:下载慢?**
282
+ 飞书对下载限速(单连接约 0.1MB/s),已做 4 线程分块提速约 3 倍;
283
+ 等不及可从更新弹窗"前往下载"走飞书网页高速通道,手动安装。
284
+
285
+ **Q:升级后某功能不工作,发现新配置键没出现?**
286
+ 看启动日志有没有 `[补齐]` 行;没有则检查新包内
287
+ `/opt/<应用>/configs/secrets.yaml.example`(或 Windows 对应目录)是否含该键——
288
+ 大概率是打包含旧 build.py 或 example 里没加键。
289
+
290
+ **Q:安装时日志说"跳过",迁移是不是失败了?**
291
+ 不是。新增键类条目在安装时静默跳过是预期行为(旧包模板没有新键),
292
+ 升级后首次启动会自动补齐(见 5.6 双保险)。
293
+
294
+ **Q:升级失败会怎样?**
295
+ 配置有备份目录、程序文件由安装器管理,旧文件都在;查
296
+ `datas/logs/update_install.log`,或手动 `sudo dpkg -i` 重试。
297
+
298
+ **Q:旧版本能回滚吗?**
299
+ 能。飞书货架保留全部历史安装包;配置有 `config_backups` 与 `.bak`。
300
+
301
+ ---
302
+
303
+ ## 十、维护者文件速查(xb 模板仓库)
304
+
305
+ | 模板文件 | 职责 |
306
+ |----------|------|
307
+ | `xb/templates/root/build.py.j2` | 构建编排 + example 同步/混淆/恢复 + 上传进度渲染 |
308
+ | `xb/templates/scripts/feishu_upload.py.j2` | 分片上传、token 记录、旧文件清理 |
309
+ | `xb/templates/configs/config_changes.json.j2` | 迁移清单骨架(`{}`) |
310
+ | `xb/templates/backend/api/update/__init__.py.j2` | 更新接口 |
311
+ | `xb/templates/backend/services/updater.py.j2` | 核心更新逻辑 |
312
+ | `xb/templates/backend/services/feishu_client.py.j2` | 飞书接口封装 |
313
+ | `xb/templates/backend/managers/secret_obfuscator.py.j2` | `obf1:` 混淆 |
314
+ | `xb/templates/backend/managers/path_manager.py.j2` | 首启补齐(`init_user_configs`)/ overwrite 对齐(`sync_declared_overwrites`) |
315
+ | `xb/templates/frontend/src/components/version/*.j2` | 更新弹窗 + 提交历史 |
316
+ | `xb/templates/version/scripts/*.j2` | 版本 bump 四文件同步 + hook |
317
+
318
+ ---
319
+
320
+ *最后更新:2026-09-20 · 对应 xb 提交 `e3d2c72`(--update 功能)及其后续修复:
321
+ 块版本语义澄清、example 深合并保留占位键、sudo_password 强制留空、
322
+ 静默跳过日志、弹窗遮挡修复、nrm/chsrc 源优化。*
@@ -377,6 +377,11 @@ class TemplateEngine:
377
377
  # AGENTS.md:AI 编码助手项目级约定(opencode / Claude Code / Cursor 等会自动读取)
378
378
  self._render_template("root/AGENTS.md.j2", target_dir / "AGENTS.md", context)
379
379
 
380
+ # docs/:自动更新说明 + 配图,仅 --update 项目随模板分发(原样拷贝,不渲染变量)
381
+ if context["enable_update"]:
382
+ docs_src = Path(__file__).resolve().parent.parent / "templates" / "root" / "docs"
383
+ if docs_src.exists():
384
+ shutil.copytree(docs_src, target_dir / "docs", dirs_exist_ok=True)
380
385
 
381
386
  (target_dir / "datas" / "logs").mkdir(parents=True, exist_ok=True)
382
387
  (target_dir / "datas" / "pids").mkdir(parents=True, exist_ok=True)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes