mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-10 14:50:40 +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
|
||||
;
|
||||
|
||||
# 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
|
||||
# to any system via nodes.<name>
|
||||
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
|
||||
# has ssh and some other convenience stuff preconfigured.
|
||||
# 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.nix
|
||||
|
||||
./d2diag.nix
|
||||
./topology.nix
|
||||
];
|
||||
|
||||
nixpkgs.overlays = [
|
||||
|
|
|
@ -72,6 +72,8 @@ in {
|
|||
};
|
||||
|
||||
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