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。

相关推荐
kaixin_啊啊15 分钟前
【零基础学AI】第 27 章课后练习与答案
人工智能
慢慢来不要急17 分钟前
Anthropic 自建源码控制,AI Agent 把 Git 跑到了极限 · 11-09
人工智能·开发工具
CyrusYiVPP29 分钟前
实测 Ollama 本地部署:我用 Python 搓了个离线问答机器人,Qwen4 27B 一到换个模型名就能用
人工智能·python
菩提小狗33 分钟前
每日极客日报 · 2026年11月09日
ai·开源·极客日报·it热点·技术资讯
美团技术团队33 分钟前
美团 LongCat-2.5 开源:从看见,到抵达
人工智能
aifannihao39 分钟前
AI 学习第 8 课:1200 列纯噪声 CV 出 0.47,泄漏在筛特征那步
人工智能·机器学习·scikit-learn·学习路线·数据泄漏
chengliu050843 分钟前
从 Rag 到领域 Agent
agent
老金带你玩AI1 小时前
我宣布!Magpie就是版本答案!
人工智能
阿里云云原生1 小时前
TechTalk丨从能买奶茶到能进企业,Agent 绕不开同一组问题
云原生·agent
RobinDevNotes1 小时前
从 Electron 到 GPUI,9 个跨平台桌面方案该怎么选
前端·javascript·rust·electron·openresty·前端开发工具