Files
crossdesk/docs/UPDATER.md
T

134 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 桌面端应用内升级
Windows 安装版、macOS PKG 安装版和 Linux DEB 安装版共用
`apps/desktop/src/updater` 中的升级核心。各平台的安装和进程操作位于
`apps/desktop/src/platform/{windows,macos,linux}/updater`。
便携版仅检查版本并提供对应 ZIP 的手动下载地址,不构建或打包
`crossdesk_updater`,不启动安装流程。没有配置公钥、缺少有效签名、
发布清单版本不匹配或运行位置不受支持时,安装版也显示手动下载入口。
## 使用流程
1. 用户点击“安装更新”并确认连接和文件传输将中断。
2. 主程序异步下载,显示进度,并验证签名、大小和 SHA-256。
3. 升级器从私有暂存目录启动,取得安装目录对应的维护锁。
4. macOS/Linux 先请求系统授权,再由提权后的安装进程再次验证签名、
将包复制到只有管理员可写的私有目录并验证副本。完成这些步骤后才
通知主程序退出;等待授权期间远程连接继续运行,取消授权不会关闭
主程序。准备阶段最多等待五分钟。
5. 主程序收到就绪通知后提交安装并正常退出。Windows 保持原有流程:
先等待同一用户的 GUI 和守护进程退出,再启动提权安装进程验证副本。
6. 安装进程确认相关程序退出后调用平台安装器。macOS/Linux 未收到
主程序的安装提交、收到取消通知或等待提交超时时,不执行安装。
7. 升级器以原调用者身份重启程序。新程序进入 GUI 事件循环后确认启动;
90 秒内未收到确认会记录失败。启动确认不依赖远程服务器连通性。
系统代码签名不是这里的更新来源验证机制。Windows/macOS 安全检查、
管理员授权,以及现有 macOS 安装包的权限处理仍然适用。
## 发布密钥
客户端内置一个 Ed25519 公钥,发布端保存对应的私钥。不要把私钥提交到
仓库,也不要在普通构建或不可信 PR 的工作流中提供私钥。
用 OpenSSL 在可信环境生成密钥:
```sh
openssl genpkey -algorithm ED25519 -out crossdesk-update-private.pem
openssl pkey -in crossdesk-update-private.pem -pubout -outform DER -out crossdesk-update-public.der
python3 -c "from pathlib import Path; p=Path('crossdesk-update-public.der').read_bytes(); assert len(p)==44 and p[:12].hex()=='302a300506032b6570032100'; print(p[12:].hex())"
```
在 GitHub 仓库配置:
- Actions variable `CROSSDESK_UPDATE_PUBLIC_KEY`:上述 64 位小写十六进制公钥。
- Actions secret `CROSSDESK_UPDATE_PRIVATE_KEY`:PEM 私钥文件的完整内容。
桌面构建工作流将公钥传给 Xmake。只有发布清单生成步骤读取私钥。
首次需要手动安装包含升级器、公钥及新安装元数据的版本。
GitHub Actions 从分支手动打包时,在 `Build and Release` 的
`Run workflow` 中填写 `desktop_version`(例如 `1.7.2`),`patch` 保持
`0`。三个桌面平台会使用同一个数字版本并追加构建日期。空着不填的分支
构建使用 `0.0.0-日期`;版本标签构建继续使用标签中的版本号。
`desktop_version` 只影响桌面安装包,不改变移动端版本。
不要使用 `vauto-update-20261020` 这类分支名作为版本号验证升级。
新版本必须增加数字版本或 patch;只改变日期不视为升级。例如先手动安装
`v1.7.2-20261020`,再验证到 `v1.7.3-20261020` 的升级。
分支手动构建只上传 Actions 构建产物,不会自动发布版本清单。
本地构建可以设置同名环境变量,或使用:
```sh
xmake f --CROSSDESK_UPDATE_PUBLIC_KEY=<64位小写十六进制公钥>
xmake b crossdesk
```
没有公钥的构建可以正常运行和检查版本,但不执行自动安装。
私钥与公钥不匹配时,清单生成失败。当前只支持单个公钥;更换公钥前需要
设计过渡版本,不能直接替换发布私钥而让旧客户端继续自动升级。
## 清单格式
`.github/scripts/update_version_manifest.py` 为安装包写入 `size`、`sha256`。
配置私钥后,桌面安装包还包含:
```json
{
"update": {
"payload": "{\"filename\":\"...\",\"platform\":\"windows-x64\",\"schema\":1,\"sha256\":\"...\",\"size\":123,\"url\":\"...\",\"version\":\"...\"}",
"signature": "128位小写十六进制Ed25519签名"
}
}
```
签名覆盖 `payload` 字符串的原始 UTF-8 字节;客户端先验签,再解析。
签名包含版本、平台、包名、下载地址、大小和哈希,避免下载记录被互换。
仅允许 `https://downloads.crossdesk.cn/` 下与版本和平台匹配的包名,
禁止下载重定向,包大小上限为 2 GiB,禁止同版本安装及降级。
便携包和移动端包不会获得此自动安装入口。
## 平台边界
- Windows:安装目录必须匹配 HKLM 卸载记录中的 `InstallLocation`。
停止 CrossDesk 服务并等待其退出;确认同一路径的主程序全部结束后,
以 `/S /CROSSDESK_UPDATE /D=...` 调用 NSIS。升级模式不按进程名强杀
程序,且由升级器负责重启。原先运行的服务会尝试恢复。
- macOS:支持 `/Applications/CrossDesk.app`,通过系统授权调用
`/usr/sbin/installer` 安装 PKG。其他安装位置使用手动下载。
升级器只能依赖系统动态库,打包时验证这一点。
- Linux:支持 `/usr/lib/crossdesk/crossdesk-bin`,根据 `package-name`
选择普通包或虚拟桌面包。使用 `pkexec` 授权和 `apt-get install`
安装本地 DEB;禁止包管理器自动删除依赖。需要可用的授权代理。
自定义 systemd/headless supervisor 必须由管理员先协调停止;
检测到仍在运行的程序会中止升级,不猜测或停止其他服务。
退出等待有时间限制,跨用户仍有程序运行会报告失败。维护锁在进程退出时
由系统释放,遗留的 POSIX 锁文件不会被当作正在升级。
## 失败处理与验证
下载、验签或准备失败时不关闭主程序。主程序退出后的安装失败会尝试
重启;macOS/Linux 在退出前取消授权时保留原程序和连接。
`last-update` 位于应用缓存目录,指向私有暂存目录;`request.json` 和
`result.json` 保留升级版本及结果,未完成的任务不会自动重放。成功启动后
删除大型安装包,保留少量诊断文件。失败任务保留下载包便于排查。
NSIS、PKG 和 DEB 的安装操作不是跨平台事务。当前没有自动回滚承诺:
安装中断或新版本启动失败时,保留错误并提供手动修复安装路径。不能通过
只恢复 EXE 来回滚服务、依赖库或配置迁移。
```sh
xmake b update_checker_test version_checker_test
xmake r update_checker_test
xmake r version_checker_test
python3 -m unittest discover -s .github/scripts -p test_update_version_manifest.py
xmake lua apps/desktop/tests/build_define_scope_test.lua windows true
```
发布前仍须在各平台的隔离安装环境验证:授权取消、其他会话占用、服务
退出、安装失败、升级器自身更新和新程序启动确认。不要在开发机上通过
自动运行生产安装包来代替这些安装测试。