Wails v3项目 GitHub Release 应用版本更新
背景
ZGit 是基于 Wails v3 + Go + Vue 3 的 Git 桌面工具。
安装包通过 GitHub Actions 构建并发布到独立仓库 psvmc/z-git-releases。
应用需要具备:
- 启动后自动检查 GitHub Release 是否有新版本
- 系统设置中支持手动「检查更新」
- 用户确认后再下载安装,支持「稍后 / 跳过该版本 / 立即更新」
- Windows 下载 NSIS 安装包后弹出安装向导(非静默),无需管理员权限;默认安装到
%LOCALAPPDATA%\Programs\z-git,并记住上次安装目录;安装完成页可勾选「立即运行」 - 下载过程中显示进度条(百分比、速率、校验/准备安装阶段)
- macOS 尽量做到一键替换并重启;Linux 走 deb/rpm 包安装
本文记录 ZGit 项目中的具体实现方式,便于后续维护或在其它 Wails v3 项目中复用。
配置要求
Windows下 安装包的 staging 目录要放在系统的临时文件夹
下载超时时长设置为10分钟,Wails GitHub 更新 provider 默认 HTTP 客户端也设置为10分钟。
NSIS 安装包的安装作用域设置为 **user **
整体架构
1 | ┌─────────────────────────────────────────────────────────────┐ |
核心思路:下载与替换逻辑交给 Wails 内置 Updater,业务 UI 自己实现(Window: WindowNone 不用框架自带小窗),通过 UpdateService 暴露给前端。
版本号与构建标签
当前版本定义在 version.go,需与 build/config.yml 的 info.version 及各平台打包配置保持一致:
1 | // version.go |
自动更新仅在 production 构建 中启用(wails3 dev 不会弹更新):
1 | // internal/update/enabled_prod.go (//go:build production) |
Release 构建需带 -tags production,与项目 build/windows/Taskfile.yml 等配置一致。
发版前可用 scripts\set-version.bat 同步版本号:
1 | scripts\set-version.bat REM 交互:当前 1.4.1 时提示 e.g. 1.4.2,回车即写入 |
脚本会更新 version.go、build/config.yml,并执行 wails3 task common:update:build-assets 同步 NSIS / deb / macOS 等打包配置。
初始化 Updater
main.go 中注册 UpdateService,并在启动时初始化 Updater:
1 | if err := update.InitUpdater(app, Version); err != nil { |
internal/update/setup.go 使用 GitHub Releases Provider,数据源为:
1 | const GitHubRepository = "psvmc/z-git-releases" |
等价于请求:
1 | https://api.github.com/repos/psvmc/z-git-releases/releases/latest |
初始化时关闭框架自带更新窗口,UI 完全自定义:
1 | app.Updater.Init(updater.Config{ |
按平台匹配 Release 资源
GitHub Release 上各平台产物文件名固定,通过自定义 AssetMatcher 精确匹配,避免默认规则误选:
| 平台 | 资源文件名 |
|---|---|
| Windows amd64 | ZGit-amd64-installer.exe |
| Windows arm64 | ZGit-arm64-installer.exe |
| Windows 386 | ZGit-386-installer.exe |
| macOS | ZGit-macos-universal.zip |
| Linux deb 系 | ZGit.deb |
| Linux rpm 系 | ZGit.rpm |
Linux 根据 /etc/os-release 等判断优先 deb 还是 rpm(internal/update/asset.go)。
UpdateService 对外方法
| 方法 | 作用 |
|---|---|
GetCurrentVersion() |
返回当前运行版本 |
CheckForUpdate() |
调用 app.Updater.Check,有新版本则返回 release 信息 |
ApplyUpdate() |
下载并安装,各平台策略见下节 |
SkipUpdateVersion(version) |
跳过指定版本,持久化到本地配置 |
CheckForUpdate 返回结构体包含:available、enabled、currentVersion、latestVersion、releaseName、notes、releaseURL 等字段,供前端展示更新日志。
各平台安装策略
Windows
Windows 发布产物为 NSIS 安装包(wails3 task windows:package 或本地 wails3 task package),不再单独发布裸 ZGit.exe。
NSIS 安装包配置
本地与 GitHub Actions 共用同一套配置(build/windows/Taskfile.yml + build/windows/nsis/project.nsi),默认 user 作用域:
| 项目 | 配置 |
|---|---|
| 安装作用域 | user(INSTALL_SCOPE 默认 user) |
| 执行级别 | RequestExecutionLevel user,不触发 UAC |
| 默认安装路径 | %LOCALAPPDATA%\Programs\z-git |
| 注册表 | HKCU 写入 InstallLocation |
| 完成页 | 勾选「立即运行 ZGit」(默认勾选;LangString 多语言,中文系统不乱码) |
build/windows/Taskfile.yml 在 user 模式下调用 makensis 时传入:
1 | /INPUTCHARSET UTF8 -DWAILS_INSTALL_SCOPE=user -DREQUEST_EXECUTION_LEVEL=user |
/INPUTCHARSET UTF8 强制按 UTF-8 解析脚本,避免中文 LangString 在本地与 GitHub Actions 上编译乱码。
project.nsi 用 !ifndef WAILS_INSTALL_SCOPE 兜底为 user,避免与 makensis 命令行重复定义冲突。完成页文案勿在 !define 里直接写中文,应使用 LangString + SimpChinese / English 语言包。
应用内更新流程
DownloadAndInstall— 从 GitHub 下载 NSIS 安装包到系统 Temp 目录(前端订阅wails:updater:download-progress等事件,弹出UpdateProgressDialog显示进度)- 同盘 staging — 将安装包复制到当前 exe 同目录(
RelocateBesideExecutable),避免 Temp 路径或跨盘导致安装程序异常 - 可见 NSIS 向导 — 不带
/S,以独立进程启动安装程序(ApplyVisibleDetachAttrs);不能用CommandContext,否则ApplyUpdate返回后 context 被 cancel 会杀掉安装进程 app.Quit()— 主进程退出,用户在 NSIS 向导中完成覆盖安装;勾选「立即运行」则安装完成后自动启动新版本
NSIS 脚本会写入并读取注册表 InstallLocation,升级时自动预填上次安装目录;旧版本若无该字段,则从 UninstallString 解析路径作为回退。
1 | !ifndef WAILS_INSTALL_SCOPE |
注意:若用户曾用旧版 machine 作用域装到
Program Files,首次切到 user 安装包时会装到%LOCALAPPDATA%\Programs\z-git;之后升级会记住该目录。建议卸载旧版 machine 安装残留。
macOS
流程:
DownloadAndInstall— 从 GitHub 下载到系统 Temp 目录- 同盘 staging — 将文件复制到 exe 同目录(见下节「跨盘修复」)
- 启动 Wails Helper 进程 — 等待主进程退出后
Rename替换二进制 app.Quit()— 主进程退出,Helper 完成替换并拉起新版本
macOS 的 zip 内含 .app,Wails Updater 会先解压再交给 Helper;swapTarget 会定位到 .app bundle 路径。
Linux
Linux 不直接替换正在运行的二进制,而是下载 deb/rpm 后:
- 优先
pkexec dpkg -i/pkexec rpm -U - 其次尝试
sudo - 都不可用则
xdg-open打开安装包,提示用户手动安装
安装命令在独立进程中执行,随后 app.Quit()。
跳过版本
用户可在更新弹窗点击 「跳过该版本」。实现要点:
- 调用
app.Updater.SkipVersion(version)— 内存中生效 - 写入
%APPDATA%\z-git-tools\config.json的skippedUpdateVersion— 重启后仍生效 - 应用启动时
applySkippedVersion读配置并恢复 Skip 状态
仅跳过指定版本号;若之后发布更高版本(如跳过 1.2.2,后来有 1.2.3),仍会正常提醒。
前端实现
启动时检查
App.vue 在恢复标签页与批量刷新完成后触发(waitForUiReady 延迟,避免与 WebView 初始化竞争):
1 | onMounted(async () => { |
useAppUpdate.ts 封装检查逻辑;有新版本时弹出 UpdateConfirmDialog,无新版本时启动检查静默跳过(手动检查才 Toast「已是最新」)。
下载进度弹窗
用户点击「立即更新」后,不再用全局 Loading 遮罩,改为 UpdateProgressDialog(360px 宽,与批量刷新进度弹窗风格一致):
| 阶段 | 显示内容 |
|---|---|
| 下载中 | 百分比、已下载/总大小、下载速率 |
| 校验中 | 正在校验数字签名… |
| 准备安装 | 正在解压并准备安装程序… |
useUpdateProgress.ts 订阅 Wails Updater 事件:
wails:updater:download-started/download-progresswails:updater:verifying/installing
通过 runWithProgress(() => UpdateService.ApplyUpdate()) 在调用前后自动打开/关闭进度状态。
更新确认弹窗
UpdateConfirmDialog.vue(680px 宽)展示:
- 版本对比:
v1.2.1 → v1.2.3 - Release 标题
- 可滚动更新日志区域(最高 320px)
- 三个按钮:跳过该版本 | 稍后 | 立即更新
弹出更新确认前会关闭 批量刷新结果弹窗;系统设置里点「检查更新」会先 关闭设置弹窗 再检查。
系统设置入口
SystemSettingsDialog.vue 增加「软件更新」区块,显示当前版本与「检查更新」按钮,复用同一套 useAppUpdate 逻辑。
与 CI 发版的配合
Release 由 GitHub Actions 构建(workflow 在 scripts/release/github-workflow-release-all.yml),从 Gitee 拉 tag 源码,三平台产物上传到 psvmc/z-git-releases。
发版流程(简要):
1 | git tag 1.2.3 |
Updater 依赖 Release 资源文件名与 AssetMatcher 一致,发版 workflow 中 copy 产物时需保持命名,例如:
- Windows:
ZGit-amd64-installer.exe(NSIS 安装包) - macOS:
ZGit-macos-universal.zip - Linux:
ZGit.deb/ZGit.rpm
发版前可用 scripts\set-version.bat 一键同步 version.go、build/config.yml 及各平台打包配置中的版本号。交互模式下会根据当前版本自动建议 patch +1(如 1.4.1 → 1.4.2),直接回车即采用该版本;也可传参指定,例如 scripts\set-version.bat 1.5.0。
本地 Windows 验证更新流程时,可执行 scripts\build-release.bat 生成 bin\ZGit-amd64-installer.exe(需 NSIS),与 CI 产物一致。两者均走 build/windows/Taskfile.yml,默认 INSTALL_SCOPE=user,NSIS 脚本为 build/windows/nsis/project.nsi。
关键代码
后端
注册服务
main.go:
1 | if err := update.InitUpdater(app, Version); err != nil { |
初始化 GitHub Provider
internal/update/setup.go:
1 | func InitUpdater(app *application.App, currentVersion string) error { |
按平台匹配 Release 资源
internal/update/asset.go:
1 | func PreferredAssetName(platform, arch string) string { |
暴露给前端的 Service
services/update_service.go:
1 | func (s *UpdateService) CheckForUpdate() (models.UpdateCheckResult, error) { |
macOS 安装(Helper 替换)
services/update_apply_other.go(//go:build darwin):
1 | func (s *UpdateService) applyPlatformUpdate(ctx context.Context) error { |
Windows 安装(NSIS 可见向导)
services/update_apply_windows.go(//go:build windows):
1 | func (s *UpdateService) applyPlatformUpdate(ctx context.Context) error { |
Linux 安装 deb/rpm
services/update_apply_linux.go:
1 | func (s *UpdateService) applyPlatformUpdate(ctx context.Context) error { |
跨盘 staging 与 Helper
Windows 将 NSIS 安装包复制到 exe 同目录后再启动;macOS 将更新包 staging 后交给 Helper 替换。共用 RelocateBesideExecutable:
internal/update/staging.go:
1 | // RelocateBesideExecutable — 复制到 exe 同目录,避免跨盘或 Temp 路径问题 |
internal/update/helper_restart.go:
1 | // helper_restart.go — 启动 Wails Helper 替换二进制 |
启动时恢复跳过版本
internal/update/skip.go:
1 | func applySkippedVersion(app *application.App) { |
配置写入 %APPDATA%\z-git-tools\config.json:
1 | { |
前端
检查、确认、安装
frontend/src/composables/useAppUpdate.ts:
1 | const promptAndApply = async (result: UpdateCheckResult) => { |
更新确认弹窗
frontend/src/composables/useUpdateConfirm.ts:
1 | export type UpdatePromptChoice = 'confirm' | 'later' | 'skip' |
frontend/src/components/dialogs/UpdateConfirmDialog.vue 底部按钮:
1 | <NSpace justify="space-between" align="center" class="update-footer"> |
更新日志区域使用可滚动 <pre>,弹窗宽度 680px、max-height: 320px。
启动时检查
frontend/src/App.vue:
1 | const dismissBlockingDialogs = () => { |
系统设置里点击「检查更新」时先 show.value = false 关闭设置弹窗,再调用 checkForUpdate()(frontend/src/components/dialogs/SystemSettingsDialog.vue)。
调试建议
| 场景 | 处理方式 |
|---|---|
| 开发模式不检查更新 | 正常,需 production 构建 |
| macOS 下载成功但未替换 | 查看 %TEMP%\wails-update-*.log |
| macOS 跨盘 Rename 失败 | 确认已包含 relocateNextToExecutable 逻辑 |
| Windows 更新无反应 | 确认 Release 上传的是 *-installer.exe;不要用 CommandContext 启动安装程序 |
| Windows 更新要求管理员 | 确认安装包为 user 作用域(INSTALL_SCOPE=user),勿用 machine;旧版 machine 安装需先卸载 |
| Windows 安装程序闪退 | 旧版用 /S 静默安装或 context cancel 杀进程;现改为可见 NSIS + 独立 exec.Command |
| 升级时安装目录被重置 | 确认 NSIS 脚本写入/读取 InstallLocation(user 为 HKCU,build/windows/nsis/project.nsi) |
| makensis 报重复定义 | WAILS_INSTALL_SCOPE 勿在 project.nsi 中无条件 !define,应用 !ifndef 包裹 |
| 完成页「立即运行」乱码 | 勿在 MUI_FINISHPAGE_RUN_TEXT 直接写中文;用 LangString + SimpChinese/English,makensis 加 /INPUTCHARSET UTF8 |
| 手动验证 | 将 version.go 改为低于 Release 的版本后构建测试 |
小结
ZGit 的自动更新基于 Wails v3 内置 Updater + GitHub Releases Provider,业务层补充了:
- 自定义更新确认 UI(含跳过版本、更新日志)
- 按平台精确匹配 Release 资源(Windows 为 NSIS 安装包)
- Windows NSIS user 作用域可见安装向导(无需管理员)+ 默认路径
%LOCALAPPDATA%\Programs\z-git+ 完成页「立即运行」+ 下载进度条 + 记住安装目录;macOS 同盘 staging + Helper 替换 - Linux deb/rpm 安装策略
- 跳过版本持久化
- 与批量刷新、系统设置等弹窗的联动关闭
若在新项目中复用,最少需要:InitUpdater + UpdateService + 前端确认弹窗 + 下载进度弹窗 + Release 资源命名约定;Windows 生产环境建议发布 user 作用域 NSIS 安装包(默认路径 %LOCALAPPDATA%\Programs\<app>),更新时用独立进程启动可见安装向导(勿用 CommandContext、勿用 /S 静默);macOS 建议保留同盘 staging 逻辑。