How to handle variables in multi host configurations?

Hello all,
I currently have a flakes configuration, it has more than 9 hosts
They fall under 2 categories:

  • Desktops (with a DE & stuff)
  • Servers (headless)
  • Virtualisation (could be one of above or a completely separate use case)

This is what the tree looks like: link
Explanation:
./flake.nix > ./configuration.nix (imports ./hosts/hostname/ + all the host agnostic stuff like ./sys.nix, everything in ./ some things in ./programs/) > ./hosts/hostname/configuration.nix (imports hardware-configuration.nix & ./hosts/category/configuration.nix (which imports pkgs, programs, other files))

./programs/ is just a bunch of home manager stuff, dont worry about it

These are the important files:
flake.nix
vars.nix

(ive redacted a bunch of stuff for privacy and brevity), I would not like to change my filetree drastically, I have a bunch more important variables in my vars.nix which is why I find having per category & per host variables beneficial

So far, i’ve split-up my configs with:

  • files

  • nix modules

  • variables (this is what im trying to figure out how to do correctly)

This is my desired vars.nix structure:

In order of precedence: (1 has most precedence)
1. variables set per hostname
2 category set variables
3. general/ globally set variables

I would like to use these variables (in my actual configuration) as so:
e.g: users.users."${attributename.usrname}" = { isnormalUser = true; }

**however, I am not opposed to other implementations as long as they aren't very complex**

Any suggestions on how to do this would be greatly appreciated

I asked an LLM (one good at programming) for a solution, it gave me these snippets:

to my vars.nix:

  getVarsForHost = hostname:
    let
      category = hostCategories.${hostname} or "desktop";
      categoryVars = categories.${category};
      hostVars = hostOverrides.${hostname} or {};
    in
      categoryVars // hostVars; # Host overrides have final precedence

in
{
  # Export the function
  inherit getVarsForHost;`

to my flake.nix:

  let
    getVarsForHost = import ./vars.nix;
  in
  {
    # Desktop's
    ## desktop1
    nixosConfigurations.desktop1 = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      specialArgs = {
        inherit inputs;
        hostVars = getVarsForHost "desktop1";
      };

what it would look like in-practice

{ config, lib, pkgs, hostVars, hostname, ... }:

{
  # Use your variables directly
  users.users.${hostVars.usrname} = {
    isNormalUser = true;
    home = "/home/${hostVars.usrname}";
  };

this is a good solution, but I couldn’t get it to work, I kept encountering recursive errors for hours

I found a solution, it works exactly as I intended

vars.nix (variables file)

defaults = {
    # put ur global/ default settings here
    mymainusername = "default";
}
categories = { # host's categories go here
    desktops = { # example category named desktop
        mymainusername = "admin";
    }
}
hosts = { # put your host specific overrides here
    hostname1 = { # example host
        mymainusername = "usr1";
        category = "desktops"; # you must declare what category the host belongs to so it can inherit its variables
    }
}
# Explaination: the host hostname1 will have the variable mymainusername == usr1
# a host which also belongs to the desktops category, the variable mymainusername == admin
# another random host thats not in the desktops category would have the variable mymainusername == default

  # Logic
  getHostVars = hostname:
    let
      hostConfig = hosts.${hostname} or {};
      categoryName = hostConfig.category or null;
      categoryConfig = if categoryName != null then categories.${categoryName} or {} else {};
      mergedConfig = defaults // categoryConfig // hostConfig;
    in
    mergedConfig // {
      home = "/home/${mergedConfig.usr}";
      homeDir = if mergedConfig ? homeDir then mergedConfig.homeDir else "/home/${mergedConfig.usr}";
    };
  getHostsInCategory = categoryName:
    builtins.filter (hostName:
      let hostConfig = hosts.${hostName};
      in hostConfig ? category && hostConfig.category == categoryName
    ) (builtins.attrNames hosts);
  getHostCategory = hostname:
    let hostConfig = hosts.${hostname} or {};
    in hostConfig.category or null;
}

put this in your flake.nix file after “outputs”

  let
    varsConfig = import ./path/to/vars.nix;
  in
  {
    ## hostname1
    nixosConfigurations.hostname1 = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      specialArgs = {
        inherit inputs;
        vars = varsConfig.getHostVars "hostname1"; # function arg
      };
      modules = [
      ./path/to/configuration.nix
      ];
    };

then to use these variables globally in your config:

{ vars, ...}: # Add the function arg "vars" like so
{
  networking.hostName = vars.mymainusername; # this would equal "usr1" for the host, "hostname1"
}

Please feel free to message me if would would like help

1 Like