Replacing rc.local in systemd Linux systems

Missing rc.local for adding commands to run on startup? Here’s how to set up similar functionality with today’s systemd.

The rc.local file was—and in some cases still is—the place for Linux sysadmins to put commands that need to be run at startup. Use of the rc.local file is not only deprecated but after a couple of hours worth of attempts, was not working in any event. This despite the fact that the systemd documentation mentions the use of a “generator” that generates systemd services from an rc.local file if one exists.

Boot vs. startup

Understanding the Linux boot and startup process is important for configuring Linux and resolving startup issues. In reality, there are two sequences of events that are required to boot a Linux computer and make it usable: boot and startup. The boot sequence starts when the computer is turned on and finishes when the kernel is initialized and systemd is launched. The startup process then takes over and finishes the task of getting the Linux computer into an operational state.

Overall, the Linux boot and startup process is fairly simple to understand. It is comprised of the following steps, which will be described later in more detail:

  1. BIOS Power-On Self-Test (POST)
  2. Boot loader (GRUB2)
  3. Kernel
  4. systemd

Local startup

Sysadmins sometimes add commands to the startup sequence that are locally useful. These additions may aim to start or run local processes that are not part of the standard systemd startup. It is possible to add a new systemd service unit to launch each program needed at startup, but the old rc.local method provided a single executable file for any and all local startup needs. We, too, can use this single file approach with systemd. The elegance of this solution is that it makes it easy to add more startup commands at a later time, without the need to add more service units to systemd.

Our solution is to create a single systemd service unit and place any needed Linux commands into the executable file. There are two parts to this solution. One is obvious: We need an executable file. And two, we need to create a service unit for systemd that runs the executable.

Create the executable file

This is a trivial exercise for any sysadmin familiar with Bash programming. In fact, we will create a Bash program and place it in the Linux Filesystem Hierarchical Standard (FHS) location for local executable files, /usr/local/bin. An argument could be made for placing this executable file in another location, but /usr/local/bin is the one that makes the most sense to me since this location makes it easy for the sysadmin to run the script from the command line if necessary. The /usr/local/bin directory is always in every user’s $PATH, including that of the root user.

Create the mystartup.sh file shown here and place it in /usr/local/bin (be sure to make it executable):

#!/usr/bin/bash

################################################################################
# mystartup.sh
#
# This shell program is for testing a startup like rc.local using systemd.
# By David Both
# Licensed under GPL V2
#
################################################################################

# This program should be placed in /usr/local/bin

################################################################################
# This is a test entry

echo `date +%F" "%T` "Startup worked" >> /root/mystartup.log

Note: The comments in the included files tell you where they need to be located.

Be sure to test this executable by running it from the command line. The first time you run this shell script, you should see a new file, /root/mystartup.log, with a time and date along with the text, "Startup worked". We create this log file and add lines to it every time the script is run as a simple test to ensure that our script is working.

Run the script a couple more times. Your results should be similar to those here:

[root@testvm1 ~]#  mystartup.sh
[root@testvm1 ~]#  cat mystartup.log 
2019-09-12 19:58:00 Startup worked 
2019-09-12 19:58:17 Startup worked 
2019-09-12 19:58:54 Startup worked 
2019-09-12 19:59:00 Startup worked 
2019-09-12 20:01:08 Startup worked 
2019-09-12 20:04:01 Startup worked 
2019-09-12 20:04:13 Startup worked 
2019-09-12 20:06:11 Startup worked 
2019-09-12 20:06:28 Startup worked 
2019-09-16 09:51:21 Startup worked 
2019-09-16 09:51:51 Startup worked 

That is all we need to do to create the file that may eventually contain our local startup commands. Just add anything that needs to run at startup to this file.

Create the systemd service

The service unit we will now create is a standard systemd service unit file. This simple file is used only to run the mystartup.sh script at startup.

Create a new file, /usr/local/lib/systemd/system/mystartup.service, and add the contents shown below:

################################################################################
# mystartup.service
#
# This service unit is for testing my systemd startup service
# By David Both
# Licensed under GPL V2
#
################################################################################
# This program should be placed in /usr/local/lib/systemd/system/.
# Create a symlink to it from the /etc/systemd/system directory.
################################################################################

[Unit]

Description=Runs /usr/local/bin/mystartup.sh

  
[Service]

ExecStart=/usr/local/bin/mystartup.sh


[Install]

WantedBy=multi-user.target

This file does not need to be executable. This file could also be located in /etc/systemd/system, but as a local file it is better placed in the /usr/local branch of the directory structure, with a link to it from /etc/systemd.system.

Now, go to /etc/systemd/system and create the symbolic link in the service unit file:

[root@testvm1 system]#  ln -s /usr/local/lib/systemd/system/mystartup.service

Test the service unit

We should test the final service unit file before rebooting the Linux host for the final test. First, let’s verify that systemd sees the service:

[root@testvm1 ~]#  systemctl status mystartup

● mystartup.service - Runs /usr/local/bin/mystartup.sh

Loaded: loaded (/usr/local/lib/systemd/system/mystartup.service; linked; vendor preset: disabled)

Active: inactive (dead)

This result tells us that the service is recognized by systemd. Now, let’s start the service. Doing so will run the script but will not configure the new service to run at boot time:

[root@testvm1 ~]#  systemctl start mystartup

Check the log file’s contents to verify the new line was added.

Enable the service

All that is left is to enable the service so that it runs on startup:

[root@testvm1 ~]#  systemctl enable mystartup

Created symlink /etc/systemd/system/multi-user.target.wants/mystartup.service →

/usr/local/lib/systemd/system/mystartup.service.

Final test

Before we reboot, let’s look the journalctl command and how we can use it to view the journal entries that relate to mystartup.service. We can also use the journalctl command to verify this because systemd keeps a journal of everything it does.

In the following command, the -u option shows only entries for the mystartup unit:

[root@testvm1 ~]#  journalctl -u mystartup

-- Logs begin at Mon 2019-04-15 22:50:27 EDT, end at Mon 2019-09-16 11:44:30 EDT. --

Sep 16 11:09:28 testvm1 systemd[1]: Started Runs /usr/local/bin/mystartup.sh.

Now, reboot the Linux host and check the log file to ensure that a new line was added:

[root@testvm1 ~]#  systemctl status mystartup

● mystartup.service - Runs /usr/local/bin/mystartup.sh

Loaded: loaded (/usr/local/lib/systemd/system/mystartup.service; enabled; vendor preset: disabled)

Active: inactive (dead) since Mon 2019-09-16 11:45:59 EDT; 1min 30s ago

Process: 819 ExecStart=/usr/local/bin/mystartup.sh (code=exited, status=0/SUCCESS)

Main PID: 819 (code=exited, status=0/SUCCESS)


Sep 16 11:45:55 testvm1 systemd[1]: Started Runs /usr/local/bin/mystartup.sh.

[root@testvm1 ~]#  journalctl -u mystartup

-- Logs begin at Mon 2019-04-15 22:50:27 EDT, end at Mon 2019-09-16 11:47:45 EDT. --

Sep 16 11:09:28 testvm1 systemd[1]: Started Runs /usr/local/bin/mystartup.sh.

-- Reboot --

Sep 16 11:45:55 testvm1 systemd[1]: Started Runs /usr/local/bin/mystartup.sh.

[root@testvm1 ~]#

Conclusion

The Bash shell script we have created for this experiment runs once at startup and then exits. It does not remain in memory as a daemon because it was not designed to do so.

It is possible to add support for the old rc.local file by enabling the service with the command, systemctl enable rc-local. The commands in the rc.local file will run at the next boot. Of course, you can use systemctl enable rc-local to run rc.local immediately.

However, it is still true that rc.local is obsolete. The man page for systemd-rc-local-generator states, “Support for /etc/rc.local is provided for compatibility with specific System V systems only. However, it is strongly recommended to avoid making use of this script today, and instead provide proper unit files with appropriate dependencies for any scripts to run during the boot process.”


– masterkenneth

Leave a Reply

Your email address will not be published. Required fields are marked *