FreeBSD Service Configuration: A primer & example
19 Jun 2021They say you should blog about problems you’ve solved. So here is a blog post about today’s problem: Figuring out how to configure FreeBSD services. We’ll break down the configuration for a simple service, linking you to all the relevant docs along the way.
The service
The service we’re setting up is syncthing, which I use to synchronise files across my devices via my home server. It works very well and I wholeheartedly recommend it.
Originally when I set this up I used the FreeNAS “plugin” for syncthing. This does not auto-update and that’s a problem so for ages I’ve been meaning to replace that with just a regular jail that runs syncthing. So we create a jail, download syncthing, setup some config, and now we need to create a service for it so that it will startup and shutdown automatically with the jail.
Here is the service config (/usr/local/etc/rc.d/syncthing
) that I’m using:
#!/bin/sh
# PROVIDE: syncthing
# REQUIRE: LOGIN
# KEYWORD: shutdown
. /etc/rc.subr
name=syncthing
rcvar=syncthing_enable
load_rc_config $name
: ${syncthing_enable="NO"}
: ${syncthing_home_dir:="/usr/local/syncthing"}
pidfile="/var/run/${name}.pid"
procname=/usr/local/share/syncthing/syncthing
command=/usr/sbin/daemon
command_args="-f -p ${pidfile} -u syncthing ${procname} --home=${syncthing_home_dir} --logfile=default"
run_rc_command "$1"
Let’s go over that bit-by-bit and explain what on earth we’re doing here and why.
The breakdown
#!/bin/sh
This is a shell script. No surprises here.
# PROVIDE: syncthing
# REQUIRE: LOGIN
# KEYWORD: shutdown
These comments are used by the rcorder
program to determine the what needs to be executed and when during startup.
PROVIDE
specifies a name for the service that this script provides. This can be referenced by other scripts that want to start before or after our service.REQUIRE
specifies which other services are required by this script. They will be started before our services.LOGIN
is a “placeholder” for “user login services as well as services which might run commands as users”.KEYWORD
is a list of “tags” for our service which are used by others to interact with a whole group of services. Theshutdown
tag is used byrc.shutdown
(presumably defined as “on system shutdown” or something similar, I’m not entirely certain) to stop services that should be gracefully terminated.
So basically: “We’re providing a service called syncthing
which should start with user login and should be shutdown gracefully”
There are also other fields you could specify that aren’t used here, see the rcorder docs. For more info (such as other possible placeholders), see the rc docs.
. /etc/rc.subr
This is where a lot of the magic in these scripts comes from. This is a system-provided script that provides an assortment of helper functions that you probably want when setting up services. We’re just “sourcing”/importing it here as we might any other shell script. See the docs (or the file itself) for more info.
name=syncthing
rcvar=syncthing_enable
These are variables used by the run_rc_command
function below. name
is the name of this script. rcvar
is the name of the variable that is checked to determine if the relevant action should run when this script is invoked.
load_rc_config $name
A helper function from rc.subr
that will “source the configuration file(s) for a given service”.
In hindsight maybe this is a little pointless in my case because I believe it will basically just source this file that we’re already running. I expect it’s important if you have a service with more configuration that is spread across various files (and not all in the same file that defines the service).
: ${syncthing_enable="NO"}
: ${syncthing_home_dir:="/usr/local/syncthing"}
I didn’t know this, but this is just Bourne shell syntax for “set the variable if it is empty or undefined” (as explained in this StackOverflow question). I haven’t used it but I imagine the benefit of this (over just setting the variable normally) is that you can overwrite it in some other configuration (maybe the config you loaded above with load_rc_config
!)
pidfile="/var/run/${name}.pid"
procname=/usr/local/share/syncthing/syncthing
command=/usr/sbin/daemon
command_args="-f -p ${pidfile} -u syncthing ${procname} --home=${syncthing_home_dir} --logfile=default"
These are more variables used by run_rc_command
.
pidfile
is where the pid of the process will be stored while it is running (and what will be checked when we need to know if the process is running).procname
is the process name that will be checked when looking for the service process (if this is different to the value ofcommand
, which it is here)command
is the command that we want to runcommand_args
is (surprise!) the arguments to pass to the command that we run
I’m using daemon because it provides the necessary behaviour for a service/daemon and lets you “wrap” other executables that probably don’t do that. For example I originally was just running syncthing directly and on the FreeNAS web UI that would cause the “starting jail” modal to never go away, even once the jail (and syncthing) had successfully started up.
run_rc_command "$1"
Do the thing. Everything has just been setup until now. There are extensive docs in rc.subr
so check that for more detail but this executes the relevant command for this script. The argument we’re passing in is the action to execute for the service (e.g start, stop, status etc). This function uses that to decide what needs doing and all the other variables we set above to decide how to do it.
One last thing
To enable the service you need to add syncthing_enable=YES
(or whatever your configured rc_var was above) before the service
program will let you interact with this script. If you try start the service without doing so, you get an error message telling you to add it.
Once you’ve done that you can happily start/stop/etc your service by running service syncthing start
and the like (obviously substituting syncthing
for whatever you defined name
to be in your configuration script).
Well that wasn’t so hard now was it?
To be honest, every time I figure this stuff out (I only need it once a year or so), I am surprised by how simple it actually is. Hopefully next time you or I need this, it’ll be a bit quicker to get started.