I’ve recently had another go at organising my settings files (‘dotfiles’) and the way that I install command line applications and tools. It started out highly sophisticated (Nixpkgs and Home Manager), and then reverted to much simpler but more maintainable system (Homebrew and Stow). It has been an interesting and intermittently frustrating process, but I’ve ended up with a system that I like and understand.
You may remember that last year I played with setting up an old Macbook Air running NixOS. That was a fun learning experience, and I was impressed with NixOS, and the way that you could declaratively define in a setting file both the software to install and the configuration file to go with it. In theory, this should make it easy to keep multiple machines updated with the same system, and to set up a new machine quickly with all the tools you need.
I was getting a new Macbook Air for work, so setting up a new machine cleanly was at the top of my mind. I decided (before getting the new laptop), that I would try configuring my old laptop and my home iMac using Home Manager in combination with the Nix package manager. In theory, this would allow me to store the list of software to install and bundle that together with the respective dotfiles. However, unlike my previous experiment, this system would be running on top of macOS, not within NixOS, which would constrain the range of stuff which could be configured at the operating system level.
To cut a very long and frustrating story short, I got it to work, but it was a very difficult process. Part of the problem was that not all of the software available through the Nix packages channel is set up to build on macOS, and I didn’t understand enough about the Nix language to be able to pull in and build packages outside this system. I hit lots of frustrating barriers, and even situations where something would build on one machine but not on another, which — given that Nix is supposed to provide a reliable and reproducible system — was baffling. Eventually, it became apparent that it was going to be too much work for me to maintain a system like this, and it would incur too high a risk that it would suddenly and inexplicably break. I would need to learn a lot more about Nix before really knowing what I was doing with it, and I think it also needs some of the macOS issues smoothed out as well. My previous experience running NixOS was much less problematic, so I suspect it’s much better if you go all-in with it, and use it to run the whole operating system.
Previously, I had been using dotdrop for my dotfiles together with Homebrew to
install software. I decided to stick with Homebrew, but moved to Stow to manage
my dotfiles. After my battles with Nix’s complexity, I wanted something really
simple and easy to maintain. Stow is designed to organise software installed
from source (similarly to Homebrew, but for any Unix-like operating system), but
it can also be used to copy a directory of dotfiles in one source location to
the proper locations for each installation. This article by Alex Pearce explains
the way this works, but essentially you create a single ‘dotfiles’ directory
wherever it is convenient (which can be version controlled). Inside this
directory, you have top-level folders for each of the ‘packages’ you want to
configure (e.g. vim, emacs, zsh and so on). Within each of these, you set up the
exact file structure you want to appear in your home directory. To install these
files, you move into the dotfiles directory and use the command
stow zsh (for
example) to move all the files inside the zsh directory to their proper
locations in your home folder. You then repeat this for each set of
configuration files, or when you add a file and want to update the
configuration. This means that you don’t need to install everything on every
machine, but can pick and choose what you install. It’s a simple but effective
system, with a very easy to understand model because all you need to do is
reproduce the desired organisation and naming of files within your dotfiles
The next piece of the puzzle was Homebrew. I cleaned up what I had installed and
discovered that you can dump a list of Homebrew-installed software to a
Brewfile, and then use that to install the same packages on another machine
brew bundle commands. You can even use
brew bundle cleanup to
uninstall anything which isn’t specified in the Brewfile, so you can try out
software and then get your system back to a known state using the Brewfile. It
was also news to me that you can install quite a few GUI applications (like
brew cask commands. This has meant that I have been able to
organise a lot more of my installed tools via Homebrew, making it much quicker
to get started when setting up (or cleaning up) a new machine.
Finally, I switched to using zprezto instead of oh-my-zsh to configure zsh to my
liking. It seems a bit lighter, and I like the organisation of the configuration
files better, though the end result is very similar. I’m starting to use more of
zsh’s more interesting features as a result, like global aliases which allow you
to create aliased commands to use anywhere in a command pipeline, not just at
the start of the command. These are extremely handy for commonly used tasks like
piping a command to
| wc -l or something similar. I’m also using direnv to
manage per-project configuration (for Python versions and packages, and so on),
which integrates very nicely with both zsh and emacs (through emacs-direnv).
Many languages (like Ruby and Python) have ways of achieving something similar,
but direnv allows you to use one common method to handle them all (plus any
shell-specific environment variables you need), which is much cleaner and more
I’ve also created a README file for myself in my dotfiles directory which outlines both the steps to install all the necessary stuff and to maintain and update the system, which is something that I typically forget how to do after a while. I may even write a proper bootstrap script at some point which installs and configures everything from scratch, but for now, doing a few steps manually is fine with the instructions in front of me. I have a nice, stable, maintainable system, and it is easy to install or uninstall new stuff cleanly and easily.