systemd user services
This is a text version of a lighting talk I gave at recurse center Feb 2020.
Back when I was first getting into Linux, I had arch installed on my laptop that I was using for schoolwork, and was trying all the latest ｍｎｍｌ window managers, which meant there was no builtin display for how much battery I had left.
Some googling showed me that you could do the following to get the charge on Linux:
$ cat /sys/class/power_supply/BAT0/capacity 92
That’s a pain to type out, so I aliased it to something spam-able like
a and called it a day. But then… surprise shutdowns still happened on occasion. So I just got into the habit of spamming checking all the time due to laptop FOMO. This is not ideal.
Next step was to wrap the above in a script and display it in my panel. Nice! now the battery status is always a glance away. BUT! pixel real estate is precious on my 1366 x 768 resolution, so I would often hide the panel or full-screen something like media, or my text editor. We are back where we started.
1 Alerts ⚓
With something like dunst, I can send notifications that appear only when I’m concerned with them. This birthed a script that would only bother me if my battery was below a certain level, while polling periodically. I decoupled that last part into a script named
periodically, so ended up with a
battery_alert daemon-like script that looked like
periodically 60 check_battery.
Making this call in my
.xinitrc worked out OK, but I’m sometimes at a desktop using the same set of dotfiles, and there’s no battery to check there. About this point I realized I had stuffed a lot in my startup, such as
dunst, you name it. I had heard of systemd user services and decided to look into them.
2 The big D ⚓
Systemd user services are regular systemd service files that live in
$HOME/.config/systemd/user. A service file matching the above problem might look like this:
[Unit] Description=Notify when the battery is low [Service] ExecStart=/path/to/bin/periodically 60 check_battery ExecStop=/path/to/bin/kill -int $MAINPID Environment=PATH=/paths/that/contain/check_battery:...:.... Environment=DISPLAY=":0" Environment=XAUTHORITY="/home/neeasade/.Xauthority" [Install] WantedBy=default.target
Some things to note:
- The full path to the
- The inclusion of
XAUTHORITY– this is so the
- The setting of
PATHsuch that “the usual suspects” are available within my daemon script
With the above I get the systemd goodness but for managing my local stuff, just passing
# start, stop systemctl --user start battery_alert systemctl --user stop battery_alert # logs (can also tail theme) systemctl --user status battery_alert # automatic startup systemctl --user enable battery_alert
So the next step in this systemd journey was to integrate service generation into my dotfiles setup, generating a number of service files that contain full paths to binaries with intended arguments for a range of daemons.
3 The big.. T? ⚓
Those of you in the know will see something very wrong with the above. Systemd has a “when to run” concept: timers! Using timers files you can do things like:
- run on boot
- run on login
- run once per day/once per time interval
- run periodically
Let’s take a look at what that last one looks like. You create a file in the same directory with the same name, but extension timer:
[Unit] Description=Run check_battery every 60 seconds [Timer] # on boot # OnBootSec=0min # on service manager activation OnStartupSec=0min OnUnitActiveSec=60s [Install] WantedBy=timers.target
See the reference for time-span syntax here
And then we would change the
ExecStart in our
battery_alert.service file to:
And bam! we have done the thing.