Epistemic Status: Blooming — This isn’t quite fully polished. I need to clean up the example and provide others. It’s related to NixOS Modules as an Expert System.
For a while now I’ve started to use things like shell aliases and scripts, small CLI tools, and so forth as a way to distill knowledge into usable artifacts that not only help do the thing they were written for but help to document the thing they were written for. Whether I use them enough to justify them becomes somewhat irrelevant because they’re documented in the code if I forget them.1
I find I try to do this all the time especially ever since I started using NixOS and Home Manager to codify my configurations. I’ve also started using devshell
2 to include useful tooling in project level flake.nix
files even if they’re simple wrappers around whatever useful commands I use in that project3. Its message of the day feature is perfect for reminding myself of things when I enter the directory and it prints out (because direnv
+ nix-direnv). Things that I’ll easily forget when enough time passes.
A concrete example is in how I configure Git using Home Manager. I recently learned about the !
in Git Aliases for running arbitrary scripts. I needed it because I wanted an alias for searching Git history and it needed to take arguments in arbitrary places which regular aliases can’t do. I read about a pattern of using it to define a function and then calling it, so that you can easily string together multiple things. I’m certainly not using that part of it yet but it’s there when I need it.
Basically you can do something like this:
That will take git foo
and run command1
followed by command2
. You can use this for places where you want an alias to take arguments. At a certain point it makes more sense to make a script that takes advantage of Git’s subcommand pluggable interface, but that’s sometimes overkill.
To “document” that pattern, I initially wrote a Nix function in my Home Manager configuration file like this4 :
Not quite ready for prime time...
I realized the other day that this code doesn’t actually work for multiple lines of script. It’s too naïve to work.
I have a better solution involving adding an option to create Git subcommands, i.e. scripts that are named
git-<name>
and thus get treated by Git as subcommands. I’ll update this once I have the new version of the module hammered out.
Then, when I want to use it down in my configuration, I can just do this:
Ideally this would be codified into the programs.git
options.
That way I don’t have to think about it, and I can encode the type restrictions in the option type. Module options are the one place where NixOS does a good job of providing typing tools.
I wasn’t sure if this was possible, to modify existing options like this, but I figured I’d give it a try. Here’s the Home Manager module I came up with to do this. It’s probably not in the best shape it could be in, but it illustrates the idea.
The advantage of this approach is that I don’t have to repeat that pattern everywhere, or remember what it’s for, or where I got it from.
This idea comes up often in my various configuration files where I document why I changed a setting, or especially with NixOS, how a collection of settings is interrelated to a problem I’m solving, such as configuring a Windows VM with PCI Passthrough for gaming, or the various settings needed to access the VPN and office networks at work, or settings related to a ZFS setup that resets the root partition on boot (see Erase your darlings5). Often times that leads to a new module that I pull out and abstract from the configuration.
Footnotes
-
I find arguments about portability somewhat moot. This is the “what if you’re logged into a system that doesn’t have your aliases?” argument. If it’s easier for me to remember short aliases than a long series of command arguments, then I’d rather have that documented than try to remember the underlying command. Especially when you’re someone like me with poor working memory, the less I need to actively remember, the better. I can look it up on my local machine if I don’t have it remotely, or better, just use my home manager configs on the remote system.
-
I don’t usually use the
devshell.toml
support, but put it all directly into the Nix code inflake.nix
. If I were working with other people less experienced with Nix, I might use it, but even then I’d probably just try to nudge them towards the underlying Nix. -
One nice version of this is that I’ve written a script that goes through the steps I take to configure a new computer with NixOS, and included it in the system configuration flake so that I can just do a
nix run flake-name#new-nixos-setup
and run through it on a new system, even without checking out the repo as long as I have network access to it. I do it so infrequently, but when I do, having that makes it much easier. -
This obviously has some edge cases that I haven’t tested or tried, e.g. what happens if I give it a single line without a terminating newline? It should probably make sure the string ends with a semicolon and add one if not.
-
I have big caveats for that method that I intend to document at some point. I ran into a lot of small gotchas along the way the first time I tried to set this up. Yet another reason I wrote a script for the process, so I wouldn’t have to remember.