Tauriの基礎 - 自動アップデート
概要
Tauriの自動アップデート機能 (updater) は、デスクトップアプリケーションにシームレスな更新体験を提供する公式プラグインである。
このプラグインを使用することで、ユーザはアプリケーションを手動で再インストールすることなく、最新バージョンへの更新が可能となる。
updaterプラグインの主な特徴は以下の通りである。
- セキュリティ
- 署名検証により、改ざんされていない正規の更新パッケージのみをインストールする。
- クロスプラットフォーム
- Linux、Windows、MacOSの全プラットフォームで動作する。
- 柔軟なサーバ構成
- 静的ファイルサーバ、GitHub Releases、独自のAPIサーバ等に対応する。
- 進捗表示
- ダウンロード進捗をリアルタイムで取得し、UIにフィードバックできる。
- カスタマイズ可能なフロー
- 更新チェックのタイミング、インストールの実行をアプリケーションの要件に合わせて制御できる。
updaterプラグインの導入
updaterプラグインを使用するには、Rustバックエンドとフロントエンドの両方にパッケージを追加して、設定ファイルを構成する必要がある。
インストール
updaterプラグインをインストールするには、以下に示す3つの手順を実行する。
Rust側のインストール
tauri add コマンドを実行してプラグインを追加する。
# Tauri CLIを使用してupdaterプラグインを追加 pnpm tauri add updater # またはnpmを使用する場合 npm run tauri add updater
手動で追加する場合は、Cargo.tomlファイル ファイルに依存関係を記述する。
# Cargo.toml
[target."cfg(not(any(target_os = "android", target_os = "ios")))".dependencies]
tauri-plugin-updater = "2.0.0"
フロントエンド側のインストール
JavaScript / TypeScriptバインディングをインストールする。
# pnpmを使用する場合 pnpm add @tauri-apps/plugin-updater @tauri-apps/plugin-process # npmを使用する場合 npm install @tauri-apps/plugin-updater @tauri-apps/plugin-process
@tauri-apps/plugin-process は、更新後のアプリケーション再起動に必要である。
Rust側の登録
プラグインをTauriアプリケーションに登録する。
src-tauri/src/lib.rs (または、main.rs) ファイルに以下に示す処理を追加する。
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
// デスクトップ環境でのみupdaterプラグインを有効化
#[cfg(desktop)]
{
app.handle().plugin(tauri_plugin_updater::Builder::new().build())?;
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[cfg(desktop)] 属性により、モバイルプラットフォームではupdaterが無効化される。
tauri.conf.jsonの設定
tauri.conf.json ファイルでupdaterの設定を行う。
{
"bundle": {
"createUpdaterArtifacts": true
},
"plugins": {
"updater": {
"pubkey": "CONTENT_FROM_PUBLICKEY_PEM",
"endpoints": [
"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
]
}
}
}
下表に、設定項目の詳細を示す。
| 設定項目 | 説明 |
|---|---|
createUpdaterArtifacts |
ビルド時に更新アーティファクト (署名付きパッケージ) を生成するかどうか true に設定する。 |
pubkey |
公開鍵の内容 (ファイルパスではなく、鍵の内容を直接記述) 署名検証に使用する。 |
endpoints |
更新チェック用のエンドポイントURL配列 複数指定した場合は順に試行される。 |
windows.installMode |
Windowsでのインストールモード "passive" (ユーザ操作なし) または "basicUi" (基本UI表示) |
署名鍵の生成と管理
updaterプラグインは、更新パッケージの署名検証を行うことでセキュリティを確保する。
鍵ペアの生成
署名鍵ペアを生成するには、Tauri CLIの signer generate コマンドを実行する。
# 鍵ペアを生成 (対話的にパスワード入力を求められる) pnpm tauri signer generate -w ~/.tauri/myapp.key # または環境変数でパスワードを指定 TAURI_SIGNING_PRIVATE_KEY_PASSWORD=my_password pnpm tauri signer generate -w ~/.tauri/myapp.key
このコマンドにより、以下に示す2つのファイルが生成される。
| ファイル | 説明 | 取扱い |
|---|---|---|
| myapp.key | 秘密鍵 | 厳重に管理する。 バージョン管理システムにコミットしない。 |
| myapp.key.pub | 公開鍵 | tauri.conf.json ファイルの pubkey に設定する。
|
環境変数の設定
ビルド時に署名を行うために、環境変数を設定する必要がある。
# 秘密鍵の内容を環境変数に設定 (bash/zshの場合) export TAURI_SIGNING_PRIVATE_KEY=$(cat ~/.tauri/myapp.key) # パスワードも設定 export TAURI_SIGNING_PRIVATE_KEY_PASSWORD=my_password
CI/CD環境では、シークレットとして設定することを推奨する。
# GitHub Actionsの例
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
公開鍵の設定
生成された公開鍵の内容を tauri.conf.json ファイルに設定する。
# 公開鍵の内容を確認 cat ~/.tauri/myapp.key.pub
出力された内容を pubkey フィールドに貼り付ける。
{
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkK..."
}
}
}
セキュリティ考慮事項
署名鍵の管理に関する重要な注意事項を以下に示す。
- 秘密鍵は絶対に公開しない。
- 秘密鍵が漏洩すると、攻撃者が偽の更新パッケージを作成できる。
- バージョン管理システム (.git ディレクトリ等) に含めない。
- パスワードは強力なものを使用する。
- 秘密鍵のパスワードは推測困難な文字列を設定する。
- CI/CDではシークレット機能を使用する。
- GitHub Secrets、GitLab CI Variables等の暗号化されたシークレット機能を使用する。
- 本番環境ではHTTPSを使用する。
- 更新エンドポイントは必ずHTTPSで配信する。
- HTTPの場合は中間者攻撃のリスクがある。
アップデートサーバの設定
updaterは、更新情報を提供するサーバ (エンドポイント) と通信して更新を確認する。
静的JSONサーバ
最も簡単な構成は、静的JSONファイルをWebサーバで配信する方法である。
JSON形式
更新情報のJSON形式を以下に示す。
{
"version": "1.2.0",
"notes": "バグ修正とパフォーマンス改善",
"pub_date": "2025-01-15T10:30:00Z",
"platforms": {
"linux-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"url": "https://releases.myapp.com/v1.2.0/myapp_1.2.0_amd64.AppImage"
},
"windows-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"url": "https://releases.myapp.com/v1.2.0/myapp_1.2.0_x64-setup.exe"
},
"darwin-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"url": "https://releases.myapp.com/v1.2.0/myapp_1.2.0_x64.dmg"
},
"darwin-aarch64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"url": "https://releases.myapp.com/v1.2.0/myapp_1.2.0_aarch64.dmg"
}
}
}
各フィールドの説明を以下に示す。
| フィールド | 説明 | 必須 |
|---|---|---|
version |
最新バージョン番号 | Yes |
notes |
リリースノート (Markdown形式) | No |
pub_date |
公開日時 (ISO 8601形式) | No |
platforms |
プラットフォーム別の更新情報 | Yes |
platforms.<プラットフォーム>.signature |
パッケージの署名 | Yes |
platforms.<プラットフォーム>.url |
パッケージのダウンロードURL | Yes |
プラットフォーム識別子
platforms オブジェクトのキーには、以下に示す形式を使用する。
{target}-{arch}
| target | arch | 説明 |
|---|---|---|
linux |
x86_64, aarch64 |
Linux |
windows |
x86_64, i686 |
Windows |
darwin |
x86_64, aarch64 |
MacOS |
動的エンドポイント
tauri.conf.json ファイルのエンドポイントURLには、動的変数を使用できる。
| 変数 | 説明 | 例 |
|---|---|---|
{{current_version}} |
現在のアプリバージョン | 1.0.0 |
{{target}} |
OS名 | linux, windows, darwin |
{{arch}} |
アーキテクチャ | x86_64, aarch64, armv7 |
これらの変数を使用することにより、サーバ側でプラットフォームを判定できる。
{
"endpoints": [
"https://api.myapp.com/update?version={{current_version}}&platform={{target}}&arch={{arch}}"
]
}
動的サーバのレスポンス形式
動的サーバの場合、更新が存在する場合は 200 OK でJSONを返し、更新がない場合は 204 No Content を返す。
更新がある場合のレスポンス例を以下に示す。
{
"version": "1.2.0",
"pub_date": "2025-01-15T10:30:00Z",
"url": "https://releases.myapp.com/v1.2.0/myapp_1.2.0_x64-setup.exe",
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"notes": "## 新機能\n- 機能Aを追加\n- 機能Bを改善"
}
GitHub Releasesの活用
GitHub Releasesを使用して更新パッケージを配信する場合の構成例を示す。
{
"plugins": {
"updater": {
"pubkey": "YOUR_PUBLIC_KEY",
"endpoints": [
"https://github.com/user/myapp/releases/latest/download/latest.json"
]
}
}
}
GitHub Actionsでビルド時にJSONを生成する例を以下に示す。
# .github/workflows/release.yml
- name: Generate update JSON
run: |
cat > latest.json << EOF
{
"version": "${{ env.VERSION }}",
"platforms": {
"linux-x86_64": {
"signature": "$(cat myapp_${{ env.VERSION }}_amd64.AppImage.sig)",
"url": "https://github.com/user/myapp/releases/download/v${{ env.VERSION }}/myapp_${{ env.VERSION }}_amd64.AppImage"
}
}
}
EOF
React / TypeScriptでの実装
フロントエンドで更新チェックとインストールを行う定義を示す。
基本的な更新チェック
最も簡単な更新チェックの例を以下に示す。
// src/hooks/useUpdateCheck.ts
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
import { useState, useCallback } from 'react';
// 更新情報の型定義
interface UpdateInfo {
version: string;
currentVersion: string;
date?: string;
body?: string;
}
export function useUpdateCheck() {
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
const [isChecking, setIsChecking] = useState(false);
const [error, setError] = useState<string | null>(null);
// 更新チェックを実行
const checkForUpdate = useCallback(async () => {
setIsChecking(true);
setError(null);
try {
const update = await check();
if (update) {
// 更新が存在する場合
setUpdateInfo({
version: update.version,
currentVersion: update.currentVersion,
date: update.date,
body: update.body,
});
}
else {
// 更新がない場合
setUpdateInfo(null);
}
}
catch (err) {
// エラーハンドリング
const message = err instanceof Error ? err.message : String(err);
setError(message);
}
finally {
setIsChecking(false);
}
}, []);
return { updateInfo, isChecking, error, checkForUpdate };
}
進捗表示付きダウンロードとインストール
ダウンロード進捗を表示しながら更新をインストールする例を以下に示す。
// src/hooks/useUpdateInstall.ts
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
import { useState, useCallback } from 'react';
// ダウンロード進捗の型
interface DownloadProgress {
downloaded: number;
total: number | null;
percentage: number;
}
export function useUpdateInstall() {
const [isInstalling, setIsInstalling] = useState(false);
const [progress, setProgress] = useState<DownloadProgress | null>(null);
const [error, setError] = useState<string | null>(null);
// 更新をダウンロードしてインストール
const downloadAndInstall = useCallback(async () => {
setIsInstalling(true);
setError(null);
setProgress({ downloaded: 0, total: null, percentage: 0 });
try {
const update = await check();
if (!update) {
throw new Error('利用可能な更新がありません');
}
// ダウンロードとインストールを実行 (進捗コールバック付き)
await update.downloadAndInstall((event) => {
switch (event.event) {
case 'Started':
// ダウンロード開始
setProgress({
downloaded: 0,
total: event.data.contentLength,
percentage: 0,
});
break;
case 'Progress':
// ダウンロード進捗更新
setProgress((prev) => {
if (!prev) return null;
const downloaded = prev.downloaded + event.data.chunkLength;
const percentage = prev.total
? Math.round((downloaded / prev.total) * 100)
: 0;
return { ...prev, downloaded, percentage };
});
break;
case 'Finished':
// ダウンロード完了
setProgress((prev) => prev ? { ...prev, percentage: 100 } : null);
break;
}
});
// アプリケーションを再起動
await relaunch();
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
setError(message);
}
finally {
setIsInstalling(false);
}
}, []);
return { isInstalling, progress, error, downloadAndInstall };
}
更新通知UIコンポーネント
更新通知を表示するReactコンポーネントの例を以下に示す。
// src/components/UpdateNotification.tsx
import { useState, useEffect } from 'react';
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
interface UpdateNotificationProps {
autoCheck?: boolean; // 自動チェックを有効にするか
checkInterval?: number; // チェック間隔 (ミリ秒)
}
export function UpdateNotification({
autoCheck = true,
checkInterval = 3600000, // デフォルト: 1時間
}: UpdateNotificationProps) {
const [update, setUpdate] = useState<Awaited<ReturnType<typeof check>>>();
const [downloading, setDownloading] = useState(false);
const [progress, setProgress] = useState(0);
const [dismissed, setDismissed] = useState(false);
// 更新チェック関数
const checkUpdate = async () => {
const result = await check();
setUpdate(result);
};
// 初回チェックと定期チェック
useEffect(() => {
if (!autoCheck) return;
// 初回チェック
checkUpdate();
// 定期チェック
const interval = setInterval(checkUpdate, checkInterval);
return () => clearInterval(interval);
}, [autoCheck, checkInterval]);
// 更新をインストール
const handleInstall = async () => {
if (!update) return;
setDownloading(true);
try {
await update.downloadAndInstall((event) => {
if (event.event === 'Progress') {
// 進捗を計算 (簡易版)
setProgress((prev) => Math.min(prev + 10, 90));
}
});
setProgress(100);
await relaunch();
}
catch (error) {
console.error('更新のインストールに失敗しました:', error);
setDownloading(false);
}
};
// 更新がない、または、却下された場合は表示しない
if (!update || dismissed) return null;
return (
<div className="update-notification">
<div className="update-content">
<h3>新しいバージョンが利用可能です</h3>
<p>
バージョン {update.version} がリリースされました
(現在: {update.currentVersion})
</p>
{update.body && (
<div className="release-notes">
<pre>{update.body}</pre>
</div>
)}
</div>
<div className="update-actions">
{downloading ? (
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
<span>{progress}%</span>
</div>
) : (
<>
<button
className="btn-primary"
onClick={handleInstall}
>
今すぐ更新
</button>
<button
className="btn-secondary"
onClick={() => setDismissed(true)}
>
後で
</button>
</>
)}
</div>
</div>
);
}
アップデートフローのカスタマイズ
updaterプラグインは、アプリケーションの要件に合わせて更新フローをカスタマイズできる。
自動更新チェック
アプリケーション起動時に自動で更新をチェックする実装例を以下に示す。
// src/App.tsx
import { useEffect, useRef } from 'react';
import { check } from '@tauri-apps/plugin-updater';
export function App() {
const hasChecked = useRef(false);
useEffect(() => {
// 既にチェック済みの場合はスキップ
if (hasChecked.current) return;
hasChecked.current = true;
// 起動時に更新チェック (バックグラウンド)
check().then((update) => {
if (update) {
console.log(`更新があります: v${update.version}`);
// 通知状態を更新する等の処理
}
}).catch((error) => {
// エラーは静かに処理 (ユーザ体験を損なわない)
console.warn('更新チェックに失敗しました:', error);
});
}, []);
return (
<div>
{/* アプリケーションコンテンツ */}
</div>
);
}
強制更新と任意更新
セキュリティ修正等重要な更新の場合は、ユーザに更新を強制できる。
// src/utils/updateManager.ts
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
// 更新の重要度レベル
type UpdateSeverity = 'optional' | 'recommended' | 'critical';
interface UpdateResult {
available: boolean;
severity: UpdateSeverity;
version?: string;
install: () => Promise<void>;
}
// バージョン比較関数
function parseVersion(version: string): number[] {
return version.split('.').map(Number);
}
function compareVersions(v1: string, v2: string): number {
const parts1 = parseVersion(v1);
const parts2 = parseVersion(v2);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 > p2) return 1;
if (p1 < p2) return -1;
}
return 0;
}
// 重要度を判定 (サーバ側で設定するか、バージョン差分で判定)
function determineSeverity(
currentVersion: string,
newVersion: string
): UpdateSeverity {
const current = parseVersion(currentVersion);
const latest = parseVersion(newVersion);
// メジャーバージョンが異なる場合は重要
if (latest[0] > current[0]) {
return 'critical';
}
// マイナーバージョンが異なる場合は推奨
if (latest[1] > current[1]) {
return 'recommended';
}
// パッチレベルは任意
return 'optional';
}
// 更新チェック関数
export async function checkUpdateSeverity(): Promise<UpdateResult> {
const update = await check();
if (!update) {
return {
available: false,
severity: 'optional',
install: async () => {},
};
}
const severity = determineSeverity(
update.currentVersion,
update.version
);
return {
available: true,
severity,
version: update.version,
install: async () => {
await update.downloadAndInstall();
await relaunch();
},
};
}
Rust側での更新処理
Rustバックエンドで更新処理を行うことも可能である。
// src-tauri/src/lib.rs
use tauri_plugin_updater::UpdaterExt;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
#[cfg(desktop)]
{
// updaterプラグインを登録
app.handle().plugin(tauri_plugin_updater::Builder::new().build())?;
// バックグラウンドで更新チェック
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
if let Err(e) = check_and_update(handle).await {
eprintln!("更新チェックエラー: {}", e);
}
});
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[cfg(desktop)]
async fn check_and_update(app: tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
// 更新をチェック
if let Some(update) = app.updater()?.check().await? {
println!("更新が見つかりました: {}", update.version);
let mut downloaded = 0;
let content_length = update.content_length.unwrap_or(0);
// ダウンロードとインストール
update
.download_and_install(
|chunk_length, _| {
downloaded += chunk_length;
println!("ダウンロード中: {} / {} bytes", downloaded, content_length);
},
|| {
println!("ダウンロード完了");
},
)
.await?;
println!("更新完了。再起動します。");
app.restart();
}
Ok(())
}
トラブルシューティング
updaterプラグイン使用時によく発生する問題と解決方法を以下に示す。
署名検証エラー
Signature verification failed エラーが発生する場合の確認事項を以下に示す。
- 公開鍵が正しく設定されているか確認する。
- tauri.conf.json ファイルの
pubkeyフィールドが、ビルド時に使用した秘密鍵と対になっているか確認する。
- tauri.conf.json ファイルの
- 環境変数が正しく設定されているか確認する。
TAURI_SIGNING_PRIVATE_KEYに秘密鍵の内容が設定されているか確認する。
- JSONの署名が正しいか確認する。
- サーバのJSONに含まれる
signatureが、ビルド時に生成された署名と一致するか確認する。
- サーバのJSONに含まれる
エンドポイント接続エラー
Failed to fetch update エラーが発生する場合の確認事項を以下に示す。
- URLが正しいか確認する。
- エンドポイントURLに誤字がないか、変数が正しく展開されているか確認する。
- CORS設定を確認する。
- サーバがCORSヘッダーを返しているか確認する。
- HTTPSを使用しているか確認する。
- 本番環境ではHTTPSが必須である。
- 開発環境でHTTPを使用する場合は、
dangerousInsecureTransportProtocolを設定する。
{
"plugins": {
"updater": {
"dangerousInsecureTransportProtocol": true
}
}
}
更新が検出されない
check() が null を返す場合の確認事項を以下に示す。
- バージョン番号を確認する。
- サーバのJSONの
versionが、現在のアプリバージョンより新しいか確認する。
- サーバのJSONの
- プラットフォーム情報を確認する。
- JSONに現在のプラットフォーム (
linux-x86_64等) のエントリが存在するか確認する。
- JSONに現在のプラットフォーム (
- サーバが 204 No Content を返していないか確認する。
- 動的サーバの場合、更新がない時は 204 を返すのが正しい動作である。
デバッグ方法
updaterのデバッグには、以下に示す方法を活用する。
ログ出力の有効化
Rust側でログ出力を有効にする。
// Cargo.tomlに追加
[dependencies]
log = "0.4"
env_logger = "0.10"
// main.rs/lib.rsの先頭に追加
env_logger::init();
環境変数でログレベルを設定する。
# Linux / MacOS RUST_LOG=debug pnpm tauri dev # Windows (PowerShell) $env:RUST_LOG="debug"; pnpm tauri dev
ネットワークリクエストの確認
Webブラウザの開発者ツール や curlコマンドでエンドポイントにアクセスして、レスポンスを確認する。
# エンドポイントのレスポンスを確認 curl -v https://releases.myapp.com/latest.json
手動での更新チェック
webブラウザのコンソールまたは開発ツールで手動実行する。
// ブラウザコンソールで実行
import('@tauri-apps/plugin-updater').then(({ check }) => {
check().then((update) => console.log(update));
});
参考リンク