Go, by its very nature, is multithreaded. This makes a traditional approach of daemonizing Go programs by forking a bit difficult.
To get around this, you could try something as simple as backgrounding your Go program and instructing it to ignore the HUP signal:
nohup your-go-binary &
But what if your Go program is a web server that you need to be able to stop and start? (Particularly during development.) It can quickly become a pain to use the above approach, as you’ll have to look up the process identifier each time you need to stop your server. Moreover, using nohup isn’t an ideal means to turn your program into a daemon, since it doesn’t accomplish tasks commonly associated with daemons, like setting the root directory as the current working directory, setting the umask to 0, and more.
In steps daemonize, which runs any command as a Unix daemon. It automatically performs the aforementioned tasks and allows stdout and stderr to be redirected to files of your choosing. Here’s a quick example usage:
daemonize -o stdout.log -e stderr.log /absolute/path/to/go-program
While daemonize
takes care of the nitty gritty details of becoming a daemon,
we still cannot start or stop our program as easily as we can with other common
daemons (like httpd, crond, cupsd, etc.).
In order to accomplish such a thing easily, it’s usually convenient to mimmick how other daemons are set up.
Since I use Archlinux, my daemons
are organized in a BSD-style setup.
Namely, they are all located in /etc/rc.d
.
Building off of the /etc/rc.d/crond
daemon, I came up with the following for
the daemon powering this blog (located at /etc/rc.d/blog-burntsushid
):
#!/bin/bash
. /etc/rc.conf
. /etc/rc.d/functions
name=blog-burntsushi
logOut=/home/andrew/log/blog.stdout
logErr=/home/andrew/log/blog.stderr
full="/home/andrew/www/burntsushi.net/blog/blog"
cmd="/usr/sbin/daemonize -o $logOut -e $logErr $full"
user="andrew"
# Go environment setup.
export GOROOT=/opt/go
export GOPATH=/home/andrew/go/world:/home/andrew/go/me
export GOMAXPROCS=4
# You shouldn't have to edit below this line.
PID=$(pidof -o %PPID $full)
case "$1" in
start)
stat_busy "Starting $name daemon"
[[ -z "$PID" ]] && su $user -m -c "$cmd" &>/dev/null \
&& { add_daemon $name; stat_done; } \
|| { stat_fail; exit 1; }
;;
stop)
stat_busy "Stopping $name daemon"
[[ -n "$PID" ]] && kill $PID &>/dev/null \
&& { rm_daemon $name; stat_done; } \
|| { stat_fail; exit 1; }
;;
reload)
stat_busy "Reloading $name daemon"
[[ -n "$PID" ]] && kill -HUP $PID &>/dev/null \
&& { stat_done; } \
|| { stat_fail; exit 1; }
;;
restart)
$0 stop
sleep 1
$0 start
;;
*)
echo "usage: $0 {start|stop|restart|reload}"
;;
esac
exit 0
Simply altering the environment variables at the top should be enough to adapt
it to your own purposes.
In particular, $name
refers to the name of the daemon—it needn’t correspond
to any actual file.
$full
corresponds to the absolute path name of your Go binary.
(The path must be absolute because daemonize
requires it.)
$logOut
and $logErr
correspond to the log files containing the stdout and
the stderr of your program.
$cmd
corresponds to the full daemonize
command.
$user
is the name of the user that should run the daemon.
I’ve chosen to run my blog as myself for security purposes.
$GOROOT
, $GOPATH
and $GOMAXPROCS
should be set according to your
Go environment.
Finally, the command is actually run using:
su $user -m -c "$cmd"
Using su
will run the daemon as the user you specified.
The -m
switch tells su
to use the current environment to run the command
in, which is required for the $GO
variables to have any effect.
Your Go program can now be started, stopped or restarted like so:
/etc/rc.d/blog-burntsushid start
/etc/rc.d/blog-burntsushid restart
/etc/rc.d/blog-burntsushid stop
The Archlinux Wiki has more information on writing rc.d scripts.