NixOS Configuration Files

In January of this year I shared a repo with an earlier version of my NixOS configuration, and since then I have continued to make steady progress. I wanted to share it’s current state to get thoughts from others, and perhaps give others inspiration. GitHub - k-sommer/nixos-config: ❄️ My NixOS configurations ❄️ · GitHub

1 Like

Thank you for sharing.

  1. mkEnableOption is an anti-pattern, in my mind.
  2. I recommend the dendritic pattern
  3. I treat monitors configuration as mutable, not checked in
  4. Setting flake=false on flake inputs where possible might decrease your lockfile.
1 Like

Why? I have actually considered using mkEnableOption and similar things for modules.generic.default to take full advantage of the module system with the dendritic pattern. I used to do this but did it quite badly (using only mkEnableOption and not making other options), so I agree with you if you mean using only mkEnableOption without other options.

Example of what I’m getting at:

{
  lib,
  ...
}:
{
  flake.modules.generic.default =
    let
      inherit (lib) mkOption types;
    in
    {
      options.my.users = mkOption {
        type = types.attrsOf (types.submodule ({ name, config, ... }: {
          options.someService = {
            enable = lib.mkEnableOption "someService";

            socketDir = mkOption {
              type = types.str;
              default = "/run/user/${toString config.uid}";
            };
          };
        }));
      };
    };

  flake.modules.homeManager.default =
    {
      config,
      ...
    }:
    {
      services.someService = {
        inherit (config.my.users.${config.home.username}.someService) enable socketDir;
      };
    };

  flake.modules.nixos.default =
    {
      config,
      ...
    }:
    let
      anySomeServiceEnabled =
        lib.any
          (user: user.someService.enable)
          (lib.attrValues config.my.users);
    in
    {
      config = lib.mkIf anySomeServiceEnabled {
        services.udev.extraRules = ''
          # udev rules for someService
        '';
      };
    };
}

mkEnableOption is an anti-pattern because the pattern “importing a module suffices to enable it” is simpler.

mkEnableOption is fine, it’s what NixOS does. It comes down to preference.

11 Likes

The problem is that I run into cases where I want to disable a module but my aspects import too much and I don’t want per-host aspects to all have to contain way more includes than needed because in most cases I do want them. AFAIK with flake-aspects there’s no “undo include” thing to include an aspect without one of its includes.

I don’t actually have this problem but it feels like something I will run into when I start having more hosts.

Also of course you can see the socketDir example, imagine you want socketDir to be different for different users on different hosts and the udev rule also used socketDir.

It may be simpler, but it’s too limiting unless you have a higher level of abstraction in place to do computed imports higher up, which is a choice that a lot of people don’t want to make, and for good reason. There’s a reason the base NixOS modules do it this way. You’re pretty clearly different from the community as a whole in what you’re considering good and bad code here. Acting like that choice is definitive is just deceptive behavior.

Also, even if one accepts that argument (which I do not), it’s strange to call that “mkEnableOption being an anti-pattern,” as there’s nothing wrong with the function itself. What you’re considering an anti-pattern here is more the concept of modules having a global enable/disable switch.

16 Likes

The other aspect I find is that imports cannot depend on config, but attributes within config can of course depend on another attr of config, so the imports-only route is a bit more limiting.

I’m also just against wrapping the module system in another module system (dendritic and co), since we know the module system overhead is pretty noticeable… unless I see a massive benefit to doing so, I wouldn’t do it.

Everything’s a tradeoff, just pick what you find most workable.

3 Likes

Just added this to the dendritic pattern readme:

anti-pattern: enable options · mightyiam/dendritic@586e113

Nixpkgs importing NixOS was never a good idea. And that is the only reason most NixOS modules and an enable option.

@tejing accusing me of “deceptive behavior” is inappropriate and unpleasant. Please be polite.

imports cannot depend on config is relevant, but it somehow never compelled me to declare an enable option in practice :person_shrugging:

@waffle8946 I don’t see how wrapping the module system in another module system is relevant here. Edit: ah, I did recommend dendritic, yes. Forgot that for a moment.

It’s a sincerely given criticism of your behavior. Those are naturally unpleasant to receive, but sometimes necessary. Please actually consider my words.

12 Likes

Why was it a bad idea?

Edit:

Ah, I missed that part:

I kinda agree, at least for my personal config. Simple imports are often easier. But the same reason of preferring simplicity is why I’ve (so far!) not tried the “dendritic pattern”, so I’d say it’s more a matter of taste.

I am considering your words. Perhaps a more appropriate term would be “misleading”.

1 Like

Point taken. My wording suggested more intentionality than it should have.

3 Likes

I’m probably just ignorant, but can you point me at where nixpkgs imports NixOS? I thought it was the other way around. Like, I know that NixOS is implemented within github:NixOS/nixpkgs, but what git repo some code lives in doesn’t seem relevant to an enable.

If I do an import <nixpkgs> {}, am I also causing evaluation of <nixpkgs/nixos> in some way that helps me judge whether or not mkEnableOption is an anti-pattern?

Only thing I can think of is the nixos tests. I’m really not sure what he’s talking about either, though.

Sorry, did I write that? Here’s what I meant to write:

NixOS auto-importing most if not all of the NixOS modules that Nixpkgs provides was never a good idea. And that is the only reason most NixOS modules have an enable option.

1 Like

Ah, that makes more sense, in view of your other arguments.

Thanks for the clarification but I am (genuinely) still a bit confused. If I were to rephrase your statement as “NixOS auto-importing most if not all of the NixOS modules that Nixpkgs provides was never a good idea.”, am I still understanding the point you are making?

Sorry I know this might be a nit but it’s a nit that is relevant to my still burgeoning understanding of the nix landscape.

In my head, I’ve been conflating nixpkgs with what is returned by import <nixpkgs> {} and NixOS with what is returned by import <nixpkgs/nixos> {configuration.imports = [ ./configuration.nix ]; }. Is that reasonable? Is my framing of these two things too rigid/inflexible because of where I am in my learning journey? Serious question, as I think probably one of the hardest things that I struggled with initially was the overloading of similar terms (and hence which resource/manual I should start looking at to address various problems).

Under that frame, the only “NixOS modules that Nixpkgs provides” seems to be some stuff about tracking maintainers, which seems… fine? Not particularly heavy? Hence why I am wondering if “that Nixpkgs provides” was actually intended to narrow the scope to modules in <nixpkgs/modules>, or if you just meant in general that you would have preferred a NixOS architecture where users had to import specific modules from <nixpkgs/nixos/modules> instead of needing to option.enable = true autoloaded modules?

Yes, this is definitely a reasonable way to define the split. You could probably also just define nixos by “everything under the nixos subdirectory in the nixpkgs repo.”

I don’t want to put words in someone else’s mouth, but yes, I’m fairly certain this is what he’s getting at.

1 Like

I think that that is referred to as a pkgs. It even has a type="pkgs" and there’s a lib.types.pkgs for those.

import <nixpkgs/nixos> is a wrapper around lib.evalModules that does a few things, including importing all(?) NixOS modules that are in Nixpkgs.

Well, in this sentence, Nixpkgs is the entire repo, not pkgs.

“NixOS Modules that Nixpkgs provides” refers to thousands of NixOS modules under the nixos/modules path of the Nixpkgs repository.

Yes to this. That would have been a far more appropriate alternative to what I was referring to as “was never a good idea”.

2 Likes