Module with a systemd service option

Hello. I started using Nix OS a few days ago. Yay! Anyway, I’m trying to make a module that has an option for overriding its systemd service. Here’s the code:

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

let
  cfg = config.programs.spotify;
in
with utils;
with systemdUtils.lib;
with systemdUtils.unitOptions;
with lib;
with types;
{
  options.programs.spotify = {
    enable = mkEnableOption "Spotify desktop application";

    daemon.enable = mkEnableOption "Spotify Daemon (spotifyd)";

#    daemon.users = mkOption {
#      description = "Users to enable the daemon for.";
#      type = listOf str;
#      default = attrNames config.users.users;
#    };

    daemon.service = mkOption {
      description = "systemd service.";
      type = submodule [ stage2ServiceOptions unitConfig stage2ServiceConfig ];
      #type = attrs;
      default = {
        name = "spotifyd";
        description = "Spotify daemon";
        documentation = [ "https://github.com/Spotifyd/spotifyd" ];
        wants = [ "sound.target" "network-online.target" ];
        after = [ "sound.target" "network-online.target" ];

        wantedBy = [ "default.target" ];

        serviceConfig = {
          ExecStart = ''${pkgs.spotifyd}/bin/spotifyd --no-daemon --config-path=%h/.config/spotifyd'';
          Restart = "always";
          RestartSec = 12;
        };
      };
    };
  };

  config = {
    environment.systemPackages =
      with pkgs;
      optionals cfg.enable [
        spotify
        spotifywm
      ] ++ optional cfg.daemon.enable
        spotifyd;

    systemd.user.services =
      optionalAttrs cfg.daemon.enable {
        spotifyd = cfg.daemon.service;
      };
  };
}

But I get: “error: The option `programs.spotify.daemon.service.startLimitIntervalSec’ is used but not defined.” (Also for startLimitIntervalBurst) when using “type = submodule [ stage2ServiceOptions unitConfig stage2ServiceConfig ]” which I deduced from reading the source code. This seems to be because startLimitIntervalSec and startLimitIntervalBurst lack defaults in their definitions. This was discussed here and decided here.

This is all very frustrating because the only code that seems to be using these options seems to be checking if they’re defined or not:

at /nix/var/nix/profiles/per-user/root/channels/nixos/nixos/lib/systemd-lib.nix:392:12:

  391|           OnSuccess = toString config.onSuccess; }
  392|         // optionalAttrs (options.startLimitIntervalSec.isDefined) {
     |            ^
  393|           StartLimitIntervalSec = toString config.startLimitIntervalSec;

Da hec? Why would checking if an option is defined require it to be defined?

If I use “type = attr;” it works fine, but I would prefer to have better type checking than that.

Full log here: https://pastebin.com/DKQZrakP

I would skip the indirection that your programs.spotify.daemon.service option adds and just set what is now its default value directly in systemd.user.services.spotifyd. Then if users of your module want to override specific settings on the service they can do so by overriding the individual fields in systemd.user.services.spotifyd.<field>.

1 Like

Yep. That makes perfect sense. Why didn’t I think of that?

I’d still like to know why it doesn’t work the way I was trying to do it.