Writing Systemd Service Units

Some people have the misfortune of not having their keyboard or mouse working when they complete their boot. There was a post yesterday that had this problem. I had not used the USB restart service/scripts at startup before, but this seemed like the perfect opportunity to test its effectiveness at startup.

Here is the link to the help thread:

https://forum.manjaro.org/t/wireless-usb-keyboard-mouse-not-responding/75700/11

This service (and a single restart script) seems to have tentatively fixed the non working keyboard/mouse problem for the user. The user modified the script to use the device ID in the script. Otherwise the users solution was essentially the same as given below.

Let me make a disclaimer first. Enabling start up services is inherently more risky than suspend/resume services. If you make any mistakes in editing or creating this startup service you could potentially lock yourself out of your desktop (as you may not have the use of your keyboard/mouse after booting). You should know how to boot to a TTY from grub to repair any potential errors you could make.

I chose the display-manager.service as the target for this service at startup.

This service should automatically refresh your USB devices before login. This will hopefully fix problems for any USB device that was not initialized properly during the early startup process.

Systemd USB Restart Service File:

With a text editor create:

/etc/systemd/system/usb-restart.service

Service file contents:

#/etc/systemd/system/usb-restart.service
#sudo systemctl enable usb-restart.service
#sudo systemctl start usb-restart.service
#systemctl list-unit-files --state=enabled
#sudo systemctl stop usb-restart.service
#sudo systemctl disable usb-restart.service
#systemctl status usb-restart.service
#sudo systemctl daemon-reload

[Unit]
Description=Restart USB
WantedBy=multi-user.target
Requires=display-manager.service
StopWhenUnneeded=yes

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/sudo -E  /usr/local/bin/restart_usb.sh

[Install]
WantedBy=multi-user.target

Restart USB Script

Create the script that is to be executed before login:

With a text editor create:

/usr/local/bin/restart_usb.sh

Suspend script contents:

#!/bin/bash
#Restart all USB devices
#/usr/local/bin/restart_usb.sh
set -euo pipefail
IFS=$'\n\t'

VENDOR="****"
PRODUCT="****"

for DIR in $(find /sys/bus/usb/devices/ -maxdepth 1 -type l); do
  if [[ -f $DIR/idVendor && -f $DIR/idProduct &&
        $(cat $DIR/idVendor) == $VENDOR && $(cat $DIR/idProduct) == $PRODUCT ]]; then
    echo 0 > $DIR/authorized
    sleep 2 
    echo 1 > $DIR/authorized
  fi
done

Save the file with root permissions, and exit the text editor.

Ensure the script is executable:

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

Then, enable the service.

sudo systemctl enable usb-restart.service

Then restart.

3 Likes

The service includes some contradicting settings, I think. Couldn’t it be done for all users?

  • service file/name includes specific user name. I think it should exclude the user name and only have @.
  • In the file, there is a general User= and afterwards the Exec command uses specific user home folder. Could the latter have a general user name?

Wanted to ask, is there a lock.target ? Don’t mind my ignorance, as i didn’t look into this.
Here was an issue as when using multiple keyboard layouts, when the system is locked, the layout remains to de one used last time, but if the password needs the “us” one, the person has to change it in the lock screen (not a big deal, but if can be done automatically, why not)
My proposal vas this

So, if there is a lock.taget could be a systemd service made to use the
setxkbmap "us,ru"
or as many someone has, as will also keep that order and not encounter the issue when logging back in, the layout switch needs a right click to work again ? :slight_smile:

I can’t say as how I remember any units like that. Here is a list of the predefined targets.

basic.target

bluetooth.target

boot-complete.target

cryptsetup-pre.target

cryptsetup.target

ctrl-alt-del.target

dbus.service

dbus.socket

default.target

display-manager.service

emergency.target

exit.target

final.target

getty.target

getty-pre.target

graphical.target

halt.target

hibernate.target

hybrid-sleep.target

initrd-fs.target

initrd-root-device.target

initrd-root-fs.target

kbrequest.target

init.scope

kexec.target

local-fs-pre.target

local-fs.target

machines.target

-.mount

multi-user.target

network-online.target

network-pre.target

network.target

nss-lookup.target

nss-user-lookup.target

paths.target

poweroff.target

printer.target

reboot.target

remote-cryptsetup.target

remote-fs-pre.target

remote-fs.target

rescue.target

rpcbind.target

runlevel2.target

runlevel3.target

runlevel4.target

runlevel5.target

shutdown.target

sigpwr.target

sleep.target

-.slice

machine.slice

system.slice

user.slice

slices.target

smartcard.target

sockets.target

sound.target

suspend.target

suspend-then-hibernate.target

swap.target

sysinit.target

syslog.socket

system-update-cleanup.service

system-update.target

system-update-pre.target

time-sync.target

timers.target

umount.target

You are not limited to just the predefined targets. Say the service you want to start doesn’t have a target, but it does have another service that you are targeting, then you can list that related service as a dependency:

I think what you’re looking for is the getty.target. This is only an example I’ve dreamt up. I have no idea exactly what you would need to make this work. Systemd sometimes is not so straight forward and you need to experiment with many variables to get things working.

 [Unit]
After=getty-pre.target systemd-user-sessions.service
Wants= systemd-user-sessions.service logind.service
Requires=getty.target 

[Service]
Type=simple
ExecStart=/path/to/start/script or arguments

[Install]
WantedBy=getty.target

I’ve no real idea if that’s what you’re looking for, just a shot in the dark on my part.

Good luck.

1 Like

Thanks a lot !!! Indeed it makes more sense this way and keep the one spot Run command from notifications for something else.

1 Like

Thought I’d add another service to the growing collection. I’m always testing out new suspend services. My logitech unifying receiver never seemed to work to wake my computer from suspend. This was never an issue for me before, so I just never really worried about it. That is until I started working on suspend services.

When working on a troublesome suspend service I can end up going through countless suspend cycles to get the service working properly. Then I started to think maybe the wake by mouse feature might be handy to have. So, it turned out that to enable that feature I had to write the “enable” setting to /sys/bus/usb/devices/ at startup.

Well, of course I decided the easiest way to do that was with a service. I enabled the individual bus addresses for both my mouse and keyboard. USB bus addresses can switch, so I also enabled wakeup on every USB bus as well.

I thought it was rather fitting to write a service to make it easier for me to write services. The nice part was it worked the first time after a reboot. No muss, no fuss.

USB WakeUp Service

Create:

/etc/systemd/system/usb-wakeup.service

With the following contents:

#/etc/systemd/system/usb-wakeup.service
#sudo systemctl enable usb-wakeup.service
#sudo systemctl start usb-wakeup.service
#sudo systemctl stop usb-wakeup.service
#sudo systemctl disable usb-wakeup.service
#systemctl status usb-wakeup.service
#sudo systemctl daemon-reload

[Unit]
Description=Enables USB wakeup on all ports

[Service]
Type=oneshot
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/2-1/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/7-2/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/8-2/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb1/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb2/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb3/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb4/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb5/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb6/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb7/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb8/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb9/power/wakeup'
ExecStart=/usr/bin/sh -c 'echo "enabled" > /sys/bus/usb/devices/usb10/power/wakeup'

[Install]
WantedBy=multi-user.target

Enable the service:

sudo systemctl enable usb-wakeup.service

I have 10 USB bus ports. You may have to modify the service to reflect how may ports you have.

Hope that helps someone looking to enable wake from suspend via wireless USB keyboard or mouse.

6 Likes

Maybe I can get some advice on writing this service unit correctly. As of now, it works but I’m sure there are mistakes/better ways of doing this, my attempt seems rather vanilla. (PATH/TO/SCRIPT needs to be set per install)

[Unit]
Description=BabyGate
After=network.target

[Service]
Type=simple
User=root
ExecStart= /PATH/TO/YOUR/SCRIPT
Restart=on-failure

[Install]
WantedBy=multi-user.target

Here’s a link to the script it runs: https://github.com/ericlay/BabyGate/blob/master/babygate

1 Like

It seems like a pretty basic service. There’s only one change I can think of off hand.

I would change:

After=network.target

to

After=network-online.target
2 Likes

@tbg Many thanks for this thread, thought I should get with the times and move away from crontab and try systemd.
After a little reading I was able to move my hosts updater file to systemd without issues.
:+1:

3 Likes

You’re very welcome. My understanding of how systemdd works gets a little better every day. I try to spend some time every day trying to improve my systemd skills. My earlier efforts look very amateurish to me now when I look at my units from when I first started at this.

I don’t know why, but I really enjoy writing services and the challenges they present. I actually find it quite fun.

The bonus is that I’ve helped a lot of people fix there computer issues with a service and that makes me very happy as well.

Glad to see you’re getting into it too.

3 Likes

Actually, the first systemd unit I ever wrote was to do just the opposite: disable unattended wakeup.

[mbb@mbb-laptop ~]$ cat /etc/systemd/system/wakeup-events.service 
[Unit]
Description=Disable wakeup events on startup

[Service]
Type=oneshot
ExecStart=/bin/bash /home/mbb/.bin/wakeup-events.sh

[Install]
WantedBy=multi-user.target
[mbb@mbb-laptop ~]$ cat .bin/wakeup-events.sh 
#!/bin/bash
# Disable wakeup events
echo LANC > /proc/acpi/wakeup
echo EHC1 > /proc/acpi/wakeup
echo EHC2 > /proc/acpi/wakeup
echo " XHC" > /proc/acpi/wakeup
[mbb@mbb-laptop ~]$ cat /proc/acpi/wakeup
Device  S-state   Status   Sysfs node
LANC      S0    *disabled  pci:0000:00:19.0
EHC1      S0    *disabled  pci:0000:00:1d.0
EHC2      S0    *disabled  pci:0000:00:1a.0
XHC       S0    *disabled  pci:0000:00:14.0
PCIB      S5    *disabled
RP02      S4    *disabled
ECF0      S4    *disabled
RP03      S4    *enabled   pci:0000:00:1c.2
RP04      S5    *enabled   pci:0000:00:1c.3
WNIC      S5    *disabled  pci:0000:03:00.0
RP06      S0    *disabled
NIC       S0    *disabled
RP07      S4    *disabled
RP08      S0    *disabled
HST1      S5    *disabled
3 Likes

Here’s a new service that I’m sure @AgentS will appreciate. This service automatically restarts Plasma after suspend for those KDE users who experience video corruption when they resume this should correct it.

Plasma Restart Service

With a root text editor create:

/etc/systemd/system/plasma-restart@.service

Service file contents:

#/etc/systemd/system/plasma-restart@.service
#sudo systemctl enable plasma-restart@$USER.service
#sudo systemctl start plasma-restart@$USER.service
#sudo systemctl stop plasma-restart@$USER.service
#sudo systemctl disable plasma-restart@$USER.service 

[Unit]
Description=Plasma Restart Service 
After=suspend.target 
StopWhenUnneeded=yes

[Service]
User=%i
WorkingDirectory=/home/%i
Type=oneshot
Slice=user-%i.slice
RemainAfterExit=yes
ExecStart=/bin/bash -alc "sudo -Hiu %i pkill -ABRT plasmashell"

[Install]
WantedBy=suspend.target 
Alias=plasma-restart@%i.service

Save the service file with root permissions, and exit the text editor.

Then, enable the service:

sudo systemctl enable plasma-restart@$USER.service

Then restart.

3 Likes

I usually use a Require in parallel with After:

After=network.target
Require=network.target

From the systemd unit docs:

It is a common pattern to include a unit name in both the After= and Requires= options, in which case the unit listed will be started before the unit that is configured with these options.
2 Likes

A user yesterday was having suspend issues with Nvidia and KDE, so I advised to disable compositing temporarily. That fixed the problem, but the user wanted to have the process automated of stopping/restarting Kwin, (so I wrote a service to do that).

This service automatically restarts KDE's Kwin compositor after suspend. For those KDE users who experience video problems related to the compositor when they resume, this should correct it.

Kwin Compositor Restart Service

With a root capable text editor create:

/etc/systemd/system/kwin-restart@.service

With the following contents:

#/etc/systemd/system/kwin-restart@.service
#sudo systemctl enable kwin-restart@$USER.service
#sudo systemctl start kwin-restart@$USER.service
#sudo systemctl stop kwin-restart@$USER.service
#sudo systemctl disable kwin-restart@$USER.service
#systemctl status kwin-restart@$USER.service
#sudo systemctl daemon-reload
 
[Unit]
Description=Kwin Suspend/Resume Service 
Before=sleep.target
StopWhenUnneeded=yes

[Service]
User=%i
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -lc "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%U/bus DESKTOP_SESSION=/usr/share/xsessions/plasma sudo -iu %i DISPLAY=:0 qdbus org.kde.KWin /Compositor suspend"
ExecStop=/bin/bash -lc "sleep 10; DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%U/bus DESKTOP_SESSION=/usr/share/xsessions/plasma sudo -iu %i DISPLAY=:0 qdbus org.kde.KWin /Compositor resume"

[Install]
WantedBy=sleep.target

Save the service file with root permission, and exit the text editor.

Then, enable the service:

sudo systemctl enable plasma-restart@$USER.service

Then restart.

I added a 10 second sleep after resuming before kwin is restarted. A short sleep unit before executing the kwin restart ensures that everything else has had a chance to fully come out of suspend. Without a sleep unit sometimes the service will choke on resume. You can reduce or eliminate the sleep time if your system responds well without it.

(edit) It has just come to my attention from a user that tested this script that it may need to be modified if you are not using bash as your default shell. If the service does not executute properly you may want to try replacing some of the variables in the "Exec Start=" lines of the service.

Try replacing "%U" with your Users ID (UID) number (usually 1000). Also try replacing "%i" with your username. Hopefully these changes will result in the service running successfully for you if it is failing.

4 Likes

Create a centralized media library with a service:

I keep very little data on my small SSD that I use strictly for the OS. I have 6 other drives in this computer not including my mounted network shares. I tend to keep stuff split up between different drives and I've often thought it would be nice to be able to have my movies, music, pictures from different sources symlinked to a central library. This is normally not doable using symlinks as a single symlink cannot link to multiple sources.

I found a simple python script today that will symlink multiple sources into a single destination directory. It allows all sources to be viewed and accessed together in one single library. The script auto generates all the symlinks very quickly when launched and it also removes old dead links at the same time. I have only used it briefly but it has really impressed me. I have been looking for a way to centralize thousands of movies into one all encompassing movie library. This script seems to accomplish that perfectly.

My python skills are pretty much non-existent. I did not write this script I was merely lucky enough to stumble across it on the internet. This script seemed a natural for a systemd timer to run on a schedule hourly. You could also use a bash alias to run the script and update the library on demand.

These are my notes on how this is best accomplished.

Create the following python script (choose any name you wish):

/home/$USER/.local/bin/movie_library.py

Substitute your username for "$USER" in the above example.

Add the following contents to the script:

#!/usr/bin/env python3
import os
import sys

if len(sys.argv)<3:
    print("target and source not found")
    exit(1)

target = sys.argv[1] 
sources = sys.argv[2:] 

currlinks = os.listdir(target)
compare = []
for dr in sources:
    for f in os.listdir(dr):
        compare.append(f)
        if not f in currlinks:
            # create link
            os.symlink(dr+"/"+f, target+"/"+f)
# clean up possible broken links
for link in currlinks:
    if not link in compare:
        os.remove(target+"/"+link) 

Information regarding customizing and executing the script:

Substitute your username for "$USER" in any examples here.

The script is called via its directory path and then additional arguments for the source directories are passed sequentially.

Script path:

/home/$USER/.local/bin/movie_library.py

"sys.argv[1]" is passed to the script first (which is the destination directory) where you want all source directories symlinked.

/home/$USER/Movies

The following line in the script adds the directories you wish to link together.

sources = sys.argv[2:] 

"target = sys.argv[1]" is the destination path, and it is the first argument. The "sources = sys.argv[2:] line reflects how many sources you wish to symlink into the destination directory. I am symlinking four directories, so the fields will be "2, 3, 4, 5". This is more simply expressed as [2:] which includes all source paths from 2 to the end of the last path specified. Simply add on any path you wish linked into your library directory.

My destination directory for the movie library is:

/home/$USER/Movies

These are my video sources:

/run/media/$USER/6TB_SG18/Movies

/run/media/$USER/6TB_SG18/Recent_Movies

/run/media/$USER/1TB_WD_ext4/Transfers/Movies-Scraped

/run/media/$USER/1TB_WD_ext4/Transfers/Movies-Unscraped

To call the python script via arguments to link four separate locations into one directory:

/python3 /home/$USER/.local/bin/movie_library.py /home/$USER/Movies /run/media/$USER/6TB_SG18/Movies /run/media/$USER/1TB_WD_ext4/Transfers/Movies-Scraped /run/media/$USER/1TB_WD_ext4/Transfers/Movies-Unscraped

The python script can be started as above from the terminal, or from the "ExecStart=" line of a systemd service.

Substitute your username for "$USER" in any of the above examples.

This can be called by a bash alias on demand, or by a systemd timer to update the library contents on a regular schedule.

The script will automatically symlink any new contents added since the last time it was run to the destination directory. It will also automatically delete the symlinks to any files that have been removed since its last execution. The script will display an error on duplicate files but the symlink will still be created and functional.

Create a systemd service and timer to execute the python script at a predetermined interval to update your library contents.

I have chosen to update the Library on an hourly basis. You may alter the time interval if you would prefer this done more or less frequently.

Substitute your username for "$USER" in any of the below examples.

Create the service file:

/home/$USER/.config/systemd/user/library-update.service

With the following contents:

#~/.config/systemd/user/library-update.service
#systemctl --user enable library-update.service
#systemctl --user disable library-update.service
#systemctl --user start library-update.service
#systemctl --user stop library-update.service
#systemctl --user status library-update.service
#systemctl --user daemon-reload

[Unit]
Description=Library service
StopWhenUnneeded=yes

[Service]
Type=oneshot
RemainAfterExit=yes 
ExecStart=/python3 /home/$USER/.local/bin/movie_library.py /home/$USER/Movies /run/media/$USER/6TB_SG18/Movies /run/media/$USER/1TB_WD_ext4/Transfers/Movies-Scraped /run/media/$USER/1TB_WD_ext4/Transfers/Movies-Unscraped


[Install]
WantedBy=multi-user.target

Create the timer file:

/home/$USER/.config/systemd/user/library-update.timer

With the following contents:

#~/.config/systemd/user/library-update.timer
#systemctl --user enable library-update.timer
#systemctl --user disable library-update.timer
#systemctl --user start library-update.timer
#systemctl --user stop library-update.timer
#systemctl --user status library-update.timer
#systemctl --user daemon-reload
#systemctl --user list-timers

[Unit]
Description=Runs library update hourly

[Timer]
OnUnitActiveSec=1h
Unit=library-update.service
OnBootSec=180

[Install]
WantedBy=timers.target

Once you have finished creating the service and timer, enable them both:

systemctl --user enable library-update.timer && systemctl --user enable library-update.service

Then restart.

(edit)

This is a modified version of the above service that also includes a combined library for my network shares mounted in /media.

I have written this service so that it should not fail if the network shares are offline when the service starts.

Service to include mounted network shares:

#~/.config/systemd/user/library-update.service
#systemctl --user enable library-update.service
#systemctl --user disable library-update.service
#systemctl --user start library-update.service
#systemctl --user stop library-update.service
#systemctl --user status library-update.service
#systemctl --user daemon-reload

[Unit]
Description=Library service
StopWhenUnneeded=yes
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes 
ExecStartPre=sleep 20
ExecStart=/home/htpc/.local/bin/movie_library.py /home/htpc/Videos/Movies-Local /run/media/htpc/6TB_SG18/Movies /run/media/htpc/1TB_WD_ext4/Transfers/Movies-Scraped /run/media/htpc/1TB_WD_ext4/Transfers/Movies-Unscraped /run/media/htpc/1TB_WD_ext4/Transfers/Movies-New /run/media/htpc/1TB_WD_ext4/Transfers/Movies-Newer /run/media/htpc/1TB_WD_ext4/Transfers/Movies-Newest
ExecStartPost=-/home/htpc/.local/bin/movie_library.py /home/htpc/Videos/Movies-Remote /media/nfs/3tb/Movies_A-M /media/nfs/4tb/Movies /media/nfs/4tb/Movies-New /media/nfs/4tb/Movies-Newer /media/nfs/4tb/Movies-Newest

[Install]
WantedBy=multi-user.target

Be sure to substitute in your username in any path statements that require modification.

4 Likes

Missing / between $USER and .local :wink:

Thank you @sgs. That must have happened when I edited out my username. Awesome catch.

Have you tested out the script yet it works amazingly well considering I added about 3000 directories to symlink. It created all the symlinks in no time flat.

1 Like

No, sorry :wink: I am on my Tablet and admire your work. :slight_smile:
Typos always occur and force beginners to think. :smiley:

1 Like

no, we can get parameters 2 to "end"

if len(sys.argv)<3:
    print("target and source not found")
    exit(1)

target = sys.argv[1] 
sources = sys.argv[2:]
  • add test if source directory exists (mounted)
  • make a recursive find in source directory
  • why ExecStart=/bin/bash -c "/home/htpc/.local/bin/movie_library.py and not ExecStart=/home/htpc/.local/bin/movie_library.py ... ?

1 hour for film ? for me is best 7 days :smile: and for me is most usefull for images ...

3 Likes

Hey I said to adjust the time however you like. :smile:

I like to make sure I don't duplicate stuff when my memories not so good, and I've got files scattered in a bunch of different places.

How the heck did you get so damn good at all this stuff @papajoke. Maybe one day I could dream to be half as good as you. As I mentioned I know very little about python, so all your tips are greatly appreciated.

I will adjust it as you suggested from 2 to end. That definitely makes it simpler for people testing the script.

Why? because as I said I know next to nothing about python, so I'm kind of fumbling around in the dark. If I get things to work at all I'm thrilled. I don't know best practices. I'm just getting some practice. :smile:

I used "bash -c" because I thought that was the best method for multiple arguments containing white spaces. I will modify it to this if you think this is preferable:

python3 /home/htpc/.local/bin/movie_library.py /home/htpc/Movies /run/media/htpc/6TB_SG18/Movies /run/media/htpc/1TB_WD_ext4/Transfers/Movies-Scraped /run/media/htpc/1TB_WD_ext4/Transfers/Movies-Unscraped

Thanks again for all the advice.

3 Likes

Forum kindly sponsored by