Infinite recursion when replicating service

I noticed some behaviour I cannot explain. This line causes infinite recursion, apparently:

config.systemd.services.asdasdasd = config.systemd.services.yxcyxcyxc;

It doesn’t matter whether the service on the right exists or not, there is an infinite recursion regardless.

Is there any way to avoid this? I need to clone a service with a small modification.

Share the actual full error.

Sorry:

error:
       … while calling the 'head' builtin
         at /nix/store/6wpzhvnb026lab4b2pzph2qlql4zwasa-source/lib/attrsets.nix:1581:13:
         1580|           if length values == 1 || pred here (elemAt values 1) (head values) then
         1581|             head values
             |             ^
         1582|           else

       … while evaluating the attribute 'value'
         at /nix/store/6wpzhvnb026lab4b2pzph2qlql4zwasa-source/lib/modules.nix:1118:7:
         1117|     // {
         1118|       value = addErrorContext "while evaluating the option `${showOption loc}':" value;
             |       ^
         1119|       inherit (res.defsFinal') highestPrio;

       … while evaluating the option `system.build.toplevel':

       … while evaluating definitions from `/nix/store/6wpzhvnb026lab4b2pzph2qlql4zwasa-source/nixos/modules/system/activation/top-level.nix':

       … while evaluating the option `assertions':

       … while evaluating definitions from `/nix/store/6wpzhvnb026lab4b2pzph2qlql4zwasa-source/nixos/modules/system/boot/systemd.nix':

       … while evaluating the option `systemd.services':

       … while evaluating definitions from `/nix/store/kz5gsmljfkyjsnwlcgc85fff6hg3jvj2-source/modules/mosquitto-bridge.nix':

       (stack trace truncated; use '--show-trace' to show the full, detailed trace)

       error: infinite recursion encountered
       at /nix/store/6wpzhvnb026lab4b2pzph2qlql4zwasa-source/lib/modules.nix:1118:7:
         1117|     // {
         1118|       value = addErrorContext "while evaluating the option `${showOption loc}':" value;
             |       ^
         1119|       inherit (res.defsFinal') highestPrio;

Okay, that does seem to point to this definition being an issue.

Are you sure that this is exactly how you wrote it?

Or did you have config.systemd.services = { ... } or some other format?

image
I did not beautify the statement in any way.

Does the same statement cause an infinite recursion on your system?

Yes, it infrecs for me as well.

It’s because the full list of systemd services somehow depends on the assertions/warnings in the systemd module, which depends on the full list of services. There’s probably some other circular dependencies there too.

It also would be simply wrong to do, even if it did work, because name, PATH, etc. need to be different per systemd service,

Simple answer is don’t do this.

You could probably get away with something more like (only including whichever attrs you care about):

              systemd.services.abc = {
                inherit (config.systemd.services.xyz)
                  after
                  aliases
                  before
                  bindsTo
                  conflicts
                  description
                  documentation
                  notSocketActivated
                  onFailure
                  onSuccess
                  overrideStrategy
                  partOf
                  postStart
                  postStop
                  preStart
                  preStop
                  reload
                  reloadIfChanged
                  reloadTriggers
                  requiredBy
                  requires
                  requisite
                  restartIfChanged
                  restartTriggers
                  script
                  scriptArgs
                  serviceConfig
                  startAt
                  stopIfChanged
                  upheldBy
                  upholds
                  wantedBy
                  wants
                  ;
              };

Thanks for your research!

I don’t quite understand though.
name is different, in my example, right?
And why does path need to be different?

Thanks!

No, you’re overwriting the name attribute. For systemd.services.asdasdasd, name is normally "asdasdasd.service", you’re overwriting it with "yxcyxcyxc.service" with the same priority, that is both incorrect and also will cause an eval error.

Correcting myself - it should be environment that is set uniquely. You can inherit path.

Non-mergeable types must have unique definitions. environment contains str which is non-mergeable. You can’t have two definitions of a non-mergeable type with the same priority - you need to raise the priority of one to give it precedence. And the systemd module already sets a default environment.PATH, so you can’t set it at the same priority - though you can set it at a higher one.

Thanks for the explanation!

I don’t quite understand what the mechanism is that overwrites the name though…

I would expect both keys to be present in services?

I don’t understand your question.

If you have a.name and b.name, then a = config.b will set everything underneath.

The “mechanism” is the code you wrote.

In addition to the reasons you specified here, there’s also the problem that you’re taking the post-merge values for all these options, then merging them into more options, with defaults and other potential interactions thus happening twice.

I rather imagined it like

{
  services = {
    yxcyxcyxc = {
      ExecStart = "...";
    };
  };
}

and after the assignment:

{
  services = {
    yxcyxcyxc = {
      ExecStart = "...";
    };
    asdasdasd = {
      ExecStart = "...";
    };
  };
}

There are a lot more values under there than you think. Some of them are calculated in terms of others or set automatically. You’re not just replicating data here.

1 Like

Okay I see the confusion.

config on the left-hand side is just a top-level module attribute. That indicates that you want to define some option in the NixOS configuration.

config on the right-hand side is a completely different thing - it’s a module argument that refers to the finally-merged values of all of the modules in the NixOS configuration, including modules defined in nixpkgs itself and anything else you add to imports.

For example:

$ nixos-rebuild repl
[...snip...]

nix-repl> config.systemd.services.polkit
{
  after = [ ];
  aliases = [ ];
  before = [ ];
  bindsTo = [ ];
  confinement = { ... };
  conflicts = [ ];
  description = "";
  documentation = [ ];
  enable = true;
  enableStrictShellChecks = false;
  environment = { ... };
  jobScripts = [ ];
  name = "polkit.service";
  networkAccess = «error: The option `systemd.services.polkit.networkAccess' was accessed but has no value defined. Try setting the option.»;
  notSocketActivated = false;
  onFailure = [ ];
  onSuccess = [ ];
  overrideStrategy = "asDropinIfExists";
  partOf = [ ];
  path = [ ... ];
  postStart = "";
  postStop = "";
  preStart = "";
  preStop = "";
  reload = "";
  reloadIfChanged = false;
  reloadTriggers = [ ];
  requiredBy = [ ];
  requires = [ ];
  requisite = [ ];
  restartIfChanged = true;
  restartTriggers = [ ... ];
  runner = «error: cannot coerce a list to a string: [ "" «thunk» ]»;
  script = "";
  scriptArgs = "";
  serviceConfig = { ... };
  startAt = [ ];
  startLimitBurst = «error: The option `systemd.services.polkit.startLimitBurst' was accessed but has no value defined. Try setting the option.»;
  startLimitIntervalSec = «error: The option `systemd.services.polkit.startLimitIntervalSec' was accessed but has no value defined. Try setting the option.»;
  stopIfChanged = false;
  unitConfig = «error: attribute 'dirtyRev' missing»;
  upheldBy = [ ];
  upholds = [ ];
  wantedBy = [ ];
  wants = [ ];
}

These are all things that you didn’t define but are still present anyway.

Ahh, that makes sense. Thanks!