Recently I have begun migrating my workstation and laptop from Gentoo to NixOS. There are a great deal of tradeoffs between the two operating systems. Before going into the details, consider where I’m coming from and why I moved away from Gentoo below.
§Why was I running Gentoo on workstations??
This is my heuristic for a good operating system:
The Distro must provide facility to modify system packages and maintain their modifications in sync with the upstream distro.
This eliminates a lot of “popular” distros as it is not a an ergonomic process to work with RPM spec files, the Debian build system, and even Arch is not very practical in this regard1. The remaining candidate Distros are the likes of Gentoo, GuixSD, NixOS, Alpine, and using linuxbrew/pkgsrc/other “ports-like” systems.
Anyways, back in 2018 I migrated from Arch to Gentoo. It went super well! I
gained a lot of functionality. Most importantly I could patch any package I
wish without risking dealing with library packages breaking my packages on
major revision bumps. If you drop a /etc/portage
, /var/lib/portage/world
,
/var/lib/portage/world_sets
in a fresh Gentoo install, then rebuild world,
you’re set. In other words, the package configuration is declarative in
Gentoo. You specify what you want in a bunch of text files, and you know
exactly what you’re getting and why. In fact, you can review my Gentoo
configuration files in the GitLab.com winny-gentoo-ops namespace.
§Some Gentoo Gotchas
There are a few downsides with Gentoo I like to mention. There are no binary packages publicly available. This is a half-issue, as I built binary packages for all my installed software. Then I was able to install the same exact binary packages on my laptop. Still, it can take a bit of time to build packages.
The community in Gentoo is very kind and welcoming. There is presence on Discord, IRC, Forums, Mailing Lists. If you have a question you can usually get an answer. On the other hand, when trying to get bugs fixed, there aren’t enough people with commit status responsible for the ebuild repository. This results in a many bugs left unaddressed for months despite well-tested patches on the Bugzilla tracker. Due to lack of movement of bugs and PRs, myself and perhaps a portion of users don’t really bother contributing patches back anymore. It’s not a good use of time. It’s far more time efficient to drop patches on my own systems and call it a day. One should always report bugs, but I don’t hold my breath about them being fixed in the official repository.
The above also applies to contributing back to third party overlays - a lot of maintainers don’t really care for contributions and won’t overtly say that, but you see it in how they triage their PRs. They just want you to go away and do your own thing. Some aren’t like that, but the majority feel this way. I guess it makes sense, given the reason they use Gentoo is the similar to my own needs - ease of customization and ease of package maintenance.
§Why NixOS??
Now that you understand why I have reservations with using Gentoo for my workstation and laptop, I’ll contrast with the NixOS project.
First, the contribution model for NixOS is clearly defined and while there are a lot more open PRs and issues, there are also a lot more people triaging. This means you usually have tight feedback loop on pending contributions. There is a higher chance of upstreaming your fixes to the Distro on NixOS than Gentoo at this time.
Second, the package selection on NixOS is insanely diverse. According to Repology, there are 9195 unique packages in nixpkgs stable 22.05, followed by Raspbian Stable, Ubuntu 22.04, Debian 12, and FreeBSD.2 This means I’ll invest less time packaging software, instead spend more time bugfixing and using my system.
Third, ZFS support is baked into the system. This is sort of a minor one, but any distro that makes you work to get ZFS support (e.g. having to take extra steps to enable/maintain it) is not good for ZFS adoption. I actively discourage using ZFS on such distros because the risk of messing everything up is just too great. Use something else like LVM2+Ext4/BTRFS and have less pain. On NixOS you simply just specify you wish to use ZFS and that’s it.3
Fourth, NixOS feels a lot more maintainable in general. As one specifies the
majority of the system configuration in /etc/nixos
, most of the important
configuration is easy to version and track. Troubleshooting errors from nix
feels a lot easier than browsing the portage codebase… the portage codebase
feels less than ideal and hard to follow - it has a lot of history.4
Building upon the above, the lifecycle of NixOS versus Gentoo is a lot less
work on the user side. On NixOS you usually just run nixos-rebuild switch --upgrade
to update. On Gentoo you need to do a bit of gymnastics to ensure
your system is upgraded to a sane state. Please see this script I wrote to
automate the process. If I didn’t have the script I’d forget steps.
§Assessing NixOS viability
Still reading? Great! You’re a pretty cool reader, by the way. :)
To prepare for the migration, I tested NixOS installations within libvirtd. First was a simple Ext4 rootfs, followed by a BTRFS rootfs, then a ZFS rootfs. Then I went a bit more off the wall, tried Ext4 in LVM2 in LUKS - works great. Now what impressed me is I was able to also enable ZFS encryption on the root filesystem. This is the ticket - I can use ZFS and discard the encryption keys when I am ready to reuse the hardware.
Quick aside: as I played with the configuration, one very amusing tunable was discovered:
services.xserver.desktopManager.cde.enable
. Enable this, then reboot your
system, enjoy! Yes this is a screenshot from my VM!
During the above testing, I found it a bit frustrating using the NixOS Gnome or KDE live media. I also don’t use qwerty so that’s a bit of a extra work to set up dvorak every time I boot the live media. After a quick Google, it turns out it is possible to create one’s own live media without much effort. In fact from start of reading the Wiki page to creating my custom live media elapsed about 1 hour - 30 minutes of which was the computer spinning its wheels compressing the squashfs. See my barebones dvorak, XFCE, Emacs livecd configuration here. Since creating this custom live media, I haven’t used the official media - it’s just so comfy.
I invested a bit of time when running NixOS in a testbench to port over most of my package selections from Gentoo. During testing I found a few packages that were missing… they might be my first packages to contribute back. I also found one bug that was promptly fixed by a caring maintainer.
§The migration
See also the Installation guide in the NixOS Manual.
The migration was fairly straightforward. I made a full disk image of my OS
SSD using GNU ddrescue and saved it to an external HDD. This served as a fresh
backup, though I had other local and offsite backups to refer to in case the
external HDD fails. After verifying I can access data off the external HDD, I
went ahead and wiped the SSD using blkdiscard
.
Next I created the GPT partition table, a EFI and /boot partition, then the rest of the disk was set up for ZFS. Here is the partition listing:
[root@stargate:~]# parted /dev/nvme0n1 print
Model: Samsung SSD 980 PRO 1TB (nvme)
Disk /dev/nvme0n1: 1000GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
1 1049kB 1000MB 999MB fat32 boot boot, esp
2 1000MB 1000GB 999GB nixos
Next I created the ZFS pool. According to zpool history rpool
, these are the
commands executed to create the ZFS Zpool and ZFS Datasets:
zpool create \
-O mountpoint=/ \
-O encryption=on \
-O keyformat=passphrase \
-O keylocation=prompt \
-o ashift=12 \
-o autotrim=on \
-O acltype=posixacl \
-O xattr=sa \
-O relatime=on \
-O canmount=off \
-O compression=zstd \
-O dnodesize=auto \
-O normalization=formD \
-R /mnt rpool /dev/nvme0n1p2
zfs create -o mountpoint=none rpool/nixos
zfs create -o mountpoint=legacy rpool/nixos/nix
zfs create -o mountpoint=none rpool/userdata
zfs create -o mountpoint=legacy rpool/userdata/home
zfs create -o mountpoint=legacy rpool/userdata/root
All Datasets are encrypted by a passphrase. Note I chose to enable ZSTD compression out of the box for all Datasets. Compression reduces the amount data that’s written to the storage device, with a slight increase in CPU load. This tradeoff is usually worth it because disk IO is usually orders of magnitude slower than the compression/decompression CPU cycles.
Once the Datasets are created, be sure to mount them using an incantation like
mount -t zfs POOLNAME/DATASET MOUNTPOINT
. See the ZFS wiki page for more
detail.
After all filesystems are mounted in the installation prefix, I ran
nixos-generate-config --root /mnt
to generate the configuration file
skeleton. I cloned the git repository where I stored my testbench VM
configurations, and dropped in my common winston.nix
file(s) for use on all
my machines. These configurations installed all the packages I want, set up
XFCE autologin, and so on. After adjusting the config files with my favorite
text editor, I ran nixos-install
, entered a new root password. Before
rebooting, I needed to set my user password, so I ran nixos-enter
to automate
the chroot steps and run commands within the new NixOS environment without
rebooting. Finally I set the password for my user within the chroot: passwd winston
.
After completion I rebooted, typed in my ZFS encryption passphrase, and was met with an auto-login XFCE4 desktop. I think that was the fastest OS installation I’ve done in a long time. It took about 30 minutes, most was downloading tens of GiB of packages (I run a bloat-y OS I know !).
Here’s an obligatory neofetch.
§First NixOS upgrade ever
Shortly after I installed NixOS, 22.05 was released. I held off for a few days until I felt the system was configured more-or-less as I wished it to be for the hardware. It turned out all one had to do is change the Nix channel (aka package repository) then run a command to rebuild the system. See Upgrading NixOS in the manual.
There were a few packages that got moved to different names. Nix told me about
them, though it did not attempt to auto-migrate these obsolete package names,
so I still had to edit the .nix
files. Ah well, can’t win them all :).
One surprise was change in behavior in the nix package manager. nix search
no longer worked, instead printing out a warning you have to enable
experimental features. I had to add the following to my configuration.nix
nix = {
extraOptions = ''
experimental-features = nix-command flakes
'';
};
The behavior of nix search
had changed a bit, so one now needs to use nix search nixpkgs thing
instead of nix search thing
. Much to my surprise, the
release notes did not mention this. See the version from June 2nd; compare to
current. Thankfully a thoughtful developer reworked the release notes
recently, so other users will know which nix release notes to also review (yay!).
There was one other minor issue caused by a setting being renamed or the like. That took a bit of research to determine how to port that knob to the new version of NixOS. I don’t recall which knob it was. It was confusing but I figured it out.
A quick reboot and everything is great as it was previously!
§ZFS Snapshot gotcha
I had a funny experience with enabling ZFS Automatic Snapshots. As it turns out
you need to do a bit more than add services.zfs.autoSnapshot.enable
. Indeed,
you need to set a property on every ZFS Dataset you wish to be considered for
automatic snapshots. In my case I just had to run zfs set com.sun:auto-snapshot=true rpool
to enable automatic snapshots of every
Dataset.
This is sort of my fault, as I did not do enough due diligence. It’s mentioned in the above option’s documentation, though I assumed nix would complain about a nonsensical configuration. In any case, I filed a GitHub issue to track this gotcha.
§Secondary LUKS volume
This was another funny NixOS gotcha. I kept asking in Matrix about how to
enable LUKS for a second volume and nobody was familiar with how to answer that
question. There was discussion to enable LUKS for the root filesystem.
After reviewing the source code for luksroot.nix
, it was evident this was not what I
wanted. It took a bit of researching on Google and the Nix Discourse to come
up with a solution and it’s kind of surprising.
The solution: just use /etc/crypttab
like most systemd distros. Given most
of NixOS is about wrapping up settings into a unified configuration language,
it feels rather goofy to just drop in a standard configuration file. The way
to do this is add something like this to your NixOS configuration:
environment.etc.crypttab = {
enable = true;
text = ''
dmMapperNameHere UUID=b8104880-1fbf-41b5-9a90-2a4f1cc5d6de /secrets/your.key luks
'';
};
Then nixos-rebuild switch
. Your LUKS volume should now be automatically unlocked
by keyfile.
§What’s next?
Currently, this workstation has automatic local snapshots, but needs off-site
backups implemented. Previously I have used borgmatic to manage borgbackup
repositories. I have one repository on a local NAS server and another hosted
at borgbase.com. I think it would be more ideal to use zfs send
because it’s
much faster, especially for restoring from backup.
My laptop, still runs the old Gentoo configuration I shared between this workstation and the laptop. It seldom compiled packages locally because I bulk-built packages on my workstation. Now I’ll have to build packages on my laptop until I migrate. Maybe I’ll do the migration this week… no time like the present!
I need to learn a lot more about Nix and NixOS. I don’t know how profiles, flakes, or much of the internals work. I just know how to read source code and have some prior knowledge of how this system generally works (after all it’s just a Linux system with a lot of special sauce on top).
The ultimate test will be to do like I do on Gentoo; locally modify a system package without much fuss. Nobody is the wiser. Stay tuned for content about packaging software on Nix.
§Final Remarks
(XKCD, CC BY-NC 2.5)
Dear reader, if you wish to give NixOS a try, I strongly recommend ignoring much
of the advice you get from experienced NixOS users. You may find them giving
you good recommendations, but they are usually advanced stuff, like using
home-manager, flakes, profiles, etc. All you really need to know is how to
add stuff to /etc/nixos/configuration.nix
and how to read error traces. Once
you get a working NixOS system and you’re productive on it, I recommend
exploring these more advanced topics, like I am doing in the near future.
I’m looking forward to streamlining more of my day-to-day computing. Gentoo was fun, but I need to re-invest some of the time and energy into other endeavors. One place where I am looking to continue to use Gentoo is specialty hardware and Retro-computing. Some distros have been bumping their minimum x86 supported CPU recently. This is starting to impact my Thinkpad x31 hobby laptop.
I have a challenge for the reader, try installing NixOS in a VM. Maybe deploy a website with it. See what you think for yourself. Maybe it’s not for you, but it’s important to be aware of these growths in computing.
§Thanks
Thank you to bard and haavard for copy editing this post. It was chock-full of typos!
-
Yes, Arch packages are fairly easy to write, but maintaining (system packages) over time is like pulling teeth at best ↩︎
-
There are two repositories with more unique packages than nixpkgs stable 22.05: nixpkgs unstable and AUR. The graph does not include unstable/testing or low quality repositories (AUR). ↩︎
-
Friendly callout to Alpine Linux and Ubuntu for shipping ZFS ootb! ↩︎
-
With the exception of reading ebuilds and eclasses, those aren’t so bad; I’m referring to the Portage Python sources. ↩︎