本文へスキップ

自動更新

quickgui クレートの updater 機能を有効にします。アップデーターは Rust コアの一部なので、Rust アプリは quickgui::updater を直接呼び出します。Go と TypeScript は同じ実装を拡張機能として読み込み、フィード、鍵、インストールヘルパーも共通です。

#設定

#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 が決まるため、フィードやダウンロードの URL を自分で書く必要はありません。両方のセクションを設定に残しておいても構いません。使われるのは target が指す方だけです。フィードのアドレスはアプリに組み込まれるため、後から target を変更しても、影響するのは変更後にビルドしたバージョンだけです。ユーザーが移行し終えるまでは、以前の target にも公開し続けてください。

GitHub Releases に必要なのはリポジトリ名だけです。インストール済みのアプリは認証なしで更新をダウンロードするため、リポジトリは公開されている必要があります。

import { readFileSync } from "node:fs";
import { defineConfig } from "@quickgui/cli";

export default defineConfig({
  language: "rust",
  name: "My App",
  identifier: "com.example.my-app",
  version: "1.0.0",
  entry: ".",
  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 には、バケット、そのリージョン、そしてバケットを配信する公開 HTTPS アドレス(バケット自身の URL、またはその前段の CloudFront ドメイン)が必要です。

import { readFileSync } from "node:fs";
import { defineConfig } from "@quickgui/cli";

export default defineConfig({
  language: "rust",
  name: "My App",
  identifier: "com.example.my-app",
  version: "1.0.0",
  entry: ".",
  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 互換ストレージ では、S3 API のアドレスである endpoint を追加します。publicUrl は引き続きユーザーがダウンロードに使うアドレスで、R2 ではカスタムドメインまたはバケットの r2.dev URL です。

import { readFileSync } from "node:fs";
import { defineConfig } from "@quickgui/cli";

export default defineConfig({
  language: "rust",
  name: "My App",
  identifier: "com.example.my-app",
  version: "1.0.0",
  entry: ".",
  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",
    },
  },
});

バケットは、アップロードされたファイルの公開読み取りを許可している必要があります。QuickGUI はあなたの認証情報でアップロードしますが、バケットポリシーは変更しません。

クレートの機能を有効にします。クレートのバージョンはプロジェクトの既存のものを使ってください:

[dependencies]
quickgui = { version = "...", features = ["updater"] }

#3. updates の全オプション

オプション必須意味
publicKeyはいquickgui-update.pub の内容。アプリに埋め込まれ、すべての更新の検証に使われます。
targetはい"github" または "s3"。どのセクションを使うかを選びます。
github.repositorytarget = "github" の場合は必須公開 GitHub リポジトリの owner/name。
github.tagPrefixいいえリリースタグはこの接頭辞にバージョンを付けたものです。既定は "v" で、バージョン 1.2.0 は v1.2.0 として公開されます。
s3.buckettarget = "s3" の場合は必須バケット名。
s3.publicUrltarget = "s3" の場合は必須バケットを配信する公開 HTTPS アドレス。クエリ文字列や認証情報は含められません。
s3.endpointいいえAWS 以外のプロバイダーの S3 API エンドポイント。AWS では省略します。
s3.regionいいえバケットのリージョン。R2 では "auto"。
s3.prefixいいえバケット内のフォルダー。publicUrl の後ろにも付加されます。
automaticChecksいいえ新規インストールが起動時に確認するかどうか。既定は true。ユーザー自身の選択は記憶され、優先されます。
changelogいいえすべてのバージョンをまとめた Markdown の変更履歴。version と一致する ## x.y.z セクションがリリースノートになります。CHANGELOG.md があれば既定でそれを使います。
ed25519SecretKeyいいえローカルビルド用の秘密鍵ファイルのパス。環境変数 QUICKGUI_UPDATER_PRIVATE_KEY が優先されます。
manifestいいえtrue にすると、--update-manifest と同様に、本番ビルドのたびに署名して更新フィードを書き出します。

#URL の決まり方

target によってすべてのアドレスが決まります。<file> は appcast-darwin-arm64.xml のようなファイル名、<tag> は tagPrefix にバージョンを付けたものです。

GitHubS3
更新フィード、install.sh、latest-linux-<arch>.txthttps://github.com/<repository>/releases/latest/download/<file><publicUrl>/<prefix>/<file>
インストーラーと更新アーカイブhttps://github.com/<repository>/releases/download/<tag>/<file><publicUrl>/<prefix>/<file>

GitHub では、フィードは常に Latest と表示されているリリースから取得されます。ドラフトとプレリリースは latest にならないため、通常のリリースとして公開するまでユーザーには届きません。--upload は常にドラフトを作成します。

CLI はバージョン、識別子、フィード URL、公開鍵をアプリに埋め込みます。秘密鍵が埋め込まれることはありません。

#アプリでの使用

アプリケーションの準備ができた後に Updater を 1 つ開始し、アプリケーションスレッドでイベントを読み取ります:

use std::rc::Rc;
use quickgui::updater::{UpdateStatus, Updater};

fn start_updates(&mut self, cx: &mut quickgui::EventContext) {
    let task = cx.spawn(|task_cx: quickgui::AsyncViewContext<Self>| async move {
        let Ok((updater, mut events)) = Updater::start(quickgui::updater_options!()).await else {
            return Ok(());
        };
        let updater = Rc::new(updater);
        task_cx.update({
            let updater = updater.clone();
            move |this, _| this.updater = Some(updater)
        }).await?;
        while let Some(event) = events.next().await {
            task_cx.update(move |this, cx| {
                if event.quit_required {
                    cx.exit();
                }
                this.update_available = event.status == UpdateStatus::Available;
                this.update_version = event.version.clone();
                cx.invalidate();
            }).await?;
        }
        Ok::<(), quickgui::AsyncContextError>(())
    });
    self.update_events = task.ok();
}

quickgui::updater_options!() は quickgui build が埋め込んだ設定に展開されます。単なる cargo run では空になり、すべての開発ビルドと同様にアップデーターは Disabled を報告します。

「アップデートを確認」アクションから updater.check().await を、更新バナーから updater.install().await を、設定画面から updater.set_automatic_checks(enabled).await を呼び出します。いずれもコマンドが受理または拒否された時点で完了し、結果はイベントとして届きます。updater.state() はネイティブ呼び出しなしで最新のイベントを返します。

#ライフサイクル

Updater はプロセスの存続期間中保持してください。ウィンドウではなくアプリケーションに属します。破棄すると確認とダウンロードがキャンセルされます。quit_required は、検証済みインストーラーがアプリの終了を待っていることを意味します。作業を保存してから cx.exit() を呼び出してください。終了がキャンセルされた場合、ヘルパーはタイムアウトして現在のアプリをそのまま残します。新しいバージョンが起動したことはコアがヘルパーに通知し、起動しなかった場合はヘルパーが以前のバージョンを復元します。

#対応プラットフォーム

プラットフォーム更新ペイロードインストール
macOS署名済み .app を含む ZIPSparkle(この機能を有効にした場合のみ同梱)
WindowsQuickGUI の NSIS .exe インストーラーヘルパーが終了を待ち、検証済みインストーラーを実行して再起動します
LinuxType-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 はこの 3 つをすべて公開し、GitHub ではスクリプトに固定の URL が付きます。curl -fsSL https://github.com/<owner>/<repo>/releases/latest/download/install.sh | sh は root なしで ~/.local/<package>.app にインストールし、アプリケーションメニューに登録します。このインストールは自動的に更新されます。不要な場合は linux.tarball = false を設定してください。

#更新の公開

#1. 手元のマシンからリリースする

まず設定の version を上げます。アプリは自分より高いバージョンしかインストールしません。そのバージョンの ## <version> セクションを CHANGELOG.md に追加します。次に、ビルドに秘密鍵を渡し、--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 はアプリをビルドし、更新ファイルに署名し、フィードを書き出して、すべてを公開します。完了すると公開 URL を 1 つずつ表示します。GitHub の場合、これらの URL はドラフトリリースを公開した後に有効になります。アップロードせずに署名とフィードの書き出しだけを行いたい場合(先に dist/ を確認したい場合など)は、代わりに --update-manifest を使います。このコマンドはターゲットごとに 1 回実行します。ターゲットごとに専用のフィードがあるため、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

バージョン付きファイルが先に、フィードが最後にアップロードされるため、フィードがまだ存在しないファイルを指すことはありません。GitHub では、最初のターゲットがリリース <tag> をドラフトとして作成し(そのバージョンの変更履歴セクションが説明になります)、以降のターゲットは自分のファイルをそこに追加します。再実行すると同名のファイルは置き換えられます。

ドラフトはユーザーには見えず、更新フィードも変わらないため、アップロード途中のリリースが配信されることはありません。すべてのターゲットのアップロードが終わったら、ドラフトを確認して公開します。リリースページから、または次のコマンドで行えます:

gh release edit v1.2.0 --repo example/my-app --draft=false

公開すると最新リリースになり、インストール済みのアプリは次回の確認時に更新を見つけます。S3 にはドラフトの段階がなく、ファイルはアップロードされた時点で有効になります。

#3. GitHub Actions からリリースする

秘密鍵を QUICKGUI_UPDATER_PRIVATE_KEY という名前のリポジトリシークレットとして追加し(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: dtolnay/rust-toolchain@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 }}

3 つのビルドジョブは同じドラフトリリースにアップロードし、すべてが成功した後にだけ publish がそれを公開するため、プラットフォームが欠けたリリースがユーザーに届くことはありません。gh CLI は GitHub のランナーにプリインストールされています。配布する macOS ビルドには --sign と --notarize も指定してください。Windows ではランナーに NSIS(makensis)が必要です。Linux は appimagetool がインストールされている場合にのみ AppImage を生成します(tarball には何も必要ありません)。

#リリースノート

リリースノートは、すべてのバージョンを 1 つにまとめた 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。
  • ノートは、その見出しの下から次の ## 見出しまでのすべてです。最初のリリース見出しより上にあるテキスト(タイトルや説明など)が公開されることはありません。
  • 必要であれば「追加」「修正」などのグループに ### を使えます。これらはセクションの中にとどまります。
  • 1 つのセクションは最大 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 コンポーネントでレンダリングするか、テキストとして表示してください。
macOSSparkle の更新ウィンドウが Markdown としてレンダリングします。
GitHub のリリースページ(target = "github")リリースの説明になります。

#S3 の認証情報

変数意味
AWS_ACCESS_KEY_ID または S3_ACCESS_KEY_IDバケットにオブジェクトを書き込む権限を持つアクセスキー
AWS_SECRET_ACCESS_KEY または S3_SECRET_ACCESS_KEYそのシークレット
AWS_SESSION_TOKEN または S3_SESSION_TOKEN一時的な認証情報の場合のみ

R2 では、バケットに対する Object Read & Write 権限を持つ API トークンを作成し、そのアクセスキー ID とシークレットを使います。CDN がバケットをキャッシュしている場合は、appcast-*.xml、latest-linux-<arch>.txt、install.sh のキャッシュ期間を短くしてください。これらはリリースのたびに変わります。

#Linux のインストールコマンド

Linux ビルドは install.sh も公開します。root なしで tarball を ~/.local/<package>.app にインストールし、アプリケーションメニューに登録します。このインストールは自動的に最新の状態に保たれます。ユーザーには次の 1 行を案内してください:

# 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 を付けるとアンインストールできます。tarball とスクリプトが不要な場合は linux.tarball = false を設定します。

#トラブルシューティング

  • アプリが「最新です」と表示する。 公開した version はインストール済みのものより高い必要があります。GitHub ではリリースが公開済みで(--upload はドラフトのままにします)、プレリリースに指定されていない必要があり、そのターゲットのフィードが存在している必要があります。上の表のフィード 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 見出しを追加し、その下に少なくとも 1 行書いてください。
  • ステータスが disabled になる。 開発ビルドは更新されません。.deb、システム全体、root 所有の Linux インストールも同様です。本番ビルドでテストしてください。
  • GitHub upload failed。 gh auth status、トークンにリポジトリへの書き込み権限があるか(Actions では contents: write)、リポジトリ名が正しいかを確認してください。
  • S3 upload failed。 認証情報、region、endpoint を確認してください。ダウンロード時の 403 は、バケットが公開読み取りを許可していないことを意味します。

#ネイティブ成果物

アップデーターは実行ファイルにリンクされます。quickgui build はこの機能を検出し、同じバージョンの @quickgui/extension-updater からインストールヘルパー(quickgui-updater-helper、macOS では Sparkle.framework)を実行ファイルの隣に配置します。現在の事前ビルド済みパッケージは macOS 向けです。他のホストでは、先にソースチェックアウトで bun packages/native/build.ts --extension updater を実行してください。