Is it possible to define systemd services in a submodule?

Is it possible to define top-level systemd config inside a submodule?

To elaborate more on the question, let’s say you define a module that has one option which is a submodule and is responsible for defining the systemd config of one type of service. Then you have another submodule responsible for defining the systemd config of another type of service. Since systemd is top-level config, it will not be accessible inside the submodule.

Is there a way of making it accessible?

I know it is possible to just flatten the structure and define one module called instance1 and one module called instance2, but when working with many module-instances it is beneficial to create a hierarchical structure so that the config is more easily organized.

Appreciate all help :slight_smile:

{ config, lib, ... }:
with lib;
with types;
{
  options.myModule = {
    instance1 = mkOption {
      type = submodule {
        options = {
          # Define instance1 options here
          # ...
          # ...
        };
        config = {
          systemd.services = {
            # Define instance1 systemd service-config here
            # ...
          };
        };
      };
    };
    instance2 = mkOption {
      type = submodule {
        options = {
          # Define instance2 options here
          # ...
          # ...
        };
        config = {
          systemd.services = {
            # Define instance2 systemd service-config here
            # ...
          };
        };
      };
    };
  };
}

is there a problem with defining systemd services for each of the instances inside your top level module config, or you just want to structure it that way for mental clarity?

Both for mental clarity and for the possibility of bulking systemd services together. I want to have an enable-option in my module which gets inherited to the instances. The idea is that i can enable/disable multiple systemd services at a time.

1 Like

generally no you can’t do that

the main options you have are too create multiple systemd services with something like lib.mapAttrs, lib.genAttrs, etc…, or using systemd templates - let me know if you need any clarification on that


i will mention for fun that @roberth had some interesting ideas in RFC 163 to make something similar to what your want a possibility

The short answer is indeed “no”, but you could manually forward information from a submodule to systemd.services and any other options that you want the submodule to affect.

@nbp and I have discussed such a feature before:

However, this feature was not merged, and so “supermodules” are still a hypothetical feature, or perhaps an implementation pattern where a module forwards generic definitions from submodules to “cousin” options outside those submodules.
I think the PR stalled because it is non-trivial, yet does not add functionality that could not be expressed before.

RFC 163

Here’s an example of copying information from a submodule to its destination - systemd.services. Because that information is in the form of whole modules, it’d be quite a generic mechanism.

1 Like

Thank you for your help! I looked into the “supermodules” idea and agree that it, if available, could be very beneficial. Currently i have found a work-around where i create a systemd service for each instance in the top-level module by using the instance-options and passing them through a “mkServiceConfig” function. However, this becomes difficult especially when having multiple submodule “layers”.

{ config, lib, ... }:
with lib;
with types;

let 
  cfg = config.myModule;

  mkServiceConfig = name: instance: 
  mkIf instance.enable {
    "${name}" = {
      wantedBy = [ ... ];
      # Systemd config here
      # ...
    };
  };
in {
  options.myModule = {

    enable = mkEnableOption "MyModule";

    instances = mkOption {
      type = attrsOf submodule ... ;
    };
    
  };

  config = {
    systemd.services = (mkMerge (
      map
        (name: mkServiceConfig name cfg.instances."${name}")
        (attrNames cfg.instances)
    ));
  };
}