launchctlで load の代わりに bootstrap/bootout を使う

公開日:
目次

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 の不備や権限の問題で読み込みに失敗しても、ターミナルに何も表示せず終わることがあります。一方 bootstrapBootstrap 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 で何が動いているか観察するところから始めると、ドメインの感覚もつかみやすいと思います。

脚注
  1. Convert to using launchctl bootstrap instead of deprecated launchctl load -w · Homebrew/homebrew-services PR #112 ↩︎