How to differentiate configurations in modular configuration.nix

I was discussing ideas on how to differentiate between multiple configs in a modular nix- or NixOS-configuration with @fricklerhandwerk the other day and since we didn’t seem to have found a really good parameter to differentiate between them, I wanted to let the community participate.

I’ve seen configs, were the differentiating factor was the $hostname. I currently do something like that myself, in that I have set up a fish-abbreviation, that runs nixos-rebuild switch -I nixos-config= with the $hostname passed as a parameter.

Making nixos-rebuild switch dependent on a fish-abbreviation doesn’t seem like a clean way. That’s why I wanted to post this thread. There are a few other methods, that I thought about:

The value of /etc/machine-id can be used to identify a particular Linux installation. It’s basically a random hex-number, that is generated by systemd during the installation-procedure or during the first boot after installation. However using this number, I would have to learn a few digits of the hex-number to actually know, which configuration to edit, if I wanted to make changes. It’s not very informative.

Another idea was to use the values of the files in /sys/devices/virtual/dmi/id/. They contain information about the hardware of the system at hand. For example /sys/devices/virtual/dmi/id/product_family contains ThinkPad X1 Carbon 5th in my case. The problem with these files is, that they are apparently too short to be read by builtins.readFile. What does work is to compare the hash of the file, for example using

builtins.hashFile "md5" /sys/devices/virtual/dmi/id/product_family

but then I would have some hash-value that again wouldn’t be very informative.

@rycee suggests linking the config file of the current host to /etc/nixos/configuration.nix (in the case of NixOS), but this would require manually creating the links. I would want to automate the installation-procedure and adding new configs as much as possible.

My personal config is GitHub - tim-hilt/nixos: My configuration-files for NixOS. It’s not very clean by any means and is under heavy construction at the moment. I’m still a “new” NixOS-user atm, so bear that in mind.

I’m happy for every idea and suggestion regarding this topic! Is there maybe some unknown (to me) consensus on modular config-files? Is there simply a cleaner way than the ideas I’ve explained above?

4 Likes

The only thing I find unclean about it is that it’s tied to fish specifically. Does anything speak against wrapping the call in nix?

What makes it better suited to identify a machine than a hostname anyway? When bootstrapping a fresh system, neither is set to anything you control, so you’d have to pass some parameter. And once the environment is in place, it can pick up the parameter value from itself, as linked above.

You’ll eventually get into trouble binding your configuration to hardware identifiers. What happens if you change your hardware for whatever reason? You’d have to manually update your configuration. Just noting this, since I don’t have an answer. On my systems I decided to let systemd mounts reference disk WWNs, and it’s a mess if you have to replace a disk. It’s too specific and you can’t set them. Using Linux device names is too vague and not deterministic. For file systems, partition UUIDs are probably better, because you can set them on creation. The gist is, maybe that’s true for the system in general: It should be a value you define once.

I’m sure there’s a nice way to do it with stable Nix, but it would probably be a lot easier with the flakes interface if your willing to try it, since nixos-rebuild will automatically attempt to build the nixosConfiguration ouput matching your current hostname. It’s fairly trivial to then share modules between various hosts.

Since it is practically a default feature of flakes, I figured it was worth a mention.

4 Likes

That’s indeed nice to know! :slight_smile: I was just starting to look into flakes, but didn’t find much time to dive deeper or try things out. This sounds very promising and clean though! Will keep that in mind.

Ah, you’re right. This is indeed a great observation, that I didn’t think of before. Based on what you and @nrdxp commented, I think that the hostname might very well be the best and cleanest way to differentiate configs!

I have an “approach” in my nixcfg repo: GitHub - colemickens/nixcfg at 49d2d92139f593b29043a994bf8bc1030aa3df1a

  • I make no attempt to pretend NixOS is a multi-user system
  • I make no attempt to isolate HM config from NixOS config (there are too many places they need configuring in tandem, just go all in)
  • I have three important top dirs:
    • hosts/<hostname>/configuration.nix - this is the top nix file that my flake nixosConfigurations.<hostname> points to, effectively (or you could point nixos-rebuild at, etc)
    • that will import from profiles/ and import:
      • profiles/desktop-sway.nix - cascade imports “core” + “user” + “interactive” profile, sets up sway, etc
      • profiles/interactive.nix - used on devices I regularly SSH into and want niceties on tmux, etc, imports “core”, etc.
      • profiles/{core,user.nix} - even slimmer profiles
    • the hosts configs will also directly includes bits from mixins/:
      • libvirt.nix - everything HM/NixOS related to getting a usable libvirt experience on a host
      • etc, sshd.nix, waybar.nix, tailscale.nix, etc.

So far it has grown with me to span across my laptops, desktops, server, phone, pi scenarios, etc.

4 Likes

I’m facing a similar challenge. My goal is to maintain a single repository for /etc/nixos that is shared among all machines and supports variations based on the services I want available in each environment with minimal dependence on machine-specific identifiers (including the hostname, which is obtained via DHCP on my network).

To that end, I’ve coarsely modularized the configuration into a list of files that are imported in configuration.nix if they are not commented out. I don’t track boot.nix or hardware-configuration.nix because they are generated during the installation process. The repository includes a local.nix template file that I edit locally for machine-specific quirks (rarely used).

Since most of my machines are generic graphical workstations, I target that as the default so that installing the unmodified repository in /etc/nixos just works. For some servers, I uncomment the appropriate service files and comment out the workstation.nix file. At some point, I’d like to improve the configuration by figuring out how to set variables to affect which files get imported and add functions that take arguments so I can deduplicate configurations that only deviate by a couple of settings (things like interface names and setting masters).

might as well do a bit of shameless self promotion since it is so relevant to this question:
There is also the DevOS project which already solves a lot of these concerns if you don’t mind a bit of an opionated approach.

3 Likes

Today I tried integrating my current configuration into a flake and slowly transition to use flakes the way they are intended to.

I read Eelcos Blog-Post on configuring a NixOS-system with flakes and simply added modules = [ ./configuration.nix ] to include my current top-level configuration.

However, when I run nixos-rebuild switch --flake . inside the repo-directory, I keep getting an error, that I really can’t make sense of:

❯ doas nixos-rebuild switch --flake . --show-trace
doas (tim@x1carbon) password:
error: error: executing 'git': No such file or directory
error: program 'git' failed with exit code 1

       … while fetching the input 'git+file:///home/tim/dev/nixos'

git is totally present on my system! Why do I keep getting that error? I pushed the current state to my GitHub-repo: GitHub - tim-hilt/nixos: My configuration-files for NixOS if anyone wants to take a look.

:confused: Can you try sudo? Does root have access to git or only your normal user?

1 Like

Ooooh that’s it, of course! I disabled git-support for the root-user!

EDIT:

It didn’t actually work, after I added git to the root-account, so I tried the other suggestion and tried it with sudo instead of with doas. Now it works as expected, even without root having access to git.