tl;dr How can I set the correct PATH
for systemd user units before starting a session?
The problem
To bind mount some directories into my home directory I have the following systemd user unit:
[Unit]
Description=Bind mount /scratch/%u/%I
ConditionPathIsDirectory=/scratch/%u/%I
ConditionPathIsDirectory=%h/%I
ConditionPathIsMountPoint=!%h/%I
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/env bindfs --no-allow-other "/scratch/%u/%I" "%h/%I"
ExecStop=/usr/bin/env fusermount -u "%h/%I"
[Install]
WantedBy=default.target
This unit is stored in my home directory at ~/.config/systemd/user/bindfs@.service
and it has to be stored there because I use this config on multiple machines, not all of which have NixOS installed.
However, this unit completely fails to run on NixOS with the error:
/usr/bin/env: ‘bindfs’: No such file or directory
This is due to the fact that on NixOS systemd starts out with PATH=/no-such-path
, as introduced by this commit
https://github.com/NixOS/systemd/commit/bdfe641e4b713ce77d65c411fc589144d6f58849
Generally this makes sense for declarative service management but makes it entirely impossible to do any kind of old-fashioned stateful service management as is common in configurations that are used on different machines. This comes as kind of a surprise to me because this way it is not even possible to access programs installed via environment.systemPackages
which should be available to all users (at least according to the docstring in system-path.nix
).
Attempted workarounds
In my configuration.nix
I have already set
{
environment.extraInit = lib.mkAfter ''
[ -f ~/.profile ] && . ~/.profile
${config.systemd.package}/bin/systemctl --user import-environment PATH
'';
}
to import my local environment variables into the shell and the service manager for interactive logins (graphical or ssh). However, this snippet is only sourced when the login shell is started which happens after systemd starts the user session which means that the aforementioned user unit will not see the updated PATH
. In fact, when I restart the failed unit manually after my shell prompt has loaded
$ systemctl --user restart bindfs@data.service
it succeeds because PATH
has been updated. Now I could of course add this command to my .bashrc
or whatever but this completely defeats the purpose of systemd user services.
So far I was unable to find a solution to this problem. It is also kind of a hen-and-egg problem because I’d like systemd to inherit PATH
which can happen earliest in the shell init script but spawning the shell is done my the systemd user service manager (As you can see by typing systemctl status
. Your login shell is nested under user-<UID>.slice
).
Notes
As an aside, for a graphical login (at least via GDM) you don’t get a login shell and therefore none of the shell init files are sourced. To source your .profile
and import the path into the service manager I added the same snippet as above to the sessionCommands
:
{
services.xserver.displayManager.sessionCommands = ''
[ -e ~/.profile ] && . ~/.profile
${config.systemd.package}/bin/systemctl --user import-environment PATH
'';
}