!wfudwzqQUiJYJnqfSY:nixos.org

NixOS Module System

125 Members
23 Servers

Load older messages


SenderMessageTime
23 May 2025
@ss:someonex.netSomeoneSerge (migrating synapse) Btw that magic was in pkgs/ in nixosOptionsDoc 22:32:42
@ss:someonex.netSomeoneSerge (migrating synapse) Next one! Given (evalModules {...}).options and a path in options, is there a ready tool to tell which definition (with location) won the priority? I see I can access definitionsWithLocations which is an unfiltered list, and, to further complicate things, in case of an attrsOf option there's just the top-level list, which doesn't directly relate paths under the option to definitions or locations. 22:36:26
@mattsturg:matrix.orgMatt Sturgeon

For simple/flat options, all the definition listed in the option are the ones that "won" the priority.

Specifically, all of opt.definitions and opt.definitionsWithLocations have opt.highestPrio.

That's because most wrappers like mkIf, mkMerge, mkOverride, and mkOrder are resolved before creating the option's definitions list.

However, this is only done for the option's definitions (i.e. the top-level). Wrappers on nested attrs are applied by the option's type when it merges the option's definitions.

I don't know of an "easy" way to filter for definitions of a specific attr on an attrsOf type.

There are some functions like lib.modules.mergeAttrDefinitionsWithPrio, so maybe something similar exists or could be written that exposes definition location for attrsOf sub-attrs?

If you were dealing with a submodule, and had an explicit suboption declared, then you could access the suboption's definitions within the submodule eval. E.g., you could expose them to the wider module eval by defining an internal option in the submodule.

22:48:40
24 May 2025
@jappie:jappie.devjappie

I'm trying to create a settings option for services.dovecot2 (https://github.com/NixOS/nixpkgs/pull/388463/) and running into some issues trying to use a submodule for the type of the settings option.

Dovecot allows you to do something like this:

# dovecot config format:
service imap-login {
  restart_request_count = 1
  inet_listener imaps {
    port = 993
    ssl = yes
  }
}

# Nix:
"service imap-login" = {
  restart_request_count = 1;
  "inet_listener imaps" = {
    port = 993;
    ssl = true;
  };
};

Technically the following works for options.settings.type:

type =
  let
    inherit (lib.types) bool int listOf nullOr oneOf str submodule;
    inherit (lib) elemAt singleton split;
    any = oneOf [ int str bool (lib.types.lazyAttrsOf configValueType) ];
    configValueType = nullOr (oneOf [
      any
      (listOf any)
    ]);
  in
  configValueType;

But a better solution would be to define the section type & section name (e.g. service & imap-login or inet_listener & imaps from above) in the options part of a submodule instead of hiding it in the parser that writes the config file:

type =
  let
    inherit (lib.types) attrsOf bool int listOf nullOr oneOf str submodule;
    inherit (lib) elemAt singleton split;
    section = submodule (
      { name, config, ... }:
      let
        # split on first space
        splits = elemAt (elemAt (split "^([^ ]+)( +(.+))?$" name) 1);
        typeDefault = splits 0;
        nameDefault = splits 1; # 2;
      in
      {
        options = {
          section = {
            type = mkOption {
              description = "...";
              type = str;
              default = typeDefault;
            };
            name = mkOption {
              description = "...";
              type = nullOr str;
              default = nameDefault;
            };
          };
        };
        freeformType = configValueType;
      }
    );
    any = oneOf [ int str bool (lib.types.lazyAttrsOf section) ];
    configValueType = nullOr (oneOf [
      any
      (listOf any)
    ]);
  in
  configValueType;

However this throws and then infrecs trying to evaluate description:

       … from call site
         at /nix/store/iarry5sq6zg1wm91f4dn68a91nqs369w-source/nixos/modules/services/mail/dovecot.nix:794:65:
          793|       }
          794|       // optionalAttrs (cfg.settings ? mail_uid && cfg.settings.mail_uid != null && cfg.createMailUser) {
             |                                                                 ^
          795|         ${cfg.settings.mail_uid} =

       … while calling anonymous lambda
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:818:27:
          817|                   (
          818|                     name: defs:
             |                           ^
          819|                     let

       … while evaluating the attribute 'optionalValue.value'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/modules.nix:1148:41:
         1147|
         1148|     optionalValue = if isDefined then { value = mergedValue; } else { };
             |                                         ^
         1149|   };

       … while calling the 'throw' builtin
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/modules.nix:1139:11:
         1138|           in
         1139|           throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
             |           ^
         1140|       else

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … while evaluating the attribute '_module.freeformType.description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … from call site
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1026:13:
         1025|           description = "null or ${
         1026|             optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType
             |             ^
         1027|           }";

       … while calling 'optionDescriptionPhrase'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
          289|     optionDescriptionPhrase =
          290|       unparenthesize: t:
             |                       ^
          291|       if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … from call site
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1383:18:
         1382|             else
         1383|               "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
             |                  ^
         1384|                 optionDescriptionPhrase (

       … while calling 'optionDescriptionPhrase'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
          289|     optionDescriptionPhrase =
          290|       unparenthesize: t:
             |                       ^
          291|       if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … from call site
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1384:17:
         1383|               "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
         1384|                 optionDescriptionPhrase (
             |                 ^
         1385|                   class: class == "noun" || class == "conjunction" || class == "composite"

       … while calling 'optionDescriptionPhrase'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
          289|     optionDescriptionPhrase =
          290|       unparenthesize: t:
             |                       ^
          291|       if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … from call site
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:808:22:
          807|             (if lazy then "lazy attribute set" else "attribute set")
          808|             + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
             |                      ^
          809|           descriptionClass = "composite";

       … while calling 'optionDescriptionPhrase'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
          289|     optionDescriptionPhrase =
          290|       unparenthesize: t:
             |                       ^
          291|       if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       error: infinite recursion encountered
       at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

Is what I'm trying to do here not possible with submodules due to the recursion or am I missing something?

12:06:15
@jappie:jappie.devjappie *

I'm trying to create a settings option for services.dovecot2 (https://github.com/NixOS/nixpkgs/pull/388463/) and running into some issues trying to use a submodule for the type of the settings option.

Dovecot allows you to do something like this:

# dovecot config format:
service imap-login {
  restart_request_count = 1
  inet_listener imaps {
    port = 993
    ssl = yes
  }
}

# Nix:
"service imap-login" = {
  restart_request_count = 1;
  "inet_listener imaps" = {
    port = 993;
    ssl = true;
  };
};

Technically the following works for options.settings.type:

type =
  let
    inherit (lib.types) bool int listOf nullOr oneOf str submodule;
    inherit (lib) elemAt singleton split;
    any = oneOf [ int str bool (lib.types.lazyAttrsOf configValueType) ];
    configValueType = nullOr (oneOf [
      any
      (listOf any)
    ]);
  in
  configValueType;

But a better solution would be to define the section type & section name (e.g. service & imap-login or inet_listener & imaps from above) in the options part of a submodule instead of hiding it in the parser that writes the config file:

type =
  let
    inherit (lib.types) attrsOf bool int listOf nullOr oneOf str submodule;
    inherit (lib) elemAt singleton split;
    section = submodule (
      { name, config, ... }:
      let
        # split on first space
        splits = elemAt (elemAt (split "^([^ ]+)( +(.+))?$" name) 1);
        typeDefault = splits 0;
        nameDefault = splits 1;
      in
      {
        options = {
          section = {
            type = mkOption {
              description = "...";
              type = str;
              default = typeDefault;
            };
            name = mkOption {
              description = "...";
              type = nullOr str;
              default = nameDefault;
            };
          };
        };
        freeformType = configValueType;
      }
    );
    any = oneOf [ int str bool (lib.types.lazyAttrsOf section) ];
    configValueType = nullOr (oneOf [
      any
      (listOf any)
    ]);
  in
  configValueType;

However this throws and then infrecs trying to evaluate description:

       … from call site
         at /nix/store/iarry5sq6zg1wm91f4dn68a91nqs369w-source/nixos/modules/services/mail/dovecot.nix:794:65:
          793|       }
          794|       // optionalAttrs (cfg.settings ? mail_uid && cfg.settings.mail_uid != null && cfg.createMailUser) {
             |                                                                 ^
          795|         ${cfg.settings.mail_uid} =

       … while calling anonymous lambda
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:818:27:
          817|                   (
          818|                     name: defs:
             |                           ^
          819|                     let

       … while evaluating the attribute 'optionalValue.value'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/modules.nix:1148:41:
         1147|
         1148|     optionalValue = if isDefined then { value = mergedValue; } else { };
             |                                         ^
         1149|   };

       … while calling the 'throw' builtin
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/modules.nix:1139:11:
         1138|           in
         1139|           throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
             |           ^
         1140|       else

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … while evaluating the attribute '_module.freeformType.description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … from call site
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1026:13:
         1025|           description = "null or ${
         1026|             optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType
             |             ^
         1027|           }";

       … while calling 'optionDescriptionPhrase'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
          289|     optionDescriptionPhrase =
          290|       unparenthesize: t:
             |                       ^
          291|       if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … from call site
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1383:18:
         1382|             else
         1383|               "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
             |                  ^
         1384|                 optionDescriptionPhrase (

       … while calling 'optionDescriptionPhrase'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
          289|     optionDescriptionPhrase =
          290|       unparenthesize: t:
             |                       ^
          291|       if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … from call site
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1384:17:
         1383|               "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
         1384|                 optionDescriptionPhrase (
             |                 ^
         1385|                   class: class == "noun" || class == "conjunction" || class == "composite"

       … while calling 'optionDescriptionPhrase'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
          289|     optionDescriptionPhrase =
          290|       unparenthesize: t:
             |                       ^
          291|       if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       … while evaluating description
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
          265|             functor;
          266|         description = if description == null then name else description;
             |                          ^
          267|       };

       … from call site
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:808:22:
          807|             (if lazy then "lazy attribute set" else "attribute set")
          808|             + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
             |                      ^
          809|           descriptionClass = "composite";

       … while calling 'optionDescriptionPhrase'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
          289|     optionDescriptionPhrase =
          290|       unparenthesize: t:
             |                       ^
          291|       if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";

       … while evaluating the attribute 'description'
         at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

       error: infinite recursion encountered
       at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
          265|             functor;
          266|         description = if description == null then name else description;
             |         ^
          267|       };

Is what I'm trying to do here not possible with submodules due to the recursion or am I missing something?

12:06:55
25 May 2025
@mattsturg:matrix.orgMatt Sturgeon

lazyAttrsOf

Slightly off topic, but this probably isn't what you want in a freeform settings type; if a use does settings.foo = mkIf false {}, then foo will be defined, but as an "empty value" stub; usually an empty value is a throw expression throwing something like "option settings.foo is used but not defined". This will be a problem when you later want to serialize settings into the program's config format.

00:06:33
@mattsturg:matrix.orgMatt Sturgeon *

lazyAttrsOf

Slightly off topic, but this probably isn't what you want in a freeform settings type; if a user defines settings.foo = mkIf false {}, then foo will be defined, but as an "empty value" stub; usually an empty value is a throw expression throwing something like "option settings.foo is used but not defined". This will be a problem when you later want to serialize settings into the program's config format.

00:06:48
@mattsturg:matrix.orgMatt Sturgeon

However this throws and then infrecs trying to evaluate description:

Typically, these RFC42-style types define their own description. Looking in pkgs/pkgs-lib/formats.nix you'll see descriptions like "TOML value", "Lua value", "JSON value", etc.

00:11:06
@mattsturg:matrix.orgMatt Sturgeon

section submodule of { type :: string, name :: string, ... :: configValueType }

It's neat how it auto-splits the attrname into type and name options, however one concern I have with your "section" type is naming conflicts. E.g. what would happen if the section needed to define actual config with the key "name" or "type"?

There aren't many good solutions to this, and we have a similar issue with libconfuse sections in #401565. In fact, this program may even be using a libconfuse-compatible config format, so perhaps we can collaborate on a solution there?

One solution may be to _prefix these options? this would indicate they are "internal" to the nix type, and should reduce the chance of naming conflicts.

Incidentally, libconfuse calls these elements "section name" and "section title" respectively. Libconfuse is further complicated by also supporting sections that don't take a "title", so we'd need to decide how to represent that. With your design, it could be that the title option is nullable.

00:20:34
@mattsturg:matrix.orgMatt Sturgeon
# split on first space
splits = elemAt (elemAt (split "^([^ ]+)( +(.+))?$" name) 1);
typeDefault = splits 0;
nameDefault = splits 1;

Personally I would match this with regex groups instead of trying to use split. The trouble with split is that it'll split an arbitrary number of times.

splits = builtins.match "([^ ]+) (.+)" name;
typeDefault = if splits == null then name else builtins.elemAt splits 0; # aka libconfuse "name"
nameDefault = if splits == null then null else builtins.elemAt splits 1;   # aka libconfuse "title"
00:26:09
@mattsturg:matrix.orgMatt Sturgeon *

However this throws and then infrecs trying to evaluate description:

Typically, these RFC42-style types define their own description. Looking in pkgs/pkgs-lib/formats.nix you'll see descriptions like "TOML value", "Lua value", "JSON value", etc.

Specifying an explicit description should avoid the inf-rec:

in
configValueType // {
  description = "TODO: write desc";
};
00:27:41
@jappie:jappie.devjappie

Thanks for the feedback! I used lazyAttrsOf to avoid some uncaught infinite recursion (maxCallDepth reached or something) in an earlier revision, but it seems fine to just use attrsOf now, and setting a description avoids the infrec.

It is eerie how similar libconfuse & dovecot's config formats are... I cannot find any reference to libconfuse in dovecot's source though. Btw, credit for coming up with section.{type,name} goes to 2xsaiko (Dovecot module maintainer).

one concern I have with your "section" type is naming conflicts. E.g. what would happen if the section needed to define actual config with the key "name" or "type"?

I'm not sure I understand what you're saying here... both name & type are in the section attrs, so unless some Dovecot setting or plugin has an attribute called section we should be fine.

Example:

settings = {
  service = [ # this sets section.type = service; & section.name = "";
    {
      section.name = "imap"; # now this section will get parsed to "service imap { ... }"
      restart_request_count = 1;
    }
  ]

  # or we can do this:
  foobar = {
    section = {
      type = "service";
      name = "imap";
    };
    restart_request_count = 1;
  };
};

Though it's not a bad idea to prefix section with an underscore imo, better safe than sorry.

Incidentally, libconfuse calls these elements "section name" and "section title" respectively. Libconfuse is further complicated by also supporting sections that don't take a "title", so we'd need to decide how to represent that. With your design, it could be that the title option is nullable.

yeah this is literally dovecot lol

2xsaiko & I have started calling these elements section type & section name respectively, Dovecot 2.4 has 'named filters' (sections with only a type, e.g. auth_policy { server_url = foobar } is the same as auth_policy_server_url = foobar) and named list filters of which there can be multiple with different names (e.g. namespace inbox {}, namespace virtual {}).

So the title / name / second parameter of a section seems nullable for both Dovecot & libconfuse.

11:30:48
@jappie:jappie.devjappie

huh... the below code still throws

       error: A definition for option `services.dovecot2.settings.mail_uid' is not of type `Dovecot config value'. Definition values:
       - In `/nix/store/sgdcq1qbjgx0wsb1rwn2yi3k5vsr4pg6-source/hosts/Flugel/services/mail.nix': "dovemail"

even though "dovemail" is a string and str should be allowed

type =
  let
    inherit (lib.types) attrsOf bool int listOf nullOr oneOf str submodule;
    section = submodule (
      { name, config, ... }: {
        freeformType = configValueType;
      }
    );
    configValueType =
      nullOr (oneOf [
        int
        str
        bool
        (listOf configValueType)
        (attrsOf section) # this doesn't work
        # (attrsOf configValueType) # this works
      ])
      // {
        description = "Dovecot config value";
      };
  in
  configValueType;

so putting a submodule with freeformType = configValueType in configValueType instead of referring directly to itself throws everything off

11:56:49
@mattsturg:matrix.orgMatt Sturgeon

so putting a submodule with freeformType = configValueType in configValueType instead of referring directly to itself throws everything off

I believe submodules only support attrsOf or lazyAttrsOf freeformTypes. So that may be part of the issue.

I'd need to look in more detail, ideally in vim instead of matrix, but maybe the relationship between the submodule and the oneOf should be tweaked?

12:00:17
@jappie:jappie.devjappie

and the reason for attrsOf section is because Dovecot allows the following:

service auth {
  unix_listener auth-master {
    # ...
  }
}

both service auth and unix_listener auth-master are named list filters and thus need to support section.{type,name}

12:08:00
@mattsturg:matrix.orgMatt Sturgeon

I'm not sure I understand what you're saying here... both name & type are in the section attrs, so unless some Dovecot setting or plugin has an attribute called section we should be fine.

Hm, I initially read it as them being top-level attrs within a freeform "section". I guess I misread.

12:01:20
@jappie:jappie.devjappie *

and the reason I want attrsOf section instead of attrsOf configValueType is because Dovecot allows the following:

service auth {
  unix_listener auth-master {
    # ...
  }
}

both service auth and unix_listener auth-master are named list filters and thus need to support section.{type,name}

12:08:21
@jappie:jappie.devjappie *

and the reason I want attrsOf section instead of attrsOf configValueType is because Dovecot allows the following:

service auth {
  unix_listener auth-master {
    # ...
  }
}

both service auth and unix_listener auth-master are named list filters and thus need to support section.{type,name}

(and from what I can tell the same goes for libconfuse)

12:15:23
@jappie:jappie.devjappie

I believe submodules only support attrsOf or lazyAttrsOf freeformTypes

seems like that's the case - the below works, thank you so much 😄

 type =
   let
     inherit (lib.types) attrsOf bool int listOf nullOr oneOf str submodule;
     section = submodule (
       { name, config, ... }: {
-        freeformType = configValueType;
+        freeformType = attrsOf configValueType;
       }
     );
     configValueType =
       nullOr (oneOf [
         int
         str
         bool
         (listOf configValueType)
-        (attrsOf section) # this doesn't work
-        # (attrsOf configValueType) # this works
+        section # this works!
       ])
       // {
         description = "Dovecot config value";
       };
   in
   configValueType
18:44:29
27 May 2025
@deeok:matrix.orgmatrixrooms.info mod bot (does NOT read/send messages and/or invites; used for checking reported rooms) joined the room.15:31:38
29 May 2025
@minion3665:nixos.devSkyler joined the room.22:09:24
@minion3665:nixos.devSkyler

So, hi! I'm working on a thing to let me write home-manager homes that can be either used independently or attached to NixOS systems... my homes look like some attrset of { modules = [ ... ]; args = { ... }; }, args being some specialArgs provided to home-manager normally ...

... attaching the home modules to a home-manager NixOS home is fairly straightforward, I can write some code that looks a little like this to generate some NixOS modules that put everything where I want it to go ...

map (module: {
  home-manager.users.${username} = module;
}) home.modules;

unfortunately, not so with setting the special args. Home-manager's NixOS module has a way to do this: home-manager.extraSpecialArgs, but it's not per-user so if I attach multiple homes that would cause issues. I tried setting _module.args but (1) was getting infinite recursion when I tried to use any of the new args, and (2) that is a little different from how home-manager standalone is consuming the args, and I'd prefer to avoid inconsistencies if at all possible...

...I wondered about merging in some options that defined more specialArgs, since as in NixOS a home-manager module is a submoduleWith and I know those can be merged. Unfortunately, it's actually an attrsOf submoduleWith, so I can't merge into home-manager.users.${username} or I get this "would be a parent of the following options but its type does not support nested options" error...

...the comment above that seems to suggest that's because it w

22:24:26
@minion3665:nixos.devSkyler *

So, hi! I'm working on a thing to let me write home-manager homes that can be either used independently or attached to NixOS systems... my homes look like some attrset of { modules = [ ... ]; args = { ... }; }, args being some specialArgs provided to home-manager normally ...

... attaching the home modules to a home-manager NixOS home is fairly straightforward, I can write some code that looks a little like this to generate some NixOS modules that put everything where I want it to go ...

map (module: {
  home-manager.users.${username} = module;
}) home.modules;

unfortunately, not so with setting the special args. Home-manager's NixOS module has a way to do this: home-manager.extraSpecialArgs, but it's not per-user so if I attach multiple homes that would cause issues. I tried setting _module.args but (1) was getting infinite recursion when I tried to use any of the new args, and (2) that is a little different from how home-manager standalone is consuming the args, and I'd prefer to avoid inconsistencies if at all possible...

...I wondered about merging in some options that defined more specialArgs, since as in NixOS a home-manager module is a submoduleWith and I know those can be merged. Unfortunately, it's actually an attrsOf submoduleWith, so I can't merge into home-manager.users.${username} or I get this "would be a parent of the following options but its type does not support nested options" error...

...the comment above that seems to suggest that's because it would be ambiguous

22:24:36
@minion3665:nixos.devSkyler

https://github.com/NixOS/nixpkgs/blob/f880c595eafbd35ed066f0a8097409cb79188798/lib/modules.nix#L822

          # Raw options can only be merged into submodules. Merging into
          # attrsets might be nice, but ambiguous. Suppose we have
          # attrset as a `attrsOf submodule`. User declares option
          # attrset.foo.bar, this could mean:
          #  a. option `bar` is only available in `attrset.foo`
          #  b. option `foo.bar` is available in all `attrset.*`
          #  c. reject and require "<name>" as a reminder that it behaves like (b).
          #  d. magically combine (a) and (c).
          # All of the above are merely syntax sugar though.

I want option A here I think - but I'm not sure what it's syntax sugar for... does anyone know what this is syntax sugar for and/or does anyone else have a pointer to how I might be able to set these specialArgs for only a single home-manager user

22:25:52
@minion3665:nixos.devSkyler *

https://github.com/NixOS/nixpkgs/blob/f880c595eafbd35ed066f0a8097409cb79188798/lib/modules.nix#L822

          # Raw options can only be merged into submodules. Merging into
          # attrsets might be nice, but ambiguous. Suppose we have
          # attrset as a `attrsOf submodule`. User declares option
          # attrset.foo.bar, this could mean:
          #  a. option `bar` is only available in `attrset.foo`
          #  b. option `foo.bar` is available in all `attrset.*`
          #  c. reject and require "<name>" as a reminder that it behaves like (b).
          #  d. magically combine (a) and (c).
          # All of the above are merely syntax sugar though.

I want option A here I think - but I'm not sure what it's syntax sugar for... does anyone know what this is syntax sugar for and/or does anyone else have a pointer to how I might be able to set these specialArgs for only a single home-manager user?

22:26:00
30 May 2025
@mattsturg:matrix.orgMatt Sturgeon

Apologies if I missed anything, but why do you need to set specialArgs? Generally, they should be avoided at all costs as they cannot be configured from within modules* and they make any modules that rely on them very not-portable.

If you just want extra module args, you can usually use the _module.args option. "Special" args are only needed when defining imports.

If you just need to pass some value from another scope into a modules (e.g. to access inputs from a flake) then you can use lexical scoping (define the module within a file that has access to what you need) or pass in extra args using something like lib.modules.importApply or flake-part's fancy moduleWithSystem function, if you're using flake-parts.

I wondered about merging in some options

You could always declare these options within the submodule rather than merging them in by declaring them in the outer module eval. If you do this in a module that is defined within a file with access to the args you want, then you shouldn't need to do anything fancy. That said, these wouldn't be "special" because they'd be configured within the submodule eval, but that's no different to a merged-in option declaration.

* it is the fact that these args are defined outside of the module system eval that makes them "special".

00:58:12
@nbp:mozilla.orgnbp

If you want an option to be available for a single user, this should be as simple as:

  config.home-manager.users.${username} = module;

Where the module declares the option you want to see available for this user only.

Submodules are literally modules, following the same shortcuts that config is the default attribute when not specified. Thus specifying options and config in the submodule should merge it like any other module.

09:36:55
@minion3665:nixos.devSkyler

I think I maybe misphrased my question, sorry, this was supposed to all be about getting args into the module... the tangent with the options was one way I'd tried to do this

i.e. literally taking another options.home-manager.users.${username} = mkOption { type = subModuleWith { specialArgs = ... }; }; and having them merged together by the module system

when I tried setting home-manager.users.${username}._module.args, if I tried to use the arg in another module I was getting an infinite recursion error however when I tried home-manager.extraSpecialArgs I was able to use the arg - this led me to conclude that I needed args outside the module system, however if I understand you right I don't if they aren't being used by imports(?)

my example is kinda convoluted at the moment - but I'll see if I can get something more minimal to send here...

20:54:40
@mattsturg:matrix.orgMatt Sturgeon

Without getting too much into the weeds, infinite recursion can show up for a number of reasons. Not just defining imports via a config value (such as non-special args).

For example, trying to use an arg that doesn't exist usually shows up as infinite recursion too. E.g. if you were defining or using args in mismatched module evals.

As for "merging in" special args; this isn't really possible because the whole point of special args is that they aren't defined by the module system.

Is there a reason you need different args per user instead of using home-manager's shared extraSpecialArgs NixOS option?

21:02:00
@minion3665:nixos.devSkyler
In reply to @mattsturg:matrix.org

Without getting too much into the weeds, infinite recursion can show up for a number of reasons. Not just defining imports via a config value (such as non-special args).

For example, trying to use an arg that doesn't exist usually shows up as infinite recursion too. E.g. if you were defining or using args in mismatched module evals.

As for "merging in" special args; this isn't really possible because the whole point of special args is that they aren't defined by the module system.

Is there a reason you need different args per user instead of using home-manager's shared extraSpecialArgs NixOS option?

For example, trying to use an arg that doesn't exist usually shows up as infinite recursion too. E.g. if you were defining or using args in mismatched module evals.

Yeah, I've definitely seen this... the way I'm doing it at the moment is generating a list of modules for my NixOS config, including both modules that set home-manager user modules up and modules that set home-manager.users.${username}._module.args ... the symptoms are the same as if I don't set them though... I guess maybe they are in mismatched module evals (how would I know?)

As for "merging in" special args; this isn't really possible because the whole point of special args is that they aren't defined by the module system.

gotcha - yeah... the special args are defined in NixOS and I have what I want them to be for the homes in NixOS. If I could merge in a different type for each attr of the attrsOf I think I could do it, since as I could merge in another submodule that has the specialArgs I want ... unfortunately it's pretty much all or nothing there

Is there a reason you need different args per user instead of using home-manager's shared NixOS option?

it's a pretty artificial constraint, but I'm not making the structure for homes themselves - so I can't guarentee that there aren't conflicting home args set in different homes ... I'm trying to make a thing that can be used to take standalone home-manager homes and put them in nixos systems - and the individual standalone home-manager home spec I have here doesn't share special args... what I'm trying to do is mostly plumbing, I hadn't considered seeing if I could get the home spec modified to better fit this case (or just, you know, ban setting these to different things)

23:06:28

Show newer messages


Back to Room ListRoom Version: 10