launchctl でサービスを自動起動する

macOSにはサービスを自動起動するために launchctl という機構があります。10年前にApacheの設定をした ときから気づいてはいたのですが、使ってみることはありませんでした。今回 MacBook でも Nginx を動かそうかと思っているので、その予行演習として、Node製のサービスを自動起動してみます。

launchctl の使い方

使い方は Linux の systemctl に似ています。launchd.plist と呼ばれる起動方法を記述したXMLファイルを launchctl に読み込ませると launchd が自動起動するようになるという機構です。systemd.unit と呼ばれる設定ファイルを systemctl で読み込むと systemd によって自動起動される Linux の機構と同じですね。

launchd.plist の記述法

Apacheの launchd.plist は /System/Library/LaunchDaemons/org.apache.httpd.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>Disabled</key>
	<true/>
	<key>Label</key>
	<string>org.apache.httpd</string>
	<key>EnvironmentVariables</key>
	<dict>
		<key>XPC_SERVICES_UNAVAILABLE</key>
		<string>1</string>
		<key>OBJC_DISABLE_INITIALIZE_FORK_SAFETY</key>
		<string>YES</string>
	</dict>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/sbin/httpd-wrapper</string>
		<string>-D</string>
		<string>FOREGROUND</string>
	</array>
	<key>KeepAlive</key>
	<true/>
</dict>
</plist>

XMLなので非常に冗長ですが、JSONで表現すれば、

{
    "Disabled": true,
    "Label": "org.apache.httpd",
    "EnvironmentVariables": {
        "XPC_SERVICES_UNAVAILABLE": "1",
        "OBJC_DISABLE_INITIALIZE_FORK_SAFETY": "YES"
    },
    "ProgramArguments": [ "/usr/sbin/httpd-wrapper", "-D", "FOREGROUND" ],
    "KeepAlive": true
}

ということですね。

Disabled
サービスを強制的にロードするとき false に設定する。省略した場合のデフォルト値は false。*1
Label
launchd に対する一意なサービス名*2を指定する。
EnvironmentVariables
サービス起動時に設定する環境変数。
ProgramArguments
サービスの起動引数。
KeepAlive
サービスを常駐させるとき true に設定する。*3

LabelProgramArguments は必須です。他にもプロパティは定義されているので、詳しく知りたければ man 5 launchd.plist でドキュメントが参照できます。

launchctl の使用例

launchctl の使用例をいくつか見てみましょう。

$ launchctl load service.plist

ファイル名を指定してサービスを launchctl の管理下に置く。KeepAlive に true を指定したサービスはこの瞬間に起動されます。

$ launchctl start label

サービス名を指定してサービスを起動する。

$ launchctl stop label

サービス名を指定してサービスを(一時)停止する。KeepAlive に true を指定したサービスはOS再起動時に再起動されるので一時停止です。

$ launchctl unload service.plist

ファイル名を指定してサービスを launchctl の管理下から外す。KeepAlive に true を指定したサービスはこれで起動されなくなります。

詳細は man launchctl 参照。

launchd.plist の配置場所

man 8 launchd より。

配置場所 説明
~/Library/LaunchAgents 各ユーザ管理の定期起動サービス
/Library/LaunchAgents 管理者管理の定期起動サービス
/Library/LaunchDaemons 管理者管理の常駐サービス
/System/Library/LaunchAgents OS管理の定期起動サービス
/System/Library/LaunchDaemons OS管理の常駐サービス.

定期起動サービスと常駐サービスの違いがよくわかりませんが、各ユーザが作ったサービスは ~/Library/LaunchAgents に置くので定期起動サービスの扱いになります。もっともログインすれば必ず起動しているので、ユーザからしてみれば常駐サービスと同じです。

実際に動かしてみる

Node製のサービス LiuLian を「ユーザ管理」で常駐させます。ログイン時に起動し、ログアウト時に停止する使い方です。

plistの記述

ローカルディレクトリに net.kobalab.liulian.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>Disabled</key>
	<true/>
	<key>Label</key>
	<string>net.kobalab.liulian</string>
	<key>ProgramArguments</key>
	<array>
		<string>/opt/homebrew/bin/node</string>
		<string>/opt/homebrew/bin/liulian</string>
		<string>/Users/koba/Work/LiuLian/</string>
	</array>
	<key>KeepAlive</key>
	<true/>
</dict>
</plist>

ログインシェルが設定する PATH は使えないので、コマンド名などはフルパスで記述します*4

起動

作成した plist を ~/Library/LaunchAgents に配置します。

$ mv net.kobalab.liulian.plist ~/Library/LaunchAgents

すると以下の通知が現れます。これだけで load されたようです。

この後は システム設定一般ログイン項目 で操作できるようになります*5

*1:これはデフォルト値が悪い。デフォルトが true なら、みな省略すると思います

*2:なのでサービス開発元のドメイン名を使うのが流儀のようです

*3:実際にはもっと複雑な指定ができるようです

*4:本来は liulian ~/Work/LiuLian/ で起動できるので冗長ですね

*5:サービス名が表示されないので識別が面倒ですが