Can I create an sdImage with a preconfigured default channel?

Hello – I’m continuing to struggle to get compressed BTRFS root working on my NixOS Pi.

Having tinkered with quite a bit, I realized today that it would probably be helpful for me to pin my nixpkgs version, since it seems to default to unstable.

From Towards reproducibility: pinning Nixpkgs — nix.dev documentation, I can do something like pkgs ? fetchTarball… in my sd-image.nix, but that seems to specify the nixpkgs used in building the sdImage but once I boot, I still see unstable as the default when I nix-channel --list.

I found this SO answer: NixOS: Setting the default channel in configuration.nix - Stack Overflow which recommends e.g. system.autoUpgrade.channel = "https://nixos.org/channels/nixos-16.03-small/";, but I presume this would only take effect with autoupgrades (or perhaps nixos-rebuild --upgrade switch).

Is there a config parameter somewhere that I can use to build a sdImage that uses a default channel?

I think nix.nixPath is the thing that you will need to set to something along the lines of [ "nixpkgs=${builtins.fetchTarball {...}}"]
https://search.nixos.org/options?channel=22.05&show=nix.nixPath&from=0&size=50&sort=relevance&type=packages&query=nix.nixPath

Thanks for the suggestion. I’ll try putting that in sd-image.nix and report back.

This seems to work in my flake.nix:

inputs = { nixpkgs.url = "nixpkgs/nixos-22.05-aarch64"; };

I read something about it setting the default channel to the channel used to build the image, which seems to be true in this instance.

Actually I’m not entirely sure this is working as I intended.

I’m setting inputs.nixpkgs.url = "nixpkgs/nixos-22.05-aarch64";: https://github.com/n8henrie/nixos-btrfs-pi/blob/0b33a340eea39aa8642ef9f6223aeebbf3d16316/flake.nix

which I’m using to generate the channel: https://github.com/n8henrie/nixos-btrfs-pi/blob/0b33a340eea39aa8642ef9f6223aeebbf3d16316/btrfs-sd-image.nix#L61

but when the image boots:

# nix-channel --list
nixos https://nixos.org/channels/nixos-22.05
# nix-info -m
 - system: `"aarch64-linux"`
 - host os: `Linux 5.15.74, NixOS, 22.05 (Quokka), 22.05.3767.6107f97012a`
 - multi-user?: `yes`
 - sandbox: `yes`
 - version: `nix-env (Nix) 2.8.1`
 - channels(root): `"nixos-22.05"`
 - nixpkgs: `/nix/var/nix/profiles/per-user/root/channels/nixos`

nix-channel keeps a seperate “database” of the channels configured. It does not care for might or might not be in the nix path.

It just tells you what it has in that database.

Similarily nix-info is just summarizing some infos it collects from other nix executables, eg nix-channel.

Huh, so nix-channel --list is “lying” to me? I noticed that its output is found somewhere in /nix/var/nix/db/db.sqlite (used nix-channel --add to add something bizarre and searched my system for that string).

On the system in question:

# nix-channel --list
nixos https://nixos.org/channels/nixos-22.05
# cat /nix/var/nix/profiles/per-user/root/channels/nixos/.git-revision
6107f97012a0c134c5848125b5aa1b149b76d2c9

So without even having to clone nixpkgs locally, I can look at the following and see that it is indeed pointing to the most recent nixos-22.05-aarch64:

Looks like there are commits every day or two, so I guess I’ll wait until it updates, nix-channel --update, and see where the channel is pointing to?

I don’t see where it is keeping the actual path to that channel (its upstream). I guess that’s also in the db somewhere?

# cat /nix/var/nix/profiles/per-user/root/channels/manifest.nix 
[ { meta = { }; name = "nixos-22.05"; out = { outPath = "/nix/store/b2786phzz13fr7nmfn48n03j924iljpr-nixos-22.05"; }; outPath = "/nix/store/b2786phzz13fr7nmfn48n03j924iljpr-nixos-22.05"; outputs = [ "out" ]; system = "builtin"; type = "derivation"; } ]

Channels and flake inputs are distinct.

nix-channel --update will update the channels. This will not change what the system does get build from if you build by flake.

And of course, the contents of the commit you build from will be available locally. This is how flakes work, they download their inputs.

Sorry, I think I was unclear, and thanks for your time and responses. I’m using a flake to build the sdImage but have not yet adapted the image itself (or its system rather) to use flakes; it’s still using a regular /etc/nixos/configuration.nix.

My question in this thread is about how to configure the image builder (which is flake-based) to pre-configure the resulting sdImage (which is not flake-based) to use a specific channel by default.

I am not aware of a way to declaratively configure the channel(s), the best you can do is to set nix.nixPath and ignore nix-channel completely.

2 Likes

Fair enough – though it seems like that approach will have to download everything remotely every time – is that right? Since there is no nix-channel --update equivalent?

For the moment I just made a very simple flake and it seems to get the job done. Thanks again for your time and responses!

When the inputs are exposed as nix.nixPath, then they are referenced by the closure and should not be collected.

Not sure what else you could mean by “download everything […] every time”

I think I was confused because after spinning up a fresh image it seems to have to redownload much more for the first nixos-rebuild --upgrade switch when I set nix.nixPath as opposed to when I don’t. I think that may be because I’m pre-populating the channel and closure during the image build (or at least trying to). Not sure.

Currently, when I set nix.nixPath, if I try to nixos-rebuild --upgrade switch right off the bat it fails; I have to first run it without --upgrade, and then can run with --upgrade. ¯\_(ツ)_/¯

That’s not a big deal though.

You do not want to use nixos-rebuild --upgrade with flakes. That will use channels rather than your configs input.

To be honest, on my flakified systems /etc/nixos doesn’t even exist and nixos-config is unset in the nix.nixPath.

This way I will never be able to accidentally replace my system with something else.

You really should totally ignore anything that operates on channels. Sure you can use paths that get resolved via the nix path, though you shall not run nix-channel or anything that uses it.

Perhaps once to make sure you do not have any channels at all.

Wait, really? nixos-rebuild seems to detect /etc/nixos/flake.nix and prefer it over /etc/nixos/configuration.nix, even though both exist, so I assumed that --upgrade would just run a nix flake update instead of nix-channel --update. Interesting choice of behavior here.

I skimmed the nixos-rebuild script, and for me it looks like the following:

  • --upgrade will be a NOOP if --flake was given explicetely
  • --upgrade will not see the flake, if one relies on it being detected implicitely so it will try to update the channels by the regular means.

For flakes you have to do update and build/switch seperately, and this is a good thing!

Always do nix flake update --commit-lock-file && nixos-rebuild build || git revert HEAD (schematic example).

Personally I have gone thus far that I never “switch” from a dirty tree.

This means:

  • I allow myself to run nixos-rebuild build from any tree state
  • I allow myself to run nixos-rebuild test only from a clean tree
  • I allow myself to run nixos-rebuild boot or nixos-rebuild switch only from the remote

One of my machines does not even have a local clone of the repository. I know, this is a bit extreme, though I know this way, I can always find any boot entry in my repository as well. And due to the use of configurationRevision I can always find the exact commit.

I’m really grateful for all your time with these responses!

I was just doing the same.

I don’t think so – it will be a noop if $flake is empty (1). Wait, no you’re right – because flake isn’t implicitly set until 2… which seems odd. I wonder if that’s intentional? If implicitly determining flake behavior, wouldn’t one want to sort that out as early as possible in the script in order to have more predictable behavior?

Interesting. Why would one prefer nix flake update && nixos-rebuild... vs nixos-rebuild --upgrade? Seems like updating the “inputs” is explicit either way. What would be the downside of having nixos-rebuild --upgrade run a nix flake update if it implicitly detects a flake-based configuration?

Only doing the rebuild and update separate steps with committing the configuration will ensure that you:

  1. Can properly revert the update, optionally with a commit message that explains the “why”
  2. You get a config revision that you can use to follow the current generation to the correct git commit. (Assuming you did the necessary wire up first)

I guess that makes sense, separation of concerns:

  • nixos-rebuild switch to make live changes to one’s configuration
  • nix flake update to update the inputs, ostensibly without changing the configuration

If nixos-rebuild --upgrade switch both made live the changed to one’s configuration and updated the inputs at the same time, it would be difficult to know what was to blame when something went wrong.

Perhaps not – see the note here:

If you are using flakes and use a local repo you can add
[ “–update-input” “nixpkgs” “–commit-lock-file” ]
to update nixpkgs.

Testing now to see if this only works with a “local repo” or if it will behave similarly as non-flake behavior with automatic upgrades.