Rust 桌面应用自托管自动更新:latest.json 清单 + minisign 签名校验的完整做法

Rust 桌面应用自托管自动更新:latest.json 清单 + minisign 签名校验的完整做法

学习目标:学完这篇,你会给自己的 Rust 桌面应用接上一套不依赖 Tauri updater / electron-updater 的自托管自动更新------清单轮询、下载、验签、换包、重启,全链路自己可控,还能防 CDN 投毒。步骤给全,可直接照抄。

0. 先说问题

我的 boundless 是个 Rust 写的桌面白板(GPUI 框架),发版后用户手里的版本就凝固了。靠用户手动下载新包,更新率惨不忍睹。Tauri 和 Electron 都有官方 updater,但纯 Rust 写的桌面应用没有现成轮子------那就自己搭一条链路。

boundless 的做法是:一个 latest.json 清单 + minisign 签名,src/updater.rs 几百行 Rust 全链路打通,外加一份 139 行的配置手册 UPDATER_CONFIG.md。今天把这套做法拆开讲。

1. 前置知识:更新链路只有五步

动手前先把链路想清楚,自动更新归根结底是五步:查 (有没有新版)→ 下 (下载产物)→ 验 (签名对不对)→ 换 (替换旧文件)→ 起(重启)。每一步的失败处理想清楚了,链路就稳了。

再科普一个概念「minisign」:轻量签名工具,一对密钥,签名就是 4 行文本的 .sig 文件。公钥可以编译进二进制------这是整套方案信任锚的关键。

2. 第一步:生成密钥对,公钥 bake 进二进制

用仓库自带的 examples/gen_minisign_keypair.rs 生成密钥对。注意一个坑:必须用这个 helper 生成,别用 minisign CLI ------两者密钥 KDF 不一致,用 CLI 生成的密钥,签名端(examples/sign_release.rs,同一个 minisign crate)解不开。

公钥写成常量编译进二进制:

rust 复制代码
pub const MANIFEST_URL: &str = "https://boundlesscdn.xi-n.com/boundless-latest.json";
pub const MINISIGN_PUBKEY: &str = "RWTgy8P9e5EU/l+TFMe/JOCA/Dkn+5dDYdQgQySZUl959/zDM+NnbK9X";

私钥只存在 CI 的 secret 里,这辈子不出 CI。还有个守卫函数:

rust 复制代码
pub fn signing_configured() -> bool {
    !MINISIGN_PUBKEY.contains("REPLACE_ME")
}

公钥还是占位符 REPLACE_ME 时,验签直接拒绝------没配好密钥的构建,更新功能就当不存在。

翻译成人话:验签的"根信任"钉死在二进制里,不经过网络。CDN 被投毒也装不上坏包。

3. 第二步:CI 签名产物,生成 latest.json

发版时 CI 跑 examples/sign_release.rs:用私钥给产物签名,生成 <产物>.sig,把签名内容嵌进 latest.json:

json 复制代码
{
  "version": "0.4.8",
  "notes": "...",
  "pubdate": "...",
  "platforms": {
    "windows-x86_64": { "signature": "<.sig 的 4 行文本>", "url": "https://..." },
    "darwin-aarch64": { "signature": "<.sig 的 4 行文本>", "url": "https://..." }
  }
}

对应代码里的 Manifest / PlatformEntry 结构(src/updater.rs)。schema 兼容 Tauri updater 那套(version、notes、pubdate、按平台分的 signature + url),latest.json 托管在 CDN 上。平台 key 按系统区分:windows-x86_64、darwin-aarch64、darwin-x86_64,不支持的平台直接标 unsupported。

4. 第三步:客户端轮询清单,比版本

rust 复制代码
pub async fn fetch_manifest() -> Result<Manifest>

拉 latest.json 时,URL 后面拼 ?t={时间戳} 做缓存 bust------不然 CDN 缓存会让你"永远没新版"。is_newer() 做 semver 版本比较,current_version() 取编译进二进制的 CARGO_PKG_VERSION。

调试技巧:debug 构建认 BOUNDLESS_FAKE_VERSION 环境变量,可以假装自己是旧版本,把"标题栏图标 → 更新弹窗 → 重启提示"整条 UI 链路走一遍,不用真发版。

5. 第四步:下载到临时文件,带进度

rust 复制代码
pub async fn download<F>(url: &str, dest: &Path, progress: F) -> Result<()>

异步下载,带进度回调(给进度条用)。关键:先下到临时文件,不直接覆盖运行中的程序。下载和换包是两个阶段,中间隔着验签。

6. 第五步:验签,不通过绝不应用

rust 复制代码
pub fn verify(artifact: &Path, signature: &str) -> Result<()>

用编译进二进制的公钥验 .sig。签名不对、公钥是占位符,一律拒绝,更新流程到此为止,旧版本继续跑。

翻译成人话:这是整条链路最重要的一行纪律------验签失败就当没这回事,绝不"先装上再说"。自动更新最大的风险不是"更不上",是"更上了坏东西"。

7. 第六步:平台差异化换包

rust 复制代码
pub fn apply(artifact: &Path) -> Result<()>
  • Windows :运行中的 exe 不能被覆盖,但可以被改名。做法:从 zip 里解出新 exe 放到临时位置 → 把运行中的 exe 改名成 .old → 把新 exe 移进来 → 移动失败就回滚(把 .old 移回来,保证应用还能启动)。.old 下次启动时由 cleanup_old() 清理。
  • macOS :不能替换运行中的文件,那就整包 swap------把新的 Boundless.app 解到同级临时目录,整个目录换掉旧包。
  • 其他平台:直接 bail "not implemented"。不支持就是不支持,不装假。

8. 第七步:重启,退出旧进程

rust 复制代码
pub fn restart() -> Result<()>

起新进程,然后 std::process::exit(0)。新旧交接完成,用户下次看到的就是新版本。

9. 为什么这样设计

  1. 公钥编译进二进制:信任锚不经过网络,这是防投毒的根本。
  2. macOS 整包 swap:系统限制绕不过去,就顺着它------换整个 .app 而不是单个文件。
  3. Windows 两步换包 + 回滚:rename 旧文件、move 新文件,中间失败能回到旧版本,应用永远可启动。
  4. 占位符守卫:密钥没配好的构建,更新功能静默不存在,而不是带着假信任跑。

10. 避坑清单

  1. 密钥用配套 helper 生成,别用 minisign CLI(KDF 不一致,签名端解不开)。
  2. latest.json 一定要加缓存 bust,不然 CDN 缓存让你"永远没新版"。
  3. 验签失败的处理是"当没这回事",不是重试------签名不对重试 100 次也不对。
  4. Windows 换包顺序别反:先 rename 旧的,再 move 新的,失败回滚。直接覆盖运行中的 exe 会失败。

11. 总结

自托管自动更新七步:密钥对(公钥 bake 进二进制)→ CI 签名 + latest.json → 客户端轮询比版本 → 下载到临时文件 → 验签(不过就停)→ 平台差异化换包 → 重启。核心思想一句话:信任锚钉死在二进制里,失败时永远能回到可启动的旧版本。

给开发者的建议:如果你也在用 Rust 写桌面应用(GPUI、egui、iced 都一样),这套链路可以直接抄------updater.rs 的结构是平台无关的,换掉产物格式和平台分支就能用。

项目开源在 GitHub:tsingliuwin/boundless,觉得有用欢迎点个 star。

相关推荐
打工仔折腾 AI1 小时前
极空间部署Portainer实战:绕过Socket权限、解决9000端口冲突与固定内网访问
人工智能·后端·python·性能优化
前端双越老师1 小时前
程序员 1v1 访谈系列第五期:裁掉正岗,招外包
agent·ai编程·全栈
嘻嘻的AI日记1 小时前
涉外涉密文件怎么译?本地化 AI 翻译,原文不出内网
人工智能
科创致远1 小时前
第一代 e+SOP 盒子配显示器实测:物美价廉的产线可视化方案
大数据·网络·人工智能·信息可视化·计算机外设·制造·精益工程
志尊宝1 小时前
AI原生前端开发(010):AI时代的开发者工作流重构
人工智能·重构·ai-native
努力变大白1 小时前
用 ReAct 搭一个简单 Agent:零基础操作指南
人工智能·agent·react框架
像风一样自由20201 小时前
59.实战为RAG系统加入评估监控权限和审计
人工智能·智能体
QYR-分析1 小时前
汽车可开启全景天窗:座舱舒适核心配置,智能化升级打开产品溢价空间
大数据·人工智能·汽车
king13598745501 小时前
LiteRT Tensor API:C++ 编写端侧模型图,XNNPACK、GPU 与 NPU 怎么选?
开发语言·c++·人工智能·chatgpt·架构·aigc