Tauri v2で作ったデスクトップアプリに、GitHub Actions経由の自動更新機能とmacOS向けのコード署名・Notarization(公証)を導入した。
これやれば、AppStoreを使って配信していないアプリでもバージョンアップをした際に最新版があることをユーザーに通知し、更新を促すことができる。
リポジトリは公開していないので、配信先にはCloudflare R2を使っている。
設定箇所が多く、ハマりポイントもいくつかあったので、一連の流れをまとめて記録しておく。
この記事はmacOSへの配信のみを取り扱っています。
myappと書かれているところはアプリ名です。適宜置き換えてください。
Tauriアップデーターの概要
Tauri v2には @tauri-apps/plugin-updater というプラグインがあり、アプリ起動時にJSON(latest.json)を取得して新しいバージョンがあるかチェックし、あればダウンロード・インストール・再起動まで自動で行える。
latest.json の中身はこんな構造:
{
"version": "1.4.5",
"pub_date": "2026-03-28T03:16:52Z",
"platforms": {
"darwin-universal": {
"url": "https://example.com/myapp/myapp.app.tar.gz",
"signature": "dW50cnVzdGVk..."
}
}
}
signature はTauri独自の更新署名で、Apple署名とは別物。アプリ側に埋め込んだ公開鍵で検証し、改ざんされていないことを確認する仕組み。
コード署名をする理由
macOSではApple Developer IDで署名されていないアプリを起動しようとすると、Gatekeeperによってブロックされる。
ユーザーがシステム設定から手動で許可すれば起動できるが、体験として良くない。
さらに、Notarization(Apple公証)を通すことで「Appleが悪意のあるソフトウェアでないことを確認済み」という状態になり、初回起動時の警告もなくなる。
自動更新と組み合わせる場合、更新後のバイナリも署名・公証済みである必要があるため、CI/CDで自動化しておくのが現実的。
前提
- Tauri v2(v2.10.3)
- macOS向けビルド(Universal Binary: Intel + Apple Silicon)
- GitHub Actionsでビルド・署名・公証・リリース
- Cloudflare R2で
latest.jsonとバイナリを配信(privateリポジトリ対応) - Apple Developer Programに登録済み
手順
1. Tauri更新署名キーの生成
アップデーターの改ざん検知に使うキーペアを生成する。Apple署名とは別物。
pnpm tauri signer generate -w ~/.tauri/myapp.key
- 公開鍵 →
tauri.conf.jsonに埋め込む - 秘密鍵(
~/.tauri/myapp.keyの中身)→ GitHub Secretに設定 - パスワード → GitHub Secretに設定
2. Apple Developer ID 証明書の作成
macOSでApp Store外に配布するアプリの署名には「Developer ID Application」証明書が必要。Xcodeのインストールが前提。
CSR(証明書署名要求)の作成
キーチェーンアクセスを開く:
open -a "Keychain Access"
パスワードアプリを開くか、キーチェーンアクセスを開くか聞かれるので、「キーチェーンアクセス」を選択。
メニューから「キーチェーンアクセス」→「証明書アシスタント」→「認証局に証明書を要求」を選択。メールアドレスを入力し「ディスクに保存」でCSRファイルを保存する。
Apple Developerサイトで証明書を作成
- developer.apple.com にログイン
- 「Certificates, Identifiers & Profiles」→「Certificates」
- 「+」ボタンで新規作成
- 「Software」セクション内の 「Developer ID Application」 を選択
- 先ほど作成したCSRファイルをアップロード
- 生成された証明書をダウンロードし、ダブルクリックでキーチェーンに登録
注意: 「Apple Development」証明書は開発・テスト用であり、配布には使えない。必ず「Developer ID Application」を選ぶこと。
登録確認
security find-identity -v -p codesigning
以下のように表示されればOK。「Apple Development〜」と出ているものがあるときは、そっちは使わない。「Developer ID Application〜」とあるものが必要。
1) xxxxxxxx "Developer ID Application: Your Name (TEAMID)"
Developer ID Application が表示されない場合、CSR作成時の秘密鍵がキーチェーンにないか、証明書の登録に失敗している可能性がある。CSR作成からやり直すこと。
3. Notarization用のApp用パスワード作成
appleid.apple.com にログインし、「サインインとセキュリティ」→「アプリ用パスワード」から専用パスワードを生成する。これはNotarization APIへの認証に使う。Apple IDのパスワードとは別物。
4. .p12ファイルのエクスポート(CI用)
ローカルでしかビルドしない場合はこの手順は不要。GitHub ActionsなどCI環境で署名する場合、証明書を .p12 ファイルとしてエクスポートし、Base64エンコードしてSecretsに登録する。
# キーチェーンから証明書+秘密鍵を.p12形式でエクスポート
security export -k ~/Library/Keychains/login.keychain-db \
-t identities -f pkcs12 -o cert.p12 -P "任意のパスワード"
-P で指定するパスワードは .p12 ファイル自体を保護するためのもので、自分で自由に決める。このパスワードはCI環境でのインポート時にも必要になるので、GitHub Secretの APPLE_CERTIFICATE_PASSWORD に設定する。
Base64エンコードしてクリップボードにコピー:
base64 -i cert.p12 | pbcopy
この値をGitHub Secretの APPLE_CERTIFICATE に設定する。
5. プラグインのインストール
Rust側:
# src-tauri/Cargo.toml
tauri-plugin-updater = "2"
tauri-plugin-process = "2"
フロントエンド側:
pnpm add @tauri-apps/plugin-updater @tauri-apps/plugin-process @tauri-apps/plugin-dialog
6. tauri.conf.jsonの設定
ポイントは3つ:
plugins.updaterで配信先エンドポイントと公開鍵を設定bundle.createUpdaterArtifactsをtrueにする(これがないと.sigファイルが生成されない)your-r2-domain.example.com/myappの部分は適宜置き換え
{
"plugins": {
"updater": {
"endpoints": ["https://your-r2-domain.example.com/myapp/latest.json"],
"pubkey": "公開鍵の文字列"
}
},
"bundle": {
"active": true,
"createUpdaterArtifacts": true,
"targets": "all",
"macOS": {
"signingIdentity": null
}
}
}
signingIdentity は null にしておき、CI側で環境変数 APPLE_SIGNING_IDENTITY として渡す。ローカルビルドでは署名をスキップできる。
7. Rustプラグイン登録
// src-tauri/src/lib.rs
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_process::init())
// ... 他のプラグイン
8. Capability追加
// src-tauri/capabilities/default.json
{
"permissions": [
"core:default",
"updater:default",
"process:default"
]
}
9. フロントエンド側の更新チェック
アプリ起動時にチェックし、更新があればダイアログで確認してからインストールする実装にした。
// src/lib/updater.ts
import { check } from "@tauri-apps/plugin-updater";
import { relaunch } from "@tauri-apps/plugin-process";
import { ask } from "@tauri-apps/plugin-dialog";
export async function checkForUpdates(): Promise<void> {
try {
const update = await check();
if (!update) return;
const yes = await ask(
`新しいバージョン v${update.version} が利用可能です。更新しますか?`,
{ title: "アップデート", kind: "info", okLabel: "更新する", cancelLabel: "あとで" }
);
if (yes) {
await update.downloadAndInstall();
await relaunch();
}
} catch (error) {
console.error("Update check failed:", error);
}
}
Appコンポーネントの useEffect で呼び出す:
useEffect(() => {
checkForUpdates();
}, []);
10. GitHub Secretsの設定
GitHub リポジトリの Settings → Secrets and variables → Actions に以下を設定:
| Secret | 説明 |
|---|---|
APPLE_CERTIFICATE | Base64エンコードした .p12 証明書 |
APPLE_CERTIFICATE_PASSWORD | .p12 のパスワード |
APPLE_SIGNING_IDENTITY | Developer ID Application: 名前 (TEAMID) |
APPLE_ID | Apple ID メールアドレス |
APPLE_PASSWORD | App-specific password |
APPLE_TEAM_ID | 10文字の Team ID |
TAURI_SIGNING_PRIVATE_KEY | Tauri署名秘密鍵 |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD | Tauri署名パスワード |
R2_ACCESS_KEY_ID | Cloudflare R2のAccess Key ID |
R2_SECRET_ACCESS_KEY | Cloudflare R2のSecret Access Key |
R2_ENDPOINT | https://<ACCOUNT_ID>.r2.cloudflarestorage.com |
R2_BUCKET | R2バケット名 |
R2_PUBLIC_URL | R2の公開URL |
11. GitHub Actionsワークフロー
v* タグのpushをトリガーにビルド → 署名 → 公証 → GitHub Releaseドラフト作成 → R2アップロードを行う。
name: Release
on:
push:
tags:
- "v*"
jobs:
release:
permissions:
contents: write
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: pnpm/action-setup@v4
- run: brew install nasm
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin,x86_64-apple-darwin
- uses: swatinem/rust-cache@v2
with:
workspaces: src-tauri
- run: pnpm install
- name: Import Apple certificate
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: \
-k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: v__VERSION__
releaseName: "myapp v__VERSION__"
releaseDraft: true
args: --target universal-apple-darwin
- name: Upload to Cloudflare R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
R2_PUBLIC_URL: ${{ secrets.R2_PUBLIC_URL }}
run: |
ARTIFACTS_DIR="src-tauri/target/universal-apple-darwin/release/bundle"
# dmg, tar.gz, sig をR2にアップロード
# latest.json のURLをR2に書き換えてアップロード
- name: Cleanup keychain
if: always()
run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
12. Cloudflare R2の設定
privateリポジトリの場合、GitHub Releasesの latest.json にはアクセスできないため、Cloudflare R2に配置する。
カスタムドメインの場合はデフォルトがパブリックアクセスなので、ドメインを設定すればそのまま公開となる。
- Cloudflareダッシュボードで R2 バケットを作成
- Settings → Public access でR2.devサブドメインまたはカスタムドメインで公開
- R2 API Token(Object Read & Write)を作成し、Access Key IDとSecret Access Keyを取得
R2のS3互換エンドポイントは https://<ACCOUNT_ID>.r2.cloudflarestorage.com で、AWS CLIがそのまま使える。
ハマったポイント
pnpmのバージョン未指定
GitHub Actionsで pnpm/action-setup@v4 を使う際、pnpmバージョンが指定されていないとエラーになる。
Error: No pnpm version is specified.
package.json に packageManager フィールドを追加して解決:
{
"packageManager": "pnpm@10.32.1"
}
nasmが見つからない(アプリ特有の問題)
これは自分が作っているアプリ特有の問題。
AVIF画像変換に使っている rav1e クレートがx86_64ビルド時に nasm(アセンブラ)を必要とする。
GitHub Actionsのmacosランナーにはデフォルトで入っていない。
NASM build failed. Make sure you have nasm installed
ワークフローに brew install nasm を追加して解決。
.sigファイルが生成されない
これが一番時間を使った。ビルドは成功するが .sig ファイルが一切生成されず、latest.json の signature が空になる問題。
TAURI_SIGNING_PRIVATE_KEY は正しく設定されているのに生成されない。原因は tauri.conf.json に createUpdaterArtifacts: true が設定されていなかったこと。
{
"bundle": {
"createUpdaterArtifacts": true
}
}
Tauri v2ではこの設定がないと、ビルド時に TAURI_SIGNING_PRIVATE_KEY を読み取って .sig を生成する処理自体がスキップされる。公式ドキュメントの Updater に記載があるが、見落としやすい。
tauri-action のソースコードを確認したところ、アクション側は .sig ファイルを「探す」だけで「生成する」わけではなく、tauri build 自体が生成する仕組みだった。
latest.jsonのGitHub Release取得
tauri-action は latest.json をGitHub Releaseのアセットとしてアップロードする。ドラフトリリースの場合、gh release download のタイミングによっては no assets match the file pattern エラーになることがある。
対策として、ビルド成果物から直接 latest.json を生成してR2にアップロードする方式に切り替えた。
リリースの流れ
すべてセットアップが完了したら、リリースは以下の手順で行える:
git tag v1.5.0
git push origin v1.5.0
GitHub Actionsが自動で:
- Universal Binary(Intel + Apple Silicon)をビルド
- Developer ID Applicationで署名
- Apple Notarization(公証)を実行
- GitHub Releaseをドラフト作成
- Cloudflare R2にdmg、tar.gz、latest.jsonをアップロード
ドラフトリリースを確認してGitHubで公開すれば完了。既存ユーザーのアプリは次回起動時に自動で更新を検知する。
やり直す場合は既存タグを削除してから同じタグを打ち直せばよい:
git push origin :refs/tags/v1.5.0
git tag -d v1.5.0
git tag v1.5.0
git push origin v1.5.0
おわりに
手順が多く、めちゃ大変だった。一回やってしまえば、あとは慣れの問題な気もする。
個人的には、開発を開始した段階で、このフローを構築してしまうのが好み。
あとは、作成した証明書やパスワードは紛失しないように同じ場所にまとめて配置して管理しておきましょう。