Eight Months of NixOS
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.
customizability
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
- Better documentation. Stuff that covers "I have a piece of software that's not packaged, how do I package it locally". Or "the software in nixpkgs is broken in some way, I'd like to test out some changes to the package definition, how do I set up the overrides for that". That sort of thing. A lot of this is covered on the wiki, so the usual WP:SOFIXIT applies, but this is the sort of thing that I think should have already been written.
- Faster switches for trivial changes. The big reason I don't
manage my .emacs.d via nix is that it means that even a trivial
change requires me to do the whole
sudo nixos-rebuild switch
dance, which can take 20-30 seconds just to make a one-line change. This kills my iteration velocity. There are workarounds like having a 'staging' file that gets sourced and then every so often moving code from the staging file into the main file, but that seems like a workaround for a problem that shouldn't exist. - Some kind of lockfile to store package versions. One of the bits
of state that isn't stored in
/etc/nixos
is what version of the nixpkgs and home-manager repos I'm currently on. This means that if I run aswitch --upgrade
, there's no way to go back without rolling back those channels vianix-env
. On the other hand, if those versions were stored alongside my nix files, then I could just revert them in case of a bad upgrade. My understanding is that flakes are an upcoming feature that solves this problem, among others.
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.