nix-community / home-manager

Manage a user environment using Nix [maintainer=@rycee]
https://nix-community.github.io/home-manager/
MIT License
6.79k stars 1.78k forks source link

Add proper support for systemd templated services #5557

Open ItsDrike opened 3 months ago

ItsDrike commented 3 months ago

Description

A systemd templated service is one that ends with @ and can be enabled multiple times with various values.

However, while I am able to define the service with home-manager, I'm not sure how to declaratively enable it for specific values. This is what I have:

    systemd.user.services = {
      "eww-window@" = {
        Unit = {
          Description = "Open %I eww (ElKowar's Wacky Widgets) window";
          After = [ "eww.service" ];
          PartOf = [ "graphical-session.target" ];
        };

        Service = {
          Type = "oneshot";
          RemainAfterExit = true;
          ExecStart = "${pkgs.eww}/bin/eww open %i";
          ExecStop = "${pkgs.eww}/bin/eww close %i";
          Restart = "on-failure";
        };

        Install.WantedBy = [ "graphical-session.target" ];
      };
    };

Current behavior with the above

This will create the eww-window@ user service and it does get put into ~/.config/systemd/user/eww-window@.service as expected, however, it also gets put into ~/.config/systemd/user/graphical-session.target.wants/eww-window@.service, which doesn't make sense, as only the specific variants for this service should be getting enabled for the target.

It's not really possible to avoid this though, since home-manager will put it here whenever Install.WantedBy is set, though this setting is necessary to be in this template service, as the specific services that rely on this template are just symlinks to it, so this sections needs to be present, even though this service on it's own shouldn't be getting activated, but rather only it's variants.

That said, even though it's weird to have just the eww-window@.service in graphical-session.target.wants, it doesn't seem to cause any issues, so it's probably fine.

Manual enabling

I can now enable the various variants manually with a command, like: systemctl enable --user eww-window@bar0. Running this results in the following output:

Created symlink /home/itsdrike/.config/systemd/user/eww-window@bar0.service → /nix/store/l4jk087fm76p6vlkkmj971swr0xwm8hq-eww-window-.service/eww-window@.service.
Created symlink /home/itsdrike/.config/systemd/user/graphical-session.target.wants/eww-window@bar0.service → /nix/store/l4jk087fm76p6vlkkmj971swr0xwm8hq-eww-window-.service/eww-window@.service.

Notice that it made another service in ~/.config/systemd/user/ that's called eww-window@bar0.service which is just a symlink to the original eww-window@.service. This however isn't even needed in this case, it would be enough to just make the ~/.config/systemd/user/graphical-session.target.wants/eww-window@bar0.service. I'm not sure why it's making this symlink here.

That said, this isn't really a major issue either and it might just be something to do with systemd (though I know that on Arch linux, the link would only be in graphical-session.target.wants).

Core issue

The issues above are pretty minor and aren't really that important though. The main problem with this is that there is no way to declaratively enable this service for whatever values I need. It's necessary to do this manually with systemctl.

I have found https://github.com/NixOS/nixpkgs/issues/80933, which does have a fix for system services, where it's possible to use overrideStrategy = "asDropin": and define a service like this:

{
  systemd.services."systemd-nspawn@archlinux" = {
    wantedBy = [ "machines.target" ];
    overrideStrategy = "asDropin";
  };
}

But this kind of setup doesn't work with home-manager and at least from the docs I found, I didn't see this being supported in any way.

I feel like this is a pretty important issue though, especially for someone like me who also uses home impermanence, so my home directory is getting wiped between each reboot, taking the manually made symlinks with it.

rycee commented 3 months ago

I think template services simply hasn't been very important in a HM setup since Nix is a decent templating language by itself. That is, typically one would simply directly generate the desired unit definition. Something like

systemd.user.services =
  let
    eww = name: {
      "eww-window-${name}" = {
        Unit = {
          Description = "Open ${name} eww (ElKowar's Wacky Widgets) window";
          After = [ "eww.service" ];
          PartOf = [ "graphical-session.target" ];
        };

        Service = {
          Type = "oneshot";
          RemainAfterExit = true;
          ExecStart = "${pkgs.eww}/bin/eww open ${name}";
          ExecStop = "${pkgs.eww}/bin/eww close ${name}";
          Restart = "on-failure";
        };

        Install.WantedBy = [ "graphical-session.target" ];
      };
    };
  in eww "bar0" // eww "bar1";
ItsDrike commented 3 months ago

The issue is that I do have some need for the dynamicness here, as I'm starting barN service whenever a new monitor gets plugged in and only bar0 should be enabled by default (I'm on a laptop, and I often switch between docked/undocked modes).

This is probably a pretty rare case, I do understand that, but I do think that there could be benefit in supporting template services. For example I've seen people have services that they run like systemctl start --user myservice@$(date) to distniguish them, and this simply needs to be dynamic, it's not always possible to template everything in nix.

I can hack my way through this and especially in this case, where I can just call the eww commands manually in the script that gets run when I plug monitors in (though the advantage of this is that I can enable/disable systemd services from the script, not just start/stop, and if I restart eww service, these units will be restarted too, re-opening my bars), but I do believe that support for this could be beneficial to more people than just me.

nyarly commented 2 months ago

I have an ongoing usecase for Systemd template units - I have a hacky fork of the polybar service so that I can have a polybar for each screen. Since screens change based on what's plugged into the computer, the only solution I've found for this is to use polybar@ and then have my arandr wrapper enable screens as they're attached etc.

nyarly commented 2 months ago

My biggest current irritation with the level of support for templated units is that switch will report Failed to get properties: Unit name polybar@.service is neither a valid invocation ID nor unit name.