From 544a53ae9c9de2aae5f89c2bc3de5d587aec829a Mon Sep 17 00:00:00 2001 From: clan-bot Date: Mon, 27 Oct 2025 20:01:46 +0000 Subject: [PATCH 01/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 3f2095faa..6969fbe56 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761544814, - "narHash": "sha256-t5f0A+2MtSWTfA6hzMNiotpIMGLlSQF2JnK9m6nkzIY=", + "lastModified": 1761588227, + "narHash": "sha256-Mi6om4///aDbH1Z5pmPia5Qq8NSZ3iN/oYKGIHEEd9I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e5aa45ed6c45058ec109658b2b7352a9a062cdf3", + "rev": "3796dbcc0de8f8ba0e2bbd11ee7805343691ba38", "type": "github" }, "original": { From 4239f4d27f38d023d1d49ffc75988162a184fdd7 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 27 Oct 2025 17:24:45 +0100 Subject: [PATCH 02/39] clan/module: explain throw --- modules/clan/module.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/clan/module.nix b/modules/clan/module.nix index cdd616b40..a3bc09ddc 100644 --- a/modules/clan/module.nix +++ b/modules/clan/module.nix @@ -167,6 +167,9 @@ in { ... }@args: let _class = + # _class was added in https://github.com/NixOS/nixpkgs/pull/395141 + # Clan relies on it to determine which modules to load + # people need to use at least that version of nixpkgs args._class or (throw '' Your version of nixpkgs is incompatible with the latest clan. Please update nixpkgs input to the latest nixos-unstable or nixpkgs-unstable. From a2cec323a23955d161deead8c3c749b6a78016d6 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 27 Oct 2025 17:27:54 +0100 Subject: [PATCH 03/39] modules: move nixos modules into nixosModules folder --- modules/clan/module.nix | 4 ++-- {modules => nixosModules}/machineModules/forName.nix | 1 + {modules => nixosModules}/machineModules/overridePkgs.nix | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename {modules => nixosModules}/machineModules/forName.nix (92%) rename {modules => nixosModules}/machineModules/overridePkgs.nix (100%) diff --git a/modules/clan/module.nix b/modules/clan/module.nix index a3bc09ddc..ab433b68a 100644 --- a/modules/clan/module.nix +++ b/modules/clan/module.nix @@ -100,7 +100,7 @@ let _: machine: machine.extendModules { modules = [ - (lib.modules.importApply ../machineModules/overridePkgs.nix { + (lib.modules.importApply ../../nixosModules/machineModules/overridePkgs.nix { pkgs = pkgsFor.${system}; }) ]; @@ -179,7 +179,7 @@ in in { imports = [ - (lib.modules.importApply ../machineModules/forName.nix { + (lib.modules.importApply ../../nixosModules/machineModules/forName.nix { inherit (config.inventory) meta; inherit name diff --git a/modules/machineModules/forName.nix b/nixosModules/machineModules/forName.nix similarity index 92% rename from modules/machineModules/forName.nix rename to nixosModules/machineModules/forName.nix index 361f58dfb..a485f2c60 100644 --- a/modules/machineModules/forName.nix +++ b/nixosModules/machineModules/forName.nix @@ -3,6 +3,7 @@ directory, meta, }: +# The following is a nixos/darwin module { _class, lib, diff --git a/modules/machineModules/overridePkgs.nix b/nixosModules/machineModules/overridePkgs.nix similarity index 100% rename from modules/machineModules/overridePkgs.nix rename to nixosModules/machineModules/overridePkgs.nix From 9503b46b2191714cb69799eb8f7e27eede23c56e Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 27 Oct 2025 17:31:26 +0100 Subject: [PATCH 04/39] modules: rename arbitrary interface.nix to 'top-level-interface' --- modules/clan/default.nix | 2 +- modules/clan/{interface.nix => top-level-interface.nix} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename modules/clan/{interface.nix => top-level-interface.nix} (100%) diff --git a/modules/clan/default.nix b/modules/clan/default.nix index af742cbcc..8a55c44a0 100644 --- a/modules/clan/default.nix +++ b/modules/clan/default.nix @@ -6,7 +6,7 @@ inherit (clan-core) clanLib; }; imports = [ + ./top-level-interface.nix ./module.nix - ./interface.nix ]; } diff --git a/modules/clan/interface.nix b/modules/clan/top-level-interface.nix similarity index 100% rename from modules/clan/interface.nix rename to modules/clan/top-level-interface.nix From 3e950bc66fcc05ad3a8660d60dac04925d19314b Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 27 Oct 2025 17:38:34 +0100 Subject: [PATCH 05/39] docs: add doc-comment for template submodule --- modules/clan/templates.nix | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/modules/clan/templates.nix b/modules/clan/templates.nix index 91042f821..84c24791b 100644 --- a/modules/clan/templates.nix +++ b/modules/clan/templates.nix @@ -1,3 +1,28 @@ +/** + The templates submodule + + 'clan.templates' + + Different kinds supported: + + - clan templates: 'clan.templates.clan' + - disko templates: 'clan.templates.disko' + - machine templates: 'clan.templates.machine' + + A template has the form: + + ```nix + { + description: string; # short summary what the template contains + path: path; # path to the template + } + ``` + + The clan API copies the template from the given 'path' + into a target folder. For example, + + `./machines/` for 'machine' templates. +*/ { lib, ... From b3323007b213f2dc29443e9fe00fd3dca00c1bab Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 27 Oct 2025 17:44:02 +0100 Subject: [PATCH 06/39] test: update test filesets --- clanServices/syncthing/flake-module.nix | 1 + clanServices/zerotier/flake-module.nix | 1 + lib/inventory/distributed-service/flake-module.nix | 1 + 3 files changed, 3 insertions(+) diff --git a/clanServices/syncthing/flake-module.nix b/clanServices/syncthing/flake-module.nix index 63368048d..64afbda26 100644 --- a/clanServices/syncthing/flake-module.nix +++ b/clanServices/syncthing/flake-module.nix @@ -22,6 +22,7 @@ in ../../clanServices/syncthing # Required modules ../../nixosModules/clanCore + ../../nixosModules/machineModules # Dependencies like clan-cli ../../pkgs/clan-cli ]; diff --git a/clanServices/zerotier/flake-module.nix b/clanServices/zerotier/flake-module.nix index 78933d589..49b36f770 100644 --- a/clanServices/zerotier/flake-module.nix +++ b/clanServices/zerotier/flake-module.nix @@ -21,6 +21,7 @@ in ../../clanServices/zerotier # Required modules ../../nixosModules/clanCore + ../../nixosModules/machineModules # Dependencies like clan-cli ../../pkgs/clan-cli ]; diff --git a/lib/inventory/distributed-service/flake-module.nix b/lib/inventory/distributed-service/flake-module.nix index 621e069f7..8cddb90ba 100644 --- a/lib/inventory/distributed-service/flake-module.nix +++ b/lib/inventory/distributed-service/flake-module.nix @@ -21,6 +21,7 @@ in ../../../flakeModules ../../../lib ../../../nixosModules/clanCore + ../../../nixosModules/machineModules ../../../machines ../../../inventory.json ../../../modules From 0060ead876922b2cfecf53f38805d9a702d16878 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 28 Oct 2025 09:40:31 +0100 Subject: [PATCH 07/39] clan/checks: move into lib function; add tests --- flakeModules/clan.nix | 11 ++--------- lib/clan/checkConfig.nix | 19 +++++++++++++++++++ lib/clan/default.nix | 35 +++++++++++++++++++---------------- lib/default.nix | 2 ++ lib/tests.nix | 30 ++++++++++++++++++++++++++++++ modules/clan/default.nix | 11 +++++++++++ 6 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 lib/clan/checkConfig.nix diff --git a/flakeModules/clan.nix b/flakeModules/clan.nix index 76eb2177b..bb9d402da 100644 --- a/flakeModules/clan.nix +++ b/flakeModules/clan.nix @@ -56,15 +56,8 @@ in } ]; }; - apply = - config: - lib.deepSeq (lib.mapAttrs ( - id: check: - if check.ignore || check.assertion then - null - else - throw "clan.checks.${id} failed with message\n${check.message}" - ) config.checks) config; + # Important: !This logic needs to be kept in sync with lib.clan function! + apply = config: clan-core.lib.checkConfig config.checks config; }; # Mapped flake toplevel outputs diff --git a/lib/clan/checkConfig.nix b/lib/clan/checkConfig.nix new file mode 100644 index 000000000..3256f7945 --- /dev/null +++ b/lib/clan/checkConfig.nix @@ -0,0 +1,19 @@ +{ lib, ... }: +/** + Function to assert clan configuration checks. + + Arguments: + + - 'checks' attribute of clan configuration + - Any: the returned configuration (can be anything, is just passed through) +*/ +checks: +lib.deepSeq ( + lib.mapAttrs ( + id: check: + if check.ignore || check.assertion then + null + else + throw "clan.checks.${id} failed with message\n${check.message}" + ) checks +) diff --git a/lib/clan/default.nix b/lib/clan/default.nix index bdd41c88b..c8cd72a6b 100644 --- a/lib/clan/default.nix +++ b/lib/clan/default.nix @@ -33,20 +33,23 @@ let nixpkgs = self.inputs.nixpkgs or clan-core.inputs.nixpkgs; nix-darwin = self.inputs.nix-darwin or clan-core.inputs.nix-darwin; + configuration = ( + lib.evalModules { + class = "clan"; + specialArgs = { + inherit + self + ; + inherit + nixpkgs + nix-darwin + ; + }; + modules = [ + clan-core.modules.clan.default + m + ]; + } + ); in -lib.evalModules { - class = "clan"; - specialArgs = { - inherit - self - ; - inherit - nixpkgs - nix-darwin - ; - }; - modules = [ - clan-core.modules.clan.default - m - ]; -} +clan-core.clanLib.checkConfig configuration.config.checks configuration diff --git a/lib/default.nix b/lib/default.nix index 672b08515..41b21871e 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -16,6 +16,8 @@ lib.fix ( */ callLib = file: args: import file ({ inherit lib clanLib; } // args); + checkConfig = clanLib.callLib ./clan/checkConfig.nix { }; + evalService = clanLib.callLib ./evalService.nix { }; # ------------------------------------ # ClanLib functions diff --git a/lib/tests.nix b/lib/tests.nix index 74afb8238..a087038a8 100644 --- a/lib/tests.nix +++ b/lib/tests.nix @@ -212,6 +212,36 @@ in }; }; + test_clan_check_simple_fail = + let + eval = clan { + checks.constFail = { + assertion = false; + message = "This is a constant failure"; + }; + }; + in + { + result = eval; + expr = eval.config; + expectedError.type = "ThrownError"; + expectedError.msg = "This is a constant failure"; + }; + test_clan_check_simple_pass = + let + eval = clan { + checks.constFail = { + assertion = true; + message = "This is a constant success"; + }; + }; + in + { + result = eval; + expr = lib.seq eval.config 42; + expected = 42; + }; + test_get_var_machine = let varsLib = import ./vars.nix { }; diff --git a/modules/clan/default.nix b/modules/clan/default.nix index 8a55c44a0..2c7d59b8e 100644 --- a/modules/clan/default.nix +++ b/modules/clan/default.nix @@ -1,3 +1,14 @@ +/** + Root 'clan' Module + + Defines lib.clan and flake-parts.clan options + and all common logic for the 'clan' module. + + - has Class _class = "clan" + + - _module.args.clan-core: reference to clan-core flake + - _module.args.clanLib: reference to lib.clan function +*/ { clan-core }: { _class = "clan"; From 591e53e9be337406224ae265594c40f8aff94b0c Mon Sep 17 00:00:00 2001 From: clan-bot Date: Tue, 28 Oct 2025 10:01:54 +0000 Subject: [PATCH 08/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 6969fbe56..f4e566819 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761588227, - "narHash": "sha256-Mi6om4///aDbH1Z5pmPia5Qq8NSZ3iN/oYKGIHEEd9I=", + "lastModified": 1761631514, + "narHash": "sha256-VsXz+2W4DFBozzppbF9SXD9pNcv17Z+c/lYXvPJi/eI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3796dbcc0de8f8ba0e2bbd11ee7805343691ba38", + "rev": "a0b0d4b52b5f375658ca8371dc49bff171dbda91", "type": "github" }, "original": { From b7508b2b43c5d8de95e997eda2b043c6a9b17361 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 28 Oct 2025 11:25:20 +0100 Subject: [PATCH 09/39] clan/checks: fix clanLib not checking --- flakeModules/clan.nix | 15 --------------- lib/dir_test.nix | 14 ++++++++++++-- modules/clan/checks.nix | 16 ++++++++++++++++ modules/clan/default.nix | 1 + 4 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 modules/clan/checks.nix diff --git a/flakeModules/clan.nix b/flakeModules/clan.nix index bb9d402da..ec45927b7 100644 --- a/flakeModules/clan.nix +++ b/flakeModules/clan.nix @@ -39,21 +39,6 @@ in }; modules = [ clan-core.modules.clan.default - { - checks.minNixpkgsVersion = { - assertion = lib.versionAtLeast nixpkgs.lib.version "25.11"; - message = '' - Nixpkgs version: ${nixpkgs.lib.version} is incompatible with clan-core. (>= 25.11 is recommended) - --- - Your version of 'nixpkgs' seems too old for clan-core. - Please read: https://docs.clan.lol/guides/nixpkgs-flake-input - - You can ignore this check by setting: - clan.checks.minNixpkgsVersion.ignore = true; - --- - ''; - }; - } ]; }; # Important: !This logic needs to be kept in sync with lib.clan function! diff --git a/lib/dir_test.nix b/lib/dir_test.nix index 1c35a8989..3be34f6f5 100644 --- a/lib/dir_test.nix +++ b/lib/dir_test.nix @@ -53,7 +53,12 @@ in }; }; }).clan - { config.directory = rootPath; }; + { + directory = rootPath; + self = { + inputs.nixpkgs.lib.version = "25.11"; + }; + }; in { inherit vclan; @@ -94,7 +99,12 @@ in }; }; }).clan - { config.directory = rootPath; }; + { + directory = rootPath; + self = { + inputs.nixpkgs.lib.version = "25.11"; + }; + }; in { inherit vclan; diff --git a/modules/clan/checks.nix b/modules/clan/checks.nix new file mode 100644 index 000000000..55f7879e5 --- /dev/null +++ b/modules/clan/checks.nix @@ -0,0 +1,16 @@ +{ lib, nixpkgs, ... }: +{ + checks.minNixpkgsVersion = { + assertion = lib.versionAtLeast nixpkgs.lib.version "25.11"; + message = '' + Nixpkgs version: ${nixpkgs.lib.version} is incompatible with clan-core. (>= 25.11 is recommended) + --- + Your version of 'nixpkgs' seems too old for clan-core. + Please read: https://docs.clan.lol/guides/nixpkgs-flake-input + + You can ignore this check by setting: + clan.checks.minNixpkgsVersion.ignore = true; + --- + ''; + }; +} diff --git a/modules/clan/default.nix b/modules/clan/default.nix index 2c7d59b8e..f35ca8b2e 100644 --- a/modules/clan/default.nix +++ b/modules/clan/default.nix @@ -19,5 +19,6 @@ imports = [ ./top-level-interface.nix ./module.nix + ./checks.nix ]; } From 33a868acc25bf824662cfee9e6e62c85c599de29 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Wed, 29 Oct 2025 00:03:27 +0000 Subject: [PATCH 10/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index f4e566819..7099f5a58 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761631514, - "narHash": "sha256-VsXz+2W4DFBozzppbF9SXD9pNcv17Z+c/lYXvPJi/eI=", + "lastModified": 1761676996, + "narHash": "sha256-mAB2hKwu+6ufnxdNJganMbPbfhTYzJGAWnfcC2JLEeQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a0b0d4b52b5f375658ca8371dc49bff171dbda91", + "rev": "7f2539ca08e04c9bd337c00a80fefec5bd146b29", "type": "github" }, "original": { From d6170e5efbc0a1fd6e9232d9692e40dcd6bb6abb Mon Sep 17 00:00:00 2001 From: clan-bot Date: Wed, 29 Oct 2025 10:01:53 +0000 Subject: [PATCH 11/39] Update nuschtos in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 7099f5a58..219573ef6 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -128,11 +128,11 @@ ] }, "locked": { - "lastModified": 1760652422, - "narHash": "sha256-C88Pgz38QIl9JxQceexqL2G7sw9vodHWx1Uaq+NRJrw=", + "lastModified": 1761730856, + "narHash": "sha256-t1i5p/vSWwueZSC0Z2BImxx3BjoUDNKyC2mk24krcMY=", "owner": "NuschtOS", "repo": "search", - "rev": "3ebeebe8b6a49dfb11f771f761e0310f7c48d726", + "rev": "e29de6db0cb3182e9aee75a3b1fd1919d995d85b", "type": "github" }, "original": { From 995b7cf50ddbfc8e88e3c93fbbd5040c360d8f3c Mon Sep 17 00:00:00 2001 From: clan-bot Date: Wed, 29 Oct 2025 15:01:49 +0000 Subject: [PATCH 12/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 219573ef6..f2b772494 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761676996, - "narHash": "sha256-mAB2hKwu+6ufnxdNJganMbPbfhTYzJGAWnfcC2JLEeQ=", + "lastModified": 1761720681, + "narHash": "sha256-goSYTA/8PyE26HrXi+9EVUOSI5cAMXh0krPz2HrqqdI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7f2539ca08e04c9bd337c00a80fefec5bd146b29", + "rev": "6bc0e28ddaa0ea7c9686031a41da1893370e1195", "type": "github" }, "original": { From 999d709350242deb1bf0ad335560f172e9219311 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Wed, 29 Oct 2025 20:01:48 +0000 Subject: [PATCH 13/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index f2b772494..9d5d7e1ef 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761720681, - "narHash": "sha256-goSYTA/8PyE26HrXi+9EVUOSI5cAMXh0krPz2HrqqdI=", + "lastModified": 1761748483, + "narHash": "sha256-v7fttCB5lJ22Ok7+N7ZbLhDeM89QIz9YWtQP4XN7xgA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6bc0e28ddaa0ea7c9686031a41da1893370e1195", + "rev": "061c55856b29b8b9360e14231a0986c7f85f1130", "type": "github" }, "original": { From eb08803e2ab89d5bd1ada52515bfc0ecfc640cf8 Mon Sep 17 00:00:00 2001 From: hsjobeki Date: Thu, 30 Oct 2025 08:29:43 +0000 Subject: [PATCH 14/39] revert bfb30251e6a86d25ff9e91a46c053c8106028885 revert lib: replace uniqueStrings after upstreamed TODO: Reapply after 25.11 release --- clanServices/sshd/default.nix | 3 ++- clanServices/zerotier/default.nix | 5 ++++- lib/inventory/distributed-service/service-module.nix | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/clanServices/sshd/default.nix b/clanServices/sshd/default.nix index 1c511e0fc..e49cd3bc8 100644 --- a/clanServices/sshd/default.nix +++ b/clanServices/sshd/default.nix @@ -39,6 +39,7 @@ ... }: let + uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list); # Collect searchDomains from all servers in this instance allServerSearchDomains = lib.flatten ( lib.mapAttrsToList (_name: machineConfig: machineConfig.settings.certificate.searchDomains or [ ]) ( @@ -46,7 +47,7 @@ ) ); # Merge client's searchDomains with all servers' searchDomains - searchDomains = lib.uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains); + searchDomains = uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains); in { clan.core.vars.generators.openssh-ca = lib.mkIf (searchDomains != [ ]) { diff --git a/clanServices/zerotier/default.nix b/clanServices/zerotier/default.nix index a74c8a41f..770023f4b 100644 --- a/clanServices/zerotier/default.nix +++ b/clanServices/zerotier/default.nix @@ -140,6 +140,9 @@ pkgs, ... }: + let + uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list); + in { imports = [ (import ./shared.nix { @@ -156,7 +159,7 @@ config = { systemd.services.zerotier-inventory-autoaccept = let - machines = lib.uniqueStrings ( + machines = uniqueStrings ( (lib.optionals (roles ? moon) (lib.attrNames roles.moon.machines)) ++ (lib.optionals (roles ? controller) (lib.attrNames roles.controller.machines)) ++ (lib.optionals (roles ? peer) (lib.attrNames roles.peer.machines)) diff --git a/lib/inventory/distributed-service/service-module.nix b/lib/inventory/distributed-service/service-module.nix index 06ee03330..63e3f1dce 100644 --- a/lib/inventory/distributed-service/service-module.nix +++ b/lib/inventory/distributed-service/service-module.nix @@ -7,10 +7,14 @@ ... }: let - inherit (lib) mkOption types uniqueStrings; + inherit (lib) mkOption types; inherit (types) attrsWith submoduleWith; errorContext = "Error context: ${lib.concatStringsSep "." _ctx}"; + # TODO: + # Remove once this gets merged upstream; performs in O(n*log(n) instead of O(n^2)) + # https://github.com/NixOS/nixpkgs/pull/355616/files + uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list); /** Merges the role- and machine-settings using the role interface From c7f65e929f23010e4233385169431c20701694f7 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 30 Oct 2025 10:10:31 +0100 Subject: [PATCH 15/39] inventoryAdapter: replace importedModulesEvaluated by equivalent config --- clanServices/wifi/tests/eval-tests.nix | 4 ++-- .../distributed-service/inventory-adapter.nix | 3 --- lib/inventory/distributed-service/tests/default.nix | 8 ++++---- .../distributed-service/tests/per_instance_args.nix | 12 ++++++------ .../distributed-service/tests/per_machine_args.nix | 8 ++++---- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/clanServices/wifi/tests/eval-tests.nix b/clanServices/wifi/tests/eval-tests.nix index 981c85cc3..8713a6971 100644 --- a/clanServices/wifi/tests/eval-tests.nix +++ b/clanServices/wifi/tests/eval-tests.nix @@ -41,14 +41,14 @@ let # In this case it is 'self-zerotier-redux' # This is usually only used internally, but we can use it to test the evaluation of service module in isolation # evaluatedService = - # testFlake.clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.self-zerotier-redux.config; + # testFlake.clanInternals.inventoryClass.distributedServices.servicesEval.config.mappedServices.self-zerotier-redux.config; in { test_simple = { inherit testFlake; expr = - testFlake.config.clan.clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.self-wifi.config; + testFlake.config.clan.clanInternals.inventoryClass.distributedServices.servicesEval.config.mappedServices.self-wifi.config; expected = 1; # expr = { diff --git a/lib/inventory/distributed-service/inventory-adapter.nix b/lib/inventory/distributed-service/inventory-adapter.nix index 8d82ed97d..7c19997b3 100644 --- a/lib/inventory/distributed-service/inventory-adapter.nix +++ b/lib/inventory/distributed-service/inventory-adapter.nix @@ -155,8 +155,6 @@ } ]; }; - importedModulesEvaluated = servicesEval.config.mappedServices; - in { inherit @@ -165,7 +163,6 @@ # Exposed for testing grouped allMachines - importedModulesEvaluated ; }; } diff --git a/lib/inventory/distributed-service/tests/default.nix b/lib/inventory/distributed-service/tests/default.nix index 6a7beb4db..048b0b14c 100644 --- a/lib/inventory/distributed-service/tests/default.nix +++ b/lib/inventory/distributed-service/tests/default.nix @@ -81,7 +81,7 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = res.importedModulesEvaluated ? "-simple-module"; + expr = res.servicesEval.config.mappedServices ? "-simple-module"; expected = true; inherit res; }; @@ -177,7 +177,7 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.importedModulesEvaluated.self-A.instances; + expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.instances; expected = [ "instance_bar" "instance_foo" @@ -233,7 +233,7 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.importedModulesEvaluated.self-A.result.allMachines; + expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.result.allMachines; expected = [ "jon" "sara" @@ -285,7 +285,7 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.importedModulesEvaluated.self-A.result.allMachines; + expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.result.allMachines; expected = [ "jon" "sara" diff --git a/lib/inventory/distributed-service/tests/per_instance_args.nix b/lib/inventory/distributed-service/tests/per_instance_args.nix index 6795c0ebc..9fe0c0316 100644 --- a/lib/inventory/distributed-service/tests/per_instance_args.nix +++ b/lib/inventory/distributed-service/tests/per_instance_args.nix @@ -107,7 +107,7 @@ in test_per_instance_arguments = { expr = { instanceName = - res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName; + res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName; # settings are specific. # Below we access: @@ -115,11 +115,11 @@ in # roles = peer # machines = jon settings = - res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings; + res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings; machine = - res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine; + res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine; roles = - res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles; + res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles; }; expected = { instanceName = "instance_foo"; @@ -160,9 +160,9 @@ in # TODO: Cannot be tested like this anymore test_per_instance_settings_vendoring = { - x = res.importedModulesEvaluated.self-A; + x = res.servicesEval.config.mappedServices.self-A; expr = - res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings; + res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings; expected = { timeout = "config.thing"; }; diff --git a/lib/inventory/distributed-service/tests/per_machine_args.nix b/lib/inventory/distributed-service/tests/per_machine_args.nix index ac98a9ffe..5907e8bf2 100644 --- a/lib/inventory/distributed-service/tests/per_machine_args.nix +++ b/lib/inventory/distributed-service/tests/per_machine_args.nix @@ -79,7 +79,7 @@ in inherit res; expr = { hasMachineSettings = - res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon + res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon ? settings; # settings are specific. @@ -88,10 +88,10 @@ in # roles = peer # machines = jon specificMachineSettings = - res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings; + res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings; hasRoleSettings = - res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer + res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer ? settings; # settings are specific. @@ -100,7 +100,7 @@ in # roles = peer # machines = * specificRoleSettings = - res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer; + res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer; }; expected = { hasMachineSettings = true; From affb926450cf00a79d12ae4ff45239f4746362c0 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 30 Oct 2025 10:10:55 +0100 Subject: [PATCH 16/39] services: remove duplicate module args --- lib/inventory/distributed-service/all-services-wrapper.nix | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/inventory/distributed-service/all-services-wrapper.nix b/lib/inventory/distributed-service/all-services-wrapper.nix index 7da8d68b6..381f4c555 100644 --- a/lib/inventory/distributed-service/all-services-wrapper.nix +++ b/lib/inventory/distributed-service/all-services-wrapper.nix @@ -37,9 +37,6 @@ in { name, ... }: { _module.args._ctx = [ name ]; - _module.args.clanLib = specialArgs.clanLib; - _module.args.exports = config.exports; - _module.args.directory = directory; } ) From 2e55028a1b12e6d8e8659bee7a3749133af5b4a3 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 30 Oct 2025 11:48:13 +0100 Subject: [PATCH 17/39] services: move into clan submodule --- .../all-services-wrapper.nix | 3 +- .../distributed-service/inventory-adapter.nix | 4 +- modules/clan/default.nix | 1 + modules/clan/distributed-services.nix | 163 ++++++++++++++++++ modules/clan/module.nix | 16 +- modules/inventoryClass/default.nix | 3 - 6 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 modules/clan/distributed-services.nix diff --git a/lib/inventory/distributed-service/all-services-wrapper.nix b/lib/inventory/distributed-service/all-services-wrapper.nix index 381f4c555..65cd40fb4 100644 --- a/lib/inventory/distributed-service/all-services-wrapper.nix +++ b/lib/inventory/distributed-service/all-services-wrapper.nix @@ -28,16 +28,15 @@ in elemType = submoduleWith { class = "clan.service"; specialArgs = { - exports = config.exports; directory = directory; clanLib = specialArgs.clanLib; + exports = config.exports; }; modules = [ ( { name, ... }: { _module.args._ctx = [ name ]; - } ) ./service-module.nix diff --git a/lib/inventory/distributed-service/inventory-adapter.nix b/lib/inventory/distributed-service/inventory-adapter.nix index 7c19997b3..9f4f65d0b 100644 --- a/lib/inventory/distributed-service/inventory-adapter.nix +++ b/lib/inventory/distributed-service/inventory-adapter.nix @@ -159,10 +159,10 @@ { inherit servicesEval - importedModuleWithInstances + allMachines # Exposed for testing grouped - allMachines + importedModuleWithInstances ; }; } diff --git a/modules/clan/default.nix b/modules/clan/default.nix index f35ca8b2e..10392376a 100644 --- a/modules/clan/default.nix +++ b/modules/clan/default.nix @@ -19,6 +19,7 @@ imports = [ ./top-level-interface.nix ./module.nix + ./distributed-services.nix ./checks.nix ]; } diff --git a/modules/clan/distributed-services.nix b/modules/clan/distributed-services.nix new file mode 100644 index 000000000..adcff5cca --- /dev/null +++ b/modules/clan/distributed-services.nix @@ -0,0 +1,163 @@ +{ + lib, + clanLib, + config, + clan-core, + ... +}: +let + inherit (lib) mkOption types; + # Keep a reference to top-level + clanConfig = config; + + inventory = clanConfig.inventory; + flakeInputs = clanConfig.self.inputs; + clanCoreModules = clan-core.clan.modules; + + grouped = lib.foldlAttrs ( + acc: instanceName: instance: + let + inputName = if instance.module.input == null then "" else instance.module.input; + id = inputName + "-" + instance.module.name; + in + acc + // { + ${id} = acc.${id} or [ ] ++ [ + { + inherit instanceName instance; + } + ]; + } + ) { } importedModuleWithInstances; + + importedModuleWithInstances = lib.mapAttrs ( + instanceName: instance: + let + resolvedModule = clanLib.resolveModule { + moduleSpec = instance.module; + inherit flakeInputs clanCoreModules; + }; + + # Every instance includes machines via roles + # :: { client :: ... } + instanceRoles = lib.mapAttrs ( + roleName: role: + let + resolvedMachines = clanLib.inventory.resolveTags { + members = { + # Explicit members + machines = lib.attrNames role.machines; + # Resolved Members + tags = lib.attrNames role.tags; + }; + inherit (inventory) machines; + inherit instanceName roleName; + }; + in + # instances..roles. = + # Remove "tags", they are resolved into "machines" + (removeAttrs role [ "tags" ]) + // { + machines = lib.genAttrs resolvedMachines.machines ( + machineName: + let + machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { }; + in + # TODO: tag settings + # Wait for this feature until option introspection for 'settings' is done. + # This might get too complex to handle otherwise. + # settingsViaTags = lib.filterAttrs ( + # tagName: _: machineHasTag machineName tagName + # ) instance.roles.${roleName}.tags; + { + # TODO: Do we want to wrap settings with + # setDefaultModuleLocation "inventory.instances.${instanceName}.roles.${roleName}.tags.${tagName}"; + settings = { + imports = [ + machineSettings + ]; # ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags); + }; + } + ); + } + ) instance.roles; + in + { + inherit (instance) module; + inherit resolvedModule instanceRoles; + } + ) inventory.instances or { }; +in +{ + _class = "clan"; + options._services = mkOption { + visible = false; + description = '' + All service instances + + !!! Danger "Internal API" + Do not rely on this API yet. + + - Will be renamed to just 'services' in the future. + Once the name can be claimed again. + - Structure will change. + + API will be declared as public after beeing simplified. + ''; + type = types.submoduleWith { + # TODO: Remove specialArgs + specialArgs = { + inherit clanLib; + }; + modules = [ + (import ../../lib/inventory/distributed-service/all-services-wrapper.nix { + inherit (clanConfig) directory; + }) + # Dependencies + { + exportsModule = clanConfig.exportsModule; + } + { + # TODO: Rename to "allServices" + # All services + mappedServices = lib.mapAttrs (_module_ident: instances: { + imports = [ + # Import the resolved module. + # i.e. clan.modules.admin + { + options.module = lib.mkOption { + type = lib.types.raw; + default = (builtins.head instances).instance.module; + }; + } + (builtins.head instances).instance.resolvedModule + ] # Include all the instances that correlate to the resolved module + ++ (builtins.map (v: { + instances.${v.instanceName}.roles = v.instance.instanceRoles; + }) instances); + }) grouped; + } + ]; + }; + default = { }; + }; + options._allMachines = mkOption { + internal = true; + type = types.raw; + default = lib.mapAttrs (machineName: _: { + # This is the list of nixosModules for each machine + machineImports = lib.foldlAttrs ( + acc: _module_ident: serviceModule: + acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ] + ) [ ] config._services.mappedServices; + }) inventory.machines or { }; + }; + + config = { + clanInternals.inventoryClass.machines = config._allMachines; + # clanInternals.inventoryClass.distributedServices = config._services; + + # Exports from distributed services + exports = config._services.exports; + }; +} diff --git a/modules/clan/module.nix b/modules/clan/module.nix index ab433b68a..26739055c 100644 --- a/modules/clan/module.nix +++ b/modules/clan/module.nix @@ -219,8 +219,6 @@ in inherit nixosConfigurations; inherit darwinConfigurations; - exports = config.clanInternals.inventoryClass.distributedServices.servicesEval.config.exports; - clanInternals = { inventoryClass = let @@ -254,21 +252,9 @@ in exportsModule = config.exportsModule; } ( - { config, ... }: + { ... }: { staticModules = clan-core.clan.modules; - - distributedServices = clanLib.inventory.mapInstances { - inherit (config) - inventory - directory - flakeInputs - exportsModule - ; - clanCoreModules = clan-core.clan.modules; - prefix = [ "distributedServices" ]; - }; - machines = config.distributedServices.allMachines; } ) ]; diff --git a/modules/inventoryClass/default.nix b/modules/inventoryClass/default.nix index 20893aaa6..f355e9533 100644 --- a/modules/inventoryClass/default.nix +++ b/modules/inventoryClass/default.nix @@ -67,9 +67,6 @@ in type = types.raw; }; - distributedServices = mkOption { - type = types.raw; - }; inventory = mkOption { type = types.raw; }; From 169b4016e6b39484cfac2971b43e623e281dc3ae Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 30 Oct 2025 12:51:18 +0100 Subject: [PATCH 18/39] docs: set self to clan-core for docs --- modules/clan/eval-docs.nix | 4 ++++ modules/clan/flake-module.nix | 1 + pkgs/docs-from-code/flake-module.nix | 3 +++ 3 files changed, 8 insertions(+) diff --git a/modules/clan/eval-docs.nix b/modules/clan/eval-docs.nix index 68360723e..0e7d966fc 100644 --- a/modules/clan/eval-docs.nix +++ b/modules/clan/eval-docs.nix @@ -3,12 +3,16 @@ lib, clanModule, clanLib, + clan-core, }: let eval = lib.evalModules { modules = [ clanModule ]; + specialArgs = { + self = clan-core; + }; }; evalDocs = pkgs.nixosOptionsDoc { diff --git a/modules/clan/flake-module.nix b/modules/clan/flake-module.nix index a84dc554f..0178888ca 100644 --- a/modules/clan/flake-module.nix +++ b/modules/clan/flake-module.nix @@ -12,6 +12,7 @@ in }: let jsonDocs = import ./eval-docs.nix { + clan-core = self; inherit pkgs lib diff --git a/pkgs/docs-from-code/flake-module.nix b/pkgs/docs-from-code/flake-module.nix index fd1dc1b4f..627721493 100644 --- a/pkgs/docs-from-code/flake-module.nix +++ b/pkgs/docs-from-code/flake-module.nix @@ -64,6 +64,9 @@ ''; in { + legacyPackages = { + inherit jsonDocs clanModulesViaService; + }; packages = { inherit module-docs; }; From be31b9ce21ce392d82aa02b81d514bad955b57f2 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 30 Oct 2025 13:46:42 +0100 Subject: [PATCH 19/39] docs: remove service options from nuschtSearch These hacks are blocking the flake level vars and exports Maybe we bring this back later So far nobody seemed using nuschtSearch --- pkgs/option-search/flake-module.nix | 147 ---------------------------- 1 file changed, 147 deletions(-) diff --git a/pkgs/option-search/flake-module.nix b/pkgs/option-search/flake-module.nix index 9e7628371..baadbdd82 100644 --- a/pkgs/option-search/flake-module.nix +++ b/pkgs/option-search/flake-module.nix @@ -11,151 +11,10 @@ ... }: let - inherit (lib) - mapAttrsToList - mapAttrs - mkOption - types - splitString - stringLength - substring - ; - inherit (self) clanLib; - - serviceModules = self.clan.modules; baseHref = "/option-search/"; - getRoles = - module: - (clanLib.evalService { - modules = [ module ]; - prefix = [ ]; - }).config.roles; - - getManifest = - module: - (clanLib.evalService { - modules = [ module ]; - prefix = [ ]; - }).config.manifest; - - settingsModules = module: mapAttrs (_roleName: roleConfig: roleConfig.interface) (getRoles module); - # Map each letter to its capitalized version - capitalizeChar = - char: - { - a = "A"; - b = "B"; - c = "C"; - d = "D"; - e = "E"; - f = "F"; - g = "G"; - h = "H"; - i = "I"; - j = "J"; - k = "K"; - l = "L"; - m = "M"; - n = "N"; - o = "O"; - p = "P"; - q = "Q"; - r = "R"; - s = "S"; - t = "T"; - u = "U"; - v = "V"; - w = "W"; - x = "X"; - y = "Y"; - z = "Z"; - } - .${char}; - - title = - name: - let - # split by - - parts = splitString "-" name; - # capitalize first letter of each part - capitalize = part: (capitalizeChar (substring 0 1 part)) + substring 1 (stringLength part) part; - capitalizedParts = map capitalize parts; - in - builtins.concatStringsSep " " capitalizedParts; - - fakeInstanceOptions = - name: module: - let - manifest = getManifest module; - description = '' - # ${title name} (Clan Service) - - **${manifest.description}** - - ${lib.optionalString (manifest ? readme) manifest.readme} - - ${ - if manifest.categories != [ ] then - "Categories: " + builtins.concatStringsSep ", " manifest.categories - else - "No categories defined" - } - - ''; - in - { - options = { - instances.${name} = lib.mkOption { - inherit description; - type = types.submodule { - options.roles = mapAttrs ( - roleName: roleSettingsModule: - mkOption { - type = types.submodule { - _file = "docs flake-module"; - imports = [ - { _module.args = { inherit clanLib; }; } - (import ../../modules/inventoryClass/role.nix { - nestedSettingsOption = mkOption { - type = types.raw; - description = '' - See [instances.${name}.roles.${roleName}.settings](${baseHref}?option_scope=0&option=inventory.instances.${name}.roles.${roleName}.settings) - ''; - }; - settingsOption = mkOption { - type = types.submoduleWith { - modules = [ roleSettingsModule ]; - }; - }; - }) - ]; - }; - } - ) (settingsModules module); - }; - }; - }; - }; - - docModules = [ - { - inherit self; - } - self.modules.clan.default - { - options.inventory = lib.mkOption { - type = types.submoduleWith { - modules = [ - { noInstanceOptions = true; } - ] - ++ mapAttrsToList fakeInstanceOptions serviceModules; - }; - }; - } - ]; baseModule = # Module @@ -208,12 +67,6 @@ title = "Clan Options"; # scopes = mapAttrsToList mkScope serviceModules; scopes = [ - { - inherit baseHref; - name = "Flake Options (clan.nix file)"; - modules = docModules; - urlPrefix = "https://git.clan.lol/clan/clan-core/src/branch/main/"; - } { name = "Machine Options (clan.core NixOS options)"; optionsJSON = "${coreOptions}/share/doc/nixos/options.json"; From 1953540d08c22f785819eb49041ef36697183c98 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 30 Oct 2025 13:47:34 +0100 Subject: [PATCH 20/39] tests: update inventory tests to use whole clan modules --- .../distributed-service/tests/default.nix | 242 +++++++++--------- .../tests/import_module_spec.nix | 32 +-- .../tests/per_instance_args.nix | 77 +++--- .../tests/per_machine_args.nix | 66 ++--- .../distributed-service/tests/settings.nix | 41 +-- .../distributed-service/tests/specialArgs.nix | 24 +- 6 files changed, 245 insertions(+), 237 deletions(-) diff --git a/lib/inventory/distributed-service/tests/default.nix b/lib/inventory/distributed-service/tests/default.nix index 048b0b14c..068e944a1 100644 --- a/lib/inventory/distributed-service/tests/default.nix +++ b/lib/inventory/distributed-service/tests/default.nix @@ -4,63 +4,53 @@ ... }: let - inherit (lib) - evalModules - ; - evalInventory = - m: - (evalModules { - # Static modules - modules = [ - clanLib.inventory.inventoryModule - { - _file = "test file"; - tags.all = [ ]; - tags.nixos = [ ]; - tags.darwin = [ ]; - } - { - modules.test = { }; - } - m - ]; - }).config; - - callInventoryAdapter = - inventoryModule: - let - inventory = evalInventory inventoryModule; - flakeInputsFixture = { - self.clan.modules = inventoryModule.modules or { }; - # Example upstream module - upstream.clan.modules = { - uzzi = { - _class = "clan.service"; - manifest = { - name = "uzzi-from-upstream"; - }; - }; + flakeInputsFixture = { + upstream.clan.modules = { + uzzi = { + _class = "clan.service"; + manifest = { + name = "uzzi-from-upstream"; }; }; - in - clanLib.inventory.mapInstances { - directory = ./.; - clanCoreModules = { }; - flakeInputs = flakeInputsFixture; - inherit inventory; - exportsModule = { }; }; + }; + + createTestClan = + testClan: + let + res = clanLib.clan ({ + # Static / mocked + specialArgs = { + clan-core = { + clan.modules = { }; + }; + }; + self.inputs = flakeInputsFixture // { + self.clan = res.config; + }; + directory = ./.; + exportsModule = { }; + + imports = [ + testClan + ]; + }); + in + res; + in { extraModules = import ./extraModules.nix { inherit clanLib; }; exports = import ./exports.nix { inherit lib clanLib; }; - settings = import ./settings.nix { inherit lib callInventoryAdapter; }; - specialArgs = import ./specialArgs.nix { inherit lib callInventoryAdapter; }; - resolve_module_spec = import ./import_module_spec.nix { inherit lib callInventoryAdapter; }; + settings = import ./settings.nix { inherit lib createTestClan; }; + specialArgs = import ./specialArgs.nix { inherit lib createTestClan; }; + resolve_module_spec = import ./import_module_spec.nix { + inherit lib createTestClan; + }; test_simple = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -71,7 +61,7 @@ in }; }; # User config - instances."instance_foo" = { + inventory.instances."instance_foo" = { module = { name = "simple-module"; }; @@ -81,7 +71,7 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = res.servicesEval.config.mappedServices ? "-simple-module"; + expr = res.config._services.mappedServices ? "-simple-module"; expected = true; inherit res; }; @@ -92,7 +82,7 @@ in # All instances should be included within one evaluation to make all of them available test_module_grouping = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -112,18 +102,19 @@ in perMachine = { }: { }; }; + # User config - instances."instance_foo" = { + inventory.instances."instance_foo" = { module = { name = "A"; }; }; - instances."instance_bar" = { + inventory.instances."instance_bar" = { module = { name = "B"; }; }; - instances."instance_baz" = { + inventory.instances."instance_baz" = { module = { name = "A"; }; @@ -133,16 +124,16 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.mapAttrs (_n: v: builtins.length v) res.grouped; - expected = { - "-A" = 2; - "-B" = 1; - }; + expr = lib.attrNames res.config._services.mappedServices; + expected = [ + "-A" + "-B" + ]; }; test_creates_all_instances = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -154,22 +145,24 @@ in perMachine = { }: { }; }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inventory = { + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; }; - }; - instances."instance_bar" = { - module = { - name = "A"; - input = "self"; + instances."instance_bar" = { + module = { + name = "A"; + input = "self"; + }; }; - }; - instances."instance_zaza" = { - module = { - name = "B"; - input = null; + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; }; }; }; @@ -177,7 +170,7 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.instances; + expr = lib.attrNames res.config._services.mappedServices.self-A.instances; expected = [ "instance_bar" "instance_foo" @@ -187,7 +180,7 @@ in # Membership via roles test_add_machines_directly = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -202,38 +195,40 @@ in # perMachine = {}: {}; }; - machines = { - jon = { }; - sara = { }; - hxi = { }; - }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inventory = { + machines = { + jon = { }; + sara = { }; + hxi = { }; }; - roles.peer.machines.jon = { }; - }; - instances."instance_bar" = { - module = { - name = "A"; - input = "self"; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { }; }; - roles.peer.machines.sara = { }; - }; - instances."instance_zaza" = { - module = { - name = "B"; - input = null; + instances."instance_bar" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.sara = { }; + }; + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; + roles.peer.tags.all = { }; }; - roles.peer.tags.all = { }; }; }; in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.result.allMachines; + expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines; expected = [ "jon" "sara" @@ -243,7 +238,7 @@ in # Membership via tags test_add_machines_via_tags = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -257,35 +252,37 @@ in # perMachine = {}: {}; }; - machines = { - jon = { - tags = [ "foo" ]; + inventory = { + machines = { + jon = { + tags = [ "foo" ]; + }; + sara = { + tags = [ "foo" ]; + }; + hxi = { }; }; - sara = { - tags = [ "foo" ]; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.tags.foo = { }; }; - hxi = { }; - }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; + roles.peer.tags.all = { }; }; - roles.peer.tags.foo = { }; - }; - instances."instance_zaza" = { - module = { - name = "B"; - input = null; - }; - roles.peer.tags.all = { }; }; }; in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.result.allMachines; + expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines; expected = [ "jon" "sara" @@ -293,6 +290,9 @@ in }; machine_imports = import ./machine_imports.nix { inherit lib clanLib; }; - per_machine_args = import ./per_machine_args.nix { inherit lib callInventoryAdapter; }; - per_instance_args = import ./per_instance_args.nix { inherit lib callInventoryAdapter; }; + per_machine_args = import ./per_machine_args.nix { inherit lib createTestClan; }; + per_instance_args = import ./per_instance_args.nix { + inherit lib; + callInventoryAdapter = createTestClan; + }; } diff --git a/lib/inventory/distributed-service/tests/import_module_spec.nix b/lib/inventory/distributed-service/tests/import_module_spec.nix index a8d29538d..dde5a9934 100644 --- a/lib/inventory/distributed-service/tests/import_module_spec.nix +++ b/lib/inventory/distributed-service/tests/import_module_spec.nix @@ -1,4 +1,4 @@ -{ callInventoryAdapter, ... }: +{ createTestClan, ... }: let # Authored module # A minimal module looks like this @@ -23,10 +23,13 @@ let resolve = spec: - callInventoryAdapter { - inherit modules machines; - instances."instance_foo" = { - module = spec; + createTestClan { + inherit modules; + inventory = { + inherit machines; + instances."instance_foo" = { + module = spec; + }; }; }; in @@ -36,25 +39,16 @@ in (resolve { name = "A"; input = "self"; - }).importedModuleWithInstances.instance_foo.resolvedModule; - expected = { - _class = "clan.service"; - manifest = { - name = "network"; - }; - }; + }).config._services.mappedServices.self-A.manifest.name; + expected = "network"; }; test_import_remote_module_by_name = { expr = (resolve { name = "uzzi"; input = "upstream"; - }).importedModuleWithInstances.instance_foo.resolvedModule; - expected = { - _class = "clan.service"; - manifest = { - name = "uzzi-from-upstream"; - }; - }; + }).config._services.mappedServices.upstream-uzzi.manifest.name; + expected = "uzzi-from-upstream"; + }; } diff --git a/lib/inventory/distributed-service/tests/per_instance_args.nix b/lib/inventory/distributed-service/tests/per_instance_args.nix index 9fe0c0316..371ce9554 100644 --- a/lib/inventory/distributed-service/tests/per_instance_args.nix +++ b/lib/inventory/distributed-service/tests/per_instance_args.nix @@ -58,39 +58,43 @@ let sara = { }; }; res = callInventoryAdapter { - inherit modules machines; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inherit modules; + + inventory = { + inherit machines; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { + settings.timeout = lib.mkForce "foo-peer-jon"; + }; + roles.peer = { + settings.timeout = "foo-peer"; + }; + roles.controller.machines.jon = { }; }; - roles.peer.machines.jon = { - settings.timeout = lib.mkForce "foo-peer-jon"; + instances."instance_bar" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { + settings.timeout = "bar-peer-jon"; + }; }; - roles.peer = { - settings.timeout = "foo-peer"; + # TODO: move this into a seperate test. + # Seperate out the check that this module is never imported + # import the module "B" (undefined) + # All machines have this instance + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; + roles.peer.tags.all = { }; }; - roles.controller.machines.jon = { }; - }; - instances."instance_bar" = { - module = { - name = "A"; - input = "self"; - }; - roles.peer.machines.jon = { - settings.timeout = "bar-peer-jon"; - }; - }; - # TODO: move this into a seperate test. - # Seperate out the check that this module is never imported - # import the module "B" (undefined) - # All machines have this instance - instances."instance_zaza" = { - module = { - name = "B"; - input = null; - }; - roles.peer.tags.all = { }; }; }; @@ -105,9 +109,10 @@ in { # settings should evaluate test_per_instance_arguments = { + inherit res; expr = { instanceName = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName; # settings are specific. # Below we access: @@ -115,11 +120,11 @@ in # roles = peer # machines = jon settings = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings; machine = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine; roles = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles; }; expected = { instanceName = "instance_foo"; @@ -160,9 +165,9 @@ in # TODO: Cannot be tested like this anymore test_per_instance_settings_vendoring = { - x = res.servicesEval.config.mappedServices.self-A; + x = res.config._services.mappedServices.self-A; expr = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings; expected = { timeout = "config.thing"; }; diff --git a/lib/inventory/distributed-service/tests/per_machine_args.nix b/lib/inventory/distributed-service/tests/per_machine_args.nix index 5907e8bf2..b650281a6 100644 --- a/lib/inventory/distributed-service/tests/per_machine_args.nix +++ b/lib/inventory/distributed-service/tests/per_machine_args.nix @@ -1,4 +1,4 @@ -{ lib, callInventoryAdapter }: +{ lib, createTestClan }: let # Authored module # A minimal module looks like this @@ -39,36 +39,40 @@ let jon = { }; sara = { }; }; - res = callInventoryAdapter { - inherit modules machines; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + res = createTestClan { + inherit modules; + inventory = { + + inherit machines; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { + settings.timeout = lib.mkForce "foo-peer-jon"; + }; + roles.peer = { + settings.timeout = "foo-peer"; + }; }; - roles.peer.machines.jon = { - settings.timeout = lib.mkForce "foo-peer-jon"; + instances."instance_bar" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { + settings.timeout = "bar-peer-jon"; + }; }; - roles.peer = { - settings.timeout = "foo-peer"; + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; + roles.peer.tags.all = { }; }; }; - instances."instance_bar" = { - module = { - name = "A"; - input = "self"; - }; - roles.peer.machines.jon = { - settings.timeout = "bar-peer-jon"; - }; - }; - instances."instance_zaza" = { - module = { - name = "B"; - input = null; - }; - roles.peer.tags.all = { }; - }; }; in @@ -79,7 +83,7 @@ in inherit res; expr = { hasMachineSettings = - res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon + res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon ? settings; # settings are specific. @@ -88,10 +92,10 @@ in # roles = peer # machines = jon specificMachineSettings = - res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings; + res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings; hasRoleSettings = - res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer + res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer ? settings; # settings are specific. @@ -100,7 +104,7 @@ in # roles = peer # machines = * specificRoleSettings = - res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer; + res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer; }; expected = { hasMachineSettings = true; diff --git a/lib/inventory/distributed-service/tests/settings.nix b/lib/inventory/distributed-service/tests/settings.nix index f9214abda..422a529e7 100644 --- a/lib/inventory/distributed-service/tests/settings.nix +++ b/lib/inventory/distributed-service/tests/settings.nix @@ -1,6 +1,6 @@ -{ callInventoryAdapter, lib, ... }: +{ createTestClan, lib, ... }: let - res = callInventoryAdapter { + res = createTestClan { modules."A" = { _class = "clan.service"; manifest = { @@ -21,28 +21,31 @@ let }; }; }; - machines = { - jon = { }; - sara = { }; - }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inventory = { + + machines = { + jon = { }; + sara = { }; }; - # Settings for both jon and sara - roles.peer.settings = { - timeout = 40; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + # Settings for both jon and sara + roles.peer.settings = { + timeout = 40; + }; + # Jon overrides timeout + roles.peer.machines.jon = { + settings.timeout = lib.mkForce 42; + }; + roles.peer.machines.sara = { }; }; - # Jon overrides timeout - roles.peer.machines.jon = { - settings.timeout = lib.mkForce 42; - }; - roles.peer.machines.sara = { }; }; }; - config = res.servicesEval.config.mappedServices.self-A; + config = res.config._services.mappedServices.self-A; # applySettings = diff --git a/lib/inventory/distributed-service/tests/specialArgs.nix b/lib/inventory/distributed-service/tests/specialArgs.nix index 8670f0e45..c29b266e5 100644 --- a/lib/inventory/distributed-service/tests/specialArgs.nix +++ b/lib/inventory/distributed-service/tests/specialArgs.nix @@ -1,6 +1,6 @@ -{ callInventoryAdapter, lib, ... }: +{ createTestClan, lib, ... }: let - res = callInventoryAdapter { + res = createTestClan { modules."A" = m: { _class = "clan.service"; config = { @@ -14,19 +14,21 @@ let default = m; }; }; - machines = { - jon = { }; - }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inventory = { + machines = { + jon = { }; + }; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { }; }; - roles.peer.machines.jon = { }; }; }; - specialArgs = lib.attrNames res.servicesEval.config.mappedServices.self-A.test.specialArgs; + specialArgs = lib.attrNames res.config._services.mappedServices.self-A.test.specialArgs; in { test_simple = { From da98ca0f1ce81c9e411404fa8aadea111b1f33b2 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 30 Oct 2025 13:47:52 +0100 Subject: [PATCH 21/39] clanLib: remove unused mapInstances --- lib/inventory/default.nix | 4 - .../distributed-service/inventory-adapter.nix | 168 ------------------ 2 files changed, 172 deletions(-) delete mode 100644 lib/inventory/distributed-service/inventory-adapter.nix diff --git a/lib/inventory/default.nix b/lib/inventory/default.nix index 90f25ea44..3e75f13c3 100644 --- a/lib/inventory/default.nix +++ b/lib/inventory/default.nix @@ -2,11 +2,7 @@ lib, clanLib, }: -let - services = clanLib.callLib ./distributed-service/inventory-adapter.nix { }; -in { - inherit (services) mapInstances; inventoryModule = { _file = "clanLib.inventory.module"; imports = [ diff --git a/lib/inventory/distributed-service/inventory-adapter.nix b/lib/inventory/distributed-service/inventory-adapter.nix deleted file mode 100644 index 9f4f65d0b..000000000 --- a/lib/inventory/distributed-service/inventory-adapter.nix +++ /dev/null @@ -1,168 +0,0 @@ -# Adapter function between the inventory.instances and the clan.service module -# -# Data flow: -# - inventory.instances -> Adapter -> clan.service module -> Service Resources (i.e. NixosModules per Machine, Vars per Service, etc.) -# -# What this file does: -# -# - Resolves the [Module] to an actual module-path and imports it. -# - Groups together all the same modules into a single import and creates all instances for it. -# - Resolves the inventory tags into machines. Tags don't exist at the service level. -# Also combines the settings for 'machines' and 'tags'. -{ - lib, - clanLib, - ... -}: -{ - mapInstances = - { - # This is used to resolve the module imports from 'flake.inputs' - flakeInputs, - # The clan inventory - inventory, - directory, - clanCoreModules, - prefix ? [ ], - exportsModule, - }: - let - # machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags; - - # map the instances into the module - importedModuleWithInstances = lib.mapAttrs ( - instanceName: instance: - let - resolvedModule = clanLib.resolveModule { - moduleSpec = instance.module; - inherit flakeInputs clanCoreModules; - }; - - # Every instance includes machines via roles - # :: { client :: ... } - instanceRoles = lib.mapAttrs ( - roleName: role: - let - resolvedMachines = clanLib.inventory.resolveTags { - members = { - # Explicit members - machines = lib.attrNames role.machines; - # Resolved Members - tags = lib.attrNames role.tags; - }; - inherit (inventory) machines; - inherit instanceName roleName; - }; - in - # instances..roles. = - # Remove "tags", they are resolved into "machines" - (removeAttrs role [ "tags" ]) - // { - machines = lib.genAttrs resolvedMachines.machines ( - machineName: - let - machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { }; - in - # TODO: tag settings - # Wait for this feature until option introspection for 'settings' is done. - # This might get too complex to handle otherwise. - # settingsViaTags = lib.filterAttrs ( - # tagName: _: machineHasTag machineName tagName - # ) instance.roles.${roleName}.tags; - { - # TODO: Do we want to wrap settings with - # setDefaultModuleLocation "inventory.instances.${instanceName}.roles.${roleName}.tags.${tagName}"; - settings = { - imports = [ - machineSettings - ]; # ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags); - }; - } - ); - } - ) instance.roles; - in - { - inherit (instance) module; - inherit resolvedModule instanceRoles; - } - ) inventory.instances or { }; - - # Group the instances by the module they resolve to - # This is necessary to evaluate the module in a single pass - # :: { _ :: [ { name, value } ] } - # Since 'perMachine' needs access to all the instances we should include them as a whole - grouped = lib.foldlAttrs ( - acc: instanceName: instance: - let - inputName = if instance.module.input == null then "" else instance.module.input; - id = inputName + "-" + instance.module.name; - in - acc - // { - ${id} = acc.${id} or [ ] ++ [ - { - inherit instanceName instance; - } - ]; - } - ) { } importedModuleWithInstances; - - # servicesEval.config.mappedServices.self-A.result.final.jon.nixosModule - allMachines = lib.mapAttrs (machineName: _: { - # This is the list of nixosModules for each machine - machineImports = lib.foldlAttrs ( - acc: _module_ident: serviceModule: - acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ] - ) [ ] servicesEval.config.mappedServices; - }) inventory.machines or { }; - - evalServices = - { modules, prefix }: - lib.evalModules { - class = "clan"; - specialArgs = { - inherit clanLib; - _ctx = prefix; - }; - modules = [ - (import ./all-services-wrapper.nix { inherit directory; }) - ] - ++ modules; - }; - - servicesEval = evalServices { - inherit prefix; - modules = [ - { - inherit exportsModule; - mappedServices = lib.mapAttrs (_module_ident: instances: { - imports = [ - # Import the resolved module. - # i.e. clan.modules.admin - { - options.module = lib.mkOption { - type = lib.types.raw; - default = (builtins.head instances).instance.module; - }; - } - (builtins.head instances).instance.resolvedModule - ] # Include all the instances that correlate to the resolved module - ++ (builtins.map (v: { - instances.${v.instanceName}.roles = v.instance.instanceRoles; - }) instances); - }) grouped; - } - ]; - }; - in - { - inherit - servicesEval - allMachines - # Exposed for testing - grouped - importedModuleWithInstances - ; - }; -} From c454b1339d432f31deffe3a64403f08e79af847d Mon Sep 17 00:00:00 2001 From: clan-bot Date: Thu, 30 Oct 2025 15:01:51 +0000 Subject: [PATCH 22/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 9d5d7e1ef..0a10c322c 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761748483, - "narHash": "sha256-v7fttCB5lJ22Ok7+N7ZbLhDeM89QIz9YWtQP4XN7xgA=", + "lastModified": 1761789293, + "narHash": "sha256-zwQKLaUgHSpY6SvB/MDgPYRPomWAmbkS3Xfo6JvFVOA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "061c55856b29b8b9360e14231a0986c7f85f1130", + "rev": "8d42228a0de7c23b012e2f7dd963425a372e1b0e", "type": "github" }, "original": { From 38c13673229e75357246166e79b627567b0523c1 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Thu, 30 Oct 2025 20:01:49 +0000 Subject: [PATCH 23/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 0a10c322c..70ae98d83 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761789293, - "narHash": "sha256-zwQKLaUgHSpY6SvB/MDgPYRPomWAmbkS3Xfo6JvFVOA=", + "lastModified": 1761834939, + "narHash": "sha256-0A6pYhkran9nY/g5wxw0gpYtNk20z98D2wusH6D5QXI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8d42228a0de7c23b012e2f7dd963425a372e1b0e", + "rev": "5f7f854b1475fd323aa0701c0d1f9d665b131336", "type": "github" }, "original": { From 4b1955b189c4626c99397dd9a27c01a902ec65f2 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Fri, 31 Oct 2025 00:02:00 +0000 Subject: [PATCH 24/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 70ae98d83..c5884391e 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761834939, - "narHash": "sha256-0A6pYhkran9nY/g5wxw0gpYtNk20z98D2wusH6D5QXI=", + "lastModified": 1761853358, + "narHash": "sha256-1tBdsBzYJOzVzNOmCFzFMWHw7UUbhkhiYCFGr+OjPTs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5f7f854b1475fd323aa0701c0d1f9d665b131336", + "rev": "262333bca9b49964f8e3cad3af655466597c01d4", "type": "github" }, "original": { From 42acbe95b8f99273e7cff89a2bddb4ca4d821f5a Mon Sep 17 00:00:00 2001 From: clan-bot Date: Fri, 31 Oct 2025 10:00:58 +0000 Subject: [PATCH 25/39] Update disko --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index b83c46db8..b81339d10 100644 --- a/flake.lock +++ b/flake.lock @@ -31,11 +31,11 @@ ] }, "locked": { - "lastModified": 1760701190, - "narHash": "sha256-y7UhnWlER8r776JsySqsbTUh2Txf7K30smfHlqdaIQw=", + "lastModified": 1761899396, + "narHash": "sha256-XOpKBp6HLzzMCbzW50TEuXN35zN5WGQREC7n34DcNMM=", "owner": "nix-community", "repo": "disko", - "rev": "3a9450b26e69dcb6f8de6e2b07b3fc1c288d85f5", + "rev": "6f4cf5abbe318e4cd1e879506f6eeafd83f7b998", "type": "github" }, "original": { From 7a63cb964258c47a0bf84fab88a71f540dcf9813 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Fri, 31 Oct 2025 15:01:50 +0000 Subject: [PATCH 26/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index c5884391e..55e4464f0 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761853358, - "narHash": "sha256-1tBdsBzYJOzVzNOmCFzFMWHw7UUbhkhiYCFGr+OjPTs=", + "lastModified": 1761907660, + "narHash": "sha256-kJ8lIZsiPOmbkJypG+B5sReDXSD1KGu2VEPNqhRa/ew=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "262333bca9b49964f8e3cad3af655466597c01d4", + "rev": "2fb006b87f04c4d3bdf08cfdbc7fab9c13d94a15", "type": "github" }, "original": { From 58d85b117a66fe99f9ff34b23acef624fea9a90f Mon Sep 17 00:00:00 2001 From: Qubasa Date: Fri, 31 Oct 2025 16:05:54 +0100 Subject: [PATCH 27/39] clan_lib/flake: Improve select error message --- pkgs/clan-cli/clan_lib/flake/flake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/clan-cli/clan_lib/flake/flake.py b/pkgs/clan-cli/clan_lib/flake/flake.py index 29dcedff1..85cc0b71b 100644 --- a/pkgs/clan-cli/clan_lib/flake/flake.py +++ b/pkgs/clan-cli/clan_lib/flake/flake.py @@ -211,7 +211,7 @@ class ClanSelectError(ClanError): def __str__(self) -> str: if self.description: - return f"{self.msg} Reason: {self.description}" + return f"{self.msg} Reason: {self.description}. Use flag '--debug' to see full nix trace." return self.msg def __repr__(self) -> str: From 0a2fefd141aa6d2701ac7840630480e10ba269b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Fri, 31 Oct 2025 18:49:02 +0100 Subject: [PATCH 28/39] treewide: replace pkgs.hostPlatform with pkgs.stdenv.hostPlatform nixpkgs now throws an error for this, the other variant in stdenv also exists in the previous release --- checks/flash/flake-module.nix | 70 +++++++++++++------------- checks/installation/flake-module.nix | 10 ++-- checks/llm/default.nix | 4 +- checks/systemd-abstraction/default.nix | 2 +- checks/update/flake-module.nix | 18 +++---- nixosModules/flake-module.nix | 2 +- nixosModules/installer/zfs-latest.nix | 2 +- 7 files changed, 55 insertions(+), 53 deletions(-) diff --git a/checks/flash/flake-module.nix b/checks/flash/flake-module.nix index 0d1e1a5fc..13f00583a 100644 --- a/checks/flash/flake-module.nix +++ b/checks/flash/flake-module.nix @@ -58,51 +58,53 @@ pkgs.buildPackages.xorg.lndir pkgs.glibcLocales pkgs.kbd.out - self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles - self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.FileSlurp + self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles + self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".pkgs.perlPackages.FileSlurp pkgs.bubblewrap - self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel - self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript - self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath + self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.toplevel + self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript + self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript.drvPath ] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs); closureInfo = pkgs.closureInfo { rootPaths = dependencies; }; in { # Skip flash test on aarch64-linux for now as it's too slow - checks = lib.optionalAttrs (pkgs.stdenv.isLinux && pkgs.hostPlatform.system != "aarch64-linux") { - nixos-test-flash = self.clanLib.test.baseTest { - name = "flash"; - nodes.target = { - virtualisation.emptyDiskImages = [ 4096 ]; - virtualisation.memorySize = 4096; + checks = + lib.optionalAttrs (pkgs.stdenv.isLinux && pkgs.stdenv.hostPlatform.system != "aarch64-linux") + { + nixos-test-flash = self.clanLib.test.baseTest { + name = "flash"; + nodes.target = { + virtualisation.emptyDiskImages = [ 4096 ]; + virtualisation.memorySize = 4096; - virtualisation.useNixStoreImage = true; - virtualisation.writableStore = true; + virtualisation.useNixStoreImage = true; + virtualisation.writableStore = true; - environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ]; - environment.etc."install-closure".source = "${closureInfo}/store-paths"; + environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ]; + environment.etc."install-closure".source = "${closureInfo}/store-paths"; - nix.settings = { - substituters = lib.mkForce [ ]; - hashed-mirrors = null; - connect-timeout = lib.mkForce 3; - flake-registry = ""; - experimental-features = [ - "nix-command" - "flakes" - ]; - }; + nix.settings = { + substituters = lib.mkForce [ ]; + hashed-mirrors = null; + connect-timeout = lib.mkForce 3; + flake-registry = ""; + experimental-features = [ + "nix-command" + "flakes" + ]; + }; + }; + testScript = '' + start_all() + machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub") + # Some distros like to automount disks with spaces + machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"') + machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.stdenv.hostPlatform.system}") + ''; + } { inherit pkgs self; }; }; - testScript = '' - start_all() - machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub") - # Some distros like to automount disks with spaces - machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"') - machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}") - ''; - } { inherit pkgs self; }; - }; }; } diff --git a/checks/installation/flake-module.nix b/checks/installation/flake-module.nix index dc7bcfa6b..298324849 100644 --- a/checks/installation/flake-module.nix +++ b/checks/installation/flake-module.nix @@ -160,9 +160,9 @@ closureInfo = pkgs.closureInfo { rootPaths = [ privateInputs.clan-core-for-checks - self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel - self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.initialRamdisk - self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript + self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.toplevel + self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.initialRamdisk + self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript pkgs.stdenv.drvPath pkgs.bash.drvPath pkgs.buildPackages.xorg.lndir @@ -215,7 +215,7 @@ # Prepare test flake and Nix store flake_dir = prepare_test_flake( temp_dir, - "${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}", + "${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}", "${closureInfo}" ) @@ -296,7 +296,7 @@ # Prepare test flake and Nix store flake_dir = prepare_test_flake( temp_dir, - "${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}", + "${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}", "${closureInfo}" ) diff --git a/checks/llm/default.nix b/checks/llm/default.nix index 0c0e143fc..89e72b7ca 100644 --- a/checks/llm/default.nix +++ b/checks/llm/default.nix @@ -2,7 +2,7 @@ let - cli = self.packages.${pkgs.hostPlatform.system}.clan-cli-full; + cli = self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full; ollama-model = pkgs.callPackage ./qwen3-4b-instruct.nix { }; in @@ -53,7 +53,7 @@ in pytest pytest-xdist (cli.pythonRuntime.pkgs.toPythonModule cli) - self.legacyPackages.${pkgs.hostPlatform.system}.nixosTestLib + self.legacyPackages.${pkgs.stdenv.hostPlatform.system}.nixosTestLib ] )) ]; diff --git a/checks/systemd-abstraction/default.nix b/checks/systemd-abstraction/default.nix index 4150690ad..de80b08ac 100644 --- a/checks/systemd-abstraction/default.nix +++ b/checks/systemd-abstraction/default.nix @@ -2,7 +2,7 @@ let - cli = self.packages.${pkgs.hostPlatform.system}.clan-cli-full; + cli = self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full; in { name = "systemd-abstraction"; diff --git a/checks/update/flake-module.nix b/checks/update/flake-module.nix index ddeef5c71..0957cc76d 100644 --- a/checks/update/flake-module.nix +++ b/checks/update/flake-module.nix @@ -115,9 +115,9 @@ let closureInfo = pkgs.closureInfo { rootPaths = [ - self.packages.${pkgs.hostPlatform.system}.clan-cli - self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks - self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel + self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli + self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks + self.clanInternals.machines.${pkgs.stdenv.hostPlatform.system}.test-update-machine.config.system.build.toplevel pkgs.stdenv.drvPath pkgs.bash.drvPath pkgs.buildPackages.xorg.lndir @@ -132,7 +132,7 @@ imports = [ self.nixosModules.test-update-machine ]; }; extraPythonPackages = _p: [ - self.legacyPackages.${pkgs.hostPlatform.system}.nixosTestLib + self.legacyPackages.${pkgs.stdenv.hostPlatform.system}.nixosTestLib ]; testScript = '' @@ -154,7 +154,7 @@ # Prepare test flake and Nix store flake_dir = prepare_test_flake( temp_dir, - "${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}", + "${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}", "${closureInfo}" ) (flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists @@ -226,7 +226,7 @@ "--to", "ssh://root@192.168.1.1", "--no-check-sigs", - f"${self.packages.${pkgs.hostPlatform.system}.clan-cli}", + f"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli}", "--extra-experimental-features", "nix-command flakes", ], check=True, @@ -242,7 +242,7 @@ "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", f"root@192.168.1.1", - "${self.packages.${pkgs.hostPlatform.system}.clan-cli}/bin/clan", + "${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli}/bin/clan", "machines", "update", "--debug", @@ -270,7 +270,7 @@ # Run clan update command subprocess.run([ - "${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan", + "${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full}/bin/clan", "machines", "update", "--debug", @@ -297,7 +297,7 @@ # Run clan update command with --build-host subprocess.run([ - "${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan", + "${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full}/bin/clan", "machines", "update", "--debug", diff --git a/nixosModules/flake-module.nix b/nixosModules/flake-module.nix index 3d82fec62..ffda62e1f 100644 --- a/nixosModules/flake-module.nix +++ b/nixosModules/flake-module.nix @@ -18,7 +18,7 @@ let inputs.data-mesher.nixosModules.data-mesher ]; config = { - clan.core.clanPkgs = lib.mkDefault self.packages.${pkgs.hostPlatform.system}; + clan.core.clanPkgs = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}; }; }; in diff --git a/nixosModules/installer/zfs-latest.nix b/nixosModules/installer/zfs-latest.nix index 77f060d9a..c31fe9ad2 100644 --- a/nixosModules/installer/zfs-latest.nix +++ b/nixosModules/installer/zfs-latest.nix @@ -30,5 +30,5 @@ let in { # Note this might jump back and worth as kernel get added or removed. - boot.kernelPackages = lib.mkIf (lib.meta.availableOn pkgs.hostPlatform pkgs.zfs) latestKernelPackage; + boot.kernelPackages = lib.mkIf (lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.zfs) latestKernelPackage; } From e21a6516b5c8603660dad29230f4ca98cd997cb0 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sat, 1 Nov 2025 12:30:01 +0100 Subject: [PATCH 29/39] docs: update experimental notes as planned in release-notes --- clanServices/certificates/README.md | 3 +++ clanServices/coredns/README.md | 3 +++ clanServices/internet/README.md | 7 ++----- clanServices/monitoring/README.md | 3 +++ clanServices/tor/README.md | 7 ++----- clanServices/yggdrasil/README.md | 9 +++------ 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/clanServices/certificates/README.md b/clanServices/certificates/README.md index 16fb4b048..7a891725c 100644 --- a/clanServices/certificates/README.md +++ b/clanServices/certificates/README.md @@ -1,3 +1,6 @@ +!!! Danger "Experimental" + This service is experimental and will change in the future. + This service sets up a certificate authority (CA) that can issue certificates to other machines in your clan. For this the `ca` role is used. It additionally provides a `default` role, that can be applied to all machines diff --git a/clanServices/coredns/README.md b/clanServices/coredns/README.md index 506ab6bd0..628304599 100644 --- a/clanServices/coredns/README.md +++ b/clanServices/coredns/README.md @@ -1,3 +1,6 @@ +!!! Danger "Experimental" + This service is experimental and will change in the future. + This module enables hosting clan-internal services easily, which can be resolved inside your VPN. This allows defining a custom top-level domain (e.g. `.clan`) and exposing endpoints from a machine to others, which will be diff --git a/clanServices/internet/README.md b/clanServices/internet/README.md index d712fadd7..d057d9f30 100644 --- a/clanServices/internet/README.md +++ b/clanServices/internet/README.md @@ -1,8 +1,5 @@ -🚧🚧🚧 Experimental 🚧🚧🚧 - -Use at your own risk. - -We are still refining its interfaces, instability and breakages are expected. +!!! Danger "Experimental" + This service is experimental and will change in the future. --- diff --git a/clanServices/monitoring/README.md b/clanServices/monitoring/README.md index 9f8c0bf7c..b6e8ea45c 100644 --- a/clanServices/monitoring/README.md +++ b/clanServices/monitoring/README.md @@ -1,3 +1,6 @@ +!!! Danger "Experimental" + This service is experimental and will change in the future. + ## Usage ``` diff --git a/clanServices/tor/README.md b/clanServices/tor/README.md index 693dddf38..2d5743077 100644 --- a/clanServices/tor/README.md +++ b/clanServices/tor/README.md @@ -1,8 +1,5 @@ -🚧🚧🚧 Experimental 🚧🚧🚧 - -Use at your own risk. - -We are still refining its interfaces, instability and breakages are expected. +!!! Danger "Experimental" + This service is experimental and will change in the future. --- diff --git a/clanServices/yggdrasil/README.md b/clanServices/yggdrasil/README.md index a9bb48cb4..ae9b5cd98 100644 --- a/clanServices/yggdrasil/README.md +++ b/clanServices/yggdrasil/README.md @@ -1,12 +1,9 @@ -🚧🚧🚧 Experimental 🚧🚧🚧 - -Use at your own risk. - -We are still refining its interfaces, instability and breakages are expected. +!!! Danger "Experimental" + This service is experimental and will change in the future. --- -This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across your clan. +This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across your clan. Yggdrasil is designed to be a future-proof and decentralised alternative to the structured routing protocols commonly used today on the internet. Inside your From 7b6cec41000dcc9980f4bc35448a3f3ccc7ec1d9 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sat, 1 Nov 2025 12:25:50 +0100 Subject: [PATCH 30/39] services: update hello-world readme and tests --- clanServices/hello-world/README.md | 84 ++++++++++++++++++- clanServices/hello-world/default.nix | 2 +- clanServices/hello-world/flake-module.nix | 2 +- clanServices/hello-world/tests/eval-tests.nix | 20 +++-- 4 files changed, 100 insertions(+), 8 deletions(-) diff --git a/clanServices/hello-world/README.md b/clanServices/hello-world/README.md index 04c30234b..5a6451870 100644 --- a/clanServices/hello-world/README.md +++ b/clanServices/hello-world/README.md @@ -1 +1,83 @@ -This a test README just to appease the eval warnings if we don't have one \ No newline at end of file +!!! Danger "Experimental" + This service is for demonstration purpose only and may change in the future. + +The Hello-World Clan Service is a minimal example showing how to build and register your own service. + +It serves as a reference implementation and is used in clan-core CI tests to ensure compatibility. + +## What it demonstrates + +- How to define a basic Clan-compatible service. +- How to structure your service for discovery and configuration. +- How Clan services interact with nixos. + +## Testing + +This service demonstrates two levels of testing to ensure quality and stability across releases: + +1. **Unit & Integration Testing** — via [`nix-unit`](https://github.com/nix-community/nix-unit) +2. **End-to-End Testing** — via **NixOS VM tests**, which we extended to support **container virtualization** for better performance. + +We highly advocate following the [Practical Testing Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html): + +* Write **unit tests** for core logic and invariants. +* Add **one or two end-to-end (E2E)** tests to confirm your service starts and behaves correctly in a real NixOS environment. + +NixOS is **untyped** and frequently changes; tests are the safest way to ensure long-term stability of services. + +``` + / \ + / \ + / E2E \ + /-------\ + / \ + /Integration\ + /-------------\ + / \ + / Unit Tests \ + ------------------- +``` + +### nix-unit + +We highly advocate the usage of + +[nix-unit](https://github.com/nix-community/nix-unit) + +Example in: tests/eval-tests.nix + +If you use flake-parts you can use the [native integration](https://flake.parts/options/nix-unit.html) + +If nix-unit succeeds you'r nixos evaluation should be mostly correct. + +!!! Tip + - Ensure most used 'settings' and variants are tested. + - Think about some important edge-cases your system should handle. + +### NixOS VM / Container Test + +!!! Warning "Early Vars & clanTest" + The testing system around vars is experimental + + `clanTest` is still experimental and enables container virtualization by default. + This is still early and might have some limitations. + +Some minimal boilerplate is needed to use `clanTest` + +```nix +nixosLib = import (inputs.nixpkgs + "/nixos/lib") { } +nixosLib.runTest ( + { ... }: + { + imports = [ + self.modules.nixosTest.clanTest + # Example in tests/vm/default.nix + testModule + ]; + hostPkgs = pkgs; + + # Uncomment if you don't want or cannot use containers + # test.useContainers = false; + } +) +``` diff --git a/clanServices/hello-world/default.nix b/clanServices/hello-world/default.nix index 5c682cb13..d86d5cfe8 100644 --- a/clanServices/hello-world/default.nix +++ b/clanServices/hello-world/default.nix @@ -8,7 +8,7 @@ { _class = "clan.service"; manifest.name = "clan-core/hello-word"; - manifest.description = "This is a test"; + manifest.description = "Minimal example clan service that greets the world"; manifest.readme = builtins.readFile ./README.md; # This service provides two roles: "morning" and "evening". Roles can be diff --git a/clanServices/hello-world/flake-module.nix b/clanServices/hello-world/flake-module.nix index e41c5c196..f62f0811f 100644 --- a/clanServices/hello-world/flake-module.nix +++ b/clanServices/hello-world/flake-module.nix @@ -26,7 +26,7 @@ in # The hello-world service being tested ../../clanServices/hello-world # Required modules - ../../nixosModules/clanCore + ../../nixosModules ]; testName = "hello-world"; tests = ./tests/eval-tests.nix; diff --git a/clanServices/hello-world/tests/eval-tests.nix b/clanServices/hello-world/tests/eval-tests.nix index 1cca5c54b..dfa735af2 100644 --- a/clanServices/hello-world/tests/eval-tests.nix +++ b/clanServices/hello-world/tests/eval-tests.nix @@ -4,7 +4,7 @@ ... }: let - testFlake = clanLib.clan { + testClan = clanLib.clan { self = { }; # Point to the folder of the module # TODO: make this optional @@ -33,10 +33,20 @@ let }; in { - test_simple = { - config = testFlake.config; + /** + We highly advocate the usage of: + https://github.com/nix-community/nix-unit - expr = { }; - expected = { }; + If you use flake-parts you can use the native integration: https://flake.parts/options/nix-unit.html + */ + test_simple = { + # Allows inspection via the nix-repl + # Ignored by nix-unit; it only looks at 'expr' and 'expected' + inherit testClan; + + # Assert that jon has the + # configured greeting in 'environment.etc.hello.text' + expr = testClan.config.nixosConfigurations.jon.config.environment.etc."hello".text; + expected = "Good evening World!"; }; } From fc37140b52312b6c6ad0e8881067d921fc286d43 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Sat, 1 Nov 2025 15:01:52 +0000 Subject: [PATCH 31/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 55e4464f0..0741f9d0e 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761907660, - "narHash": "sha256-kJ8lIZsiPOmbkJypG+B5sReDXSD1KGu2VEPNqhRa/ew=", + "lastModified": 1761994314, + "narHash": "sha256-IOZofbuQ+gnM4t/nkN9wc1LvRDLKNhEftLILRBa+1Gc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fb006b87f04c4d3bdf08cfdbc7fab9c13d94a15", + "rev": "1e0996604d71646c3061842452df7f03f3eb26ab", "type": "github" }, "original": { From 6fe60f61cf3ecef13727cdc59c20d688bbd59513 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Sat, 1 Nov 2025 20:00:58 +0000 Subject: [PATCH 32/39] Update nix-darwin --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index b81339d10..6dafbcef5 100644 --- a/flake.lock +++ b/flake.lock @@ -71,11 +71,11 @@ ] }, "locked": { - "lastModified": 1761339987, - "narHash": "sha256-IUaawVwItZKi64IA6kF6wQCLCzpXbk2R46dHn8sHkig=", + "lastModified": 1762022020, + "narHash": "sha256-tNj4SqLu87rV3z2Pf1Zr3vC93zYyMuLif1qLhHmQl64=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "7cd9aac79ee2924a85c211d21fafd394b06a38de", + "rev": "fc4e3dbe4039f8ff4fc303e50491ca8ba009ffd4", "type": "github" }, "original": { From 7a9062b629b7395f6c63c9d6ab281397087760c9 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Sun, 2 Nov 2025 05:01:01 +0000 Subject: [PATCH 33/39] Update flake-parts --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 6dafbcef5..0fca4ff08 100644 --- a/flake.lock +++ b/flake.lock @@ -51,11 +51,11 @@ ] }, "locked": { - "lastModified": 1760948891, - "narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=", + "lastModified": 1762040540, + "narHash": "sha256-z5PlZ47j50VNF3R+IMS9LmzI5fYRGY/Z5O5tol1c9I4=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04", + "rev": "0010412d62a25d959151790968765a70c436598b", "type": "github" }, "original": { From 8d26ec17608da12ff4304c63a670629c1b73036f Mon Sep 17 00:00:00 2001 From: clan-bot Date: Sun, 2 Nov 2025 05:01:04 +0000 Subject: [PATCH 34/39] Update nix-darwin --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 6dafbcef5..36f050644 100644 --- a/flake.lock +++ b/flake.lock @@ -71,11 +71,11 @@ ] }, "locked": { - "lastModified": 1762022020, - "narHash": "sha256-tNj4SqLu87rV3z2Pf1Zr3vC93zYyMuLif1qLhHmQl64=", + "lastModified": 1762039661, + "narHash": "sha256-oM5BwAGE78IBLZn+AqxwH/saqwq3e926rNq5HmOulkc=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "fc4e3dbe4039f8ff4fc303e50491ca8ba009ffd4", + "rev": "c3c8c9f2a5ed43175ac4dc030308756620e6e4e4", "type": "github" }, "original": { From 16917fd79be21ff71ab8365c9a46d90d7868e622 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Sun, 2 Nov 2025 10:01:50 +0000 Subject: [PATCH 35/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 0741f9d0e..65fcd724f 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761994314, - "narHash": "sha256-IOZofbuQ+gnM4t/nkN9wc1LvRDLKNhEftLILRBa+1Gc=", + "lastModified": 1762036413, + "narHash": "sha256-iW67/G1f+S2X/BX5k6uRXUsrLXGx3NPOx427PpaEgw4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1e0996604d71646c3061842452df7f03f3eb26ab", + "rev": "5466503e6db0ff1d73b4afc56678917e5ae840e6", "type": "github" }, "original": { From 4d6ab607935f01f9d7a5305a0cb9e59810b9c028 Mon Sep 17 00:00:00 2001 From: i18n Date: Sun, 2 Nov 2025 13:23:04 +0000 Subject: [PATCH 36/39] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20docs/site/getting-st?= =?UTF-8?q?arted/creating-your-first-clan.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/site/getting-started/creating-your-first-clan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/site/getting-started/creating-your-first-clan.md b/docs/site/getting-started/creating-your-first-clan.md index 0f0bdc2f9..199507d9b 100644 --- a/docs/site/getting-started/creating-your-first-clan.md +++ b/docs/site/getting-started/creating-your-first-clan.md @@ -51,7 +51,7 @@ Make sure you have the following: **Note:** This creates a new directory in your current location ```shellSession - nix run https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli --refresh -- flakes create + nix run "https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli" --refresh -- flakes create ``` 3. Enter a **name** in the prompt: From a569a1d147a2bb8699354547a9c89566d138e567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 2 Nov 2025 16:01:43 +0100 Subject: [PATCH 37/39] Fix vars upload for public vars with neededFor activation/partitioning When vars are marked with neededFor="activation" or "partitioning", they need to be available early in the boot process. However, the populate_dir methods in both sops and password_store secret backends were only calling self.get() which only retrieves secret vars from the .../secret path. This caused public vars (stored at .../value) to fail with "Secret does not exist" errors when trying to upload them. The fix uses file.value property instead, which properly delegates to the correct store (SecretStore or FactStore) based on whether the file is marked as secret or public. Fixes affected all neededFor phases in both backends: - sops: activation and partitioning phases - password_store: activation and partitioning phases --- pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py | 4 ++-- pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py b/pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py index ad76e6f3e..f40aab411 100644 --- a/pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py +++ b/pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py @@ -245,7 +245,7 @@ class SecretStore(StoreBase): output_dir / "activation" / generator.name / file.name ) out_file.parent.mkdir(parents=True, exist_ok=True) - out_file.write_bytes(self.get(generator, file.name)) + out_file.write_bytes(file.value) if "partitioning" in phases: for generator in vars_generators: for file in generator.files: @@ -254,7 +254,7 @@ class SecretStore(StoreBase): output_dir / "partitioning" / generator.name / file.name ) out_file.parent.mkdir(parents=True, exist_ok=True) - out_file.write_bytes(self.get(generator, file.name)) + out_file.write_bytes(file.value) hash_data = self.generate_hash(machine) if hash_data: diff --git a/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py b/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py index c09358d10..1e3b8a860 100644 --- a/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py +++ b/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py @@ -246,7 +246,7 @@ class SecretStore(StoreBase): ) # chmod after in case it doesn't have u+w target_path.touch(mode=0o600) - target_path.write_bytes(self.get(generator, file.name)) + target_path.write_bytes(file.value) target_path.chmod(file.mode) if "partitioning" in phases: @@ -260,7 +260,7 @@ class SecretStore(StoreBase): ) # chmod after in case it doesn't have u+w target_path.touch(mode=0o600) - target_path.write_bytes(self.get(generator, file.name)) + target_path.write_bytes(file.value) target_path.chmod(file.mode) @override From 40a8a823b863906be6543e62ef0162a950b8570d Mon Sep 17 00:00:00 2001 From: clan-bot Date: Sun, 2 Nov 2025 20:01:50 +0000 Subject: [PATCH 38/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 65fcd724f..6a64fb70b 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1762036413, - "narHash": "sha256-iW67/G1f+S2X/BX5k6uRXUsrLXGx3NPOx427PpaEgw4=", + "lastModified": 1762080734, + "narHash": "sha256-fFunzA7ITlPHRr7dECaFGTBucNiWYEVDNPBw/9gFmII=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5466503e6db0ff1d73b4afc56678917e5ae840e6", + "rev": "bc7f6fa86de9b208edf4ea7bbf40bcd8cc7d70a5", "type": "github" }, "original": { From e5105e31c4378426f439f3cc8583a104c1fd09b3 Mon Sep 17 00:00:00 2001 From: clan-bot Date: Mon, 3 Nov 2025 10:01:47 +0000 Subject: [PATCH 39/39] Update nixpkgs-dev in devFlake --- devFlake/flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devFlake/flake.lock b/devFlake/flake.lock index 6a64fb70b..f3ec3fe3e 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1762080734, - "narHash": "sha256-fFunzA7ITlPHRr7dECaFGTBucNiWYEVDNPBw/9gFmII=", + "lastModified": 1762125068, + "narHash": "sha256-G2flpMLVSk/EJ/HJ3YABZA0FgbWJcp1VKj0/yX3Fz48=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "bc7f6fa86de9b208edf4ea7bbf40bcd8cc7d70a5", + "rev": "cd46903a30b60161802ffe01728949e3385b983e", "type": "github" }, "original": {