Replacing rc.local in systemd Linux systems
“2018/365/230 To Be Replaced” by cogdogblog is licensed under CC0 1.0
Missing rc.local
for adding commands to run on startup? Here’s how to set up similar functionality with today’s systemd
.
A few years ago I encountered two different problems on two different Linux hosts. Each required a unique circumvention because I had not yet found a real solution. However, the method for delivering each circumvention was the same: Run a command during or soon after Linux startup.
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. That seems to be a good way as any to enforce deprecation—make it not work.
The details of my specific problem are not particularly relevant to this discussion, so I will use a simple and easily tracked command as the content of our local startup file. We will add a date-stamped line to a local log file to verify that the Bash program we need to run at startup actually works.
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:
- BIOS Power-On Self-Test (POST)
- Boot loader (GRUB2)
- Kernel
systemd
For a much more detailed description of both the boot and startup sequences, refer to my other article, An introduction to the Linux boot and startup processes with GPT and GRUB2 .
Local startup
System administrators 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). Be sure to use the location for Bash that is correct for your distribution. For example, Debian-based distributions locate Bash at /bin/bash
.
#!/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)
[root@testvm1 ~]#
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.
[root@testvm1 ~]#
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.
[root@testvm1 ~]#
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. In case you had not noticed, the procedure we used to create our local startup service can also be used to create any new service for systemd
. It is not that hard once we know how to do it.
Update
Soon after this article was originally published at Opensource.com I received an email from Tom Murphy who informed me of the existence of the rc-local
service that is part of systemd
. I appreciate that email because I was not aware of that service so I learned something new.
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 --now 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.”
Resources
- Both.org: Linux Filesystem Hierarchical Standard (FHS)
- Wikipedia: GNU GRUB information
- GNU.org: GNU GRUB manual
- Wikipedia: Master Boot Record
- Wikipedia: Multiboot specification
- Wikipedia: systemd information
- Freedesktop.org: systemd bootup process
- Freedesktop.org: systemd index of man pages
- Both.org: An introduction to the Linux boot and startup processes