From a89502e47f39399cee9955314b87c3039c0d6ade Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 1 Jul 2025 16:54:19 +0200 Subject: [PATCH 1/4] clanServices: add flake level exports --- clanModules/borgbackup/roles/client.nix | 1 - lib/modules/clan/interface.nix | 35 ++++ lib/modules/clan/module.nix | 7 +- .../all-services-wrapper.nix | 75 ++++++++ .../distributed-service/inventory-adapter.nix | 53 ++++-- .../distributed-service/service-module.nix | 36 ++++ .../distributed-service/tests/default.nix | 8 +- .../distributed-service/tests/exports.nix | 170 ++++++++++++++++++ .../tests/per_instance_args.nix | 12 +- .../tests/per_machine_args.nix | 8 +- 10 files changed, 373 insertions(+), 32 deletions(-) create mode 100644 lib/modules/inventory/distributed-service/all-services-wrapper.nix create mode 100644 lib/modules/inventory/distributed-service/tests/exports.nix diff --git a/clanModules/borgbackup/roles/client.nix b/clanModules/borgbackup/roles/client.nix index 35d6526c2..e2cb8e591 100644 --- a/clanModules/borgbackup/roles/client.nix +++ b/clanModules/borgbackup/roles/client.nix @@ -185,7 +185,6 @@ in ]; clan.core.vars.generators.borgbackup = { - files."borgbackup.ssh.pub".secret = false; files."borgbackup.ssh" = { }; files."borgbackup.repokey" = { }; diff --git a/lib/modules/clan/interface.nix b/lib/modules/clan/interface.nix index 02cbc7eb4..643be1d79 100644 --- a/lib/modules/clan/interface.nix +++ b/lib/modules/clan/interface.nix @@ -67,6 +67,41 @@ in ''; }; + # TODO: make this writable by moving the options from inventoryClass into clan. + exports = lib.mkOption { + readOnly = true; + }; + + exportsModule = lib.mkOption { + type = types.deferredModule; + # can be set only once + readOnly = true; + description = '' + A module that is used to define the module of flake level exports - + + such as 'exports.machines.' and 'exports.instances.' + + Example: + + ```nix + { + options.vars.generators = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submoduleWith { + modules = [ + { + options.script = lib.mkOption { type = lib.types.str; }; + } + ]; + } + ); + default = { }; + }; + } + ``` + ''; + }; + specialArgs = lib.mkOption { type = types.attrsOf types.raw; default = { }; diff --git a/lib/modules/clan/module.nix b/lib/modules/clan/module.nix index 6f47050cb..917468e0f 100644 --- a/lib/modules/clan/module.nix +++ b/lib/modules/clan/module.nix @@ -224,6 +224,8 @@ in inherit nixosConfigurations; inherit darwinConfigurations; + exports = config.clanInternals.inventoryClass.distributedServices.servicesEval.config.exports; + clanInternals = { inventoryClass = let @@ -244,10 +246,13 @@ in inherit inventory directory; } ( + let + clanConfig = config; + in { config, ... }: { distributedServices = clanLib.inventory.mapInstances { - inherit (config) inventory; + inherit (clanConfig) inventory exportsModule; inherit flakeInputs; clanCoreModules = clan-core.clan.modules; prefix = [ "distributedServices" ]; diff --git a/lib/modules/inventory/distributed-service/all-services-wrapper.nix b/lib/modules/inventory/distributed-service/all-services-wrapper.nix new file mode 100644 index 000000000..0f345da9c --- /dev/null +++ b/lib/modules/inventory/distributed-service/all-services-wrapper.nix @@ -0,0 +1,75 @@ +# Wraps all services in one fixed point module +{ + lib, + config, + specialArgs, + _ctx, + ... +}: +let + inherit (lib) mkOption types; + inherit (types) attrsWith submoduleWith; +in +{ + # TODO: merge these options into clan options + options = { + exportsModule = mkOption { + type = types.deferredModule; + readOnly = true; + }; + mappedServices = mkOption { + visible = false; + type = attrsWith { + placeholder = "mappedServiceName"; + elemType = submoduleWith { + modules = [ + ( + { name, ... }: + { + _module.args._ctx = [ name ]; + _module.args.exports' = config.exports; + } + ) + ./service-module.nix + # feature modules + (lib.modules.importApply ./api-feature.nix { + inherit (specialArgs) clanLib; + prefix = _ctx; + }) + ]; + }; + }; + default = { }; + }; + exports = mkOption { + type = submoduleWith { + modules = [ + { + options = { + instances = lib.mkOption { + # instances.... + type = types.attrsOf (submoduleWith { + modules = [ + config.exportsModule + ]; + }); + }; + # instances.... + machines = lib.mkOption { + type = types.attrsOf (submoduleWith { + modules = [ + config.exportsModule + ]; + }); + }; + }; + } + ] ++ lib.mapAttrsToList (_: service: service.exports) config.mappedServices; + }; + default = { }; + }; + debug = mkOption { + default = lib.mapAttrsToList (_: service: service.exports) config.mappedServices; + }; + }; +} diff --git a/lib/modules/inventory/distributed-service/inventory-adapter.nix b/lib/modules/inventory/distributed-service/inventory-adapter.nix index 15120bed3..b21dcc31c 100644 --- a/lib/modules/inventory/distributed-service/inventory-adapter.nix +++ b/lib/modules/inventory/distributed-service/inventory-adapter.nix @@ -26,6 +26,7 @@ in inventory, clanCoreModules, prefix ? [ ], + exportsModule, }: let # machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags; @@ -89,23 +90,6 @@ in } ) inventory.instances or { }; - # TODO: Eagerly check the _class of the resolved module - importedModulesEvaluated = lib.mapAttrs ( - module_ident: instances: - clanLib.evalService { - prefix = prefix ++ [ module_ident ]; - modules = - [ - # Import the resolved module. - # i.e. clan.modules.admin - (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; - # Group the instances by the module they resolve to # This is necessary to evaluate the module in a single pass # :: { _ :: [ { name, value } ] } @@ -133,9 +117,44 @@ in acc ++ [ eval.config.result.final.${machineName}.nixosModule or { } ] ) [ ] importedModulesEvaluated; }) inventory.machines or { }; + + evalServices = + { modules, prefix }: + lib.evalModules { + specialArgs = { + inherit clanLib; + _ctx = prefix; + }; + modules = [ + ./all-services-wrapper.nix + ] ++ modules; + }; + + servicesEval = evalServices { + inherit prefix; + modules = [ + { + inherit exportsModule; + mappedServices = lib.mapAttrs (_module_ident: instances: { + imports = + [ + # Import the resolved module. + # i.e. clan.modules.admin + (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 grouped allMachines diff --git a/lib/modules/inventory/distributed-service/service-module.nix b/lib/modules/inventory/distributed-service/service-module.nix index dc597fdf4..f088d7883 100644 --- a/lib/modules/inventory/distributed-service/service-module.nix +++ b/lib/modules/inventory/distributed-service/service-module.nix @@ -384,6 +384,10 @@ in type = types.deferredModuleWith { staticModules = [ ({ + options.exports = mkOption { + type = types.deferredModule; + default = { }; + }; options.nixosModule = mkOption { type = types.deferredModule; default = { }; @@ -514,6 +518,10 @@ in type = types.deferredModuleWith { staticModules = [ ({ + options.exports = mkOption { + type = types.deferredModule; + default = { }; + }; options.nixosModule = mkOption { type = types.deferredModule; default = { }; @@ -608,6 +616,34 @@ in modules = [ v ]; }).config; }; + + exports = mkOption { + default = { }; + type = types.submoduleWith { + # Static modules + modules = + [ + { + options.instances = mkOption { + type = types.attrsOf types.deferredModule; + }; + } + { + options.machines = mkOption { + type = types.attrsOf types.deferredModule; + }; + } + ] + ++ lib.mapAttrsToList (_roleName: role: { + instances = lib.mapAttrs (_instanceName: instance: { + imports = lib.mapAttrsToList (_machineName: v: v.exports) instance.allMachines; + }) role.allInstances; + }) config.result.allRoles + ++ lib.mapAttrsToList (machineName: machine: { + machines.${machineName} = machine.exports; + }) config.result.allMachines; + }; + }; # --- # Place the result in _module.result to mark them as "internal" and discourage usage/overrides # diff --git a/lib/modules/inventory/distributed-service/tests/default.nix b/lib/modules/inventory/distributed-service/tests/default.nix index 0ccbe3d7f..c8a3e1583 100644 --- a/lib/modules/inventory/distributed-service/tests/default.nix +++ b/lib/modules/inventory/distributed-service/tests/default.nix @@ -48,9 +48,11 @@ let clanCoreModules = { }; flakeInputs = flakeInputsFixture; inherit inventory; + exportsModule = { }; }; in { + exports = import ./exports.nix { inherit lib clanLib; }; resolve_module_spec = import ./import_module_spec.nix { inherit lib callInventoryAdapter; }; test_simple = let @@ -171,7 +173,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.config.instances; + expr = lib.attrNames res.importedModulesEvaluated.self-A.instances; expected = [ "instance_bar" "instance_foo" @@ -227,7 +229,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.config.result.allMachines; + expr = lib.attrNames res.importedModulesEvaluated.self-A.result.allMachines; expected = [ "jon" "sara" @@ -279,7 +281,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.config.result.allMachines; + expr = lib.attrNames res.importedModulesEvaluated.self-A.result.allMachines; expected = [ "jon" "sara" diff --git a/lib/modules/inventory/distributed-service/tests/exports.nix b/lib/modules/inventory/distributed-service/tests/exports.nix new file mode 100644 index 000000000..bc9c6cb54 --- /dev/null +++ b/lib/modules/inventory/distributed-service/tests/exports.nix @@ -0,0 +1,170 @@ +{ lib, clanLib }: +let + clan = clanLib.clan { + self = { }; + directory = ./.; + + exportsModule = { + options.vars.generators = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submoduleWith { + # TODO: import the vars submodule here + modules = [ + { + options.script = lib.mkOption { type = lib.types.str; }; + } + ]; + } + ); + default = { }; + }; + }; + + machines.jon = { }; + machines.sara = { }; + # A module that adds exports perMachine + modules.A = + { exports', ... }: + { + manifest.name = "A"; + roles.peer.perInstance = + { machine, ... }: + { + # Cross reference a perMachine exports + exports.vars.generators."${machine.name}-network-ip".script = + "A:" + exports'.machines.${machine.name}.vars.generators.key.script; + # Cross reference a perInstance exports from a different service + exports.vars.generators."${machine.name}-full-hostname".script = + "A:" + exports'.instances."B-1".vars.generators.hostname.script; + }; + roles.server = { }; + perMachine = + { machine, ... }: + { + exports = { + vars.generators.key.script = machine.name; + }; + }; + }; + # A module that adds exports perInstance + modules.B = { + manifest.name = "B"; + roles.peer.perInstance = + { instanceName, ... }: + { + exports = { + vars.generators.hostname.script = instanceName; + }; + }; + }; + + inventory = { + instances.B-1 = { + module.name = "B"; + module.input = "self"; + roles.peer.tags.all = { }; + }; + instances.B-2 = { + module.name = "B"; + module.input = "self"; + roles.peer.tags.all = { }; + }; + instances.A-1 = { + module.name = "A"; + module.input = "self"; + roles.peer.tags.all = { }; + roles.server.tags.all = { }; + }; + instances.A-2 = { + module.name = "A"; + module.input = "self"; + roles.peer.tags.all = { }; + roles.server.tags.all = { }; + }; + }; + }; +in +{ + test_1 = { + inherit clan; + expr = clan.config.exports; + expected = { + instances = { + A-1 = { + vars = { + generators = { + jon-full-hostname = { + script = "A:B-1"; + }; + jon-network-ip = { + script = "A:jon"; + }; + sara-full-hostname = { + script = "A:B-1"; + }; + sara-network-ip = { + script = "A:sara"; + }; + }; + }; + }; + A-2 = { + vars = { + generators = { + jon-full-hostname = { + script = "A:B-1"; + }; + jon-network-ip = { + script = "A:jon"; + }; + sara-full-hostname = { + script = "A:B-1"; + }; + sara-network-ip = { + script = "A:sara"; + }; + }; + }; + }; + B-1 = { + vars = { + generators = { + hostname = { + script = "B-1"; + }; + }; + }; + }; + B-2 = { + vars = { + generators = { + hostname = { + script = "B-2"; + }; + }; + }; + }; + }; + machines = { + jon = { + vars = { + generators = { + key = { + script = "jon"; + }; + }; + }; + }; + sara = { + vars = { + generators = { + key = { + script = "sara"; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/lib/modules/inventory/distributed-service/tests/per_instance_args.nix b/lib/modules/inventory/distributed-service/tests/per_instance_args.nix index b94697c59..05e1b16a2 100644 --- a/lib/modules/inventory/distributed-service/tests/per_instance_args.nix +++ b/lib/modules/inventory/distributed-service/tests/per_instance_args.nix @@ -106,7 +106,7 @@ in test_per_instance_arguments = { expr = { instanceName = - res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName; + res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName; # settings are specific. # Below we access: @@ -114,11 +114,11 @@ in # roles = peer # machines = jon settings = - res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings; + res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings; machine = - res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine; + res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine; roles = - res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles; + res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles; }; expected = { instanceName = "instance_foo"; @@ -161,9 +161,9 @@ in # TODO: Cannot be tested like this anymore test_per_instance_settings_vendoring = { - x = res.importedModulesEvaluated.self-A.config; + x = res.importedModulesEvaluated.self-A; expr = - res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings; + res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings; expected = { timeout = "config.thing"; }; diff --git a/lib/modules/inventory/distributed-service/tests/per_machine_args.nix b/lib/modules/inventory/distributed-service/tests/per_machine_args.nix index 444b94569..ac68631d9 100644 --- a/lib/modules/inventory/distributed-service/tests/per_machine_args.nix +++ b/lib/modules/inventory/distributed-service/tests/per_machine_args.nix @@ -81,7 +81,7 @@ in inherit res; expr = { hasMachineSettings = - res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon + res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon ? settings; # settings are specific. @@ -89,10 +89,10 @@ in # instance = instance_foo # roles = peer # machines = jon - specificMachineSettings = filterInternals res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings; + specificMachineSettings = filterInternals res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings; hasRoleSettings = - res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer + res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer ? settings; # settings are specific. @@ -100,7 +100,7 @@ in # instance = instance_foo # roles = peer # machines = * - specificRoleSettings = filterInternals res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.settings; + specificRoleSettings = filterInternals res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.settings; }; expected = { hasMachineSettings = true; From af06dec6f48eeea23052bb04f11109ec7e3ad41e Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 1 Jul 2025 17:35:04 +0200 Subject: [PATCH 2/4] clanServices: add test to ensure nixosModule is imported --- .../distributed-service/inventory-adapter.nix | 7 +-- .../distributed-service/tests/default.nix | 1 + .../tests/machine_imports.nix | 49 +++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 lib/modules/inventory/distributed-service/tests/machine_imports.nix diff --git a/lib/modules/inventory/distributed-service/inventory-adapter.nix b/lib/modules/inventory/distributed-service/inventory-adapter.nix index b21dcc31c..1c7e515dd 100644 --- a/lib/modules/inventory/distributed-service/inventory-adapter.nix +++ b/lib/modules/inventory/distributed-service/inventory-adapter.nix @@ -110,12 +110,13 @@ in } ) { } 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: eval: - acc ++ [ eval.config.result.final.${machineName}.nixosModule or { } ] - ) [ ] importedModulesEvaluated; + acc: _module_ident: serviceModule: + acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ] + ) [ ] servicesEval.config.mappedServices; }) inventory.machines or { }; evalServices = diff --git a/lib/modules/inventory/distributed-service/tests/default.nix b/lib/modules/inventory/distributed-service/tests/default.nix index c8a3e1583..9164c2e1d 100644 --- a/lib/modules/inventory/distributed-service/tests/default.nix +++ b/lib/modules/inventory/distributed-service/tests/default.nix @@ -288,6 +288,7 @@ 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; }; nested = import ./nested_services { inherit lib clanLib; }; diff --git a/lib/modules/inventory/distributed-service/tests/machine_imports.nix b/lib/modules/inventory/distributed-service/tests/machine_imports.nix new file mode 100644 index 000000000..a0d364d1a --- /dev/null +++ b/lib/modules/inventory/distributed-service/tests/machine_imports.nix @@ -0,0 +1,49 @@ +{ lib, clanLib }: +let + clan = clanLib.clan { + self = { }; + directory = ./.; + + machines.jon = { }; + machines.sara = { }; + # A module that adds exports perMachine + modules.A = + { ... }: + { + manifest.name = "A"; + roles.peer.perInstance = + { ... }: + { + nixosModule = { + options.bar = lib.mkOption { + default = 1; + }; + }; + }; + roles.server = { }; + perMachine = + { ... }: + { + nixosModule = { + options.foo = lib.mkOption { + default = 1; + }; + }; + }; + }; + inventory.instances.A = { + module.input = "self"; + roles.peer.tags.all = { }; + }; + }; +in +{ + test_1 = { + inherit clan; + expr = { inherit (clan.config.clanInternals.machines.x86_64-linux.jon.config) bar foo; }; + expected = { + foo = 1; + bar = 1; + }; + }; +} From 416d7891881b78e36cab5d48ae41380af2134af8 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 2 Jul 2025 10:22:09 +0200 Subject: [PATCH 3/4] flake-exports: add options documentation --- lib/modules/clan/interface.nix | 4 + .../distributed-service/service-module.nix | 156 +++++++++++++++--- lib/modules/inventory/flake-module.nix | 2 +- 3 files changed, 140 insertions(+), 22 deletions(-) diff --git a/lib/modules/clan/interface.nix b/lib/modules/clan/interface.nix index 643be1d79..3d8b851c0 100644 --- a/lib/modules/clan/interface.nix +++ b/lib/modules/clan/interface.nix @@ -70,9 +70,13 @@ in # TODO: make this writable by moving the options from inventoryClass into clan. exports = lib.mkOption { readOnly = true; + visible = false; + internal = true; }; exportsModule = lib.mkOption { + internal = true; + visible = false; type = types.deferredModule; # can be set only once readOnly = true; diff --git a/lib/modules/inventory/distributed-service/service-module.nix b/lib/modules/inventory/distributed-service/service-module.nix index f088d7883..4e8f27653 100644 --- a/lib/modules/inventory/distributed-service/service-module.nix +++ b/lib/modules/inventory/distributed-service/service-module.nix @@ -104,6 +104,13 @@ let in { options = { + # Option to disable some behavior during docs rendering + _docs_rendering = mkOption { + default = false; + visible = false; + type = types.bool; + }; + instances = mkOption { visible = false; defaultText = "Throws: 'The service must define its instances' when not defined"; @@ -387,6 +394,29 @@ in options.exports = mkOption { type = types.deferredModule; default = { }; + description = '' + export modules defined in 'perInstance' + mapped to their instance name + + Example + + with instances: + + ```nix + instances.A = { ... }; + instances.B= { ... }; + + roles.peer.perInstance = { instanceName, machine, ... }: + { + exports.foo = 1; + } + + This yields all other services can access these exports + => + exports.instances.A.foo = 1; + exports.instances.B.foo = 1; + ``` + ''; }; options.nixosModule = mkOption { type = types.deferredModule; @@ -521,6 +551,28 @@ in options.exports = mkOption { type = types.deferredModule; default = { }; + description = '' + export modules defined in 'perMachine' + mapped to their machine name + + Example + + with machines: + ```nix + instances.A = { roles.peer.machines.jon = ... }; + instances.B = { roles.peer.machines.jon = ... }; + + perMachine = { machine, ... }: + { + exports.foo = 1; + } + + This yields all other services can access these exports + => + exports.machines.jon.foo = 1; + exports.machines.sara.foo = 1; + ``` + ''; }; options.nixosModule = mkOption { type = types.deferredModule; @@ -618,30 +670,92 @@ in }; exports = mkOption { + description = '' + This services exports. + Gets merged with all other services exports + + Final value (merged and evaluated with other services) available as `exports'` in the arguments of this module. + + ```nix + { exports', ... }: { + _class = "clan.service"; + # ... + } + ``` + ''; default = { }; type = types.submoduleWith { # Static modules - modules = - [ - { - options.instances = mkOption { - type = types.attrsOf types.deferredModule; - }; - } - { - options.machines = mkOption { - type = types.attrsOf types.deferredModule; - }; - } - ] - ++ lib.mapAttrsToList (_roleName: role: { - instances = lib.mapAttrs (_instanceName: instance: { - imports = lib.mapAttrsToList (_machineName: v: v.exports) instance.allMachines; - }) role.allInstances; - }) config.result.allRoles - ++ lib.mapAttrsToList (machineName: machine: { - machines.${machineName} = machine.exports; - }) config.result.allMachines; + modules = [ + { + options.instances = mkOption { + type = types.attrsOf types.deferredModule; + description = '' + export modules defined in 'perInstance' + mapped to their instance name + + Example + + with instances: + + ```nix + instances.A = { ... }; + instances.B= { ... }; + + roles.peer.perInstance = { instanceName, machine, ... }: + { + exports.foo = 1; + } + + This yields all other services can access these exports + => + exports.instances.A.foo = 1; + exports.instances.B.foo = 1; + ``` + ''; + }; + options.machines = mkOption { + type = types.attrsOf types.deferredModule; + description = '' + export modules defined in 'perMachine' + mapped to their machine name + + Example + + with machines: + + ```nix + instances.A = { roles.peer.machines.jon = ... }; + instances.B = { roles.peer.machines.jon = ... }; + + perMachine = { machine, ... }: + { + exports.foo = 1; + } + + This yields all other services can access these exports + => + exports.machines.jon.foo = 1; + exports.machines.sara.foo = 1; + ``` + ''; + }; + # Lazy default via imports + # should probably be moved to deferredModuleWith { staticModules = [ ]; } + imports = + if config._docs_rendering then + [ ] + else + lib.mapAttrsToList (_roleName: role: { + instances = lib.mapAttrs (_instanceName: instance: { + imports = lib.mapAttrsToList (_machineName: v: v.exports) instance.allMachines; + }) role.allInstances; + }) config.result.allRoles + ++ lib.mapAttrsToList (machineName: machine: { + machines.${machineName} = machine.exports; + }) config.result.allMachines; + } + ]; }; }; # --- diff --git a/lib/modules/inventory/flake-module.nix b/lib/modules/inventory/flake-module.nix index 06824258d..081dcd661 100644 --- a/lib/modules/inventory/flake-module.nix +++ b/lib/modules/inventory/flake-module.nix @@ -47,7 +47,7 @@ in (pkgs.nixosOptionsDoc { options = (self.clanLib.evalService { - modules = [ ]; + modules = [ { _docs_rendering = true; } ]; prefix = [ ]; }).options; warningsAreErrors = true; From 2afc6538771dac549d35ca1dfa202a754c4c5b32 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 2 Jul 2025 10:32:56 +0200 Subject: [PATCH 4/4] clan/exportsModule: add missing default --- lib/modules/clan/interface.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/modules/clan/interface.nix b/lib/modules/clan/interface.nix index 3d8b851c0..5a8a8b4de 100644 --- a/lib/modules/clan/interface.nix +++ b/lib/modules/clan/interface.nix @@ -78,8 +78,7 @@ in internal = true; visible = false; type = types.deferredModule; - # can be set only once - readOnly = true; + default = { }; description = '' A module that is used to define the module of flake level exports -