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/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/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!"; }; } 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/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/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/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/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/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 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/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/devFlake/flake.lock b/devFlake/flake.lock index 3f2095faa..f3ec3fe3e 100644 --- a/devFlake/flake.lock +++ b/devFlake/flake.lock @@ -105,11 +105,11 @@ }, "nixpkgs-dev": { "locked": { - "lastModified": 1761544814, - "narHash": "sha256-t5f0A+2MtSWTfA6hzMNiotpIMGLlSQF2JnK9m6nkzIY=", + "lastModified": 1762125068, + "narHash": "sha256-G2flpMLVSk/EJ/HJ3YABZA0FgbWJcp1VKj0/yX3Fz48=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e5aa45ed6c45058ec109658b2b7352a9a062cdf3", + "rev": "cd46903a30b60161802ffe01728949e3385b983e", "type": "github" }, "original": { @@ -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": { 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: diff --git a/flake.lock b/flake.lock index b83c46db8..c8d260bb6 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": { @@ -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": { @@ -71,11 +71,11 @@ ] }, "locked": { - "lastModified": 1761339987, - "narHash": "sha256-IUaawVwItZKi64IA6kF6wQCLCzpXbk2R46dHn8sHkig=", + "lastModified": 1762039661, + "narHash": "sha256-oM5BwAGE78IBLZn+AqxwH/saqwq3e926rNq5HmOulkc=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "7cd9aac79ee2924a85c211d21fafd394b06a38de", + "rev": "c3c8c9f2a5ed43175ac4dc030308756620e6e4e4", "type": "github" }, "original": { diff --git a/flakeModules/clan.nix b/flakeModules/clan.nix index 76eb2177b..ec45927b7 100644 --- a/flakeModules/clan.nix +++ b/flakeModules/clan.nix @@ -39,32 +39,10 @@ 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; - --- - ''; - }; - } ]; }; - 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/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/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/all-services-wrapper.nix b/lib/inventory/distributed-service/all-services-wrapper.nix index 7da8d68b6..65cd40fb4 100644 --- a/lib/inventory/distributed-service/all-services-wrapper.nix +++ b/lib/inventory/distributed-service/all-services-wrapper.nix @@ -28,19 +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 ]; - _module.args.clanLib = specialArgs.clanLib; - _module.args.exports = config.exports; - _module.args.directory = directory; - } ) ./service-module.nix 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 diff --git a/lib/inventory/distributed-service/inventory-adapter.nix b/lib/inventory/distributed-service/inventory-adapter.nix deleted file mode 100644 index 8d82ed97d..000000000 --- a/lib/inventory/distributed-service/inventory-adapter.nix +++ /dev/null @@ -1,171 +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; - } - ]; - }; - importedModulesEvaluated = servicesEval.config.mappedServices; - - in - { - inherit - servicesEval - importedModuleWithInstances - # Exposed for testing - grouped - allMachines - importedModulesEvaluated - ; - }; -} 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 diff --git a/lib/inventory/distributed-service/tests/default.nix b/lib/inventory/distributed-service/tests/default.nix index 6a7beb4db..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.importedModulesEvaluated ? "-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.importedModulesEvaluated.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.importedModulesEvaluated.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.importedModulesEvaluated.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 6795c0ebc..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.importedModulesEvaluated.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.importedModulesEvaluated.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.importedModulesEvaluated.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.importedModulesEvaluated.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.importedModulesEvaluated.self-A; + x = res.config._services.mappedServices.self-A; expr = - res.importedModulesEvaluated.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 ac98a9ffe..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.importedModulesEvaluated.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.importedModulesEvaluated.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.importedModulesEvaluated.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.importedModulesEvaluated.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 = { 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/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 af742cbcc..10392376a 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"; @@ -6,7 +17,9 @@ inherit (clan-core) clanLib; }; imports = [ + ./top-level-interface.nix ./module.nix - ./interface.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/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/modules/clan/module.nix b/modules/clan/module.nix index cdd616b40..26739055c 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}; }) ]; @@ -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. @@ -176,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 @@ -216,8 +219,6 @@ in inherit nixosConfigurations; inherit darwinConfigurations; - exports = config.clanInternals.inventoryClass.distributedServices.servicesEval.config.exports; - clanInternals = { inventoryClass = let @@ -251,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/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, ... 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 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; }; 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; } 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 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 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: 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; }; 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";