WindowsのRunOnce
インフラ構築をコードで自動化していると、たまに以下のように、「システム起動時にプログラムを1回だけ自動実行」したくなることがあります。
- アンインストーラの削除(アンインストーラ自身で自分を削除できないので)
- 処理の途中で再起動が必要な場合、再起動後に残りの処理を自動実行する
Windowsなら「RunOnce」というレジストリキーにプログラムを登録するだけで実現できることですが、CentOS 7でどうやって実現するのかを検討しました。
CentOS 7 起動時にプログラムを自動実行
まず「1回だけ」を抜きにして、システム起動時にプログラムを自動実行する方法ですが、CentOS 7ではプログラムをサービスユニットとして登録して自動起動を有効化します。
普通のサービスは起動したプロセスがシステムに残りますが、実行して終了するだけのプログラムもサービスにすることができます。
サービスにするメリット
以下のような条件をユニットファイルに記述するだけで指定することができるようになります。
- プログラムの実行前に起動完了してほしいサービス
- 逆に、プログラムの実行が終了するまで起動してほしくないサービス
- プログラムで使用する環境変数
- プログラム終了後に実行してほしい処理
- 実行ユーザー
ユニットファイルには、ほかにも多くのオプションを指定することができます。
参考: SYSTEMD のユニットファイルの作成および変更
またsystemctl
コマンドを使って、他のサービスと同じ方法で操作することができるようになります。
参考: システムサービスの管理
ユニットファイル
ユニットファイルを以下の例のように作成し、/etc/systemd/system/
にユニット名+拡張子.service
で保存します。ユニット名は任意なので、以下の例ではautorun
としています。
[Unit]
After=network-online.target
[Service]
User=root
ExecStart=/root/myscript
[Install]
WantedBy=multi-user.target
-
Unit
セクション- プログラムを実行する前に起動完了してほしいサービスを
After=
で指定する - この例では
network-online.target
を指定しているので、プログラムの中でネットワークを使用することができる - 逆に実行が終了するまで起動してほしくないサービスがあれば、
Before=
で指定することもできる
- プログラムを実行する前に起動完了してほしいサービスを
-
Service
セクション- 実行するユーザーを
User=
で指定する - 自動実行するプログラムを
ExecStart=
で指定する - この例では
/root/myscript
というスクリプトを実行する
- 実行するユーザーを
-
Install
セクション- 起動する必要のあるターゲットを
WantedBy=
で指定する - この例では
multi-user.target
を指定しているので、CUI/GUIのマルチユーザー(ランレベル3または5)で起動する
- 起動する必要のあるターゲットを
サービス自動起動の有効化
以下のコマンドでサービスの自動起動を有効化します。
systemctl enable autorun.service
ファイルを確認すると、WantedBy=multi-user.target
と指定したのでmulti-user.target.wants/
ディレクトリにシンボリックリンクが作成されたことが分かります。
$ find /etc/systemd -name "autorun.*" -print
/etc/systemd/system/multi-user.target.wants/autorun.service
/etc/systemd/system/autorun.service
起動時に1回だけ実行 その1 - 実行後に自動起動を無効化
このままでは登録したプログラムが起動のたびに実行されてしまうので、設定後最初の起動時にだけ実行されるようにします。
2回目以降のシステム起動時に自動実行されなくするだけなら、ExecStart=
を以下のコードに変更するだけでも実現できます。
ExecStart=/bin/bash -c "/root/myscript \
&& /usr/bin/systemctl disable autorun.service"
ExecStart=
には複数のコマンドを指定できないので、bashを-c
オプションで実行することで、続く文字列を評価して実行する例です。
この例では、/root/myscript
の実行に成功したらsystemctl disable
コマンドでサービスの自動起動を解除しています。
逆に/root/myscript
の実行に失敗した場合は自動起動が解除されないので、成功するまで何度でもシステム起動時に実行されます。
再度ファイルを確認するとsystemctl enable
で生成されたシンボリックリンクが削除されているので、2回目からは自動実行されないようになりました。
$ find /etc/systemd -name "autorun.*" -print
/etc/systemd/system/autorun.service
起動時に1回だけ実行 その2 - サービスを削除
autorun.service
を今後も使用する予定があるならこのままでいいのですが、二度と使わないなら削除しないと気持ち悪いです。
ExecStart=
でbashに評価させる文字列に、ユニットファイルを削除する処理を追加しました。
ExecStart=/bin/bash -c "/root/myscript \
&& /usr/bin/systemctl disable autorun.service \
&& rm -f /etc/systemd/system/autorun.service"
もう一度systemctl enable autorun.service
で自動起動を有効化してから再起動すると、サービス自体が削除されたことが分かります。
$ find /etc/systemd -name "autorun.*" -print | wc -l
0
$ systemctl list-unit-files autorun.service
UNIT FILE STATE
0 unit files listed.
起動時に1回だけ実行 その3 - プログラムも削除
自動実行するプログラムも削除してしまえば、1回実行した後には何も残らなくなります。
ExecStart=/bin/bash -c "/root/myscript \
&& /usr/bin/systemctl disable autorun.service \
&& rm -f /etc/systemd/system/autorun.service /root/myscript"