Writing Your Own Init Scripts

As you may have noticed in our previous post, we use capistrano for our go deployments. Today I want to show how we use the start-stop-daemon from OpenRC to start these apps.

As we use Gentoo Linux on our servers, we have easy access to OpenRC. If you are interested in installing OpenRC on another linux distribution, please checkout the projects documentation. If you followed the instructions in our post about go app deployment you are ready to set up the init script for your app.

Creating the template

This is easy on Gentoo. Just go to the right directory and use your favorite editor:

$> cd /etc/init.d/
$> vim myApp

You should see a basic template like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
 #!/sbin/runscript
 # Copyright 1999-2013 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 # $Header: $

 depend() {
 }

 start() {
 }

 stop() {
 }

Customize the template

Of course you have to adjust the template to your own needs. It is important that you either fill the functions body or delete it. The start-stop-daemon will refuse to start init scripts which have functions with empty bodies.

depend function

This function sets the dependencies for your app. If you have a webserver you may want to make sure that you have a working network connection and filesystem access. In this case you would create a depend-Function like this:

1
2
3
4
depend() {
    need net
    need localmount
}

While ‘need’ tells the init-daemon that it is a hard dependency, you could also use ‘use’. This tells the init-daemon that you use the functionality of this dependency but your system will still work. If you are not sure, use ‘need’. You can also use the ‘before’ and ‘after’ keywords. These keywords can be used to say you want to start your app before or after other systems. For example ‘before mta’ will start your app before postfix.

start function

This function tells the init system how to start your app. Here at adeven we use start-stop-daemon to start our apps, as this software offers a lot of functionality we would have to implement on our own otherwise. We started with the empty start function body:

1
2
start() {
}

First we add some information the user is given when using our script. This can easily be done with the ‘ebegin’ and ‘eend’ functions. You could also use echo, but this way they are formatted more nicely and are in the right stream (stdout or stderr):

1
2
3
4
start() {
    ebegin "Starting myApp"
    eend $?
}

As I mentioned, we want to use the start-stop-daemon to start our app and run it in the background.

1
2
3
4
5
start() {
    ebegin "Starting myApp"
    start-stop-daemon --background --start --exec /home/user/myApp
    eend $?
}

That would be the easiest way of starting an app. Go programmers will notice the first advantage: You do not have to take care of the daemonizing of your app. But we want to be able to keep track of the status of the app. To do so let us add a pidfile.

1
2
3
4
5
6
7
start() {
    ebegin "Starting myApp"
    start-stop-daemon --background --start --exec /home/user/myApp \
    --make-pidfile --pidfile /home/user/myApp.pid
    eend $?
}
#notice the bash like escape to use newlines

And thats pretty much it. You do not have to take care of making a pidfile on your own. The start-stop-daemon uses this pidfile to stop the app later.

Now let us add flags your app might use:

1
2
3
4
5
6
7
start() {
    ebegin "Starting myApp"
    start-stop-daemon --background --start --exec /home/user/myApp \
    --make-pidfile --pidfile /home/user/myApp.pid \
    -- --flag1 option1 --flag2 option2
    eend $?
}

Of course you can also use environment variables. Although it is possible to set one environmental option via the start-stop-daemon it is advisable to use ‘env’ if you want to set more than one:

1
2
3
4
5
6
7
8
9
start() {
    ebegin "Starting myApp"
    start-stop-daemon --background --start --exec \
    env MYENV1=env1 MYENV2=env2 \
    /home/user/myApp \
    --make-pidfile --pidfile /home/user/myApp.pid \
    -- --flag1 option1 --flag2 option2
    eend $?
}

On Linux systems it is common to have different users for different apps. As the start-stop-daemon normally starts apps as root you may want to change this to an unprivileged user.

1
2
3
4
5
6
7
8
9
10
start() {
    ebegin "Starting myApp"
    start-stop-daemon --background --start --exec \
    env MYENV1=env1 MYENV2=env2 \
    /home/user/myApp \
    -u user \
    --make-pidfile --pidfile /home/user/myApp.pid \
    -- --flag1 option1 --flag2 option2
    eend $?
}

Sometimes you create apps which take a while to start up. For example it is possible that your app uses a database or a message queue. If your app can not connect to other apps it most likely will retry to connect several times. As your app is considered running while doing so, the start-stop-daemon will happily tell you everything went well. When your app reaches your specified max timeout or max reconnect count it will crash in the background. To avoid this behaviour you can instruct the start-stop-daemon to wait a while. Let us make the daemon wait for 5 seconds:

1
2
3
4
5
6
7
8
9
10
11
start() {
    ebegin "Starting myApp"
    start-stop-daemon --wait 5000 \
    --background --start --exec \
    env MYENV1=env1 MYENV2=env2 \
    /home/user/myApp \
    -u user \
    --make-pidfile --pidfile /home/user/myApp.pid \
    -- --flag1 option1 --flag2 option2
    eend $?
}

stop function

The stop function is used to stop your app. Also it is called when you invoke the restart command. Let us start with increasing the verbosity of our stop function just as we did for the start function:

1
2
3
4
stop() {
    ebegin "Stopping myApp"
    eend $?
}

We want to use the start-stop-daemon, because he already knows our app.

1
2
3
4
5
stop() {
    ebegin "Stopping myApp"
    start-stop-daemon --stop --exec /home/user/myApp
    eend $?
}

Although the start-stop-daemon is clever enough to determine what you want, you can help him by telling him which pidfile you created for your app. This increases the performance slightly and makes sure he really stops the right app.

1
2
3
4
5
6
stop() {
    ebegin "Stopping myApp"
    start-stop-daemon --stop --exec /home/user/myApp \
    --pidfile /home/user/myApp.pid
    eend $?
}

That all you have to do to stop your app.

custom functions

Of course you are free to define other functions, if you need them. You could for example define a reload function:

1
2
3
4
reload() {
    ebegin "Reloading myApp"
    eend $?
}

Let us say you implemented a signal handler to your app which causes it to reload the config on SIGHUP. Of course you could signal your app via bash, but the start-stop-daemon provides easy access to this functionality. As SIGHUP is signal ‘1’, you just have to add this:

1
2
3
4
5
6
7
reload() {
    ebegin "Reloading myApp"
    start-stop-daemon --exec /home/user/myApp \
    --pidfile /home/user/myApp.pid \
    -s 1
    eend $?
}

You can easily create more functions which the start-stop-daemon could handle for you.

Starting your app

If you did everything right your brand new OpenRC init script is almost set up to work. You just have to make it executable to use it.

$> chmod +x /etc/init.d/myApp
$> /etc/init.d/myApp start
* Starting myAPP ...
$> /etc/init.d/myApp reload
* Reloading myApp ...
$> /etc/init.d/myApp stop
* Stopping myApp ...

After this you can put it in the OpenRC autostart. If you do not know about runlevels, just just the default one:

$> rc-update add myApp default
* service myApp added to runlevel default
$> rc-status
Runlevel: default
myApp        [  started  ]

Last but not least you have to add a sudoers entry for your app user, which allows him to start his app via start-stop-daemon to use this inside your capistrano/capper scripts.

1
2
3
4
user my.examplehost.com = NOPASSWD: /etc/init.d/user start
user my.examplehost.com = NOPASSWD: /etc/init.d/user stop
user my.examplehost.com = NOPASSWD: /etc/init.d/user restart
user my.examplehost.com = NOPASSWD: /etc/init.d/user reload

Conclusion

Before implementing a lot of stuff (daemonizing, pid tracking, autostart) in your app yourself, you should consider taking advantage of all the functionality that modern operating systems offer you. Using this functionality makes Go-deployment with capistrano a breeze.

Comments