Okay. So: so far I’ve managed to write a flake, but I have no idea how to… use it. And I’ve managed to restore nix search to functionality, basically, by pinning my nixpkgs flake… but now I am searching something different than I will be installing by using nix-env, or by referencing in my shell.nix files, and this bothers me.

Which means I’m going to try the new flake-powered profiles, and figure out what the flake-equivalent version of a shell.nix is, and the new way to write nix-shell -i and nix-shell -p, and all of that good stuff.

But before I get into that, there was one other nix command that didn’t work for me before, because it depended on flakes. So let’s see if it works now that I’ve enabled them:

$ nix path-info 'nixpkgs#git'
warning: error: unable to download 'https://github.com/NixOS/flake-registry/raw/master/flake-registry.json': Couldn't connect to server (7); retrying in 307 ms
warning: error: unable to download 'https://github.com/NixOS/flake-registry/raw/master/flake-registry.json': Couldn't connect to server (7); retrying in 515 ms
warning: error: unable to download 'https://github.com/NixOS/flake-registry/raw/master/flake-registry.json': Couldn't connect to server (7); retrying in 1400 ms
warning: error: unable to download 'https://github.com/NixOS/flake-registry/raw/master/flake-registry.json': Couldn't connect to server (7); retrying in 2038 ms
warning: error: unable to download 'https://github.com/NixOS/flake-registry/raw/master/flake-registry.json': Couldn't connect to server (7); using cached version
querying info about missing paths/nix/store/bcjf7pr923hinmzcci9qh47g4zd3ds4q-git-2.34.0

Oh okay neat great I love that.

Didn’t I just pin nixpkgs so that I wouldn’t need to download things?

Ugh ugh ugh.

Okay, so I actually saw an option yesterday, when I was scanning through man 5 nix.conf, but I didn’t mention it at the time because I was busy with other things. But I believe that it’s relevant here:

โ€ข flake-registry

  Path or URI of the global flake registry.

  Default:
  https://github.com/NixOS/flake-registry/raw/master/flake-registry.json

I mention that because I don’t know where I would have even begun to look into this if I hadn’t seen that. But now I have some idea that it has to download the global flake registry every time (every time?) I do anything involving flakes. Which… yeah cool no thank you.

It has a cached version, too. It just… doesn’t want to use it, until it spends over four seconds retrying this download.

So obviously… let’s not do that. I download the file locally, and add this line to my ~/.config/nix/nix.conf:

$ grep flake-registry ~/.config/nix/nix.conf
flake-registry = ~/dotfiles/flake-registry.json

So let’s try that again.

$ nix path-info 'nixpkgs#git'
warning: error: unable to download '~/dotfiles/flake-registry.json': Couldn't resolve host name (6); retrying in 320 ms
warning: error: unable to download '~/dotfiles/flake-registry.json': Couldn't resolve host name (6); retrying in 567 ms
warning: error: unable to download '~/dotfiles/flake-registry.json': Couldn't resolve host name (6); retrying in 1003 ms
warning: error: unable to download '~/dotfiles/flake-registry.json': Couldn't resolve host name (6); retrying in 2782 ms
error: unable to download '~/dotfiles/flake-registry.json': Couldn't resolve host name (6)
nix path-info 'nixpkgs#git'  0.08s user 0.03s system 2% cpu 4.808 total

Heavy sigh. I guess that ~ expansion is a feature of my shell, so it was foolish of me to expect Nix to parse that as a local file path. Is that really a valid, like, URI? I dunno.

Let’s try the absolute version:

$ grep flake-registry ~/.config/nix/nix.conf
flake-registry = /Users/ian/dotfiles/flake-registry.json

$ nix path-info 'nixpkgs#git'
/nix/store/bcjf7pr923hinmzcci9qh47g4zd3ds4q-git-2.34.0

Okay, that worked fine.

So. I can now… query properties of my Nix store again, without making HTTP requests to GitHub.

Ha. Without making these particular HTTP requests to GitHub. I’ve already squashed two different cases where Nix was connecting to The Internet in order to run basic commands that previously worked offline. Who knows how many more of those there are.

Anyway, nix path-info works; it’s slightly worse, because I have to type the quotes and the # character and all that, but whatever. It’s not a command I use very often.

Now, you might reasonably be wondering at this point: is this real? Did GitHub just happen to be down at the one moment you started writing this blog post? Or did you stage all of this just for rhetorical effect?

That’s a good question. The answer is this 100% happened; this is all real, but that it’s not because GitHub is down.

I had GitHub blackholed in my /etc/hosts because it’s on a long list of tech-related websites that I disable when I am trying not to think about computer things for a moment, just one moment, just one brief second of quietude, and I forgot to re-enable it when I started writing this blog post.

And I’m glad I did, because I learned… well, that Nix is constantly making HTTP requests to GitHub!

I once again wish that I had dtruss available, so I could see what else Nix is doing. I can’t imagine what it must feel like to run Nix without a fast internet connection right now.

Anyway, we were supposed to look at the new flake-enabled profiles, right?

Let’s do that.

And then maybe I can figure out how to install my sd overlay.

nix profile --help

So we have all of the following subcommands:

ยท nix profile diff-closures - show the closure difference between each version of
  profile
ยท nix profile history - show all versions of a profile
ยท nix profile install - install a package into a profile
ยท nix profile list - list installed packages
ยท nix profile remove - remove packages from a profile
ยท nix profile rollback - roll back to the previous version or a specified version
  of a profile
ยท nix profile upgrade - upgrade packages using their most recent flake
ยท nix profile wipe-history - delete non-current versions of a profile

I’m excited about upgrade, as that was a pretty big missing feature of nix-env.

I learn that it uses the same numbered-symlink approach that nix-env did, and that the default profile is still in ~/.nix-profile, and it still supports --profile to change that.

I learn that the manifest.nix has been replaced by manifest.json, and that its contents is actually documented now. Nice! That’s great. No more pawing around in the dark trying to figure out just how to look at that file.

And that’s all that the top-level docs have to say. Let’s dive in!

$ nix profile install 'nixpkgs#hello' --profile ~/scratch/test-profile

$ tree ~/scratch/test-profile
/Users/ian/scratch/test-profile
โ”œโ”€โ”€ bin -> /nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10/bin
โ”œโ”€โ”€ manifest.json
โ””โ”€โ”€ share -> /nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10/share

Neat. Okay. Same as before, basically, with a slightly nicer looking command. Except instead of nixpkgs.hello – a reasonable, intuitive syntax – it’s now 'nixpkgs#hello', because… because, really, I don’t even know, you know?

It’s not like it was pleasant to type nix-env -iA nixpkgs.hello before, and indeed I made an alias so I could just type sd nix install hello a long time ago. I expect I’ll do the same for all of the common flake commands I type, given time.

$ nix profile remove 'nixpkgs#hello' --profile ~/scratch/test-profile

$ nix profile upgrade 'nixpkgs#hello' --profile ~/scratch/test-profile

$ echo $?
0

Oh. Huh. I expected that to fail, because I expected that nix profile remove would… remove the package. But it didn’t:

$ tree ~/scratch/test-profile
/Users/ian/scratch/test-profile
โ”œโ”€โ”€ bin -> /nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10/bin
โ”œโ”€โ”€ manifest.json
โ””โ”€โ”€ share -> /nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10/share

Huh.

Did the remove secretly fail?

$ nix profile remove 'nixpkgs#help' --profile ~/scratch/test-profile

$ echo $?
0

Yep. Just a silent no-op. Cool.

Well, let’s see. nix profile remove --help lists all of the following possible invocations:

ยท Remove a package by position:

    | # nix profile remove 3

ยท Remove a package by attribute path:

    | # nix profile remove packages.x86_64-linux.hello

ยท Remove all packages:

    | # nix profile remove '.*'

ยท Remove a package by store path:

    | # nix profile remove /nix/store/rr3y0c6zyk7kjjl8y19s4lsrhn4aiq1z-hello-2.10

Well… none of those are what I want. What are package “positions”? I don’t know. Presumably I could find out with --list:

$ nix profile list --profile ~/scratch/test-profile
0 flake:nixpkgs#legacyPackages.x86_64-darwin.hello github:NixOS/nixpkgs/18e6b5184274305f2c9dc36141003473375a5df9#legacyPackages.x86_64-darwin.hello /nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10

Yeah, okay, I assume that’s what the 0 means. But… what? This seems like some weird implementation detail leaking out. I’m not supposed to actually know… what?

So… let me get this straight. If I’m reading this correctly: in order to remove a package that I installed as nixpkgs#hello, I’m supposed to type:

$ nix profile remove packages.x86_64-darwin.hello

Is that… is that real? Is that the actual CLI?

I guess those numbers are there as like… ergonomic aids? Because the designer of this feature realized that it was unreasonable to expect someone to type packages.x86_64-darwin.hello, so instead of supporting the obvious invocation…?

Remember when I was excited that nix profile would liberate me from the terrible UI of nix-env? How young I was, then.

Anyway let’s try it out anyway:

$ nix profile remove packages.x86_64-darwin.hello --profile ~/scratch/test-profile

Did it work this time? Or did it silently fail again?

$ tree ~/scratch/test-profile
/Users/ian/scratch/test-profile
โ”œโ”€โ”€ bin -> /nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10/bin
โ”œโ”€โ”€ manifest.json
โ””โ”€โ”€ share -> /nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10/share

Fantastic.

Ah, because by spooning my way through the punctuation soup of the nix profile list output, I guess that I’m actually supposed to type:

$ nix profile remove legacyPackages.x86_64-darwin.hello --profile ~/scratch/test-profile

$ tree ~/scratch/test-profile
/Users/ian/scratch/test-profile
โ””โ”€โ”€ manifest.json

Okay, well, that was horrible; let’s never do that again.

I basically never run nix-env --uninstall, because I use nix-shell -p pretty frequently, and only add something to my profile if I’m pretty confident that I’m going to keep it around. And even when I do uninstall something from my profile, I usually do that by typing sd nix sync.

So let’s try to come up with the equivalent flake-based declarative profile.

So just to refresh, I currently have a file that looks like this:

$ cat ~/dotfiles/user.nix
with import <nixpkgs> {}; [
  git
  nix
  zsh
  ...
]

And I run nix-env -irf ~/dotfiles/user.nix to set my profile to only the derivations listed in that file. I use this to install things and to uninstall things, and also to upgrade things, because previously Nix didn’t have a way to upgrade things.

So: how do I do that with profiles?

Well, gosh. From reading the --help pages, it doesn’t seem like there’s a way to both add and remove packages from a profile in a single operation.

I could do something where I like… uninstall everything, and then install a list of things. But that would mean that nix profile rollback wouldn’t revert my “sync” operation; it would revert the install, and then it would revert the uninstall everything.

So…

How am I supposed to write down the packages that I want in my profile?

Another very important part of my workflow is sd nix diff, which shows me output like this:

$ sd nix diff
- htop 3.1.0
+ racer 2.1.48
! nodejs 14.18.0 -> 14.18.1
! git 2.33.0 -> 2.33.1
! delta 0.8.3 -> 0.10.2

So I can see what effect running sd nix sync will have on my profile.

But… well, I guess I can sort of maybe figure out the “current version” by running nix profile list, and parsing the output? There are no options to control what that actually prints out. There’s no equivalent of nix-env -q --json, which is what I use to generate that diff.

But… I guess the manifest file is basically that?

$ jq . ~/scratch/test-profile/manifest.json
{
  "elements": [
    {
      "active": true,
      "attrPath": "legacyPackages.x86_64-darwin.hello",
      "originalUri": "flake:nixpkgs",
      "storePaths": [
        "/nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10"
      ],
      "uri": "github:NixOS/nixpkgs/18e6b5184274305f2c9dc36141003473375a5df9"
    }
  ],
  "version": 1
}

Okay no. I should have paid attention before. The old manifest.nix was a full working copy of the derivation. This is just… well, I can probably turn those into derivations?

$ nix flake show 'nixpkgs#hello'
error: unexpected fragment 'hello' in flake reference 'nixpkgs#hello'

I don’t actually know what to do with a flakeref once I have it. I don’t know how to evaluate one, or to see anything about the derivation that it evaluates to. Huh.

I look through nix --help and on a whim I tried nix eval.

And it just… hard broke?

$ nix eval
error (ignored): error: interrupted by the user
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C

I actually had to kill -9 it. Wow.

I thought that this was a bug because I didn’t type any arguments, so I tried it again with an argument:

$ nix eval 'nixpkgs#hello'
error: interrupted by the user
^C
$

It still just hung – I waited about a minute – but at least I could interrupt it this time.

So no idea… what that’s about.

nix eval --help gives this example:

ยท Print the store path of the Hello package:

    | # nix eval --raw nixpkgs#hello

I copy it, foolishly:

$ nix eval --raw nixpkgs#hello
zsh: no matches found: nixpkgs#hello

And I sigh deeply, and run:

$ nix eval --raw 'nixpkgs#hello'
/nix/store/6sv1isax4axkxfnmg4gxg1pzr4417r6v-hello-2.10\

Okay. So that works? But I don’t really want the path. I want the pname and version.

$ nix eval --raw 'nixpkgs#hello.pname'
hello\
$ nix eval --raw 'nixpkgs#hello.version'
2.10\

Okaaaay. Oh, I should maybe mention: those \ characters are added by my shell, to tell me that the command actually had no newline after it, so zsh inserted it for me. On bash, this would instead look like:

$ nix eval --raw 'nixpkgs#hello.pname'
hello$ nix eval --raw 'nixpkgs#hello.version'
2.10$

In my shell these special backslashes render with reversed colors, so it’s obvious they aren’t part of the output, but my blog is not smart enough to be able to reproduce that.

Anyway. I can evaluate one of these things, but how do I evaluate both of these things? I do not know. I tried a few different invocations, but none of them worked. So I gave up.

I want to do this so that I can diff the “current” version to the “new” version before I run upgrade. And here I’m just trying to see the “current” version. But how do I see the new version?

Let’s see. I look in nix profile upgrade --help, expecting to see a --dry-run flag or something. But there isn’t one. So I don’t know how to see the new version either.

So: my conclusion from all of this is that nix profile is not a viable replacement for nix-env. There’s no way to print a human-readable list of the currently installed packages, there’s no way to see what is going to be upgraded, there’s no way to… use nix profile.

So I won’t!

Which is fine. It is an experimental feature, after all. I can keep using nix-env.

The reason to use nix profile is, of course, that nix search now operates in flake town, and I don’t want to nix search through a slightly different set of things than I will be able to install.

So let’s instead try to figure out how to fix that, and to make nix search match nix-env again. To make a “flake” that matches the contents of my channel.

Hmm. That shouldn’t be too hard.

$ nix registry add nixpkgs ~/.nix-defexpr/channels/nixpkgs

And now my nixpkgs flake is tied to my channel, and I can update it with normal nix-channel --update commands. Right?

$ nix search nixpkgs hello
error: access to path '/nix/store/dd99fd7ik49hfc7ywiy85n2wbylhamjr-nixpkgs-22.05pre335173.56cbe42f166/nixpkgs/flake.nix' is forbidden in restricted mode

Hmmm. Very interesting.

$ nix --help | grep restricted
$ nix flake --help | grep restricted
$ nix registry --help | grep restricted

I have no idea what “restricted mode” is. I finally find a reference to it in man 5 nix.conf:

โ€ข allowed-uris

  A list of URI prefixes to which access is allowed in restricted
  evaluation mode. For example, when set to https://github.com/NixOS,
  builtin functions such as fetchGit are allowed to access
  https://github.com/NixOS/patchelf.git.

  Default: empty

But no explanation of what this… means.

So… I guess I can’t weasel my way out of flakes that easily.

Well, no problem! Since I’ve declared all of the packages I want installed, I can just:

$ sed -i '' s/nix/nix_2_3/ ~/dotfiles/user.nix

$ sd nix diff
! nix 2.4 -> 2.3.16

$ sd nix sync
...

$ nix --version
nix (Nix) 2.3.16

I missed you, old friend. You wouldn’t believe the crazy week I’ve had.


  • So how’s Guix these days?