Start a systemd service in the context of the user that starts it

I use sxhkd as my hotkey manager, and I launch it at the start of the system as a systemd user service (i.e. the service file is in the ~/.config/systemd/user/). Among other things it manages a hotkey to launch rofi launcher, from which I launch most applications.

However, the apps launched this way do not have environment variables set to values of this user, and it causes many quirks.

For example, Cherrytree (a note-taking app) has a function for creating “Today’s Node”, and the name of this node is created according to $LC_TIME variable. So if this variable is ru_*, the node name for today is “21 Ср” in an upper-level node of “сентября”. If this variable is en_*, then the node name for today is “21 Wed” in an upper-level node of “September”. My actuial $LC_TIME value is en_DK.UTF-8, so I expect the node names to be in English. However, during the installation Calamares set it to ru_RU.UTF-8 (I changed it to en_DK.UTF-8 after the installation). And when I launch Cherrytree via rofi (which is launched by a hotkey triggered by sxhkd), “Today’s Node” is created in Russian! Obviously, sxhkd for some reason is running in the context where $LC_TIME has the value that was automatically set by Calamares during the installation.

That is only one example, there are many other variables that have wrong values (i.e. not those set for that user) and it causes quirky behavior for the apps launched that way. However I want to use both rofi and sxhkd because they are very good programs, and it seems that since the systemd service for starting sxhkd is run as a user service, it should be trivial to give it access to all the user’s variables, but I can’t find how.

Why I don’t start sxhkd by creating a desktop file and putting it into autostart? Because I still regularly edit sxhkdrc, so I need a way to make sxhkd to reload its configuration, and the most convenient way is by systemctl --user restart sxhkd.service.

Here is my sxhkd.service file:

[Unit]
Description=Simple X Hotkey Daemon
Documentation=man:sxhkd(1)

[Service]
Environment="SCREENDIR=%h/Pictures/Screenshots"
ExecStart=/usr/bin/sxhkd
Restart=on-failure
RestartSec=3
KillMode=process

[Install]
WantedBy=default.target

You can add extra Environment=VARIABLE lines to add the variables you need; alternatively, you could put them all in a file and I think load that with EnvironmentFile=/path/to/file

Yes, I have one such variable there — Screendir, but there is a lot of variables in the system, and I don’t know which of them I’m gonna need for that file. It took a lot of time before I realized why Cherretree was creating nodes in Russian. Or do I put just all of the system variables in the EnvironmentFile? Yes, and then manage them in two places: where they are set for the rest of the system, and in this file. Change one of them in one place, forget the other, and then wonder why some new quirk arose.

Well, actually it would be 3 places because user variables are already divided between .bashrc (or .zshrc in my case) for console programs and .profile for GUI ones. And now it would be three! Damn, I don’t like Windows, but one of the things that it does right is that all environment variables are in one place.

I would personally put all the variables I need in the .service file, then I would be less likely to lose track of them. I’ve never needed many environment variables for my services, so have always just put one or two in the .service file as needed. If I had a lot that I needed, I might be more tempted to put them in a separate EnvironmentFile file or override .conf in the service directory (but that’s just me).

I’m not familiar with sxhkd or what variables you may need (aside from those you have mentioned), but maybe the XDG variables would be a good base? You can check which are loaded for your user with:

$ env | grep XDG

Maybe this helps: systemd/User - ArchWiki

1 Like

Thanks, that helped! I didn’t know that you could have conf files in ~/.config/environment.d/ that are automatically used by all user services. Of course, just putting some variables there manually wouldn’t have solved the problem of manually managing user variables in several different places, so I decided to automate it this way:

I created a dump_env_vars.service with this content:

[Unit]
Description=Dump all environment variables into a conf file in .config/environment.d/

[Service]
Type=oneshot
ExecStart=%h/bin/dump_env_vars.sh

[Install]
WantedBy=sxhkd.service

It launches dump_env_vars.sh with this content:

#!/bin/sh

IFS='
'

FILES=( ~/.xprofile ~/.xinitrc ~/.xsession ~/.profile )
tmp_file=$XDG_RUNTIME_DIR/tmp_env_vars
end_file=$HOME/.config/environment.d/autogenerated.conf

rm $end_file
rm $tmp_file

for file in "${FILES[@]}"; do
	grep "^export " $file | sed 's/^export //' >> $tmp_file
done

sort --unique $tmp_file > $end_file
sleep 1
systemctl --user daemon-reload

This creates $HOME/.config/environment.d/autogenerated.conf file and auto-populates it with all the global variables from the files listed in the script.

And finally, I added an “After” directive to the sxhkd.service, to make it load only after the autogenerated.conf is … well, autogenerated :slight_smile:

[Unit]
Description=Simple X Hotkey Daemon
Documentation=man:sxhkd(1)
After=dump_env_vars.service

[Service]
# ExecStartPre=%h/bin/dump_env_vars.sh
ExecStart=/usr/bin/sxhkd
Restart=on-failure
RestartSec=3
KillMode=process

[Install]
WantedBy=default.target

First I just used ExecStartPre directive (it is still in the file but commented out now), but later switched to having a separate unit specifically devoted to creating the autogenerated.conf. That’s because I decided that later I might have more than one service that needs my environment variables, so it would be more correct way to load all these units after the environment-generating unit than to run ExecStartPre in each separate unit.

I have tested it and everything seems to be working as intended.

Interesting approach. Well done.

I don’t recall how I did it, maybe I did nothing and it is out of box, but when I list user environment variables (with systemctl --user show-environment) they are nearly the same as when I list environment variables in terminal (with env). On brief investigation, only the environment variables set in my .zshrc are missing.

I tried to investigate, where they get loaded, but found no clue. :frowning: Maybe it’s zsh or GNOME feature.

More likely Gnome, I also use zsh and in my case when I disable the solution that I describe above and reboot, my output of systemctl --user show-environment is quite short and many values are different from env output.