mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
chore: play with topology generation in graphviz
This commit is contained in:
parent
fbab6415ca
commit
714dec1c33
6 changed files with 321 additions and 99 deletions
18
flake.nix
18
flake.nix
|
@ -152,18 +152,6 @@
|
||||||
nixosConfigurationsMinimal
|
nixosConfigurationsMinimal
|
||||||
;
|
;
|
||||||
|
|
||||||
# XXX: WIP: only testing
|
|
||||||
d2diag = let
|
|
||||||
inherit
|
|
||||||
(nixpkgs.lib)
|
|
||||||
attrValues
|
|
||||||
concatLines
|
|
||||||
;
|
|
||||||
in
|
|
||||||
self.pkgs.x86_64-linux.writeText "test.d2" (
|
|
||||||
concatLines (map (x: x.config.d2diag.text) (attrValues self.nixosConfigurations))
|
|
||||||
);
|
|
||||||
|
|
||||||
# All nixosSystem instanciations are collected here, so that we can refer
|
# All nixosSystem instanciations are collected here, so that we can refer
|
||||||
# to any system via nodes.<name>
|
# to any system via nodes.<name>
|
||||||
nodes = self.nixosConfigurations // self.guestConfigs;
|
nodes = self.nixosConfigurations // self.guestConfigs;
|
||||||
|
@ -195,6 +183,12 @@
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# XXX: WIP: only testing
|
||||||
|
topology = import ./generate-topology.nix {
|
||||||
|
inherit pkgs;
|
||||||
|
nixosConfigurations = self.nodes;
|
||||||
|
};
|
||||||
|
|
||||||
# For each major system, we provide a customized installer image that
|
# For each major system, we provide a customized installer image that
|
||||||
# has ssh and some other convenience stuff preconfigured.
|
# has ssh and some other convenience stuff preconfigured.
|
||||||
# Not strictly necessary for new setups.
|
# Not strictly necessary for new setups.
|
||||||
|
|
185
generate-topology.nix
Normal file
185
generate-topology.nix
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
renderer ? "graphviz",
|
||||||
|
nixosConfigurations,
|
||||||
|
}: let
|
||||||
|
inherit
|
||||||
|
(pkgs.lib)
|
||||||
|
any
|
||||||
|
attrNames
|
||||||
|
attrValues
|
||||||
|
concatLines
|
||||||
|
concatMapStrings
|
||||||
|
concatStringsSep
|
||||||
|
const
|
||||||
|
elem
|
||||||
|
escapeXML
|
||||||
|
flip
|
||||||
|
filterAttrs
|
||||||
|
id
|
||||||
|
imap0
|
||||||
|
mapAttrs
|
||||||
|
mapAttrs'
|
||||||
|
nameValuePair
|
||||||
|
mapAttrsToList
|
||||||
|
optional
|
||||||
|
optionalAttrs
|
||||||
|
optionalString
|
||||||
|
optionals
|
||||||
|
;
|
||||||
|
|
||||||
|
global = {
|
||||||
|
# global entities;
|
||||||
|
};
|
||||||
|
|
||||||
|
asjson = builtins.toFile "topology.dot" (
|
||||||
|
builtins.toJSON (map (x: x.config.topology) (attrValues nixosConfigurations))
|
||||||
|
);
|
||||||
|
|
||||||
|
colors.base00 = "#101419";
|
||||||
|
colors.base01 = "#171B20";
|
||||||
|
colors.base02 = "#21262e";
|
||||||
|
colors.base03 = "#242931";
|
||||||
|
colors.base03b = "#353c48";
|
||||||
|
colors.base04 = "#485263";
|
||||||
|
colors.base05 = "#b6beca";
|
||||||
|
colors.base06 = "#dee1e6";
|
||||||
|
colors.base07 = "#e3e6eb";
|
||||||
|
colors.base08 = "#e05f65";
|
||||||
|
colors.base09 = "#f9a872";
|
||||||
|
colors.base0A = "#f1cf8a";
|
||||||
|
colors.base0B = "#78dba9";
|
||||||
|
colors.base0C = "#74bee9";
|
||||||
|
colors.base0D = "#70a5eb";
|
||||||
|
colors.base0E = "#c68aee";
|
||||||
|
colors.base0F = "#9378de";
|
||||||
|
|
||||||
|
nodesById = mapAttrs' (_: node: nameValuePair node.config.topology.id node) nixosConfigurations;
|
||||||
|
|
||||||
|
xmlAttrs = attrs: concatStringsSep " " (mapAttrsToList (n: v: "${n}=\"${v}\"") attrs);
|
||||||
|
font = attrs: text: "<font ${xmlAttrs attrs}>${text}</font>";
|
||||||
|
fontMono = {face = "JetBrains Mono";};
|
||||||
|
mono = font fontMono;
|
||||||
|
monoColor = color: font (fontMono // {color = color;});
|
||||||
|
|
||||||
|
mkCell = cellAttrs: text: "<td ${xmlAttrs cellAttrs}>${text}</td>";
|
||||||
|
mapToTableRows = xs: {
|
||||||
|
columnOrder,
|
||||||
|
columns,
|
||||||
|
titleRow ? true,
|
||||||
|
titleRowColor ? colors.base0C,
|
||||||
|
titleRowAttrs ? {bgcolor = titleRowColor;},
|
||||||
|
alternateRowAttrs ? {bgcolor = colors.base03b;},
|
||||||
|
}:
|
||||||
|
concatLines (
|
||||||
|
optional titleRow "<tr>${concatStringsSep "" (flip map columnOrder (c: mkCell titleRowAttrs "<b>${mono columns.${c}.title}</b>"))}</tr>"
|
||||||
|
++ flip imap0 xs (
|
||||||
|
i: x: "<tr>${concatStringsSep "" (flip map columnOrder (c:
|
||||||
|
mkCell
|
||||||
|
(optionalAttrs (pkgs.lib.mod i 2 == 1) alternateRowAttrs // (columns.${c}.cellAttrs or {}))
|
||||||
|
(columns.${c}.transform x.${c})))}</tr>"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
mkTable = xs: settings: ''
|
||||||
|
<table border="0" cellborder="0" cellspacing="0" cellpadding="4" bgcolor="${colors.base03}" color="${colors.base04}">
|
||||||
|
${mapToTableRows xs settings}
|
||||||
|
</table>
|
||||||
|
'';
|
||||||
|
|
||||||
|
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
|
||||||
|
topo = node.config.topology;
|
||||||
|
|
||||||
|
diskTable = mkTable (attrValues topo.disks) {
|
||||||
|
titleRowColor = colors.base0F;
|
||||||
|
columnOrder = ["name"];
|
||||||
|
columns = {
|
||||||
|
name = {
|
||||||
|
title = "Name";
|
||||||
|
transform = mono;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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}";
|
||||||
|
|
||||||
|
${nodeId topo.id} [label=<
|
||||||
|
<table border="0" cellborder="0" cellspacing="0" cellpadding="4" bgcolor="${colors.base03}" color="${colors.base04}">
|
||||||
|
<tr><td bgcolor="${colors.base08}"><b>${mono "Attribute"}</b></td><td bgcolor="${colors.base08}"><b>${mono "Value"}</b></td></tr>
|
||||||
|
<tr><td>${mono "id"}</td><td>${mono topo.id}</td></tr>
|
||||||
|
<tr><td>${mono "type"}</td><td>${mono topo.type}</td></tr>
|
||||||
|
</table>
|
||||||
|
>];
|
||||||
|
|
||||||
|
{
|
||||||
|
rank = "same";
|
||||||
|
${nodeId "${topo.id}.disks"} [label=<
|
||||||
|
${diskTable}
|
||||||
|
>];
|
||||||
|
${nodeId "${topo.id}.interfaces"} [label=<
|
||||||
|
${interfaceTable}
|
||||||
|
>];
|
||||||
|
}
|
||||||
|
|
||||||
|
${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;
|
||||||
|
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))}
|
||||||
|
}
|
||||||
|
''
|
|
@ -1,85 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
nodes,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit
|
|
||||||
(lib)
|
|
||||||
attrNames
|
|
||||||
concatMap
|
|
||||||
getAttrFromPath
|
|
||||||
mkMerge
|
|
||||||
mkOption
|
|
||||||
optionals
|
|
||||||
types
|
|
||||||
;
|
|
||||||
|
|
||||||
nodeName = config.node.name;
|
|
||||||
in {
|
|
||||||
options.d2diag.text = mkOption {
|
|
||||||
# TODO readonly, _text
|
|
||||||
description = "TODO";
|
|
||||||
type = types.lines;
|
|
||||||
};
|
|
||||||
|
|
||||||
options.d2diag.services = mkOption {
|
|
||||||
description = "TODO";
|
|
||||||
type = types.attrsOf (types.submodule {
|
|
||||||
options = {
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
d2diag.text =
|
|
||||||
''
|
|
||||||
${nodeName}: ${nodeName} {
|
|
||||||
disks: Disks {
|
|
||||||
shape: sql_table
|
|
||||||
${lib.concatLines (map (x: "${x}: 8TB") (lib.attrNames config.disko.devices.disk))}
|
|
||||||
}
|
|
||||||
net: Interfaces {
|
|
||||||
shape: sql_table
|
|
||||||
${lib.concatLines (lib.mapAttrsToList (n: v: ''${n}: ${v.mac}'') (config.repo.secrets.local.networking.interfaces or {lan.mac = "?";}))}
|
|
||||||
}
|
|
||||||
''
|
|
||||||
+ (lib.optionalString (config.guests != {}) ''
|
|
||||||
guests: {
|
|
||||||
${
|
|
||||||
lib.concatLines (
|
|
||||||
lib.flip lib.mapAttrsToList config.guests (
|
|
||||||
guestName: guestDef:
|
|
||||||
(
|
|
||||||
if guestDef.backend == "microvm"
|
|
||||||
then config.microvm.vms.${guestName}.config
|
|
||||||
else config.containers.${guestName}.nixosConfiguration
|
|
||||||
)
|
|
||||||
.config
|
|
||||||
.d2diag
|
|
||||||
.text
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
${
|
|
||||||
lib.concatLines (
|
|
||||||
lib.flip lib.mapAttrsToList config.guests (
|
|
||||||
guestName: guestDef: "net.lan -> guests.${(
|
|
||||||
if guestDef.backend == "microvm"
|
|
||||||
then config.microvm.vms.${guestName}.config
|
|
||||||
else config.containers.${guestName}.nixosConfiguration
|
|
||||||
)
|
|
||||||
.config
|
|
||||||
.node
|
|
||||||
.name}.net.lan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
'')
|
|
||||||
+ ''
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -41,7 +41,7 @@
|
||||||
./wireguard-proxy.nix
|
./wireguard-proxy.nix
|
||||||
./wireguard.nix
|
./wireguard.nix
|
||||||
|
|
||||||
./d2diag.nix
|
./topology.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
nixpkgs.overlays = [
|
nixpkgs.overlays = [
|
||||||
|
|
|
@ -72,6 +72,8 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
networking.renameInterfacesByMac.${guestCfg.networking.mainLinkName} = guestCfg.microvm.mac;
|
networking.renameInterfacesByMac.${guestCfg.networking.mainLinkName} = guestCfg.microvm.mac;
|
||||||
systemd.network.networks."10-${guestCfg.networking.mainLinkName}".matchConfig.MACAddress = guestCfg.microvm.mac;
|
systemd.network.networks."10-${guestCfg.networking.mainLinkName}".matchConfig = mkForce {
|
||||||
|
MACAddress = guestCfg.microvm.mac;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
126
modules/topology.nix
Normal file
126
modules/topology.nix
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit
|
||||||
|
(lib)
|
||||||
|
attrNames
|
||||||
|
concatMap
|
||||||
|
flip
|
||||||
|
filterAttrs
|
||||||
|
getAttrFromPath
|
||||||
|
mapAttrs
|
||||||
|
mapAttrs'
|
||||||
|
mapAttrsToList
|
||||||
|
mkMerge
|
||||||
|
mkOption
|
||||||
|
nameValuePair
|
||||||
|
optionals
|
||||||
|
types
|
||||||
|
;
|
||||||
|
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
|
||||||
|
unique or don't reflect the name you use to refer to that node.
|
||||||
|
'';
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
interfaces = mkOption {
|
||||||
|
description = "TODO";
|
||||||
|
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.";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
addresses = mkOption {
|
||||||
|
description = "The configured address(es), or a descriptive string (like DHCP).";
|
||||||
|
type = types.listOf types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
disks = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule (submod: {
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "The name of this disk";
|
||||||
|
type = types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = submod.config._module.args.name;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.topology = mkMerge [
|
||||||
|
{
|
||||||
|
################### TODO user config! #################
|
||||||
|
id = config.node.name;
|
||||||
|
################### END user config #################
|
||||||
|
|
||||||
|
guests =
|
||||||
|
flip mapAttrsToList (config.microvm.vms or {})
|
||||||
|
(_: vmCfg: vmCfg.config.config.topology.id);
|
||||||
|
# TODO: container
|
||||||
|
|
||||||
|
disks =
|
||||||
|
flip mapAttrs (config.disko.devices.disk or {})
|
||||||
|
(_: _: {});
|
||||||
|
# TODO: microvm shares
|
||||||
|
# TODO: container shares
|
||||||
|
|
||||||
|
interfaces = let
|
||||||
|
isNetwork = netDef: (netDef.matchConfig != {}) && (netDef.address != [] || netDef.DHCP != null);
|
||||||
|
macsByName = mapAttrs' (flip nameValuePair) (config.networking.renameInterfacesByMac or {});
|
||||||
|
netNameFor = netName: netDef:
|
||||||
|
if netDef ? matchConfig.Name
|
||||||
|
then netDef.matchConfig.Name
|
||||||
|
else 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:
|
||||||
|
if netDef ? matchConfig.MACAddress
|
||||||
|
then netDef.matchConfig.MACAddress
|
||||||
|
else 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
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue