Homebrewでインストールした各種サービスをlaunchdでデーモン化する＆他のコンピューターから繋げるようにする

ご存じのとおり、Homebrewでインストールしたサービスは、launchdを使ってユーザーのLaunchAgentsに設定することで、すぐに起動設定ができるようになっているものが多いですよね？

LaunchAgentsにパッケージに付属している`plist`のシンボリックリンクを張って、`launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.<product-name>.plist`のようにすれば、簡単に起動できます。

それらのサービスは、ログインユーザーだけが使えるようになっているので、ユーザーのログイン無しでも各サービスが使えるようにするには、さらにいくつかの手順が必要になります。

今回は、その手順について書いておきます。


## 始める前に

launchd、Homebrew、各サービスの操作方法については、ここでは詳しく説明しませんので、他の記事を参照してください。

設定を変更する前に、`<your-id>`ユーザーのLaunchAgentsで起動できることを先に確認しておいたほうが良いかも知れません。
デーモン化の際はLaunchAgentsの設定を外すことをお忘れなく。

**外部からアクセスできるようになっているようなケースでは、セキュリティーに十分ご注意ください。**


## 凡例

 - `<product-name>`: サービス名（製品名）
 - `<your-id>`: ログインユーザーID
 - `<myapp>`: 自作アプリ名


## 実行環境と扱うサービス

### 環境

 - OS X El Capitan (10.11.2)
 - Homebrew

### サービス

 - PostgreSQL 9.4.5
 - MySQL 5.7.10
 - Jenkins 1.643
 - GitBucket 3.9
 - JVMで動作するスタンドアローンな自作アプリ

最後の項目は、Jenkinsなどと同様に、Jettyなどを使って自らサーバーとして起動するタイプのアプリです。これも同様の構成にすれば、デーモン化できます。

上記以外のサービスでも、共通する点はあると思います。


## デーモン化

### plistのコピー

Homebrewのインストール後メッセージには、`ln -sfv /usr/local/opt/<product-name>/*.plist ~/Library/LaunchAgents`のようにシンボリックリンクを張るように出てきますが、デーモン化の際には内容を変更するので、コピーします。
コピーした後で、所有権が`root`になっていることを確認してください。

```
$ sudo cp -i /usr/local/opt/<product-name>/*.plist /Library/LaunchDaemons
$ ls -l /Library/LaunchDaemons
-rw-r--r--  1 root  wheel  1888 12 26 17:48 homebrew.mxcl.<product-name>.plist
```

サービスをアップグレードする際は、`/usr/local/opt/<product-name>/homebrew.mxcl.<product-name>.plist`を見て、変更が無いかどうか確認してください。


### デーモンの実行ユーザーを設定

`<your-id>`ユーザーがHomebrewでインストールしたサービスは、`<your-id>`ユーザーで起動できるように構成されています。

デーモン化する場合、デフォルトでは`root`権限で起動されますので、明示的に`<your-id>`ユーザーで起動するように設定を追加します。

```plist
    <key>UserName</key>
    <string>your-id</string>
```

あと、必須ではないですが、サービスの起動に失敗した場合、エラーメッセージが見られないと困るので、標準出力と標準エラー出力をリダイレクトしておくと便利です。

```plist
    <key>StandardOutPath</key>
    <string>/usr/local/var/product-name/stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/usr/local/var/product-name/stderr.log</string>
```


### 動作確認

設定が終わったら、起動してみましょう。
`$ launchctl load -w /Library/LaunchDaemons/homebrew.mxcl.<product-name>.plist`
この時点では外部からアクセスできるようになっていないので、ローカルのMacで確認してください。
また、ポートが被る可能性がありますし、どちらにしてもいったん停止させるので、ひとつずつ起動／停止したほうが良いと思います。

無事動作確認できたら、いったんアンロードしておきます。
`$ launchctl unload -w /Library/LaunchDaemons/homebrew.mxcl.<product-name>.plist`


## 外部から接続できるようにする

サービスのホストアドレスにローカル・ループバック・アドレス(IPv4の場合`127.0.0.1`, IPv6の場合`::1`, `localhost`も同様)が設定されていると、ループバック・アドレスでLISTENするので、他のコンピューターから繋げることはできません。


Homebrewでインストールした各サービスの設定は、おそらく安全のためにほとんどこれになっているので、`127.0.0.1`のようにループバックアドレスでLISTENされていないかどうか、`netstat`コマンド（後述）で確認してください。


### LISTENアドレスの変更

この設定は、`plist`を修正するものと、固有の設定ファイルを変更するものがあります。

PostgreSQLの場合は、設定ファイル`/usr/local/var/postgres/postgresql.conf`の`listen_addresses`項目を変更します。
デフォルトでは、
`#listen_addresses = 'localhost'`
のようにコメントアウトされていますので、それを外して、
`listen_addresses = '*'`
のように変更します。
ちなみに、ここでは直接は関係ありませんが、`'*'`の代わりに`'0.0.0.0'`を設定すると、IPv4だけでLISTENするようになります。


それ以外のサービスは、plistの修正です。
LISTENアドレスの設定を削除すれば、`*`でLISTENするようになります。
それぞれ設定名が異なりますが、意味は同じです。これらを削除します。

 - MySQL: `<string>--bind-address=127.0.0.1</string>`
 - Jenkins: `<string>--httpListenAddress=127.0.0.1</string>`
 - GitBucket: `<string>--host=127.0.0.1</string>`


デフォルトでポート`8080`でLISTENするサービスを複数利用する場合は番号が被ってしまうので、適当にポート番号を変更してください。
例えばJenkinsは`<string>--httpPort=8080</string>`になっていますね。


### 動作確認

各サービスを起動し、LISTEN状態を確認します。
コマンド`netstat -an | grep LISTEN`を実行すると、下記のようになっているのが分かります。
関係ないサービスのものは省いています。

```
tcp4       0      0  *.5432                 *.*                    LISTEN
tcp46      0      0  *.9900                 *.*                    LISTEN
tcp46      0      0  *.9901                 *.*                    LISTEN
tcp46      0      0  *.8888                 *.*                    LISTEN
tcp46      0      0  *.8811                 *.*                    LISTEN
tcp46      0      0  *.3306                 *.*                    LISTEN
```

`8888`はJenkins、`8811`はGitBucket、9000番台は自作アプリです。それ以外はデフォルトポートです。
PostgreSQLはIPv4だけLISTENしています。


## おまけ：自作アプリの設定

自作アプリの場合は、`<your-id>`ユーザーで実行する必要はありませんが、それ以外は同様に下記のように設定して、`/Library/LaunchDaemons/`に保存し、ロードしてください。

```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>Label</key>
    <string>local.myapp</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/bin/java</string>
      <string>-jar</string>
      <string>/usr/local/var/myapp/myapp.jar</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>WorkingDirectory</key>
    <string>/usr/local/var/myapp</string>
    <key>StandardOutPath</key>
    <string>/usr/local/var/myapp/stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/usr/local/var/myapp/stderr.log</string>
  </dict>
</plist>
```


## 最終確認

以上で、各サービスをデーモン化して他のコンピューターから繋げるようになりました。

Macを再起動して、ログインせずに接続できることを確認してみてください。

そのあとで、launchdの状態を確認してみましょう。

```
$ sudo launchctl list | grep homebrew
48      0       homebrew.mxcl.jenkins
67      0       homebrew.mxcl.gitbucket
82      0       homebrew.mxcl.postgresql
60      0       homebrew.mxcl.mysql
$
```

（おわり）