Channel vs pkgs vs flake

I’m trying to pin my nixpkgs channel by copying it into my derivation (for pushing to other systems) and I noticed that if I use pkgs.path to symlink, I get something that I need to push completely and can’t substitute on the remote.

I’m also playing with flakes and I think flakes would be substitutable but I can’t push those yet with morph.

I also noticed that channel downloads include nixos/programs.sqlite but I can’t find where that is generated.

So now I’m wondering what is the difference between pkgs.path, someFlake.outputPath and a channel. I’d also like to know how to get programs.sqlite via flakes.

Sorry, rambling :slight_smile:

Flakes uses flake.lock to download a version of nixpkgs and store it in the nix store, and assigns that path to nixpkgs.outPath (where nixpkgs is the nixpkgs flake input variable). Channels, however, are completely different. The nix-channel tool just has a little bit of state on your system that tells it what URL to check for updates to your configured channels. The nixpkgs channels stored at are a bit different than a nixpkgs checkout from GitHub; like they include programs.sqlite.

Finally, pkgs.path is just a path literal that points to the path on disk of whatever nixpkgs source was imported to get pkgs from. The awkward thing about pkgs.path is that it’s a path literal. Which means that when you interpolate it into a string, it gets copied into the store at a path whose hash component includes both the name and the contents.

nix-repl> pkgs = import <nixpkgs> {}                                

nix-repl> pkgs.path                                                 

nix-repl> "${pkgs.path}"                                            

nix-repl> "${builtins.path { path = pkgs.path; name = "source"; }}" 

So since the name of path at pkgs.path is 3c249himjn7sh3cwnapmh5qgrnmd42xg-source, the path you store it at with "${pkgs.path}" is /nix/store/<hash of contents & the name "3c249himjn7sh3cwnapmh5qgrnmd42xg-source". But if you use builtins.path to set the name to the same as the nixpkgs source, in this case source, then that name part of the hash becomes the same, as are the contents, so it ends up at the same path (i.e. deduplicated, and probably substitute-able)

1 Like

Thanks that makes sense. However, it is still not substitutable, unfortunately.

So right now I’m doing

  # Pin the current pkgs set inside the system closure
  nix.nixPath = [ "nixpkgs=/run/current-system/nixpkgs" ];
  system.extraSystemBuilderCmds = ''
    ln -s ${builtins.path { path = pkgs.path; name = "source"; }} $out/nixpkgs

I wonder, I really want to get the channel in there instead. In my case I always have it set to the tgz file, for example Is there a way for me to get a unpacked version in the store?

I guess could actually run a command on the remote system to populate the store, in which case I’d still need some build expression, but I wonder if there’s a cleverer way to do it? Maybe I should keep the packed version in the store instead? Wouldn’t that slow down nix commands? And programs.sqlite wouldn’t be available, hmm.

Wait, are you using channels or flakes?

Well, I’d like to use flakes to deploy systems but morph doesn’t support them yet. So I need to use channels. I wasn’t aware of the difference between pkgs and a channel.

And even when I use flakes, I now realize that I wouldn’t have programs.sqlite. Hmmm.

Well if you’re not using flakes, I can’t guarantee that ${builtins.path { path = pkgs.path; name = "source"; }} is the right thing to use. I have no idea what pkgs.path evaluates to. (Also, if it evaluates to /run/current-system/nixpkgs, then this is going to blow up, because you’ll be making /run/current-system/nixpkgs be a symlink to /run/current-system/nixpkgs).

Getting programs.sqlite via flakes is not possible (or at least not easily). It is injected into the channel tarball before being uploaded to S3 or wherever they are stored nowadays. The nixos-channel-scripts repository implements this functionality (among other things).

1 Like

Ok, I was able to achieve my goals by first getting the channel URL and then fetching+unpacking it with nix-prefetch-url:

nix-prefetch-url $(<nix-channel) --print-path --unpack --name nixpkgsChannel | grep /nix > nix-channel.path

and then I can pre-fetch it on the remotes with

morph exec "$FILE" "hostname; if [ ! -r $(<nix-channel.path) ]; then echo Fetching channel; nix-prefetch-url $(<nix-channel) --unpack --name nixpkgsChannel; fi"

and I can use the path in the expression with

  # Pin the current pkgs set inside the system closure
  nix.nixPath = [ "nixpkgs=/run/current-system/nixpkgs" ];
  system.extraSystemBuilderCmds = ''
    ln -s ${lib.removeSuffix "\n" (builtins.readFile ./nix-channel.path)} $out/nixpkgs

but what are your goals?
Do you want a reproducable build? like a pinned version of nixpkgs? that’s what flakes are for, no need for any magic, just use a desired revision as a flake input.

Yes I’d like that but right now we’re in a transition period where flakes are experimental and not supported by morph.

do you really have to use morph? There are other options like GitHub - serokell/deploy-rs: A simple multi-profile Nix-flake deploy tool. + GitHub - yaxitech/ragenix: age-encrypted secrets for NixOS; drop-in replacement for agenix

Flakes - NixOS Wiki
Using flakes project from a legacy Nix
There is a flake-compat library you can use to shim legacy default.nix and shell.nix files. It will download the inputs of the flake, pass them to the flake’s outputs function and return an attribute set containing defaultNix and shellNix attributes. The attributes will contain the output attribute set with an extra default attribute pointing to current platform’s defaultPackage (resp. devShell for shellNix ).

Have you tried this?

1 Like

Yes that looks great now - I saw it before but was scared away by the flake aspect. Ragenix looks good too!

1 Like