How to Write a Custom `rc.d` Startup Script on FreeBSD Operating System

Learn how to write a custom rc.d startup script on FreeBSD operating system. This guide covers the basics of the rc.d system, the structure of an rc.d script, and provides a step-by-step guide to creating your own script.

FreeBSD, a powerful and versatile Unix-like operating system, is widely used for its robustness, scalability, and advanced networking capabilities. One of the key features of FreeBSD is its init system, which manages the startup and shutdown of services. The rc.d framework is a critical component of this system, allowing administrators to define and control how services are started, stopped, and managed.

In this article, we will explore how to write a custom rc.d startup script on FreeBSD. We will cover the basics of the rc.d system, the structure of an rc.d script, and provide a step-by-step guide to creating your own script. By the end of this article, you should have a solid understanding of how to create and manage custom startup scripts on FreeBSD.

Understanding the rc.d System

The rc.d system in FreeBSD is responsible for managing the initialization of system services during boot and their termination during shutdown. It is a collection of shell scripts located in the /etc/rc.d directory, each corresponding to a specific service. These scripts are executed by the rc script, which is the main init script that runs at system startup.

The rc.d system is designed to be modular and extensible, allowing administrators to easily add custom services or modify existing ones. Each rc.d script follows a specific structure and uses a set of predefined variables and functions to control the service.

Structure of an rc.d Script

An rc.d script is essentially a shell script that adheres to a specific format. It typically includes the following components:

  1. Shebang Line: The script begins with a shebang line (#!/bin/sh) that specifies the shell interpreter to be used.

  2. Metadata and Documentation: The script may include comments that provide metadata and documentation, such as the script’s purpose, author, and usage instructions.

  3. Variables: The script defines variables that control the behavior of the service. These variables include name, rcvar, command, pidfile, and others.

  4. Functions: The script may define custom functions to perform specific tasks, such as starting, stopping, or reloading the service.

  5. Case Statement: The script includes a case statement that handles different commands (e.g., start, stop, restart) and invokes the appropriate functions.

  6. Exit Status: The script returns an exit status to indicate success or failure.

Step-by-Step Guide to Writing a Custom rc.d Script

Now that we have a basic understanding of the rc.d system and the structure of an rc.d script, let’s walk through the process of creating a custom rc.d script for a hypothetical service called my_service.

Step 1: Define the Service

Before writing the script, you need to define the service you want to manage. For this example, let’s assume that my_service is a custom application that runs as a daemon and listens on a specific port. The service executable is located at /usr/local/bin/my_service, and it writes its PID to /var/run/my_service.pid.

Step 2: Create the Script File

Create a new script file in the /etc/rc.d directory. The filename should match the name of the service, so in this case, we will create a file named my_service.

touch /etc/rc.d/my_service
chmod 755 /etc/rc.d/my_service

Step 3: Add the Shebang Line and Metadata

Open the script file in a text editor and add the shebang line and some metadata comments.

#!/bin/sh
#
# PROVIDE: my_service
# REQUIRE: LOGIN
# KEYWORD: shutdown
#
# Add a brief description of the service here.
# my_service is a custom daemon that does something useful.
#
  • PROVIDE: Specifies the name of the service provided by this script.
  • REQUIRE: Lists the services that must be started before this service. In this case, we require the LOGIN service, which ensures that the system is ready for user logins.
  • KEYWORD: Specifies additional keywords that affect the script’s behavior. The shutdown keyword indicates that the service should be stopped during system shutdown.

Step 4: Define Variables

Next, define the variables that control the behavior of the service.

name="my_service"
rcvar="my_service_enable"

command="/usr/local/bin/${name}"
pidfile="/var/run/${name}.pid"
  • name: The name of the service, which should match the script filename.
  • rcvar: The name of the variable that controls whether the service is enabled or disabled. This variable is typically set in /etc/rc.conf.
  • command: The path to the service executable.
  • pidfile: The path to the file where the service’s PID will be stored.

Step 5: Load Configuration

Add the following line to load the configuration from /etc/rc.conf and other relevant files.

. /etc/rc.subr

This line imports the functions and variables defined in /etc/rc.subr, which are essential for the rc.d script to work correctly.

Step 6: Define Custom Functions (Optional)

If your service requires custom behavior, you can define additional functions. For example, you might want to add a function to check the service’s status.

my_service_status() {
    if [ -f "${pidfile}" ]; then
        echo "${name} is running as PID $(cat ${pidfile})."
    else
        echo "${name} is not running."
    fi
}

Step 7: Handle Commands with a Case Statement

The core of the rc.d script is the case statement that handles different commands. Add the following code to handle start, stop, restart, and status commands.

case "$1" in
    start)
        echo "Starting ${name}."
        ${command} &
        echo $! > ${pidfile}
        ;;
    stop)
        echo "Stopping ${name}."
        if [ -f "${pidfile}" ]; then
            kill $(cat ${pidfile})
            rm ${pidfile}
        else
            echo "${name} is not running."
        fi
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    status)
        my_service_status
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

exit 0
  • start: Starts the service by running the command in the background and writing the PID to the PID file.
  • stop: Stops the service by killing the process using the PID from the PID file and then removing the PID file.
  • restart: Restarts the service by calling the stop and start commands in sequence.
  • status: Calls the custom my_service_status function to check the service’s status.
  • *: Handles any other commands by displaying usage information and exiting with an error code.

Step 8: Enable the Service

To enable the service, add the following line to /etc/rc.conf:

my_service_enable="YES"

This line sets the my_service_enable variable to YES, which tells the rc.d system to start the service at boot.

Step 9: Test the Script

Finally, test the script to ensure it works as expected. You can manually start, stop, restart, and check the status of the service using the following commands:

/etc/rc.d/my_service start
/etc/rc.d/my_service status
/etc/rc.d/my_service stop
/etc/rc.d/my_service restart

If everything works correctly, the service should start, stop, and restart without any issues.

Conclusion

Writing a custom rc.d startup script on FreeBSD is a straightforward process that involves creating a shell script with a specific structure and placing it in the /etc/rc.d directory. By following the steps outlined in this article, you can create a custom script to manage any service on your FreeBSD system.

The rc.d system is a powerful and flexible tool that allows you to control how services are started, stopped, and managed. With a solid understanding of the rc.d framework and the ability to write custom scripts, you can tailor your FreeBSD system to meet your specific needs and ensure that your services run smoothly and reliably.