Does Nix Support Creation of Template-based Configuration.nix files?

Say I had a dozen field sales people outfitted with the same model laptop but having different role-based application suites, networking credentials, access to backoffice systems, etc.

Does Nix support templating such that I could use Nix itself to generate individually customized configuration.nix files for each employee programatically?

Like Mail Merge for email, or Mustache, Handlebars, Jinja2 for HTML.

Well… it is a programming language. So what did you have in mind? You could do pretty much whatever you want. Declare the username as an input and create values and conditionals based on that, etc… Skip the file generation and just provide values as conditionals, etc…

2 Likes

I’d like the user-specific data to reside in a cvs, json or xml formatted file where fields in each row, record, etc. can be used to populate a configuration.nix template and produce customized configuration.nix files for each user.

Does Nix support any kind of templating natively?

Are there Nix addons or modules that can extend Nix for this use-case?

I think we should take a step back and find out what you’re trying to accomplish here, instead of immediately jumping to template files. What is your actual deployment method? Are you copying the files to each device by hand? Do you pull your configuration from a git repository? Do you build locally and target remote machines?

An option I would recommend would be a single central flake.nix with outputs for each device in the org. You can certainly load data from a JSON file using readFile + fromJSON (Built-in Functions - Nix Reference Manual), and generate the flake outputs dynamically with conditional settings, etc. based on attributes of the JSON data (or plain Nix data). Then you’ll have many nixosConfigurations based on a single common framework, each corresponding to its target machine.

When you run a nixos-rebuild --flake /path/to/flake-or-remote-repo, it will default to the output that matches the network hostname of the device. However, you could also select a specific output named joesmith say by running nixos-rebuild --flake /path/to/flake-or-remote-repo#joesmith.

2 Likes

To expand on that answer, here’s a simple example of a flake.nix:

{                                                                                                                                                                                                                                                                                   
  inputs.nixpkgs.url="github:NixOS/nixpkgs/23.05";                                                                                                                                                                                                                                  
  outputs = { self, nixpkgs, ... }: {                                                                                                                                                                                                                                               
    nixosConfigurations = nixpkgs.lib.genAttrs [ "alice" "bob" "chloe" ] (                                                                                                                                                                                                          
      user: nixpkgs.lib.nixosSystem {                                                                                                                                                                                                                                               
        system =  "x86_64-linux";                                                                                                                                                                                                                                                   
        modules = [                                                                                                                                                                                                                                                                 
          (import ./configuration.nix { inherit user; })                                                                                                                                                                                                                            
        ];                                                                                                                                                                                                                                                                          
      }                                                                                                                                                                                                                                                                             
    );                                                                                                                                                                                                                                                                              
  };                                                                                                                                                                                                                                                                                
} 

And the accompanying configuration.nix:

{ user }:                                                                                                                                                                                                                                                                           
{ config, pkgs, ... }:                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                                    
{                                                                                                                                                                                                                                                                                   
  services.httpd.enable = true;                                                                                                                                                                                                                                                     
  services.httpd.adminAddr = "${user}@example.org";                                                                                                                                                                                                                                 
  services.httpd.virtualHosts.localhost.documentRoot = "/webroot";                                                                                                                                                                                                                  
} 

The configuration is not complete, but you get the idea. Instead of a regular function, configuration.nix is now a higher-order function that you can call with some arguments (in this case just user) and it will return a new function where the user name has been inserted to the services.httpd.adminAddr option.

The flake.nix file then, instead of just passing the configuration as a module directly, imports it and calls it with the username. nixpkgs.lib.genAttrs just takes the usernames and creates a separate output for each:

$ nix flake show
path:/Users/felix/repos/experiments/sysconf?lastModified=1687636645&narHash=sha256-963ttCohsJfEMsciNWnoB1dQxLh6ElnXSL/nDanr6qE%3D
└───nixosConfigurations
    ├───alice: NixOS configuration
    ├───bob: NixOS configuration
    └───chloe: NixOS configuration

You can then build a single one of these configurations with nixos-rebuild build --flake .#alice.

To find ways of reading and extracting data from json/yaml files, I recommend searching through the library and builtin functions on noogle. Nix currently has no builtin support for csv, and I don’t know of a library for that either.

To actually deploy this, you can build bootable images from these configurations with nixos-generators. You could then put an ISO on a stick to install the OS, or put an image on a PXE server and make a fully automated install. Not sure how sophisticated your operation is.

For maintenance, a good idea might also be to ensure that ssh is enabled by default, the hostname is set to something known like "${user}-pc" and your public ssh key is already installed. Then, if you change something in the configuration.nix and want to deploy that, you can execute nixos-rebuild switch --flake .#alice --targetHost alice-pc on your machine, it will rebuild the config, copy it to alice’s PC, ssh into it and execute nixos-rebuild switch automatically.

2 Likes

I’m trying to accomplish something similar (or superior) to the system I’ve currently got for managing a private Asterisk PBX that serves a dozen field personnel using LinPhone on their Android Phones and their Linux Laptops.

The user-specific names, hostnames, credentials, etc. are in a json file which a python script uses to configure the asterisk accounts AND a custom LinPhone configuration file thats read automatically by their LinPhone clients so they don’t have to manually and tediously configure or reconfigure their phones.

Thank you for your suggestion.

It looks quite promising and is further confirmation I need to accelerate my understanding/deployment of Flakes.

My endgame would be to deploy and configure my entire Asterisk PBX solution using Nix/NixOS alone and eliminate Python Scripts, Ansible, etc.

This is good stuff and further confirms my intuition that the cover charge to learn/master Nix/NixOS will pay major dividends in terms of manageability, deployment, reliability, and of course reproducibility.

Functional programming concepts and Nix syntax are unfamiliar to me but I am making progress.

Would you agree with the advice I’m see frequently in forums and tutorials, to take the plunge and begin using Flakes now rather than wait for them to be promoted officially from experimental?

1 Like

I would like to say yes, because as you saw flakes allow you to do some very cool stuff and have nice tools around them like nix flake show. But as you’re using this in production to manage devices for actual users and I don’t know your workload, I’m somewhat hesitant to recommend it.

Experimental means experimental, though the interface has been mostly unchanged for I think over two years now. There is a lot of discussion going on right now about our roadmap to stabilize flakes, so it will happen at some point, but there might be some pains when upgrading Nix until that happens. Some people argue that we can break basically anything while a feature is marked experimental, but I feel like that is the minority, especially regarding flakes.

The big problem is that this breakage may prevent you from installing updates. If the flake interface changes, and nixpkgs is updated to align with them, and there’s some incompatibility with your configuration, you may not be able to upgrade packages that require urgent security fixes. Now, granted, the possibility of this happening is quite slim in my personal opinion, but as the feature is experimental, there is no guarantee that it will not happen.

So, in the end, it’s your call. Flakes will make your life easier, they are most likely the future, but there is a risk associated with using them in production environments right now.

1 Like

I’m somehow doing something similar in here: GitHub - drupol/nixos-x260: Contains the configuration of every home computers

1 Like

Yes, I think flakes would suit your needs well and are probably worth jumping to here, particularly because you are trying to create a set of related-but-distinct configurations. They have other advantages too so I think it’s best place to start, especially if you are comfortable with git.

Since you mentioned credentials, I should probably advise that it’s best practice to avoid including them in plain text as part of your flake (at least if you are committing them to git, especially if you are hosting the repository on the internet; but regardless, they will be written to the Nix store which is world-readable on each device). If you want to use a more robust strategy, it’s a common pattern to encrypt secrets for a target’s host key, put those in your flake, and decrypt them at runtime. Or deploy the secrets outside of Nix using Ansible or other scripts.

In general, I think you can remove the need for Ansible or Python by using NixOS, but if you are deploying lots of target machines then you may want to use a deployment tool or script to automate the process.