Purely Functional Package Management
Being a fan of pure functional programming, I’m intrigued by the prospect of applying those concepts to package management on my systems. Since I’m mostly on macOS these days, I’m going to start with the Nix package manager for my initial foray into this new and exciting world.
I’ve been a dedicated user of Homebrew since Max first released it. However, after watching Tim Steinbach’s λC 2019 presentation Sane System Management with NixOS - I Do Declare!, I found myself motivated to give Nix a try. The talk is well worth a watch. Tim does a good job of making the case for using Nix. For me, some highlights from the talk were
- One single configuration file for kernel, services, and updates
- Atomic upgrades and rollbacks
- Side-by-side installation of different versions of the same package
- (Re-)installation within minutes
- Bitwise reproducibility of the entire system
Quickstart
The first time I ran through the steps below, I ran into the following issue on Catalina:
mkdir: cannot create directory ‘/nix’: Read-only file system
Starting with Catalina, macOS is split across two volumes (system and data) with the system volume being read-only and non-writable. After some searching through Nix issues and random blog posts I came across NixOS/nix#3212 and the relevant script to help create the appropriate APFS volume and mount point. The shortened URL follows:
$ curl -L https://git.io/JfRJm | sh
Once completed you’ll see the following output:
------------------------------------------------------------------
| 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
Configuring /etc/synthetic.conf...
Password:
nix
Creating mountpoint for /nix...
Creating a Nix Store volume...
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 disk1s6
Mounting disk
Setting volume permissions
Disk from APFS operation: disk1s6
Finished APFS operation on disk1
Configuring /etc/fstab...
123
164
The following options can be enabled to disable spotlight indexing
of the volume, which might be desirable.
$ sudo mdutil -i off /nix
I wasn’t able to successfully disable spotlight indexing of the /nix
volume
until after I rebooted my machine, which you need to do before proceeding anyway.
Now that the volume is available, we can proceed with the installation.
By default, the Nix installer will perform a single-user installation. However, the documentation highly recommends opting in to the mult-user installation.
From a terminal, install multi-user Nix:
$ sh <(curl https://nixos.org/nix/install) --daemon
Since Nix installs everything in the Nix store, removal is quite easy if we find that we don’t actually like the project. On macOS:
$ sudo rm -rf \
/etc/profile/nix.sh \
/etc/nix \
/nix \
~root/.nix-profile \
~root/.nix-defexpr \
~root/.nix-channels \
~/.nix-profile \
~/.nix-defexpr \
~/.nix-channels
$ sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
$ sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
# rollback global etc files modified during install
$ mv /etc/profile.backup-before-nix /etc/profile
$ mv /etc/bashrc.backup-before-nix /etc/bashrc
$ mv /etc/zshrc.backup-before-nix /etc/zshrc
Migrating off of homebrew
Before we start messing with our installed packages, its a good idea to dump our homebrew packages so we can restore everything if things go sideways with, or we decide we don’t actually like, Nix.
$ brew bundle dump
This will generate a Brewfile
in the current working directory, which we can
squirrel away in the event we need it later. We can restore our homebrew managed
packages with:
$ brew bundle
Hopefully we won’t need it as I anticipate not wanting to come back after we free our mind and take the red pill, but better to be safe and prepared just in case we change our mind.
Salar Rahmanian put together a quick guide on getting started migrating off homebrew. In it he provides a command that lists the formula installed by homebrew and the formulae installed that specify the formula as a dependency.
$ brew list -1 \
| while read formula; do echo -ne "\x1B[1;34m $formula \x1B[0m"; brew uses $formula --installed \
| awk '{printf(" %s ", $0)}'; echo ""; done
Once you’ve visualized your homebrew footprint you can use the CLI to look for a similarly named package:
$ nix-env -qaP | grep -i <packagename>
or search packages online to find a suitable Nix flavored package.
Another helpful resource that goes deeper into Nix is Geoffrey Huntley’s Mastering Nix Workshop. This coupled with Mastering NixOS offer a treasure trove of learnings I plan on mining next.
Next Steps
In Tim’s talk, he mentions how he uses NixOS in addition to the Nix package manager. NixOS is a GNU/Linux distribution that allows you to achieve a fully declarative system configuration model. Nix modules for darwin attempts to achieve similar things for macOS.
Once I’ve fully digested these concepts, I’d like to codify the best practices in my dotfiles repo and get to an even more idealized place of consistent, functional, and sane systems management.