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:
- BIOS Power-On Self-Test (POST)
- Boot loader (GRUB2)
- Kernel
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