自动更新
导入 github.com/egoist/quickgui/extensions/updater 即可包含自动更新。未导入的应用不会附带更新器。
#配置
#1. 创建签名密钥
每个更新都用 Ed25519 密钥签名。密钥对只需创建一次,并在应用的整个生命周期内保留:已安装的应用只接受用构建时那把密钥签名的更新。
quickgui keygen --out-dir ~/.config/my-app/update-keys该命令会写出 quickgui-update.pub 和 quickgui-update.key。.pub 的内容写入配置。.key 文件是机密:请保存在密码管理器或 CI 的密钥存储中,绝不要放进仓库。一旦丢失,已安装的应用将永远无法再更新。
#2. 选择发布位置
把 updates.target 设为 "github" 或 "s3",并填写同名的配置段。target 决定 --upload 把文件发到哪里,以及应用检查哪些 URL,因此你无需自己填写 feed 或下载地址。两个配置段可以同时保留,只有 target 指定的那个会被使用。feed 地址会内置到应用中,所以之后修改 target 只影响修改之后构建的版本。在用户迁移完成之前,请继续向旧的 target 发布。
GitHub Releases 只需要仓库名。仓库必须是公开的,因为已安装的应用在下载更新时不带任何凭据。
[updates]
target = "github" # publish to and update from GitHub Releases
publicKey = "PASTE_THE_CONTENTS_OF_quickgui-update.pub"
automaticChecks = true # default for new installs; users can change it
changelog = "CHANGELOG.md" # optional; this file is used by default when it exists
[updates.github]
repository = "example/my-app" # owner/name of a PUBLIC repository
# tagPrefix = "v" # release tag = tagPrefix + version, e.g. v1.2.0Amazon S3 需要 bucket、所在区域,以及对外提供该 bucket 的公开 HTTPS 地址(bucket 自身的 URL,或其前面的 CloudFront 域名)。
[updates]
target = "s3" # publish to and update from the bucket below
publicKey = "PASTE_THE_CONTENTS_OF_quickgui-update.pub"
automaticChecks = true # default for new installs; users can change it
changelog = "CHANGELOG.md" # optional; this file is used by default when it exists
[updates.s3]
bucket = "my-app-releases"
region = "us-east-1"
publicUrl = "https://my-app-releases.s3.us-east-1.amazonaws.com"
# prefix = "stable" # optional folder inside the bucketCloudflare R2、MinIO 等兼容 S3 的存储 还需要 endpoint,即 S3 API 的地址。publicUrl 仍然是用户下载时访问的地址,对 R2 来说是自定义域名或 bucket 的 r2.dev URL。
[updates]
target = "s3" # publish to and update from the bucket below
publicKey = "PASTE_THE_CONTENTS_OF_quickgui-update.pub"
automaticChecks = true # default for new installs; users can change it
changelog = "CHANGELOG.md" # optional; this file is used by default when it exists
[updates.s3]
bucket = "my-app-releases"
endpoint = "https://ACCOUNT_ID.r2.cloudflarestorage.com" # S3 API endpoint
region = "auto"
publicUrl = "https://downloads.example.com" # custom domain or https://pub-….r2.dev
# prefix = "stable"bucket 必须允许公开读取已上传的文件;QuickGUI 使用你的凭据上传,但不会修改 bucket 策略。
#3. updates 的全部选项
| 选项 | 是否必填 | 含义 |
|---|---|---|
publicKey | 是 | quickgui-update.pub 的内容。会嵌入应用,用于校验每一个更新。 |
target | 是 | "github" 或 "s3"。选择使用哪个配置段。 |
github.repository | target = "github" 时必填 | 公开 GitHub 仓库的 owner/name。 |
github.tagPrefix | 否 | release 标签为该前缀加版本号。默认 "v",因此版本 1.2.0 发布为 v1.2.0。 |
s3.bucket | target = "s3" 时必填 | bucket 名称。 |
s3.publicUrl | target = "s3" 时必填 | 对外提供该 bucket 的公开 HTTPS 地址。不能带查询字符串或凭据。 |
s3.endpoint | 否 | 非 AWS 服务商的 S3 API 地址。使用 AWS 时省略。 |
s3.region | 否 | bucket 所在区域;R2 使用 "auto"。 |
s3.prefix | 否 | bucket 内的文件夹,同时会追加到 publicUrl 后面。 |
automaticChecks | 否 | 新安装的应用是否在启动时检查更新。默认 true。用户自己的选择会被记住并优先生效。 |
changelog | 否 | 覆盖所有版本的 Markdown 更新日志。与 version 匹配的 ## x.y.z 一节会成为发行说明。存在 CHANGELOG.md 时默认使用它。 |
ed25519SecretKey | 否 | 本地构建使用的私钥文件路径。环境变量 QUICKGUI_UPDATER_PRIVATE_KEY 的优先级更高。 |
manifest | 否 | 设为 true 时,每次生产构建都会签名并写出更新 feed,效果等同于 --update-manifest。 |
#URL 从何而来
target 决定了所有地址。<file> 是类似 appcast-darwin-arm64.xml 的文件名,<tag> 是 tagPrefix 加版本号。
| GitHub | S3 | |
|---|---|---|
更新 feed、install.sh、latest-linux-<arch>.txt | https://github.com/<repository>/releases/latest/download/<file> | <publicUrl>/<prefix>/<file> |
| 安装程序和更新归档 | https://github.com/<repository>/releases/download/<tag>/<file> | <publicUrl>/<prefix>/<file> |
在 GitHub 上,feed 始终来自标记为 Latest 的 release。草稿和预发布永远不会成为 latest,因此在你把它们发布为正式 release 之前,用户不会收到它们。--upload 始终创建草稿。
CLI 会把版本、标识符、feed URL 和公钥嵌入应用。私钥绝不会被嵌入。App Store 和沙盒构建应通过 Go build tag 排除更新器的导入。
#应用用法
在 native.Run 就绪之后、组件之外启动。整个应用共用一个更新器。
import (
"log"
"github.com/egoist/quickgui/go/native"
"github.com/egoist/quickgui/extensions/updater"
)
var updates *updater.Updater
func startUpdater() {
updates = updater.Start(updater.Options{}, func(event updater.Event) {
if event.Error != "" {
log.Print(event.Error)
}
if event.QuitRequired {
native.App.Quit(false, nil)
}
}, func(err error) {
if err != nil {
log.Print(err)
}
})
}从「检查更新」菜单项调用 updates.Check(done),从可用更新横幅调用 updates.Install(done),从设置调用 updates.SetAutomaticChecks(enabled, done)。回调在 UI 协程上运行。State() 返回最近一次事件。
Options 可以覆盖 feed URL 和公钥。开发构建默认禁用。只在测试 feed 和可丢弃的应用包上使用 AllowDevelopment: true。
#生命周期
Event.Status 为 idle、checking、available、downloading、installing 或 disabled。事件包含版本、说明、进度、自动检查偏好,以及可选错误。
在 macOS 上,Sparkle 负责下载、校验、安装和重新启动。手动检查可以显示 Sparkle 的标准界面;定时检查可以把结果提供给应用内横幅。
Windows 和 Linux 在启用自动检查时,启动时检查一次。QuitRequired 表示助手正在等待应用退出。保存工作后调用 native.App.Quit(false, nil)。如果退出被取消,助手会超时并保留当前应用。
#平台支持
| 平台 | 更新包 | 安装方式 |
|---|---|---|
| macOS | 包含已签名 .app 的 ZIP | Sparkle,仅随更新器一起提供 |
| Windows | NSIS .exe 安装程序 | 助手等待退出,静默运行已校验的安装程序,然后重新启动 |
| Linux | Type-2 .AppImage,或 install.sh 安装的 tarball | 助手等待退出并替换用户拥有的 AppImage 或安装目录;新版本启动失败时会恢复旧版本 |
Linux .deb 包、系统二进制文件以及 root 拥有的安装会报告 disabled。这些情况请使用平台包管理器。
quickgui build 还会生成 <Name>-<version>-linux-<arch>.tar.gz、install.sh 和 latest-linux-<arch>.txt。--upload 会发布这三个文件,使用 GitHub 时脚本有固定的 URL:curl -fsSL https://github.com/<owner>/<repo>/releases/latest/download/install.sh | sh 无需 root 即可安装到 ~/.local/<package>.app,并把应用加入应用程序菜单,这种安装会自动保持更新。设置 linux.tarball = false 可跳过它。
Feed 是带 Ed25519 签名的 HTTPS Sparkle RSS appcast。没有未签名的回退。
#发布更新
#1. 在本机发布
先提高配置中的 version:应用只会安装比自身更高的版本。在 CHANGELOG.md 中为它添加 ## <version> 一节。然后把私钥交给构建,并带上 --upload 运行:
export QUICKGUI_UPDATER_PRIVATE_KEY="$(cat ~/.config/my-app/update-keys/quickgui-update.key)"
quickgui build --upload使用 GitHub 时,先用 gh auth login 登录一次(或设置 GH_TOKEN)。使用 S3 时,设置 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY。
--upload 会构建应用、为更新文件签名、写出 feed 并发布所有内容,完成后打印每个公开 URL;使用 GitHub 时,这些 URL 在你发布草稿 release 之后才可访问。如果只想签名并写出 feed 而不上传(例如先检查 dist/),请改用 --update-manifest。每个目标平台运行一次该命令;每个目标都有自己的 feed,因此 Mac 永远不会下载到 Linux 构建。
#2. 会上传哪些文件
| 目标 | 文件 |
|---|---|
| macOS | 给新用户的 <Name>.dmg、用于更新的 <Name>-<version>-<target>.zip、appcast-<target>.xml |
| Windows | <Name>-<version>-<target>.exe(安装与更新共用)、appcast-<target>.xml |
| Linux | .AppImage、<Name>-<version>-<target>.tar.gz、已配置时的 .deb、install.sh、latest-linux-<arch>.txt、appcast-<target>.xml |
带版本号的文件先上传,feed 最后上传,因此 feed 永远不会指向尚不存在的文件。在 GitHub 上,第一个目标会以草稿形式创建 release <tag>,并以该版本的更新日志一节作为描述,之后的目标把自己的文件追加进去。重新运行会替换同名文件。
草稿对用户不可见,更新 feed 也不会变化,因此尚未上传完整的 release 永远不会被推送给用户。所有目标都上传完成后,检查草稿并将其发布——可以在 release 页面操作,也可以运行:
gh release edit v1.2.0 --repo example/my-app --draft=false发布后它会成为最新 release,已安装的应用会在下次检查时发现更新。S3 没有草稿阶段:文件上传后立即生效。
#3. 通过 GitHub Actions 发布
把私钥添加为名为 QUICKGUI_UPDATER_PRIVATE_KEY 的仓库 secret(Settings → Secrets and variables → Actions),然后推送一个与配置中版本号一致的标签:
# .github/workflows/release.yml
name: Release
on:
push:
tags: ["v*"]
permissions:
contents: write # lets GITHUB_TOKEN create the release and upload assets
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: darwin-arm64
- os: ubuntu-22.04
target: linux-x64
- os: windows-latest
target: windows-x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-go@v5
with:
go-version: stable
- run: bun install
- run: bunx @quickgui/cli build --target ${{ matrix.target }} --upload
env:
QUICKGUI_UPDATER_PRIVATE_KEY: ${{ secrets.QUICKGUI_UPDATER_PRIVATE_KEY }}
GH_TOKEN: ${{ github.token }}
# For updates.s3 instead of GH_TOKEN:
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# updates.github only: the uploads above go into a draft release. Publish it once every
# target succeeded. Remove this job to review and publish drafts by hand.
publish:
needs: build
runs-on: ubuntu-latest
steps:
- run: gh release edit "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --draft=false
env:
GH_TOKEN: ${{ github.token }}三个构建 job 会上传到同一个草稿 release,只有它们全部成功后 publish 才会让它生效,因此用户永远不会拿到缺少某个平台的 release。GitHub 的 runner 已预装 gh CLI。对外分发的 macOS 构建还应加上 --sign 和 --notarize;Windows 需要 runner 上有 NSIS(makensis);Linux 只有在安装了 appimagetool 时才会生成 AppImage(tarball 不需要任何工具)。
#发行说明
发行说明来自一个覆盖所有版本的 Markdown 更新日志。发布 1.2.0 版本时,构建会取出 ## 1.2.0 标题下的那一节,并随更新一起发布。
updates.changelog 指定该文件,路径相对于项目。不设置时,如果项目目录中存在 CHANGELOG.md,就会使用它。
# Changelog
## 1.2.0 - 2026-09-19
Faster startup and a fix for lost drafts.
- Search inside a project with **Ctrl+Shift+F**
- Windows remember their size and position
- Fix drafts being lost when the app quits during a save
## 1.1.0
- Add a tray icon
- Fix a crash when opening an empty project- 版本标题的格式是
## x.y.z,其中x.y.z是配置中的version。后面可以跟日期:## x.y.z - 2026-09-19。 - 说明内容是该标题之下、直到下一个
##标题之前的全部内容。第一个版本标题之前的文字(例如标题或简介)永远不会被发布。 - 如果需要,可以用
###划分“新增”“修复”等分组;它们属于当前这一节。 - 每一节最多 16 KiB。
- 最新的版本放在最上面。请面向应用的使用者来写:对他们来说有什么变化,而不是提交历史。
如果缺少该标题,或者该节内容为空,发布会失败并提示 CHANGELOG.md has no notes for this release. Add a `## 1.2.0` section,这样就不会带着错误的说明发布。完全没有更新日志的项目会不带说明发布;更新仍会提供,只显示版本号。构建还会把提取出的这一节写入 dist/<target>/release-notes.md。
| 出现的位置 | 显示方式 |
|---|---|
| Windows 和 Linux | 你的应用会原样收到这段 Markdown(event.Notes)。可以用 Markdown 组件渲染,也可以直接作为文本显示。 |
| macOS | Sparkle 的更新窗口会将它渲染为 Markdown。 |
GitHub release 页面(target = "github") | 它会成为该 release 的描述。 |
#S3 凭据
| 变量 | 含义 |
|---|---|
AWS_ACCESS_KEY_ID 或 S3_ACCESS_KEY_ID | 有权向该 bucket 写入对象的访问密钥 |
AWS_SECRET_ACCESS_KEY 或 S3_SECRET_ACCESS_KEY | 对应的密钥 |
AWS_SESSION_TOKEN 或 S3_SESSION_TOKEN | 仅用于临时凭据 |
对于 R2,请创建对该 bucket 具有 Object Read & Write 权限的 API token,并使用它的 access key ID 和 secret。如果 bucket 前面有 CDN 缓存,请为 appcast-*.xml、latest-linux-<arch>.txt 和 install.sh 设置较短的缓存时间,因为它们每次发布都会变化。
#Linux 安装命令
Linux 构建还会发布 install.sh。它无需 root 即可把 tarball 安装到 ~/.local/<package>.app,把应用加入应用程序菜单,并且这种安装会自动保持更新。只需给用户一行命令:
# updates.github
curl -fsSL https://github.com/example/my-app/releases/latest/download/install.sh | sh
# updates.s3
curl -fsSL https://downloads.example.com/install.sh | sh在同一 URL 后加上 | sh -s -- --uninstall 即可卸载。设置 linux.tarball = false 可跳过 tarball 和脚本。
#故障排查
- 应用提示已是最新版本。 已发布的
version必须高于已安装的版本;GitHub 上的 release 必须已经发布(--upload创建的是草稿),并且不能标记为预发布;并且该目标的 feed 必须存在。用浏览器打开上表中的 feed URL 进行检查。 Set updates.publicKey and QUICKGUI_UPDATER_PRIVATE_KEY。 构建时没有私钥。请导出该变量或设置ed25519SecretKey。Update signing key does not match updates.publicKey。 私钥与配置的公钥不是一对。请使用最初的密钥;已发布出去的应用只信任它。CHANGELOG.md has no notes for this release。 添加一个与version完全一致的## x.y.z标题,并在其下至少写一行内容。- 状态为
disabled。 开发构建永远不会更新,.deb、系统级或 root 拥有的 Linux 安装也不会。请用生产构建测试。 GitHub upload failed。 检查gh auth status、token 是否有仓库写权限(Actions 中为contents: write),以及仓库名是否正确。S3 upload failed。 检查凭据、region和endpoint。下载时出现 403 表示 bucket 不允许公开读取。
#原生产物
在本仓库中,用 bun packages/native/build.ts --extension updater 构建一次更新器。打包应用在存在 Go 导入时会自动包含更新器。
