[Linux] systemdのユーザインスタンスで、サーバ起動後にユーザ固有のジョブを自動実行する
systemdは、いつものsudo
つきでシステムワイドに実行する他にも、Linuxユーザごとに固有のサービス起動を指定・実行することができます。
本記事では、これを利用して、サーバ(再)起動後の初回ログイン時[1]に、自動で特定ユーザ用のジョブを実行させる例を示します。
(筆者のsystemdの理解がそこまで深くないので、手順をなぞる寄りの記事になります。)
この記事でやること
systemdユニットファイルに、やりたいジョブの具体的な実行方法(スクリプトやコマンドの実行など)をまとめます。
また、このユニットファイルが自動実行されるように設定します。
対象読者
sudo systemctl
は使ったことがあるけれど、sudo
なし&--user
オプション付きでsystemdを使うことは知らなかった人
筆者の使用例
-
sudo apt update -y
を走らせる -
.ssh/configを、外部のプライベートGitリポジトリから持ってきてセッティングする
systemdうま味
-
systemctl
コマンドで、いろんなサービスを同じ操作で実行できる -
何を用意したか一覧できる:
-
systemctl --user list-dependencies
などで確認する -
~/.config/systemd/user/配下のユニットファイルを
ls
などで見る
-
-
ログも、
journalctl
コマンドで、同じ操作で確認できる
まとめ
初めてこの記事を読む際は、ここは適当に読み流して、実装例の項を眺めた方が読みやすいと思います。
やり方要点
-
「~/.config/systemd/user/<サービス名>.service」というユニットファイルを作る
ユニットファイル例
単発で実行するコマンド例なので、
Type=oneshot
と指定しています。[Unit] Description=Apt Update [Service] Type=oneshot ExecStart=/usr/bin/sudo /usr/bin/apt update -y [Install] WantedBy=default.target
-
オプション
--user
つき(&sudo
なし)で、systemctl
コマンドで色々する-
ユニットファイルを更新したので、
daemon-reload
する$ systemctl --user daemon-reload
-
サービスの起動
start
・停止stop
・再起動restart
-
サーバ(再)起動後初回ログイン時[1:1]にサービスを実行したいので、
enable
する$ systemctl --user enable <サービス名>.service
-
list-dependencies
で、実行ツリーにサービスが含まれているかどうかを確認する$ systemctl --user list-dependencies
-
-
(おまけ)
journalctl
コマンドで、実行結果を確認する$ journalctl --user -xeu <サービス名>.service
オプションの意味(
journalctl --user --help
より):-
-x
:説明文を追加する -
-e
:ページャの末尾に移動した状態にする -
-u
:指定したユニットのログを表示する
-
サービス内で環境変数を使う際の注意点
systemdのサービス自動実行は、ユーザのログインシェルセッションと独立しています[2:1]。
したがって、Linuxログイン時に.bashrcなどで自動的に設定されるような環境変数(例:PATH
)を使いたい場合、何らかの方法で設定してあげる必要があります。
無難と思われる例:
-
environment.d ディレクトリを使用する[5]
-
ユニットファイル中で呼び出すシェルスクリプトに
PATH
の設定を書いてしまう- ちょっと〇〇のランタイムだけ必要なんだよね、くらいなら
(ついでに)サービスを手動実行する場合
(今回の主旨とすこしずれるけれど、ついでに)自動実行ではなく、ログインシェル上で、systemctl --user start
コマンドなどでサービスを手動実行する場合:
-
systemctl --user import-environment
を使う
実装例
ここでは、以下2つの「単発実行ジョブを走らせる」サービスを、サーバ起動時の実行ユーザサービスとして設定することにします:
-
sudo apt update -y
するサービス -
実行時刻と
env
コマンドの結果を、~/record_env_info.txtというファイルに追記するサービス-
実行コマンドとしてシェルスクリプト(実行ファイル)を指定するパターン
-
一応、systemdユーザインスタンス自動実行時の
PATH
やらなんやらが確認できるから、サービス開発の時なんかには役に立つし...(サービス内で環境変数を使う際の注意点 < やり方要点まとめ)
-
sudo apt update -y
するサービスの実装
1. 1-1. ユニットファイルの作成
ユニットファイル「~/.config/systemd/user/apt-update.service」を作成します。
ポイントは、
[Unit]
Description=Apt Update
[Service]
Type=oneshot
ExecStart=/usr/bin/sudo /usr/bin/apt update -y
[Install]
WantedBy=default.target
1-2. ユーザユニットファイルの変更を反映
daemon-reload
サブコマンドで、ユニットファイルの変更を反映します:
$ systemctl --user daemon-reload
反映ついでに、今作成したapt-update.serviceの状態確認をしておきましょう:
# status
$ systemctl --user status apt-update.service
○ apt-update.service - Apt Update
Loaded: loaded (/home/ubuntu/.config/systemd/user/apt-update.service; disabled; vendor preset: enabled)
Active: inactive (dead)
# is-enabled
$ systemctl --user is-enabled apt-update.service
disabled
また、list-dependencies
サブコマンドで、実際にブート後に実行されるサービスの一覧が確認できます:
$ systemctl --user list-dependencies
default.target
● └─basic.target
● ├─paths.target
● ├─sockets.target
● │ ├─dbus.socket
● │ ├─dirmngr.socket
● │ ├─gpg-agent-browser.socket
● │ ├─gpg-agent-extra.socket
● │ ├─gpg-agent-ssh.socket
● │ ├─gpg-agent.socket
● │ ├─pk-debconf-helper.socket
● │ └─snapd.session-agent.socket
● └─timers.target
今作成したapt-update.serviceは、まだenable
設定していない現時点では、この一覧に含まれていないことがわかります。
1-3. 手動実行して動作確認
restart
(今回はType=oneshot
なので、start
でもよい)サブコマンドで、一度手動実行してみます。
$ systemctl --user restart apt-update.service
# 内容がsudo apt updateなので、少し待ち時間が生じる
systemctl --user status
やjournalctl --user
で、実行ログを確認できます:
$ systemctl --user status apt-update.service
$ journalctl --user -xeu apt-update.service
# ページャが開く(qキーで閉じる)
オプションの意味(journalctl --user --help
より):
-
-x
:説明文を追加する -
-e
:ページャの末尾に移動した状態にする -
-u
:指定したユニットのログを表示する
エラーがない場合
エラーが起きなければ、特に出力なく終わります。
詳細
status
サブコマンドで実行ログを確認したり、
$ systemctl --user status apt-update.service
○ apt-update.service - Apt Update
Loaded: loaded (/home/ubuntu/.config/systemd/user/apt-update.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Jul 08 14:00:14 ip-172-31-1-136 systemd[786]: Starting Apt Update...
Jul 08 14:00:14 ip-172-31-1-136 sudo[882]: ubuntu : PWD=/home/ubuntu ; USER=root ; COMMAND=/usr/bin/apt update -y
Jul 08 14:00:14 ip-172-31-1-136 sudo[882]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
Jul 08 14:00:20 ip-172-31-1-136 sudo[882]: pam_unix(sudo:session): session closed for user root
Jul 08 14:00:20 ip-172-31-1-136 systemd[786]: Finished Apt Update.
Jul 08 14:00:20 ip-172-31-1-136 systemd[786]: apt-update.service: Consumed 4.463s CPU time.
あるいは、journalctl
で詳細を確認できます。
$ journalctl --user -xeu apt-update.service
# ページャが開く(qキーで閉じる)
エラーがある場合
エラーがある場合は、その旨のメッセージが出ます。
詳細
$ systemctl --user restart apt-update.service
Job for apt-update.service failed because the control process exited with error code.
See "systemctl --user status apt-update.service" and "journalctl --user -xeu apt-update.service" for details.
エラーメッセージが書いてくれているように、systemctl --user status
や
$ systemctl --user status apt-update.service
× apt-update.service - Apt Update
Loaded: loaded (/home/ubuntu/.config/systemd/user/apt-update.service; disabled; vendor preset: enabled)
Active: failed (Result: exit-code) since Fri 2022-07-08 14:02:53 UTC; 14min ago
Process: 1228 ExecStart=/usr/bin/sudoo /usr/bin/apt update -y (code=exited, status=203/EXEC)
Main PID: 1228 (code=exited, status=203/EXEC)
CPU: 769us
Jul 08 14:02:53 ip-172-31-1-136 systemd[786]: Starting Apt Update...
Jul 08 14:02:53 ip-172-31-1-136 systemd[1228]: apt-update.service: Failed to locate executable /usr/bin/sudoo: No such file or directory
Jul 08 14:02:53 ip-172-31-1-136 systemd[1228]: apt-update.service: Failed at step EXEC spawning /usr/bin/sudoo: No such file or directory
Jul 08 14:02:53 ip-172-31-1-136 systemd[786]: apt-update.service: Main process exited, code=exited, status=203/EXEC
Jul 08 14:02:53 ip-172-31-1-136 systemd[786]: apt-update.service: Failed with result 'exit-code'.
Jul 08 14:02:53 ip-172-31-1-136 systemd[786]: Failed to start Apt Update.
journalctl --user -xeu
で
$ journalctl --user -xeu apt-update.service
# ページャが開く(qキーで閉じる)
エラー内容を確認して、ユニットファイルを修正しましょう。
修正が完了したら、
$ systemctl --user daemon-reload
で変更を反映したあと、
$ systemctl --user restart apt-update.service
で再実行して、再度様子を確認してみます。
(注意点として、systemctl --user status
やjournalctl --user
の結果には、前回の失敗時のログも混ざっています。時刻で判断しましょう。)
$ systemctl --user status apt-update.service
○ apt-update.service - Apt Update
Loaded: loaded (/home/ubuntu/.config/systemd/user/apt-update.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Jul 08 14:17:48 ip-172-31-1-136 systemd[1353]: apt-update.service: Failed at step EXEC spawning /usr/bin/sudoo: No such file or directory
Jul 08 14:17:48 ip-172-31-1-136 systemd[786]: apt-update.service: Main process exited, code=exited, status=203/EXEC
Jul 08 14:17:48 ip-172-31-1-136 systemd[786]: apt-update.service: Failed with result 'exit-code'.
Jul 08 14:17:48 ip-172-31-1-136 systemd[786]: Failed to start Apt Update.
# ここから下が今回の再実行のログ(実際は境目はないので、時刻の部分で判断する)
Jul 08 14:27:43 ip-172-31-1-136 systemd[786]: Starting Apt Update...
Jul 08 14:27:43 ip-172-31-1-136 sudo[1421]: ubuntu : PWD=/home/ubuntu ; USER=root ; COMMAND=/usr/bin/apt update -y
Jul 08 14:27:43 ip-172-31-1-136 sudo[1421]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
Jul 08 14:27:45 ip-172-31-1-136 sudo[1421]: pam_unix(sudo:session): session closed for user root
Jul 08 14:27:45 ip-172-31-1-136 systemd[786]: Finished Apt Update.
Jul 08 14:27:45 ip-172-31-1-136 systemd[786]: apt-update.service: Consumed 1.503s CPU time.
1-4. ブート後に実行されるサービスとして設定
問題なさそうであれば、enable
します:
$ systemctl --user enable apt-update.service
Created symlink /home/ubuntu/.config/systemd/user/default.target.wants/apt-update.service → /home/ubuntu/.config/systemd/user/apt-update.service.
状態を確認してみましょう。特に、list-dependencies
の一覧に作成したapt-update.serviceが加わっていることが重要です:
-
status
$ systemctl --user status apt-update.service ○ apt-update.service - Apt Update Loaded: loaded (/home/ubuntu/.config/systemd/user/apt-update.service; enabled; vendor preset: enabled) Active: inactive (dead) # Loaded行の、ユニットファイルのパスの次が、enabledになっている
-
is-enabled
$ systemctl --user is-enabled apt-update.service enabled
-
list-dependencies
:大事!$ systemctl --user list-dependencies default.target ○ ├─apt-update.service # ここに追加されている! ● └─basic.target ● ├─paths.target ● ├─sockets.target ● │ ├─dbus.socket ● │ ├─dirmngr.socket ● │ ├─gpg-agent-browser.socket ● │ ├─gpg-agent-extra.socket ● │ ├─gpg-agent-ssh.socket ● │ ├─gpg-agent.socket ● │ ├─pk-debconf-helper.socket ● │ └─snapd.session-agent.socket ● └─timers.target
1-5. サーバを再起動して、動作確認
enabledになっていることを確認したら、実際にサーバを再起動してみましょう:
$ sudo reboot
再起動後ログインし、実行結果を確認してみましょう:
$ systemctl --user status apt-update.service
○ apt-update.service - Apt Update
Loaded: loaded (/home/ubuntu/.config/systemd/user/apt-update.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Fri 2022-07-08 15:03:04 UTC; 1min 17s ago
Process: 796 ExecStart=/usr/bin/sudo /usr/bin/apt update -y (code=exited, status=0/SUCCESS)
Main PID: 796 (code=exited, status=0/SUCCESS)
CPU: 4.351s
Jul 08 15:02:57 ip-172-31-1-136 systemd[789]: Starting Apt Update...
Jul 08 15:02:58 ip-172-31-1-136 sudo[796]: ubuntu : PWD=/home/ubuntu ; USER=root ; COMMAND=/usr/bin/apt update -y
Jul 08 15:02:58 ip-172-31-1-136 sudo[796]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
Jul 08 15:03:04 ip-172-31-1-136 sudo[796]: pam_unix(sudo:session): session closed for user root
Jul 08 15:03:04 ip-172-31-1-136 systemd[789]: Finished Apt Update.
Jul 08 15:03:04 ip-172-31-1-136 systemd[789]: apt-update.service: Consumed 4.351s CPU time.
$ journalctl --user -xeu apt-update.service
# ページャが開く(qキーで閉じる)
env
コマンドの結果を、~/record_env_info.txtというファイルに追記するサービスの実装
2. 実行時刻とsystemdユーザインスタンス自動実行時のPATH
やらなんやらを確認したい、というモチベーションのサービス、ということにします: サービス内で環境変数を使う際の注意点 < やり方要点まとめ
1. sudo apt update -y
するサービスの実装と同じフローの部分は省略気味にします。
2-1. ユニットファイルの作成
まず、ユニットファイル「~/.config/systemd/user/record-env-info.service」を作成します。
ポイントは、
-
Type=onshot
にした[4:2] -
実行コードは、ワンライナーで収まらないので、シェルスクリプトにした
-
実行スクリプトは絶対パスで指定した[2:4]
-
WantedBy=default.target
[Unit]
Description=Record env Information
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/record_env_info.sh
[Install]
WantedBy=default.target
2-2. シェルスクリプトの作成
次に、呼び出すシェルスクリプト「/usr/local/sbin/record_env_info.sh」を作成します。
/usr/local/sbin[7:1]配下のファイルなので、sudo
つきでエディタを起動する必要があります。
(~配下などで一旦作成して、sudo mv
してもよいです。)
#!/bin/bash
RECORD_STR="[$(date "+%Y/%m/%d %H:%M:%S")]\n$(env)"
echo -e $RECORD_STR >> ~/record_env_info.txt
一応、実行権限の設定をしておきます。
$ sudo chmod 755 /usr/local/sbin/record_env_info.sh
$ ls -l /usr/local/sbin/record_env_info.sh
-rwxr-xr-x 1 root root 110 Jul 9 02:03 /usr/local/sbin/record_env_info.sh
2-3. ユーザユニットファイルの変更を反映
daemon-reload
サブコマンドで、ユニットファイルの変更を反映します:
# ユーザユニットファイルの変更を反映
$ systemctl --user daemon-reload
状態確認 (status, is-enabled, list-dependencies)
# status
$ systemctl --user status record-env-info.service
○ record-env-info.service - Record env Information
Loaded: loaded (/home/ubuntu/.config/systemd/user/record-env-info.service; disabled; vendor preset: enabled)
Active: inactive (dead)
# is-enabled
$ systemctl --user is-enabled record-env-info.service
disabled
# (ついでに確認)現時点では「record-env-info.service」はログイン直後の実行サービスに含まれていない
$ systemctl --user list-dependencies
default.target
○ ├─apt-update.service
● └─basic.target
● ├─paths.target
● ├─sockets.target
● │ ├─dbus.socket
● │ ├─dirmngr.socket
● │ ├─gpg-agent-browser.socket
● │ ├─gpg-agent-extra.socket
● │ ├─gpg-agent-ssh.socket
● │ ├─gpg-agent.socket
● │ ├─pk-debconf-helper.socket
● │ └─snapd.session-agent.socket
● └─timers.target
2-4. 手動実行して動作確認
restart
(今回はType=oneshot
なので、start
でもよい)サブコマンドで、一度手動実行してみます。
$ systemctl --user restart record-env-info.service
成功した場合は、エラーが出ない他に、「~/record_env_info.txt」というファイルが作成されていることも確認できるはずです。
$ ls ~
record_env_info.txt
$ cat ~/record_env_info.txt
[2022/07/09 02:21:09]
SHELL=/bin/bash QT_ACCESSIBILITY=1 PWD=/home/ubuntu LOGNAME=ubuntu SYSTEMD_EXEC_PID=1393 HOME=/home/ubuntu LANG=C.UTF-8 INVOCATION_ID=e9b31fd383a9477b967ff9c75551cf8d MANAGERPID=808 USER=ubuntu SHLVL=0 XDG_RUNTIME_DIR=/run/user/1000 JOURNAL_STREAM=8:22384 XDG_DATA_DIRS=/usr/local/share/:/usr/share/:/var/lib/snapd/desktop PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus _=/usr/bin/env
ログ確認のコマンド
$ systemctl --user status record-env-info.service
$ journalctl --user -xeu record-env-info.service
# ページャが開く(qキーで閉じる)
2-5. ブート後に実行されるサービスとして設定
問題なさそうであれば、enable
します:
$ systemctl --user enable record-env-info.service
Created symlink /home/ubuntu/.config/systemd/user/default.target.wants/record-env-info.service → /home/ubuntu/.config/systemd/user/record-env-info.service.
status
, is-enabled
, list-dependencies
で、うまく設定できたかを確認しましょう。
確認 (status, is-enabled, list-dependencies)
-
status
$ systemctl --user status record-env-info.service ○ record-env-info.service - Record env Information Loaded: loaded (/home/ubuntu/.config/systemd/user/record-env-info.service; enabled; vendor preset: enabled) Active: inactive (dead) # Loaded行の、ユニットファイルのパスの次が、enabledになっている
-
is-enabled
$ systemctl --user is-enabled record-env-info.service enabled
-
list-dependencies
:大事!$ systemctl --user list-dependencies default.target ○ ├─apt-update.service ○ ├─record-env-info.service # ここに追加されている! ● └─basic.target ● ├─paths.target ● ├─sockets.target ● │ ├─dbus.socket ● │ ├─dirmngr.socket ● │ ├─gpg-agent-browser.socket ● │ ├─gpg-agent-extra.socket ● │ ├─gpg-agent-ssh.socket ● │ ├─gpg-agent.socket ● │ ├─pk-debconf-helper.socket ● │ └─snapd.session-agent.socket ● └─timers.target
2-6. サーバを再起動して、動作確認
enabledになっていることを確認したら、実際にサーバを再起動してみましょう:
$ sudo reboot
再起動後ログインし、実行結果を確認してみましょう:
$ systemctl --user status record-env-info.service
○ record-env-info.service - Record env Information
Loaded: loaded (/home/ubuntu/.config/systemd/user/record-env-info.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Sat 2022-07-09 02:51:30 UTC; 6s ago
Process: 800 ExecStart=/usr/local/sbin/record_env_info.sh (code=exited, status=0/SUCCESS)
Main PID: 800 (code=exited, status=0/SUCCESS)
CPU: 4ms
Jul 09 02:51:30 ip-172-31-1-136 systemd[792]: Starting Record env Information...
Jul 09 02:51:30 ip-172-31-1-136 systemd[792]: Finished Record env Information.
$ journalctl --user -xeu record-env-info.service
# ページャが開く(qキーで閉じる)
$ ls ~
record_env_info.txt
$ cat ~/record_env_info.txt
# 過去の実行時のデータ(省略)
# ...
[2022/07/09 02:51:30]
SHELL=/bin/bash QT_ACCESSIBILITY=1 PWD=/home/ubuntu LOGNAME=ubuntu SYSTEMD_EXEC_PID=800 HOME=/home/ubuntu LANG=C.UTF-8 INVOCATION_ID=c96d6336a9744ee4a6d51835c53f6ecc MANAGERPID=792 USER=ubuntu SHLVL=0 XDG_RUNTIME_DIR=/run/user/1000 JOURNAL_STREAM=8:18804 XDG_DATA_DIRS=/usr/local/share/:/usr/share/:/var/lib/snapd/desktop PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus _=/usr/bin/env
ついでに、このときのファイル「~/record_env_info.txt」の中身(env
コマンドの結果)から、systemdユーザインスタンス自動実行時のPATH
の状態なども確認できるので、よかったら眺めてみてください。
(~/.profileや~/bash_profile, ~/.bashrcに設定したPATH
は、反映されていないことがわかる: サービス内で環境変数を使う際の注意点 < やり方要点まとめ)
(おわり)
-
ログアウトしてもユーザプロセスは終了しないので、次回のユーザサービスが実行されるのは、サーバ再起動後のタイミングになります:ログアウト時にユーザープロセスを終了 < systemd/ユーザー (Arch Wiki) ↩︎ ↩︎
-
10.6. systemd のユニットファイルの作成および変更 (Red Hat Customer Portal) ↩︎ ↩︎ ↩︎
-
/usr/local : Local hierarchy (Filesystem Hierarchy Standard) ↩︎
-
Linux豆知識 214 「/bin」「/usr/bin」「/usr/local/bin」ディレクトリの使い分け (LinuC) ↩︎ ↩︎
Discussion