I recently bought a new computer for personal use. Like any new computer, I had to set up my favorite software on it: Spotify, Firefox, the One True Editor, and so on.
A reasonable person would go about this problem the way they’d go about most problems: by solving it. For better or for worse, I am not such a person. I decided to automate this setup so that in theory I’d never have to do it again.
As the saying goes, now I’ve got two problems.
This post is a mostly unstructured catalog of my thoughts about Nix. If you a want a more detailed, thorough look at Nix, Ian Henry’s tour of the Nix manual is where it’s at.
In a past life, I worked on a system for provisioning MapReduce clusters. People doing data stuff would click buttons in a UI, go get a cup of coffee, and come back with a ready-to-use cluster for data stuff.
These clusters were often too large (in general, they were just too large – but that’s an aside) to manage by hand, so my team used Ansible.
Ansible playbooks allow you to write a YAML file that does normal sysadmin stuff like
installing software, configuring software, pipe curl
into sh
, and so on. Ansible is a fine tool, but I didn’t enjoy using it much personally.
I thought to myself, “Surely I can use something simpler than Ansible, like my own Bash script.”
Then I got into about 100 lines of Bash, decided some logic was better handled in a Python script, wrote about 100 lines of Python, and thought again – surely there’s something simpler that’s a better fit here.
So obviously I landed on Nix.
I don’t remember when or where I first heard about Nix, but I remember hearing that it was confusing and poorly documented. But that was a long time ago. I’m a smarter person now, and Nix has probably gotten better since then.
I started using Nix by treating it like a black box, which is a strategy that’s treated me well for being productive quickly.
I found an example Nix configuration (thanks Dustin!) that checked some boxes I knew I’d care about:
But most importantly, a really obvious iteration loop: change some files, run bin/build
, and see if it worked.
The nix
command has a lot going on:
$ nix --help | wc -l
897
# As of...
$ nix --version
nix (Nix) 2.18.1
And I bet that makes a lot of sense for someone out there, but as a brand new user, not for me. A suite of helper scripts would probably be the first thing I write, so it makes sense to bootstrap it by lifting someone else’s repo.
This reduces a daunting space of problems, learning Nix, to a normal process for working on code, which I do every day. Great!
I won’t describe every problem I ran into, because most of them are better described elsewhere or just typical newbie problems.
tl;dr I fixed a minor inconvenience (extra icons in my Dock) in using Doom Emacs on macOS by also using Chemacs, another Emacs package. Since they’re all managed by plain-text config files, and Nix is very amicable to such config management, I was able to get everything working “just right.”
One sort of interesting problem I ran into was with getting Doom Emacs to work in a Nix-y way.
Emacs in general is pretty Nix-friendly. Most Emacs config is manage declaratively (or at least as plain-text source files) these days, and there’s an emacsPackages
package set that works out-of-the-box with Nix if you’d rather use that.
But Doom Emacs is a little different, in that its installation process is meant for any Emacs user and is generally Nix-agnostic. There’s a community-managed derivation for it, but it’s effectively unmaintained, so I didn’t bother with it.
Luckily, the creator of Doom Emacs is also a Nix user! Using his configuration I was able to get a working Doom Emacs installation, with one small problem.
I’m using macOS, which provides a nice-looking Dock to display my favorite and currently-running applications. When I’m running Emacs.app
(installed via Nix), everything looks fine. I’ve got one icon for Emacs, and nothing else, because I’m not running anything else. But this gives me Emacs, not Doom Emacs, because the config lives in different places: ~/.emacs.d
vs. ~/.doom.d
and ~/.config/emacs
.
But when I run doom run
, I get Doom Emacs, but it uses a separate icon in the Dock than regular Emacs.app
. That’s not terrible, but:
doom run
from the dockSo I tried setting up an Automator script as an application to run doom run
so I could run it from the dock, but this still gave me extra icons. In fact, it gave me an extra one, which pointed to the Automator script. That icon pointing to the Automator was also a default “text file” icon, which was even uglier.
I needed some way to trick my Emacs.app
into running Doom’s config as-is – not as a separate executable.
Enter Chemacs. Chemacs is an “Emacs profile switcher,” or a “bootloader for Emacs,”" which lets you easily switch between different Emacs configs by basically adding one more dotfile to the mix.
So now my Nix config looks pretty simple:
# Wherever you'd define your dotfiles, or however else you define your emacs config:
{ pkgs, config, ... }:
{
".emacs-profiles.el" = {
text = ''
(("default" . ((user-emacs-directory . "${XDG_CONFIG_HOME}/emacs")))
;; Any other profiles you care about go here.
)
'';
};
}
That’s it. Get Doom and Chemacs playing nicely and you remove the minor inconvenience of extra icons in your Dock.
So far:
Unless:
I absolutely hate the inconsistency between hosts that I was managing: my work laptop, my development VM at work, my personal laptop, and a homelab server. I dislike it enough that I figured it’s worth a weekend to grapple with Nix. I think that’s paid off pretty well already, and will continue to do so as long as I maintain my discipline about it.
I stand on the shoulders of giants: