Linux の任意のスクリプトをサービス登録し OS 起動時に自動起動させる [init.d/SystemD 編]

> この記事は、[前回の記事](https://qiita.com/KEINOS/items/abb5985a7c15081ad22e)のフォローアップ[^followup] 記事です。
>
> - <sub>前回の記事: 「[スクリプト をサービス登録するテンプレート [`init.d` 編]](https://qiita.com/KEINOS/items/abb5985a7c15081ad22e)」 @ Qiita </sub>

## 「お前もデーモン登録してやろうか」<sub><sup>〜俺様スクリプトのデーモン化〜</sup></sub>

この記事では、**`init.d` もしくは `SystemD` に「自作スクリプト(アプリ)を登録し、サービスとして自動起動する」までの手順**を記載しています。

[前回](https://qiita.com/KEINOS/items/abb5985a7c15081ad22e)の `init.d` の登録の仕方の記事を読んで、`SystemD` でも自作スクリプトやアプリをデーモナイズ（[デーモン](https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%83%A2%E3%83%B3_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2))化／サービス化）したいと思われたら参考ください。また、念のため `init.d` の登録方法についても言及しているため、若干前回と重複します。

:::note info
🐒 &nbsp; 本記事は <a href="#fn2">Dasher</a> の著者から許可をいただいたので、Wiki にある「<a href="https://github.com/maddox/dasher/wiki/Running-Dasher-on-a-Raspberry-Pi-at-startup" target="_blank">Running Dasher on a Raspberry Pi at startup</a>」を翻訳する予定でした。

ところが、ちんたらしてたら <a href="https://github.com/maddox/dasher" target="_blank">Dasher の開発自体が終了</a>してしまいました。とほほ。

そのため Dasher に特化しない<strong>汎用的な記事として新たに書き起こしました</strong>が、本記事は<a href="https://github.com/maddox/dasher/blob/master/LICENSE">同じ MIT ライセンス</a>とします。また、追記／誤記／誤字などありましたら遠慮なく<a href="https://qiita.com/KEINOS/items/f3e6b3064b0cbe35fd03/edit">編集リクエスト</a>ください。
- 参考文献：「<a href="https://github.com/maddox/dasher/wiki/Running-Dasher-on-a-Raspberry-Pi-at-startup">Running Dasher on a Raspberry Pi at startup</a> | Dasher」@ GitHub

:::

- 併せて読みたい
    - [Debian 12.8 に netatalk をインストールした際の Systemd Service Fileの書き方](https://qiita.com/KEINOS/items/f3e6b3064b0cbe35fd03#comment-3a4dd336a41faab50005) @ コメント

## init.d と SystemD の違い

`init.d`（もしくは `SystemV init`）の方が古く、`SystemD` の方が新しい仕組みです。共通することを先に説明します。

### 共通点

基本的に `init.d` の `init` プログラム、`SystemD` の `systemd` プログラムは、どちらも **OS が最初に起動するプログラム**です。

そして、どちらも「[デーモン](https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%83%A2%E3%83%B3_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2))」として常駐し、サービス（常駐アプリ）となるプログラムやスクリプトの「起動」「終了」「再起動」などを管理したり、他のプロセスを監視する点でも同じです。親となるプロセスの総本山とも言えるものです。

そのため、**このプログラム自体が起動できないとカーネル・パニックを起こします**。

:::note info
読み違えやすいのが、この**全ての親プロセスを管理するプログラムを `INIT` システムと総称したりもする**ことです。つまり、`SystemD` のことを `INIT` システムと呼ぶことがあります。そのため `init.d` の `init` と間違えないように、`init.d` を `SystemV init` と明示することもあります。
:::

また、どちらも「サービスごと」に設定ファイルを用意しておき、専用のディレクトリに設置します。その後、それらをサービス登録することでサービスとしての利用が可能になります。

「サービス登録」と言っても、登録コマンドを叩くと設置したファイルのシンボリック・リンクがサービス起動用のディレクトリに作られるだけです。そして `init.d` もしくは `systemd` は起動時に、それらシンボリックリンクを順に起動して行きます。

また、「サービスとしての利用」と言っても、OS 起動後の自動起動や、`start` や `stop` コマンドなどによる管理ができると言うだけです。

:::note info
**「デーモン」と「サービス」**

ぶっちゃけ「デーモン」と「サービス」、どちらも同じものです。

プロセス（メモリ上で展開している処理）のうち、一度起動すると終了シグナルを受け取るまで、何かしらのサービスを提供しつづけるタイプです。

「デーモン」（`daemon`）とは `demon`（悪魔）の古語で、常に存在しているが姿が見えず、良くも悪くも振る舞う、超自然的な精霊のような存在のことです。

深く悩まずに、Linux や macOS など UNIX 寄りの表現がデーモン、Windows 寄りの表現がサービス程度の認識でいいと思います。

Linux の記事なので、正しくは「デーモン」で統一すべきなのですが、恐らくこの記事が必要なユーザーは Windows や macOS ユーザーが多いと思うので、把握しやすいように「サービス」という言い方をしています。

ここで言う「サービス」は、いわゆる「ただの常駐アプリ」ではなく、OS が起動でき、終了・再起動といったシグナルも受け取ることを前提とした常駐アプリを指します。
:::

### initd と systemd の違い

`init.d` と `SystemD` の一番の違いは、サービスごとに必要な<strong>「設定ファイル」の中身</strong>です。

`init.d` の本体となる `init` プログラム自体は、以下のシンプルな動作しか基本的にできません。

- OS 起動時に、登録されたサービスの設定ファイルを `start` の引数を付けて実行する。
- OS シャットダウン時にはサービスの設定ファイルを `stop` の引数を付けて実行する。

ここで注目して欲しいのが「実行する」の部分です。

設定ファイルなのに「実行する」と言うのも、実は<strong><font color="darkorange">`init.d` のサービス設定ファイルは「実質的にシェルのスクリプト」</font>です</strong>。

`init.d` の設定ファイルの内容を見るとわかりますが、起動・終了などの処理が、普通にシェル・スクリプトで `start` `stop` の引数によって俺様アプリの起動と `kill` を実行していることが確認できます。

<details><summary>例）cron サービスの設定ファイルの場合</summary><div><br>

以下は RaspberryPi OS の <code>cron</code> サービスの設定ファイルです。

ここでは詳しく見る必要はなく、スクリプト末尾にある `case "$1" in` のセクションに注目してください。スクリプトの第 1 引数（`$1`）によって実行内容を変えているのがわかります。

```sh:/etc/init.d/cron
#!/bin/sh
# Start/stop the cron daemon.
#
### BEGIN INIT INFO
# Provides:          cron
# Required-Start:    $remote_fs $syslog $time
# Required-Stop:     $remote_fs $syslog $time
# Should-Start:      $network $named slapd autofs ypbind nscd nslcd winbind sssd
# Should-Stop:       $network $named slapd autofs ypbind nscd nslcd winbind sssd
# Default-Start:     2 3 4 5
# Default-Stop:
# Short-Description: Regular background program processing daemon
# Description:       cron is a standard UNIX program that runs user-specified 
#                    programs at periodic scheduled times. vixie cron adds a 
#                    number of features to the basic UNIX cron, including better
#                    security and more powerful configuration options.
### END INIT INFO

PATH=/bin:/usr/bin:/sbin:/usr/sbin
DESC="cron daemon"
NAME=cron
DAEMON=/usr/sbin/cron
PIDFILE=/var/run/crond.pid
SCRIPTNAME=/etc/init.d/"$NAME"

test -f $DAEMON || exit 0

. /lib/lsb/init-functions

[ -r /etc/default/cron ] && . /etc/default/cron

# Read the system's locale and set cron's locale. This is only used for
# setting the charset of mails generated by cron. To provide locale
# information to tasks running under cron, see /etc/pam.d/cron.
#
# We read /etc/environment, but warn about locale information in
# there because it should be in /etc/default/locale.
parse_environment () 
{
    for ENV_FILE in /etc/environment /etc/default/locale; do
        [ -r "$ENV_FILE" ] || continue
        [ -s "$ENV_FILE" ] || continue

         for var in LANG LANGUAGE LC_ALL LC_CTYPE; do
             value=`egrep "^${var}=" "$ENV_FILE" | tail -n1 | cut -d= -f2`
             [ -n "$value" ] && eval export $var=$value

             if [ -n "$value" ] && [ "$ENV_FILE" = /etc/environment ]; then
                 log_warning_msg "/etc/environment has been deprecated for locale information; use /etc/default/locale for $var=$value instead"
             fi
         done
     done

# Get the timezone set.
    if [ -z "$TZ" -a -e /etc/timezone ]; then
        TZ=`cat /etc/timezone` 
    fi
}

# Parse the system's environment
if [ "$READ_ENV" = "yes" ] ; then
    parse_environment
fi


case "$1" in
start)	log_daemon_msg "Starting periodic command scheduler" "cron"
        start_daemon -p $PIDFILE $DAEMON $EXTRA_OPTS
        log_end_msg $?
	;;
stop)	log_daemon_msg "Stopping periodic command scheduler" "cron"
        killproc -p $PIDFILE $DAEMON
        RETVAL=$?
        [ $RETVAL -eq 0 ] && [ -e "$PIDFILE" ] && rm -f $PIDFILE
        log_end_msg $RETVAL
        ;;
restart) log_daemon_msg "Restarting periodic command scheduler" "cron" 
        $0 stop
        $0 start
        ;;
reload|force-reload) log_daemon_msg "Reloading configuration files for periodic command scheduler" "cron"
	# cron reloads automatically
        log_end_msg 0
        ;;
status)
        status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
        ;;
*)	log_action_msg "Usage: /etc/init.d/cron {start|stop|status|restart|reload|force-reload}"
        exit 2
        ;;
esac
exit 0
```

---

</div></details>

このように、`init.d` の設定ファイルはスクリプトであるため、挙動を柔軟に制御できる反面、スクリプトに問題があっても気付きにくいなど、いささか複雑です。しかも、下手するとカーネル・パニックを起こします。

対して、<font color=darkorange>**`SystemD` のサービス設定ファイルは「設定のみが記載されたファイル」**</font>です。

起動（`start`）・終了（`stop`）などの処理は、本体である `systemd` に組み込まれているため、設定ファイルはサービスの起動に必要な情報だけで済みます。

つまり、<strong>`SystemD` のサービス設定ファイルは、本当の意味で「設定<ruby>だけ<rt>・・</rt></ruby>のファイル」</strong>です。スクリプトよりは自由度は低いものの、記載がシンプルなぶん、安定した柔軟性があるという違いがあります。

<details><summary>例）sshd サービスの設定ファイルの場合</summary><div><br>

以下は RaspberryPi OS の `sshd` サービスの設定ファイルです。

ここでは何をしているかよりも、パラメーターしか設定していないことに注目です。

```ini:/etc/systemd/system/sshd.service
[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
Alias=sshd.service
```

---

</div></details>

:::note info
実は、[SystemD](https://ja.wikipedia.org/wiki/Systemd) はプロセス管理以上の処理（プロセス間通信、ログインやネットワークの起動時処理など）も担えます。そのため、ユーザ・メリットが多くデフォルトの `INIT` システム（最上位プロセス）として `SystemD` を採用している[ディストロ](https://ja.wikipedia.org/wiki/Linux%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)は多いです。

しかし `init.d`（`SystemV init`）に比べて `SystemD` は多機能な反面、依存も多いので「1 つのことを行い、またそれをうまくやるプログラム」という [Unix 哲学](https://ja.wikipedia.org/wiki/UNIX%E5%93%B2%E5%AD%A6)に反するとして `init.d` 系（`SystemV init` 系）を好むディストロも多くあります。

特に、使い勝手（多機能性や利便性）より、堅牢性を重視したディストロ（例えば Alpine Linux など）では [OpenRC](https://ja.wikipedia.org/wiki/OpenRC) という、シンプル・軽量かつ堅牢な `INIT` を採用していたりします。そのぶん `SystemD` が担っていた処理を手前で行う必要があるため、より Linux の知識を必要とします。

とは言え、<font color=darkorange>**この違いは「良い」「悪い」ではなくポリシーの問題**</font>です。

かく言う Alpine Linux も、[BusyBox](https://ja.wikipedia.org/wiki/BusyBox) という `cd` や `ls` などの本来個別である基本コマンドを 1 つのプログラムで動かせるようにしています。ロード時間のオーバーヘッド（読み込み回数の短縮）をカバーするものの、同様に「Unix 哲学うんぬん」と反発する人も多いです。

結局のところ、自分の好みにあったディストロが、どの `INIT` システムを採用しているかを把握して、まずは使ってみるのがベストだと思います。
:::

### `init.d` or `SystemD`

自分の OS が、どちらのデーモンをメインに利用しているか確認したい場合、１番目のプロセス ID を確認するのが簡単です。

```shell-session:PID1をPSコマンドで確認
$ ps auxq 1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME  COMMAND
root         1  0.0  0.6  28208  6044 ?        Ss    3月31  31:13 /sbin/init
```

上記の場合、`/sbin/init` が PID 1 で実行されているため一般的には `init.d` が使われていることになります。

しかし、<font color=red>OS によって注意</font>する必要があります。RaspberryPi OS（旧 Raspbian）などの Debian/Ubuntu 系の**一部の OS では両方が使われていることがある**からです。

RaspberryPi OS で具体的に確認してみてみましょう。

まずは、`ps auxq 1` コマンドで確認すると 1 番プロセス（`PID 1`）の実行プログラムは `/sbin/init` であることが確認できます。

```shell-session
$ # PID1をPSコマンドで確認
$ ps auxq 1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME  COMMAND
root         1  0.0  0.6  28208  6044 ?        Ss    3月31  31:13 /sbin/init
```

次に `init` の本体のパスである `/sbin/init` のファイルの詳細を `ls -l` で見てみます。シンボリック・リンクで、本体は別であることに注目です。

```shell-session
$ # init は systemd のシンボリック・リンクになっている
$ ls -l /sbin/init
lrwxrwxrwx 1 root root 20  7月 22  2019 /sbin/init -> /lib/systemd/systemd
```

このように、`/sbin/init` の実体は `/lib/systemd/systemd` なのがわかります。

つまり<font color="darkorange">**「`init` と言いながら `systemd` が動いている」と言うこと**</font>です。この場合、OS は `init.d` を呼び出す（実行する）も実質的に `systemd` に処理を任せていることになります。
　
この `init` の皮をかぶった `systemd` ですが、まず `init.d` 向けのサービスを替わりに起動します。次に、`SystemD` 向けのサービスを起動することになります。そのため、このような `systemd` が `init` を兼任して両方が使える OS があるので注意します。

逆に言えば、この仕組み（`init.d` `systemd` 併用）の場合は、どちらのサービス設定ファイルを設置しても動くと言うことです。

どちらも使える OS の場合に、どちらの設定ファイルのサービスにしようか悩んだら、「起動するだけで十分」なら `SystemD`、「起動前後にゴニョゴニョと処理をしたい」なら `init.d` が良いと思います。

- 「[init](https://ja.wikipedia.org/wiki/Init)」@ Wikipedia ／「[systemd](https://ja.wikipedia.org/wiki/Systemd)」@ Wikipedia
- 「[What is the difference between /etc/init/ and /etc/init.d/?](https://askubuntu.com/questions/5039/what-is-the-difference-between-etc-init-and-etc-init-d) | Ask Ubuntu」@ StackExchange

---

## 俺様アプリの用意

`init.d` にせよ `SystemD` にせよ、まずは対象となる俺様アプリ（実行ファイルもしくはスクリプト）を事前に用意する必要があります。

:::note info
<sub><sup>🐒 &nbsp; 本記事は「スクリプトをサービス（<a href="https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%83%A2%E3%83%B3_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)" target="_blank">デーモン</a>）として常駐させる」ことを目的としています。そのため、<strong>起動時に１回だけ実行させたい</strong>のであれば <a href="https://www.google.com/search?q=site%3Aqiita.com+cron+crontab+%E8%B5%B7%E5%8B%95%E6%99%82+%E5%AE%9F%E8%A1%8C+%7B1%E5%9B%9E+%E4%B8%80%E5%BA%A6+%E4%B8%80%E5%9B%9E%7D"><code>crond</code> にスクリプトを登録</a>（<code>crontab</code> に <code>@reboot /path/to/myScript.py</code> などと記載）したほうが楽だと思います。一回きりの起動であれば、Raspbian/Debian ですが下記記事が参考になります。</sup></sub>
- <sub><sup><a href="https://qiita.com/karaage0703/items/ed18f318a1775b28eab4">Raspberry Piでプログラムを自動起動する5種類の方法を比較・解説</a> @ Qiita</sup></sub>
:::

他のサービス同様に「`start`」や「`stop`」（`/etc/init.d/myService start` や `/etc/init.d/myService stop`）などで起動・終了できるようにしたい場合は、本記事を参考にしてください。

<details><summary>俺様サービス作成時の<font color=red>注意事項</font></summary><div><br>

俺様サービスを Linux で作りたい場合、プログラム言語は問いません。つまり Bash でも PHP でも Go でも Python でも俺様サービスを作ることは可能です。

しかし、最低限以下の点を網羅する必要があります。

1. コマンドラインから呼び出し／実行できる状態であること。
2. プログラムは、呼び出されると中断シグナルを受け取るまで常駐（実行）すること。
3. メモリの割り当てに注意すること。

まずは、1 の「呼び出し／実行できる状態」とは、ターミナルやコマンドラインから叩けば無限ループで実行される状態ということです。

`$ python /path/to/myScript.py` `$ php /path/to/myScript.php` `$ ./path/to/myGoApp` などです。`shebang`[^shebang] を付けて `$ /path/to/myScript.rb` などと直接起動できるタイプでもかまいません。、**コマンドを叩くと起動できるようになっている**ということです。

そして 2 番目の「呼び出されると常駐する」とは、実行されたら処理を終了させないようにループなどで継続させる必要があるということです。Web　サービスのアプリなどがわかりやすいでしょうか。

最後の「メモリの割り当てに注意」とは、使用メモリが無駄に肥大化しないように注意して設計する、ということです。プログラムの組み方やプログラム言語にも依存するのですが。

例えば、**ループ内でメモリを新規確保し続けない**などです。メモリがどんどん肥大化していくからです。

また、カウンターなど永遠とカウントアップさせていて、気付いたら（変数が）とんでもない数値になっていた、みたいなことにならないように注意します。

<h3>【<font color=red>注意点</font>】 </h3>

プロセスを終了させないことで常駐させるには、基本的に `while(true){}` などの無限ループでスクリプトが終了しないようにすると思います。

この時、注意すべき基本が2つあります。

1. 終了/中断シグナルの監視を忘れない
2. `sleep` を入れることも検討する

通常、無限ループのスクリプトを強制終了させるには `Ctrl+C` などの `SIGINT`（２番シグナル）がスクリプトのプロセスに送られます。

しかし、OS の再起動時、シャットダウン時もしくは手動によるサービス停止などの場合に意図しないシグナルが送られることがあるので注意します。

例えば、**サービスが停止されると `kill <サービスの PID>` コマンドの `SIGTERM`（１５番シグナル）や `SIGKILL`（９番シグナル）が実行中のスクリプトに送信されます**。

そのため、ループ内での「終了シグナルの検知処理」などにも注意する必要があります。

特に、PHP の `register_shutdown_function()` や Golang の `defer` などの**終了時に実行される関数は、`SIGINT` や `SIGTERM` による終了の場合、発火（実行）されません**。ループ内でシグナルをチェックする必要があります。

この処理を意識しないと、ちゃんとシャットダウン・コマンドを実行して再起動しているのに、スクリプトからみると「急に PC の電源を引っこ抜かれた」時と同じ状態になります。「ファイルを閉じる」や「別プロセスを終了させる」などの処理が必要な場合は特に注意です。

その場合、例えば PHP の場合はプロセスのシグナルを検知・コントロールする <code><a href="https://www.php.net/manual/ja/pcntl.example.php" target="_blank">pcntl_signal</a></code>を使う、などの工夫が必要です。

他にも注意しておく点として、`sleep` があります。

ループ内で処理が発生しない場合、すぐに `continue` をせずに `sleep` などのクッションを入れることも検討してください。

```php
while(true) {
    if (isSomething() === false) {
        continue; // ここで超高速な無限ループが発生しているため、他のプロセスが割り込む余地がない
    }
    doSomething();
}
```

上記のように、入力や状態監視をする際に、ループが高速すぎるため CPU 負荷が上がってしまうことがあります。

この場合、`sleep` を一瞬でも入れることで、かなり CPU 負荷が減ります。

```diff
  while(true) {
      if (isSomething() === false) {
+         usleep(20000);
          continue;
      }
      doSomething();
  }
```
- 関連情報
    - [「終了シグナル 受け取り SIGTERM」の Qiita 記事検索](https://www.google.com/search?q=site:qiita.com+%E7%B5%82%E4%BA%86%E3%82%B7%E3%82%B0%E3%83%8A%E3%83%AB+%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8A+SIGTERM) @ Google
    - [Docker で CPU 使用率 100% のコンテナを軽減・回避する](https://qiita.com/KEINOS/items/df3474624efec3da539f) @ Qiita

---

</div></details>

登録したいプログラム／スクリプトを用意した次に必要なのが、そのスクリプトをサービスとして登録するための設定ファイルの作成とインストール（設定ファイルの適用）です。

## init.d で起動時に任意のスクリプトをサービス実行する

RaspberryPi（Raspbian Jessie）などの [System V 系の `init`](https://ja.wikipedia.org/wiki/Init#System_V%E7%B3%BB%E3%81%AEinit) で登録する場合は、本項をご覧ください。`SystemD` で登録したい場合は[次項](#systemd-%E3%81%A7%E8%B5%B7%E5%8B%95%E6%99%82%E3%81%AB%E4%BB%BB%E6%84%8F%E3%81%AE%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B)で説明しています。

### サービス設定ファイル「init スクリプト」

`init` のサービス用設定ファイルと言っても、前述したように実態は `sh` などの `shebang` 付きのシェル・スクリプトです。

その設定ファイル（スクリプト）が実行される際に `start` 引数が付いていたら自分のプログラム（登録したプログラム／スクリプト）を起動し、`stop` 引数が付いていたらプロセスを探して終了させるだけのスクリプトです。意外に力技な仕組みなのです。

普通のシェル・スクリプトとの違いは、呼び出し元の `init` プログラムに準拠したヘッダー情報（コメント行）および必須機能を含んでいることです。以降は、このサービス用設定ファイルを `init` スクリプトと呼びます。

インストールは、必要事項を記載した `init` スクリプトを `/etc/init.d/` に設置し、登録コマンド `update-rc.d` を実行することで行われます。

「登録コマンド」と言っても、具体的には `/etc/rc[0-9S].d/` のディレクトリ内にエイリアス（シンボリック・リンク）を張るだけです。そして、`init` プログラムが起動すると `/etc/rc[0-9S].d/` にあるファイルを順番に実行して行きます。

`init` スクリプト（サービス設定ファイル）は、他のサービスを参考にしても良いのですが、[Felix H. Dahlke 氏](https://github.com/fhd) が [GitHub で無償で提供しているテンプレート](https://github.com/fhd/init-script-template)を使うと便利です。気に入ったら「いいね（「★Star」）」してあげてください。

なお、このテンプレートの使い方の詳細は前述の「[スクリプト をサービス登録するテンプレート[init.d編]](https://qiita.com/KEINOS/items/abb5985a7c15081ad22e)」@ Qiita をご覧ください。

#### `Dasher` を `init.d` でサービス登録する例

それでは `init.d` に登録する例として、`dasher`[^dasher] というコマンド・アプリをラズパイの `init.d` にサービスとして登録したいと思います。

何度か言及していますが、起動したら `Ctrl+C` の押下もしくはプロセスを `kill` するまで動き続けるスクリプト or アプリであれば、プログラム言語問わず何でも登録可能です。`dasher` を自身のスクリプト名に置き換えてお読みください。（詳しくは上記の「スクリプト作成時の注意点」をご覧ください。）

1. [テンプレートの生データ](https://raw.githubusercontent.com/fhd/init-script-template/master/template)を開き全体をコピーする。
2. ラズパイ上の `/etc/init.d/` ディレクトリ内にファイルを作成する。（ファイル名はサービス名と同じにします。今回は `dasher`）
3. `sudo` 権限でファイルを開きペーストします。（`nano` でも `vim` でも可）

```shell-session
$ # ラズパイに SSH 接続する
$ ssh pi@raspberrypi.local
$ 
$ # sudo 権限でテキスト・エディタを開き dasher ファイルにコピー内容をペースト
$ sudo nano /etc/init.d/dasher 
[クリップボードの内容を貼り付ける]
```

次に、ファイルの先頭にあるヘッダー情報を変更する必要があります。以下が一例です。

```sh
#!/bin/sh
### BEGIN INIT INFO
# Provides:          dasher
# Required-Start:    $network $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start dasher daemon at boot time
# Description:       Enable daemon service
### END INIT INFO

dir="/home/pi/dasher"
cmd="npm run start"
user="root"
```

`dir` の項目に注目ください。これは、作業ディレクトリを `dir` に変更します。ここでは `Dasher` の本体が `/home/pi/dasher/` ディレクトリに設置 or インストール済みであることを前提としています。

`cmd` は実際の実行コマンドで、`user` は実行するユーザーです。セキュリティの問題で `root` 以外のユーザーに設定することもできます。

どのユーザーであれ、ユーザーの環境変数は読み込まないことに注意します。つまり、<font color=darkorange>**実行ユーザーの環境変数に追加したパスなども通っていない**</font>ので、絶対パスで指定するか、実行スクリプト内で読み込む必要があります。

さて、今回使ったエディタは `nano` であるため、保存するには `CTRL + o` と入力してから `Enter` を押し、`CTRL + x` でエディタを終了します。

次に、ファイルのパーミッションを変更してスクリプトを 「インストール」（登録）します。

```shell-session
$ sudo chmod 755 /etc/init.d/dasher
$ sudo update-rc.d dasher defaults
```

以上でラズパイが再起動したときに実行されるはずです。他にも以下ように手動で起動することもできます。

```shell-session
$ sudo /etc/init.d/dasher start
```

実行中のログを表示するには、ログやエラーファイルを開いて確認します。ログはファイルの末尾に追加されていくので、最新のログを確認したい場合は `tail` コマンドで出力ログまたはエラーログを表示します。

```shell_session
$ tail -f /var/log/dasher.log
$ tail -f /var/log/dasher.err
```

## SystemD で起動時に任意のスクリプトをサービス実行する

`init.d` 同様に、`SystemD` でサービス登録する場合も登録用の「SystemD Unit ファイル」の作成が必要です。

同じく、例として `dasher`[^dasher] というコマンド・アプリをサービスとして登録したいと思います。

1. 下記 Unit ファイルのサンプルをコピーする。

    ```ini:SystemDのサービス登録用のテンプレート（Unitファイル）
    [Unit]
    Description=Dasher
    After=network.target
    
    [Service]
    Type=simple
    # dasher のファイルにアクセスするユーザー
    User=root
    # dasher の本体および作業ディレクトリ
    WorkingDirectory=/home/pi/dasher
    # "which npm"コマンドで NPM の絶対パスを確認します
    ExecStart=/usr/local/bin/npm run start
    
    
    [Install]
    WantedBy=multi-user.target
    ```

2. ラズパイ上の `/etc/systemd/system/` ディレクトリ内に任意の `<サービス名>.service` のファイルを作成します。（今回は `dasher.service`）

3. `sudo` 権限でファイルを開きペーストします。（`nano` でも `vim` でも可）

    ```shell-session:ファイルの作成とエディタを開く
    $ # ラズパイに SSH 接続する
    $ ssh pi@raspberrypi.local
    $ 
    $ # sudo 権限でテキスト・エディタを開き dasher.service ファイルにコピー内容をペースト
    $ sudo nano /etc/systemd/system/dasher.service
    [クリップボードの内容を貼り付ける]
    ```
    今回使ったエディタは `nano` であるため、保存するには `CTRL + o` と入力してから `Enter` を押し、`CTRL + x` でエディタを終了します。

4. 登録した Unit の再読み込み、自動起動設定および手動起動を行います。

    ```shell-session
    $ sudo systemctl --system daemon-reload
    $ sudo systemctl enable dasher
    $ sudo systemctl start dasher
    ```

以上でサービスは起動し、再起動時にも起動するはずです。

すべての設定が正しく機能しているかどうかを確認するには、次のコマンドを使用します。

```shell-session:サービス起動の確認
$ sudo systemctl status dasher
```

```shell-session:サービスの実行ログを見る
$ sudo journalctl -f -u dasher
```

<!-- 注釈 -->

[^followup]: 【フォローアップ（Follow-up）とは】前回の不足分を補うための続報・追跡・追求・補足をすること。

[^shebang]: 【shebang（シバンまたはシェバン）とは】UNIX/linux のスクリプトの `#!` から始まる１行目のこと。スクリプトの実行に必要なインタプリタを指定します。`shebang` なしの場合の呼び出しは「`$ php /path/to/myScript.php`」となりインタプリタを指定する必要がありますが、`shebang` ありの場合（スクリプト内の１行目に`#!/usr/bin/env php` の記載がある場合）の呼び出しは「`$ /path/to/myScript.php`」とインタプリタの指定が不要になります。環境変数のパスに指定されたディレクトリに設置されたスクリプトは、スクリプト名の指定だけ（パスの指定なし）で実行するためには必須でもあります。

[^dasher]: 【Dasher とは】ネットワークを監視し、特定の MAC アドレス（物理アドレス）からのブロードキャスト信号を検知すると、Web Hook を叩く（任意の URL にアクセスする） Node.js ベースのアプリケーションのこと。市販の Amazon Dash Button（ADB）を合法な状態（物理的なクラッキングをせずに） IoT のトリガー（ボタン）として使えるようにしたパイオニア的アプリケーションです。現在は開発を終了しており、後継ではないものの Python ベースの「[Nekmo/amazon-dash](https://github.com/Nekmo/amazon-dash)」の利用が推奨されています。