個人開発のデスクトップアプリもセキュリティ警告なしで配布する方法
ローカルで問題なく動作するデスクトップアプリが完成したとしても、開発の半分が終わったに過ぎません。本当の難関は、ユーザーがダウンロードリンクをクリックした際、画面に「破損したファイル」や「PCが保護されました」といった赤い警告が表示される瞬間から始まります。
OSのセキュリティ障壁を越え、ビルド速度を制御し、ユーザーがアプリを開くたびに最新版へ自動更新される仕組みを構築するのは、想像以上に骨の折れる作業です。Electronよりも軽量であるという理由でTauri v2を選んだとしても、配布過程における現実的な課題はそのまま残ります。個人開発者や小規模チームが、不要なコストや時間を浪費することなく、製品をスマートに配布する方法をまとめました。
1. 年間700ドルの証明書の代わりにクラウド署名を活用する
コード署名(Code Signing)のないデスクトップアプリは、OSレベルでマルウェアとして扱われます。ユーザーのPCにセキュリティ警告を表示させないためには、費用と書類作業が必要です。
macOS: Apple Developer AccountとEntitlementsの設定
macOSで配布するには、年間99ドルのApple Developer Programへの加入が必須です。アカウントを取得したら、Tauriのウェブビューが正しく動作するように、メモリセキュリティの例外権限を定義した src-tauri/Entitlements.plist を作成する必要があります。この設定が漏れると、アプリは起動直後にクラッシュします。
`xml
com.apple.security.cs.allow-jit
com.apple.security.cs.allow-unsigned-executable-memory
`
このファイルを src-tauri/tauri.conf.json のbundleオプションに指定します。
`json
{
"bundle": {
"macOS": {
"signingIdentity": "Developer ID Application: Your Name (TEAMID)",
"entitlements": "./Entitlements.plist",
"minimumSystemVersion": "11.0",
"dmg": {
"appPosition": { "x": 180, "y": 170 },
"applicationFolderPosition": { "x": 480, "y": 170 }
}
}
}
}
`
Windows: Azure Trusted Signingでコストを抑える
WindowsのSmartScreenフィルタを通過するためには、かつては年間400~700ドルにも達するEV(Extended Validation)証明書を物理USBトークンの形式で発行してもらう必要がありました。費用もさることながら、個人で管理するにはあまりにも煩雑です。
代替案は、Microsoftのクラウドベース署名サービスであるAzure Trusted Signing (ATS)です。月額9.99ドル程度のサブスクリプション料金を支払うだけで、Microsoftが管理するHSMクラウド内で署名を処理するため、物理キーを保管する必要はありません。
- AzureポータルでAzure Trusted Signingアカウントと証明書プロファイルを作成します。
- GitHub Actionsの環境変数(Secrets)にAzureのサブスクリプション情報(
AZURE_TENANT_ID, CLIENT_ID, CLIENT_SECRET)とATS情報を入力します。
- ビルド過程で
sign-tool を実行し、TauriがコンパイルしたMSIやEXEファイルにデジタル署名を適用します。
このように署名されたアプリは、初回ダウンロード時点からWindows SmartScreenの警告を回避できるため、インストール段階での離脱を防ぐことができます。
2. GitHub Actionsのビルド速度を90%以上高速化する方法
Tauriは軽量ですが、ビルド過程ではRustコンパイラと各OSのネイティブツールチェーンを丸ごと動かす必要があります。自分のPCではうまく動いても、他のチームメンバーのPCではリンカーエラーが発生したり、ローカル環境の依存関係の汚染により配布版が壊れたりすることは珍しくありません。配布用のビルドは、必ず分離されたCI/CDパイプラインで実行しなければなりません。
問題は、GitHub ActionsのデフォルトのホストランナーがRustをビルドするにはスペック不足である点です。毎回依存関係をダウンロードしてビルドする構成であれば、リリースビルド一つに10分以上かかることもよくあります。
この際、単純にファイルを圧縮してクラウドとやり取りする actions/cache の代わりに、NVMe高性能ストレージをターゲットとして動作する専用キャッシュプラグイン(swatinem/rust-cache)や、専用ホストランナー(Namespace、Depotなど)を組み合わせることで速度が劇的に向上します。
音楽プレイヤーのオープンソースプロジェクトである spotify-player のビルドログを基準に、通常のGitHubランナーとローカルボリュームキャッシュを適用した専用ランナーの性能比較結果は以下の通りです。
| プラットフォームとキャッシュ構成 |
通常のGitHubランナー所要時間 |
キャッシュ最適化適用時の所要時間 |
ビルド時間短縮率 |
| Ubuntu Linux |
9分31秒 |
34秒 |
94.0%短縮 |
| macOS Darwin |
9分31秒 |
27秒 |
95.2%短縮 |
| Windows MSVC |
9分31秒 |
44秒 |
92.2%短縮 |
| ワークフローコスト |
1回実行あたり$0.44 |
1回実行あたり$0.074 |
83.1%削減 |
継続的に保持されるボリュームキャッシュインフラを連携させるだけで、開発チームのビルド待ち時間が少なくとも40%以上短縮されます。
配布自動化の設定は、.github/workflows/publish.yml に以下のように指定します。
`yaml
jobs:
build-binaries:
strategy:
matrix:
platform: [macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
# ... ビルド段階を実行後、tauri-actionを呼び出す
`
tauri-apps/tauri-action をワークフローの最後に配置しておけば、新しいタグをプッシュするたびに、両OS用の署名済みインストーラーがGitHub Release Draftに自動的に登録されます。
3. ウェブビューセッションの初期化に備えるSQLiteデータ分離
Windows用のインストーラーをパッケージングする際、ウェブビューエンジンであるWebView2のインストール方式を決定する必要があります。インターネット接続が保証されており、ダウンロードファイルサイズを極限まで小さくする必要がある環境であれば、バンドルサイズが増加しない downloadBootstrapper 方式が無難です。逆に、閉域網やオフライン環境をターゲットにするのであれば、インストールファイルに約127MBが加算されても offlineInstaller を同梱する方が安全です。
Tauriベースのアプリを運用する際、データを扱う方法も重要です。ブラウザのストレージであるIndexedDBやLocalStorageにデータをただ入れておくのは危険です。
実際にTauri v1からv2へ移行する際、Windows環境のウェブビューのドメインスキマが [https://tauri.localhost](https://tauri.localhost) から [http://tauri.localhost](http://tauri.localhost) に変わるという内部的な変更がありました。このため、ブラウザのキャッシュパスが強制的に切り替わり、既存のデータをすべて失うケースが多く発生しました。
配布後にデータが初期化される大惨事を防ぐには、重要な情報はウェブビューのストレージではなく、ネイティブのファイルシステム領域にSQLiteファイルとして直接保管すべきです。Tauri v2の appDataDir APIを使えば、OS規格に適合した安全なサンドボックスパスを自動的に検索してくれます。
- Windows:
C:\Users\<UserName>\AppData\Roaming\<BundleIdentifier>
- macOS:
/Users/<UserName>/Library/Application Support/<BundleIdentifier>
Tauri v2のバックエンドであるRustコード(src-tauri/src/lib.rs)内でアプリのライフサイクルに介入し、SQLiteデータベースを安全領域にバインディングしてスキーママイグレーションを実行する例は以下の通りです。
`rust
use std::fs;
use tauri::Manager;
use tauri_plugin_sql::{Migration, MigrationKind};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let database_migrations = vec![
Migration {
version: 1,
description: "initialize_user_profiles_table",
sql: "CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);",
kind: MigrationKind::Up,
}
];
tauri::Builder::default()
.setup(|app| {
let local_app_dir = app.path().app_data_dir()
.expect("Critical: Could not resolve target operating system app data path.");
if !local_app_dir.exists() {
fs::create_dir_all(&local_app_dir)
.expect("Critical: Failed to establish persistent storage directory structure.");
}
Ok(())
})
.plugin(
tauri_plugin_sql::Builder::default()
.add_migrations("sqlite:users.db", database_migrations)
.build()
)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
`
このように構成しておけば、自動更新や再インストールによってElectronやChromiumウェブビューの内部キャッシュがすべて消えても、実際のユーザーデータベースは安全に保護されます。
4. バックグラウンド自動更新の構築
ユーザーにその都度ホームページに来て新バージョンを再ダウンロードするように誘導する方法は、離脱率を高めます。クラウドオブジェクトストレージとCDNを組み合わせて、静かに更新ファイルを配信する構造を作る必要があります。
配布サーバーとしては、Cloudflare R2とAWS CloudFrontの組み合わせが効率的です。Cloudflare R2はデータ転送量(Egress Fees)がかからないため、大規模な更新ファイルをリリースする際に発生するネットワークトラフィック費用をゼロに抑えることができます。
CDNキャッシング制御ポリシー
クライアントが新しいバージョンが出ているか確認する際に照会するメタデータファイル(latest.json)は、CDNやブラウザにキャッシュされてはいけません。レスポンスヘッダーに以下のポリシーを明示する必要があります。
`http
Cache-Control: no-cache, no-store, must-revalidate
`
一方で、実際のインストールバイナリファイルは固有のハッシュ値が含まれた不変(Immutable)な状態であるため、CDNでできるだけ長く保持するように設定し、オリジンサーバーのトラフィック負担を軽減します。
`http
Cache-Control: public, max-age=31536000, immutable
`
Tauri v2アップデータ設定の変更点
Tauri v2では、更新関連オプションの位置が plugins.updater ブロック配下に移動しました。以下は tauri.conf.json の設定仕様です。
`json
{
"bundle": {
"createUpdaterArtifacts": true
},
"plugins": {
"updater": {
"active": true,
"endpoints": [
"https://cdn.myapp.com/releases/latest.json"
],
"dialog": false,
"pubkey": "dW5zaWduZWQgYm91bmRmaXg...",
"windows": {
"installMode": "passive"
}
}
}
}
`
Windows環境でユーザーが面倒な確認ウィンドウを押さずに更新できるようにするには、installMode を passive または quiet に設定する必要があります。passive モードは、インストールウィザードウィンドウの代わりに控えめなゲージバーを表示するだけで、静かに更新を完了します。
設定が終わったら、フロントエンド領域で @tauri-apps/plugin-updater と @tauri-apps/plugin-process を連携させ、アプリ起動時に新しいパッチをチェックして再起動を誘導するロジックを追加します。
`typescript
import { check } from "@tauri-apps/plugin-updater";
import { ask } from "@tauri-apps/plugin-dialog";
import { relaunch } from "@tauri-apps/plugin-process";
export async function runBackgroundUpdater(): Promise {
try {
const updatePayload = await check();
if (updatePayload && updatePayload.available) {
const userResponse = await ask(
`新しいバージョン [v${updatePayload.version}] をダウンロードできます。今すぐ更新してアプリを再起動しますか?`,
{
title: "ソフトウェア自動更新の案内",
kind: "info",
okLabel: "更新して再起動",
cancelLabel: "後で適用"
}
);
if (userResponse) {
await updatePayload.downloadAndInstall();
await relaunch();
}
}
} catch (error) {
console.error("自動更新確認プロセスで例外が発生:", error);
}
}
`
この関数を最上位のReactコンポーネントやVueの初期マウント段階に埋め込んでおくだけで、ユーザーはわざわざホームページを探し回ることなく、常に最新状態のソフトウェアを使用できるようになります。
5. 要約
- コスト削減: 毎年数百ドルかかるEV証明書の代わりに月額10ドル程度のAzure Trusted Signingを適用し、ダウンロード転送手数料がかからないCloudflare R2を組み合わせる方が、個人開発チームには現実的です。
- データ保護: セッションの期限切れやドメイン仕様変更時に消失しやすいブラウザのローカルストレージに頼ることなく、ネイティブが制御する
appDataDir パス配下にSQLiteデータベースファイルを置き、長期的なスキーママイグレーションを実行することで、アプリ更新中にデータが破損するのを防げます。
- 配布速度: 自分のPCでビルドして手動でアップロードするのではなく、ランナーのキャッシング技術が適用されたビルドマトリックスを構成し、リリースバージョンを自動化することで、パッケージ生成段階でのストレスをなくすべきです。