Instantiated systemd services

Is it possible to write instantiated systemd services?

If so, can someone point me at an example? Then how would you pass the template declaritively?

1 Like

on nixos, we configure systemd services though configuring nixos modules.

Here’s the alsa modules. The rest of the file is to ensure the options are exposed correctly and configuration gets propagated to other modules

2 Likes

Am I missing something or is that a normal systemd service? What about an instantiated service? Is that possible?

1 Like

Disclaimer: I’m not a systemd guru.

As far as I’m aware, instantiated services are just services which are able to take an argument. Generally services don’t need to be passed arguments in nixos, because the configuration and options are made through the nixos module options, thus we don’t need another mechanism to pass additional configuration.

1 Like

OK, trying to wrap my head around the right way to do this. So if you had a service for which you wanted to enable an arbitrary number systemd services of “named” instances you would somehow do that with submodules that each created a systemd service?

out of my expertise, sorry. I’m sure it’s possible, I just don’t know how.

1 Like

The phpfpm module is an example of this: nixpkgs/default.nix at a7d55dd3b7e80e446a6d5e3801fd13d1dab32a3f · NixOS/nixpkgs · GitHub

1 Like

See github pr 77734 …

1 Like

Thanks for this. It seems like the most pure nix approach. With my current experience level of writing nix expressions, it will take me some time to parse through all that to learn how it works and be able to replicate it but it sounds like time well spent.

I suppose the alternative would be to modify the systemd module to support enabling instantiated services. To be fair, I am not sure that would have much advantage over the above approach. It would just be easier for people less familiar with nix.

Thanks for pointing this out, it looks like this avoids the issue of not being able to enable an instantiated service by enabling a normal service that starts the instantiated services.

Feel free to ask any questions if you get stuck.

1 Like

Well…thanks for the help everyone. It turns out that was surprisingly easy once I realized what it was doing.

Here is a trivialized example that may be helpful to someone in the future:

{ config, lib, pkgs, ... }:

with lib;

{
  options.services.testService = {
    enable = mkOption {
      type = types.bool;
      default = false;
      description = ''
        If the testService should be enabled
      '';
    };

    instances = mkOption {
      description = "An instance";
      type = with types; attrsOf (submodule {
        options = {
          enable = mkOption {
            type = types.bool;
            default = false;
            description = ''
              If the instance should be enabled
            '';
          };

          location = mkOption {
            type = types.nullOr types.path;
            default = null;
            description = ''
              The path to the files
            '';
          };
        };
      });
    };
  };


  config = mkIf config.services.testService.enable {

    environment.systemPackages = [ pkgs.jre ];

    systemd.services = mapAttrs' (name: instance:
      nameValuePair "testService-${name}" {
        description = "Manager service for ${name}";
        wantedBy = [ "multi-user.target" ];
        serviceConfig = {
          Type = "oneshot";
          RemainAfterExit = true;
          ExecStart = "${pkgs.jre}/bin/java -jar ${instance.location}/test.jar";
        };
      }
    ) config.services.testService.instances;
  };
}

It is worth noting that the “enable” in the submodule isn’t used because I haven’t quite figured out how to make that work.

2 Likes

As for the “enable” option I’ve defined a enabledInstances as filterAttrs (_: instance: instance.enable) config.services.testService.instances and used it instead.