In this short post, I will show you how to create a FreeBSD rc.d script for a Go program. To make it more interesting, this daemon will be supervised, i.e. if the program terminates for some reason (for example because of an error), the Go program will be restarted.
To begin with, I recommend reading (the excellent) [practical rc.d scripting in BSD] (https://www.freebsd.org/doc/en/articles/rc-scripting/index.html). FreeBSD rc.d scripts are usually a very simple /bin/sh
scripts. What allows the rc.d scripts to remain simple is [rc.subr] (https://www.freebsd.org/cgi/man.cgi?query=rc.subr&sektion=8) framework, that takes care of more advanced things like managing dependencies.
With rc.subr
, we can handle most of the use cases, but because we want to supervise the daemon, we will use the [daemon utility] (https://www.freebsd.org/cgi/man.cgi?query=daemon&sektion=8).
Let’s see an example rc.d
script. Since we use the /usr/sbin/daemon
utility to manage the daemon’s life cycle, we can not rely on rc.subr
’s pid
file creation mechanism, and start
, stop
, and status
procedures. We need to write these three procedures ourselves.
The most important part of the rc.d script is goprogram_start
and /usr/sbin/daemon -P ${pidfile} -r -f -u $goprogram_user $command
, where we say we want to create a parent pid
file (the -P
flag), we want the goprogram
to be restarted when it exits (the -r
flag) and we want it to be run as user goprogram
(the -u
flag). The goprogram_stop
and goprogram_status
procedures have to use the newly created pid
file.
#!/bin/sh
#
# PROVIDE: goprogram
# REQUIRE: networking
# KEYWORD:
. /etc/rc.subr
name="goprogram"
rcvar="goprogram_enable"
command="/usr/local/goprogram/goprogram"
goprogram_user="goprogram"
pidfile="/var/run/${name}.pid"
start_cmd="goprogram_start"
stop_cmd="goprogram_stop"
status_cmd="goprogram_status"
goprogram_start() {
/usr/sbin/daemon -P ${pidfile} -r -f -u $goprogram_user $command
}
goprogram_stop() {
if [ -e "${pidfile}" ]; then
kill -s TERM `cat ${pidfile}`
else
echo "${name} is not running"
fi
}
goprogram_status() {
if [ -e "${pidfile}" ]; then
echo "${name} is running as pid `cat ${pidfile}`"
else
echo "${name} is not running"
fi
}
load_rc_config $name
: ${goprogram_enable:=no}
run_rc_command "$1"
Do not forget to run chmod +x /usr/local/etc/rc.d/goprogram
and add goprogram_enable='YES'
to /etc/rc.conf
file.
Edit: As swills and Freeky pointed out in a reddit discussion, there is a more elegant approach (thank you guys):
#!/bin/sh
#
# PROVIDE: goprogram
# REQUIRE: networking
# KEYWORD:
. /etc/rc.subr
name="goprogram"
rcvar="goprogram_enable"
goprogram_user="goprogram"
goprogram_command="/usr/local/goprogram/goprogram"
pidfile="/var/run/goprogram/${name}.pid"
command="/usr/sbin/daemon"
command_args="-P ${pidfile} -r -f ${goprogram_command}"
load_rc_config $name
: ${goprogram_enable:=no}
run_rc_command "$1"
Let’s try out our new rc.d script. First, start the newly created daemon:
service goprogram start
Looking at the running processes we see that both, the supervisor and the supervised program are running:
ps aux
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
goprogram 49036 0.0 0.0 10468 1640 - SsJ Mon11 0:00.06 daemon: /usr/local/goprogram/goprogram[49037] (daemon)
goprogram 49037 0.0 0.0 41572 12840 - IJ Mon11 0:42.54 /usr/local/goprogram/goprogram
Let’s see what happens when we forcibly kill the supervised program:
kill -9 49037
ps aux
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
goprogram 49036 0.0 0.0 10468 1644 - SsJ Mon11 0:00.07 daemon: /usr/local/goprogram/goprogram[49691] (daemon)
goprogram 49691 0.0 0.0 30940 8788 - SJ 09:54 0:00.01 /usr/local/goprogram/goprogram
The supervisor detected that the supervised program with the original pid 49037 had exited and restarted the supervised program. The pid of the newly created instance is 49691.