I wanted to have a general RHEL /CentOS init script that I could drop onto servers to start node processes. The features I wanted were:
Ability to reuse it for multiple node processes on the same server.
Ability to run as non-root user.
The classic service start/stop/restart/status functionality.
I had previously written a node init script using forever , but I ended up not liking it. This script still isn’t perfect, but it seems more generally useful to me.
Drop the script in /etc/init.d/[name]
and make it executable, the custom configuration in /etc/sysconfig/[name]
, and use service [name] start
to fire it up.
#!/bin/bash
#
# chkconfig: 35 90 12
# description: Start node apps.
#
# This grabs the name of the init script. The weird string match handles
# the case when the init script is called on boot as /etc/rcN/S90[name].
INIT_SCRIPT_BASENAME=`basename $0`
SERVICE=${INIT_SCRIPT_BASENAME#S90}
#############################################################################
# CONFIGURATION BEGIN
#############################################################################
# These options can all be overridden in /etc/sysconfig/[name of init script]
# eg., if your init script is /etc/init.d/foo, then /etc/sysconfig/foo.
# This allows this one init script to be reused multiple times.
# Full path to the app file you want to launch.
APP=/full/path/to/app.js
# Name of the file to log all console output from the node process. You
# should be able to use /dev/null here if you just want to throw it away.
LOGFILE=/full/path/to/log/file
# Any arguments you want to pass to the app.
APP_ARGS=""
# Full path to the node executable. The 'which' program can usually locate
# this for you, but you can also hard code this if necessary.
NODE=`which node`
# User to run the process as.
NODE_USER="node"
# Setting for the NODE_ENV environment variable.
NODE_ENV="development"
# Name of pid/lock files -- these are sensible defaults.
PIDFILE=/var/run/${SERVICE}.pid
LOCKFILE=/var/lock/subsys/${SERVICE}
# How many seconds to wait before checking if the node process has started
# successfully.
START_CHECK_LOOPS=5
STOP_TIMEOUT=10
#############################################################################
# CONFIGURATION END
#############################################################################
DAEMON_ARGS="--check $SERVICE --pidfile $PIDFILE"
# Load the function library.
. /etc/init.d/functions
# Override defaults with custom settings.
if [ -f /etc/sysconfig/${SERVICE} ]; then
. /etc/sysconfig/${SERVICE}
fi
RETVAL=0
rh_status() {
status -p $PIDFILE $SERVICE
}
rh_status_quiet() {
rh_status >/dev/null 2>&1
}
start() {
# Trim the whitespace from the command, needed for pgrep below
local NODE_COMMAND="$(echo -e "$NODE $APP $APP_ARGS" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
if [ "$NODE_USER" != "root" ]; then
DAEMON_ARGS="$DAEMON_ARGS --user $NODE_USER"
fi
# Starts the node app as a background process.
echo -n "Starting ${SERVICE}: "
daemon $DAEMON_ARGS NODE_ENV="${NODE_ENV}" $NODE_COMMAND >> $LOGFILE 2>&1 &
# Writing the pidfile on behalf of the node process requires avoiding a race
# condition with the daemon call. Pausing here allows the call to complete
# before the pidfile is written.
sleep 1
# Also, unfortunately, the $! bash variable doesn't contain the proper pid
# needed. This strategy should be pretty safe, since running the same app
# with the same arguments using this init script doesn't make much sense.
# Certain special characters in $APP_ARGS might break this functionality, not
# tested extensively.
local pid=`pgrep -f -n "$NODE_COMMAND"`
if [ -n "$pid" ]; then
echo $pid > $PIDFILE
local count=1
while true; do
rh_status_quiet
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
touch $LOCKFILE && success || failure
break
else
if [ $count -gt $START_CHECK_LOOPS ]; then
rm -f $PIDFILE
failure
break
else
((count++))
fi
fi
sleep 1
done
else
failure
fi
echo
}
stop() {
echo -n "Stopping ${SERVICE}: "
killproc -p $PIDFILE -d $STOP_TIMEOUT $SERVICE -INT
RETVAL=$?
if [ $RETVAL = 0 ]; then
cleanup && success || failure
else
failure
fi
echo
}
restart() {
stop
start
}
cleanup() {
rm -f $PIDFILE && rm -f $LOCKFILE
return $?
}
case "$1" in
start)
rh_status_quiet && exit 0
start
;;
stop)
rh_status_quiet
if [ $? -eq 0 ]; then
stop
else
cleanup && exit 0
fi
;;
restart)
restart
;;
status)
rh_status
;;
*)
echo $"Usage: $0 {start|stop|restart|status}"
RETVAL=1
esac
exit $RETVAL