目次
macOSでlaunchdを触り始めるとほぼ最初に出会うのが launchctl load ですが、これはmacOS 10.10以降ずっと非推奨扱いです。実際、Homebrewのサービス管理スクリプトも bootstrap への置き換えが進んでいます[1]。自分も最近 launchd の設定記事を書いていて「これ古い書き方のまま広めてしまっているな」と気付いたので、いまから書くなら何を使うべきかを整理します。
load が古いとされる理由
launchctl load は10.10/Yosemiteの時点でlegacy扱いになりました。後継として launchctl bootstrap / launchctl bootout / launchctl enable などが追加されています。動かないわけではないので、ググって出てくる古い記事のとおりに打てば今でも一応動きます。ただ、続けて使うには面倒な落とし穴がいくつか残っています。
エラーが出ても理由を返さない
launchctl load は plist の不備や権限の問題で読み込みに失敗しても、ターミナルに何も表示せず終わることがあります。一方 bootstrap は Bootstrap failed: 5: Input/output error のように番号付きの理由を返してくれるため、原因の切り分けがしやすくなりました。launchctl load で「無言で動かない」状態の原因調査に時間を溶かした経験がある人は、これだけでも乗り換える価値があります。
root と一般ユーザーで挙動が変わる
旧来の load は、実行ユーザーがrootなら system domain(システム全体)、一般ユーザーなら user domain(自分のセッション)を勝手に選びます。同じコマンドでも「rootで sudo を付けたか」「ターミナルからユーザー権限で叩いたか」で結果が変わるため、スクリプト化したときに事故が起こりやすい仕様でした。bootstrap は 必ずドメインを明示する設計に変わったので、挙動が予測しやすくなっています。
ドメインという考え方
bootstrap を使うにはドメイン(domain target)の指定が必要です。launchdは「どのコンテキストでジョブを動かすか」を3種類に分けて管理します。
system
OS起動直後、ログイン前から動かしたいジョブ用。/Library/LaunchDaemons/ 配下に置く plist が対象で、rootとして実行されます。データベースや常駐サーバーをマシン起動と同時に立ち上げたいときに選びます。
user/UID
特定ユーザーがログインしなくても、そのユーザーのセッションとして動かしたいジョブ。/Library/LaunchAgents/ または ~/Library/LaunchAgents/ 配下の plist が対象で、ユーザーIDを指定します。launchctl bootstrap user/501 ... のように書きます。
gui/UID
ユーザーがGUIにログインしている間だけ動かすジョブ。スクリーンセーバーやデスクトップに関わる処理など、画面セッションが必要なものに向いています。launchctl bootstrap gui/501 ... の形で指定します。
実際に置き換える
旧来の書き方と新しい書き方を並べます。題材は cron 代わりに毎時走らせるシェルスクリプト(plist 名は com.example.hourly)です。
読み込む
旧:
# launch agent を読み込む
launchctl load ~/Library/LaunchAgents/com.example.hourly.plist
新:
# 自分のUIDをまず確認
id -u
# user ドメインに対して読み込む
launchctl bootstrap user/$(id -u) ~/Library/LaunchAgents/com.example.hourly.plist
bootstrap はパスの解決が load より厳しいので、~/ の展開や絶対パス指定でハマることがあります。エラーで落ちたら絶対パスに直してみるのが早道です。
取り外す
旧:
launchctl unload ~/Library/LaunchAgents/com.example.hourly.plist
新:
# サービス名でも plist パスでも指定できる
launchctl bootout user/$(id -u) ~/Library/LaunchAgents/com.example.hourly.plist
# もしくはサービス識別子で
launchctl bootout user/$(id -u)/com.example.hourly
bootout はジョブが走っていれば停止してから取り外します。unload と違って、対象がそもそも読み込まれていない状態で叩くと Could not find specified service のように明示的にエラーを返します。
設定だけ書き換えてリロードしたい
plist を編集した後の再読込は bootout してから bootstrap で入れ直すのが基本です。一手で済む reload のような便利コマンドは launchctl にはありません。
launchctl bootout user/$(id -u) ~/Library/LaunchAgents/com.example.hourly.plist
launchctl bootstrap user/$(id -u) ~/Library/LaunchAgents/com.example.hourly.plist
enable / disable との関係
bootstrap で読み込んでも、launchctl disable でジョブを無効化していると起動しません。一度 disable を叩いたジョブは plist を入れ直しても無効のままなので、再有効化に enable が必要です。
launchctl enable user/$(id -u)/com.example.hourly
launchctl bootstrap user/$(id -u) ~/Library/LaunchAgents/com.example.hourly.plist
「load し直しても動かない」「bootstrap も通っているのにジョブが走らない」とハマったら、まずこの disable 状態を疑うと早いです。
まとめ
新しく launchd ジョブを設定するときは bootstrap / bootout で書く、これが今のmacOSでの素直な構え方です。旧来の load / unload は完全に動かなくなったわけではなく、しばらく共存していくと思いますが、エラー追跡のしやすさで bootstrap 系の方が日々の運用に向いています。
ただし、bootstrap 系には弱点もあって、cron風の感覚で短いコマンドにエイリアスすると、ドメイン指定を間違えたまま使い続けて事故ります。スクリプト化するときは user/$(id -u) のように動的に解決する書き方にしておくと、別マシンに持ち運ぶときも壊れにくいです。
launchd 自体に入門する段階の人は、まず launchctl list で何が動いているか観察するところから始めると、ドメインの感覚もつかみやすいと思います。
