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.iso
DRY 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-install
In 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.