I start reading through the Nix manual. I won’t quote it in its entirety like I did in the glossary. But I will any time I want to highlight something or I have a question or I don’t understand something.
Chapter 1. About Nix
This is mostly a feature tour – sort of explaining what Nix is and why you should use it. There’s very little actual information about how Nix works, but it does prime the mind with a few questions.
The first I have is about rollbacks:
$ nix-env --upgrade some-packages $ nix-env --rollback
What exactly does it mean to roll back a package?
I remember from the glossary that the user environment is a bunch of symlinks to executables. Presumably rolling back packages means changing the symlinks back. But how does it know what they were before? I suspect the “Nix database” keeps track of this, but I will have to wait to see if I am right.
I then notice that everything in the manual seems to use the “old” style of commands. One of the reasons I was excited to return to Nix was the prospect of a more user-friendly command-line interface – the nix
executable. So I’m surprised that the manual, at least in the introduction, doesn’t use it. All the examples are of the “old style” commands, like nix-env
and nix-shell
.
I suspect this is just documentation rot, but Nix 2.0 came out in the beginning of 2018, and I’m writing this in 2021, so if I got the math right that’s not super reassuring. Either the rest of the manual is going to be quite outdated as well, or the nix
interface is still not considered stable enough for real world use. I suppose I will find out in the rest of the manual.
Moving on, we get to “transparent source/binary deployment,” which basically says that prebuilt binaries will be fetched from https://cache.nixos.org/
. It doesn’t say why or how to configure that – I assume I can set up my own custom cache? That domain is not magical, I hope? This is of course fine for an introduction, but something that I will need to follow up on for my own understanding.
We see a brief introduction to nix-shell
, which includes our first truly confounding command, which is not explained at all:
$ nix-shell '<nixpkgs>' -A pan
(pan
, in case you aren’t following along with the manual at home, is an example package – a Gnome Usenet client, I guess.)
What is -A
? Elsewhere in the manual there they use long flags, but here we get an abbreviation, which is a pet peeve of mine to see in documentation, so I will expand it for you:
$ nix-shell '<nixpkgs>' --attr pan
And it’s no more clear. We want to start a shell with pan
installed, but we specify it as --attr
? Huh?
I think this is a great example where there is a disconnect between the way that I think about things (“I want a shell with this package in it”) and what Nix needs me to say (“I want a shell with this particular named symbol, or maybe record element or something, which I call an attribute, to be available, and because that attribute refers to a derivation or something that package will ultimately be installed.") I am, obviously, guessing here, helped only by a very shaky memory. Maybe that’s not what it means.
More confusing is the <nixpkgs>
line. What do those angle brackets mean?1 Why is that an anonymous argument, when --attr
was named?
I vaguely recall that this is some sort of import
or open
statement or something, which includes the definition of the symbol (attribute?) pan
. But I have no idea what the nixpkgs
string actually refers to: is that a filename somewhere? Is it the name of a channel? I don’t think I’ve forgotten this: I think I never really understood what that meant.
Chapter 2. Quick Start
This chapter begins with a great line:
This chapter is for impatient people who don’t like reading documentation.
Being the exact opposite of that person, I wondered if I should skip it, but decided that would be foolish. This is probably the most commonly read section in the manual. If anything should be improved to make Nix adoption easier, it’s this.
Installation
First we see the install instructions:
$ bash <(curl -L https://nixos.org/nix/install)
I think I speak for all of us when I say: really? Yeah, this is what Homebrew does, but isn’t Nix like… hardcore? Shouldn’t we be looking down our noses at people who suggest we run code that we found on the internet? It’s not like we’re about to give this software total control over our computer to do whatever it okay fine.
I am pleased to see that the main install page at least gives instructions for how to verify that the installation script has not been tampered with:
$ curl -o install-nix-2.3.10 https://releases.nixos.org/nix/nix-2.3.10/install $ curl -o install-nix-2.3.10.asc https://releases.nixos.org/nix/nix-2.3.10/install.asc $ gpg2 --recv-keys B541D55301270E0BCF15CA5D8170B4726D7198DE $ gpg2 --verify ./install-nix-2.3.10.asc $ sh ./install-nix-2.3.10
Probably. I mean, who knows? Has anyone ever actually done that? Have you really? Do you even have gpg2
installed? Don’t you lie to me.
So it’s probably fine. You can see that this script just downloads another script and executes that, and like, I’m not gonna read that script too. I’ve got docs to read.
So let’s try it out:
$ bash <(curl -L https://nixos.org/nix/install)
(some curl progress)
Note: a multi-user installation is possible. See https://nixos.org/nix/manual/#sect-multi-user-installation
Installing on macOS >=10.15 requires relocating the store to an apfs volume.
Use sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume or run the preparation steps manually.
See https://nixos.org/nix/manual/#sect-macos-installation
Okay. That did not work, and redirected me to a couple of different manual sections. I don’t particularly care about a multi-user installation on my laptop – I am the only user – so I skipped that and went on to read about the macOS problem.
And I learn that apparently I cannot just make /nix
anymore.
$ mkdir /nix
mkdir: /nix: Read-only file system
They ain’t lyin'.
So that’s not great. The manual provides a few options to get around this:
- Change the Nix store path prefix
Which means forgoing binary caches, which makes this an absolute nonstarter for me. Ain’t nobody got time for that.
- Use a separate encrypted volume
This sounds fine, but annoying, as certain things won’t be available immediately after booting, which could cause weird failures if I have software configured to run immediately. But I hardly ever reboot, so this doesn’t sound that bad.
- Symlink the Nix store to a custom location
The manual basically just says “this don’t work.”
- Use a separate unencrypted volume
This is given as the recommended command above, but the manual explains what it is and how to do it.
I have to say, I’m incredibly impressed by this manual section. This is great. It makes me feel good; it puts me at ease; it gives me reassurance that the people behind Nix spent time thinking about this problem and the best ways to solve it.
And having read their recommendation, I concur with their conclusion, and will run the recommended command:
$ sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume
Uninstallation is now more complicated than rm -r /nix
, but the script tells me what I need to do. Because I will definitely forget and am not sure if these steps will be documented anywhere else, I write them down in my blog:
Creating volume and mountpoint /nix.
------------------------------------------------------------------
| This installer will create a volume for the nix store and |
| configure it to mount at /nix. Follow these steps to uninstall. |
------------------------------------------------------------------
1. Remove the entry from fstab using 'sudo vifs'
2. Destroy the data volume using 'diskutil apfs deleteVolume'
3. Remove the 'nix' line from /etc/synthetic.conf or the file
Unfortunately, the script fails shortly after that.
error: refusing to create Nix store volume because the boot volume is
FileVault encrypted, but encryption-at-rest is not available.
Manually create a volume for the store and re-run this script.
See https://nixos.org/nix/manual/#sect-macos-installation
I don’t know what that means. What is encryption-at-rest? I do not know. The manual mentioned something about “T2 chips,” but I don’t know what that means either. It sure sounds futuristic, though, and my laptop is still powered by steam, so I’d be very surprised if I have one of those.
I try to create the volume manually, following the instruction in the manual, but that too gives me an error:
$ sudo diskutil apfs addVolume diskX APFS 'Nix Store' -mountpoint /nix
Could not find APFS Container Reference diskX
Boo. What looked like a command to run was in fact the template for a command to run, which I did not know because I don’t know the first thing about the diskutil
CLI.
diskutil list
tells me I have disk0
and disk1
. Which one is the elusive diskX
? I don’t know.
$ diskutil list
/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *121.3 GB disk0
1: EFI EFI 209.7 MB disk0s1
2: Apple_APFS Container disk1 121.1 GB disk0s2
/dev/disk1 (synthesized):
#: TYPE NAME SIZE IDENTIFIER
0: APFS Container Scheme - +121.1 GB disk1
Physical Store disk0s2
1: APFS Volume Macintosh HD - Data 68.8 GB disk1s1
2: APFS Volume Preboot 365.1 MB disk1s2
3: APFS Volume Recovery 613.7 MB disk1s3
4: APFS Volume VM 3.2 GB disk1s4
5: APFS Volume Macintosh HD 19.5 GB disk1s5
6: APFS Snapshot com.apple.os.update-... 19.5 GB disk1s5s1
man diskutil
tells me that the first argument to addVolume
should be the containerReferenceDevice
. I make a weak pattern-matching guess that that should be disk1
, and hope that I’m not about to brick my computer:
$ sudo diskutil apfs addVolume disk1 APFS 'Nix Store' -mountpoint /nix
Password: hunter2
Will export new APFS Volume "Nix Store" from APFS Container Reference disk1
Started APFS operation on disk1
Preparing to add APFS Volume to APFS Container disk1
Creating APFS Volume
Created new APFS Volume disk1s7
Mounting disk
Setting volume permissions
Disk from APFS operation: disk1s7
Finished APFS operation on disk1
That looks… good? I guess? diskutil list
now includes my Nix Store
volume. My computer did not start emitting smoke or noxious fumes. Let’s re-run the install command:
$ sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume
(buncha curl stuff)
Using existing 'Nix Store' volume
Configuring /etc/fstab...
123
164
performing a single-user installation of Nix...
copying Nix to /nix/store.............................................
installing 'nix-2.3.10'
building '/nix/store/h37hlzjs8grkr2d6zzl8yv9j459k0zy2-user-environment.drv'...
created 7 symlinks in user environment
installing 'nss-cacert-3.49.2'
building '/nix/store/z1mna9c241jqw3raldq0cpqi6z9f9zr9-user-environment.drv'...
created 9 symlinks in user environment
unpacking channels...
created 1 symlinks in user environment
modifying /Users/ian/.zshenv...
Installation finished! To ensure that the necessary environment
variables are set, either log in again, or type
. /Users/ian/.nix-profile/etc/profile.d/nix.sh
in your shell.
And it worked! 123 164!
I am now the proud owner of nix-2.3.10
.
I don’t love that it modified .zshenv
for me, though my inner pedant is reluctantly pleased that it didn’t modify .zshrc
instead. The only modification is to source that script it mentions, so let’s go through that and see if we understand what it’s doing.
if [ -n "$HOME" ] && [ -n "$USER" ]; then
# Set up the per-user profile.
# This part should be kept in sync with nixpkgs:nixos/modules/programs/shell.nix
NIX_LINK=$HOME/.nix-profile
# Append ~/.nix-defexpr/channels to $NIX_PATH so that <nixpkgs>
# paths work when the user has fetched the Nixpkgs channel.
export NIX_PATH=${NIX_PATH:+$NIX_PATH:}$HOME/.nix-defexpr/channels
# Set up environment.
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
export NIX_PROFILES="/nix/var/nix/profiles/default $HOME/.nix-profile"
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch
export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
elif [ -e /etc/ssl/ca-bundle.pem ]; then # openSUSE Tumbleweed
export NIX_SSL_CERT_FILE=/etc/ssl/ca-bundle.pem
elif [ -e /etc/ssl/certs/ca-bundle.crt ]; then # Old NixOS
export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt
elif [ -e /etc/pki/tls/certs/ca-bundle.crt ]; then # Fedora, CentOS
export NIX_SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
elif [ -e "$NIX_LINK/etc/ssl/certs/ca-bundle.crt" ]; then # fall back to cacert in Nix profile
export NIX_SSL_CERT_FILE="$NIX_LINK/etc/ssl/certs/ca-bundle.crt"
elif [ -e "$NIX_LINK/etc/ca-bundle.crt" ]; then # old cacert in Nix profile
export NIX_SSL_CERT_FILE="$NIX_LINK/etc/ca-bundle.crt"
fi
if [ -n "${MANPATH-}" ]; then
export MANPATH="$NIX_LINK/share/man:$MANPATH"
fi
export PATH="$NIX_LINK/bin:$PATH"
unset NIX_LINK
fi
Well, that’s a lot of things. Let’s try to go through the important bits.
# Append ~/.nix-defexpr/channels to $NIX_PATH so that <nixpkgs>
# paths work when the user has fetched the Nixpkgs channel.
export NIX_PATH=${NIX_PATH:+$NIX_PATH:}$HOME/.nix-defexpr/channels
No idea what this means. No idea what NIX_PATH
is or why it’s being appended to when, as far as I know, it does not currently exist. Why not set it to that? I guess to not be destructive if the user configures something in their own .zshenv
. But what would I configure it to? What is NIX_PATH
?
Beyond that, what is $HOME/.nix-defexpr
? It just contains a couple symlinks:
$ tree ~/.nix-defexpr
/Users/ian/.nix-defexpr
├── channels -> /nix/var/nix/profiles/per-user/ian/channels
└── channels_root -> /nix/var/nix/profiles/per-user/root/channels
No idea what the purpose of those are. Apparently this has something to do with the mysterious <nixpkgs>
syntax we saw in the nix-shell
example, but I can’t begin to guess what. I wonder what defexpr
means: “def” means “definition” to me, but together I’m leaning towards “default expression.” If so, I wish that this were called ~/.nix-default-expression
. I wonder what that means, so I will add it as an open question.
Next:
# Set up environment.
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
export NIX_PROFILES="/nix/var/nix/profiles/default $HOME/.nix-profile"
Okay. Neat. I don’t know what it means about keeping this in sync with something, nor do I really recognize nixpkgs:nixos/modules/programs/environment.nix
as, like, a path to something. Is nixpkgs:
a namespace here? Is this a file? It reads like a protocol to me. But it’s gotta be a file, right? I don’t know.
Next there’s a long section around configuring SSL certificates.
I have a strong recollection of SSL certs not working when I used Nix before, which made every nix-env
invocation fail, I think – or maybe just nix-channel
commands? – I don’t remember. I had to do some nonsense to set some environment variable manually (presumably NIX_SSL_CERT_FILE
, but I don’t actually remember). So maybe these lines are new since I last tried Nix? Or they weren’t working on OS X? I don’t know. We shall see if they work on macOS now.
Then the script sets up PATH
and MANPATH
. No objection to any of this. Let’s source it.
$ source ~/.nix-profile/etc/profile.d/nix.sh
Isn’t that nicer to read than the .
syntax? I think so. There’s probably some ancient reason to prefer .
over source
if you’re running Plan 9 on a Commodore 64 or whatever people used to do before Steve Jobs came along and saved us all from having to know anything about computers.
Anyway, everything is working great now. Nix is installed!
First steps
The quick start goes on to describe some simple commands to try. Let’s start with the first one, to “see what installable packages are currently available in the channel”:
$ nix-env -qa
This command appears to just hang. No output at all. Is it working? Does it do anything? Ah, after thirty seconds or so it gives me a big list of words in a pager. How many words? Countless thousands. Actually 32,544. That’s too many!
As my first ever real live Nix command, I gotta say… this is not a very good showcase. I do not care to see the 32,544 packages that make up the Nix ecosystem. I care to see, like, a couple dozen of them, tops.
But still, sure, okay. The manual authors wanted to highlight this command; I should at least understand it.
$ nix-env -qa
The -q
flag is for “query” and not “quiet” as I would assume from every other CLI I have ever used. Similarly a
does not stand for --all
but --available
. The man page explains:
OPERATION --QUERY
Synopsis
nix-env {--query | -q} [--installed | --available | -a]
[{--status | -s}] [{--attr-path | -P}] [--no-name] [{--compare-versions | -c}] [--system] [--drv-path] [--out-path]
[--description] [--meta]
[--xml] [--json] [{--prebuilt-only | -b}] [{--attr | -A} attribute-path]
names...
Description
The query operation displays information about either the store paths that are installed in the current generation of the active
profile (--installed), or the derivations that are available for installation in the active Nix expression (--available). It only
prints information about derivations whose symbolic name matches one of names.
The derivations are sorted by their name attributes.
Source selection
The following flags specify the set of things on which the query operates.
--installed
The query operates on the store paths that are installed in the current generation of the active profile. This is the default.
--available, -a
The query operates on the derivations that are available in the active Nix expression.
So my query was for “derivations that are available in the active Nix expression” – whatever that means – and I didn’t have an actual query, so I got back everything. Okay. Let’s try it with a string:
$ nix-env -qa git
git-2.30.0
git-2.30.0
git-2.30.0
After thirty more seconds, I get the same package, three times. All the same version. Huh. Okay. That’s weird.
In my head and from my memory, this command is analogous to brew search
. I can run brew search git
and get back not just git, but lots of packages with the string “git” in the name, like git-annex
and something called gitmoji
. Are these packages not available in Nix?
$ nix-env -qa git-annex
git-annex-8.20210127
Huh? Okay. So I guess… it’s not really searching? The string I’m giving it is something else?
Well. Weird. But we all know that Nix traditionally has a pretty confusing – if not outright hostile – user interface. But that’s one of the big reasons we’re returning to it now, with Nix 2! We have new commands! So let’s try one now.
$ nix search git
warning: using cached results; pass '-u' to update the cache
error: no results for the given search term(s)!
Um. Okay. I suppose that since I’ve never run this before, my cache is empty? And it searched it anyway, instead of auto-updating it? Sure.
$ nix search git -u
* nixpkgs.act (act-0.2.20)
Run your GitHub Actions locally
* nixpkgs.argocd (argocd-1.8.4)
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes
* nixpkgs.axoloti (axoloti-1.0.12-2)
Sketching embedded digital audio algorithms. To fix permissions of the Axoloti USB device node, add a similar udev rule to <literal>services.udev.extraRule
s</literal>: <literal>SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="0442", OWNER="someuser", GROUP="somegroup"</literal>
(much more output elided)
It took the same amount of time, but the output I got was leaps and bounds better than with nix-env -qa
. Not only did it search package names, but also searched and displayed package descriptions – although with some apparent escaping issues.
It’s also very nicely colorized in ways that don’t show up here – my search term is highlighted wherever it appears, the scary computer version of the package (axoloti-1.0.12-2)
is nicely grayed out so that it doesn’t distract my eye. It looks great. And I love that they’re all namespaced with nixpkgs
– that makes me feel good; that makes things feel less magical. It makes me feel heard.
Scrolling through the output I see all kinds of git-related packages, including git-annex
– although, tragically, there is no gitmoji
.
So, okay. It might be good if nix search
was the first command you run, instead of nix-env -qa
. It’s familiar. It does the thing you expect. It leaves a good first impression – apart from having to re-run it with -u
. But it told me that! It didn’t just silently give me nothing! The fact that I was able to use it successfully without googling anything means that the UI is working, even if my experience could have been a little smoother.
But doing this highlighted a weird and unexpected thing. I can run man nix-env
and learn that the expanded version of that command is nix-env --query --available
, which is easier to read. But there is no man nix
. Weird. I want to learn more about nix search -u
, so I run:
$ nix help search
error: 'help' is not a recognised command
Try 'nix --help' for more information.
Because that’s how Homebrew works. And I am very pleased that it told me the correct command. So I run:
$ nix search --help
And see that -u
stands for --update-cache
. Perfect. Love it. Super explicit. I was worried it would just stand for --update
, and I would have something to grumble about.
All-in, nix search
: five stars. nix-env -qa
: three wats.
Let’s keep going.
The next command is listed as “install some packages from the channel.” I still don’t know what a “channel” is, but okay.
$ nix-env -i hello
Is -i
short for --interactive
? --ignore
? No: it’s --install
, another “subcommand” of nix-env
. I’ve gotta say, I really like the convention of subcommands not being flags. I’m glad we landed there. The world is a much better place now than it once was.
Anyway, let’s run it.
It… is slow. Probably another 30 seconds before it starts printing anything. I’m starting to sense a pattern.
$ nix-env -i hello
installing 'hello-2.10'
these paths will be fetched (14.84 MiB download, 74.75 MiB unpacked):
/nix/store/2s8x59z3cly97fyc3hvlngl637snpwcq-swift-corefoundation
/nix/store/541lzmhppr82600vz7ap54n22dz7gydf-ICU-66108
/nix/store/5y6lcfjghk5kbv4782vi7w79vz60gsyn-bash-4.4-p23
/nix/store/aqf78c2x1kwv9i123p55m3f4iyx9fz3c-openssl-1.1.1i
/nix/store/c1ln5y1p009wn44j03z1r7rdhmjx3k2j-curl-7.74.0
/nix/store/mw590ax8x2n9161hwvnl9j5156di6d47-libc++abi-7.1.0
/nix/store/pakmb65sf3g2hkbm1fdgk2fh6hiij720-hello-2.10
/nix/store/pqajcmw6jmq2i8ka001z53r1a09w4y67-libssh2-1.9.0
/nix/store/q1rhw9f30fm0yzx1hic1hgj31sfc6k4p-libkrb5-1.18
/nix/store/s5rd3hgdirrdwdfbn7m4hd0i3d2zqpz5-libxml2-2.9.10
/nix/store/vrmgqjl51gwf47i5i1rbs7dnkb1g1pvf-libc++-7.1.0
/nix/store/wphpzw2swy36pm4ph3r1zfvwfj2njxjf-zlib-1.2.11
/nix/store/yi9klhxd1243l7inrkn12f6lwzp9bki4-Libsystem-1238.60.2
/nix/store/zxya6i6ncqs8q6fq3mcl0igflmy2219n-nghttp2-1.41.0-lib
copying path '/nix/store/yi9klhxd1243l7inrkn12f6lwzp9bki4-Libsystem-1238.60.2' from 'https://cache.nixos.org'...
copying path '/nix/store/5y6lcfjghk5kbv4782vi7w79vz60gsyn-bash-4.4-p23' from 'https://cache.nixos.org'...
copying path '/nix/store/mw590ax8x2n9161hwvnl9j5156di6d47-libc++abi-7.1.0' from 'https://cache.nixos.org'...
copying path '/nix/store/q1rhw9f30fm0yzx1hic1hgj31sfc6k4p-libkrb5-1.18' from 'https://cache.nixos.org'...
copying path '/nix/store/vrmgqjl51gwf47i5i1rbs7dnkb1g1pvf-libc++-7.1.0' from 'https://cache.nixos.org'...
copying path '/nix/store/zxya6i6ncqs8q6fq3mcl0igflmy2219n-nghttp2-1.41.0-lib' from 'https://cache.nixos.org'...
copying path '/nix/store/541lzmhppr82600vz7ap54n22dz7gydf-ICU-66108' from 'https://cache.nixos.org'...
copying path '/nix/store/aqf78c2x1kwv9i123p55m3f4iyx9fz3c-openssl-1.1.1i' from 'https://cache.nixos.org'...
copying path '/nix/store/wphpzw2swy36pm4ph3r1zfvwfj2njxjf-zlib-1.2.11' from 'https://cache.nixos.org'...
copying path '/nix/store/pqajcmw6jmq2i8ka001z53r1a09w4y67-libssh2-1.9.0' from 'https://cache.nixos.org'...
copying path '/nix/store/s5rd3hgdirrdwdfbn7m4hd0i3d2zqpz5-libxml2-2.9.10' from 'https://cache.nixos.org'...
copying path '/nix/store/c1ln5y1p009wn44j03z1r7rdhmjx3k2j-curl-7.74.0' from 'https://cache.nixos.org'...
copying path '/nix/store/2s8x59z3cly97fyc3hvlngl637snpwcq-swift-corefoundation' from 'https://cache.nixos.org'...
copying path '/nix/store/pakmb65sf3g2hkbm1fdgk2fh6hiij720-hello-2.10' from 'https://cache.nixos.org'...
building '/nix/store/dgm6dvfqzvk0jf4r0qn3sbrfp8nn00w3-user-environment.drv'...
created 40 symlinks in user environment
Neat! 40 symlinks! For one command! Why!
So I look in ~/.nix-profile/bin/
– the only Nix-flavored directory on my PATH
– and find just one new symlink: hello
. So that’s good. Not sure where the other 39 symlinks got off to. Kind of a weird upsetting message, to be honest. But at least I now have hello
.
$ hello
Hello, world!
Excellent.
It then tells me how to uninstall a package, with – you know what? I’m not going to tell you.
I want you to guess. nix-env -u
, for uninstall? nix-env -r
, for remove? nix-env -d
, for delete?
No. None of this. It’s nix-env -e
. The -e
is short for, according to the manual, --uninstall
. I am not making this up.
I sort of refuse to use nix-env -e
on principal, and instead run it like this:
$ nix-env --uninstall hello
uninstalling 'hello-2.10'
building '/nix/store/29hrprfz6bn7qnn39a5xjw3mm563xbxs-user-environment.drv'...
created 9 symlinks in user environment
What? You already created 40 symlinks to install it, you need 9 more just to uninstall it? This seems… it seems like this message is telling me something very different from what I assume it is telling me.
But it worked!
$ hello
zsh: command not found: hello
Rest in peace, friend.
Fortunately, the next example is to “test out” a command by opening a shell that has the command available, without actually installing it (er, creating symlinks to it, I guess).
That looks like this (quite different from the pan
example we saw before):
$ nix-shell -p hello
And this installed way more than when I just ran nix-env -i
. Like, way more. I won’t even include the output of the command because there was so much of it. For scale, this told me (138.31 MiB download, 695.33 MiB unpacked)
vs (14.84 MiB download, 74.75 MiB unpacked)
for nix-env -i
. And I assume that hello
itself was still around – that uninstalling it didn’t actually delete the files, because I remember that much from years ago.
So that’s all… overhead? Shell overhead? It’s not just bash
? I do not know. The shell does seem to work, though:
[nix-shell:~]$ hello
Hello, world!
Excellent.
Subsequent invocations of nix-shell -p hello
don’t download anything, but the command still has to think for a solid 2 or 3 seconds before I get into my shell. Which is annoying.
So, okay. These seem like pretty important, common commands. Let’s see how they behave with the nix
command line.
$ nix install hello
error: 'install' is not a recognised command
Try 'nix --help' for more information.
Huh.
I look through nix --help
, but nothing sounds like install
to me. There’s build
? add-to-store
? These don’t really sound like install
. So I guess… nix-env
is still the only way to do this? Look for yourself:
$ nix --help
Usage: nix <COMMAND> <FLAGS>... <ARGS>...
Common flags:
--debug enable debug output
--help show usage information
--help-config show configuration options
--no-net disable substituters and consider all previously downloaded files up-to-date
--option <NAME> <VALUE> set a Nix configuration option (overriding nix.conf)
-L, --print-build-logs print full build logs on stderr
--quiet decrease verbosity level
-v, --verbose increase verbosity level
--version show version information
In addition, most configuration settings can be overriden using '--<name> <value>'.
Boolean settings can be overriden using '--<name>' or '--no-<name>'. See 'nix
--help-config' for a list of configuration settings.
Available commands:
add-to-store add a path to the Nix store
build build a derivation or fetch a store path
cat-nar print the contents of a file inside a NAR file
cat-store print the contents of a store file on stdout
copy copy paths between Nix stores
copy-sigs copy path signatures from substituters (like binary caches)
doctor check your system for potential problems
dump-path dump a store path to stdout (in NAR format)
edit open the Nix expression of a Nix package in $EDITOR
eval evaluate a Nix expression
hash-file print cryptographic hash of a regular file
hash-path print cryptographic hash of the NAR serialisation of a path
log show the build log of the specified packages or paths, if available
ls-nar show information about the contents of a NAR file
ls-store show information about a store path
optimise-store replace identical files in the store by hard links
path-info query information about store paths
ping-store test whether a store can be opened
repl start an interactive environment for evaluating Nix expressions
run run a shell in which the specified packages are available
search query available packages
show-config show the Nix configuration
show-derivation show the contents of a store derivation
sign-paths sign the specified paths
to-base16 convert a hash to base-16 representation
to-base32 convert a hash to base-32 representation
to-base64 convert a hash to base-64 representation
to-sri convert a hash to SRI representation
upgrade-nix upgrade Nix to the latest stable version
verify verify the integrity of store paths
why-depends show why a package has another package in its closure
Note: this program is EXPERIMENTAL and subject to change.
Boo. I’m actually pretty surprised by this. Nix 2 brings this new fancy interface, but it doesn’t bother to include a command to install packages? In a package manager? This is… this does not bode well. I’m not sure how much Nix I can Nix if I have to type nix-env -e
ever again. I realize there are no laws around user ergonomics for command line tools, but, like, doesn’t that feel a little bit like a violation of some kind of inalienable right?
I do see that there’s a nix run
command. Let’s try that, as the description implies it’s like nix-shell
:
$ nix run hello
error: attribute 'hello' in selection path 'hello' not found
I don’t know what that means but it’s vaguely frightening. Ah, but the --help
text has an example that gets me there:
$ nix run nixpkgs.hello
The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
bash-3.2$ hello
Hello, world!
Wha? This is a very different shell than nix-shell
. And for some reason tells me something about zsh
. zsh
has been my default interactive shell for… ever. The link to an Apple support article makes me think this invoked the system bash
? But that doesn’t sound like something nix
would do. That’d be crazy. I guess all macOS builds link to an Apple support article? Weird. It prints it every time I use nix run
, too! It’s not just a first-run message. So… gross. This command seems worse than nix-shell
.
Let’s move on. Next we learn how “to keep up-to-date with the channel.” Still no idea what a channel actually is.
$ nix-channel --update nixpkgs $ nix-env -u '*'
The manual clarifies:
The latter command will upgrade each installed package for which there is a “newer” version (as determined by comparing the version numbers).
Okay. So I guess nix-env
supports globs. Maybe that would make my nix-env -qa git
search work.
$ nix-env -qa '*git*'
error: An empty regex is not allowed in the POSIX grammar.
Amazingly, this still took 30 seconds to print that error. Which is a syntax error! What is that command doing?
I dunno. But let’s try updating my channel:
$ nix-channel --update nixpkgs
unpacking channels...
Pretty quick! No idea if it… did anything. I only installed Nix like an hour ago, so I assume there’s nothing to update yet.
This is analogous to brew update
, if I recall correctly: it refreshes the local cache of what packages are available. Meanwhile nix-env -u
(or nix-env --upgrade
) is analogous to brew upgrade
.
For fun, I run:
$ nix-env --install hello
installing 'hello-2.10'
$ nix-env --upgrade '*'
upgrading 'nss-cacert-3.49.2' to 'nss-cacert-3.60'
these paths will be fetched (0.22 MiB download, 0.41 MiB unpacked):
/nix/store/b7ykp2bibwrhgf67ql9dq1yfyy8a8h3a-nss-cacert-3.60-unbundled
/nix/store/ki7gssifc0xracrah8ygm63xj23wkjdz-nss-cacert-3.60
copying path '/nix/store/ki7gssifc0xracrah8ygm63xj23wkjdz-nss-cacert-3.60' from 'https://cache.nixos.org'...
copying path '/nix/store/b7ykp2bibwrhgf67ql9dq1yfyy8a8h3a-nss-cacert-3.60-unbundled' from 'https://cache.nixos.org'...
building '/nix/store/gmh97cqlq90cn7j7jsacsaaram1g7pxf-user-environment.drv'...
created 40 symlinks in user environment
Each of those commands still takes 30 seconds every time.
Impressively, I did have something to upgrade, even in so short a time. So I got to see that the command did, in fact, work. I now suspect that the “created 40 symlinks in user environment” is like… the total number? Not 40 new symlinks, but it’s like building a whole new user environment every time?
Let’s find out: the next example command is a rollback.
$ nix-env --rollback
switching from generation 6 to 5
That happened instantly. How? What did it actually do? How did it know what to restore? Is anything actually different? Let’s find out.
$ nix-env --upgrade --dry-run '*'
(dry run; not doing anything)
upgrading 'nss-cacert-3.49.2' to 'nss-cacert-3.60'
30 seconds later… alright! Okay. So that worked.
I wonder how a numbered generation works if I do something now – would that put me on generation 7? Or overwrite generation 6? Can I undo a rollback?
Let’s find out.
$ nix-env --uninstall hello
uninstalling 'hello-2.10'
$ nix-env --list-generations
1 2021-02-20 11:00:07
2 2021-02-20 11:00:07
3 2021-02-20 12:44:56
4 2021-02-20 12:51:54
5 2021-02-20 13:32:32
6 2021-02-20 13:33:58
7 2021-02-20 13:39:57 (current)
$ nix-env --switch-generation 6
switching from generation 7 to 6
$ nix-env --upgrade --dry-run '*'
(dry run; not doing anything)
$ hello
Hello, world!
Cool! Okay. Very cool. I got those commands from man nix-env
under the --rollback
command – they are not listed in the quick start guide.
Lastly, we come to:
$ nix-collect-garbage -d
-d
is short for --delete-old
, which deletes packages that are no longer needed. Is the default garbage collection to… keep packages that are no longer needed? I expect it will delete the old nss-cacert-3.49.2
when I run it. Let’s find out.
Holy gosh. I’m not sure what all it printed because it blew out my scrollback printing so much. It ended with:
1788 store paths deleted, 700.10 MiB freed
Wow. That’s quite a lot of garbage. Considering, you know, I just got here. I just installed hello
. I don’t know what all that mess is. I certainly didn’t put it there.
I suspect that it was all the stuff that was installed when I ran nix-shell -p hello
, so I run that again. And yep, sure enough, now it has to re-download all… whatever it was that it downloaded. So that’s… annoying. That doesn’t really seem like garbage to me, if I have to re-download it every time I want to use nix-shell
.
And just for good measure: no, there is no nice nix gc
version of this command.
Early impressions
That’s the end of the quick start guide. I learned a lot. I started. Not particularly quickly.
My main takeaway from this experience? nix-env
invocations are too slow for anyone to reasonably use Nix as a package manager. Full stop. Every. Single. Command. 30 seconds. It was unbearable. I mean, it was nice for writing this blog post – plenty of time to get my thoughts down – but if I were an actual, normal user? Trying to use Nix? I would just stop. My Nix adventure would end before I finished running the first command. Because I would assume it was broken, ctrl-C
outta there, and go back to using Homebrew (or pacman, or apt, or whatever).
I don’t remember this being such a huge issue before. I do remember using nox
to actually manage packages – it offered a fuzzy search a little bit like nix search
, and it provided a better interface for installing stuff. But I don’t remember it taking 30 seconds to do anything. Did I just block this out? Did I use nox
for so long that I just never had to experience the joy of running bare nix-env
commands? That’s crazy. This is Nix 2 land, the land of making things better. Surely we aren’t still supposed to use nox
? Right? That would be crazy, right?
To test if this is some weird problem with my multi-volume setup in macOS, I got on my NixOS box and ran time nix-env -i hello
. If it finishes before I publish this blog post, I’ll let you know.
Oh, there it goes:
42.03s user 22.34s system 26% cpu 3:58.80 total
Four minutes. Super.
Look, this is truly insane, and at this point I was actually mystified at how Nix is even a thing. But I’m writing here to say: it gets better. There’s a better way. We just have to get slightly outside of the quick start guide to see it.
Chapter 8. Upgrading Nix
Yes, we went from chapter 3 to chapter 8. It’s not actually a big jump: chapters 4-6 are about installation; I already read the salient bits. Chapter 7 is about environment variables, but doesn’t really explain anything about them, just tells you what to set to get things working – things that the install script already did.
Chapter 8 is about upgrading Nix, and it is two sentences long. One sentence is about single-user installs, and the other is about multi-user installs.
I did a single-user install, so to me upgrading looks like this:
nix-env -iA nixpkgs.nix
And that blew the case wide open.
It’s a little weird: it uses a qualified path, which the quick start guide didn’t. It’s weird that it’s an install, not an upgrade – I would have expected just nix-env -u nix
. If --install
performs an upgrade, why is there a separate --upgrade
? Could nix-env
drop that, and free up -u
for --uninstall
? I’m still not over nix-env -e
.
Anyway, I read man nix-env
to see what the -A
means.
If
--attr
(-A
) is specified, the arguments are attribute paths that select attributes from the top-level Nix expression. This is faster than using derivation names and unambiguous.
Faster? Faster, you say?
$ nix-env -iA nixpkgs.nix
replacing old 'nix-2.3.10'
installing 'nix-2.3.10'
Wow! Basically instant, in fact. No 30 second sleep. It just did stuff.
But this made me wonder: are there “fast” ways to install regular packages? Or is this some special thing about upgrading nix
?
For science, I timed what I’ve been dealing with this whole time:
$ time nix-env --install hello
installing 'hello-2.10'
nix-env --install hello 14.08s user 5.51s system 60% cpu 32.211 total
My 30 second guesstimates have actually been pretty close. Nice.
And what about…
$ time nix-env -iA nixpkgs.hello
installing 'hello-2.10'
nix-env -iA nixpkgs.hello 0.42s user 0.10s system 69% cpu 0.759 total
What? Are you kidding me? Why – why is the other thing even a thing? Why are there two ways to install packages, when one of them is short but terrible and one of them is slightly more to type but usable? Why isn’t there a short and usable command? And isn’t that what the nix
tool is supposed to be? Where is it in all of this? Is it complicit??
I’m sure there is some complicated reason for all of this – that without saying nixpkgs.hello
it needs to like evaluate every single package in the entire package store – all 32,544 of them – and then do a linear scan for one called hello
, or something ridiculous like that. Because how could it possibly guess that by hello
I might mean nixpkgs.hello
. But by using -A
/--attr
, it can just go to the right one? (???)
This is crazy. This is exactly the sort of thing that seems crazy to me, as a new user, but that I’m sure has some perfectly reasonable explanation to the Nix maintainers or power users who actually understand how nix-env
works. “Of course you need to specify -A
,” I can hear them saying. “Why would you ever use nix-env -i
without -A
?”
I am not being generous; I have no idea what the Nix community is actually like. I don’t think that I’ve ever been on the Nix IRC or Discord or mailing list or whatever; I’m sure they’re perfectly nice and would very patiently explain to me why things are the way that they are without judging me or making me feel inadequate for my lack of experience.
But.
I think I can say, just, like, as an objective truth: the quick start guide should not use the 30 second versions of these commands. It should use the instant versions. That’s just a bug in the documentation.
Maybe there wasn’t much of a difference between the two versions, back when the manual was written. Maybe the 30 second commands were 1 second commands, and the relative simplicity of nix-env -i hello
versus nix-env -iA nixpkgs.hello
outweighed that small delay. Maybe it got slower over time, and no one thought to update the guide.
I can definitely sympathize. Documentation is hard; that’s why I’m publishing this.
Anyway, I’m glad I kept reading before I gave up completely.
New open questions:
- How do I configure the location of the Nix binary cache (or other “substituters,” if I recall the terminology correctly)?
- What/why is
nix-shell '<nixpkgs>' --attr pan
? - What is
NIX_PATH
? - What does the weird comment mean about “appending channels” to
NIX_PATH
? - What is
~/.nix-defexpr
? - What is
~/.nix-profile
? - What is
~/.nix-channels
? - What is
NIX_PROFILES
? - Why does
nix-env -qa git
print the same line three times? - Why doesn’t
nix-env -qa
behave like a search? - How do I search for packages by name using
nix-env
? - Why is there no
man nix
? - Why did
nix-env -i hello
tell me it “created 40 symlinks in user environment”? - How do I install commands using
nix
instead ofnix-env
? - Why does
nix-shell
download so many packages? What are they for? - Why does
nix run
need thenixpkgs
qualifier whennix-shell
doesn’t? - Why does every invocation of
nix-env
take 30 seconds before it does anything? - How do I collect garbage without making my next invocation of
nix-shell -p hello
redownload all the stuff it needs to be a shell? - Why is there an
--upgrade
command if--install
will upgrade packages? - Why does the manual explicitly call out upgrading Nix? Will it not be upgraded through “normal” means? Do I have to upgrade it separately?
-
This is a case where my past Nix experience may have saved me a bit of time. A friend read this blog post and made the following very good point: “surrounding a word in angle brackets is canonically the way you describe a like variable substitution in example code.” Which is true! And potentially very confusing! The fact that it’s in quotes hopefully makes this interpretation less likely, but maybe not I don’t know. It didn’t happen to trip me up, but maybe it would have five years ago. ↩︎