速報: 新しいMASバイナリを使えるようになった。
現時点の情報をざっくりまとめるためのメモ。同じ情報を何度もググるのが嫌になったので。App StoreなのですべてMacが前提。動きが激しいので早晩古くなる可能性あり。更新情報を末尾に追加する。
※お気づきの点などありましたらぜひコメントお願いします。
現状突き当たっている問題
Mac 用の Electron アプリにGo言語で作ったhttpサーバーを仕込んだアプリを作った。
App Storeに掲載するために署名を行い、署名自体は成功するのに、そこにEntitlementsを指定するとアプリが起動しなくなってしまう。しかしEntitlementsを指定してSandboxで動作するようにしないと、App Storeに掲載できない。
その後解決しました(後述)。
メモ
気が遠くならないうちに。
作業の流れはElectron アプリを Mac App Store に登録する手順が参考になる。
Electron
- Node.js(JavaScript のライブラリ) と Chromium ライブラリで構成された、マルチプラットフォームのGUIフレームワーク。
- JSやHTMLでHTML/CSSベースのGUIを構築でき、Node.JSなどのJSライブラリも使えることもあり、人気も高い。
- できることがありすぎていちいち書いてられない。ここで関係あるのは「自動アップデート機能を自前で持っていること」。
- 以前はAtomシェルと呼ばれていたらしい。Atomエディタはこれで作られている。
- ちょいと検索しにくいので、electron.jsで検索するとよい。
- 内部にいくつかセキュリティ障壁のようなものがあるのでそれをくぐり抜けながらコードを書くことになる。
- 内部にいろんなロケールディレクトリがたくさんあるので、いらんものは削除しておこう。いらなさそうなバイナリも。
- Mac版Electronの内部にはざっくり以下のものがある:
- Framework(Electronのコアライブラリなど)
- どこかで見かけたのだけど、ここにあるフレームワークに署名する際にはパスにシンボリックリンクを含めないほうがいいらしい。具体的には
Version/A/云々
みたいなパスにすべきらしい。
- どこかで見かけたのだけど、ここにあるフレームワークに署名する際にはパスにシンボリックリンクを含めないほうがいいらしい。具体的には
- Electron ヘルパー(3種類あり、それぞれがMacアプリなので中に同じような構造がさらにある)
- MacOS(通常はここに実行ファイルを置く: ここにはElectronバイナリが置かれるが、Goバイナリもここに置いている)
- Resources(アイコンやロケール情報やドキュメントを置く)
- _Codesign(署名すると作成される)
- Framework(Electronのコアライブラリなど)
Node.js
- JavaScript(GoogleのV8)のプラットフォーム。
- 他にもライバルがいっぱいいる。
- jQueryよりも裏方仕事が得意そう。
-
npmというCLIツールで各種モジュールを一発インストールできる。
- npmのモジュールはプロジェクトのローカルディレクトリにインストールしてJSから使ったり、
-g
を指定してグローバルにインストールしてCLIとして実行したりできる。 - どっちにインストールするかは利用方法によって異なるのでよく考えておこう。
- ローカルにインストールするとそこにpackage.jsonというリストが作成される。
- グローバルインストールには少々面倒がつきまとう?
-
--save
オプションでインストールしたモジュールはElectronの中でrequire
できるようになる。 -
--save-dev
オプションでインストールしたモジュールはElectronに含めたくない開発用モジュールに保存するときに使う。 - 挙動がおかしいと思ったら
npm cache clean
かつプロジェクトディレクトリのnode_modules/ディレクトリ以下を削除してからnpm install
すると回復する場合がある。
- npmのモジュールはプロジェクトのローカルディレクトリにインストールしてJSから使ったり、
- Node.jsのバージョンアップが激しく、ぼやぼやしているとすぐ古くなるし、バージョン依存するモジュールがあったりするとつらくなるので、最初にnodebrewを使ってNode.js自体のバージョンも管理するとよい。
- そうすればコマンド一発でNode.jsを最新版や最新安定版にアップグレードしたり、Node.jsのあるバージョン用にインストールしたモジュールをコマンド一発で新しいNode.jsに移行することもできる。
MAS
- Mac App Storeの略。この界隈では、App Storeに掲載できるElectronアプリをMASアプリとかMASバイナリなどと呼んでいる。
- MASアプリが通常のMac用Electronアプリ(darwinと呼ばれる)と分かれているのは、darwinにあるElectron自慢の**自動アップデート機能があるとApp Storeに掲載できない(審査に通らない)**という悲しい問題があるため。
- App Store自体にアップデート配布機能があるから確かにいらないんだけど、それに対応するためにdarwinから自動アップデート機能やクラッシュリポート機能などを除去したMASバイナリがわざわざ用意されている。つまりApp Storeに掲載するならMASバイナリを使わなくてはならない。
- しかし困ったことに、v0.35.xより新しいMASバイナリには不具合があって(どうもChromium由来っぽい)、アップ後にAppleのレビューに通らないのだそうだ。従って、MASバージョンは今のところ0.35.xを使わなければならない(現在は回避方法あり: https://github.com/electron/electron/issues/3871#issuecomment-211883141)。
Electron-packager
- Electron用に書いたJSやHTMLなどを一括でパッケージ化してくれるツール。Node.jsのモジュール。
- Mac/Win/Linux用のパッケージをそれぞれ作れるすぐれもの
- 「MASもできます」と書いてあるけど実は未対応。6.0.0から対応するべく怒涛のように作業中みたいで、ドキュメントが一部先行しているので、何とも紛らわしい。
- 同じリポジトリにあるelectron-builderというサブプロジェクトとの違いがまだよくわかってない。どっちも頻繁にアップデートされている。どうもElectronの自動アップデートを活かすビルダーみたいなので、今回は関係なさそう。
- そもそも今回は上記のMASアプリを流用して自分のElectronアプリにするしかないので、packagerそのものの出番は今のところなくなった。
codesign
- 今回の要になるXCodeコマンド。これを使ってアプリに署名する。
- Entitlements(後述)もこれで指定する。
- 署名には証明書が必要で、証明書を入手するには有償でApple のDeveloperとして登録し、ウェブサイトから証明書を取得し、開発用Macのキーチェーンに読み込んでおく必要がある。やり方はあちこちにあるのでググってください。
-
codesign
では、証明書の名前を--sign
オプションの引数に指定すれば、キーチェーンから勝手に読みだして署名してくれる。 -
codesign
には--deep
というオプションがあり、そのバイナリに関連する外部ファイルにも署名して回るらしい。- しかし**
--deep
オプションはなるべく使うべきでない**という意見があり、その割にはApple自身もこのオプションを使ってたりする。よくわからん。
- しかし**
- 同じく
-i
オプションで Bundle ID(後述)を指定するとうまくいくという説も見た。ちょっとやってみたけど変わらなかった。調べが足りないかも。 -
codesign --verbose=4 --deep --strict アプリ名
などとするとバリデーションもできる。
spctl
- このXCodeコマンドは、署名されたバイナリがストアで通るかどうかをチェックしたりいろいろできるらしい。
- とりあえず
spctl --assess --verbose --type execute --raw -v アプリ名
みたいな感じでバリデーションに使っている。- いけそうなら
accept
、あかん感じならreject
と表示される。 -
--raw
オプションを指定すると詳細がXMLで出力される。
- いけそうなら
証明書
Mac App Storeで使う証明書は次の2つ:
- Mac App Distribution: 3rd Party Mac Developer Application: * (*)
- Mac Installer Distribution: 3rd Party Mac Developer Installer: * (*)
Mac App Storeの「外」で使う証明書は次の2つ:
- Developer ID Application: Developer ID Application: * (*)
- Developer ID Installer: Developer ID Installer: * (*)
参考: https://github.com/electron-userland/electron-osx-sign/wiki/1.-Getting-Started
そうだ、もう一つ問題があったんだった。上のとおりなら証明書は3rd Party一択なのだけど、そっちにするとspctl
でrejectされてしまう。Developer IDの証明書にするとacceptと表示される。何でだろう。本番でもrejectされるとは限らないかもしれないけど、イヤンな感じ。
Bundle ID
- アプリを一意に識別するための、いわゆる
com.apple.アプリ名
みたいな形式のID。 - 自分勝手に作ってもStoreに拒否されるだけなので、iTunes ConnectかどこかでApple指定の形式で登録しておく必要がある。
- 普通は自分のドメイン名を逆さにして登録する。
- アプリのInfo.plist(後述)にもこれと正確に同じBundle IDを記述しておく必要がある。両者が一致しないと当然Storeには登録できない。
Info.plist
- Macのアプリ内に置かれる、アプリ内部設定の総元締め。
- XML形式なところに時代を感じる。
- 自分でMASバイナリを加工するのに最初
sed
でネチョネチョ書き換えていたけど、plutil
という専用のコマンドを使う方がずっと楽。 -
plutil -replace
で書き換え。 -
plutil -lint
でバリデーションもできる。
Entitlements
今回立ちふさがっているもの。
- 要するにパーミッションの指定。詳しくはAppleのサイトを参照。
- App Storeに掲載するアプリは、安全のために実行バイナリをSandbox化しておく必要があり、Sandbox化されてないアプリはアップロードしたときに発見されてメッと怒られる。
- Sandbox化するためのパーミッションを、Macのinfoファイル形式(つまりXML)で記述する。
-
com.apple.security.app-sandbox
が最低限必要。 - アプリにどんなことをさせるかによって、必要なEntitlementsを追加する。
- 横着して全部足さないほうがいいとどこかで解説されていた。エントリ数に制限があるんだそうだ。必要なものだけを足すべし。
-
- Electronの場合、このEntitlementファイルをchildとparentの2種類作成する。理由はよくわからない。
- parentの方に追加のEntitlementsを記述する。parentはElectronアプリそのものに適用するらしい。
- childには
com.apple.security.app-sandbox
の他にcom.apple.security.inherit
を記述し、parentのEntitlementsを継承させる。childは、Electronアプリに含まれるすべての実行バイナリに適用するらしい。
- しかし、このEntitlementsを適用すると自分のオレオレアプリが起動しなくなってしまうことに今困っている。
参考: https://github.com/electron-userland/electron-osx-sign/wiki/3.-App-Sandbox-and-Entitlements
electron-osx-sign
- Electron-packagerのサブプロジェクト。
- Electron-packagerが6.0.0になったときにマージされるらしい。
- オプションが充実していて、Entitlementsを始めひととおりのパラメータを指定できる。
- 署名スクリプトをbashとかでスクラッチで書くより、これを使う方が楽だと思う。
- 今はどっちを使ってもEntitlementsを使う限り起動してくれないので一緒なのだけど...
- このツールがありがたいのは、Electron内部にあるバイナリを全部チェックしてもれなく署名してくれている(らしい)こと。さらに
--binaries
で追加バイナリも指定できる。 - とりあえずドキュメントはコード署名周りについてみっちり書かれているので読んでおいて損はない。
リンク
手順
- Electron アプリを Mac App Store に登録する手順
- Publish Electron app to the Mac App Store (MAS)(署名部分は上と同じ内容)
- Electronアプリをプロダクトとして「正しく」リリースするために必要な3つのこと
解説
- electron-osx-signの署名周りの解説がまとまってる。
- Electron日本語ドキュメントより
Issueなど
-App crashes when sandboxed. (codesignでは-i
オプションでメインアプリと内部のヘルパーアプリにBundle IDを指定せよとのこと)
Electron-packager 6.0.0リリースに向けた動き関連
-Release 6.0.0 #266(以下の追加バイナリ関連でちょっと手こずってるらしい)
-Pass binaries option to electron-osx-sign #285
本家など
-
Electron(github/atom)
- リリースバイナリ(MASはここからダウンロードできる)
- 公式ドキュメント
- 日本語チュートリアル(最新ではないと思う)
- Awesome ElectronはElectron関連ツール/製品を一覧できるリスト
- Electron-packager(github/electron-userland)
- Electron-osx-sign(github/electron-userland)(Electron-packager 6.0.0ではこれを導入するらしい)
更新情報
electron-packager のMAS署名対応版
-
Unsupported platform mas; must be one of: darwin, linux, win32 #288を見ると、Electron-packager 6.0.0リリース前でもリポジトリからmas対応版をダウンロードできるとある。マジ?ここを参考にやってみよ。
- 次の方法でひとまずインストールはできた:
- プロジェクトのローカルpackage.jsonに以下のようにelectron-packager行を追加
-
npm install
でmas対応版electron-packagerをプロジェクトローカルにインストール ./node_modules/.bin/electron-packager Electronソース アプリ名 --platform=mas --arch=x64 --version=0.35.6
- 次の方法でひとまずインストールはできた:
"devDependencies": {
(中略)
"electron-packager": "git://github.com/electron-userland/electron-packager.git",
(以下略)
起動方法の変更
- 上の方法で新しいelectron-packageでmasバイナリを署名できるようにはなった。それ自体はとてもうれしい。しかし当然ながら当初の問題は相変わらず。
- これまではGoからElectronをラウンチすることにこだわってしまっていたのだけど、どうもそれがバッドプラクティスだったような気がしている。もっと素直に、ElectronからGoを起動する方法に切り替えてみる。
- GoコードからElectronバイナリを削除し、単なるhttpサーバーにする
- Goコードの置き場所をMacOS/*からResouces/*に変更
- Electronバイナリの名前がGoバイナリと同じになるので同じMacOSディレクトリに置けないため
- ElectronからどうにかしてResources以下に置いたGoバイナリをラウンチする
- Electronが終了したら確実にGoバイナリも死んで欲しい(プロセスを回収して欲しい)
-
go run
用のスクリプトとか開発フォーメーションをもろもろ変更しないといけない
どうやらnode.jsのchild_processを使えば内蔵バイナリをラウンチできそうなのだけど、どうやったらいいだろう。関連しそうなのをメモ。
- https://github.com/atom/electron/issues/1777
- http://krasimirtsonev.com/blog/article/Nodejs-managing-child-processes-starting-stopping-exec-spawn
- もしかするとforeverでデーモン化するのがよいのか http://goo.gl/U0WqmI
- 同じようなことしている? http://www.slideshare.net/hiroyukianai/webelectron
- これも https://discuss.atom.io/t/loadurl-not-working-for-certain-site/25011
###そして
ついに起動方法の切り替え完了。
Electronからの子プロセス管理が不安定だったらどうしようかと思っていたけど、 require('child_process').execFile(myapp);
が予想よりずっときびきび動いてくれている。
切替中に、Electronというかnode.jsのnode_modulesディレクトリの配置が今まででたらめだったことがわかり、一人恥じ入る。道理でNode.js用モジュールを足しても全然Electronで使えなかったわけだ。
そのうち別記事で書く。
###さらにそして
ついにApp Storeへのアップロードに成功!sandboxのエラーも解決。
エウーレカユリイカ。
結局最初のGo->Electronという構成がキモかったのが原因ということか。素直にElectron->Goにすればよかった。
なお、署名したバイナリをcodesign --verbose=4
でvalidationするとちゃんとパスするが、spctl --assess --verbose
でvalidationすると「rejected」となる。にもかかわらずアップロードは正常にできた。もしかして「rejected」というのはアップロード後の話なんだろうか。
そしてさらに
そしてさらに
暫定措置ではあるけれどelectron-packagerでもっと新しいMASバイナリを使える方法が示された。
com.apple.security.temporary-exception.sbpl
(allow mach-lookup (global-name-regex #"^org.chromium.Chromium.rohitfork.[0-9]+$"))
上をparent.plist
に追加してやるのだそうだ。
これを待ってましただよ。
そしてさらに
しばらく遠ざかっていたら、Electron 1.1.1 からは上の暫定措置が不要になったらしい。以下はもうparent.plist
に追加しなくてよい。
<key>com.apple.security.temporary-exception.sbpl</key>
<string>(allow mach-lookup (global-name-regex #"^org.chromium.Chromium.rohitfork.[0-9]+$"))</string>
その代わり、1.1.1からはparent.plist
とアプリのInfo.plist
に以下の TeamIDを追加しなければならなくなった。これを行わないと、署名したアプリを起動するときにフリーズする(署名しなければフリーズしない)。
<key>com.apple.security.application-groups</key>
<string>TEAM_ID.your.bundle.id</string>
なお、TeamID は https://developer.apple.com/ にログインして左バーの Membership をクリックすれば確認できる。
electron-packagerで署名から何からすべて行うのであれば、アプリのInfo.plist
へのTeamIDの追加はelectron-packagerのオプションとして、追加する内容を適当なtemp.plistみたいなファイルに保存したものを指定することになる。署名の後でInfo.plistを書き換えても不正と思われるのがオチなので。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ElectronTeamID</key>
<string>自分のTeamID</string>
</dict>
</plist>
上のようなファイルを作っておいて --extend-info="plist/temp.plist" \
オプションを electron-packagerの実行オプションに追加すればよい。