FreeBSD Service Configuration: A primer & example

19 Jun 2021

They 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.

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.

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.

Other References