September 22, 2020
Feature image for the article titled Raspberry PI startup script using Systemd

Raspberry PI startup script using Systemd

Developing your own program for a Raspberry PI, which you want to automatically start each time your Raspberry PI boots? Not sure how to do this? Well, you came to the right place. This article contains detailed instructions on how you configure a script or program using Systemd to automatically startup each time you power up your Raspberry PI.

Background

When developing a Raspberry PI based solution, this often involves the development, configuration and/or customization of software programs. Take for example a home automation solution. You typically have a basic web interface for obtaining user input. Based on the user input, the GPIO ports need to be controlled. You can develop all of this in Python. Once done, you want to automatically start your newly developed Python program each time you power up your Raspberry PI. But how do you achieve this?

This article aims to answer exactly this question. I’ll explain step-by-step how you can automatically startup a script on your Raspberry PI using Systemd. Note that when I write script, I do not just mean a shell script, but basically any sort of program. For example a Python program or your own compiled C/C++ program.

What do you need

To complete the examples described in this article, you need an operational Raspberry PI. The exact type doesn’t really matter. It can be a small Raspberry PI Zero or a more powerful one, such as the Raspberry PI 4. In addition to the Raspberry PI hardware itself, you would need a suitable power supply and a micro-SD card where you already installed the Raspberry PI operating system.

Throughout this article, I’ll use my Raspberry PI 4 with a freshly installed Raspberry PI operating system, including graphical desktop environment. This means that I also needed a monitor, monitor cable, keyboard and mouse. If you do not have these extra parts available, you could setup your Raspberry PI as a headless server instead. A previously published article provides detailed instructions for setting up your Raspberry PI as a headless server.

Systemd introduction

What is this funky Systemd thing I just mentioned? Well, in Linux and therefore also on the Raspberry PI, the so called Systemd software manages the initialization of the system during system startup. Systemd is the one that starts all the key processes to get your Linux system properly running. This includes for example the mounting of the file systems, setting up networking and getting the display manager up-and-running.

Besides managing the system initialization, Systemd also provides a services manager that assists with keeping your Linux system running. Think of automatically restarting processes, when they failed and running periodic tasks.

After reading this brief Systemd introduction, you can imagine that Systemd is a key component in Linux. As such it makes Systemd the perfect tool for configuring a script to execute each time you startup your Raspberry PI.

Example startup script

To explain the steps involved with configuring Systemd for starting up a script each time you boot your Raspberry PI, we would need a script for testing purposes. I created the following shell script, which I named testscript.sh:

#!/bin/bash
# Store first parameter in a variable, which should be the log file location.
LOG_FILE="$1"

# Set a default log file location if the parameter was empty, i.e. not specified.
if [ -z "$LOG_FILE" ]
then
  LOG_FILE="/var/log/testlog.txt"
fi

# Append information to the log file.
echo "----------------------------------------" >> "$LOG_FILE"
echo "System date and time: $(date '+%d/%m/%Y %H:%M:%S')" >> "$LOG_FILE"
echo "Kernel info: $(uname -rmv)" >> "$LOG_FILE"

The test script simply appends the current system date/time and the version of the currently installed kernel to a log-file. By default, the test script stores the log-file in /var/log/testlog.txt. However, by specifying a different location as a parameter when called the script, you can override the default location of the log-file.

Download the script

As a next step we need to get the testscript.sh file onto your Raspberry PI. Fortunately, you can download all files related to this article from a PragmaticLinux GitHub repository. Assuming that you logged into your Raspberry PI already, open up the terminal program. Next, run the following two commands:

sudo wget -P /usr/local/bin https://raw.githubusercontent.com/pragmaticlinuxblog/systemdstartupscript/master/source/testscript.sh

sudo chmod +x /usr/local/bin/testscript.sh

Terminal screenshot that show how to download the test shell script directly from the related PragmaticLinux GitHub repository with the help of the wget program.

This directly downloads the testscript.sh from the GitHub repository with the help of the wget program. The testscript.sh file is stored in the /usr/local/bin directory. The chmod command makes the script executable. Note that the root user owns the /usr/local/bin directory. Hence the necessity to run the commands with sudo.

Manually run the script

Before configuring this script to startup automatically when the Raspberry PI boots, let’s first play around with it a bit to see how it works. We stored the script in the /usr/local/bin directory. This directory is on the default Linux path. In other words, all users can directly run this script without having to specify its full path.

If you run the script as the pi user, you get a permission error. This makes sense because by default, the script wants to write to the /var/log/testlog.txt log-file. The root user owns the /var/log directory and the pi user does not have write permission to this directory. To bypass this permission problem, simply run the script with sudo to execute it with super user privileges:

sudo testscript.sh

You can check the contents of the log-file with the help of command:

cat /var/log/testlog.txt

Terminal screenshot that shows how you run the test shell script. It also highlights that you need to run it with sudo for root privileges.

If we really want to run the script as the pi user, we can simply specify a log-file location where the pi user has write permissions. For example its home directory at /home/pi. The script allows you to override the default log-file location by specifying a different location in the first parameter:

testscript.sh /home/pi/testlog.txt

Terminal screenshot that shows you how to run the test script as a non-root user. In this case the pi user runs it. Note that you need to specify the log-file location as a parameter to the script, because the log-file location needs to be writable by the user.

Alright, now that we learned how the test script works, let’s continue after quickly cleaning up the created log-files:

rm /home/pi/testlog.txt

sudo rm /var/log/testlog.txt

Run the script at startup as the root user

So called unit-files contain instructions for Systemd. The unit-file formatting resembles that of an ini-file. Systemd supports different types of unit-files. For example a startup target, an (auto)mount point, a timer and a service to name just a few.

Installing the service-file

We want Systemd to run a script at system startup on our Raspberry PI. With this goal in mind, the service unit fits our purpose the best, because a service unit describes how to manage a service or application on a Linux system. I created the following Systemd service-file called logsysteminfo.service:

[Unit]
Description=Systemd service for logging system info at startup

[Service]
ExecStart=/usr/local/bin/testscript.sh

[Install]
WantedBy=default.target

Note that service-files should have the .service extension. To save you the effort of typing in the service-file contents, you can download this service-file directly from the PragmaticLinux GitHub repository. To do so, open up the terminal program on your Raspberry PI and run the following command:

sudo wget -P /etc/systemd/system/ https://raw.githubusercontent.com/pragmaticlinuxblog/systemdstartupscript/master/source/logsysteminfo.service

Terminal screenshot that shows you how to download the example service-file directly from the related PragmaticLinux GitHub directory with the help of the wget program.

This downloads the service-file directly to the /etc/systemd/system directory. Systemd expects service-files in this directory. Note that the root user owns the /etc/systemd/system directory. Hence the necessity to run the commands with sudo.

Inspecting the service-file

Our service-file contains three sections: Unit, Service and Install. Each section contains one or more Directives. Think of a directive as a predefined variable name that you can assign a value to. For reference purposes, this web page contains an overview of all possible directives.

The Unit section

[Unit]
Description=Systemd service for logging system info at startup

The Unit section should at a minimum contain a Description directive. It can contain other directives such as other Systemd units that it depends on (Wants/Requires directives). Systemd will start the dependent units automatically in parallel. The Before/After directives could further influence the starting order if parallel activation is not desired.

Our relatively simple service-file does not depend on any other units and therefore only contains a Description directive.

The Service section

[Service]
ExecStart=/usr/local/bin/testscript.sh

Since we intend to configure a service with this Systemd unit-file, we should include a Service section. In our case we only need to inform Systemd of the script/program we want it to start, with the help of the ExecStart directive. Usually, you would also include a Type directive here. Our service type is simple, which is the default service type if the Service section contains the ExecStart directive. For this reason the Type directive was omitted.

There are many more directives available for the Service section. For example the ExecStartPre/ExecStartPost directives that allow you to specify a script/program that should be executed before or after the one specified with ExecStart. Other commonly used directives are Restart and RestartSec. These allow you the specify conditions under which the script/program should be automatically restarted.

The Install section

[Install]
WantedBy=default.target

For a Systemd service, the Install section informs Systemd at which moment during the boot process the service should be started. The recommended way is by specifying a specific Systemd target through the WantedBy directive.

During the boot process, Systemd boots your system step-by-step. Each step is a so called target that Systemd attempts to reach. By specifying a specific target in the WantedBy directive, you can latch the service on to that target. This allows you to essentially control when Systemd starts your service.

In our case we latch on to the default.target. On any Linux system with Systemd, this pretty much means the last target during the boot process. To find out which target is the default on your system, you can run the command:

systemctl get-default

If you installed the Raspberry PI operating system with a graphical desktop environment, then this outputs graphical.target. This target means full user access with graphical display and networking. If you are curious about the other available targets on your system, you can run the command:

systemctl list-units --type=target

Terminal screenshot that shows the output of the systemctl list-units --type=target command. This lists the available Systemd targets on your system.

The most commonly specified targets in the WantedBy directive are: multi-user.target, network.target and graphical.taget. Note that graphical.target is not available if you did a headless server install, since then no display manager and desktop environment were installed.

Enabling the service-file

At this point we downloaded the logsysteminfo.service example service-file to /etc/systemd/system. Although we stored the service-file in the right location, the Raspberry PI operating system will not yet run this script upon startup. For this to work we still need to enable it with Systemd. To enable the service with Systemd, run the command:

sudo systemctl enable logsysteminfo.service

Terminal screenshot that shows how systemclt enables the Systemd service, which will run automatically during the Raspberry PI boot process.

Next, reboot the Raspberry PI in order for us to test that Systemd actually executed the script during system startup:

sudo reboot

Once the Raspberry PI completed booting, we can verify if Systemd executed the script during system startup by looking at the log-file that the script should have created. From the terminal you can run the command:

cat /var/log/testlog.txt

Alternatively, you can use the file manager to locate the log-file and double-click the log-file to open and inspect it in the text editor:

Screenshot of both the file manager that shows the /var/log/testlog.txt file and the Mousepad text editor with the testlog.txt file. It proofs that the script was executed by Systemd during startup of the Raspberry PI.

In reality, not all scripts will actually create an output file. So how do you then verify that Systemd ran your service? There are two methods. Method one involves calling the journalctl program to list the log related to the logsysteminfo.service service:

journalctl --unit=logsysteminfo.service

Terminal screenshot that shows the output of the journalctl --unit=logsysteminfo.service command. It can be used to determine that the Systemd service ran.

Method two involves calling the systemctl program and to query the status of the logsysteminfo.service service:

systemctl status logsysteminfo.service

Terminal screenshot showing the output of the systemctl status logsysteminfo.service commande. It can be used to verify that the Systemd service ran and was enabled.

Personally, I prefer the method with systemctl simply because there is more information in the output. For example whether or not the service is actually enabled in Systemd. This helps with debugging in case you forgot to actually enable the service.

While still on the topic of of the systemctl utility program, note that you can also use it to disable the service with Systemd again, if you desire to. There is no need to do this now, but just so that you know for future purposes:

sudo systemctl disable logsysteminfo.service

Run startup script as a non-root user

In the previous section we installed and enabled the logsysteminfo.service Systemd service. It runs the testscript.sh upon system startup. An important detail is that Systemd runs this script as the root user. Very convenient, because the root user can access anything on the system. So no issue with file and directory permissions.

However, this poses a serious security threat at the same time. In general you should only execute a script or program as the root user if absolutely necessary. Our script only logs some basic system information to a log-file. Any user can execute the commands that the script calls. From a system security perspective, it would be better if Systemd runs the script as a non-root user. For example the pi user. In this case we just need to make sure to specify a different log-file location that the script writes to. As you might remember from a previous section, the pi user does not have write permission to the default log-file location (/var/log/testlog.txt).

Luckily, Systemd allows us to specify the user that should execute the script with the help of the User directive:

[Service]
User=pi
ExecStart=/usr/local/bin/testscript.sh /home/pi/testlog.txt

Note that in the above snippet of the logsysteminfo.service file, the User directive was added and that the /home/pi/testlog.txt log-file location is passed on as a parameter to our testscript.sh.

Open up the terminal program on your Raspberry PI and open the service-file with the Nano text editor. Refer to this blog article for a brief refresher on editing files in the Terminal with Nano.

sudo nano /etc/systemd/system/logsysteminfo.service

Make the changes, similar to what you see in the previous snippet of the Service section. Afterwards, save the changes and close the Nano editor:

Screenshot of the Systemd service-file being edited with Nano. It highlights the changes such that the script will be executed as the non-root pi user.

Verify that the startup script ran as the pi user

Now reboot the Raspberry PI so that we can test if the changes to the service-file worked:

sudo reboot

Once the Raspberry PI completed booting, we can verify if Systemd executed the script during system startup by looking at the log-file that the script should have created. From the terminal you can run the command:

cat /home/pi/testlog.txt

Furthermore, we can verify with the ls command that the pi user wrote the log-file:

ls -l /home/pi/testlog.txt

Terminal screenshot that proves that the Systemd service executed the script as the pi user during the Raspberry PI startup.

Wrap up

While working your way through this article you learned how to configure your Raspberry PI such that Systemd executes a script or program for you during system startup. This article presented two methods: one where the root user executes the script and one where a non-root user (such as pi) executes the script.

Based on the length of this article you might think that having the Raspberry PI execute a script during startup with Systemd is complex. However, this is not the case. You merely need to store your own service-file, similar to the one presented in this article, in /etc/systemd/system and then enable the service with the systemctl program. This article just turned out a bit longer, because I wanted to give you plenty of extra background information about Systemd. Meaning that I didn’t just want to show you how it works, but also explain why it works.

Note that almost all Linux distributions nowadays include Systemd. With other words, your newly gain skill-set not only applies to the Raspberry PI, but to other Linux distributions as well.

PragmaticLinux

Long term Linux enthusiast, open source software developer and technical writer.

View all posts by PragmaticLinux →

One thought on “Raspberry PI startup script using Systemd

Comments are closed.