diff --git a/topology/default.nix b/topology/default.nix index 810011a..f1fbb17 100644 --- a/topology/default.nix +++ b/topology/default.nix @@ -4,9 +4,9 @@ inputs: { }: inputs.nixpkgs.lib.evalModules { prefix = ["topology"]; - modules = [./modules] ++ modules; + modules = [./topology] ++ modules; specialArgs = { - modulesPath = builtins.toString ./modules; + modulesPath = builtins.toString ./topology; inherit pkgs; }; } diff --git a/topology/nixos/aggregators/disko.nix b/topology/nixos/extractors/disko.nix similarity index 100% rename from topology/nixos/aggregators/disko.nix rename to topology/nixos/extractors/disko.nix diff --git a/topology/nixos/aggregators/network-interfaces.nix b/topology/nixos/extractors/network-interfaces.nix similarity index 100% rename from topology/nixos/aggregators/network-interfaces.nix rename to topology/nixos/extractors/network-interfaces.nix diff --git a/topology/nixos/aggregators/node.nix b/topology/nixos/extractors/node.nix similarity index 100% rename from topology/nixos/aggregators/node.nix rename to topology/nixos/extractors/node.nix diff --git a/topology/nixos/aggregators/systemd.nix b/topology/nixos/extractors/systemd.nix similarity index 100% rename from topology/nixos/aggregators/systemd.nix rename to topology/nixos/extractors/systemd.nix diff --git a/topology/nixos/aggregators/wireguard.nix b/topology/nixos/extractors/wireguard.nix similarity index 100% rename from topology/nixos/aggregators/wireguard.nix rename to topology/nixos/extractors/wireguard.nix diff --git a/topology/nixos/module.nix b/topology/nixos/module.nix index 936eab2..3e7cb1a 100644 --- a/topology/nixos/module.nix +++ b/topology/nixos/module.nix @@ -5,17 +5,33 @@ }: let inherit (lib) + attrNames flip - filterAttrs - mapAttrs - mapAttrs' - mapAttrsToList - mkMerge + mkAliasOptionModule mkOption - nameValuePair types ; in { + imports = + [ + # Allow simple alias to set/get attributes of this node + (mkAliasOptionModule ["topology" "self"] ["topology" "nodes" config.topology.id]) + ] + ++ flip map (attrNames (builtins.readDir ../options)) (x: + import ../options/${x} ( + module: + module + // { + # Move options to subpath + options.topology = module.options or {}; + # Set the correct filename for diagnostics + _file = ../options/${x}; + # The config should only be applied on the toplevel topology module, + # not for each nixos node. + config = {}; + } + )); + options.topology = { id = mkOption { description = '' @@ -24,134 +40,57 @@ in { unique or don't reflect the name you use to refer to that node. ''; default = config.networking.hostName; - # TODO ensure unique across the board type = types.str; }; - - type = mkOption { - description = "TODO"; - default = "normal"; - type = types.enum ["normal" "microvm" "nixos-container"]; - }; - - guests = mkOption { - description = "TODO guests ids (topology.node..id) ensure exists"; - default = []; - type = types.listOf types.str; - }; - - disks = mkOption { - default = {}; - type = types.attrsOf (types.submodule (submod: { - options = { - name = mkOption { - description = "The name of this disk"; - default = submod.config._module.args.name; - readOnly = true; - type = types.str; - }; - }; - })); - }; - - interfaces = mkOption { - description = "TODO"; - default = {}; - type = types.attrsOf (types.submodule (submod: { - options = { - name = mkOption { - description = "The name of this interface"; - type = types.str; - readOnly = true; - default = submod.config._module.args.name; - }; - - mac = mkOption { - description = "The MAC address of this interface, if known."; - default = null; - type = types.nullOr types.str; - }; - - addresses = mkOption { - description = "The configured address(es), or a descriptive string (like DHCP)."; - type = types.listOf types.str; - }; - - network = mkOption { - description = '' - The global name of the attached/spanned network. - If this is given, this interface can be shown in the network graph. - ''; - default = null; - type = types.nullOr types.str; - }; - }; - })); - }; - - firewallRules = mkOption { - description = "TODO"; - default = {}; - type = types.attrsOf (types.submodule (submod: { - options = { - name = mkOption { - description = "The name of this firewall rule"; - type = types.str; - readOnly = true; - default = submod.config._module.args.name; - }; - - contents = mkOption { - description = "A human readable summary of this rule's effects"; - type = types.lines; - }; - }; - })); - }; }; - config.topology = mkMerge [ - { - ################### TODO user config! ################# - id = config.node.name; - ################### END user config ################# + config.topology = { + # Ensure a node exists for this host + nodes.${config.topology.id} = {}; + }; - guests = - flip mapAttrsToList (config.microvm.vms or {}) - (_: vmCfg: vmCfg.config.config.topology.id); - # TODO: container + #config.topology = mkMerge [ + # { + # ################### TODO user config! ################# + # id = config.node.name; + # ################### END user config ################# - disks = - flip mapAttrs (config.disko.devices.disk or {}) - (_: _: {}); - # TODO: zfs pools from disko / fileSystems - # TODO: microvm shares - # TODO: container shares - # TODO: OCI containers shares + # guests = + # flip mapAttrsToList (config.microvm.vms or {}) + # (_: vmCfg: vmCfg.config.config.topology.id); + # # TODO: container - interfaces = let - isNetwork = netDef: (netDef.matchConfig != {}) && (netDef.address != [] || netDef.DHCP != null); - macsByName = mapAttrs' (flip nameValuePair) (config.networking.renameInterfacesByMac or {}); - netNameFor = netName: netDef: - netDef.matchConfig.Name - or ( - if netDef ? matchConfig.MACAddress && macsByName ? ${netDef.matchConfig.MACAddress} - then macsByName.${netDef.matchConfig.MACAddress} - else lib.trace "Could not derive network name for systemd network ${netName} on host ${config.node.name}, using unit name as fallback." netName - ); - netMACFor = netDef: netDef.matchConfig.MACAddress or null; - networks = filterAttrs (_: isNetwork) (config.systemd.network.networks or {}); - in - flip mapAttrs' networks (netName: netDef: - nameValuePair (netNameFor netName netDef) { - mac = netMACFor netDef; - addresses = - if netDef.address != [] - then netDef.address - else ["DHCP"]; - }); + # disks = + # flip mapAttrs (config.disko.devices.disk or {}) + # (_: _: {}); + # # TODO: zfs pools from disko / fileSystems + # # TODO: microvm shares + # # TODO: container shares + # # TODO: OCI containers shares - # TODO: for each nftable zone show open ports - } - ]; + # interfaces = let + # isNetwork = netDef: (netDef.matchConfig != {}) && (netDef.address != [] || netDef.DHCP != null); + # macsByName = mapAttrs' (flip nameValuePair) (config.networking.renameInterfacesByMac or {}); + # netNameFor = netName: netDef: + # netDef.matchConfig.Name + # or ( + # if netDef ? matchConfig.MACAddress && macsByName ? ${netDef.matchConfig.MACAddress} + # then macsByName.${netDef.matchConfig.MACAddress} + # else lib.trace "Could not derive network name for systemd network ${netName} on host ${config.node.name}, using unit name as fallback." netName + # ); + # netMACFor = netDef: netDef.matchConfig.MACAddress or null; + # networks = filterAttrs (_: isNetwork) (config.systemd.network.networks or {}); + # in + # flip mapAttrs' networks (netName: netDef: + # nameValuePair (netNameFor netName netDef) { + # mac = netMACFor netDef; + # addresses = + # if netDef.address != [] + # then netDef.address + # else ["DHCP"]; + # }); + + # # TODO: for each nftable zone show open ports + # } + #]; } diff --git a/topology/options/devices.nix b/topology/options/devices.nix new file mode 100644 index 0000000..f2e03a6 --- /dev/null +++ b/topology/options/devices.nix @@ -0,0 +1,6 @@ +f: { + lib, + config, + ... +}: f { +} diff --git a/topology/options/networks.nix b/topology/options/networks.nix new file mode 100644 index 0000000..f2e03a6 --- /dev/null +++ b/topology/options/networks.nix @@ -0,0 +1,6 @@ +f: { + lib, + config, + ... +}: f { +} diff --git a/topology/options/nodes.nix b/topology/options/nodes.nix new file mode 100644 index 0000000..344a884 --- /dev/null +++ b/topology/options/nodes.nix @@ -0,0 +1,112 @@ +f: { + lib, + config, + ... +}: let + inherit + (lib) + mkOption + types + ; +in f { + options.nodes = mkOption { + default = {}; + description = '' + ''; + type = types.attrsOf (types.submodule (nodeSubmod: { + options = { + name = mkOption { + description = "The name of this node"; + default = nodeSubmod.config._module.args.name; + readOnly = true; + type = types.str; + }; + + type = mkOption { + description = "TODO"; + default = "normal"; + type = types.enum ["normal" "microvm" "nixos-container"]; + }; + + parent = mkOption { + description = "TODO guests ids (topology.node..id) ensure exists"; + default = []; + type = types.listOf types.str; + }; + + disks = mkOption { + default = {}; + type = types.attrsOf (types.submodule (submod: { + options = { + name = mkOption { + description = "The name of this disk"; + default = submod.config._module.args.name; + readOnly = true; + type = types.str; + }; + }; + })); + }; + + interfaces = mkOption { + description = "TODO"; + default = {}; + type = types.attrsOf (types.submodule (submod: { + options = { + name = mkOption { + description = "The name of this interface"; + type = types.str; + readOnly = true; + default = submod.config._module.args.name; + }; + + mac = mkOption { + description = "The MAC address of this interface, if known."; + default = null; + type = types.nullOr types.str; + }; + + addresses = mkOption { + description = "The configured address(es), or a descriptive string (like DHCP)."; + type = types.listOf types.str; + }; + + network = mkOption { + description = '' + The global name of the attached/spanned network. + If this is given, this interface can be shown in the network graph. + ''; + default = null; + type = types.nullOr types.str; + }; + }; + })); + }; + + firewallRules = mkOption { + description = "TODO"; + default = {}; + type = types.attrsOf (types.submodule (submod: { + options = { + name = mkOption { + description = "The name of this firewall rule"; + type = types.str; + readOnly = true; + default = submod.config._module.args.name; + }; + + contents = mkOption { + description = "A human readable summary of this rule's effects"; + type = types.lines; + }; + }; + })); + }; + }; + })); + }; + + config = { + # TODO: assertions = [] + }; +} diff --git a/topology/modules/default.nix b/topology/topology/default.nix similarity index 54% rename from topology/modules/default.nix rename to topology/topology/default.nix index 8daf1e3..fe14c20 100644 --- a/topology/modules/default.nix +++ b/topology/topology/default.nix @@ -6,17 +6,32 @@ inherit (lib) attrNames + concatLists filterAttrs + flip + getAttrFromPath literalExpression + mapAttrsToList mkDefault mkIf + mkMerge mkOption types ; availableRenderers = attrNames (filterAttrs (_: v: v == "directory") (builtins.readDir ./renderers)); in { - imports = map (x: ./renderers/${x}) (attrNames (builtins.readDir ./renderers)); + imports = + map (x: ./renderers/${x}) (attrNames (builtins.readDir ./renderers)) + ++ flip map (attrNames (builtins.readDir ../options)) (x: + import ../options/${x} ( + module: + module + // { + # Set the correct filename for diagnostics + _file = ../options/${x}; + } + )); options = { nixosConfigurations = mkOption { @@ -41,9 +56,22 @@ in { }; }; - config = { + config = let + # Aggregates all values for an option from each host + aggregate = optionPath: + mkMerge ( + concatLists (flip mapAttrsToList config.nixosConfigurations ( + name: cfg: + builtins.addErrorContext "while aggregating topology definitions from self.nixosConfigurations.${name} into toplevel topology:" ( + getAttrFromPath (["options" "topology"] ++ optionPath ++ ["definitions"]) cfg + ) + )) + ); + in { output = mkIf (config.renderer != null) (mkDefault config.renderers.${config.renderer}.output); + + nodes = aggregate ["nodes"]; }; } diff --git a/topology/modules/renderers/d2/default.nix b/topology/topology/renderers/d2/default.nix similarity index 100% rename from topology/modules/renderers/d2/default.nix rename to topology/topology/renderers/d2/default.nix diff --git a/topology/modules/renderers/d2/network.nix b/topology/topology/renderers/d2/network.nix similarity index 100% rename from topology/modules/renderers/d2/network.nix rename to topology/topology/renderers/d2/network.nix