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 startLooking 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/goprogramLet’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/goprogramThe 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.