Help getting submodule suboption definitions from NixOS config

Sometimes weird stuff happens that leads to NixOS config definitions not being what you expected. Commonly this is due to something somewhere in the NixOS modules that sets a higher priority than your definition, leading to it being shadowed. Tracking this down can be a nuisance. The best option that I’ve found is to use nix repl, load my NixOS config flake with :lf ., and then inspect the option through nixosConfigurations.<hostname>.options.<loc>, such as nixosConfigurations.<hostname>.options.networking.hostName. Whereas config only presents you the final value, options holds references to the definitions (even those that were overridden) as well as the highest priority level, which all serves to indicate where specifically the definition that trumped is located for further inspection. This works well for my case, but this doesn’t work for submodules.

Take for example nixosConfigurations.<hostname>.config.security.pam.services.sddm. Here you can see the final values just fine. But when your try to look for the definitions through nixosConfigurations.<hostname>.options.security.pam.services.sddm, it won’t be found since nixosConfigurations.<hostname>.options.security.pam.services is the root option, with sddm being an attribute submodule.

How can I get the option/evaluation information for the submodule? I remember at the beginning of the year when I was writing some modules myself that I had figured out how to do this, but I’ve since forgotten and can’t remember how. From what I remember you had to go through type.nestedType.elemType (which yields the submodule type) and then use getSubModules, getSubOptions, or substSubModules in some manner to get the submodule module details. I can’t piece it together and there is no documentation about this behaviour. I was deep into the types and modules Nixpkgs lib source code at the time, so I’m sure that’s where I figured out how to do this, but it’s been challenging to find what I need again.

Does anyone know how to do this? It would be greatly helpful.

I don’t think this is possible. Anything under type will only be returning the defaults for the submodule, not the actual settings. And nothing else available even seems likely to contain this information. It’s too bad, because I’d also like to see the submodule’s options value available somewhere.

1 Like

These attrs are documented in https://nixos.org/manual/nixos/stable/#sec-option-types-custom. Unfortunately I don’t see how these will get you what you’re looking for, as they will help you list all of the generic submodule options, but they don’t help you inspect a specific submodule instance. In fact I don’t see anything here that would help - echoing the above from @tejing.

nix-repl> :p options.security.pam.services.type
{
  _type = "option-type";
  check = «primop isAttrs»;
  deprecationMessage = null;
  description = "attribute set of (submodule)";
  descriptionClass = "composite";
  emptyValue = {
    value = { };
  };
  functor = {
    binOp = «lambda binOp @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:795:13»;
    name = "attrsWith";
    payload = {
      elemType = {
        _type = "option-type";
        check = «lambda check @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:1239:19»;
        deprecationMessage = null;
        description = "submodule";
        descriptionClass = null;
        emptyValue = {
          value = { };
        };
        functor = {
          binOp = «lambda binOp @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:1323:21»;
          name = "submodule";
          payload = {
            class = null;
            description = null;
            modules = [
              {
                _file = "/nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/nixos/modules/security/pam.nix";
                imports = [ «lambda pamOpts @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/nixos/modules/security/pam.nix:127:5» ];
              }
            ];
            shorthandOnlyDefinesConfig = true;
            specialArgs = { };
          };
          type = «lambda submoduleWith @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:1184:9»;
          wrapped = null;
        };
        getSubModules = «repeated»;
        getSubOptions = «lambda getSubOptions @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:1281:13»;
        merge = {
          __functor = «lambda __functor @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:1261:15»;
          v2 = «lambda v2 @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:1264:15»;
        };
        name = "submodule";
        nestedTypes = { };
        substSubModules = «lambda substSubModules @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:1302:13»;
        typeMerge = «lambda defaultTypeMerge @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:122:10»;
      };
      lazy = false;
      placeholder = "name";
    };
    type = «lambda @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:816:9»;
    wrapped = «repeated»;
    wrappedDeprecationMessage = «lambda makeWrappedDeprecationMessage @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:95:5»;
  };
  getSubModules = [
    «repeated»
  ];
  getSubOptions = «lambda getSubOptions @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:861:27»;
  merge = {
    __functor = «lambda __functor @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:830:15»;
    v2 = «lambda v2 @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:833:15»;
  };
  name = "attrsOf";
  nestedTypes = {
    elemType = «repeated»;
  };
  substSubModules = «lambda substSubModules @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:864:13»;
  typeMerge = «lambda defaultTypeMerge @ /nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/lib/types.nix:122:10»;
}

However, if you inspect options.security.pam.services.definitionsWithLocations you will at least get a list of all of the definitions (and the files they are defined in):

nix-repl> :p options.security.pam.services.definitionsWithLocations
[
  {
    file = "/nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/nixos/modules/system/boot/systemd/user.nix";
    value = {
      systemd-user = {
        pamMount = false;
        startSession = true;
      };
    };
  }
  {
    file = "/nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/nixos/modules/system/boot/systemd.nix";
    value = {
      systemd-run0 = {
        pamMount = false;
        setLoginUid = true;
      };
    };
  }
...snip...

Since you’re looking for sddm specifically, you could try something like

nix-repl> :p builtins.filter (x: x.value?sddm) options.security.pam.services.definitionsWithLocations
[
  {
    file = "/nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/nixos/modules/services/display-managers/sddm.nix";
    value = {
      sddm = { text = "auth      substack      login\naccount   include       login\npassword  substack      login\nsession   include       login\n"; };
      sddm-autologin = { text = "auth     requisite pam_nologin.so\nauth     required  pam_succeed_if.so uid >= 1000 quiet\nauth     required  pam_permit.so\n\naccount  include   sddm\n\npassword include   sddm\n\nsession  include   sddm\n"; };
      sddm-greeter = { text = "auth     required       pam_succeed_if.so audit quiet_success user = sddm\nauth     optional       pam_permit.so\n\naccount  required       pam_succeed_if.so audit quiet_success user = sddm\naccount  sufficient     pam_unix.so\n\npassword required       pam_deny.so\n\nsession  required       pam_succeed_if.so audit quiet_success user = sddm\nsession  required       pam_env.so conffile=/etc/pam/environment readenv=0\nsession  optional       /nix/store/f8plklbbq3gwkq1wfq89i3f4wy8rabzn-systemd-258/lib/security/pam_systemd.so\nsession  optional       pam_keyinit.so force revoke\nsession  optional       pam_permit.so\n"; };
    };
  }
]

Or with just selecting the sddm options in each respective value:

nix-repl> :p lib.pipe options.security.pam.services.definitionsWithLocations [
            (lib.filter (d: d.value ? sddm))
            (map (d: d // { value = {inherit (d.value) sddm;} ; }))
          ]
[
  {
    file = "/nix/store/2vkj4fh2pi6s654950jbsi5rmv9wm0za-source/nixos/modules/services/display-managers/sddm.nix";
    value = {
      sddm = { text = "auth      substack      login\naccount   include       login\npassword  substack      login\nsession   include       login\n"; };
    };
  }
]
1 Like

I can see that. It’s also possible that I am misremembering. Thanks for the input.