nixa: Toward General-Purpose NixOS Config Management

My NixOS kubernetes manager nk3 has been excellent over the last year. Effortless config changes and upgrades with no downtime. So excellent however, that I’ve found myself wanting a similar workflow for managing everything else that might be running NixOS - for example, my routers.

Introducing nixa; a general-purpose config management tool with the aim of providing an ansible-like workflow for NixOS servers - without the downsides of ansible.


The basic goal of nixa is to generate and apply configuration.nix to many hosts as defined by an inventory.yaml, and to perform nixos-native OS upgrades.

Like in ansible, inventory.yaml defines groups, host membership, and templates to apply to the groups:

---

routers:
  hosts:
    - 10.38.154.98
    - 10.38.154.87
    - 10.38.154.160
  templates:
    - lxc.nix
  nix-channel: 24.11

We will not experience the primary drawback of ansible (side effects) - since configuration.nix is generated and applied on each execution, if we remove a package or service from our templates, it Will be actually removed from the target hosts. (If you remove, say a firewall rule from an ansible role - ansible will not actually remove that rule, it simply forgets about it).


As of today, nixa only knows how to apply a single config template:

nixa > just
black .
All done! ✨ 🍰 ✨
4 files left unchanged.
flake8 . --ignore=E501,W503
nix-shell . --run 'python3 main.py'
d14d90c2-4e69-5cb0-ac9d-e1ca0ce71c19 is reachable
0a6dd2c3-bc17-59c5-859b-b9c9921b7961 is reachable
86da48cb-c4ad-548b-9859-dfd7a3ee1d87 is reachable
applying template lxc.nix to routers: ['d14d90c2-4e69-5cb0-ac9d-e1ca0ce71c19', '0a6dd2c3-bc17-59c5-859b-b9c9921b7961', '86da48cb-c4ad-548b-9859-dfd7a3ee1d87']
d14d90c2-4e69-5cb0-ac9d-e1ca0ce71c19 modified:
---

+++

@@ -8,7 +8,6 @@

   networking.hostName = "d14d90c2-4e69-5cb0-ac9d-e1ca0ce71c19";

   environment.systemPackages = with pkgs; [
-    htop
     busybox
   ];

Rebuilding NixOS on d14d90c2-4e69-5cb0-ac9d-e1ca0ce71c19
Rebooting d14d90c2-4e69-5cb0-ac9d-e1ca0ce71c19
d14d90c2-4e69-5cb0-ac9d-e1ca0ce71c19 is reachable
0a6dd2c3-bc17-59c5-859b-b9c9921b7961 modified:
---

+++

@@ -8,7 +8,6 @@

   networking.hostName = "0a6dd2c3-bc17-59c5-859b-b9c9921b7961";

   environment.systemPackages = with pkgs; [
-    htop
     busybox
   ];

Rebuilding NixOS on 0a6dd2c3-bc17-59c5-859b-b9c9921b7961
Rebooting 0a6dd2c3-bc17-59c5-859b-b9c9921b7961
0a6dd2c3-bc17-59c5-859b-b9c9921b7961 is reachable
86da48cb-c4ad-548b-9859-dfd7a3ee1d87 modified:
---

+++

@@ -8,7 +8,6 @@

   networking.hostName = "86da48cb-c4ad-548b-9859-dfd7a3ee1d87";

   environment.systemPackages = with pkgs; [
-    htop
     busybox
   ];

Rebuilding NixOS on 86da48cb-c4ad-548b-9859-dfd7a3ee1d87
Rebooting 86da48cb-c4ad-548b-9859-dfd7a3ee1d87
86da48cb-c4ad-548b-9859-dfd7a3ee1d87 is reachable

Rolling OS upgrades are done using --upgrade. This effectively just does nixos-rebuild boot --upgrade and reboots:

nixa > just upgrade
black .
All done! ✨ 🍰 ✨
4 files left unchanged.
flake8 . --ignore=E501,W503
nix-shell . --run 'python3 main.py -u'
81df66bb-c238-59ba-923b-1d3360ff4c41 is reachable
b55416a9-f3fc-59d5-b5f8-d40f13e815a1 is reachable
ab984010-325a-57fa-a04c-78fea2d5fbad is reachable
routers:
--> upgrading 81df66bb-c238-59ba-923b-1d3360ff4c41:
    enforcing nixos channel nixos-24.11
    20 paths fetched
    21 derivations built
test:
--> upgrading b55416a9-f3fc-59d5-b5f8-d40f13e815a1:
    enforcing nixos channel nixos-24.11
    4 paths fetched
    21 derivations built
--> upgrading ab984010-325a-57fa-a04c-78fea2d5fbad:
    enforcing nixos channel nixos-24.11
    4 paths fetched
    21 derivations built

Whatever channel nix-channel: is set to in the inventory is enforced. For regular ‘daily’ upgrades, just run --upgrade. For Release upgrades, go increment the channel in the inventory, then run --upgrade.


nixa is a word sandwich of ’nixos apply’ or something like that.

Nathan Hensel

on caving, mountaineering, networking, computing, electronics


2024-12-18