How to create a custom service file with systemd

Ats
6 min readAug 26, 2024

--

This is a note about what I did to run a custom script on boot on a Raspberry Pi.

Photo by Adi Goldstein on Unsplash

First of all

I used the espeak-ng in my script. I used Bullseye provided by Raspberry Pi as my Linux OS.

Background

I’ve used a Rasberry Pi many times for my personal projects and used /etc/rc.local to run my script on boot up. However, the rc.local is not recommended to use and it doesn’t work on some of Linux distrubtes. So I wanted to know the alternative ways to run custom scripts on boot up. This weekend, I had some free time and I decided to research this topic.

What I did

Frist of all, the following code lines were the content in /etc/rc.local . The espeak-ng “I’ve just booted up” & line was added by me.

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
espeak-ng "I've just booted up" &

exit 0

I googled quickly and I found the major alternative was to use systemd or crontab . I decided to go with systemd because rc.local file is also part of systemd and it made more sense for me to understand how it worked. But there were some articles about crontab and I would say it would work well as I expected.

Then I researched how to create custom systemd service and found some useful articles though two of them were Japanese. I followed the airtcles as my reference.

Following the references, I created say-ready.servicelike below under the /etc/systemd/system directory. I’m going to note three things that I was confused to do when I was creating the file.

[Unit]
Description=Say message when the network is ready
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/espeak-ng "I've just booted up from systemd"
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

Firstly, how to pick up the right moment for After or Before inside Unit section? Your custom service is supposed to be put under the /etc/systemd/system directory. However, there is lib/systemd/system directory and the services managed by package are put there like below.

ats@raspberrypi:~ $ ls /lib/systemd/system
ModemManager.service e2scrub_reap.service man-db.timer rescue-ssh.target sys-kernel-tracing.mount systemd-random-seed.service
NetworkManager-dispatcher.service emergency.service modprobe@.service rescue.service sysinit.target systemd-reboot.service
NetworkManager-wait-online.service emergency.target mosquitto.service rescue.target sysinit.target.wants systemd-remount-fs.service
NetworkManager.service exit.target multi-user.target rescue.target.wants syslog.socket systemd-resolved.service
NetworkManager.service.d fake-hwclock.service multi-user.target.wants rpc-gssd.service 'system-systemd\x2dcryptsetup.slice' systemd-rfkill.service
alsa-restore.service final.target network-online.target rpc-statd-notify.service system-update-cleanup.service systemd-rfkill.socket
alsa-state.service fio.service network-pre.target rpc-statd.service system-update-pre.target systemd-suspend-then-hibernate.service
alsa-utils.service first-boot-complete.target network.target rpc-svcgssd.service system-update.target systemd-suspend.service
apply_noobs_os_config.service fstrim.service networking.service rpcbind.service system-update.target.wants systemd-sysctl.service
apt-daily-upgrade.service fstrim.timer nfs-client.target rpcbind.socket systemd-ask-password-console.path systemd-sysusers.service
apt-daily-upgrade.timer getty-pre.target nfs-common.service rpcbind.target systemd-ask-password-console.service systemd-time-wait-sync.service
apt-daily.service getty-static.service nfs-config.service rpi-display-backlight.service systemd-ask-password-plymouth.path systemd-timedated.service
apt-daily.timer getty.target nfs-idmapd.service rpi-eeprom-update.service systemd-ask-password-plymouth.service systemd-timesyncd.service
auth-rpcgss-module.service getty.target.wants nfs-utils.service rsync.service systemd-ask-password-wall.path systemd-tmpfiles-clean.service
autovt@.service getty@.service nftables.service rsyslog.service systemd-ask-password-wall.service systemd-tmpfiles-clean.timer
avahi-daemon.service glamor-test.service nss-lookup.target rtkit-daemon.service systemd-backlight@.service systemd-tmpfiles-setup-dev.service
avahi-daemon.socket gldriver-test.service nss-user-lookup.target run-rpc_pipefs.mount systemd-binfmt.service systemd-tmpfiles-setup.service
basic.target graphical.target packagekit-offline-update.service runlevel0.target systemd-bless-boot.service systemd-udev-settle.service
blockdev@.target graphical.target.wants packagekit.service runlevel1.target systemd-boot-check-no-failures.service systemd-udev-trigger.service
bluetooth.service halt.target paths.target runlevel1.target.wants systemd-boot-system-token.service systemd-udevd-control.socket
bluetooth.target halt.target.wants pigpiod.service runlevel2.target systemd-exit.service systemd-udevd-kernel.socket
boot-complete.target hciuart.service plymouth-halt.service runlevel2.target.wants systemd-fsck-root.service systemd-udevd.service
bthelper@.service hibernate.target plymouth-kexec.service runlevel3.target systemd-fsck@.service systemd-update-utmp-runlevel.service
colord.service hwclock.service plymouth-log.service runlevel3.target.wants systemd-fsckd.service systemd-update-utmp.service
configure-printer@.service hybrid-sleep.target plymouth-poweroff.service runlevel4.target systemd-fsckd.socket systemd-user-sessions.service
console-getty.service ifup@.service plymouth-quit-wait.service runlevel4.target.wants systemd-halt.service systemd-volatile-root.service
console-setup.service ifupdown-pre.service plymouth-quit.service runlevel5.target systemd-hibernate-resume@.service time-set.target
container-getty@.service ifupdown-wait-online.service plymouth-read-write.service runlevel5.target.wants systemd-hibernate.service time-sync.target
cron.service iio-sensor-proxy.service plymouth-reboot.service runlevel6.target systemd-hostnamed.service timers.target
cryptdisks-early.service initrd-cleanup.service plymouth-start.service saned.service systemd-hwdb-update.service timers.target.wants
cryptdisks.service initrd-fs.target plymouth-switch-root.service saned.socket systemd-hybrid-sleep.service triggerhappy.service
cryptsetup-pre.target initrd-parse-etc.service plymouth.service saned@.service systemd-initctl.service triggerhappy.socket
cryptsetup.target initrd-root-device.target polkit.service seeed-voicecard.service systemd-initctl.socket udev.service
ctrl-alt-del.target initrd-root-device.target.wants portmap.service serial-getty@.service systemd-journal-flush.service udisks2.service
cups-browsed.service initrd-root-fs.target poweroff.target shutdown.target systemd-journald-audit.socket umount.target
cups.path initrd-switch-root.service poweroff.target.wants sigpwr.target systemd-journald-dev-log.socket upower.service
cups.service initrd-switch-root.target printer.target sleep.target systemd-journald-varlink@.socket usb-gadget.target
cups.socket initrd-switch-root.target.wants proc-fs-nfsd.mount slices.target systemd-journald.service usb_modeswitch@.service
dbus-org.freedesktop.hostname1.service initrd-udevadm-cleanup-db.service proc-sys-fs-binfmt_misc.automount smartcard.target systemd-journald.socket user-.slice.d
dbus-org.freedesktop.locale1.service initrd.target proc-sys-fs-binfmt_misc.mount sockets.target systemd-journald@.service user-runtime-dir@.service
dbus-org.freedesktop.login1.service ipp-usb.service procps.service sockets.target.wants systemd-journald@.socket user.slice
dbus-org.freedesktop.timedate1.service kexec.target pulseaudio-enable-autospawn.service sound.target systemd-kexec.service user@.service
dbus.service kexec.target.wants quotaon.service sound.target.wants systemd-localed.service userconfig.service
dbus.socket keyboard-setup.service raspberrypi-net-mods.service ssh.service systemd-localed.service.d vncserver-virtuald.service
debug-shell.service kmod-static-nodes.service rc-local.service ssh.socket systemd-logind.service vncserver-x11-serviced.service
default.target kmod.service rc-local.service.d ssh@.service systemd-machine-id-commit.service wpa_supplicant-nl80211@.service
dev-hugepages.mount lightdm.service rc.service sshswitch.service systemd-modules-load.service wpa_supplicant-wired@.service
dev-mqueue.mount local-fs-pre.target rcS.service sudo.service systemd-network-generator.service wpa_supplicant.service
dhcpcd.service local-fs.target reboot.target suspend-then-hibernate.target systemd-networkd-wait-online.service wpa_supplicant@.service
dphys-swapfile.service local-fs.target.wants reboot.target.wants suspend.target systemd-networkd.service x11-common.service
e2scrub@.service logrotate.service regenerate_ssh_host_keys.service swap.target systemd-networkd.socket
e2scrub_all.service logrotate.timer remote-cryptsetup.target sys-fs-fuse-connections.mount systemd-poweroff.service
e2scrub_all.timer machine.slice remote-fs-pre.target sys-kernel-config.mount systemd-pstore.service
e2scrub_fail@.service man-db.service remote-fs.target sys-kernel-debug.mount systemd-quotacheck.service

In my case, I wanted to run my script after the network was build. So I opened some target files likely related to network.

ats@raspberrypi:~ $ cat /lib/systemd/system/network.target
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.

[Unit]
Description=Network
Documentation=man:systemd.special(7)
Documentation=https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget
After=network-pre.target
RefuseManualStart=yes

Then I got the documentation link from the file and understand what it’s.

Secondly, how to write the Install section. Actually, I haven’t got what it’s exactly yet. But I concluded that WantedBy=multi-user.target was good enough because I opened many service files under /lib/systemd/system and /etc/systemd/system and most of them used WantedBy=multi-user.target in the Install section. I’ve attached the conversation about the Install section below if you’re more interested in it.

Lastly, how to activate the custom service execution on boot up. It wasn’t run automatically by default. Then I’ve checked the service status like below.

ats@raspberrypi:~ $ systemctl status say-ready.service
● say-ready.service - Say message when the network is ready
Loaded: loaded (/etc/systemd/system/say-ready.service; disabled; vendor preset: enabled)
Active: inactive (dead)

Aug 26 00:05:41 raspberrypi systemd[1]: Started Say message when the network is ready.
Aug 26 00:05:44 raspberrypi systemd[1]: say-ready.service: Succeeded.
ats@raspberrypi:~ $

Then I noticed that the service was disabled. So I enabled it with sudo systemctl enable say-ready.service command.

ats@raspberrypi:~ $ systemctl status say-ready.service
● say-ready.service - Say message when the network is ready
Loaded: loaded (/etc/systemd/system/say-ready.service; enabled; vendor preset: enabled)
Active: inactive (dead)

Aug 26 00:05:41 raspberrypi systemd[1]: Started Say message when the network is ready.
Aug 26 00:05:44 raspberrypi systemd[1]: say-ready.service: Succeeded.

I saw it enabled in the logs, rebooted the machine, and heard the service worked. I checked the logs with journalctl command just in case.

ats@raspberrypi:~ $ journalctl -u say-ready.service
-- Boot 72f8a38ab43d46f1bdc615966b717af7 --
Aug 26 00:18:26 raspberrypi systemd[1]: Started Say message when the network is ready.
Aug 26 00:18:31 raspberrypi systemd[1]: say-ready.service: Succeeded.

That’s it!

--

--

Ats
Ats

Written by Ats

I like building something tangible like touch, gesture, and voice. Ruby on Rails / React Native / Yocto / Raspberry Pi / Interaction Design / CIID IDP alumni

No responses yet