From 3a874dccc8eeed083ca27a8f81e9582f4def38a1 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 10 Jul 2026 09:32:54 +0100 Subject: [PATCH] =?UTF-8?q?test(install):=20#72=20installer=20=E2=86=94=20?= =?UTF-8?q?template=20SoT=20parity=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guard that nomarchy-install share/template ships byte-identical flake.nix/system.nix/home.nix/theme-state.json to templates/downstream (the single machine-seed SoT; default.nix copy list). - tools/check-template-sot.py — share vs SoT file-set + content - checks.template-sot — pure python + package path (theme-contrast pattern) - BACKLOG #72 removed; JOURNAL entry Verified (V0): python3 tools/check-template-sot.py templates/downstream nix build .#checks.x86_64-linux.template-sot --- flake.nix | 13 +++++ tools/check-template-sot.py | 96 +++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100755 tools/check-template-sot.py diff --git a/flake.nix b/flake.nix index 3c8a7d9..7747322 100644 --- a/flake.nix +++ b/flake.nix @@ -294,6 +294,19 @@ touch $out ''; + # Installer share ↔ templates/downstream SoT (item 72): the four + # files default.nix copies into share/.../template/ must stay + # byte-identical to the checkout template (single machine-seed + # SoT; no thinner second catalog). Cheap package build + cmp. + template-sot = pkgs.runCommand "nomarchy-template-sot" + { nativeBuildInputs = [ pkgs.python3 ]; } + '' + python3 ${./tools/check-template-sot.py} \ + ${nomarchyInstall}/share/nomarchy-install/template \ + ${./templates/downstream} + touch $out + ''; + # The installer's disko arg contract (item 49): swapSize "0"/"0G" # (bare "0" is what the swap=0 install passes) must yield NO @swap # subvolume, a sized value a correctly-sized one, and withLuks must diff --git a/tools/check-template-sot.py b/tools/check-template-sot.py new file mode 100755 index 0000000..104d79f --- /dev/null +++ b/tools/check-template-sot.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Installer ↔ templates/downstream SoT parity (flake check `template-sot`). + +Usage: + check-template-sot.py + +Asserts the files nomarchy-install ships under +$out/share/nomarchy-install/template/ are byte-identical to the same +names in templates/downstream — the single source of truth for machine +flake seed files (see pkgs/nomarchy-install/default.nix installPhase). + +The package copies a fixed file list (not a recursive tree copy): + + flake.nix system.nix home.nix theme-state.json + +README.md and hardware-configuration.nix stay repo-only (docs / +nixos-generate-config at install time) and are intentionally absent +from the share. + +Wired as `checks.template-sot` in flake.nix; also runnable after a local +package build: + + share=$(nix build --no-link --print-out-paths .#nomarchy-install) + python3 tools/check-template-sot.py \ + "$share/share/nomarchy-install/template" templates/downstream +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +# Must match the cp list in pkgs/nomarchy-install/default.nix installPhase. +SHIPPED = ("flake.nix", "system.nix", "home.nix", "theme-state.json") + + +def fail(msg: str) -> None: + print(f"template-sot: {msg}", file=sys.stderr) + sys.exit(1) + + +def main() -> int: + if len(sys.argv) != 3: + print( + f"usage: {sys.argv[0]} ", + file=sys.stderr, + ) + return 2 + + share = Path(sys.argv[1]) + sot = Path(sys.argv[2]) + + if not share.is_dir(): + fail(f"install share template dir missing: {share}") + if not sot.is_dir(): + fail(f"templates/downstream dir missing: {sot}") + + share_names = sorted(p.name for p in share.iterdir() if p.is_file()) + expected = sorted(SHIPPED) + + if share_names != expected: + fail( + "install share template file set drifted from default.nix copy list:\n" + f" share: {share_names}\n" + f" expected: {expected}\n" + " (update SHIPPED here and the cp in pkgs/nomarchy-install/default.nix together)" + ) + + mismatches = [] + for name in SHIPPED: + src = sot / name + dst = share / name + if not src.is_file(): + fail(f"SoT missing shipped file: {src}") + a = src.read_bytes() + b = dst.read_bytes() + if a != b: + mismatches.append( + f"{name}: share ({len(b)} bytes) != SoT ({len(a)} bytes)" + ) + + if mismatches: + print("template-sot: content mismatch(es):", file=sys.stderr) + for m in mismatches: + print(f" {m}", file=sys.stderr) + return 1 + + print( + f"template-sot: {len(SHIPPED)} files match " + f"{sot} ↔ {share}" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main())