Mac OSX Sierra上でアプリケーションをAgentとしてインストールする方法

## はじめに
　自作のアプリケーションをインストールおよびAgentとして起動させるinstallerの作成方法をまとめます。今回はアプリケーションの配布をApple Storeを経由しない方法を想定しています。したがってユーザはinstallerを使用してアプリケーションをインストールします。installerによってアプリケーションを定めたディレクトリに配置し、その後自動的にAgentとしてプログラムが実行されるようスクリプトを走らせます。作成したプログラムをAgentとして実行するため、今回は~/Library/LaunchAgentディレクトリにplist形式の設定ファイルを配置します。plistの詳細は割愛しますが、簡単に説明をするとAgentとして実行するアプリケーションへのパスを指示したり、起動する頻度やログの出力先を定めたりする設定ファイルとなります。

## 目次
-1. 動作環境
-2. アプリケーションの作成からinstallerの作成
-2.1 Developer ID証明書による署名
-2.2 Archiveの作成(xcode)
-2.3 Packageの作成(pkgbuild)
-2.4 Installerの作成(productbuild)
-2.5 Developer ID Installer証明書による署名(productsign)
-3. Agentとして実行するために行うこと
-3.1 インストール後に実行するScriptの作成
-3.2 Agentの起動確認
-4. 参考

## 1. 動作環境
- Mac OSX Version 10.13.2
- Xcode Version 9.2 (9C40b)
- 開発言語 : Objective-C

## 2. アプリケーションの作成からinstallerの作成
 xcodeでのプログラム作成が完了したら、続いてinstallerの作成を行います。何段階か手順を踏む必要がありますので順を追って説明を行います。
　まず、配布するアプリケーションにはDeveloper ID証明書を使って署名する必要があります。ここではxcodeのプロジェクト設定から組み込みを行います。[2.1]
　osxのappアプリケーションはディレクトリ構造をしています。まずはそのディレクトリを作成します。xcodeからArchiveの作成を行いアプリケーションを作成します。[2.2]
　続いて、インストールするアプリケーションを一つのPackageにします。今回アプリケーションは一つのみですが、複数のアプリケーションを同時にインストールする場合もあると思います。またアプリケーションをAgentとして実行するためにインストール後にScriptを走らせます。そのスクリプトはPackageに同梱します。[2.3]
　次に、Installerの作成を行います。インストールはウィザードに従って進めて行きます。このとき例えばライセンス条項を表示したり、インストール先の選択画面が表示することができますが、それらの情報をここで設定します。[2.4]
　最後に、作成したInstallerにDeveloper ID Installer証明書を使って署名をします。[2.5]

### 2.1 Developer ID証明書による署名
 プロジェクトナビゲーターを開き、Signing Certificateに「Developer ID Application」証明書を選択します。（※証明書は事前にXcodeのPreferenceから取得をしておく必要があります。）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/91391/351b847c-3a58-e4fe-4b88-e5a7024c7eb4.png)

### 2.2 Archiveの作成(xcode)
　ここでXcodeのメニューから「Product > Archive」をクリックし、プロジェクトをアーカイブします。アーカイブに成功すると、次の画面が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/91391/b2ef009e-b8d6-3838-6a96-081213cd93a1.png)

続いて、画面中の「Export...」画面をクリックすると、次の画面が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/91391/9a47c7f5-e712-42d6-e614-d9d6c98bbc57.png)

今回は、Apple Storeを通さず、直接アプリケーションを配布するため、ここでは「Developer ID」を選択し、「Next」ボタンをクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/91391/e2dac69a-f10b-e94c-2bbc-8f492c653cd7.png)

Distribution certificateにDeveloper ID Applicationが選択されていることを確認し、「Next」ボタンをクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/91391/94e6929b-0db4-9edf-a88d-eaff24d3de50.png)

「Export」ボタンをクリックし、任意の場所にアーカイブを保存します。

### 2.3 Packageの作成(Pkgbuild)
　ここからはTerminalで作業を行います。Terminalを起動し、Xcodeプロジェクトが保存されたディレクトリに移動します。ここでPkgbuildコマンドを使用してパッケージを作成しますが、パッケージの内容をplistで指示してやる必要があります。このとき、plistの雛形をPkgbuidコマンドの--analyzeオプションで作成することが出来ます。ひな形を修正したplistを次に示します。

```xml:components.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">
<array>
        <dict>
                <key>BundleHasStrictIdentifier</key>
                <true/>
                <key>BundleIsRelocatable</key>
                <false/>
                <key>BundleIsVersionChecked</key>
                <true/>
                <key>BundleOverwriteAction</key>
                <string>upgrade</string>
                <key>RootRelativeBundlePath</key>
                <string>hoge.app</string>
        </dict>
</array>
</plist>
```
2.2でプロジェクトのアーカイブを作成しましたが、ここでは仮にパッケージ名をhoge.appとし、保存先をpkgディレクトリとします。そしてPkgbuildで生成される複合パッケージをCompositePackageディレクトリに保存する場合、次のようにPkgbuildコマンドを記述します。

```bash
Pkgbuild \
--identifier com.yourOrganization.hoge \
--root pkg \
--component-plist components.plist \
--cert 'Developer ID Application' \
--version 1.0 \
--install-location ~/Applications/com.yourOrganization.hoge \
--scripts pkg/scripts/ \
CompositePackage/hoge.pkg
```
インストール前あるいは後にスクリプトを走らせたい場合、シェルスクリプトを作成し、--scriptsで指示するディレクトに入れておきます。インストール前に走らせたいときは、シェルスクリプト名をpreinstallを含む名前にし、後の場合はpostinstallを含む名前にします。

### 2.4 Installerの作成(productbuild)

　次にInstallerの作成を行います。先立って、--synthesizeオプションを使用し、installerの構成ファイルのひな形を作成します。

```bash
productbuild --synthesize --package CompositePackage/hoge.pkg Distribution.xml
```
```xml:Distribution.xml
<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="1">
    <title>hoge Install</title>
    <pkg-ref id="com.yourOrganization.hoge"/>
    <options customize="never" require-scripts="false"/>
    <choices-outline>
        <line choice="default">
            <line choice="com.yourOrganization.hoge"/>
        </line>
    </choices-outline>
    <choice id="default"/>
    <choice id="com.yourOrganization.hoge" visible="false">
        <pkg-ref id="com.yourOrganization.hoge"/>
    </choice>
    <pkg-ref id="com.yourOrganization.hoge" version="1.0" onConclusion="none">hoge.pkg</pkg-ref>
</installer-gui-script>
```

　Distribution.xmlファイルについては[こちら](https://developer.apple.com/library/content/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html#//apple_ref/doc/uid/TP40005370-CH100-SW14)を参照。デフォルト状態でもInstallerは作成できます。

```bash
productbuild --distribution Distribution.xml --package-path CompositePackage Installer/hoge.pkg
```
　上記コマンドによって、InstallerをInstallerディレクトリに作成します。

### 2.5 Developer ID Installer証明書による署名(productsign)
　最後にInstallerに対しても署名を施します。

```bash
productsign --sign "Developer ID Installer" Installer/hoge.pkg SignedInstaller/hoge.pkg
```
 

## 3. Agentとして実行するために行うこと
　一般的なアプリケーションであればここまでの作業でinstallerの作成は完了となります。今回はインストールしたアプリケーションをAgentとして登録し、ただちに起動をする必要があります。さらに今後はユーザがログインをしたタイミングで実行される必要があります。
### 3.1 インストール後に実行するScriptの作成

　本スクリプトは2.3の--scriptsオプションで指定するディレクトリに入れておきます。今回はユーザーがログオンしている間だけ実行されるAgentを作成するため、plistを\$HOME/Library/LaunchAgentsディレクトリに保存し、launchctlコマンドでAgentを実行します。この時注意が必要なことは、本スクリプトがroot権限を持ったInstallerによって呼び出されるということです。つまり本スクリプトはroot権限で実行されます。一方でplistはユーザーのHomeディレクトリ配下に配置し、ユーザー権限で実行する必要があります。したがってlaunchctrlはsudo -u \$USERを頭につけてユーザー権限で実行しています。

```bash:postinstall.sh
#!/bin/sh

identifer="com.yourOrganization.hoge"
insDir=$HOME/Applications/$identifer
echo "InstallDir : $insDir"
plistFileName="$identifer.agent.plist"
sourcePath="$insDir/$plistFileName"
installPath=$HOME/Library/LaunchAgents/$plistFileName

echo '<?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>Label</key>
    <string>com.yourOrganization.hoge.agent</string>
    <key>ProgramArguments</key>
    <array>
         <string>/usr/bin/open</string>
         <string>-W</string>
         <string>'$HOME'/Applications/com.yourOrganization.hoge/hoge.app</string>
    </array>
    <key>StandardOutPath</key>
    <string>'$HOME'/Library/Logs/hoge.log</string>
    <key>StandardErrorPath</key>
    <string>'$HOME'/Library/Logs/hoge.log</string>
    <key>StartInterval</key>
    <integer>20</integer>
</dict>
</plist>' > "$installPath"

#echo "installing launchctl plist: $sourcePath --> $installPath"
chmod 644 $installPath

echo "Unloading $installPath ..."
/usr/bin/sudo -u $USER /bin/launchctl unload $installPath

echo "Loading $installPath ..."
/usr/bin/sudo -u $USER /bin/launchctl load  $installPath
```

### 3.2 Agentの起動確認
Agentの登録確認は

```bash
launchctl list | grep $identifer
```

また登録解除は

```bash
launchctl unload $installPath
```
で行います。


## 4. 参考