[PATCH v2 09/10] nixosfi: add nixosfi-test target for operator-edited per-VM flakes

Daniel Gomez <[email protected]> Fri, 12 Jun 2026 12:55:21 +0200
Newsgroups dev.linux.lists.kdevops
Message-ID <[email protected]>
From: Daniel Gomez <[email protected]>

The existing nixosfi-rebuild-test path regenerates each per-VM
flake.nix and default.nix from kdevops Jinja templates before
nix-building and activating the closure on the running guest. When
an operator hand-edits nixos/generated/<vm>/{flake,default}.nix to
add a dev-local flake input or an extra environment.systemPackages
entry that the templates do not emit, rebuild-test wipes those edits
on every invocation.

Add a sibling activation path -- nixosfi-test -- that keeps the
per-VM flake as it is on disk and only runs the build +
switch-to-configuration test steps. refresh-source-overrides.yml
still runs so the existing 10 NIXOS_FLAKE_OVERRIDE_<PKG> source
inputs are re-pinned; operator-added inputs outside that set must
be re-pinned manually via nix flake update before invoking the
target. Naming mirrors nix-rebuild vocabulary: rebuild-test
re-renders kdevops-managed state then activates; test activates
from on-disk state. The Ansible tag is nixosfi-test (not bare
"test") to dodge cross-include tag collisions.

The build + readlink + activate sequence is intentionally
duplicated from rebuild-test.yml rather than factored into a shared
helper because both paths are validated independently and a shared
file would add refactor risk without a third consumer.

Signed-off-by: Daniel Gomez <[email protected]>
---
 playbooks/nixosfi.yml                  | 20 ++++++++++++++++
 playbooks/roles/nixosfi/tasks/test.yml | 44 ++++++++++++++++++++++++++++++++++
 scripts/nixosfi.Makefile               | 10 ++++++++
 3 files changed, 74 insertions(+)

diff --git a/playbooks/nixosfi.yml b/playbooks/nixosfi.yml
index 8f0e1dec..fb75a1c2 100644
--- a/playbooks/nixosfi.yml
+++ b/playbooks/nixosfi.yml
@@ -12,6 +12,16 @@
   vars_files:
     - "{{ playbook_dir }}/../extra_vars.yaml"
   tasks:
+    # Two activation paths, selected by tag. Pick one; running both
+    # together first regenerates then activates from the regenerated
+    # config (rebuild-test behaviour with extra cost).
+    #
+    #   --tags rebuild-test  regenerate per-VM flake from kdevops
+    #                        templates then build + switch-to-config test.
+    #   --tags nixosfi-test  keep per-VM flake on disk; build +
+    #                        switch-to-config test. Use when operator
+    #                        edits to nixos/generated/<vm>/*.nix must
+    #                        survive.
     - name: Include nixosfi rebuild-test tasks
       ansible.builtin.include_role:
         name: nixosfi
@@ -21,3 +31,13 @@
             - rebuild-test
       tags:
         - rebuild-test
+
+    - name: Include nixosfi test (keep-on-disk-flake) tasks
+      ansible.builtin.include_role:
+        name: nixosfi
+        tasks_from: test
+        apply:
+          tags:
+            - nixosfi-test
+      tags:
+        - nixosfi-test
diff --git a/playbooks/roles/nixosfi/tasks/test.yml b/playbooks/roles/nixosfi/tasks/test.yml
new file mode 100644
index 00000000..cda797b0
--- /dev/null
+++ b/playbooks/roles/nixosfi/tasks/test.yml
@@ -0,0 +1,44 @@
+---
+# SPDX-License-Identifier: copyleft-next-0.3.1
+#
+# Build the per-VM flake as it sits on disk and activate it on the
+# guest. Unlike rebuild-test.yml, no re-render from kdevops templates,
+# so operator edits to nixos/generated/<vm>/{flake,default}.nix
+# survive. Hand-added flake inputs outside NIXOS_FLAKE_OVERRIDE_<PKG>
+# must be re-pinned manually before invoking this target:
+#   nix flake update --flake "path:<config_dir>/<vm>" <input-name>
+
+- name: Refresh per-package source-override flake inputs for {{ inventory_hostname }}
+  ansible.builtin.import_tasks: refresh-source-overrides.yml
+  vars:
+    refresh_target_vms: "{{ [inventory_hostname] }}"
+
+- name: Build imageless NixOS toplevel from current per-VM flake for {{ inventory_hostname }}
+  delegate_to: localhost
+  ansible.builtin.command:
+    argv:
+      - nix
+      - build
+      - "path:{{ nixosfi_config_dir }}/{{ inventory_hostname }}#toplevel"
+      - --out-link
+      - "{{ nixosfi_config_dir }}/{{ inventory_hostname }}/result"
+  environment:
+    PATH: "/nix/var/nix/profiles/default/bin:{{ lookup('env', 'PATH') | default('/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin') }}"
+  changed_when: true
+
+- name: Resolve new toplevel store path for {{ inventory_hostname }}
+  delegate_to: localhost
+  ansible.builtin.command:
+    argv:
+      - readlink
+      - -f
+      - "{{ nixosfi_config_dir }}/{{ inventory_hostname }}/result"
+  register: nixosfi_new_toplevel
+  changed_when: false
+
+- name: Activate new toplevel on the running guest {{ inventory_hostname }}
+  ansible.builtin.command:
+    cmd: "{{ nixosfi_new_toplevel.stdout }}/bin/switch-to-configuration test"
+  register: nixosfi_activate_result
+  failed_when: nixosfi_activate_result.rc not in [0, 4, 100]
+  changed_when: nixosfi_activate_result.rc in [0, 4, 100]
diff --git a/scripts/nixosfi.Makefile b/scripts/nixosfi.Makefile
index fc3ef394..9795218b 100644
--- a/scripts/nixosfi.Makefile
+++ b/scripts/nixosfi.Makefile
@@ -112,6 +112,16 @@ nixosfi-rebuild-test: $(KDEVOPS_EXTRA_VARS)
 		--tags rebuild-test \
 		--extra-vars=@./extra_vars.yaml
 
+# nixosfi-test: like nixosfi-rebuild-test but does NOT regenerate the
+# per-VM flake.nix / default.nix from kdevops templates first. Use
+# when hand-edits to nixos/generated/<vm>/*.nix must survive. See
+# playbooks/roles/nixosfi/tasks/test.yml.
+PHONY += nixosfi-test
+nixosfi-test: $(KDEVOPS_EXTRA_VARS)
+	$(Q)ansible-playbook $(KDEVOPS_PLAYBOOKS_DIR)/nixosfi.yml \
+		--tags nixosfi-test \
+		--extra-vars=@./extra_vars.yaml
+
 PHONY += nixosfi-stop
 nixosfi-stop: $(KDEVOPS_EXTRA_VARS)
 	$(Q)ansible-playbook $(KDEVOPS_PLAYBOOKS_DIR)/nixosfi.yml \

-- 
2.54.0