rc.d scripting

FreeBSD uses the init system, and system services must be made as rc.d scripts.

You can find more detailed information and examples in the handbook: Practical rc.d scripting in FreeBSD.

CLI options choice for daemons

Find below the options we normally use for any /etc/rc.d script as daemon. See more options by in the terminal issuing:

man daemon

-f: Redirect standard input, standard output and standard error to /dev/null. When this option is used together with any of the options related to file or syslog output, the standard file descriptors are first redirected to /dev/null, then stdout and/or stderr is redirected to a file or to syslog as specified by the other options.

-S: Enable syslog output. This is implicitly applied if other syslog parameters are provided. The default values are daemon, notice, and daemon for facility, priority, and tag, respectively.

-P <supervisor pid file>: Write the ID of the daemon process into the file with given name using the pidfile functionality. The program is executed in a spawned child process while the daemon waits until it terminates to keep the supervisor_pidfile locked and removes it after the process exits. The supervisor_pidfile owner is the user who runs the daemon regardless of whether the -u option is used or not.

-r: Supervise and restart the program after a one-second delay if it has been terminated.

Connecting a script to the rc.d framework

In a nutshell, rcorder takes a set of files, examines their contents, and prints a dependency-ordered list of files from the set to stdout. The point is to keep dependency information inside the files so that each file can speak for itself only. A file can specify the following information:

  • the names of the “conditions” (which means services to us) it provides with # KEYWORD:
  • the names of the “conditions” it requires with # REQUIRE:
  • the names of the “conditions” this file should run before with # BEFORE:
  • additional keywords that can be used to select a subset from the whole set of files (rcorder(8) can be instructed via options to include or omit the files having particular keywords listed.) with # KEYWORD:

As a rule of thumb, every custom made rc.d script should provide 1 daemon, and require DAEMON and networking to be initialized. Thus the first lines of your rc.d script will generally look something like:

#!/bin/sh
#
# PROVIDE: <name of this rc.d script>
# REQUIRE: DAEMON networking
# KEYWORD:

Daemon load order

The order in which the daemons load is highly important. For this example, imagine you have an API application running with an rc.d script called my_api_service. It should run as a daemon and should be able to access the network.

This means that it should only load after DAEMON and networking are loaded and running.

You can verify the load order with:

service -e

The correct load order in this case then would be :

service -e

/etc/rc.d/cleanvar
/etc/rc.d/ip6addrctl
/etc/rc.d/netif
/etc/rc.d/virecover
/etc/rc.d/motd
/etc/rc.d/os-release
/etc/rc.d/newsyslog
/etc/rc.d/syslogd
/etc/rc.d/my_api_service
/etc/rc.d/cron

Binaries as daemons

In the case of binaries, we will use the daemon in a straightforward manner. We will run our service as root user since this will be running inside a jail, where root is the only existing user. Feel free to adjust to your use case. By convention I always create a folder called internalApplications inside any jail, and place my binaries inside there. See the example below for my_api_service:

#!/bin/sh
#
# PROVIDE: my_api_service
# REQUIRE: DAEMON networking
# KEYWORD:

. /etc/rc.subr

name="my_api_service"
rcvar="my_api_service_enable"
my_api_service_chdir="/root/internalApplications/myAPIWorkingDirectory/"
my_api_service_user="root"
my_api_service_command="/root/internalApplications/myAPIWorkingDirectory/app"
pidfile="/var/run/${name}.pid"
command="/usr/sbin/daemon"
command_args="-P ${pidfile} -r -f -S ${my_api_service_command}"

load_rc_config $name
: ${my_api_service_enable:=no}

run_rc_command "$1"

Shell scripts as daemons

Shell scripts that run utilities can also be daemonized. See the example below:

#!/bin/sh

# PROVIDE: my_script_service
# REQUIRE: DAEMON networking
# KEYWORD:
. /etc/rc.subr

name="my_script_service"
rcvar="my_script_service_enable"
my_script_service_user="root"
pidfile="/var/run/${name}.pid"
my_script_service_command="/root/internalApplications/my-script.sh"
command="/usr/sbin/daemon"
command_args="-P ${pidfile} -r -f -S ${my_script_service_command}"

load_rc_config $name
: ${my_script_service_enable:=no}

run_rc_command "$1"

Hopefully you have become a little wiser about rc.d scripting and can “daemonize” anything that comes your way :)