Pinning configuration.nix with niv

I’ve come across two different ways of pinning configuration.nix with niv and am wondering the pros and cons of each. I’m not sophisticated enough with nix to answer the question on my own so I’m asking for help.

First, Solene’s article makes it look simple. The gist that contains the magic is:

{
  sources ? import ./nix/sources.nix,
  pkgs ? import sources.nixpkgs {},
  config, ...
}:
...

In contrast, Infinisil’s sanix is quite a bit more involved but the magic becomes apparent in the rebuild script that sets NIX_PATH to the pinned nixpkgs and the host’s configuration.nix before calling nixos-rebuild:

nixpkgsPath=$(nix-instantiate --eval --read-write-mode nixpkgs/path.nix | tr -d \")
configPath=$(realpath -- "$(dirname -- "${BASH_SOURCE[0]}")/root.nix")
exec nixos-rebuild "$@" -I nixpkgs="$nixpkgsPath" -I nixos-config="$configPath"

I usually prefer the simplest solution but it isn’t clear what I’m giving up by doing so. What does one provide that the other does not?

BTW, I already use flakes so please exclude them from the answers. This question is strictly about non-flake pinning. I’m trying to understand approaches that use niv on their own merits in a thought experiment where flakes do not exist.

I haven’t used niv in a while and can’t directly answer your question. However, I’ve seen it claimed niv is not maintained anymore. Have you considered using npins? nix.dev has a section about using it.

The benefit of the latter approach, that I can see, is that you can reuse NIX_PATH in other places.

Of course you can also just set nix.nixPath in your config (requiring a rebuild and reload of the environment), among other options.

1 Like

The approach from the gist doesn’t fully work, because only uses of pkgs within that single module will use that pinned version, but for the module evaluation (including all default modules), the pinned version isn’t used.

This is because in non-Flake mode, nixos-rebuild always uses <nixpkgs> from NIX_PATH to evaluate the modules, which is why it’s necessary to inject the pinned Nixpkgs “before” calling nixos-rebuild, like I’ve done in sanix, to get it to use a different Nixpkgs.

Note that this could be much simpler if nixos-rebuild got support for e.g. an /etc/nixos/default.nix file that can declare the entry-point for the entire NixOS evaluation. This is not very hard, just nobody has implemented that yet.

And honestly, I think this is the biggest reason why Flakes has been adopted so widely: Eelco did change nixos-rebuild to support this for Flakes, making Flakes the only built-in way for nixos-rebuild to have a declarative Nixpkgs. This makes Flakes look more necessary than it really is to get a stateless NixOS config, when it’s really just a property of nixos-rebuild.

5 Likes