mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
feat(topology): add auto-aggregation and dual-definition of options
This commit is contained in:
parent
67063adf24
commit
a737071162
13 changed files with 223 additions and 132 deletions
|
@ -4,9 +4,9 @@ inputs: {
|
||||||
}:
|
}:
|
||||||
inputs.nixpkgs.lib.evalModules {
|
inputs.nixpkgs.lib.evalModules {
|
||||||
prefix = ["topology"];
|
prefix = ["topology"];
|
||||||
modules = [./modules] ++ modules;
|
modules = [./topology] ++ modules;
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
modulesPath = builtins.toString ./modules;
|
modulesPath = builtins.toString ./topology;
|
||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,17 +5,33 @@
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
|
attrNames
|
||||||
flip
|
flip
|
||||||
filterAttrs
|
mkAliasOptionModule
|
||||||
mapAttrs
|
|
||||||
mapAttrs'
|
|
||||||
mapAttrsToList
|
|
||||||
mkMerge
|
|
||||||
mkOption
|
mkOption
|
||||||
nameValuePair
|
|
||||||
types
|
types
|
||||||
;
|
;
|
||||||
in {
|
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 = {
|
options.topology = {
|
||||||
id = mkOption {
|
id = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
|
@ -24,134 +40,57 @@ in {
|
||||||
unique or don't reflect the name you use to refer to that node.
|
unique or don't reflect the name you use to refer to that node.
|
||||||
'';
|
'';
|
||||||
default = config.networking.hostName;
|
default = config.networking.hostName;
|
||||||
# TODO ensure unique across the board
|
|
||||||
type = types.str;
|
type = types.str;
|
||||||
};
|
};
|
||||||
|
|
||||||
type = mkOption {
|
|
||||||
description = "TODO";
|
|
||||||
default = "normal";
|
|
||||||
type = types.enum ["normal" "microvm" "nixos-container"];
|
|
||||||
};
|
|
||||||
|
|
||||||
guests = mkOption {
|
|
||||||
description = "TODO guests ids (topology.node.<name>.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 [
|
config.topology = {
|
||||||
{
|
# Ensure a node exists for this host
|
||||||
################### TODO user config! #################
|
nodes.${config.topology.id} = {};
|
||||||
id = config.node.name;
|
};
|
||||||
################### END user config #################
|
|
||||||
|
|
||||||
guests =
|
#config.topology = mkMerge [
|
||||||
flip mapAttrsToList (config.microvm.vms or {})
|
# {
|
||||||
(_: vmCfg: vmCfg.config.config.topology.id);
|
# ################### TODO user config! #################
|
||||||
# TODO: container
|
# id = config.node.name;
|
||||||
|
# ################### END user config #################
|
||||||
|
|
||||||
disks =
|
# guests =
|
||||||
flip mapAttrs (config.disko.devices.disk or {})
|
# flip mapAttrsToList (config.microvm.vms or {})
|
||||||
(_: _: {});
|
# (_: vmCfg: vmCfg.config.config.topology.id);
|
||||||
# TODO: zfs pools from disko / fileSystems
|
# # TODO: container
|
||||||
# TODO: microvm shares
|
|
||||||
# TODO: container shares
|
|
||||||
# TODO: OCI containers shares
|
|
||||||
|
|
||||||
interfaces = let
|
# disks =
|
||||||
isNetwork = netDef: (netDef.matchConfig != {}) && (netDef.address != [] || netDef.DHCP != null);
|
# flip mapAttrs (config.disko.devices.disk or {})
|
||||||
macsByName = mapAttrs' (flip nameValuePair) (config.networking.renameInterfacesByMac or {});
|
# (_: _: {});
|
||||||
netNameFor = netName: netDef:
|
# # TODO: zfs pools from disko / fileSystems
|
||||||
netDef.matchConfig.Name
|
# # TODO: microvm shares
|
||||||
or (
|
# # TODO: container shares
|
||||||
if netDef ? matchConfig.MACAddress && macsByName ? ${netDef.matchConfig.MACAddress}
|
# # TODO: OCI containers shares
|
||||||
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
|
# 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
|
||||||
|
# }
|
||||||
|
#];
|
||||||
}
|
}
|
||||||
|
|
6
topology/options/devices.nix
Normal file
6
topology/options/devices.nix
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
f: {
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: f {
|
||||||
|
}
|
6
topology/options/networks.nix
Normal file
6
topology/options/networks.nix
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
f: {
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: f {
|
||||||
|
}
|
112
topology/options/nodes.nix
Normal file
112
topology/options/nodes.nix
Normal file
|
@ -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.<name>.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 = []
|
||||||
|
};
|
||||||
|
}
|
|
@ -6,17 +6,32 @@
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
attrNames
|
attrNames
|
||||||
|
concatLists
|
||||||
filterAttrs
|
filterAttrs
|
||||||
|
flip
|
||||||
|
getAttrFromPath
|
||||||
literalExpression
|
literalExpression
|
||||||
|
mapAttrsToList
|
||||||
mkDefault
|
mkDefault
|
||||||
mkIf
|
mkIf
|
||||||
|
mkMerge
|
||||||
mkOption
|
mkOption
|
||||||
types
|
types
|
||||||
;
|
;
|
||||||
|
|
||||||
availableRenderers = attrNames (filterAttrs (_: v: v == "directory") (builtins.readDir ./renderers));
|
availableRenderers = attrNames (filterAttrs (_: v: v == "directory") (builtins.readDir ./renderers));
|
||||||
in {
|
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 = {
|
options = {
|
||||||
nixosConfigurations = mkOption {
|
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 =
|
output =
|
||||||
mkIf (config.renderer != null)
|
mkIf (config.renderer != null)
|
||||||
(mkDefault config.renderers.${config.renderer}.output);
|
(mkDefault config.renderers.${config.renderer}.output);
|
||||||
|
|
||||||
|
nodes = aggregate ["nodes"];
|
||||||
};
|
};
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue