Best practices for auto-upgrades of flake-enabled NixOS systems?

Hi there,

I just took the flake based example for the NixOS Wiki, but it seems that does not work out of the box. At leasst for my setup which is the following.

under /home/myuser/repos/infrastructure is my checked out git repo with the flake.nix + configuration.nix + hardware-configuration.nix

I do invoke the rebuild like this: nixos-rebuild switch --flake '.#' --sudo

Auto Upgrade part (should be almost exactly as in the wiki)

system.autoUpgrade = {
       enable = true;
        flake = inputs.self.outPath;
        flags = [ "--print-build-logs" ];
        dates = "18:10";
  };

I have not analysed the error yet, I was under the assumption it would just work. The thing that’s “weird” is that there is no mention of “updating” the flake.lock
My assumption would be that a flake based nixOS upgrade is done by updating the flake’s inputs.
But I might be wrong as this is basically my 2nd or 3rd week into nixOS on my second machine now. Still figuring things out.

Plus it might be related to systemd running the upgrade as root, the flake being located in a directory owned by my user?

Error Output here:

journalctl -u nixos-upgrade.service

Sep 04 18:10:14 rpi-nixos systemd[1]: Starting NixOS Upgrade...
Sep 04 18:10:22 rpi-nixos nixos-upgrade-start[1364]: unpacking 1 channels...
Sep 04 18:11:22 rpi-nixos nixos-upgrade-start[1334]: warning: could not re-exec in a newer version of nixos-rebuild, using current version
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1588]: unpacking 1 channels...
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: building the system configuration...
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: error: your NixOS configuration path seems to be missing essential files.
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: To avoid corrupting your current NixOS installation, the activation will abort.
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: This could be caused by Nix bug: https://github.com/NixOS/nix/issues/13367.
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: This is the evaluated NixOS configuration path: /nix/store/bzc6gvxpjgdzgcppdx3jdrfkvzkgl92j-source.
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: Change the directory to somewhere else (e.g., `cd $HOME`) before trying again.
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: If you think this is a mistake, you can set the environment variable
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM to 1
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: and re-run the command to continue.
Sep 04 18:11:23 rpi-nixos nixos-upgrade-start[1334]: Please open an issue if this is the case.
Sep 04 18:11:23 rpi-nixos systemd[1]: nixos-upgrade.service: Main process exited, code=exited, status=1/FAILURE

This resolves to a location in the nix store. Put a literal string with the location here, there is no way to get a handle on the directory programmatically.

One more question if you mind me asking: When would be the right time/use-case/setup to flake = inputs.self.outPath;, from my understanding this would make sense almost never, because the output of a flake is not a flake it’s a built artifact, it’s not even a .drv right? Is the wiki example incorrect maybe?

You can use it with --no-write-lockfile like the folks further up thread, because then nix won’t attempt to update a location in the nix store but simply ignore whatever the contents in flake.lock are.

i don’t like this very much because it means your build is practically not reproducible, but to each their own.

This statement is mixing up quite a few concepts.

“Flakes” do not have an output at all in that sense. It’s a standard format for expressing what a nix project does. Flakes have a set of outputs, and a flake can have any number of them.

These outputs can be anything you can express with nix, including simple variables, but also derivations. A nix project can have multiple different outputs.

So, a “flake’s output” is an undefined concept, because there isn’t just one. A flake’s outputs can be many things, including a .drv.

As for why it has an outPath, nix will place flakes in /nix/store before evaluating them. Apparently .outPath resolves to this location. I find this mildly surprising and it isn’t documented anywhere, but apparently that’s how that works. I suppose internally nix treats flakes as derivations - this makes sense since they are basically just fixed-output derivations.

FWIW, this displays one of the major issues of flakes: any time you evaluate a flake, nix dumps it into the world-readable /nix/store. This is a lot of disk churn for almost no reason, and has security implications. Be very aware that this is what nix does.

There’s the upcoming (well, maybe, it’s been half a decade, but I guess dix has it now) lazy trees feature which is supposed to help with this, but any reference to self or .outPath disables even that.

With explaining .outPath out of the way, referencing the generic self is very useful. You can e.g. get a handle on any packages you’ve defined in your flake to install them in your NixOS config (i.e. inputs.self.packages.${pkgs.system}.package).

And of course you can use the outPath to get a handle on the root of your flake. You can also use string interpolation: "${inputs.self}/some/file".

When you interpolate a derivation like that, nix will automatically use its .outPath. And apparently flakes are fully-fledged fixed-output derivations.