Writing custom modules for nixos configuration

Hi!

What is the preferred way of declaring custom options in nixos configuration? I tried with this layout:

modules/
├── default.nix
├── home-manager
│   ├── user0
│   │   ├── default.nix
│   │   └── packages
│   │       ├── graphical
│   │       │   └── default.nix
│   │       └── terminal
│   │           └── default.nix
│   └── user1
│       ├── default.nix
│       └── packages
│           ├── graphical
│           │   └── default.nix
│           └── terminal
│               └── default.nix
├── packages
│   ├── base
│   │   └── default.nix
│   ├── graphical
│   │   └── default.nix
│   └── terminal
│       └── default.nix
├── system
│   └── base
│       └── default.nix
└── users
    ├── groups
    │   └── group0
    │       └── default.nix
    ├── user0
    │   └── default.nix
    └── user1
        └── default.nix

And found out that it is cumbersome (difficult to read). Is there some more elegant way of doing this?

Also I thought of importing all of these modules using this file modules/default.nix:

{ config, ... }:
{
  imports = [
    ./home-manager/user1/default.nix
    ./home-manager/user1/packages/graphical/default.nix
    ./home-manager/user1/packages/terminal/default.nix
    ./packages/base/default.nix
    ./packages/graphical/default.nix
    ./packages/terminal/default.nix
    ./system/base/default.nix
  ];
}

This way I can import just one file to access all of my options. Is this good practice?

Best,
Miro

Assuming those are all NixOS modules, sure. If they’re home-manager modules that will of course not work.

There is no universally preferred way, in any case.

2 Likes

Is a subjective mattter :slight_smile:

Yes, rename files to tailcoat_suit.nix

Did you know that you can import default.nix by dirname?
imports = [./system/base];

I think, most people don’t create custom options, but custom configs.
The difference is that “options” you create alternative to others “config” and isn’t hard, is fun, is cool, but most users don’t create them.

Yes

Nice layout, but is important focus on your use case:
How many user you have, how many packages you have, how many machines you have?
How much do you think you will need to change them?

For most users a single configuration.nix is enough.

See also:

There’s no one way to do it. I personally place my modules into modules/nixos or modules/home-manager depending on which level the module is for, and then I use the config attrset path to declare the directory, so if I was making a module called my.foo for NixOS, I’d place it at modules/nixos/my/foo/module.nix.

This works well enough for me, but it’s my unique preference. Others may have something similar, or something wildly different. Do what works for you. It should be noted that I’ve had different organization methods before settling on this, so maybe it’ll take some experimenting for you too.

Hello,

Perhaps you should have a look at the Den “framework” for managing your config ?

URL: https://den.denful.dev/

It is basically a context-aware aspect-oriented Nix flake framework.

Find an example of how I manage my infra config at GitHub - drupol/infra: Contains the configuration of every home computers · GitHub

Hope you’ll find what you’re looking for !

1 Like

Assuming those are all NixOS modules, sure. If they’re home-manager modules that will of course not work.

I’ve managed even home-manager to work in this way:

f user0@kaktus 0s nixos-configuration main -> cat modules/home-manager/user0/default.nix
{ config, lib, ... }:
let
  cfg = config.internal.home-manager.user0.enable;
in
{
  options.internal.home-manager.user0 = {
    enable = lib.mkEnableOption "enable home-manager for user0";
  };
  config = lib.mkIf cfg {
    home-manager.users.user0 = { pkgs, ... }: {
      home = {
        stateVersion = "24.11";
        username = "user0";
        homeDirectory = "/home/user0";
      };
    };
  };
}
f user0@kaktus 0s nixos-configuration main ->

Is this correct / good practice?

I’m sorry I don’t understand. English isn’t my first language so maybe there’s something I don’t know?

I know now! Thank you.

Three users, 6ish machines and I’m a tinkerer, so… very much:)

Thank you for sharing. Could you show me an example of this “config attrset path”? As I understand correctly it is a concept new to me:)

I’ve heard about it but couldn’t find any good documentation. I’ll look into it!

Feel free to join the friendly Matrix room to get you started !

https://matrix.to/#/#denful:matrix.org

1 Like

That’s a NixOS module, not a home-manager one :wink:

Initially, when I setup my desktop I thought about taking the same approach as you, but I too found it aesthetically unpleasing. I wanted, almost the same layout on my Desktop as on my Laptop, but I also wanted the possibility of add one program to one machine without adding it to the other. To achieve this, I decided to base my setup upon a set of generic NixOS modules, somewhat inspired by this thread about multi-system flake configurations.

├── common/      # All common components across machines
│   ├── default-configuration.nix
│   ├── default-home.nix
│   ├── desktop-environments
│   │   ├── cosmic.nix
│   │   └── kde.nix
│   └── vim.nix
├── flake.lock
├── flake.nix
└── hosts        # Machine specific configurations
    ├── desktop
    │   ├── configuration.nix
    │   └── hardware-configuration.nix
    └── t480
        ├── configuration.nix
        └── hardware-configuration.nix

To reduce the need for repeating myself, I defined options, for the NixOS module default-configuration.nix to hold the most commonly used values. Below, an excerpt of the aforementioned file, full contents on my GitHub.

# Please read the docs about stateVersion. Link is in the bottom.
{ config, lib, pkgs, ... }:

with lib;

{
  imports = [
    ./vim.nix
  ];
  
  options.custom.identity = {
    username = mkOption {
      type = types.str;
      description = "Name of the user account.";
    };
    hostname = mkOption {
      type = types.str;
      description = "The network hostname of the machine.";
    };
    stateVersion = mkOption {
      type = types.str;
      description = "The first version of NixOS installed on this machine. Read the docs: https://nixos.org/nixos/options.html";
    };
  };

  config = {
    environment.systemPackages = with pkgs; [
      curl
      wget
    ];

    virtualisation.podman = {
      enable       = true;
      dockerCompat = true;
    };

    networking.hostName = config.custom.identity.hostname;

   # ... omitted  code ...
   
   # Define a user account. Don't forget to set a password with ‘passwd’.
    users.users."${config.custom.identity.username}" = {
      isNormalUser = true;
      description = "Gustav Hjort Bonnerup";
      extraGroups = [ "networkmanager" "wheel" ];
    };

    # ... omitted  code ...

    nix.settings.experimental-features = [ "nix-command" "flakes" ];
    nix.settings.trusted-users = [ "root" config.custom.identity.username ];

    # ... omitted  code ...

    # Before changing this value read the documentation for this option
    # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
    system.stateVersion = config.custom.identity.stateVersion; # Did you read the comment?
  };
}

For my default home manager setup, I likewise define a NixOS module, however in the shorthand format instead, as I pull in the above defined default values from the environment. Again, below is an excerpt, the full contents is available on my GitHub.

{ config, lib, osConfig, pkgs, pkgs-unstable, ...}:

with lib;

{
  # ... omitted  code ...
  
  home.username      = osConfig.custom.identity.username;
  home.homeDirectory = "/home/${osConfig.custom.identity.username}";
  home.stateVersion  = osConfig.custom.identity.stateVersion;
  home.packages = with pkgs; [
    bc
    devenv # Consider deleting at some point
    htop
    just
    kdePackages.okular
    libreoffice
    nixd
    ripgrep
    texliveFull
    tree

    pkgs-unstable.antigravity-cli
  ];

  programs.direnv = {
    enable            = true;
    nix-direnv.enable = true;
  };

  programs.emacs = {
    enable  = true;
    package = pkgs.emacs;
  };

  programs.firefox.enable = true;

  programs.git = {
    enable = true;
    settings.user = {
      name  = "Gustav Hjort Bonnerup";
      email = "gustav@hjortbonnerup.dk";
    };

    ignores = [
      "*~"
      "*.swp"
      ".direnv/"
      ".envrc.local"
    ];
  };

  # ... omitted  code ...

}

Lastly, when configuring a specific machine, I pull in the default configurations shown above, into a machine specific configuration, e.g. hosts/desktop/configuration.nix, and on top of that add additional configurations for that specific machine. Below, the full contents of my desktop configuration, please not that I have added some clarifying comments, prefixed with NOTE:, that are not in the actual config.

let      # NOTE: Global values defined below
  username     = "ghb";
  hostname     = "desktop";
  stateVersion = "26.05";
in
{ config, pkgs, ... }: {
    
  imports = [    # NOTE: Importing NixOS modules here
    ./hardware-configuration.nix
    ../../common/default-configuration.nix
    ../../common/desktop-environments/kde.nix
  ];

  # Passing variables to modules imported above
  custom.identity = {
    inherit username hostname stateVersion;
  };

  boot.kernelModules = [
    "nct6683" # Driver for motherboard fans
  ];
  
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  hardware.graphics.enable = true;
  hardware.graphics.enable32Bit = true;

  programs.coolercontrol.enable = true;
  
  # NOTE: Configuring home manager setup
  home-manager.users."${username}" = {
    imports = [
      ../../common/default-home.nix
      {
        home.packages = with pkgs; [
          steam
        ];
      }
    ];
  };

}

If considering multiple users on the same machine, the home manager configuration can easily be configured to not, import the values from the global environment, but instead using the values, that are passed to the NixOS module. This can be done by not using the NixOS module shorthand format, as is done for common/default-configuration.nix.

If it can help as inspiration, my entire NixOS setup is available on my GitHub.

1 Like