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 mnml 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 mpd
, picom
, 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:
battery_alert.service
:
[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
ExecStart
andExecStop
executable - The inclusion of
DISPLAY
andXAUTHORITY
– this is so thenotify-send
reaches dunst - The setting of
PATH
such 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 --user
:
# 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:
battery_alert.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:
ExecStart=/path/to/check_battery
And bam! we have done the thing.