🦒

【MacOS・LaunchAgent】ログイン時にスクリプトを実行する

2024/12/20に公開

はじめに

業務中に毎回、SQLクエリを叩く作業があり、PCのログイン時に処理が実行され、結果が自動で表示されたら便利だと思ったので、色々と調べて実行してみることにしました。今回はLaunchAgentという方法を使用して、実装していきたいと思います。



Launch Agentについて

LaunchAgentはmacOSのlaunchdで管理されるプロセスの一種です。ユーザーがログインしたときにロードされる為、この仕組を利用してユーザーログイン時にスクリプトを実行が可能となります。

https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html



エージェント(LaunchAgents)とデーモン(LaunchDaemons)の違いについて

エージェントとデーモンは共に、macOSでバックグラウンドプロセスを自動化するための仕組みですが、いくつか異なる点があります。デーモンは対話型ユーザーのセッションの直下ではなく、バックグラウンドプロセスとして実行され、システム全体に関わるタスクや、ユーザーセッションに依存しない処理に適しています。エージェントも同様にバックグラウンドプロセスの一種で、デーモンはシステムのプロセスとして実行されますが、エージェントは特定のユーザーのプロセスとして実行されます。エージェントはユーザーセッションに関連するタスク(ユーザーログイン後のタスク実行など)や、GUIアプリケーションの実行に適しています。つまり、エージェントがユーザーログイン時に起動するのに対して、デーモンはシステム起動時に起動します。

https://qiita.com/spc_gmorimoto/items/b7ba5d69a00e277bb259

https://www.techrepublic.com/article/macos-know-the-difference-between-launch-agents-and-daemons-and-use-them-to-automate-processes/


launchdで管理されているデーモンとエージェントのフォルダ構成

フォルダ 目的
/System/Library/LaunchDaemons システムが提供するデーモン
/Library/LaunchDaemons 他社製のシステムデーモン
/System/Library/LaunchAgents システムが提供するエージェント。すべてのユーザに対してユーザごとに適用される
/Library/LaunchAgents 他社製のエージェント。すべてのユーザに対してユーザごとに適用される
~/Library/LaunchAgents 他社製のエージェント。ログイン中のユーザにのみ適用される

https://support.apple.com/ja-jp/guide/terminal/apdc6c1077b-5d5d-4d35-9c19-60f2397b2369/mac

ユーザーがログインしていなくてもプログラムを起動したい場合は、デーモンで処理する必要があります。通常、システム起動時に実行されるデーモン用のplistファイルを/Library/LaunchDaemonsに配置します。セキュリティ上の理由から、plistファイルには適切な権限とオーナーシップを設定する必要がある為、設定ファイルの権限は644、所有者はroot:wheelにします。

sudo chown root:wheel /Library/LaunchDaemons/your_daemon.plist
sudo chmod 644 /Library/LaunchDaemons/your_daemon.plist

システム起動時に自動的に実行されるデーモンは、システムのセキュリティに影響を与える可能性があるため、信頼できるソースからのデーモンのみに限定します。

今回はログイン後に設定したスクリプトを起動させたい為、エージェントとして~/Library/LaunchAgentsに処理を記述していきます。



スクリプトの生成

まず、設定ファイルを追加する前に、実施したいスクリプトを生成します。

fetch_code.sh
#!/bin/bash

# SSHトンネルを開始
ssh -f -N -L 3306:demo-crm-db.cdmy:<サーバのポート番号> demo-api

# クエリを実行
mysql -u <your_username> -p <your_password> -h 127.0.0.1 -P 3306 -e <クエリ>

# 自動的にクエリ結果を表示する
echo "実行されました。"

設定ファイルを記述後、ファイル権限を追加します。

chmod +x fetch_code.sh

ls -la
-rwxr-xr-x   1  staff    45 12  2 11:52 fetch_code.sh

パスワードの管理方法について

今回は、SQLクエリを実行している為、セキュリティの観点から、データベースのユーザー名やパスワードをスクリプトに直書きするのは避けるべきです。ySQL の設定ファイルmy.cnfを利用してユーザー名とパスワードを安全に格納し、スクリプトから参照するように変更します。

https://dev.mysql.com/doc/refman/8.0/ja/password-security-user.html

my.cnf
[client]
user=your_username
password=your_password
host=
port=

ファイルの権限を600に設定して、他のユーザーから読み取れないようにします。
MySQLクライアントは ~/.my.cnf を自動的に参照するため、これでスクリプト内で-u-pの省略が可能です。

chmod 600 .my.cnf

http://www3.u-toyama.ac.jp/furuta/unix-permit.html



plistファイルの作成

スクリプトをLaunchdに登録するための設定ファイル(plist)を~/Library/LaunchAgents/ディレクトリに作成します。.plistはMac OSにおいてオブジェクトの永続化に用いられるファイル形式でメモリ上に生成したオブジェクトをファイルに書き込みプログラムが終了しても内容を保持します。

https://www.itmedia.co.jp/enterprise/articles/0705/14/news013.html#:~:text=plistとは、Mac OS,をサポートしている。

ファイルを作成します。

nano ~/Library/LaunchAgents/fetchCode.plist


fetchCode.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>
    <!-- ラベル(任意の名前でOK) -->
    <key>Label</key>
    <string>fetchCode</string>

    <!-- スクリプトのパス -->
    <key>ProgramArguments</key>
    <array>
      <string>/Users/<your_username>/scripts/fetch_code.sh</string>
    </array>

    <!-- 実行ディレクトリ -->
    <key>WorkingDirectory</key>
    <string>/Users/<your_username>/scripts</string>

    <!-- PC起動時に実行 -->
    <key>RunAtLoad</key>
    <true/>

    <!-- 標準出力/エラー出力 -->
    <key>StandardOutPath</key>
    <string>/Users/<your_username>/Library/Logs/fetchCode.out</string>
    <key>StandardErrorPath</key>
    <string>/Users/<your_username>/Library/Logs/fetchCode.err</string>
  </dict>
</plist>

label
launchdプロセスがデーモンを識別するために一意の文字列を指定します。

ProgramArguments
実行ファイルに引数を渡したい時に設定します。今回はスクリプトが特定のディレクトリで動作することを前提にしており、スクリプトのパスを指定(このパスは絶対バスで記載する必要があります)し<your_username>を自分のユーザー名に置き換えます。
ProgramArgumentsは最初の要素に実行するプログラムのパス、それ以降に引数を指定しており、配列で指定する必要があります。

WorkingDirectory
実行ディレクトリのパスを指定します。

RunAtLoad
実行タイミングを指定します。RunAtLoadtrueに設定することで、サービスのロード時(OS起動時を含む)に自動的にサービスが起動します。

StandardOutPath
スクリプトやコマンドがエラーを起こしている場合に、詳細なエラーを表示させるために標準出力と標準エラー出力を設定します。

https://dev.classmethod.jp/articles/mac-launch-agents/


シンタックスエラーがないかチェックし、

plutil -lint fetchCode.plist
fetchCode.plist: OK



Launch Agentのロード

launchctlコマンドを使って、上記の.plistファイルを手動でロードしてみます。

launchctl load ~/Library/LaunchAgents/fetchCode.plist

エラーが出力された場合は、ファイルの権限を確認します。

Load failed: 5: Input/output error
Try running `launchctl bootstrap` as root for richer errors.
ls -l
-rw-r--r--@   1 staff   636 12  2 11:54 fetchCode.plist

実行権限がない場合は以下で追加します。

chmod +x /Users/<username>/scripts/fetchCode.plist


または、標準出力と標準エラー出力を設定してデバッグを行い、エラー箇所を特定します。

cat /Users/takahashi_masaki/Library/Logs/fetchCode.out
cat /Users/takahashi_masaki/Library/Logs/fetchCode.err

修正後にアンロードを行い、動作確認を行います。

launchctl unload ~/Library/LaunchAgents/fetchCode.plist
launchctl load ~/Library/LaunchAgents/fetchCode.plist

https://qiita.com/ritumutaka/items/e74d2e1785c38dc265da



最後に

ここまでお読みいただきありがとうございました。



参考文献

https://qiita.com/ritumutaka/items/e74d2e1785c38dc265da

https://www.itmedia.co.jp/enterprise/articles/0704/26/news009_3.html

https://www.linkedin.com/pulse/maximizing-efficiency-macos-how-use-launchagents-run-scheduled

Arsaga Developers Blog

Discussion