Nuxt.jsのアプリケーションをinitd-foreverを使ってデーモン化、自動起動する

# 概要

Nuxt.jsのアプリケーションをサーバー環境にデプロイする際、Nuxt.jsのプロセスをバックグラウンドで起動して永続化するために、一般的にforeverを使う。
加えて、foreverをデーモン化して、serviceコマンドで[start, stop, status, restart]したり、chkconfigで自動起動しようと思ったら、initd-foreverを使う。

initd-foreverの記事はいくつか存在したけど、Nuxt.jsでそれを扱うという記事は見たところ存在しなかったので、今回はNuxt.jsのアプリケーションでinitd-foreverを導入する例を示す。

最終的には以下のような状態となる。

- Nuxt.jsアプリケーションがservice app_name startといった具合にserviceコマンドで起動、停止、状態確認、再起動できる
- Nuxt.jsアプリケーションをchkconfigでOS起動時に自動起動できる

#　環境

- Amazonlinux 1

# 準備

- nuxt.jsで作成したアプリケーションをサーバーにデプロイしておく

# 手順

## foreverをインストール

```
# npm install -g -y forever
```

## foreverでNuxt.jsを起動してみる

```
# cd /var/www/app_name/
# forever start -c "npm run start" ./
```

Nuxt.jsのアプリケーションの/(ルート)に移動してforever startを実行する。
-c はコマンドを指定するという意味で、ここではNuxt.jsの起動コマンドである、"npm run start"を指定している。これでforever経由でNuxt.jsの起動ができた。

## initd-foreverをインストール

```
# npm install -g initd-forever
```

## initd-foreverでデーモンファイルを生成

```
# cd /var/www/app_name/
# initd-forever -a /var/www/app_name/ -n app_name
Script daemon file saved to app_name
# ls
app_name
```

第一引数はNuxt.jsアプリケーションの/を指定、-n の引数はデーモンファイル名となる。カレントディレクトリにapp_nameというファイルが生成される。

ファイルの中身は以下のようになっている。

```bash

# cat /var/www/app_name/app_name

#!/bin/bash
### BEGIN INIT INFO
# If you wish the Daemon to be lauched at boot / stopped at shutdown :
#
#    On Debian-based distributions:
#      INSTALL : update-rc.d scriptname defaults
#      (UNINSTALL : update-rc.d -f  scriptname remove)
#
#    On RedHat-based distributions (CentOS, OpenSUSE...):
#      INSTALL : chkconfig --level 35 scriptname on
#      (UNINSTALL : chkconfig --level 35 scriptname off)
#
# chkconfig:         2345 90 60
# Provides:          /var/www/app_name/
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: forever running /var/www/app_name/
# Description:       /var/www/app_name/
### END INIT INFO
#
# initd a node app
# Based on a script posted by https://gist.github.com/jinze at https://gist.github.com/3748766
#

if [ -e /lib/lsb/init-functions ]; then
    # LSB source function library.
    . /lib/lsb/init-functions
fi;

pidFile="/var/run/app_name.pid"
logFile="/var/run/app_name.log"

command="node"
nodeApp="npm run start /var/www/app_name/"
foreverApp="forever"

start() {
    echo "Starting $nodeApp"

    # Notice that we change the PATH because on reboot
   # the PATH does not include the path to node.
   # Launching forever with a full path
   # does not work unless we set the PATH.
   PATH=/usr/local/bin:$PATH
    export NODE_ENV=production
   #PORT=80
   $foreverApp start --pidFile $pidFile -l $logFile -a -d -c "$command" $nodeApp
   RETVAL=$?
}

restart() {
    echo -n "Restarting $nodeApp"
    $foreverApp restart $nodeApp
    RETVAL=$?
}

stop() {
    echo -n "Shutting down $nodeApp"
   $foreverApp stop $nodeApp
   RETVAL=$?
}

status() {
   echo -n "Status $nodeApp"
   $foreverApp list
   RETVAL=$?
}

case "$1" in
   start)
        start
        ;;
    stop)
        stop
        ;;
   status)
        status
       ;;
   restart)
       restart
        ;;
    *)
       echo "Usage:  {start|stop|status|restart}"
       exit 1
        ;;
esac
exit $RETVAL
```
[start, stop, status, restart]のいずれかの引数を1つ受け取るシェルスクリプトになっている。

## デーモンファイルを再配置・権限を付与

```
# mv app_name /etc/init.d/
# chmod +x /etc/init.d/app_name
```

他のデーモンと同様、/etc/init.d/に配置し、実行権限を付与した。
Nuxt.jsの場合、このままでは実行してもエラーになってしまうので、デーモンファイル(/etc/init.d/app_name)をNuxt.js用に編集する。

## デーモンファイルをNuxt.js用に編集

### 編集後のファイル

```bash

# cat /etc/init.d/app_name

#!/bin/bash
### BEGIN INIT INFO
# If you wish the Daemon to be lauched at boot / stopped at shutdown :
#
#    On Debian-based distributions:
#      INSTALL : update-rc.d scriptname defaults
#      (UNINSTALL : update-rc.d -f  scriptname remove)
#
#    On RedHat-based distributions (CentOS, OpenSUSE...):
#      INSTALL : chkconfig --level 35 scriptname on
#      (UNINSTALL : chkconfig --level 35 scriptname off)
#
# chkconfig:         2345 90 60
# Provides:          /var/www/app_name/
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: forever running /var/www/app_name/
# Description:       /var/www/app_name/
### END INIT INFO
#
# initd a node app
# Based on a script posted by https://gist.github.com/jinze at https://gist.github.com/3748766
#

if [ -e /lib/lsb/init-functions ]; then
	# LSB source function library.
	. /lib/lsb/init-functions
fi;

pidFile="/var/run/app_name.pid"
logFile="/var/run/app_name.log"

command="node"
nodeApp="/usr/bin/npm --prefix /var/www/app_name/ run start"
foreverApp="forever"
appName="app_name"

start() {
    echo -e "Starting $appName\n"
	# Notice that we change the PATH because on reboot
	# the PATH does not include the path to node.
	# Launching forever with a full path
	# does not work unless we set the PATH.
	PATH=/usr/local/bin:$PATH
	export NODE_ENV=production
	#PORT=80
	$foreverApp start --pidFile $pidFile -l $logFile -a -d -c "$command" $nodeApp
	RETVAL=$?
}

restart() {
    stop
    start
    RETVAL=$?
}

stop() {
    if [ -e $pidFile ]; then
        echo -e -n "Shutting down $appName\n"
        cat $pidFile | xargs $foreverApp stop
    fi;
    RETVAL=$?
}

status() {
    if [ -e $pidFile ]; then
        pid=$(cat $pidFile)
        echo -e -n "$appName (pid $pid) is running...\n"
    else
        echo -e -n "$appName is stopped\n"
    fi;
    RETVAL=$?
}

case "$1" in
   start)
        start
        ;;
    stop)
        stop
        ;;
   status)
        status
       ;;
   restart)
       restart
        ;;
    *)
       echo "Usage:  {start|stop|status|restart}"
       exit 1
        ;;
esac
exit $RETVAL
```

[start, stop, status,restart]を実行できるようにした。

### 差分ファイル

```diff

# cat /etc/init.d/app_name

#!/bin/bash
### BEGIN INIT INFO
# If you wish the Daemon to be lauched at boot / stopped at shutdown :
#
#    On Debian-based distributions:
#      INSTALL : update-rc.d scriptname defaults
#      (UNINSTALL : update-rc.d -f  scriptname remove)
#
#    On RedHat-based distributions (CentOS, OpenSUSE...):
#      INSTALL : chkconfig --level 35 scriptname on
#      (UNINSTALL : chkconfig --level 35 scriptname off)
#
# chkconfig:         2345 90 60
# Provides:          /var/www/app_name/
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: forever running /var/www/app_name/
# Description:       /var/www/app_name/
### END INIT INFO
#
# initd a node app
# Based on a script posted by https://gist.github.com/jinze at https://gist.github.com/3748766
#

 if [ -e /lib/lsb/init-functions ]; then
 	# LSB source function library.
 	. /lib/lsb/init-functions
 fi;

 pidFile="/var/run/app_name.pid"
 logFile="/var/run/app_name.log"

 command="node"
-nodeApp="npm run start /var/www/app_name/"
+nodeApp="/usr/bin/npm --prefix /var/www/app_name/ run start"    # 起動のコマンドを変更
 foreverApp="forever"
+appName="app_name"    # アプリケーション名を定義、echo出力用

 start() {
-    echo "Starting $nodeApp"
+    echo -e "Starting $appName\n"    # echo出力に改行コードを追加
     # Notice that we change the PATH because on reboot
     # the PATH does not include the path to node.
     # Launching forever with a full path
     # does not work unless we set the PATH.
     PATH=/usr/local/bin:$PATH
     export NODE_ENV=production
     #PORT=80
     $foreverApp start --pidFile $pidFile -l $logFile -a -d -c "$command" $nodeApp
 	RETVAL=$?
 }

 restart() {
-    echo -n "Restarting $nodeApp"
-    $foreverApp restart $nodeApp
+    stop     # stopを呼び出す
+    start    # startを呼び出す
     RETVAL=$?
 }

 stop() {
-    echo -n "Shutting down $nodeApp"
-    $foreverApp stop $nodeApp
+    if [ -e $pidFile ]; then    # $pidFileが存在した場合、serviceが起動中であると見なす
+        echo -e -n "Shutting down $appName\n"    # echo出力に改行コードを追加
+        cat $pidFile | xargs $foreverApp stop    # $pidFileにpidをもとにforeverの該当プロセスを停止する
+    fi;
     RETVAL=$?
 }

 status() {
-    echo -n "Status $nodeApp"
-    $foreverApp list
+    if [ -e $pidFile ]; then    # $pidFileが存在した場合、serviceが起動中であると見なす
+        pid=$(cat $pidFile)    # pidを変数に代入する
+        echo -e -n "$appName (pid $pid) is running...\n"    # 起動中ステータスをユーザーに通知する
+    else    # $pidFileが存在しなかった場合、serviceが停止中であると見なす
+        echo -e -n "$appName is stopped\n"    # 停止中ステータスをユーザーに通知する
+    fi;
     RETVAL=$?
 }

 case "$1" in
    start)
         start
         ;;
     stop)
         stop
         ;;
    status)
         status
        ;;
    restart)
        restart
         ;;
     *)
        echo "Usage:  {start|stop|status|restart}"
        exit 1
         ;;
 esac
 exit $RETVAL
```

#### ちょっと解説

```
# forever start --pidFile /var/run/app_name.pid -l /var/run/app_name.log -a -d -c "node" /usr/bin/npm --prefix /var/www/app_name/ run start
```

これが実行されるコマンド。

- 非カレントディレクトリにあるnpm scriptは--prefix /var/www/app_name/ run startという感じで呼び出す。
- npmの指定は、/usr/bin/npmといった具合にフルパスで指定する。コマンド名のみで指定すると$PATH変数を読み込む前にinitで起動されて、npm does not exist.となり実行できない。

## serviceコマンドで実行してみる

```
# service app_name status
app_name is stopped
# service app_name start
Starting app_name

warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: /usr/bin/npm
# service app_name status
app_name (pid 11019) is running...
# service app_name restart
Shutting down app_name
info:    Forever stopped process:
    uid  command script                                                     forever pid   id logfile                  uptime
[0] 74Ci node    /usr/bin/npm --prefix /var/www/html/app_name/ run start 10973   11308    /var/run/app_name.log 0:0:0:0.582
Starting app_name

warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: /usr/bin/npm
# service app_name stop
Shutting down app_name
info:    Forever stopped process:
    uid  command script                                                     forever pid   id logfile                  uptime
[0] eqhV node    /usr/bin/npm --prefix /var/www/html/app_name/ run start 11354   11444    /var/run/app_name.log 0:0:0:0.850
# service app_name restart
Starting app_name

warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: /usr/bin/npm
```

start, stop, status, restartが実行できている。
statusは起動時、停止時に想定した挙動となっていて、restartも起動時、停止時に想定した挙動となっている。

## chkconfigで自動起動を設定してみる

```
# chkconfig app_name --add
# chkconfig app_name on
# chkconfig app_name --list
app_name	0:off	1:off	2:on	3:on	4:on	5:on	6:off
```

これでrunlevelが[2, 3, 4, 5]でOSが起動する際にNuxt.jsアプリケーションが自動起動されるようになった。

# 参考リンク

- [forever - npm](https://www.npmjs.com/package/forever)
- [initd-forever - npm](https://www.npmjs.com/package/initd-forever)
- [forever - github](https://github.com/foreverjs/forever)
- [initd-forever - github](https://github.com/92bondstreet/initd-forever)
- [initd-forever-nuxt.js - github](https://github.com/isao0214/initd-forever-nuxt.js) ← サンプルコードはこちら

# まとめ

シェルスクリプトを読み書きできる人なら問題なくできるっぽい。
このスクリプトを改造したら、foreverじゃなくても、いろいろ使えそう。