配布
本物のネイティブインストーラーを出荷する — クロスアーキテクチャビルド、署名、公証、CI。
murasaki bundle / murasaki installer は、macOS(.app / .dmg)、Windows(ポータブル .zip、NSIS .exe、MSI .msi)、Linux(AppDir + .AppImage、.deb)向けに、本物のインストーラーを出力します。いずれもクロスアーキテクチャ(arm64 / x64)に対応しています。Linux の .AppImage は macOS/Windows の成果物と同じように起動・自己更新できます。更新の仕組み、.deb におけるシステムのパッケージマネージャー上の制約、コンテナ/CI で AppImage を動かす際の FUSE に関する注意点については、下記の「Linux: AppImage と .deb」セクションを参照してください。
| ターゲット | バンドル | インストーラー | 公開コード署名 |
|---|---|---|---|
| macOS arm64 / x64 | .app + .app.zip | .dmg | Developer ID + 公証に標準対応 |
| Windows x64 / arm64 | ポータブルフォルダー + .zip | NSIS .exe、Windows 上で WiX .msi | SignTool 経由の Authenticode に標準対応 |
| Linux x64 / arm64 | AppDir + .AppImage | .deb | --sign による GPG 分離署名(distro リポジトリ/キーリングの信頼統合はなし) |
ファイルが生成されたからといって、そのまま配布可能とは限りません。クリーンな環境でインストール済みの成果物をテストし、署名とすべての本番用依存関係・リソースを検証してください。Murasaki は 1.0 未満であり、Node の依存関係パッケージングは、動的なネイティブアドオンや実行時に検出されるアセットの配置すべてをまだ網羅していません。
クロスアーキテクチャビルド
バンドルには、ポータブルなターゲット固有の Node ランタイム(nodejs.org から取得し、~/.murasaki/node/ にキャッシュ)と、コンパイル済みのネイティブランチャーバイナリが同梱されます — CLI をたまたま実行している node ではありません。Murasaki は公式 Node のリリースキーのフィンガープリントを固定しており、クリア署名された SHASUMS256.txt.asc の署名を検証してから、選択したアーカイブのチェックサムを信頼します。つまり、Apple Silicon の開発マシンから Intel 向けビルドを生成することも、その逆もできます:
ランタイムのダウンロードは HTTPS 限定で、タイムアウトとサイズの上限を設け、非公開の一時ファイルへストリーミングし、署名とチェックサムの検証後にのみキャッシュします。キャッシュ済みの Node / AppImage ランタイムも使用のたびに再ハッシュし、部分的にしか揃っていない、または改変されたキャッシュエントリはパッケージングせずに破棄します。
murasaki bundle --arch x64 # Apple Silicon Mac 上で x64 の .app をビルド
murasaki bundle --arch arm64 # Intel Mac 上で arm64 の .app をビルド
murasaki bundle --target win32-arm64 # arm64 の Windows バンドルmurasaki installer も、同じ方法で --arch / --target を bundle に転送します。Windows の場合は win32-x64 / win32-arm64 です。
URL スキームとファイル関連付け
パッケージ済みアプリが扱うハンドラーは、murasaki.config.ts で宣言します。
import { defineConfig } from 'murasaki'
export default defineConfig({
appId: 'com.example.notes',
productName: 'Notes',
protocols: [{ scheme: 'example-notes', name: 'Notes link' }],
fileAssociations: [{
extensions: ['enote'],
name: 'Notes document',
role: 'editor',
mimeType: 'application/x-example-note',
}],
})ターゲットごとの登録結果は次のとおりです。
| Artifact | 登録動作 |
|---|---|
macOS .app / .dmg | 署名前にアプリの Info.plist へ CFBundleURLTypes、CFBundleDocumentTypes、エクスポートされたドキュメント UTI を書き込む。DMG にはそのアプリがそのまま入る |
Windows NSIS .exe | プロトコル、ProgID、Open With のエントリ、デフォルトのアプリのケイパビリティを、デフォルトではユーザー単位、installMode: 'perMachine' を指定するとマシン単位で登録 |
Windows WiX .msi | 同じハンドラーをマシン単位で登録 |
Windows ポータブルフォルダー / .zip | レジストリを変更せず、自動登録も行わない |
Linux AppDir / .AppImage / .deb | .desktop ファイルに MimeType= 行(プロトコルごとに x-scheme-handler/<scheme>、ファイル関連付けの拡張子ごとに application/x-<extension>)を書き込む。.deb は usr/share/applications/ にインストールし、インストール/削除時にデスクトップデータベースを更新する。手動展開した AppDir/.AppImage には OS レベルの登録手順はない。コールドスタート時の argv(.desktop の Exec= 行が展開する %U/%F)と、2 回目起動時のアクティベーションは、どちらの場合も動作する — 下記参照 |
コールドスタート、2 回目の起動、macOS の open イベントのいずれでも、一致した URL / ファイルは ready() 完了後に Node Main の openRequested() フックへ渡されます。Windows のポータブルビルドでも、URL / ファイルをコマンドラインで直接渡せば受け取れますが、インストール不要の成果物が OS のデフォルトを自動で取得することはありません。
Windows への登録はアプリを選択可能なハンドラーにしますが、ユーザーが保護されたデフォルトアプリの選択を上書きすることはありません。新規インストールとアップグレードの両方をテストし、アンインストール時に自分のアプリのハンドラーエントリだけが削除されることを確認してください。
署名と公証
Murasaki が担うのは署名のオーケストレーションであり、発行者としての身元を証明するものではありません。Apple Developer ID または Windows の証明書 / Artifact Signing プロファイルを用意してください。--sign は未署名の成果物を黙って公開せず、署名できなければ失敗します。
デフォルトでは murasaki bundle は ad-hoc signed な .app を生成し、murasaki installer はそのアプリを .dmg に格納します。これにより macOS はローカルのバンドル整合性を検証できますが、信頼された開発者としての身元を示すものではなく、成果物を公証することもできません。自分でダウンロードした開発用の成果物に限り、次のコマンドで検疫属性を外せます:
xattr -dr com.apple.quarantine "<path>"これをエンドユーザー向けのインストール手順にしないでください。警告なしで公開配布するには、Developer ID 署名と公証が必要です。
警告なしで配布するには、あなた自身の Apple Developer ID で署名・公証してください — Murasaki は独自の証明書を一切同梱していません:
murasaki bundle --sign # .app を Developer ID で署名する
murasaki installer --sign --notarize # + .dmg を Apple に提出し、チケットをステープルする--signは.appを hardened runtime で署名します(Apple のドキュメント化されたフローどおり: まず内側のコード、その後外側のバンドルの順)。署名 ID は$MURASAKI_SIGN_IDENTITY、次にconfig.sign.identity、次にキーチェーン内で最初に見つかった "Developer ID Application" の ID の順で解決されます。メインアプリと同梱の Node ヘルパーは、別々のエンタイトルメントで署名されます。ホスト/システム権限はアプリ側に残し、JIT・未署名実行可能メモリ・ライブラリバリデーションの無効化は Node だけに付与します。App Sandbox は現在未対応で、sign.appSandbox: trueを指定すると、無効な継承ヘルパー署名を生成する代わりにフェイルクローズで拒否します。hardened runtime 用の権限セットはconfig.sign.entitlementsとconfig.sign.helperEntitlementsで個別に上書きでき、指定したファイルが存在しない、または無効な場合はフェイルクローズで停止します。--notarizeには--signが必須です(公証は Developer ID 署名済みのコードしか受け付けません)。認証情報はAPPLE_ID、APPLE_TEAM_ID、APPLE_APP_PASSWORD(アプリ専用パスワード)から読み取られます — 設定やファイルからは決して読み取りません。.dmgを Apple の notary サービスに提出し、結果を待ってから、Gatekeeper がオフラインで検証できるようチケットをステープルします。
どちらも、有料の Apple Developer Program メンバーシップが必要です。
macOS の成果物を検証する
署名後の最終 .app と、公証後の最終 .dmg に対して実行します。
codesign --verify --strict --verbose=2 "dist/bundle/My App.app"
codesign -dvvv --entitlements :- "dist/bundle/My App.app"
spctl -a -t open --context context:primary-signature -vv "dist/My App-1.0.0.dmg"
xcrun stapler validate "dist/My App-1.0.0.dmg"codesign --verify はアプリのコード/リソースのシールを検査し、spctl は配布用 DMG を Gatekeeper ポリシーに照らして評価し、stapler validate は公証チケットが添付されているかを検査します。これらはそれぞれ別の観点を確認するものなので、公開リリースでは全部実行してください。
Windows のインストーラーと署名
murasaki bundle --target win32-x64 は、ポータブルフォルダーと .zip を生成します。murasaki installer は、それに加えてビルドホストにすでに存在するツールも呼び出します。
- NSIS
.exe:makensis。macOS / Windows で実行可能 .msi: WiX v4。Windows 上で実行
再現性のある CI ツールチェーンでは、MURASAKI_NSIS_PATH / MURASAKI_WIX_PATH に実行ファイルの正確なパスを指定できます。明示したパスは常に優先されます。そのパスが存在しない、または実行できない場合はそのインストーラー形式をスキップし、他のインストーラーも作れなければコマンドは失敗します。
NSIS は perUser(デフォルト、昇格なし)または perMachine、MSI は常にマシン単位です。ブランディングとアップグレード ID については設定を参照してください。
署名済みのリリースビルドは Windows 上で実行してください(未署名の Windows 成果物のクロスビルドは、引き続き他 OS でも可能です):
pnpm exec murasaki bundle --target win32-x64 --sign
pnpm exec murasaki installer --target win32-x64 --signbundle コマンドは、ポータブル ZIP を作る前に <productName>.exe を署名します。installer コマンドはその署名済みペイロードを NSIS / MSI に格納し、生成したセットアップ実行ファイルと MSI 自体にも署名します。いずれも SHA-256 とデフォルトの RFC 3161 タイムスタンプを使用し、signtool verify /pa /v /tw による別途の検証も行います。署名または検証に失敗した場合、リリースは停止します。
未署名のインストーラーはローカルでのパッケージング検証には使えますが、ファイル形式が正常でも、現在の Windows のアプリケーション制御ポリシーによって遮断される場合があります。Murasaki はこの場合に警告を出します。公開する成果物には必ず署名し、利用者に SmartScreen や Smart App Control の無効化を求めないでください。
署名方法は次のいずれか1つを選びます。CI では設定ファイルを編集せずに環境変数で上書きできます。
| Signer | Config | 環境変数による上書き |
|---|---|---|
| PFX/P12 | sign.windows.certificateFile | MURASAKI_WINDOWS_CERTIFICATE_FILE + 任意で MURASAKI_WINDOWS_CERTIFICATE_PASSWORD |
| インポート済み証明書のサブジェクト名 | sign.windows.certificateSubjectName | MURASAKI_WINDOWS_CERTIFICATE_SUBJECT |
| インポート済み証明書の拇印 | sign.windows.certificateSha1 | MURASAKI_WINDOWS_CERTIFICATE_SHA1 |
| Microsoft Artifact Signing | sign.windows.artifactSigning.{dlib,metadata} | MURASAKI_WINDOWS_ARTIFACT_SIGNING_DLIB + MURASAKI_WINDOWS_ARTIFACT_SIGNING_METADATA |
セレクターを何も指定しない場合、SignTool /a が CurrentUser/My から最適なコード署名証明書を選択します。マシンストアを使うには certificateStore: 'localMachine'、SignTool のパスは MURASAKI_SIGNTOOL_PATH、タイムスタンプサービスは MURASAKI_WINDOWS_TIMESTAMP_URL で上書きできます。false を指定するとタイムスタンプを無効化できますが、公開リリースでは非推奨です。
Microsoft Artifact Signing を使う場合、dlib には Azure.CodeSigning.Dlib.dll、metadata には機密情報を含まないアカウント/プロファイルの JSON を指定します。認証は Azure CLI・ワークロード ID・マネージド ID 側に残し、認証情報を JSON や Murasaki の設定に書き込まないでください。このプロバイダーでは、Murasaki はデフォルトで Microsoft の Artifact Signing タイムスタンプ局を使用します。
updater の Ed25519 マニフェスト署名は、Windows SmartScreen における発行者としての身元を確立するものではないため、Authenticode の代わりにはなりません。正しく署名していても、新規の発行者はレピュテーションが育つまで SmartScreen の警告が表示される場合があります。
GitHub Actions での Windows 署名済みリリース
最小構成の PFX の例です。証明書はランナーの一時ディレクトリにのみ書き込みます。証明書ポリシーに合う場合は、インポート済みのストア証明書、またはクラウド/HSM プロバイダーを優先してください。
jobs:
windows:
runs-on: windows-2025
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: pnpm install --frozen-lockfile
- name: Materialize signing certificate
shell: pwsh
env:
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_PFX }}
run: |
[IO.File]::WriteAllBytes(
"$env:RUNNER_TEMP\release.pfx",
[Convert]::FromBase64String($env:CERTIFICATE_BASE64)
)
- name: Build signed installers
env:
MURASAKI_WINDOWS_CERTIFICATE_FILE: ${{ runner.temp }}\release.pfx
MURASAKI_WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: pnpm exec murasaki installer --target win32-x64 --signGitHub Actions での macOS 署名済みリリース
タグのプッシュで .dmg をビルドし、(任意で)署名・公証してから GitHub Release に添付します。あなたのアプリに .github/workflows/release.yml として次のワークフローを追加してください:
name: Release
on:
push:
tags: ['v*']
jobs:
release:
runs-on: macos-14
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: pnpm install
- name: Import signing certificate
if: ${{ secrets.APPLE_CERTIFICATE_P12 != '' }}
env:
CERT_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
CERT_PW: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
KC="$RUNNER_TEMP/app.keychain-db"
security create-keychain -p "" "$KC"
security set-keychain-settings -lut 21600 "$KC"
security unlock-keychain -p "" "$KC"
echo "$CERT_P12" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$KC" -P "$CERT_PW" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KC"
security list-keychains -d user -s "$KC" $(security list-keychains -d user | tr -d '"')
- name: Build installer
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
HAS_CERT: ${{ secrets.APPLE_CERTIFICATE_P12 != '' }}
run: |
if [ "$HAS_CERT" = "true" ]; then
pnpm exec murasaki installer --sign --notarize
else
pnpm exec murasaki installer
fi
- uses: softprops/action-gh-release@v2
with:
files: dist/*.dmg署名・公証するには、次のリポジトリシークレットを追加してください(未署名の .dmg にする場合はすべて省略します): APPLE_CERTIFICATE_P12(あなたの Developer ID の .p12 を base64 エンコードしたもの)、APPLE_CERTIFICATE_PASSWORD、APPLE_ID、APPLE_TEAM_ID、APPLE_APP_PASSWORD。
これらのシークレットを設定やソースにコミットしないでください — --notarize は意図的に、環境変数からしかそれらを読み取りません。
Linux: AppImage と .deb
ネイティブランチャーは、生成した AppDir / .AppImage / .deb を実際に動かします — ウィンドウ、WebView、シングルインスタンスロック、ディープリンク、グレースフルシャットダウン、クラッシュレポートは、いずれも macOS/Windows と同じように動作します。murasaki installer --sign は成果物を GPG で署名します — 下記の「GPG 分離署名」を参照してください。.rpm とリポジトリメタデータはまだ実装されていません — 下記の「まだ実装されていないこと」も参照してください。
murasaki bundle --target linux-x64 # dist/bundle/<Name>.AppDir/ + <Name>-<version>-linux-x64.AppImage
murasaki bundle --target linux-arm64
murasaki installer --target linux-x64 # dist/<debname>_<version>_amd64.deb
murasaki installer --target linux-arm64 # dist/<debname>_<version>_arm64.debどちらも、bundle --target win32-x64 と同様に macOS / Windows / CI からクロスビルドできます — Linux ホストは不要です。bundle が .AppImage を作るには mksquashfs が PATH 上に必要です(AppDir フォルダ自体には追加の要件はありません):
brew install squashfs # macOS
apt install squashfs-tools # Debian/Ubuntu
dnf install squashfs-tools # Fedorainstaller が生成する .deb は、純粋な Node 製の ar/tar ライターによるものです — dpkg-deb も、追加のホストツールも必要ありません。
AppDir は、macOS の .app における Contents/Resources と同じレイアウトを usr/lib/<appId>/resources/ の下に反映します(クライアントビルド、Server Actions、ダウンロードした Node ランタイム、murasaki-meta.json など)。加えて、あらゆる Linux デスクトップ環境が必要とする freedesktop.org 由来の要素 — AppRun、ルートの .desktop ファイルと usr/share/applications/<appId>.desktop、config.icon から生成する usr/share/icons/hicolor/ のアイコンテーマ一式(16〜512px)— も含みます。
AppImage を FUSE なしで動かす
AppImage は通常、自身を FUSE でマウントします。CI ランナーや最小構成のコンテナなど、一部のホストには動作する /dev/fuse がありません。通常の FUSE マウント経路を使うには libfuse2(Ubuntu 24.04 以降では libfuse2t64)をインストールしてください。あるいは --appimage-extract-and-run を渡すと、AppImage ランタイムが自身を一時ディレクトリへ展開し、そこから実行します — FUSE は不要になりますが、起動は遅くなります。Murasaki 自身の CI(.github/workflows/app-package-linux.yml)も、まさにこの理由から常に --appimage-extract-and-run を使っています。
AppImage と .deb: 更新の扱いの違い
.AppImageは Murasaki 自身の自己完結型の更新ペイロードであり、自己更新の対象でもあります —useUpdate()のinstall()は、実行中の.AppImageファイル自体をジャーナル方式でその場に置き換え(macOS の.app.zip/ Windows の NSIS セットアップと同じ、同一ボリュームでのバックアップと起動時ヘルスチェックの保証つき)、--appimage-extract-and-runで再起動します(ホストに FUSE があってもなくても動作します)。更新後の最初の起動に失敗した場合は、自動的に前のバージョンへロールバックします。murasaki release --manifestは<Name>-<version>-linux-{x64,arm64}.AppImageをスキャンします。詳しくは自動更新を参照してください。.debはパッケージマネージャーが所有します。更新ペイロードになることは決してありません — アップグレードは apt/dpkg の仕事であり、OS のパッケージマネージャーが他のインストール済みパッケージのライフサイクルを所有するのと同じことです。murasaki release --manifestが.debを探すことはなく、.debでインストールした(または手動展開したそのままの AppDir の)アプリのuseUpdate().check()は、マニフェストを確認する代わりに「更新はシステムのパッケージマネージャーが管理しています」という構造化された結果を返します — エラーにはなりません。
GPG 分離署名
ここでも、Murasaki が担うのは署名のオーケストレーションであり、代わりに GPG 鍵を生成・保管することはありません。既存の鍵を用意してください。--sign は、署名できない場合に未署名の成果物を黙って出力するのではなく、フェイルクローズで停止します。
pnpm exec murasaki installer --target linux-x64 --sign--sign は、.AppImage、.deb、そして両者のチェックサムをまとめた SHA256SUMS ファイルに対して、分離した ASCII-armored 形式の GPG 署名(<artifact>.sig)を生成します。受け取る側は、ファイル単位の署名とチェックサムマニフェストのどちらでも検証できます。dpkg-sig が PATH 上にあれば、Murasaki は .deb へ Debian ネイティブの署名も追加で埋め込みます(可能な場合のみの付加的な処理です)。これはベストエフォートであり、dpkg-sig が無くても --sign 自体は止まりません。
署名鍵は $MURASAKI_GPG_KEY(ローカルの gpg キーリングが認識しているキー ID、フィンガープリント、またはメールアドレス)、次に murasaki.config.ts の sign.linux.gpgKey の順で解決されます。パスフレーズは $MURASAKI_GPG_PASSPHRASE、またはすでにアンロック済みの gpg-agent からのみ取得します——設定やファイルからは読み取りません。
受け取る側は自分の gpg で検証します。
gpg --verify MyApp-1.0.0-linux-x64.AppImage.sig MyApp-1.0.0-linux-x64.AppImage
gpg --verify SHA256SUMS.sig SHA256SUMSこれは、成果物がその鍵によって署名された内容と一致することを証明するものであり、鍵自体が信頼できることまでは示しません——受け取る側は、鍵サーバーや公式サイト、リリースノートなどアウトオブバンドな経路で公開鍵を入手し、その信頼性については自分自身で判断する必要があります。
まだ実装されていないこと
.rpmやリポジトリメタデータ(apt/dnf のリポジトリインデックス)はありません。- 上記の GPG 署名には、apt/dnf のキーリングや distro リポジトリとの信頼統合はありません——証明するのは成果物の整合性であり、リポジトリの信頼ではありません。
.debや、手動展開したそのままの AppDir には自己更新はありません — 置き換え可能なファイルを持つのは.AppImageだけです(上記参照)。
リリースチェックリスト
- クリーンなチェックアウトと、ロック済みの依存関係グラフからビルドする
- インストーラー化する前に
bundleを起動して動作確認する - 公開するすべてのプラットフォーム/アーキテクチャについて、クリーンな環境でインストール・更新・アンインストールを確認する
- 設定した URL スキーム/ファイル関連付けを、コールドスタート時と、メインのアプリ起動中の両方で確認する
- Node Main のシャットダウン、オフライン起動、ネットワーク障害時の挙動を確認する
- macOS の署名・公証、Windows の Authenticode 署名、Linux の GPG 署名を検証する
- 最終ペイロードが変更不可能になってから、Murasaki の更新マニフェストを生成・署名する
- ペイロード、
latest.json、latest.json.sigを同時に公開する - 署名の認証情報や
MURASAKI_UPDATE_KEYは CI のシークレットに保存し、murasaki.config.tsには書かない
コード署名と更新の署名はそれぞれ独立しています。アプリ内更新を有効にする前に、セキュリティと自動更新を確認してください。