【免费下载链接】craft-launcher
The Craft App Launcher
ArtCraft Launcher 是一个纯 Rust 编写的跨平台应用启动器,负责安装、更新和启动整个 Craft App Suite。它还有一个"隐藏技能":自己能更新自己。一个正在运行的程序如何安全地下载、校验并替换掉自己的可执行文件?这篇文章带你完整剖析它的跨平台自更新机制。
为什么"自己替换自己"这么难 🤔
自更新要同时跨过三道坎:
- 文件锁:Windows 会锁住正在运行的
.exe,你根本没法在运行时直接覆盖它; - 单实例锁:ArtCraft Launcher 同一时间只允许一个实例,新版本启动时旧版本还占着位置,会互相顶掉;
- 多形态安装:macOS 是
.app打包目录,Windows 有 MSI 和绿色 zip 两种,Linux 有 AppImage、.deb、.rpm三种。每种"装进来的方式"都必须用同一种方式"换出去",否则安装会漂移。
源码作者在 crates/native/src/self_update.rs 开头用一张表把答案讲得很直白:
| 安装形态 | 替换方式 |
|---|---|
macOS.app | 直接从 DMG 把新包覆盖到.app(运行时可写),然后重新打开 |
| Windows MSI | 退出后由助手进程静默运行新 MSI(重装回原目录),再启动 exe |
| Windows 绿色 zip | 退出后把新 zip 解包覆盖到当前目录,设置文件不受影响 |
| Linux AppImage | 把新 AppImage 重命名覆盖旧的(运行时可行),再启动 |
Linux.deb/.rpm | 交给系统包管理器(会弹密码框),然后重启 |
其余情况(Flatpak、FreeBSD、开发构建)则放弃自更新,只给下载链接。
第一步:先搞清楚"我是怎么被装上的" 🔍
自更新的第一步是自我认知。Install枚举(self_update.rs#L37-L48)列出了五种可自更新的形态,current()会调用detect()判断当前进程属于哪一种:
- macOS:向上找
.app目录,读Info.plist里的CFBundleIdentifier,确认是自家 bundle; - Windows:exe 旁边有
portable.txt就是绿色版;否则查注册表HKCU\Software\Learning Machines LLC\ArtCraft Launcher的InstallDir或默认安装目录,匹配上才算 MSI 安装; - Linux:读
APPIMAGE环境变量判断 AppImage;在沙箱(Flatpak)里直接返回None;/usr/bin下的二进制则用dpkg-query和rpm -q确认包管理器。
这个判断的结果会写进 UI 能力位 Capabilities::self_update——能力为false时,界面上的按钮会从"更新并重启"降级为"下载",优雅兜底。
第二步:下载新安装包,先验明正身 📦
更新走的是和普通 Craft 应用完全相同的下载通道。pick_asset()会根据当前安装形态挑对应文件(MSI 安装就挑.msi,AppImage 就挑.AppImage),再由 download() 完成下载,规则很严格:
- 文件名必须合法(防路径穿越);
- 必须有 SHA-256 校验值:优先用 Release 里的 digest,没有就抓 SHA256SUMS.txt 补上;
- 发布方没提供校验和的文件直接拒绝安装——"没有凭证背书的东西绝不落地"。
Release 结构 对 GitHub 返回的一切视为不可信输入,下载链接必须指向仓库自己的 release 下载地址,否则整条 asset 被丢弃。
第三步:等老进程退出,助手进程接力 ⏳
这是整套机制最精妙的部分,核心思想只有一句:老进程负责换文件,新进程由一个"看守者"负责启动。
- 下载并校验完成后,先写入一个 self-update 标记(
self-update.txt,记录 tag 和时间戳),防止更新完重启后又被提示更新同一版本造成死循环; - 通过 claim_restart() 等待其他正在安装的应用全部结束(最多等 10 分钟,可手动取消),避免重启把别人的安装"腰斩";
- 启动一个脱离父进程的助手:Unix 上是一段
sh脚本(helper_command),每 0.1 秒轮询kill -0 $pid等老进程退出;Windows 上是一个隐藏窗口的 PowerShell(windows_helper),先Wait-Process再跑 MSI 或解包脚本,最后Start-Process启动新 exe; - 老进程退出,助手把新版本拉起来。
那新实例启动时,万一旧实例还没完全死透怎么办?single_instance.rs 里有个版本握手机制:后启动的实例会向监听端口发hello <版本>,运行中的实例回答running <版本>——后来者版本更新,就先退位让路。所以哪怕新旧版本短暂共存,结果也一定是新版接管、旧版退出。
几个平台细节很能体现功力:
- AppImage 替换(replace_appimage):先拷一份临时文件到旁边,
rename原子覆盖。运行中的副本挂载自已"断链"的 inode,所以覆盖不影响自己运行; - Windows MSI:用
/passive /norestart静默重装到同一目录(MSI 向导记录的安装目录,用户级安装无需管理员); - macOS
.app:直接从下载的 DMG 把新 bundle 拷过去——.app是目录,运行时可写,连重启都能省一步等待。
用户看到的是什么?✨
对普通用户来说,整个流程只有两个入口:
- 检测到新版本后,窗口顶部出现一条横幅("ArtCraft Launcher v0.2.0 可用"),按钮是"更新并重启";每个新版本只弹窗询问一次(launcher_update_dialog);
- 更新进行中,横幅变成进度条,依次显示
Downloading…→Verifying…→Waiting for other installs…→Installing, then restarting…(views.rs#L150-L172); - 如果开启了自动更新,trigger_auto_updates() 会在后台自动排队执行,每个版本只尝试一次。
检查更新本身也把自己算进去了:backend 在后台线程把整套 Craft 应用加上自己一起查最新版本,发现新版且 24 小时内没更新过时,才发出LauncherUpdate事件(backend.rs#L256-L266)。
小结
ArtCraft Launcher 的自更新设计可以总结为四个原则:
- 同进同出——用什么形态装的,就用什么形态换;
- 先验后装——没有 SHA-256 背书的新包一律拒绝;
- 助手接力——老进程只负责善后,启动新版的脏活交给脱离的助手进程;
- 优雅兜底——能力判断不过就降级为"请下载",绝不硬来。
想继续深挖,推荐从 self_update.rs 开始读起,它的文件头注释本身就是一份优秀的跨平台自更新设计文档。
【免费下载链接】craft-launcher
The Craft App Launcher
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考