How to enable upstream systemd user services declaratively?

OK so this is a bit emberrasing, as I’m using NixOS for more then a year and only now I’ve stopped to think that maybe my current usage of systemd services is not good.

So basically, if I run systemctl --user enable <this> I can enable user systemd services provided by packages. This creates a symlinks such as:

lrwxrwxrwx 1 doron developers 95 Jun 12 13:31 default.target.wants/pulseaudio.service -> /nix/store/j9sg853np8czrvdsfg6qdbmn599h3370-pulseaudio-13.0/lib/systemd/user/pulseaudio.service
lrwxrwxrwx 1 doron developers 95 Jun 12 13:31 pulseaudio.service -> /nix/store/j9sg853np8czrvdsfg6qdbmn599h3370-pulseaudio-13.0/lib/systemd/user/pulseaudio.service

But as you can see, the symlinks are absolute, meaning that nix-collect-garbage -d might remove kill the symlinks and then I’ll have to run systemctl --user reenable pulseaudio, not to mention the fact that the service that the symlink targets to, probably points in ExecStart= to the binary of the old pulseaudio…

The workaround I thought of was a script that will run systemctl --user reenable for every service I enable. And I have to run this script after every time I collect garbage.

But that’s absurd right? I could create the symlinks by myself and point them to /run/current-system/ but that means I’m not enjoying systemctl. Is it possible to use the system’s configuration.nix to do declaratively do it? Or perhaps there’s a nicer way to handle these symlinks?

I’d like to avoid using home-manager because it’s features are too bloat for me.

pulseaudio in particular has a NixOS module which you can simply enable to have the user service running. Others may have something similar.

I’m aware of this module. Though I noticed that I have to systemctl start pulseaudio in order for pulseaudio to actually run. Perhaps your experience is different, possibly due to a different xorg related settings or DE specific behavior.

That’s weird. Unless something modifies the service configuration from the side, it should just start for each user according to the source. Which release are you on?

Hmm now I tested and it seems you are correct, perhaps this was a false conclusion - that I need to enable the services by myself explicitly.

I guess the solution then is to put in my configuration.nix exactly what these services do when I enable them.

However, what I’m still unsure of, is whether service files given by upstream packages are playing along with systemd.user.services - I mean, what do I need to do if I want to enable for all users a systemd service provided by a certain package? Without any changes to it? Is systemd.packages = [ pkgs.<myPkg> ]; supposed to be enough?

I think that the answer is yes according to:

https://github.com/NixOS/nixpkgs/blob/2cd6594a8710a801038af2b72348658f732ce84a/nixos/modules/system/boot/systemd-lib.nix#L177-L198

But it doesn’t seem documented in the docs of systemd.packages?

Not the prettiest approach, nor is it declarative as you requested, but it Works For Me™ (and is independent of the system configuration): I manually symlinked service definitions from .nix-profile:

$ ls -l .config/systemd/user/syncthing.service
lrwxrwxrwx 1 linus users 56 Oct  7  2018 .config/systemd/user/syncthing.service -> ../../../.nix-profile/lib/systemd/user/syncthing.service

(as you can see, I’ve been running on this approach for a little while now)

And only then running systemctl --user daemon-reload and systemctl --user enable syncthing.service.

1 Like

This is the approach I’m testing right now. It seems to be good enough at the moment, so thanks for explaining it. It should be possible though, to replicate what e.g pulseaudio’s module does in declaratively. I noted my self to investigate it and document this in the manual.

1 Like

Since this wasn’t clarified, pulseaudio and pipewire are socket activated, meaning when a client demands access to the service, the service starts, they’re not meant to be “enabled” to launch on login / boot.

I don’t know if you’re still having the problem, but I think it’s worth noting this is how it’s meant to work.

If you want a work around, consider using system.userActivationScripts.

I’ve found myself confused by user services in NixOS, so I want to share what I have learned:

User unit files are installed in /etc/systemd/user/. This is kind of like a “library” of available user services on the system. You can add the user units from packages into the installed set with the systemd.packages option:

{
  systemd.packages = with pkgs; [ ydotool ];
}

User services typically have an [Install] section which is essentially a set of instructions for what to symlink in .config/systemd/user when systemctl --user enable <service>. For example, from ydotool.service:

[Install]
WantedBy=default.target

If you enable the service, this creates a symlink .config/systemd/user/default.target.wants/ydotool.service -> /etc/systemd/user/ydotool.service. NixOS doesn’t look at the [Install] section, so you might think (as I did) that you have to run systemctl --user enable yourself for every service, or create the relevant symlinks yourself.

That’s (thankfully) not true! You can declare the contents of the [Install] section in your NixOS configuration:

{
  systemd.packages = [ pkgs.ydotool ];
  systemd.user.services.ydotool.wantedBy = [ "default.target" ];
}

No manual symlinks or enable steps are needed. This adds symlinks to the default.target.wants directory in /etc/systemd/user. So, more than being a “library” of user units, it’s also a “base configuration” of user units for all users.

2 Likes

Thanks for sharing that @Majiir ! I think your idea can be a good addition to this section in the manual: