NixOS Pins and Needles
NixOS is a
Linux distribution built
around the nix package manager. Since
writing my first article on NixOS,
it has quickly become my personal OS (Operating System) of choice for desktops,
servers, and NixOS.
For context, you can use a single configuration.nix to build machines which is
super convenient, but my inventory is diverse and has at least
five machines.
Configuring these machines to play nice together ultimately means having to
control complexity — and that’s not always fun.
This is a fairly opinionated and under the hood take on using NixOS, you
shouldn’t take any of this as advice — I’m not a
functional or
LISP
programming expert. Lasciate ogni speranza, voi ch’entrate.
Testing Single Files Quickly
My inventory includes some low end hardware so rebuilding a small
subset of machines is NixOS. This
hacky script of mine
dry builds single files in isolation and foregoes the need of rebuilding entire
configuration sets (with some trade–offs), saving precious time. Before, I’d
usually have to wait for a complete
nixos-rebuild or
nixops
evaluation, only to discover missing imports or other syntax errors. Single
file evaluation allows working on small sections of a larger set of
configurations quickly without rebuilding or activating machines.
nixos-test to quickly evaluate small files and even a
full configuration.GNU Guix (as in geeks) is currently
superior to NixOS in this regard with commands like
guix system
that provide this type of fast inspection. If Guix is interesting to you,
Searching For Packages and Files
In most Linux distributions you can search to see which package owns a file. For
example, on Arch Linux you
would run pacman -Fy <file> to search the
<file> you’re seeking. This is great
when compiling
or writing a program. On NixOS, my preference is to use
nix-locate to find packages and
files. NixOS
package search
and naming conventions can be unintuitive. To ease my pain, the ideas of
this shell script
are adapted for my specific purposes. My
hacky version
sets up an alternate home environment, drops the desired program into a
temporary
nix-shell
with fish and prints some useful paths afterwards.
Running Configurations
NixOS comes with powerful
virtual machine integration testing modules.
I’m a bit more lazy and like to jump directly into a virtual machine from my
shell with QEMU (sometimes called Quick Emulator) to
try out a configuration. The
nixos-generate command
generates different system output formats for a specified configuration. The
ISO
ISO.
shell
$ nixos-generate --format iso --configuration server.nix --option builders ''
$ qemu-system-x86_64 -nographic -enable-kvm -m 1024 -cdrom nixos.isoDRY is Evil Incarnate
The functional ecosystem has taught me that
DRY) is the devil incarnate in my book, especially in a functional
programming context. After throwing away my
first directory structure,
I’ve since implemented what may be a kind of naive
stratified design approach to
prevent myself from wasting NixOS modules to work for a purpose
that it was not designed for.
Nixpkgs, the NixOS implementation, lets
you spin up system abstractions in your sleep and while this is good for writing
packages NixOS and Guix is amazing for writing massive amounts of system
packages quickly.
imports and does something useful is a basic
knowledge piece. Every singular piece of knowledge is saved, and whether used or
not can be combined to create higher functionality independent of itself. These
knowledge pieces should be easy to change, check, and reuse. For that simple
reason, duplication is good, really really good, and as a bonus — sharing
working snippets with others is easier.
Modules Should Do One Thing
Or rather — it seems useful to distinguish between lower level modules
(modules containing systemd
modules only) and higher level modules (modules containing multiple modules).
The high level interfacing inside a module is often too hard to pick apart.
Situations can arise where you try to do something with a module that is out of
its scope. The reality is that a lot of NixOS modules have two or more smaller
modules struggling to get out. Sometimes you’ll have to reduce upstream modules
down to its basics to get what you want done — this can be painful.
My use cases typically require running multiple copies of the same module. The
easiest way to achieve this is to keep custom made modules as small as possible
(just a systemd service and basic configuration). It’s impossible to satisfy
all use cases so everything else ends up as a test case of combinations that can
be used as higher level modules. “Lower” level modules are easier to understand
and manipulate too, since they are just an indirect version of plain systemd
files.
lib.attrsets.mapAttrs' function is great for duplicating systemd
services.Separate System and User Environments
As a user, if I can avoid a nixos-rebuild, nixops deploy,
sudo, or
doas — sign me up. My user
packages are split from the system side with
config.nix
abuse and
nix-env. The
nix community’s
home manager is nice,
but avoided because the less dependencies the better when connecting my home
folder to another Linux distribution. My programming style incorporates the
Unix shell mostly as an
integrated development environment
(IDE), so my
dotfiles and
default presets for linting and such are all that’s necessary. The new
nix flake
feature puts in the guard rails for proper reproducibility, but I don’t think
I’ll be using that for some time.
Imperative to Declarative User Experience
Once in a while I’ll take a look at the discussions on social media around the
NixOS, Guix, and functional operating system community. Recent discussions
seem to center around
the lack of popularity of NixOS and GNU Guix
and their complexity.
The unpopularity of NixOS and GNU Guix sounds about right. Functional Linux
distributions will not become generally popular anytime
I’m fairly certain that nixos-generate-config is the command with the best
user experience (UX) on the whole system — everything else is pins and
needles. NixOS needs more imperative to declarative
NixOS install procedure achieves this workflow with
two separate commands. The first command generates a declarative configuration
and the second implements it. If distribution installation speed runs were a
thing, then NixOS would have a leg up especially if a configuration.nix is
baked into a custom installer with automatic formatting options like
fileSystems.<name>.autoFormat.
shell
$ nixos-generate-config --root /mnt
$ nixos-installIn my experience, a casual NixOS user doesn’t even want to open the
configuration.nix to manually write stuff and connect modules together unless
absolutely necessary. In their world — configuration blocks should simply be
“generated” so that they can just copy and paste things.
“Why can’t it just generate the configs like it did to install my system?”
A casual user
Docker,
Kubernetes,
Tailwind, and
React all have the same
NixOS and GNU Guix are not
there yet. Searching the
NixOS options index to build up
configuration is almost like starting from scratch in a docker compose.
Conclusion
Future iterations of NixOS, Guix and its derivatives can only get more
robust.
If easily composed and shared declarative tooling (infrastructure as code)
becomes the dominant paradigm in user space, then issues in the Linux desktop
space can be resolved elegantly by lassoing the majority of desktop
configuration types and quirks. The
NixOS hardware quirks repository is
brilliant.
Interestingly enough, the nixpkgs repository is a very
Finally, The nix language is just great in my opinion, without functions, it
reads like JSON (JavaScript Object
Notation) with comments minus the dangling comma shenanigans. It can be
used as metadata
or as a language. If I could ever manage to get my NixOS configuration under
control (specifically secrets and metadata), I’d open source it, but for the
time being, I just don’t have the time.