Skip to content

Instantly share code, notes, and snippets.

@lloeki
Last active March 24, 2020 19:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lloeki/e8864cf72f54b613872c169d31ce355c to your computer and use it in GitHub Desktop.
Save lloeki/e8864cf72f54b613872c169d31ce355c to your computer and use it in GitHub Desktop.
Nix: getting started

What is Nix?

Nix is a package manager, born form a research project, with interesting design and properties.

Not to be confused with NixOS, which is:

  • an operating system
  • with a Linux kernel
  • based on nix as a package manager
  • with a descriptive configuration system from which actual configuration is generated

Install

On macOS, there is currently an issue with / being made entirely read only since Catalina. But there is an install script approved and to be merged upstream.

Here's how to install Nix in general:

# This will work in the future but currently fails on Catalina after nixbld* user creation
$ sh <(curl https://nixos.org/nix/install) --daemon

If the above fails on Catalina after the user creation step, proceed as follows:

$ curl -O https://raw.githubusercontent.com/NixOS/nix/1c6706167844014d2084c332a0829e40100f281c/scripts/create-darwin-volume.sh
$ bash create-darwin-volume.sh
$ sh <(curl https://nixos.org/nix/install) --daemon

You should be good!

Running an ephemeral command: the Nix shell

nix-shell evaluates nix expressions and commands in an environment of your control:

The following command runs the shell command python3 --version within an isolated and ephemeral enviroment where all dependencies for the python3 are satisfied, with environment variables, paths, etc... set, but anhy other package invisible. THis means that any dependency of python3 and python3 itself will be fetched as needed.

$ nix-shell --packages python3 --run 'python3 --version'

Package storage: the Nix store

At the end of the nix-shell command, the environment disappears, but the packages remain in the so-called nix store. This looks eerily like docker run --rm.

Nix downloads and unpacks packages in that store. This store is content-addressed via a hash, a bit like git objects or docker layers. This hash is created from the package version and its dependencies, so if any of those change, the hash changes. You can see it as an obscure prefix here:

$ ls /nix/store | grep python3-3
0hi6adsig2nnp0dxw4wdiswlqfamqvfs-python3-3.8.2
1s66apwyh9ia021xigf19bvjw7krhqxf-python3-3.7.6

Installing a package

This is cool, but sometimes you just want to expose the package contents outside of a nix-shell.

But if you want more casual access, you can install packages into a profile. What is a profile, you ask? It's a file hierarchy not unlike the FHS, where relevant files and directories get symlinked, so that you have direct access to them, the usual way. A user's default profile lies at ~/.nix-profile.

You could do this, but it would pick an arbitrary version (like an alpha one), which may not be what you want, so let's pretend with a dry-run:

$ nix-env --install --dry-run python3

Instead, you can specify an exact version (called "derivations" in nix parlance):

$ nix-env --install python3-3.7.6

If the version exists, it will check whether a prebuilt binary package exists, otherwise it will attempt to build it from source (and will recursively do so for any required dependency).

After that, the python3 command will be available right in in your user's path, and more:

$ rehash   # zsh:  refresh command hash table, just in case
$ hash -r  # bash: refresh command hash table, just in case
$ which python3
/Users/<user>/.nix-profile/bin/python3
$ ls -l .nix-profile/lib/python3.8
lrwxr-xr-x  1 root  wheel  71  1 Jan  1970 .nix-profile/lib/python3.8 -> /nix/store/0hi6adsig2nnp0dxw4wdiswlqfamqvfs-python3-3.8.2/lib/python3.8
$ python3
Python 3.7.6 (default, Mar 11 2020, 12:53:39)
[Clang 7.1.0 (tags/RELEASE_710/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
$ nix-env --uninstall python3
$ which python3
/usr/bin/python3

So, from a pragmatic point of view, "installing" is basically just setting up some symlinks from the profile to the store, and "uninstalling" is just removing them.

Querying for packages

Now how to find out what you have installed in your profile?

$ nix-env --query

And how to find what to install? This queries the current "channel", which is the source of derivations.

$ nix-env --query --available

But those names are quite opaque, a description might help discriminate:

$ nix-env --query --available --description

What are the available versions, say, for python3?

$ nix-env --query --available 'python3'
python3-3.5.9
python3-3.5.9
python3-3.6.10
python3-3.6.10
python3-3.7.6
python3-3.7.6
python3-3.7.6
python3-3.7.6
python3-3.8.2
python3-3.8.2
python3-3.8.2
python3-3.9.0a4

Do I have any installed or in the store?

$ nix-env --query --available --status 'python3'
--S  python3-3.5.9
--S  python3-3.5.9
--S  python3-3.6.10
--S  python3-3.6.10
-PS  python3-3.7.6
--S  python3-3.7.6
IPS  python3-3.7.6
--S  python3-3.7.6
-PS  python3-3.8.2
--S  python3-3.8.2
--S  python3-3.8.2
---  python3-3.9.0a4

I stands for installed, P stands for present (i.e in the local nix store), S stands for substitute available.

But why is there the same version multiple times? Attribute paths:

nix-env --query --attr-path --status --available python3
--S  nixpkgs.python35          python3-3.5.9
--S  nixpkgs.python35Full      python3-3.5.9
--S  nixpkgs.python36Full      python3-3.6.10
--S  nixpkgs.python36          python3-3.6.10
-PS  nixpkgs.python3           python3-3.7.6
--S  nixpkgs.python37Full      python3-3.7.6
-PS  nixpkgs.sourcehut.python  python3-3.7.6
--S  nixpkgs.python3Full       python3-3.7.6
IPS  nixpkgs.python38          python3-3.8.2
--S  nixpkgs.python39Full      python3-3.8.2
--S  nixpkgs.python38Full      python3-3.8.2
---  nixpkgs.python39          python3-3.9.0a4

Attributes is a fairly advanced feature in itself, but here this allows differentating between variants of the the same version:

$ nix-env --install --attr nixpkgs.python35

Cleaning up unused cruft in the store

Now, maybe because I ran a nix-shell command, or I uninstalled a package, I have some unneeded cruft in the nix store. Time for some garbage collection:

$ nix-collect-garbage

This will remove anything from the nix store not currently in use by a profile or a nix-shell command.

Except not! Because each change to installations creates a "generation", which allows you to rollback to a previous installation state easily:

$ nix-env --rollback

You can clean up all non-current generations at once and clean up the leftovers:

$ nix-env --delete-generations old
$ nix-collect-garbage

What's next?

That should be enough for basic usage of the Nix package manager! If you want to know more, including about channels, profiles, generations, and building your own derivations from nix expressions, read the official guide!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment