systemdでmastodonサーバ上のメディア削除を定期処理化する

# これは何？
mastodonサーバ上に蓄積されたメディアデータを毎日深夜2時に削除する定期処理を、systemdを使って実現した際の作業メモ。


本記事では、技術的な詳細やサーバ管理者の常識のようなものは追及できていない。あくまで小規模mastodonサーバ管理者(初心者)の備忘録的なものに過ぎないので注意。

# 本記事が想定する環境
- Ubuntu 20.04 LTS
- Ruby v3.0.6 (rbenvを使用)
- mastodon v4.1.5

# やりたいこと
日々の運用でストレージがいっぱいになったとき、空き容量を作るためのメンテナンス作業を自動で行いたい。ここで言うメンテナンス作業とは、具体的には以下のようなコマンドを呼び出すことを指す。

**(1) 古いプレビューカードを削除**
```
$ RAILS_ENV=production bundle exec bin/tootctl media remove --days=1
```

**(2) どこからも参照されていないメディアを削除**
```
$ RAILS_ENV=production bundle exec bin/tootctl media remove-orphans
```

**(3) 古いプレビューカードを削除**
```
$ RAILS_ENV=production bundle exec bin/tootctl preview_cards remove --days=1
```

要は上記一連のコマンドを毎日実行できれば何でもよい。本記事では便宜上、この(1)～(3)をまとめて「メディアクリンナップ処理」と呼ぶことにする。

# どうやって作るか
定期実行させる処理をLinux上で実現するための手段を調べてみたところ、crontab か systemd を使うのが良さそうに見えた。

ところで、 mastodon ではアプリケーションを systemd で管理するサービスとして定義している (mastodon-web.service、mastodon-streaming.service、mastodon-sidekiq.service)。


どのみち systemd の知識はあったほうが良さそうなので、今回の目的についても systemd を使って果たすことにした。

調べたところ、 下記の事をやれば良いということがわかった。

- サービス本体定義として <myservice>.service ファイルを作成し、systemd に読み込ませる
- これを定期的に実行するタイマー定義として <myservice>.timer ファイルを作成し、systemd に読み込ませる

# どんなものを作ったか
今回作りたいものは、下記のようなファイルを作成して組み合わせれば実現できそうだとわかった。

|ファイル名|内容|配置場所|
|--|--|--|
|media-cleanup.sh|メディアクリンナップ処理をまとめて呼び出すシェルスクリプト|どこでもよい|
|mastodon-media-cleanup.service|media-cleanup.shを実行するサービス|/run/systemd/system/|
|mastodon-media-cleanup.timer|mastodon-media-cleanup.serviceを毎日指定時刻に呼び出すタイマー|/run/systemd/system/|

#### 補足：ユニット設定ファイルの配置場所について
[入門モダンLinux](https://www.oreilly.co.jp/books/9784814400218/)によれば `/run/systemd/system/` は「一時的な実行時のユニット」を配置する場所らしい。「システム管理者が設定したユニット」を配置すべきなのは `/etc/systemd/system` の方らしいので、もしかしたら本記事で作成したユニットはこちらに置く方がよかったのかもしれない。

## media-cleanup.sh

```shell
#!/bin/bash

pushd /home/mastodon/live
/home/mastodon/.rbenv/shims/bundle exec bin/tootctl media remove --days=1
/home/mastodon/.rbenv/shims/bundle exec bin/tootctl media remove-orphans 
/home/mastodon/.rbenv/shims/bundle exec bin/tootctl preview_cards remove --days=1
popd
```

注意点は以下：
- 環境変数の設定 `RAILS_ENV=production`は .service ファイルの Environment として指定できるので .sh には書かなくてよい
- bundle はフルパスを指定する必要があった
- shebang `#!/bin/bash` が無いと systemd からこの .sh を実行できなかった
- pushd と popd を呼んでいるが、.service ファイルの WorkingDirectory として指定できるので .sh に書かなくてよかったかも
- 自分の場合はこのスクリプトを `/home/mastodon/utils/mastodon/scripts/` というディレクトリを掘って配置した

## mastodon-media-cleanup.service
[入門モダンLinux](https://www.oreilly.co.jp/books/9784814400218/)で紹介されている例(greeter)を参考にした。

```
[Unit]
Description=Mastodon Media Data Cleanup Service

[Service]
Type=oneshot
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
ExecStart=/home/mastodon/utils/mastodon/scripts/media-cleanup.sh

[Install]
WantedBy=multi-user.target
```

注意点は以下：
- Type は oneshot を選択した。simple だとコマンドを実行できた時点で成功とみなされるらしいが、かえって混乱しそうなので
- Install セクションの WantedBy を設定していない場合、サービスの起動に失敗する。入門モダンLinuxで紹介されている greeter サンプルにはこの記述が無かった

## mastodon-media-cleanup.timer
[入門モダンLinux](https://www.oreilly.co.jp/books/9784814400218/)で紹介されている例(greeter)を参考にした。

```
[Unit]
Description=Runs Mastodon media cleanup service every day

[Timer]
OnCalendar=*-*-* 02:00:00

[Install]
WantedBy=timers.target
```

注意点は以下：
- Timer セクションの OnCalendar に指定可能な日時フォーマットは[こちら](https://oreil.ly/pinVc)

# どうやって使うか
#### systemd のリロード
mastodon-media-cleanup.service と mastodon-media-cleanup.timer を /run/systemd/system/ に配置されたら、下記コマンドを実行して systemd に読み込ませる。

```
$ systemctl daemon-reload
```

#### タイマーとサービスの自動起動を有効化する
```
$ systemctl enable mastodon-media-cleanup.timer
```
```
$ systemctl enable mastodon-media-cleanup.service
```

#### タイマーとサービスを起動する
```
$ systemctl start mastodon-media-cleanup.timer
```
```
$ systemctl start mastodon-media-cleanup.service
```

.service は start した時点でメディアクリンナップ処理が実行される。


#### タイマーとサービスの稼働状況を確認する

```
$ systemctl status mastodon-media-cleanup.timer
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95847/f2258c61-06ba-deca-b59d-6f40b01a3d15.png)

```
$ systemctl status mastodon-media-cleanup.service
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95847/20623f25-9cb2-2a2a-88d0-9f3d49245fbc.png)

上記ログより、深夜2時にメディアの削除処理が走ったことがわかる。

# 振り返り
最低限の目的は達成できた。後は運用してみて問題が起きたら都度対処する。
systemdとユニットについての理解が浅いままに作れてしまった。見よう見まねでやったので粗が多いはず
あまり勉強しなくてもやりたいことが実現できたことを踏まえると、systemdという仕組みの強力さが実感できたと思う。