From a NixOS module, is it possible to delete an entry from an attrset defined by a different module?

Suppose a stock NixOS module, one of whose settings is an open attrset which defaults to a large set of things. From one of the modules defining my system configuration, I want to remove one or more of the entries in the default attrset, leaving the rest of it alone. What is the preferred way to do that?

For concreteness, the attrset I’m trying to mess with is services.postfix.settings.master, and it is insufficient to clobber the value of the attribute I want gone, e.g. neither of these has the desired effect:

# error: A definition for option `services.postfix.settings.master.local'
# is not of type `submodule'
services.postfix.settings.master.local = lib.mkForce null;

# compiles fine but there's still an entry in the generated
# master.cf for 'local'
services.postfix.settings.master.local = lib.mkForce {};

Maybe try something like this:

services.postfix.settings.master = lib.mkForce {
  local = {};
};

Just adding this to my config where postfix is not used, I do get an error so you’ll likely need to re-add the necessary options. It does prove that your desired effect seems to be achieved though.

error: The option `services.postfix.settings.master.local.chroot' was accessed but has no value defined. Try setting the option.

I think you may have misunderstood what I was going for.

Your suggestion clobbers the value of the ‘local’ entry of the attrset to a blank submodule, and also throws away all the other entries in the attrset (the list is quite long, in this particular case). As you saw for yourself, this completely breaks the postfix module (and would break the actual daemon as well if it got that far).

What I want is to throw away only the ‘local’ entry (make it not be present at all – not the same as setting its value to {}) and keep all the other entries, with their normal default values.

The usual way to remove a attribute from an attrset is builtins.removeAttrs, but you will get an infinite recursion error when trying to apply it on the same attrset as in services.postfix.settings.master = lib.mkForce (builtins.removeAttrs config.services.postfix.settings.master ["local"]);

One way around it would be to fork nixpkgs and remove the reference to local (lines 1128-1130 of nixos/modules/services/mail/postfix.nix). It should do the trick, but if you need the change long term, you will need to keep rebasing your branch on top of the latest changes to nixpkgs.

  • disable the module, copy the entire code, import that
  • fork nixpkgs
  • use applyPatches to instantiate nixpkgs and patch that module (I haven’t thought this option through too much, might be more hassle)

These aren’t “default” values so you can’t reference them in nix code.

1 Like

So what I’m hearing is that all my options are bad.

1 Like

I use a fork and rebase every so often, personally. Doesn’t seem bad for me.

There is also the option of upstreaming a version of the module that permits what you want, perhaps after using it downstream like @waffle8946 suggests - you can use disabledModules to escape most of the maintenance burden.

Though typically if this isn’t possible it’s likely something you should not want, so the final option is to review your use case and think about whether you’re deep into an XY problem. I don’t know enough about postfix to do more than play rubber duck for that option, unfortunately.

2 Likes

Yeah, bad enough for me to have proposed an API extension to cover this case, once upon a time. If you have a current use case for it, feel free to pick it up and I’ll help out as much as I can.

5 Likes

Bumping this as I just tripped over it again, this time with something more elemental.

The default value of environment.corePackages is (equivalent to)

with pkgs; [
    acl attr bashInteractive bzip2 coreutils-full cpio curl diffutils
    findutils gawk getent getconf gnugrep gnupatch gnused gnutar gzip
    xz less libcap ncurses netcat mkpasswd procps su time util-linux
    which zstd
]

I imagine some people zorch the whole thing and put in busybox instead, but that’s not what I’m going for; what I’d like to do is remove su, leaving the rest of it alone. I can’t find any way to do that without copying the entire list into my own code, or somehow manually evaluating modules/config/system-path.nix in a sandbox.

There’s isn’t a clean way to do so atm.
I copy the whole list over and comment out the ones I don’t want.

You’re not gonna believe it :laughing:

1 Like

I can’t tell if this is a joke or not. Is there a way to remove an entry from a list without doing either of the above two things?

Y’see, this is a feature request thread. I am like 99% sure that the existing module system does not give me any way to say “take this (list/attrset) from the next lower priority layer and remove these (elements/keyval pairs) from it”, and I think there should be a way to do those things.

Like, for the corePackages example, maybe something like

environment.corePackages = lib.mkFilter
    (pkg: isNull (builtins.match "^shadow-.*" pkg.name));

… that specific filter rule looks like it might catch too many things, but you get the idea.

I am being genuine though, there really isn’t a way to do it. The module system can’t use any of the various lib functions for lists due to the issue of computing the config fix-point, and there aren’t any module system specific functions that can flag an element for removal.

I believe this stuff isn’t easily possible due to technical details of the module system, but I’m honestly not very familiar with the details.

Feature reqs here won’t go anywhere, this is a help forum.
Those would go on github. (Then likely forgotten unless someone is sufficiently motivated.)

Make it 100%.

The closest thing would be something like @nrabu’s PR lib/modules: implement a way to override a property based on its previous value by nrabulinski · Pull Request #286578 · NixOS/nixpkgs · GitHub but it’s 2 years old and somewhat contentious. If you want to try it live you’re welcome to do so.

1 Like

This is also a discuss-Nixpkgs-development forum; I don’t think there’s anything wrong with spitballing ideas for improvement here.

I think mkFilter is a pretty good idea, and better than a more generic ‘override any previous property’ function. One of the stronger objections to the latter is that you need to define an ordering on multiple such functions to get consistent results, but multiple mkFilters can be and’ed together — composable and commutative. It would also work fairly naturally with the existing priority system. Values and filters can be mixed in definitions; only the strongest priority ones win out; whichever of those are values are reduced by whichever of those are filters. Easy to explain and easy to implement.

The other objection is that adding features like this discourages module authors from improving their modules. I don’t know if it’s an improvement to add a corePackagesPredicate property, or to add individual toggles for each thing that goes in corePackages. It might be an improvement to do as @infinisil has suggested and change all such properties to attribute sets, but that’s unlikely to happen with cases such as the motivation in https://github.com/NixOS/nixpkgs/pull/157070 — and besides, we’ve been talking about that concept for a while and it hasn’t happened yet. We shouldn’t let perfect be the enemy of good.

So I think a limited, composable feature like mkFilter would be worth proposing and implementing.

1 Like

There’s actually a pretty good PR for environment.systemPackages for this! https://github.com/NixOS/nixpkgs/pull/255086

4 Likes

(My ‘unlikely to happen’ continued with the words ‘with cases such as…’; I wasn’t saying that the *Packages case wasn’t likely to happen, only that there are other lists of things that should probably remain ordered.)

I feel like such cases would be better served with an attrset too, just add an optional priority attribute to the value or something.

Still, a mkFilter of some kind could make sense for lists; I doubt for example all the various *args options that become CLI args will ever be changed to attrsets, nor would that really fit cleanly (then again, a commandArgs type that makes those togglable options might also be nice…).

for something like environment.systemPackages you actually can tweak the defaults without either mkForce or patching. option merging involves an apply step that takes place after all definitions have been gathered, and you can hook that to filter out specific definitions you don’t want.

{ lib, … }:
{
  options.environment.systemPackages = lib.mkOption {
    # disable all `shadow` packages, but keep anything else that the user specified.
    apply = defns: lib.filter (pkg: isNull (builtins.match "^shadow-.\*" pkg.name)) defns;
  };
}

this requires that the nixos module doesn’t already define apply for the option. if it does, the only way i’ve found to “unset” an option is to use disabledModules. you can find more examples of both in the polyunfill.nix file i use to unset defaults i disagree with.

1 Like