自动更新
安装 @quickgui/extension-updater 即可包含自动更新。未安装的应用不会附带更新器。
#配置
bun add @quickgui/extension-updater#1. 创建签名密钥
每个更新都用 Ed25519 密钥签名。密钥对只需创建一次,并在应用的整个生命周期内保留:已安装的应用只接受用构建时那把密钥签名的更新。
bunx @quickgui/cli keygen --out-dir "$HOME/.config/my-app/update-keys"
cp "$HOME/.config/my-app/update-keys/quickgui-update.pub" ./update-public-key.pub该命令会写出 quickgui-update.pub 和 quickgui-update.key。公钥可以安全地提交;下面的配置从 update-public-key.pub 读取它。.key 文件是机密:请保存在密码管理器或 CI 的密钥存储中,绝不要放进仓库。一旦丢失,已安装的应用将永远无法再更新。
#2. 选择发布位置
把 updates.target 设为 "github" 或 "s3",并填写同名的配置段。target 决定 --upload 把文件发到哪里,以及应用检查哪些 URL,因此你无需自己填写 feed 或下载地址。两个配置段可以同时保留,只有 target 指定的那个会被使用。feed 地址会内置到应用中,所以之后修改 target 只影响修改之后构建的版本。在用户迁移完成之前,请继续向旧的 target 发布。
GitHub Releases 只需要仓库名。仓库必须是公开的,因为已安装的应用在下载更新时不带任何凭据。
import { readFileSync } from "node:fs";
import { defineConfig } from "@quickgui/cli";
export default defineConfig({
language: "typescript",
name: "My App",
identifier: "com.example.my-app",
version: "1.0.0",
entry: "app.tsx",
extensions: ["@quickgui/extension-updater"],
updates: {
target: "github", // publish to and update from GitHub Releases
publicKey: readFileSync(
new URL("./update-public-key.pub", import.meta.url),
"utf8",
).trim(),
automaticChecks: true, // default for new installs; users can change it
changelog: "CHANGELOG.md", // optional; this file is used by default when it exists
github: {
repository: "example/my-app", // owner/name of a PUBLIC repository
// tagPrefix: "v", // release tag = tagPrefix + version, e.g. v1.2.0
},
},
});Amazon S3 需要 bucket、所在区域,以及对外提供该 bucket 的公开 HTTPS 地址(bucket 自身的 URL,或其前面的 CloudFront 域名)。
import { readFileSync } from "node:fs";
import { defineConfig } from "@quickgui/cli";
export default defineConfig({
language: "typescript",
name: "My App",
identifier: "com.example.my-app",
version: "1.0.0",
entry: "app.tsx",
extensions: ["@quickgui/extension-updater"],
updates: {
target: "s3", // publish to and update from the bucket below
publicKey: readFileSync(
new URL("./update-public-key.pub", import.meta.url),
"utf8",
).trim(),
automaticChecks: true, // default for new installs; users can change it
changelog: "CHANGELOG.md", // optional; this file is used by default when it exists
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 bucket
},
},
});Cloudflare R2、MinIO 等兼容 S3 的存储 还需要 endpoint,即 S3 API 的地址。publicUrl 仍然是用户下载时访问的地址,对 R2 来说是自定义域名或 bucket 的 r2.dev URL。
import { readFileSync } from "node:fs";
import { defineConfig } from "@quickgui/cli";
export default defineConfig({
language: "typescript",
name: "My App",
identifier: "com.example.my-app",
version: "1.0.0",
entry: "app.tsx",
extensions: ["@quickgui/extension-updater"],
updates: {
target: "s3", // publish to and update from the bucket below
publicKey: readFileSync(
new URL("./update-public-key.pub", import.meta.url),
"utf8",
).trim(),
automaticChecks: true, // default for new installs; users can change it
changelog: "CHANGELOG.md", // optional; this file is used by default when it exists
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.whenReady() 之后、组件之外启动,让每个窗口共用一个会话:
import { app } from "@quickgui/native";
import Updater from "@quickgui/extension-updater";
await app.whenReady();
export const updater = new Updater({}, event => {
if (event.error) console.error(event.error);
if (event.quitRequired) {
void app.quit().catch(console.error);
}
});
await updater.ready;import { Button } from "@quickgui/solid";
import { updater } from "./updates";
export function CheckForUpdatesButton() {
return (
<Button style={{ p3: true, roundedLg: true }} onClick={() => void updater.check()}>
Check for Updates
</Button>
);
}#生命周期
| API | 用法 |
|---|---|
updater.check() | 手动检查更新。 |
updater.install() | 安装可用的更新。 |
updater.setAutomaticChecks(enabled) | 更改自动检查偏好。 |
updater.state | 读取最近一次事件。 |
updater.close() | 停止应用不再需要的会话。 |
状态为 idle、checking、available、downloading、installing 或 disabled。关闭窗口不得关闭共享的更新器。
开发构建默认报告 disabled。只在测试 feed 和可丢弃的应用包上使用 { allowDevelopment: true }。
#平台支持
macOS 使用 Sparkle。Windows 使用已签名的 NSIS 安装程序。Linux 使用 AppImage,或 quickgui build 生成的 install.sh tarball(curl -fsSL https://example.com/install.sh | sh 无需 root 即可安装到 ~/.local)。通过 Linux 包管理器安装的会报告 disabled。当前预构建包覆盖 macOS;其他目标需要在该主机上做原生构建。
#发布更新
#1. 在本机发布
先提高配置中的 version:应用只会安装比自身更高的版本。在 CHANGELOG.md 中为它添加 ## <version> 一节。然后把私钥交给构建,并带上 --upload 运行:
export QUICKGUI_UPDATER_PRIVATE_KEY="$(cat ~/.config/my-app/update-keys/quickgui-update.key)"
bunx @quickgui/cli 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
- 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 构建一次更新器。打包应用在配置中列出该扩展时会包含它。
