diff --git a/generate-topology.nix b/generate-topology.nix
index 6162d39..bae90be 100644
--- a/generate-topology.nix
+++ b/generate-topology.nix
@@ -52,130 +52,31 @@
nodesById = mapAttrs' (_: node: nameValuePair node.config.topology.id node) nixosConfigurations;
- xmlAttrs = attrs: concatStringsSep " " (mapAttrsToList (n: v: "${n}=\"${v}\"") attrs);
- font = attrs: text: "${text}";
- fontMono = {face = "JetBrains Mono";};
- mono = font fontMono;
- monoColor = color: font (fontMono // {inherit color;});
-
- mkCell = cellAttrs: text: "
${text} | ";
- mapToTableRows = xs: {
- columnOrder,
- columns,
- titleRow ? true,
- titleRowColor ? colors.base0C,
- titleRowAttrs ? {bgcolor = titleRowColor;},
- alternateRowAttrs ? {bgcolor = colors.base03b;},
- }:
- concatLines (
- optional titleRow "${concatStringsSep "" (flip map columnOrder (c: mkCell titleRowAttrs "${mono columns.${c}.title}"))}
"
- ++ flip imap0 xs (
- i: x: "${concatStringsSep "" (flip map columnOrder (c:
- mkCell
- (optionalAttrs (pkgs.lib.mod i 2 == 1) alternateRowAttrs // (columns.${c}.cellAttrs or {}))
- (columns.${c}.transform x.${c})))}
"
- )
- );
-
- mkTable = xs: settings: ''
-
- ${mapToTableRows xs settings}
-
- '';
-
- nodeId = str: "\"${escapeXML str}\"";
isGuestOfAny = node: any (x: elem node x.config.topology.guests) (attrValues nodesById);
rootNodes = filterAttrs (n: _: !(isGuestOfAny n)) nodesById;
- toDot = node: let
+ toD2 = node: let
topo = node.config.topology;
+ in ''
+ ${topo.id}: |md
+ # ${topo.id}
- diskTable = mkTable (attrValues topo.disks) {
- titleRowColor = colors.base0F;
- columnOrder = ["name"];
- columns = {
- name = {
- title = "Name";
- transform = mono;
- };
- };
- };
+ ## Guests:
+ ${concatLines (map (x: "- ${x}") topo.guests)}
- interfaceTable = mkTable (attrValues topo.interfaces) {
- titleRowColor = colors.base0D;
- columnOrder = ["name" "mac" "addresses"];
- columns = {
- name = {
- title = "Name";
- transform = x:
- if x == null
- then ""
- else mono x;
- };
- mac = {
- title = "MAC";
- transform = x:
- if x == null
- then ""
- else monoColor colors.base09 x;
- };
- addresses = {
- title = "Addr";
- transform = xs: mono (concatStringsSep " " xs);
- };
- };
- };
- in
- ''
- subgraph ${nodeId "cluster_${topo.id}"} {
- color = "${colors.base04}";
+ ## Disks:
+ ${concatLines (mapAttrsToList (_: v: "- ${v.name}") topo.disks)}
- ${nodeId topo.id} [label=<
-
- ${mono "Attribute"} | ${mono "Value"} |
- ${mono "id"} | ${mono topo.id} |
- ${mono "type"} | ${mono topo.type} |
-
- >];
+ ## Interfaces:
+ ${concatLines (mapAttrsToList (_: v: "- ${v.name}, mac ${toString v.mac}, addrs ${toString v.addresses}, network ${toString v.network}") topo.interfaces)}
- {
- rank = "same";
- ${nodeId "${topo.id}.disks"} [label=<
- ${diskTable}
- >];
- ${nodeId "${topo.id}.interfaces"} [label=<
- ${interfaceTable}
- >];
- }
+ ## Firewall Zones:
+ ${concatLines (mapAttrsToList (_: v: "- ${v.name}, mac ${toString v.mac}, addrs ${toString v.addresses}, network ${toString v.network}") topo.firewallRules)}
+ |
+ '';
- ${nodeId topo.id} -> ${nodeId "${topo.id}.disks"} [label="disks", color="${colors.base05}", fontcolor="${colors.base06}"];
- ${nodeId topo.id} -> ${nodeId "${topo.id}.interfaces"} [label="interfaces", color="${colors.base05}", fontcolor="${colors.base06}"];
- ''
- + optionalString (topo.guests != []) ''
- subgraph ${nodeId "cluster_guests_${topo.id}"} {
- color = "${colors.base04}";
- {
- rank = "same";
- ${concatLines (map (guest: "${nodeId guest};") topo.guests)}
- }
-
- ${concatLines (map (guest: dotForNodes.${guest}) topo.guests)}
- };
-
- ${concatLines (map (guest: "${nodeId topo.id} -> ${nodeId guest} [color=\"${colors.base05}\"];") topo.guests)}
- }
- ''
- + optionalString (!isGuestOfAny topo.id) ''
- root -> ${nodeId topo.id} [color="${colors.base05}"];
- '';
-
- dotForNodes = mapAttrs' (_: node: nameValuePair node.config.topology.id (toDot node)) nodesById;
+ d2ForNodes = mapAttrs' (_: node: nameValuePair node.config.topology.id (toD2 node)) nodesById;
in
- pkgs.writeText "topology.dot" ''
- digraph G {
- graph [rankdir=TB, splines=spline, bgcolor="${colors.base00}"];
- node [shape=plaintext, fontcolor="${colors.base06}", color="${colors.base06}"];
-
- ${concatLines (map (x: dotForNodes.${x}) (attrNames rootNodes))}
- }
+ pkgs.writeText "topology.d2" ''
+ ${concatLines (map (x: d2ForNodes.${x}) (attrNames rootNodes))}
''
diff --git a/modules/topology.nix b/modules/topology.nix
index 84f82d9..936eab2 100644
--- a/modules/topology.nix
+++ b/modules/topology.nix
@@ -19,24 +19,44 @@ in {
options.topology = {
id = mkOption {
description = ''
- The attribute name in nixosConfigurations corresponding to this host.
- Please overwrite with a unique identifier if your hostnames are not
+ The attribute name in the given `nodes` which corresponds to this host.
+ Please overwrite it with a unique identifier if your hostnames are not
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;
};
- guests = mkOption {
- description = "TODO guests ids (topology.id)";
- type = types.listOf types.str;
- default = [];
- };
+
type = mkOption {
description = "TODO";
- type = types.enum ["normal" "microvm" "nixos-container"];
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 {
@@ -48,30 +68,45 @@ in {
mac = mkOption {
description = "The MAC address of this interface, if known.";
- type = types.nullOr types.str;
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;
+ };
};
}));
- default = {};
};
- disks = mkOption {
+
+ firewallRules = mkOption {
+ description = "TODO";
+ default = {};
type = types.attrsOf (types.submodule (submod: {
options = {
name = mkOption {
- description = "The name of this disk";
+ 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;
+ };
};
}));
- default = {};
};
};
@@ -89,8 +124,10 @@ in {
disks =
flip mapAttrs (config.disko.devices.disk or {})
(_: _: {});
+ # TODO: zfs pools from disko / fileSystems
# TODO: microvm shares
# TODO: container shares
+ # TODO: OCI containers shares
interfaces = let
isNetwork = netDef: (netDef.matchConfig != {}) && (netDef.address != [] || netDef.DHCP != null);