Starting Node Forever Scripts at Boot w/ CentOS

I've spent the past couple of days banging my head against my desk trying to get my node apps that I run with Forever to automatically start when the server reboots. Recent DoS attacks on my hosting provider made this an even more urgent need to get up and running.

A lot of this comes from Phil Chen's Quick and Dirty How to Write an Init Script. I've changed where necessary for this specific use case.

First some fine print: this works on my setup running CentOS 6.5 with NodeJS v0.10.2 and Forever v.0.11.1. Your individual mileage may (and lets be honest probably will) vary. This is also use at your own risk, no guarantee and all that stuff.

I'm assuming you are already familiar with node, and forever, and already have all that installed. I'm also assuming you are doing all of this as root.

Do Some Planning

The first step is to do a little bit of planning to figure out where in the start order our scripts need to be started. So do the following to find out what is getting started now:

cd /etc/rc.d/rc3.d  
ls -l  

The output from that will list all of the init scripts set to run at run level 3 (more on the level thing later). But you'll see a lot of files listed that all start with a K or S followed by a number and then a name. Items that start with K aren't ran, while items with an S are. The number after that is the order that it starts in, and and then finally the name of the actual script (which also shows its a symlink to the script in /etc/init.d folder) Here's a sample from mine:

sample ls in /etc/rc.d/rc3.d
sample ls -l from /etc/rc.d/rc3.d

The node scripts I'm going to be booting use mysql and redis, so they need to be started after those. Since I see mysqld starts at 64 and redis-server starts at 85 I have to choose a number higher than that, say 86.

The Init Script

So now that we know where in the order we want our init script to run, lets create the actual script:

#!/bin/sh
#
# Note runlevel 2345, 86 is the Start order and 85 is the Stop order
#
# chkconfig: 2345 86 85
# description: Description of the Service
#
# Below is the source function library, leave it be
. /etc/init.d/functions

# result of whereis forever or whereis node
export PATH=$PATH:/usr/local/bin  
# result of whereis node_modules
export NODE_PATH=$NODE_PATH:/usr/local/lib/node_modules


start(){  
        forever start PATH_TO_NODE_SCRIPT
}

stop(){  
        forever stop PATH_TO_NODE_SCRIPT
}

restart(){  
        forever restart PATH_TO_NODE_SCRIPT
}

case "$1" in  
        start)
                echo "Start service SERVICE_NAME"
                start
                ;;
        stop)
                echo "Stop service SERVICE_NAME"
                stop
                ;;
        restart)
                echo "Restart service SERVICE_NAME"
                restart
                ;;
        *)
                echo "Usage: $0 {start|stop|restart}"
                exit 1
                ;;
esac  

This script needs to reside in /etc/init.d/ and name it something recongizable. For me, the site that's using this gprl so I'm going to save this script as /etc/init.d/grpl. Lets break this script down a little bit.

The Comment at the top

Three very important things happen in the comments at the top, so don't delete them. The first is #!/bin/sh sets this up as a shell script, nothing new there.

# chkconfig: 2345 86 85 will be parsed by the chkconfig command we'll be using to add it to the startup. It means that we want this to run at Runlevels 2, 3, 4, and 5; it should be started at #86 (what we decided earlier on) and stopped at #85.

# description: Description of the Service is also needed and you should change "Description of the Service" to something meaningful, like "The node server handling the live updates for the GRPL site" for my example.

The line right after the last comment . /etc/init.d/functions just grabs some required library stuff, don't mess with it.

Run Levels

A quick second on the run level portion of that. The SysV Init setup lets you start different things depending on the mode the system was started. Single-user mode is level 1, multi-user is 3, and multi-user with gui is 5. 2 and 4 are used for custom modes. I went with 2345 mostly because that's what apache and mysql were set to do. You can learn more about this from CentOS SysV Init Runlevels

Exports

Node and Forever aren't within the path that this script uses to run. To get around that we extend a couple of path variables. Most likely the values provided will be correct, but here's how to check.

For the first line, export PATH=$PATH:/usr/local/bin, run the command whereis forever, if it doesn't show that it is in /usr/local/bin then change that to whatever folder it is in (making sure you don't also include the forver portion).

Do the same with the second line except substitute forever with node_modules. If you need to change it, this time do include the node_modules portion.

Forever

The next set of functions are where we actually run the forever commands that correspond to the function name. Replace PATH_TO_NODE_SCRIPT with the path to the node script you want to start. If you normally pass additional arguments to forever, just rewrite these lines to include those arguments. For example, using the following would start a default install of Ghost

NODE_ENV=production forever start /var/www/ghost/index.js  

Make sure your paths are absolute!

Service Name

The case statement that takes up the rest of the script checks if your trying to start, stop, or restart the script and calls the appropriate function. We could have skipped having the function calls and written our forever commands here, but this adds some future-proofing. Change SERVICE_NAME to whatever you named the file (in my example it is grpl). It's not required, but will be displayed on the screen and system logs.

Add Our Init Script

So now that we have our init script above saved as /etc/init.d/SERVICE_NAME we have to make it executable, so run the following:

cd /etc/init.d  
chmod a+x SERVICE_NAME  

Now that it's an executable script we just have to add it to the startup using the chkconfig command (man) by running the following:

chkconfig --add SERVICE_NAME  

this will parse the levels and order from the init script and add it in all of the proper locations. You can config it is added properly by running chkconfig to view a list of all the init scripts that are registered, along with their run levels. For my example I'll see a line like:

grpl            0:off   1:off   2:on    3:on    4:on    5:on    6:off  

since I was using grpl for SERVICE_NAME and set it to run on levels 2, 3, 4, and 5.

You can also cd /etc/rc.d/rc3.d/ and then ls -l to confirm that it was setup with the proper start order.

Voila!

Now you can reboot your system and that should be running as normal.

Aron Duby

Web Developer Bacon Specialist, Introvert, Zombie Maven, Professional Photo-Bomber, Like Neo from 'The Matrix', but less terrible.