launchdでmacを目覚まし時計に

# はじめに
本記事は、[All About Group（株式会社オールアバウト） Advent Calendar 2023](https://qiita.com/advent-calendar/2023/allabout) 22日目の投稿です。

今回は主にmacで使用されるジョブスケジューラの`launchd`について、サンプルを用いてまとめてみました！

## launchdとは
> launchdは、デーモン、アプリケーション、プロセス、スクリプトの起動・停止・管理を行う、オープンソースのサービス管理フレームワークである。(wiki参照)

launchdはmacOSやFreeBSD系に使用されるジョブスケジューラです。launchdを使用することで、プログラムをデーモン化、エージェント化することができます。具体的には、バックグラウンドでスクリプトを定期実行させたり、OSを起動させたタイミングでスクリプトを実行させたりすることなどが可能です。

ジョブスケジューラの選択肢としてcronもありますが、apple的にはlaunchdを推奨しているようです。

###  デーモンとエージェント
どちらもバックグラウンドで動いているプロセス。

- デーモン => システム全体のプロセスとして動く
- エージェント => ユーザーごとのプロセスとして動く

## plist
launchdにデーモンやエージェントを登録するためには、拡張子が`plist`のファイルを作成し、所定のディレクトリに配置する必要があります。`plist`ファイルはproperty listファイルといって、apple系のxmlのような形式のファイルです。

## plistの置き場所
ざっくりですが
- `LaunchAgents`  => ログインユーザーでのみ有効
- `LaunchDaemons` => システム全体で有効

`/System/Library`配下はOSのデーモンやエージェントの設定ファイルが配置され、`/Library`配下はサードパーティの設定ファイルが配置されるようです。

```text:man launchd
~/Library/LaunchAgents         Per-user agents provided by the user.
/Library/LaunchAgents          Per-user agents provided by the administrator.
/Library/LaunchDaemons         System-wide daemons provided by the administrator.
/System/Library/LaunchAgents   Per-user agents provided by Mac OS X.
/System/Library/LaunchDaemons  System-wide daemons provided by Mac OS X.
```


## launchctl
launchdのデーモンやエージェントを操作するためのコマンド

### load/unload
loadでplistファイルを指定することで、デーモンやエージェントが起動する
unloadを実行することで停止する

```
$ launchctl load /path/to/plist
$ launchctl unload /path/to/plist
```

### list
起動中のlaunchdを表示する
```
$ launchctl list
```

### 

## やってみよう
平日の毎朝7時に目覚ましが鳴るようなジョブスケジューラを作ってみましょう

 ### 実行環境
 - macOS Ventura Version 13.1
 - シェル zsh 5.8.1 (x86_64-apple-darwin22.0)

## シェルスクリプトの作成
アラームを鳴らすための、シェルスクリプトを作成してみましょう。

```zsh
$ brew install mplayer
$ vim /usr/local/bin/weekdaysAlarm.sh
```
```sh:weekdaysAlarm.sh
#!/bin/zsh

/USR/LOCAL/BIN/mplayer /path/to/音声ファイル //（音声ファイルは自分の好きなmp3ファイルとかでok）
```

## ジョブの登録
今回はlaunchdのデーモンにジョブを登録します。
`/Library/LaunchDaemons/`配下に`weekDaysAlarm.plist`を作成しましょう。

```zsh
$ vim /Library/LaunchDaemons/weekDaysAlarm.plist 
```

```xml:weekDaysAlarm.plist
<!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>weekdays_alarm</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/zsh</string>
        <string>/usr/local/bin/weekdaysAlarm.sh</string>
    </array>
    <key>StartCalendarInterval</key>
    <array>
        <dict>
            <key>Weekday</key>
            <integer>1</integer>
            <key>Hour</key>
            <integer>7</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Weekday</key>
            <integer>2</integer>
            <key>Hour</key>
            <integer>7</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Weekday</key>
            <integer>3</integer>
            <key>Hour</key>
            <integer>7</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Weekday</key>
            <integer>4</integer>
            <key>Hour</key>
            <integer>7</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Weekday</key>
            <integer>5</integer>
            <key>Hour</key>
            <integer>7</integer>
            <key>Minute</key>
            <integer>00</integer>
        </dict>
    </array>

    <key>StandardOutPath</key>
    <string>/path/to/output.out</string>
    <key>StandardErrorPath</key>
    <string>/path/to/output.err</string>
</dict>
</plist>
```

上記のファイルを作成したら、ファイルを読み込んでデーモンとして起動させよう
```
$ launchctl load /Library/LaunchDaemons/weekDaysAlarm.plist
```

## 設定値の説明

### 識別名（必須）
ジョブを識別するために一意に特定できる名前をつける
```xml:Label
<key>Label</key>
<string>weekdays_alarm</string>
```
### 実行ファイルの指定（必須）
`ProgramArguments`で実行ファイルを指定する。
```xml:ProgramArguments
<key>ProgramArguments</key>
<array>
    <string>/bin/zsh</string>
    <string>/usr/local/bin/weekdaysAlarm.sh</string>
</array>
```
### 実行タイミング
#### 一定間隔で実行する
`StartInterval`で秒数を指定する
```xml:StartInterval
<key>StartInterval</key>
<integer>10</integer>
```
#### 特定のタイミングで実行する
`StartCalendarInterval`で特定のタイミングで実行する
- Month：月
- Weekday：曜日
- Day：日
- Hour：時間
- Minute：分

```xml:StartCalendarInterval
<key>StartCalendarInterval</key>
<array>
    <dict>
        <key>Weekday</key>
        <integer>1</integer>
        <key>Hour</key>
        <integer>6</integer>
        <key>Minute</key>
        <integer>30</integer>
    </dict>
</array>
```

### StandardErrorPath, StandardOutPath
標準出力/エラーログの出力先を指定する。

StandardOutPath   => 標準出力先
StandardErrorPath => エラーの出力先

```xml:StandardOutPath,StandardErrorPath
<key>StandardOutPath</key>
<string>/path/to/output.out</string>
<key>StandardErrorPath</key>
<string>/path/to/output.err</string>
```

## 確認
設定した時間に音声が鳴ればokです！

## まとめ
最後まで読んでいただきありがとうございました！
今回はlaunchdを使用したジョブの定期実行方法について、サンプルを用いてまとめました。
この記事が誰かの役に立てば幸いです。

## 参照
- [Scheduling Timed Jobs]( https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html)
- [man launchctl](https://ss64.com/osx/launchctl.html)
- [launchdで定期的にスクリプトを実行](https://qiita.com/rsahara/items/7d37a4cb6c73329d4683#%E8%AD%98%E5%88%A5%E5%90%8D%E5%BF%85%E9%A0%88)
- [MacOSのデーモンをマスターしよう：Launchdプロセス](https://qiita.com/spc_gmorimoto/items/b7ba5d69a00e277bb259)
- [launchd, launchctlについて(導入編)](https://gist.github.com/kozy4324/5552217)
- [wikipedia launchd](https://ja.wikipedia.org/wiki/Launchd)