Extracting username and hostname from Nix configuration (flakes)

I’m new to Nix. I’m using nix-darwin on my Mac, and I have a basic flake. In there, I use darwinConfiguration.[my_host_name] = ... where [my_host_name] is replaced with a hard-coded copy of my computer’s hostname. Since I want to keep this config on a public GitHub repo, I would like to extract this hardcoding out of my flake and instead use a variable imported from another file (that I can gitignore) like this: darwinConfiguration.${hostname}. I seemed to have gotten something to work when I did this:

# ./user.nix (is gitignored)
{
  username = "nixos-username" # Example username
  hostname = "nixos-hostname" # Example hostname
}
# ./flake.nix
...
outputs = input: 
  let inherit (import ./user.nix) username hostname;
  in
  {
    darwinConfigurations.${hostname}...
...
    users.users.${username} = {
      name = username;
      home = "/Users/${username}";
    }
...

While I was able to switch to this config, something seemed to have broken internally because darwin-rebuild and some nix commands don’t work anymore (“no command found”) and the zsh shell which I’ve configured via nix does not start (like it did before when I hardcoded my username and hostname).

I suspect it has to do with the way I’ve imported user.nix script and the way I used it to define my user.

How do I properly define this user.nix file to extract my username and hostname from the main config files?

Disclaimer: I’m not very experienced in nix and there might be better solutions. But specifically for what you ask, I’d do something like this:

let hostname = if (builtins.pathExists ./private.nix) then (import ./private.nix) else  "some default"; in hostname

So in this case:

$ cat private.nix
"iamcat"

Then in repl you get this. And as an example of what happens when file is not present, see second example with notexists.nix – you get default value:

nix-repl> let x = if (builtins.pathExists ./private.nix) then (import ./private.nix) else "some default"; in x
"iamcat"

nix-repl> let x = if (builtins.pathExists ./notexists.nix) then (import ./notexists.nix) else  "some default"; in x
"some default"

Alternatively, for a similar task of storing secrets (e.g. password or API token) I recently used sops-nix

1 Like

Thanks for the suggestion! Can you show me how you use it to define your nix configuration hostname and your users.users.${username} section? I think I might be doing this wrong somehow and breaking my system.

Sure! In my case it’s straightforward – I don’t keep hostname secret, so I just use plain string, declared here.

My users.users.${username}

As for users.users.${username} section, it was quite a journey of trial-and-error to make it work with nix-darwin and home-manager. Most of my problems were because I wanted to refactor some config pieces to be here or there, as functions/modules, with arguments – but the lack of better nix language understanding made me struggle a bit.

I ended up having main flake.nix declaring all hosts (currently four), each host lists modules which are declared under hosts/$HOSTNAME folders, and which are usual configuration.nix-equivalents. You can look at them here.

For darwin specifically I did this:

darwinConfigurations = {
  leopard = nix-darwin.lib.darwinSystem {
    system = "x86_64-darwin";
    modules = [
      home-manager.darwinModules.home-manager
      {
        nixpkgs.overlays = [ inputs.nixpkgs-firefox-darwin.overlay ];
        home-manager.useGlobalPkgs = true;
        home-manager.useUserPackages = true;
        home-manager.backupFileExtension = "bak";
        home-manager.users.nikita = import ./home/home.nix;
      }
      ./hosts/darwin
      ./hosts/darwin/leopard
    ];
    specialArgs = { inherit inputs; };
  };
};

Things to note – modules has three things imported:

  1. home manager with settings.
  2. ./hosts/darwin contains common definitions that are supposed to be shared by all darwin hosts. It also has that users.users.${username} entry there, see here.
  3. ./hosts/darwin/leopard is for specific host only.

Also, to make these modules have access to pkgs, lib, etc., note that there’s specialArgs = { inherit inputs; };

sops-nix

I also mentioned sops-nix in last comment. I used it to store a token for automated Let’s Encrypt certification handling, see here.

If you want to hide something in a publicly available nix config and ask me, I’d use this (there other similar tools, it’s just one I tried first and it worked pretty well so far).

What to read

Unfortunately, my current setup does not have good “declare user once and for everyone”, so my main user is declared a few times for other hosts. And the whole config is probably not the best example of how things should be, it’s more of a documented progress I do.

Also as an advice I’d give: explore other’s configs. Most of the stuff I learned was by reading other people’s configs. It’s a bit time consuming, and proficient users tend to complicate things in a good way, but when you have little experience, it is usually quite hard to properly understand what’s going on there. Things are declared as modules importing modules, seasoned with flake-utils and other things – but it allows to do really cool things. Most of my latest inspiration came from GitHub - Mic92/dotfiles: My NixOS dotfiles and just experimenting.

3 Likes

Thanks for taking the time to explain all of this! I’ll take a look at the resources you’ve linked.

1 Like

You are very welcome! Also, yesterday I saw a comment about how to organise config in general which I found interesting: Can anyone help explain to me how I pass values in Nix? - #2 by TLATER

I’m thinking applying those advices to my own config. Good luck!