gay robot noises

Eight Months of NixOS

Declaring my enjoyment of declarative package management.
1665 words

About 8 months ago, I decided that I'd gotten tired of macOS. It's a Unix, which is nice, and I like that I can set up things with relatively minimal effort through Homebrew, but I wanted something that actually gave me more control over my system. So I decided that 2020 was going to be the Year Of Linux On Ash's Laptop.

In addition, I decided to try out NixOS, which is an operating system oriented around the Nix package manager. The big thing that distinguishes Nix from other operating systems is that it lets you manage your packages declaratively: you specify what you want the state of your system to be, then run sudo nixos-rebuild switch, and it reconfigures your system to be in that state. I also decided I would use home-manager, which lets you do the same thing for your dotfiles. As is tradition, I also decided to version my dotfiles, though they're still kind of a mess.

the good

hardware support

I bought a used Thinkpad T450s off of eBay, because I wanted to make sure I had good hardware support, and the Thinkpad line has historically had a reputation for things working out of the box. And everything has worked fine: Bluetooth, wireless, even the touch screen (not that I use it). I didn't have to hunt down any weird driver repositories or modprobe anything into my kernel.

The one thing I haven't tested is the fingerprint reader, because I don't trust it to be secure, so I never set it up in the first place.

I did run into one issue: when I bought a replacement battery off eBay, Linux was having trouble recognizing it and reading its status. I had to do some kind of magic dance to reset something or another: I think it was 'pull the battery and AC adapter, hold the power button for 5 seconds, then plug it back in'. But once I did that, everything was fine.

declarative package management

This was the reason I specifically wanted to use NixOS in the first place. I liked the idea of having my system's configuration explicitly written out, and not just determined by whatever series of packages I happen to have apt-get installed. And NixOS delivers; my nixos-config repo contains not only my dotfiles, but a list of all the programs I have installed, my fonts, and so on. I also don't have to worry about having accidentally installed something weird and then forgotten about it; whatever's not in my /etc/nixos might as well not exist (though it can still leave stuff in ~/.config and similar places).

I intend to eventually switch the server this blog is hosted on over to NixOS for similar reasons, and I'm definitely looking forward to being able to declaratively manage things like nginx configuration and systemd scripts as opposed to scattering them all over my filesystem.


I've got everything set up the way I like it. I decided to finally give tiling window managers a shot, and after using awesomewm, I don't think I could go back to having to arrange all of my windows by hand. Even on a laptop screen, I like putting my messaging applications (Discord, Telegram, IRC) side-by-side. And of course, having purpose-driven workspaces is nice. Instead of hunting down my music player in a window switcher, I just hit Win-6, since I always keep cmus on the 6th workspace.

I've also got bars set up to display things like my upcoming todo count, my CPU usage, battery life, currently-playing music, and so on. I could certainly have done this on MacOS using Übersicht or something similar, but it feels intuitively 'cleaner' in some way to be doing this in an operating system set up to have this kind of flexibility in the first place.

the not-so-good

the nix language

The Nix package manager uses its own language, which is also called Nix, to define how packages work. The language is functional in the style of something like Haskell or the ML family, but lacks a lot of the niceties those languages have like a strong static type system or a means for doing imperative stateful programming. I ran into this when I was trying to generate a configuration file by iterating through a list and having the callback update some amount of state; Haskell has the state monad, but Nix has no such thing.

This is especially salient because Nix does not follow the standard Linux filesystem hierarchy; it stores almost all of its libraries and binaries under /nix/store. This means that you can have multiple copies of something installed without them interfering with each other, but it also means that you need to know how Nix packaging works to compile anything. Even libc isn't in its usual place, so downloading prebuilt binaries usually won't work.

Fortunately, packaging simple software generally just involves telling it how to fetch it and what its build dependencies are, but figuring out how exactly to do that and how to integrate it into the rest of your configuration requires reading documentation. Which brings me to the next point:

extremely sparse documentation

Even though the Nix language itself is fairly simple to understand, the way in which Nix puts everything together can be... obtuse. For example, each package in the nixpkgs monorepo is implemented as a function (here, the {stdenv, fetchurl}: syntax at the top means that the file declares a function that takes a record with two fields named stdenv and fetchurl. However, when defining my own derivations to include in my personal configuration for programs not packaged by Nix, instead of defining a function, I just wrote out the derivation by hand. The difference to me wasn't obvious, until I did some deep digging and learned that behind the scenes, the nixpkgs repo invokes callPackage, which invokes the function with the relevant arguments.

Even just writing those packages involved searching the nixpkgs repo for similar programs and then copy-pasting what they did. The official NixOS wiki has basically similar recommendations, and I found the NixOS manual to not be super helpful here. I wound up making heavy use of the Nix Pills, which are a series of posts that show you not only how Nix package management is implemented, but why it works the way it does.

Similarly: some commands are invoked like nix run, some are nix-shell. I don't know why.

would i recommend nix?

Only to power users. If you're going to use it, you're going to have to deal with the packaging stuff at some point, either to debug a package or to use software that doesn't have one. And this means you'll need to learn the Nix language, and wade through the nixpkgs monorepo and some fairly iffy documentation. But if you can put up with that, the 'everything is declarative' setup is exactly as nice as I hoped it'd be, and I've had multiple instances where an update broke something so I was able to just boot to an older generation. Similarly, when the BleedingTooth vulnerability came out, I was able to disable my Bluetooth stack with a one-liner in /etc/nixos/configuration.nix to just blacklist the entire hardware support and then when the kernel updates came out I was able to just remove that single line as opposed to trying to remember where the fuck I'd stored that definition.

action items for the nixos community

final thoughts

Overall, I've definitely enjoyed the switch to Linux, and I can't see myself going back to macOS any time soon. I may wind up buying a more powerful laptop at some point, since this one is having problems with driving a 2560x1440 monitor due to a low fill rate on the graphics card, but I want to reduce my electronics waste footprint (hence why I bought this one used), so I'm going to stick with it for at least a few more years.

I'm also probably going to stick with NixOS, but I hope it becomes better documented and more newbie-friendly. Right now, I'd hesitate to recommend it to anyone who wants something that just works and isn't interested in diving into the guts of their system or learning a new language just to install software.

// α-5/h