xb-init 2.1.0__tar.gz → 2.2.2__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.
- {xb_init-2.1.0 → xb_init-2.2.2}/PKG-INFO +46 -7
- {xb_init-2.1.0 → xb_init-2.2.2}/README.md +45 -6
- {xb_init-2.1.0 → xb_init-2.2.2}/pyproject.toml +1 -1
- xb_init-2.2.2/tests/test_cli.py +88 -0
- xb_init-2.2.2/tests/test_update_templates.py +77 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/uv.lock +1 -1
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/commands/init.py +108 -14
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/api/config.py.j2 +22 -0
- xb_init-2.2.2/xb/templates/backend/api/update/__init__.py.j2 +188 -0
- xb_init-2.2.2/xb/templates/backend/app_version.py.j2 +11 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/backend_build.py.j2 +7 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/main.py.j2 +25 -2
- xb_init-2.2.2/xb/templates/backend/managers/__init__.py.j2 +7 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/managers/logger_manager.py.j2 +14 -9
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/managers/path_manager.py.j2 +114 -1
- xb_init-2.2.2/xb/templates/backend/managers/secret_obfuscator.py.j2 +123 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/managers/sudoers_manager.py.j2 +14 -0
- xb_init-2.2.2/xb/templates/backend/services/__init__.py.j2 +1 -0
- xb_init-2.2.2/xb/templates/backend/services/feishu_client.py.j2 +264 -0
- xb_init-2.2.2/xb/templates/backend/services/updater.py.j2 +712 -0
- xb_init-2.2.2/xb/templates/configs/config_changes.json.j2 +1 -0
- xb_init-2.2.2/xb/templates/configs/secrets.yaml.example.j2 +9 -0
- xb_init-2.2.2/xb/templates/configs/secrets.yaml.j2 +9 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/resources/postinst.py.j2 +39 -7
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/resources/postrm.py.j2 +2 -2
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/components/ConfigSetup.vue.j2 +5 -38
- xb_init-2.2.2/xb/templates/frontend/src/components/GitVersionBadge.vue.j2 +297 -0
- xb_init-2.2.2/xb/templates/frontend/src/components/version/CommitHistoryPanel.vue.j2 +286 -0
- xb_init-2.2.2/xb/templates/frontend/src/components/version/UpdatePanel.vue.j2 +766 -0
- xb_init-2.2.2/xb/templates/frontend/src/composables/useUpdate.js.j2 +295 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/root/.gitignore.j2 +6 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/root/AGENTS.md.j2 +63 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/root/README.md.j2 +85 -4
- xb_init-2.2.2/xb/templates/root/build.py.j2 +448 -0
- xb_init-2.2.2/xb/templates/scripts/feishu_upload.py.j2 +656 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/version/scripts/gui_version_manager.py.j2 +19 -1
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/version/scripts/version_manager.py.j2 +60 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/version/scripts/web_version_manager.py.j2 +19 -1
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/utils/template_engine.py +71 -4
- xb_init-2.1.0/tests/test_cli.py +0 -14
- xb_init-2.1.0/xb/templates/backend/managers/__init__.py.j2 +0 -7
- xb_init-2.1.0/xb/templates/configs/secrets.yaml.example.j2 +0 -3
- xb_init-2.1.0/xb/templates/configs/secrets.yaml.j2 +0 -3
- xb_init-2.1.0/xb/templates/electron/.npmrc.j2 +0 -10
- xb_init-2.1.0/xb/templates/frontend/.npmrc.j2 +0 -7
- xb_init-2.1.0/xb/templates/frontend/src/components/GitVersionBadge.vue.j2 +0 -359
- xb_init-2.1.0/xb/templates/root/build.py.j2 +0 -68
- {xb_init-2.1.0 → xb_init-2.2.2}/.gitignore +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/docs/screenshot.png +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/tests/test_upgrade.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/__init__.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/cli.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/commands/__init__.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/commands/build.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/commands/dev.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/commands/doctor.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/commands/upgrade.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/commands/version.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/api/__init__.py.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/api/ports.py.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/api/terminal/__init__.py.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/api/terminal/routes.py.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/backend/requirements.txt.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/configs/global_config.yaml.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/launcher.js.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/main.js.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/package.json.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/port_diagnostics.js.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/preload.js.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/prepare.js.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/electron/resources/uninstaller.nsh.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/index.html.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/package.json.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/App.vue.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/components/DashboardHome.vue.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/components/FileManager.vue.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/components/RefreshButton.vue.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/components/TerminalPanel.vue.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/components/TerminalView.vue.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/components/ThemeToggle.vue.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/composables/useTerminalSessions.js.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/main.js.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/src/style.css.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/frontend/vite.config.js.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/root/dev.py.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/root/pyproject.toml.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/scripts/example.py.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/version/hooks/pre-commit.py.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/templates/version/scripts/install_hooks.py.j2 +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/utils/__init__.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/utils/click_helpers.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/xb/utils/validators.py +0 -0
- {xb_init-2.1.0 → xb_init-2.2.2}/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.
|
|
3
|
+
Version: 2.2.2
|
|
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
|
|
@@ -43,6 +43,7 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
- 内置全局亮色/暗色主题切换
|
|
44
44
|
- 可选 sudo 免密配置(`--sudoers`)
|
|
45
45
|
- 可选内置终端(`--terminal`)
|
|
46
|
+
- 可选应用内自动更新(`--update`,飞书云盘发布 + 版本检查 + 下载校验 + 自动安装)
|
|
46
47
|
- 可选自定义应用图标(`--icon`)
|
|
47
48
|
- 内置开发、构建、版本管理命令
|
|
48
49
|
- 自动版本检查与一键升级(`xb --upgrade`)
|
|
@@ -83,6 +84,9 @@ xb init demo --terminal
|
|
|
83
84
|
# 同时启用终端和 sudo 免密配置
|
|
84
85
|
xb init demo --terminal --sudoers
|
|
85
86
|
|
|
87
|
+
# 启用应用内自动更新(自动附带 --sudoers 免密,输入 sudo 密码即可)
|
|
88
|
+
xb init demo --update
|
|
89
|
+
|
|
86
90
|
# 带自定义图标
|
|
87
91
|
xb init demo --icon ~/icons/app.png
|
|
88
92
|
|
|
@@ -110,14 +114,18 @@ python .\dev.py stop
|
|
|
110
114
|
使用 `--terminal` 时,Windows 终端使用 PowerShell + ConPTY,支持上下箭头
|
|
111
115
|
命令历史、复制粘贴、Tab 补全和 Ctrl+C;Ubuntu/Debian 使用 bash + Unix PTY。
|
|
112
116
|
|
|
113
|
-
Electron 二进制镜像通过 `ELECTRON_MIRROR`
|
|
114
|
-
|
|
117
|
+
Electron 二进制镜像通过 `ELECTRON_MIRROR` 环境变量注入。
|
|
118
|
+
若安装过程被网络中断,生成项目的
|
|
115
119
|
`electron/prepare.js` 会在 `npm start` 或 `npm run build` 前自动检查并补装
|
|
116
120
|
缺失的 Electron 二进制。
|
|
117
121
|
|
|
118
122
|
> **`xb init` 自动行为**:
|
|
119
123
|
> - 执行 `npm install`(frontend + electron),生成 `package-lock.json`
|
|
120
|
-
> -
|
|
124
|
+
> - 生成的项目用 [nrm](https://github.com/Pana/nrm) 管理 npm 源:`xb init`
|
|
125
|
+
> 安装依赖前与 `build.py` 构建前都会自动 `nrm test` 测速所有内置源并切换
|
|
126
|
+
> 到最快的(未安装 nrm 时自动全局安装);项目不放 `.npmrc`,npm 源完全由
|
|
127
|
+
> nrm 全局管理
|
|
128
|
+
> - electron 约 200MB 二进制走华为云镜像下载
|
|
121
129
|
> - 若安装失败或超时,会打印提示但不中断项目创建,可稍后手动重试
|
|
122
130
|
> - 执行 `git init` 并提交首个 commit(包含所有文件和 lock 文件)
|
|
123
131
|
> - 若检测到 PyPI 有新版 xb,会询问是否先升级再创建项目
|
|
@@ -127,7 +135,7 @@ Electron 二进制镜像通过 `ELECTRON_MIRROR` 环境变量注入,不向 `.n
|
|
|
127
135
|
|
|
128
136
|
| 命令 | 说明 |
|
|
129
137
|
|------|------|
|
|
130
|
-
| `xb init <name> [--sudoers] [--terminal] [--icon PATH]` | 初始化项目 |
|
|
138
|
+
| `xb init <name> [--sudoers] [--terminal] [--update] [--icon PATH]` | 初始化项目 |
|
|
131
139
|
| `xb dev [start\|stop\|status]` | 启动/停止/查看开发环境 |
|
|
132
140
|
| `xb build [all\|frontend\|backend\|electron]` | 构建项目 |
|
|
133
141
|
| `xb build -f / -b / -e / -a` | 构建快捷 flag |
|
|
@@ -146,6 +154,39 @@ xb init demo --icon ./my-icon.png
|
|
|
146
154
|
# ./assets/app-icon.png, ./assets/icon.png, ./resources/icon.png
|
|
147
155
|
```
|
|
148
156
|
|
|
157
|
+
## 应用内自动更新(`--update`)
|
|
158
|
+
|
|
159
|
+
`xb init demo --update` 会让生成的项目内置与 [leo_stm32_board_test](../leo_stm32_board_test) 同源的自动更新能力,
|
|
160
|
+
以飞书云盘的一个文件夹作为发布"货架":
|
|
161
|
+
|
|
162
|
+
**生成内容:**
|
|
163
|
+
|
|
164
|
+
| 位置 | 职责 |
|
|
165
|
+
|------|------|
|
|
166
|
+
| `backend/api/update/` | 更新接口(检查 / 信息 / 下载 / 进度 / 安装) |
|
|
167
|
+
| `backend/services/updater.py` | 版本比较、信息拉取、下载校验、配置备份迁移、安装 |
|
|
168
|
+
| `backend/services/feishu_client.py` | 飞书接口封装(文件夹列举、并发下载) |
|
|
169
|
+
| `backend/managers/secret_obfuscator.py` | 敏感值混淆(`obf1:`,防围观级别) |
|
|
170
|
+
| `backend/app_version.py` | 后端版本号(version_manager 提交时自动同步) |
|
|
171
|
+
| `frontend/src/components/UpdateChecker.vue` | 版本号徽章 + 更新弹窗 + 进度条 |
|
|
172
|
+
| `frontend/src/composables/useUpdate.js` | 定时检查 / 下载进度轮询 |
|
|
173
|
+
| `scripts/feishu_upload.py` | 发版端:安装包与版本信息上传飞书 |
|
|
174
|
+
| `configs/config_changes.json` | 配置迁移清单(顶层 key 是版本号) |
|
|
175
|
+
| `build.py` | 增加 `--upload-only` 与构建后发布编排 |
|
|
176
|
+
|
|
177
|
+
**发版流程:** 在 `configs/secrets.yaml` 填 `feishu.app_id` / `app_secret` /
|
|
178
|
+
`update_folder_url` → `./build.py -a` → 确认上传。构建时敏感键自动混淆进
|
|
179
|
+
`secrets.yaml.example` 随包分发,构建结束 example 恢复为留空模板。
|
|
180
|
+
|
|
181
|
+
**客户端:** 启动时 / 每小时 / 点版本号徽章时检查更新;发现新版本后下载
|
|
182
|
+
(4 线程分块 + SHA256 校验 + `.part` 防半截包)→ 备份配置 → 按
|
|
183
|
+
`config_changes.json` 迁移 → Ubuntu 趁应用存活时免密 `sudo dpkg -i`
|
|
184
|
+
(`--update` 默认附带 `--sudoers` 免密),Windows 等退出后静默安装。
|
|
185
|
+
|
|
186
|
+
**飞书侧准备:** 开放平台建自建应用 → 开通 `drive:drive` 权限并发布 →
|
|
187
|
+
建云盘文件夹,完整链接填入 `update_folder_url`。开发模式(源码运行)不支持
|
|
188
|
+
应用内更新,检查接口返回 `packaged: false`。
|
|
189
|
+
|
|
149
190
|
## 生成的项目结构
|
|
150
191
|
|
|
151
192
|
```
|
|
@@ -162,13 +203,11 @@ demo/
|
|
|
162
203
|
├── frontend/ # Vue 3 前端
|
|
163
204
|
│ ├── package.json
|
|
164
205
|
│ ├── package-lock.json
|
|
165
|
-
│ ├── .npmrc # npm 镜像源(华为云)
|
|
166
206
|
│ ├── vite.config.js
|
|
167
207
|
│ └── src/
|
|
168
208
|
├── electron/ # Electron 主进程
|
|
169
209
|
│ ├── package.json
|
|
170
210
|
│ ├── package-lock.json
|
|
171
|
-
│ ├── .npmrc # npm + electron 二进制镜像源
|
|
172
211
|
│ ├── main.js
|
|
173
212
|
│ └── resources/ # icon + Linux DEB hooks
|
|
174
213
|
├── version/ # 版本管理(pre-commit hook)
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- 内置全局亮色/暗色主题切换
|
|
15
15
|
- 可选 sudo 免密配置(`--sudoers`)
|
|
16
16
|
- 可选内置终端(`--terminal`)
|
|
17
|
+
- 可选应用内自动更新(`--update`,飞书云盘发布 + 版本检查 + 下载校验 + 自动安装)
|
|
17
18
|
- 可选自定义应用图标(`--icon`)
|
|
18
19
|
- 内置开发、构建、版本管理命令
|
|
19
20
|
- 自动版本检查与一键升级(`xb --upgrade`)
|
|
@@ -54,6 +55,9 @@ xb init demo --terminal
|
|
|
54
55
|
# 同时启用终端和 sudo 免密配置
|
|
55
56
|
xb init demo --terminal --sudoers
|
|
56
57
|
|
|
58
|
+
# 启用应用内自动更新(自动附带 --sudoers 免密,输入 sudo 密码即可)
|
|
59
|
+
xb init demo --update
|
|
60
|
+
|
|
57
61
|
# 带自定义图标
|
|
58
62
|
xb init demo --icon ~/icons/app.png
|
|
59
63
|
|
|
@@ -81,14 +85,18 @@ python .\dev.py stop
|
|
|
81
85
|
使用 `--terminal` 时,Windows 终端使用 PowerShell + ConPTY,支持上下箭头
|
|
82
86
|
命令历史、复制粘贴、Tab 补全和 Ctrl+C;Ubuntu/Debian 使用 bash + Unix PTY。
|
|
83
87
|
|
|
84
|
-
Electron 二进制镜像通过 `ELECTRON_MIRROR`
|
|
85
|
-
|
|
88
|
+
Electron 二进制镜像通过 `ELECTRON_MIRROR` 环境变量注入。
|
|
89
|
+
若安装过程被网络中断,生成项目的
|
|
86
90
|
`electron/prepare.js` 会在 `npm start` 或 `npm run build` 前自动检查并补装
|
|
87
91
|
缺失的 Electron 二进制。
|
|
88
92
|
|
|
89
93
|
> **`xb init` 自动行为**:
|
|
90
94
|
> - 执行 `npm install`(frontend + electron),生成 `package-lock.json`
|
|
91
|
-
> -
|
|
95
|
+
> - 生成的项目用 [nrm](https://github.com/Pana/nrm) 管理 npm 源:`xb init`
|
|
96
|
+
> 安装依赖前与 `build.py` 构建前都会自动 `nrm test` 测速所有内置源并切换
|
|
97
|
+
> 到最快的(未安装 nrm 时自动全局安装);项目不放 `.npmrc`,npm 源完全由
|
|
98
|
+
> nrm 全局管理
|
|
99
|
+
> - electron 约 200MB 二进制走华为云镜像下载
|
|
92
100
|
> - 若安装失败或超时,会打印提示但不中断项目创建,可稍后手动重试
|
|
93
101
|
> - 执行 `git init` 并提交首个 commit(包含所有文件和 lock 文件)
|
|
94
102
|
> - 若检测到 PyPI 有新版 xb,会询问是否先升级再创建项目
|
|
@@ -98,7 +106,7 @@ Electron 二进制镜像通过 `ELECTRON_MIRROR` 环境变量注入,不向 `.n
|
|
|
98
106
|
|
|
99
107
|
| 命令 | 说明 |
|
|
100
108
|
|------|------|
|
|
101
|
-
| `xb init <name> [--sudoers] [--terminal] [--icon PATH]` | 初始化项目 |
|
|
109
|
+
| `xb init <name> [--sudoers] [--terminal] [--update] [--icon PATH]` | 初始化项目 |
|
|
102
110
|
| `xb dev [start\|stop\|status]` | 启动/停止/查看开发环境 |
|
|
103
111
|
| `xb build [all\|frontend\|backend\|electron]` | 构建项目 |
|
|
104
112
|
| `xb build -f / -b / -e / -a` | 构建快捷 flag |
|
|
@@ -117,6 +125,39 @@ xb init demo --icon ./my-icon.png
|
|
|
117
125
|
# ./assets/app-icon.png, ./assets/icon.png, ./resources/icon.png
|
|
118
126
|
```
|
|
119
127
|
|
|
128
|
+
## 应用内自动更新(`--update`)
|
|
129
|
+
|
|
130
|
+
`xb init demo --update` 会让生成的项目内置与 [leo_stm32_board_test](../leo_stm32_board_test) 同源的自动更新能力,
|
|
131
|
+
以飞书云盘的一个文件夹作为发布"货架":
|
|
132
|
+
|
|
133
|
+
**生成内容:**
|
|
134
|
+
|
|
135
|
+
| 位置 | 职责 |
|
|
136
|
+
|------|------|
|
|
137
|
+
| `backend/api/update/` | 更新接口(检查 / 信息 / 下载 / 进度 / 安装) |
|
|
138
|
+
| `backend/services/updater.py` | 版本比较、信息拉取、下载校验、配置备份迁移、安装 |
|
|
139
|
+
| `backend/services/feishu_client.py` | 飞书接口封装(文件夹列举、并发下载) |
|
|
140
|
+
| `backend/managers/secret_obfuscator.py` | 敏感值混淆(`obf1:`,防围观级别) |
|
|
141
|
+
| `backend/app_version.py` | 后端版本号(version_manager 提交时自动同步) |
|
|
142
|
+
| `frontend/src/components/UpdateChecker.vue` | 版本号徽章 + 更新弹窗 + 进度条 |
|
|
143
|
+
| `frontend/src/composables/useUpdate.js` | 定时检查 / 下载进度轮询 |
|
|
144
|
+
| `scripts/feishu_upload.py` | 发版端:安装包与版本信息上传飞书 |
|
|
145
|
+
| `configs/config_changes.json` | 配置迁移清单(顶层 key 是版本号) |
|
|
146
|
+
| `build.py` | 增加 `--upload-only` 与构建后发布编排 |
|
|
147
|
+
|
|
148
|
+
**发版流程:** 在 `configs/secrets.yaml` 填 `feishu.app_id` / `app_secret` /
|
|
149
|
+
`update_folder_url` → `./build.py -a` → 确认上传。构建时敏感键自动混淆进
|
|
150
|
+
`secrets.yaml.example` 随包分发,构建结束 example 恢复为留空模板。
|
|
151
|
+
|
|
152
|
+
**客户端:** 启动时 / 每小时 / 点版本号徽章时检查更新;发现新版本后下载
|
|
153
|
+
(4 线程分块 + SHA256 校验 + `.part` 防半截包)→ 备份配置 → 按
|
|
154
|
+
`config_changes.json` 迁移 → Ubuntu 趁应用存活时免密 `sudo dpkg -i`
|
|
155
|
+
(`--update` 默认附带 `--sudoers` 免密),Windows 等退出后静默安装。
|
|
156
|
+
|
|
157
|
+
**飞书侧准备:** 开放平台建自建应用 → 开通 `drive:drive` 权限并发布 →
|
|
158
|
+
建云盘文件夹,完整链接填入 `update_folder_url`。开发模式(源码运行)不支持
|
|
159
|
+
应用内更新,检查接口返回 `packaged: false`。
|
|
160
|
+
|
|
120
161
|
## 生成的项目结构
|
|
121
162
|
|
|
122
163
|
```
|
|
@@ -133,13 +174,11 @@ demo/
|
|
|
133
174
|
├── frontend/ # Vue 3 前端
|
|
134
175
|
│ ├── package.json
|
|
135
176
|
│ ├── package-lock.json
|
|
136
|
-
│ ├── .npmrc # npm 镜像源(华为云)
|
|
137
177
|
│ ├── vite.config.js
|
|
138
178
|
│ └── src/
|
|
139
179
|
├── electron/ # Electron 主进程
|
|
140
180
|
│ ├── package.json
|
|
141
181
|
│ ├── package-lock.json
|
|
142
|
-
│ ├── .npmrc # npm + electron 二进制镜像源
|
|
143
182
|
│ ├── main.js
|
|
144
183
|
│ └── resources/ # icon + Linux DEB hooks
|
|
145
184
|
├── version/ # 版本管理(pre-commit hook)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from click.testing import CliRunner
|
|
2
|
+
|
|
3
|
+
import xb.commands.init as init_module
|
|
4
|
+
from xb import cli
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_upgrade_option_runs_without_subcommand(monkeypatch):
|
|
8
|
+
called = []
|
|
9
|
+
monkeypatch.setattr(cli, "run_upgrade", lambda: called.append(True))
|
|
10
|
+
|
|
11
|
+
result = CliRunner().invoke(cli.main, ["--upgrade"])
|
|
12
|
+
|
|
13
|
+
assert result.exit_code == 0
|
|
14
|
+
assert called == [True]
|
|
15
|
+
assert "Missing command" not in result.output
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _invoke_init(monkeypatch, tmp_path, args, password):
|
|
19
|
+
"""跑 init 命令但替换掉耗时/有副作用的步骤,捕获传给模板引擎的参数。"""
|
|
20
|
+
captured = {}
|
|
21
|
+
monkeypatch.setattr(init_module, "get_latest_if_newer", lambda _v: None)
|
|
22
|
+
monkeypatch.setattr(init_module.shutil, "which", lambda _name: None)
|
|
23
|
+
monkeypatch.setattr(init_module, "init_git_repo", lambda *_a, **_k: True)
|
|
24
|
+
monkeypatch.setattr(
|
|
25
|
+
init_module.TemplateEngine,
|
|
26
|
+
"render_project",
|
|
27
|
+
lambda self, **kwargs: captured.update(kwargs),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
result = CliRunner().invoke(
|
|
31
|
+
cli.main, ["init", str(tmp_path / "demo"), *args], input=f"{password}\n"
|
|
32
|
+
)
|
|
33
|
+
assert result.exit_code == 0, result.output
|
|
34
|
+
return captured, result.output
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_update_implies_sudoers(monkeypatch, tmp_path):
|
|
38
|
+
captured, output = _invoke_init(monkeypatch, tmp_path, ["--update"], "pw123")
|
|
39
|
+
|
|
40
|
+
assert captured["enable_update"] is True
|
|
41
|
+
assert captured["enable_sudo"] is True
|
|
42
|
+
assert captured["sudo_password"] == "pw123"
|
|
43
|
+
assert "自动附带 --sudoers" in output
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_sudoers_alone_still_prompts(monkeypatch, tmp_path):
|
|
47
|
+
captured, output = _invoke_init(monkeypatch, tmp_path, ["--sudoers"], "pw123")
|
|
48
|
+
|
|
49
|
+
assert captured["enable_update"] is False
|
|
50
|
+
assert captured["enable_sudo"] is True
|
|
51
|
+
assert captured["sudo_password"] == "pw123"
|
|
52
|
+
assert "自动附带" not in output
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_plain_init_has_no_sudo(monkeypatch, tmp_path):
|
|
56
|
+
captured, output = _invoke_init(monkeypatch, tmp_path, [], "")
|
|
57
|
+
|
|
58
|
+
assert captured["enable_update"] is False
|
|
59
|
+
assert captured["enable_sudo"] is False
|
|
60
|
+
assert captured["sudo_password"] == ""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_pick_fastest_from_nrm_output():
|
|
64
|
+
output = """ npm ---------- 807 ms
|
|
65
|
+
yarn --------- 795 ms
|
|
66
|
+
tencent ------ 284 ms
|
|
67
|
+
cnpm --------- 803 ms
|
|
68
|
+
* taobao ------- 163 ms
|
|
69
|
+
npmMirror ---- 1265 ms (Fetch error, if this is your private registry, please ignore)
|
|
70
|
+
huawei ------- 96 ms
|
|
71
|
+
"""
|
|
72
|
+
assert init_module._pick_fastest_from_nrm_output(output) == ("huawei", 96)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_pick_fastest_from_nrm_output_all_failed():
|
|
76
|
+
output = " npmMirror ---- 1265 ms (Fetch error, if this is your private registry, please ignore)\n"
|
|
77
|
+
assert init_module._pick_fastest_from_nrm_output(output) == ("", None)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_windows_skips_sudo_password(monkeypatch, tmp_path):
|
|
81
|
+
monkeypatch.setattr(init_module.sys, "platform", "win32")
|
|
82
|
+
captured, output = _invoke_init(
|
|
83
|
+
monkeypatch, tmp_path, ["--sudoers"], "should-not-be-used"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
assert captured["enable_sudo"] is True
|
|
87
|
+
assert captured["sudo_password"] == ""
|
|
88
|
+
assert "跳过密码输入" in output
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from xb.utils.template_engine import TemplateEngine
|
|
7
|
+
|
|
8
|
+
UPDATE_FILES = [
|
|
9
|
+
"backend/services/updater.py",
|
|
10
|
+
"backend/services/feishu_client.py",
|
|
11
|
+
"backend/api/update/__init__.py",
|
|
12
|
+
"backend/managers/secret_obfuscator.py",
|
|
13
|
+
"backend/managers/sudoers_manager.py",
|
|
14
|
+
"backend/app_version.py",
|
|
15
|
+
"scripts/feishu_upload.py",
|
|
16
|
+
"configs/config_changes.json",
|
|
17
|
+
"frontend/src/components/version/UpdatePanel.vue",
|
|
18
|
+
"frontend/src/components/version/CommitHistoryPanel.vue",
|
|
19
|
+
"frontend/src/composables/useUpdate.js",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture()
|
|
24
|
+
def render_project(tmp_path):
|
|
25
|
+
def _render(name: str, **kwargs) -> Path:
|
|
26
|
+
target = tmp_path / name
|
|
27
|
+
TemplateEngine().render_project(
|
|
28
|
+
target_dir=target,
|
|
29
|
+
package_name=name,
|
|
30
|
+
**kwargs,
|
|
31
|
+
)
|
|
32
|
+
return target
|
|
33
|
+
|
|
34
|
+
return _render
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_init_without_update_skips_update_files(render_project):
|
|
38
|
+
target = render_project("plain_app")
|
|
39
|
+
|
|
40
|
+
for rel in UPDATE_FILES:
|
|
41
|
+
assert not (target / rel).exists(), f"不应生成 {rel}"
|
|
42
|
+
assert "update_router" not in (target / "backend" / "main.py").read_text()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_init_with_update_renders_update_files(render_project):
|
|
46
|
+
target = render_project("updatable_app", enable_update=True)
|
|
47
|
+
|
|
48
|
+
for rel in UPDATE_FILES:
|
|
49
|
+
assert (target / rel).exists(), f"缺少 {rel}"
|
|
50
|
+
|
|
51
|
+
# 配置迁移清单是合法 JSON
|
|
52
|
+
json.loads((target / "configs" / "config_changes.json").read_text())
|
|
53
|
+
|
|
54
|
+
# main.py 注册了更新路由
|
|
55
|
+
assert "app.include_router(update_router" in (target / "backend" / "main.py").read_text()
|
|
56
|
+
|
|
57
|
+
# secret_obfuscator 与 feishu_upload 的混淆密钥保持一致
|
|
58
|
+
obf = (target / "backend" / "managers" / "secret_obfuscator.py").read_text()
|
|
59
|
+
upload = (target / "scripts" / "feishu_upload.py").read_text()
|
|
60
|
+
key_line = next(line for line in obf.splitlines() if line.startswith("_KEY ="))
|
|
61
|
+
key = key_line.split("=", 1)[1].strip()
|
|
62
|
+
assert key in upload
|
|
63
|
+
|
|
64
|
+
# version_manager 会同步 app_version.py 与 package-lock.json
|
|
65
|
+
vm = (target / "version" / "scripts" / "version_manager.py").read_text()
|
|
66
|
+
assert "update_backend_version" in vm
|
|
67
|
+
assert "update_package_lock_version" in vm
|
|
68
|
+
|
|
69
|
+
# build.py 具备发布编排
|
|
70
|
+
build = (target / "build.py").read_text()
|
|
71
|
+
assert "--upload-only" in build
|
|
72
|
+
assert "_upload_stage" in build
|
|
73
|
+
|
|
74
|
+
# App.vue 挂载的 GitVersionBadge 集成了更新面板
|
|
75
|
+
badge = (target / "frontend" / "src" / "components" / "GitVersionBadge.vue").read_text()
|
|
76
|
+
assert "UpdatePanel" in badge
|
|
77
|
+
assert "CommitHistoryPanel" in badge
|
|
@@ -4,6 +4,7 @@ xb init 命令实现
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import os
|
|
7
|
+
import re
|
|
7
8
|
import shutil
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
@@ -140,6 +141,67 @@ def remove_existing_project(path: Path) -> None:
|
|
|
140
141
|
shutil.rmtree(path, onexc=clear_readonly)
|
|
141
142
|
|
|
142
143
|
|
|
144
|
+
def _pick_fastest_from_nrm_output(output: str) -> tuple[str, int | None]:
|
|
145
|
+
"""解析 `nrm test` 输出,返回 (最快源名, 毫秒);无可用源返回 ("", None)。
|
|
146
|
+
|
|
147
|
+
输出形如 " huawei ------- 96 ms",不可用源带 "(Fetch error ...)" 标记。
|
|
148
|
+
"""
|
|
149
|
+
best_name, best_ms = "", None
|
|
150
|
+
for line in output.splitlines():
|
|
151
|
+
match = re.match(r"^\s*\*?\s*(\S+)\s+-+\s+(\d+)\s+ms\b(.*)$", line)
|
|
152
|
+
if not match or "Fetch error" in match.group(3):
|
|
153
|
+
continue
|
|
154
|
+
elapsed = int(match.group(2))
|
|
155
|
+
if best_ms is None or elapsed < best_ms:
|
|
156
|
+
best_name, best_ms = match.group(1), elapsed
|
|
157
|
+
return best_name, best_ms
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def nrm_speedtest(console: Console, npm_command: str) -> None:
|
|
161
|
+
"""npm install 前用 nrm 测速所有内置源并自动切换到最快的源。
|
|
162
|
+
|
|
163
|
+
- 未安装 nrm 时自动全局安装(npm install -g nrm)
|
|
164
|
+
- nrm use 修改全局 npm 配置(~/.npmrc),对本机所有项目生效
|
|
165
|
+
- 任何失败只告警,不阻塞项目创建(沿用当前源继续安装)
|
|
166
|
+
"""
|
|
167
|
+
nrm_command = "nrm.cmd" if os.name == "nt" and shutil.which("nrm.cmd") else "nrm"
|
|
168
|
+
try:
|
|
169
|
+
if not shutil.which(nrm_command):
|
|
170
|
+
console.print("[dim]未检测到 nrm,自动安装(npm install -g nrm)...[/dim]")
|
|
171
|
+
subprocess.run(
|
|
172
|
+
[npm_command, "install", "-g", "nrm"],
|
|
173
|
+
capture_output=True,
|
|
174
|
+
text=True,
|
|
175
|
+
timeout=120,
|
|
176
|
+
check=True,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
console.print("[dim]正在测速 npm 镜像源(nrm test)...[/dim]")
|
|
180
|
+
result = subprocess.run(
|
|
181
|
+
[nrm_command, "test"],
|
|
182
|
+
capture_output=True,
|
|
183
|
+
text=True,
|
|
184
|
+
timeout=180,
|
|
185
|
+
check=True,
|
|
186
|
+
)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
console.print(f"[yellow]⚠[/yellow] nrm 测速不可用(沿用当前 npm 源): {e}")
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
best_name, best_ms = _pick_fastest_from_nrm_output(result.stdout)
|
|
192
|
+
if not best_name:
|
|
193
|
+
console.print("[yellow]⚠[/yellow] nrm 测速无可用结果(沿用当前 npm 源)")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
console.print(f"[green]✓[/green] 最快 npm 源: [cyan]{best_name}[/cyan]({best_ms} ms),切换中...")
|
|
197
|
+
try:
|
|
198
|
+
subprocess.run(
|
|
199
|
+
[nrm_command, "use", best_name], capture_output=True, text=True, check=True
|
|
200
|
+
)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
console.print(f"[yellow]⚠[/yellow] 切换 npm 源失败(沿用当前源): {e}")
|
|
203
|
+
|
|
204
|
+
|
|
143
205
|
class ParamSummaryCommand(ChineseHelpCommand):
|
|
144
206
|
pass
|
|
145
207
|
|
|
@@ -177,6 +239,13 @@ class XbGroup(click.Group):
|
|
|
177
239
|
default=False,
|
|
178
240
|
help="启用内置 Web 终端(Linux/macOS 使用 bash,Windows 使用 PowerShell)",
|
|
179
241
|
)
|
|
242
|
+
@click.option(
|
|
243
|
+
"--update",
|
|
244
|
+
"update",
|
|
245
|
+
is_flag=True,
|
|
246
|
+
default=False,
|
|
247
|
+
help="启用应用内自动更新(飞书云盘发布 + 版本检查 + 下载安装);默认附带 --sudoers 免密",
|
|
248
|
+
)
|
|
180
249
|
@click.option(
|
|
181
250
|
"--icon",
|
|
182
251
|
"icon",
|
|
@@ -184,7 +253,13 @@ class XbGroup(click.Group):
|
|
|
184
253
|
default=None,
|
|
185
254
|
help="应用图标 PNG 路径;不传时自动查找 ./app-icon.png、./icon.png、./assets/icon.png 等约定路径",
|
|
186
255
|
)
|
|
187
|
-
def init_command(
|
|
256
|
+
def init_command(
|
|
257
|
+
package: str,
|
|
258
|
+
sudoers: bool,
|
|
259
|
+
terminal: bool,
|
|
260
|
+
icon: str | None,
|
|
261
|
+
update: bool,
|
|
262
|
+
):
|
|
188
263
|
"""
|
|
189
264
|
初始化项目结构
|
|
190
265
|
|
|
@@ -196,6 +271,7 @@ def init_command(package: str, sudoers: bool, terminal: bool, icon: str | None):
|
|
|
196
271
|
xb init demo
|
|
197
272
|
xb init myapp --sudoers
|
|
198
273
|
xb init myapp --terminal
|
|
274
|
+
xb init myapp --update
|
|
199
275
|
xb init /home/user/projects/myapp
|
|
200
276
|
xb init ../other_dir/demo
|
|
201
277
|
"""
|
|
@@ -240,23 +316,37 @@ def init_command(package: str, sudoers: bool, terminal: bool, icon: str | None):
|
|
|
240
316
|
console.print(f"[green]→[/green] 使用应用图标: [cyan]{icon_path}[/cyan]")
|
|
241
317
|
|
|
242
318
|
# sudo 免密配置
|
|
319
|
+
# --update 默认附带 --sudoers:Ubuntu 下应用内安装更新依赖免密 dpkg
|
|
243
320
|
enable_sudo = False
|
|
244
321
|
sudo_password = ""
|
|
245
322
|
|
|
246
|
-
if sudoers:
|
|
247
|
-
console.print()
|
|
323
|
+
if update and not sudoers:
|
|
248
324
|
console.print(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
"启用 sudo 免密执行特定命令。\n"
|
|
252
|
-
"密码将以明文存储在 configs/secrets.yaml 中,\n"
|
|
253
|
-
"请确保该文件权限设置为 600 (仅所有者可读写)。",
|
|
254
|
-
border_style="cyan",
|
|
255
|
-
)
|
|
325
|
+
"[cyan]ℹ[/cyan] 已启用 --update,自动附带 --sudoers "
|
|
326
|
+
"(Ubuntu 应用内安装更新需要 sudo 免密)"
|
|
256
327
|
)
|
|
328
|
+
sudoers = True
|
|
329
|
+
|
|
330
|
+
if sudoers:
|
|
331
|
+
if sys.platform.startswith("win"):
|
|
332
|
+
# Windows 没有 sudo 概念:仍生成 sudoers_manager(运行时自动 no-op),
|
|
333
|
+
# 但不询问、不写入密码
|
|
334
|
+
console.print("[dim]Windows 无需 sudo 免密,跳过密码输入[/dim]")
|
|
335
|
+
enable_sudo = True
|
|
336
|
+
else:
|
|
337
|
+
console.print()
|
|
338
|
+
console.print(
|
|
339
|
+
Panel.fit(
|
|
340
|
+
"[bold cyan]Sudo 免密配置[/bold cyan]\n\n"
|
|
341
|
+
"启用 sudo 免密执行特定命令。\n"
|
|
342
|
+
"密码将以明文存储在 configs/secrets.yaml 中,\n"
|
|
343
|
+
"请确保该文件权限设置为 600 (仅所有者可读写)。",
|
|
344
|
+
border_style="cyan",
|
|
345
|
+
)
|
|
346
|
+
)
|
|
257
347
|
|
|
258
|
-
|
|
259
|
-
|
|
348
|
+
sudo_password = Prompt.ask("[cyan]请输入 sudo 密码[/cyan]", password=True)
|
|
349
|
+
enable_sudo = True
|
|
260
350
|
|
|
261
351
|
# 创建项目
|
|
262
352
|
console.print()
|
|
@@ -269,15 +359,19 @@ def init_command(package: str, sudoers: bool, terminal: bool, icon: str | None):
|
|
|
269
359
|
package_name=package_name,
|
|
270
360
|
enable_sudo=enable_sudo,
|
|
271
361
|
enable_terminal=terminal,
|
|
362
|
+
enable_update=update,
|
|
272
363
|
sudo_password=sudo_password,
|
|
273
364
|
icon_path=icon_path,
|
|
274
365
|
)
|
|
275
366
|
|
|
276
367
|
# 安装前端和 Electron 依赖(生成 package-lock.json 纳入首次 commit)
|
|
277
|
-
#
|
|
368
|
+
# npm 源由 nrm 全局管理;--no-audit/--no-fund 减少安装期网络请求
|
|
369
|
+
# electron 需下载约 200MB 二进制,超时给足 10 分钟
|
|
278
370
|
if shutil.which("npm"):
|
|
279
371
|
# Windows 上 npm 通常是 npm.cmd,CreateProcess 不能可靠地直接解析裸 npm。
|
|
280
372
|
npm_command = "npm.cmd" if os.name == "nt" and shutil.which("npm.cmd") else "npm"
|
|
373
|
+
# 安装前测速切换最快 npm 源(nrm 缺失时自动安装;失败不阻塞创建)
|
|
374
|
+
nrm_speedtest(console, npm_command)
|
|
281
375
|
for sub in ("frontend", "electron"):
|
|
282
376
|
sub_dir = target_dir / sub
|
|
283
377
|
if (sub_dir / "package.json").exists():
|
|
@@ -294,7 +388,7 @@ def init_command(package: str, sudoers: bool, terminal: bool, icon: str | None):
|
|
|
294
388
|
),
|
|
295
389
|
})
|
|
296
390
|
result = subprocess.run(
|
|
297
|
-
[npm_command, "install"],
|
|
391
|
+
[npm_command, "install", "--no-audit", "--no-fund"],
|
|
298
392
|
cwd=sub_dir,
|
|
299
393
|
env=environment,
|
|
300
394
|
capture_output=True,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pathlib
|
|
2
2
|
import shutil
|
|
3
|
+
import sys
|
|
3
4
|
import yaml
|
|
4
5
|
from fastapi import APIRouter, HTTPException
|
|
5
6
|
from pydantic import BaseModel, Field
|
|
@@ -12,6 +13,13 @@ from managers import sudoers_manager
|
|
|
12
13
|
router = APIRouter()
|
|
13
14
|
logger = get_logger(__name__)
|
|
14
15
|
|
|
16
|
+
# 运行平台标识(windows / macos / linux),前端据此隐藏平台无关的配置项
|
|
17
|
+
_PLATFORM = (
|
|
18
|
+
"windows" if sys.platform == "win32"
|
|
19
|
+
else "macos" if sys.platform == "darwin"
|
|
20
|
+
else "linux"
|
|
21
|
+
)
|
|
22
|
+
|
|
15
23
|
|
|
16
24
|
[% if enable_sudo %]
|
|
17
25
|
class ConfigRequest(BaseModel):
|
|
@@ -111,9 +119,12 @@ async def get_settings():
|
|
|
111
119
|
"config_path": str(config_file),
|
|
112
120
|
"secrets_path": str(secrets_file),
|
|
113
121
|
"is_packaged": path_manager.is_packaged,
|
|
122
|
+
"platform": _PLATFORM,
|
|
114
123
|
[% if enable_sudo %]
|
|
115
124
|
"sudo_password_configured": bool(path_manager.get_config_value("sudo_password", config_file="secrets.yaml")),
|
|
116
125
|
"sudo_password_locked": _sudo_password_locked(),
|
|
126
|
+
# Windows/macOS 上 sudoers_manager.required 为 False,前端隐藏 Sudo 密码卡片
|
|
127
|
+
"requires_sudo": sudoers_manager.required,
|
|
117
128
|
[% endif %]
|
|
118
129
|
"ports": {
|
|
119
130
|
"backend": int(ports.get("backend") or 8000),
|
|
@@ -174,6 +185,9 @@ async def save_settings(req: AppSettingsRequest):
|
|
|
174
185
|
@router.get("/config/check")
|
|
175
186
|
async def check_config():
|
|
176
187
|
secrets_file = path_manager.get_config_path("secrets.yaml")
|
|
188
|
+
# Windows/macOS 无 sudo 概念,前端据此隐藏配置入口、跳过首启弹窗
|
|
189
|
+
requires_sudo = sudoers_manager.required
|
|
190
|
+
platform = _PLATFORM
|
|
177
191
|
|
|
178
192
|
if not secrets_file.exists():
|
|
179
193
|
return {
|
|
@@ -182,6 +196,8 @@ async def check_config():
|
|
|
182
196
|
"message": "密钥配置文件不存在",
|
|
183
197
|
"config_path": str(secrets_file),
|
|
184
198
|
"is_packaged": path_manager.is_packaged,
|
|
199
|
+
"requires_sudo": requires_sudo,
|
|
200
|
+
"platform": platform,
|
|
185
201
|
}
|
|
186
202
|
|
|
187
203
|
try:
|
|
@@ -194,6 +210,8 @@ async def check_config():
|
|
|
194
210
|
"message": "密码未设置",
|
|
195
211
|
"config_path": str(secrets_file),
|
|
196
212
|
"is_packaged": path_manager.is_packaged,
|
|
213
|
+
"requires_sudo": requires_sudo,
|
|
214
|
+
"platform": platform,
|
|
197
215
|
}
|
|
198
216
|
|
|
199
217
|
if not sudoers_manager.verify_password(password):
|
|
@@ -203,6 +221,8 @@ async def check_config():
|
|
|
203
221
|
"message": "密码不正确",
|
|
204
222
|
"config_path": str(secrets_file),
|
|
205
223
|
"is_packaged": path_manager.is_packaged,
|
|
224
|
+
"requires_sudo": requires_sudo,
|
|
225
|
+
"platform": platform,
|
|
206
226
|
}
|
|
207
227
|
|
|
208
228
|
sudoers_manager.setup()
|
|
@@ -212,6 +232,8 @@ async def check_config():
|
|
|
212
232
|
"message": "配置正常",
|
|
213
233
|
"config_path": str(secrets_file),
|
|
214
234
|
"is_packaged": path_manager.is_packaged,
|
|
235
|
+
"requires_sudo": requires_sudo,
|
|
236
|
+
"platform": platform,
|
|
215
237
|
}
|
|
216
238
|
except HTTPException:
|
|
217
239
|
raise
|