LSB(Linux Standard Base)を見ながらinitスクリプトを書いた


いつもそれっぽく適当に書いていたinitスクリプト。
いい加減ちゃんと書いてみようと思い [Linux Standard Base](http://refspecs.linuxfoundation.org/lsb.shtml) の [VIII. System Initialization](http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/tocsysinit.html) あたりを見ながら書いてみた。
でもAmazon Linuxでしか確認していない。

## initスクリプト例

全体はこんな感じ。
[20.8. Init Script Functions](http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptfunc.html) というのもあって、ある程度それに沿って実装されたのが `/etc/init.d/functions` かと思われるのでそれを使っている

```bash:/etc/init.d/myapp
#! /bin/sh

### BEGIN INIT INFO
# Provides: myapp
# Required-Start: $local_fs $network $remote_fs
# Should-Start: $time
# Required-Stop: $local_fs $network $remote_fs
# Should-Stop:
# Default-Start:  3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: start and stop MyApp
# Description: MyApp is a very fast and reliable application
#              used for illustrating init scripts
### END INIT INFO

. /etc/rc.d/init.d/functions

prog="myapp"
user="myusr"
cmd="/opt/$prog/ctrl.sh"
pid_file="/var/run/$prog/$prog.pid"
lock_file="/var/lock/subsys/$prog"
kill_delay=5


function start() {
  daemon --check $prog --pidfile $pid_file --user $user $cmd start
  RETVAL=$?
  [ $RETVAL -eq 0 ] && touch $lock_file
}

function stop() {
  killproc -p $pid_file -d $kill_delay $prog
  RETVAL=$?
  [ $RETVAL -eq 0 ] && rm -f $lock_file
}

function call() {
  $cmd $1
  RETVAL=$?
}

function p_status() {
  status $prog
  return $?
}


RETVAL=0

case "$1" in

  start)
    echo -n $"Starting $prog: "
    start
    echo
    ;;

  stop)
    echo -n $"Stopping $prog: "
    stop
    echo
    ;;

  restart|try-restart)
    echo -n $"Restarting $prog: "

    p_status > /dev/null 2>&1

    if [ $? -eq 0 ]; then
      call restart
    elif [ "$1" = "restart" ]; then
      call start
    else
      [ -z $LSB ] && RETVAL=0 || RETVAL=7 # Program is not running
    fi

    [ $RETVAL -eq 0 ] && success || failure
    echo
    ;;

  reload|force-reload)
    echo -n $"Reloading $prog: "
    call reload
    [ $RETVAL -eq 0 ] && success || failure
    echo
    ;;

  status)
    p_status
    RETVAL=$?
    ;;

  *)
    RETVAL=2  # Invalid or excess argument(s)
    ;;

esac

exit $RETVAL
```

## 冒頭のコメント

```bash:comment-convensions
### BEGIN INIT INFO
# Provides: myapp
# Required-Start: $local_fs $network $remote_fs
# Should-Start: $time
# Required-Stop: $local_fs $network $remote_fs
# Should-Stop:
# Default-Start:  3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: start and stop MyApp
# Description: MyApp is a very fast and reliable application
#              used for illustrating init scripts
### END INIT INFO
```

* [20.3. Comment Conventions for Init Scripts](http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/initscrcomconv.html) を見ながら設定した。

* [20.5. Run Levels](http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/runlevels.html) や [20.6. Facility Names](http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/facilname.html) (`$local_fs`,`$network`など)の説明も書いてあるので読んでみるといいかも。

* chkconfig 専用の書き方があるけど、このLSB形式のコメントも理解してくれる。

* プライオリティは指定できない。代わりにRequired-Start|Stopに他のファシリティを書く。
例えば ntpd が S58 の場合 Required-Start に ntpd を指定すれば S59 に起動するようになる。
指定するファシリティ名はそのサービスの Provides によって決まる。

```bash:priority
Required-Start: $local_fs $network $remote_fs ntpd
```

## $LSB変数

```bash:$LSB
LSB=1
```

* Amazon Linuxの `/etc/rc.d/init.d/functions` を見ていると $LSB によって多少動きが変わるようだ。
[OK] [FAILED] などが表示されなくなったり killproc が 7 (Program is not running)を返す場合があったり。

* まぁいいかな、と思ったのでここでは指定しないことにした。

## メッセージ

```bash:message
echo -n $"Starting $prog: "    # $prog を起動中:
echo -n $"Stopping $prog: "    # $prog を停止中:
echo -n $"Restarting $prog: "  # $prog を再起動中:
echo -n $"Reloading $prog: "   # $prog を再読み込み中:
```

* 上記のようにしとくとロケールごとのメッセージで表示される。コメント部分は日本語の場合。

## 起動処理

```bash:start
  daemon --check $prog --pidfile $pid_file --user $user $cmd start
```

* daemon 関数で起動する。

* --checkオプションを使うとPIDのチェックや表示でその値が使われる。
指定しないと $cmd から取得する。

* --userオプション付きだと `runuser` コマンドで起動する。
自動起動でも limits.conf あたりの設定が効く。
`/etc/pam.d/runuser` を見ると pam_limits.so を実行するようになっているからかな。

* デフォルトで `/var/run/$prog.pid` をPIDファイルとして使う。
また、PIDファイルを使ってプロセスの起動チェックをしているので二重起動を防止してくれる。

## 停止処理

```bash:stop
killproc -p $pid_file -d $kill_delay $prog
```

* killproc 関数で停止する。

* killレベルを指定しないので TERM → (待機) → KILL の順にシグナルが発行される。
-d オプションを指定することで待機時間を秒数で指定できる。

* daemon 関数と同じくデフォルトで `/var/run/$prog.pid` をPIDファイルとして使う。

* killレベルを指定しないとPIDファイルを消してくれる。
指定すると消してくれない。なぜ。

## 再起動処理

```bash:restart
p_status > /dev/null 2>&1

if [ $? -eq 0 ]; then
  call restart
elif [ "$1" = "restart" ]; then
  call start
else
  [ -z $LSB ] && RETVAL=0 || RETVAL=7 # Program is not running
fi
```

* restart と try-restart の違いは、起動していなかった場合の挙動。
restart は起動してたら再起動、起動してなかったら起動する。
try-restart は起動してたら再起動、起動してなかったら何もしない。

* try-restart で起動してなかった場合 $LSB がセットしてあったら [20.2. Init Script Actions](http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html) にあるように 7 を返すようにした。

## 再読み込み処理

* ここでは再読み込み処理が実装済みという前提。

* reload と force-reload の違いは、再読み込み処理が未実装の場合の挙動。
reload は実装してあれば reload 、未実装ならエラー。
force-reload は実装してあれば reload 、未実装かつ起動していれば restart する。

* reload が未実装の場合はこんな感じ？

```bash:reload
reload)
  echo -n $"Reloading $prog: "
  RETVAL=3    # Unimplemented feature
  failure
  echo
  ;;
force-reload)
  echo -n $"Reloading $prog: "

  p_status > /dev/null 2>&1

  if [ $? -eq 0 ]; then
    call restart
  elif [ -z $LSB ]
    RETVAL=0
  else
    RETVAL=7  # Program is not running
  fi

  [ $RETVAL -eq 0 ] && success || failure
  echo
  ;;
```