diff --git a/flake.nix b/flake.nix index e740709..9f4f395 100644 --- a/flake.nix +++ b/flake.nix @@ -49,6 +49,12 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + nix-topology = { + url = "github:oddlama/nix-topology"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; + nixos-extra-modules = { url = "github:oddlama/nixos-extra-modules"; inputs.nixpkgs.follows = "nixpkgs"; @@ -97,13 +103,7 @@ outputs = { self, - agenix-rekey, - devshell, - nixos-extra-modules, - flake-utils, - nixos-generators, nixpkgs, - pre-commit-hooks, ... } @ inputs: let inherit @@ -123,7 +123,7 @@ extraEncryptionPubkeys = [./secrets/backup.pub]; }; - agenix-rekey = agenix-rekey.configure { + agenix-rekey = inputs.agenix-rekey.configure { userFlake = self; inherit (self) nodes pkgs; }; @@ -154,8 +154,8 @@ packages ; } - // flake-utils.lib.eachDefaultSystem (system: rec { - apps.setupHetznerStorageBoxes = import (nixos-extra-modules + "/apps/setup-hetzner-storage-boxes.nix") { + // inputs.flake-utils.lib.eachDefaultSystem (system: rec { + apps.setupHetznerStorageBoxes = import (inputs.nixos-extra-modules + "/apps/setup-hetzner-storage-boxes.nix") { inherit pkgs; nixosConfigurations = self.nodes; decryptIdentity = builtins.head self.secretsConfig.masterIdentities; @@ -168,82 +168,25 @@ import ./lib inputs ++ import ./pkgs/default.nix ++ [ - nixos-extra-modules.overlays.default - devshell.overlays.default - agenix-rekey.overlays.default + inputs.agenix-rekey.overlays.default + inputs.devshell.overlays.default + inputs.nix-topology.overlays.default + inputs.nixos-extra-modules.overlays.default ]; }; - # XXX: WIP: only testing - topology = - import ./topology inputs - /* - <-- move into topology flake - */ - { - inherit pkgs; - modules = [ - ({config, ...}: let - inherit - (config.lib.helpers) - mkInternet - mkSwitch - mkRouter - mkConnection - mkConnectionRev - ; - in { - renderer = "elk"; - nixosConfigurations = self.nodes; - - nodes.internet = mkInternet {}; - nodes.sentinel.interfaces.wan.physicalConnections = [(mkConnectionRev "internet" "*")]; - - nodes.fritzbox = mkRouter "FritzBox" { - info = "FRITZ!Box 7520"; - image = ./fritzbox.png; - interfaceGroups = [ - ["eth1" "eth2" "eth3" "eth4"] - ["wan1"] - ]; - connections.eth1 = mkConnection "ward" "wan"; - connections.wan1 = mkConnectionRev "internet" "*"; - }; - - networks.home-fritzbox = { - name = "Home Fritzbox"; - cidrv4 = "192.168.178.0/24"; - }; - - networks.ward-kea.name = "Home LAN"; - nodes.switch-attic = mkSwitch "Switch Attic" { - info = "D-Link DGS-1016D"; - image = ./dlink-dgs1016d.png; - interfaceGroups = [["eth1" "eth2" "eth3" "eth4" "eth5" "eth6"]]; - connections.eth1 = mkConnection "ward" "lan"; - connections.eth2 = mkConnection "sire" "lan"; - connections.eth3 = []; - }; - - nodes.switch-bedroom-1 = mkSwitch "Switch Bedroom 1" { - info = "D-Link DGS-105"; - image = ./dlink-dgs105.png; - interfaceGroups = [["eth1" "eth2" "eth3" "eth4" "eth5"]]; - connections.eth1 = mkConnection "switch-attic" "eth3"; - connections.eth2 = mkConnection "kroma" "lan1"; - connections.eth3 = mkConnection "nom" "lan1"; - }; - }) - { - nodes.fritzbox.interfaces.eth1.network = "home-fritzbox"; - } - ]; - }; + topology = import inputs.nix-topology { + inherit pkgs; + modules = [ + ./topology + {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. - images.live-iso = nixos-generators.nixosGenerate { + images.live-iso = inputs.nixos-generators.nixosGenerate { inherit pkgs; modules = [ ./nix/installer-configuration.nix @@ -258,16 +201,13 @@ }; # `nix flake check` - checks.pre-commit-hooks = pre-commit-hooks.lib.${system}.run { + checks.pre-commit-hooks = inputs.pre-commit-hooks.lib.${system}.run { src = cleanSource ./.; hooks = { # Nix alejandra.enable = true; deadnix.enable = true; statix.enable = true; - # Lua (for neovim) - luacheck.enable = true; - stylua.enable = true; }; }; diff --git a/hosts/ward/default.nix b/hosts/ward/default.nix index a58205a..8d53a24 100644 --- a/hosts/ward/default.nix +++ b/hosts/ward/default.nix @@ -21,7 +21,7 @@ ./kea.nix ]; - topology.self.hardware.image = ../../odroid-h3.png; + topology.self.hardware.image = ../../topology/images/odroid-h3.png; topology.self.hardware.info = "ODROID H3, 64GB RAM"; topology.self.interfaces.lan.sharesNetworkWith = x: x == "lan-self"; topology.self.interfaces.lan-self.sharesNetworkWith = x: x == "lan"; diff --git a/modules/default.nix b/modules/default.nix index 0ceaeed..eb13425 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -5,9 +5,10 @@ inputs.agenix.nixosModules.default inputs.disko.nixosModules.disko inputs.elewrap.nixosModules.default - inputs.nixos-extra-modules.nixosModules.default inputs.home-manager.nixosModules.default inputs.impermanence.nixosModules.impermanence + inputs.nix-topology.nixosModules.default + inputs.nixos-extra-modules.nixosModules.default inputs.nixos-nftables-firewall.nixosModules.default ../users/root @@ -38,8 +39,6 @@ ./provided-domains.nix ./secrets.nix ./telegraf.nix - - ../topology/nixos/module.nix ]; nixpkgs.overlays = [ diff --git a/pkgs/default.nix b/pkgs/default.nix index aa3d7a3..80c60ef 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -22,7 +22,6 @@ doCheck = false; }); awakened-poe-trade = prev.callPackage ./awakened-poe-trade.nix {}; - html-to-svg = prev.callPackage ./html-to-svg {}; kanidm-provision = prev.callPackage ./kanidm-provision.nix {}; segoe-ui-ttf = prev.callPackage ./segoe-ui-ttf.nix {}; zsh-histdb-skim = prev.callPackage ./zsh-skim-histdb.nix {}; diff --git a/pkgs/html-to-svg/default.nix b/pkgs/html-to-svg/default.nix deleted file mode 100644 index 4ca5030..0000000 --- a/pkgs/html-to-svg/default.nix +++ /dev/null @@ -1,22 +0,0 @@ -{ - buildNpmPackage, - lib, -}: -buildNpmPackage { - pname = "html-to-svg"; - version = "1.0.0"; - - src = ./.; - npmDepsHash = "sha256-0gm43QSUBg219ueFuNSjz857Y1OttSKFc4VltXF78yg="; - dontNpmBuild = true; - - #passthru.updateScript = nix-update-script { }; - - meta = with lib; { - description = "Convert satori compatible HTML to SVG"; - #homepage = "https://github.com/oddlama/html-to-svg"; - license = licenses.mit; - maintainers = with maintainers; [oddlama]; - mainProgram = "html-to-svg"; - }; -} diff --git a/pkgs/html-to-svg/main.js b/pkgs/html-to-svg/main.js deleted file mode 100644 index b6788d6..0000000 --- a/pkgs/html-to-svg/main.js +++ /dev/null @@ -1,53 +0,0 @@ -import fs from "node:fs/promises"; -import satori from "satori"; -import { html } from "satori-html"; -import { Command } from "commander"; - -const program = new Command(); - -program - .name("html-to-svg") - .description("Convert satori compatible HTML to SVG") - .version("1.0.0") - .argument("", "satori html file to render") - .argument("", "output svg") - .option("--font ", "Sets the font") - .option("--font-bold ", "Sets the bold font") - .option("-w, --width ", "Sets the width of the output. Use auto for automatic scaling.", "auto") - .option("-h, --height ", "Sets the height of the output. Use auto for automatic scaling.", "auto") - .action(async (input, output, options) => { - if (!options.font) { - console.error("--font is required"); - process.exit(1); - } - - if (!options.fontBold) { - console.error("--font-bold is required"); - process.exit(1); - } - - const markup = html(await fs.readFile(input, { encoding: "utf8" })); - const svg = await satori(markup, { - ...(options.width != "auto" && {width: options.width}), - ...(options.height != "auto" && {height: options.height}), - embedFont: true, - fonts: [ - { - name: "Font", - data: await fs.readFile(options.font), - weight: 400, - style: "normal", - }, - { - name: "Font", - data: await fs.readFile(options.fontBold), - weight: 700, - style: "normal", - }, - ], - }); - - await fs.writeFile(output, svg); - }); - -program.parse(); diff --git a/pkgs/html-to-svg/package-lock.json b/pkgs/html-to-svg/package-lock.json deleted file mode 100644 index 24c62ed..0000000 --- a/pkgs/html-to-svg/package-lock.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "name": "render-svg", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "render-svg", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "commander": "^12.0.0", - "satori": "^0.10.13", - "satori-html": "^0.3.2" - } - }, - "node_modules/@shuding/opentype.js": { - "version": "1.4.0-beta.0", - "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", - "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==", - "dependencies": { - "fflate": "^0.7.3", - "string.prototype.codepointat": "^0.2.1" - }, - "bin": { - "ot": "bin/ot" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/base64-js": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", - "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "engines": { - "node": ">=18" - } - }, - "node_modules/css-background-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz", - "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==" - }, - "node_modules/css-box-shadow": { - "version": "1.0.0-3", - "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", - "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==" - }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/fflate": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", - "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==" - }, - "node_modules/hex-rgb": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz", - "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/linebreak": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", - "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", - "dependencies": { - "base64-js": "0.0.8", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" - }, - "node_modules/parse-css-color": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz", - "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==", - "dependencies": { - "color-name": "^1.1.4", - "hex-rgb": "^4.1.0" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "node_modules/satori": { - "version": "0.10.13", - "resolved": "https://registry.npmjs.org/satori/-/satori-0.10.13.tgz", - "integrity": "sha512-klCwkVYMQ/ZN5inJLHzrUmGwoRfsdP7idB5hfpJ1jfiJk1ErDitK8Hkc6Kll1+Ox2WtqEuGecSZLnmup3CGzvQ==", - "dependencies": { - "@shuding/opentype.js": "1.4.0-beta.0", - "css-background-parser": "^0.1.0", - "css-box-shadow": "1.0.0-3", - "css-to-react-native": "^3.0.0", - "emoji-regex": "^10.2.1", - "escape-html": "^1.0.3", - "linebreak": "^1.1.0", - "parse-css-color": "^0.2.1", - "postcss-value-parser": "^4.2.0", - "yoga-wasm-web": "^0.3.3" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/satori-html": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/satori-html/-/satori-html-0.3.2.tgz", - "integrity": "sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==", - "dependencies": { - "ultrahtml": "^1.2.0" - } - }, - "node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" - }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" - }, - "node_modules/ultrahtml": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.3.tgz", - "integrity": "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==" - }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", - "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/yoga-wasm-web": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", - "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==" - } - } -} diff --git a/pkgs/html-to-svg/package.json b/pkgs/html-to-svg/package.json deleted file mode 100644 index 4dddc55..0000000 --- a/pkgs/html-to-svg/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "html-to-svg", - "version": "1.0.0", - "description": "Convert satori compatible HTML to SVG", - "main": "main.js", - "type": "module", - "author": "oddlama ", - "license": "MIT", - "bin": { - "html-to-svg": "./main.js" - }, - "dependencies": { - "commander": "^12.0.0", - "satori": "^0.10.13", - "satori-html": "^0.3.2" - } -} diff --git a/topology/README.md b/topology/README.md deleted file mode 100644 index 8b5b9a1..0000000 --- a/topology/README.md +++ /dev/null @@ -1,31 +0,0 @@ -## TODO - -- macvtap interface type svg with small link -- address port label render make newline capable (multiple port labels) -- NAT indication -- embed font globally, try removing satori embed? -- network centric view as standalone -- make rectpacking of childs - -- podman / docker harvesting -- nixos-container extractor - -- disks (from disko) + render -- impermanence render? -- nixos nftables firewall render? - -- make colors configurable -- search todo and do -- the network propagator based on options.nodes cannot deal with mkMerge and mkIf stuff currently - -## Options - -## Renderers - -### svg - -### elk - -## NixOS Extractors - -## Network propagation diff --git a/topology/default.nix b/topology/default.nix index f1fbb17..5fd759f 100644 --- a/topology/default.nix +++ b/topology/default.nix @@ -1,12 +1,54 @@ -inputs: { - pkgs, - modules ? [], -}: -inputs.nixpkgs.lib.evalModules { - prefix = ["topology"]; - modules = [./topology] ++ modules; - specialArgs = { - modulesPath = builtins.toString ./topology; - inherit pkgs; +{config, ...}: let + inherit + (config.lib.topology) + mkInternet + mkSwitch + mkRouter + mkConnection + mkConnectionRev + ; +in { + imports = [ + { + nodes.fritzbox.interfaces.eth1.network = "home-fritzbox"; + } + ]; + + nodes.internet = mkInternet {}; + nodes.sentinel.interfaces.wan.physicalConnections = [(mkConnectionRev "internet" "*")]; + + nodes.fritzbox = mkRouter "FritzBox" { + info = "FRITZ!Box 7520"; + image = ./images/fritzbox.png; + interfaceGroups = [ + ["eth1" "eth2" "eth3" "eth4"] + ["wan1"] + ]; + connections.eth1 = mkConnection "ward" "wan"; + connections.wan1 = mkConnectionRev "internet" "*"; + }; + + networks.home-fritzbox = { + name = "Home Fritzbox"; + cidrv4 = "192.168.178.0/24"; + }; + + networks.ward-kea.name = "Home LAN"; + nodes.switch-attic = mkSwitch "Switch Attic" { + info = "D-Link DGS-1016D"; + image = ./images/dlink-dgs1016d.png; + interfaceGroups = [["eth1" "eth2" "eth3" "eth4" "eth5" "eth6"]]; + connections.eth1 = mkConnection "ward" "lan"; + connections.eth2 = mkConnection "sire" "lan"; + connections.eth3 = []; + }; + + nodes.switch-bedroom-1 = mkSwitch "Switch Bedroom 1" { + info = "D-Link DGS-105"; + image = ./images/dlink-dgs105.png; + interfaceGroups = [["eth1" "eth2" "eth3" "eth4" "eth5"]]; + connections.eth1 = mkConnection "switch-attic" "eth3"; + connections.eth2 = mkConnection "kroma" "lan1"; + connections.eth3 = mkConnection "nom" "lan1"; }; } diff --git a/topology/icons/devices/cloud-server.svg b/topology/icons/devices/cloud-server.svg deleted file mode 100644 index 9912ff4..0000000 --- a/topology/icons/devices/cloud-server.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/topology/icons/devices/cloud.svg b/topology/icons/devices/cloud.svg deleted file mode 100644 index 3de2d1a..0000000 --- a/topology/icons/devices/cloud.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/topology/icons/devices/desktop.svg b/topology/icons/devices/desktop.svg deleted file mode 100644 index 6ad3e3d..0000000 --- a/topology/icons/devices/desktop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/topology/icons/devices/laptop.svg b/topology/icons/devices/laptop.svg deleted file mode 100644 index 2ea6714..0000000 --- a/topology/icons/devices/laptop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/topology/icons/devices/nixos.svg b/topology/icons/devices/nixos.svg deleted file mode 100644 index be0f489..0000000 --- a/topology/icons/devices/nixos.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/topology/icons/devices/router.svg b/topology/icons/devices/router.svg deleted file mode 100644 index 0266fdb..0000000 --- a/topology/icons/devices/router.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/topology/icons/devices/switch.svg b/topology/icons/devices/switch.svg deleted file mode 100644 index 13b4d31..0000000 --- a/topology/icons/devices/switch.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/topology/icons/interfaces/ethernet.svg b/topology/icons/interfaces/ethernet.svg deleted file mode 100644 index c1afca8..0000000 --- a/topology/icons/interfaces/ethernet.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/topology/icons/interfaces/wifi.svg b/topology/icons/interfaces/wifi.svg deleted file mode 100644 index 4beae0d..0000000 --- a/topology/icons/interfaces/wifi.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/topology/icons/interfaces/wireguard.svg b/topology/icons/interfaces/wireguard.svg deleted file mode 100644 index 81823b3..0000000 --- a/topology/icons/interfaces/wireguard.svg +++ /dev/null @@ -1,7 +0,0 @@ - - \ No newline at end of file diff --git a/topology/icons/services/adguardhome.svg b/topology/icons/services/adguardhome.svg deleted file mode 100644 index bab046d..0000000 --- a/topology/icons/services/adguardhome.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/topology/icons/services/forgejo.svg b/topology/icons/services/forgejo.svg deleted file mode 100644 index 424bf1e..0000000 --- a/topology/icons/services/forgejo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/topology/icons/services/grafana.svg b/topology/icons/services/grafana.svg deleted file mode 100644 index fc3494d..0000000 --- a/topology/icons/services/grafana.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/topology/icons/services/kanidm.svg b/topology/icons/services/kanidm.svg deleted file mode 100644 index 312ce85..0000000 --- a/topology/icons/services/kanidm.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/topology/icons/services/loki.svg b/topology/icons/services/loki.svg deleted file mode 100644 index c582c32..0000000 --- a/topology/icons/services/loki.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/topology/icons/services/nginx.svg b/topology/icons/services/nginx.svg deleted file mode 100644 index f0cbded..0000000 --- a/topology/icons/services/nginx.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/topology/icons/services/oauth2-proxy.svg b/topology/icons/services/oauth2-proxy.svg deleted file mode 100644 index 30b7670..0000000 --- a/topology/icons/services/oauth2-proxy.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/topology/icons/services/openssh.svg b/topology/icons/services/openssh.svg deleted file mode 100644 index 1d77361..0000000 --- a/topology/icons/services/openssh.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/topology/icons/services/radicale.svg b/topology/icons/services/radicale.svg deleted file mode 100644 index 49d9bb7..0000000 --- a/topology/icons/services/radicale.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/topology/icons/services/samba.svg b/topology/icons/services/samba.svg deleted file mode 100644 index d8327e2..0000000 --- a/topology/icons/services/samba.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/topology/icons/services/vaultwarden.svg b/topology/icons/services/vaultwarden.svg deleted file mode 100644 index 1f94a8b..0000000 --- a/topology/icons/services/vaultwarden.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/topology/icons/services/wireguard.svg b/topology/icons/services/wireguard.svg deleted file mode 100644 index 86f3cb3..0000000 --- a/topology/icons/services/wireguard.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/dlink-dgs1016d.png b/topology/images/dlink-dgs1016d.png similarity index 100% rename from dlink-dgs1016d.png rename to topology/images/dlink-dgs1016d.png diff --git a/dlink-dgs105.png b/topology/images/dlink-dgs105.png similarity index 100% rename from dlink-dgs105.png rename to topology/images/dlink-dgs105.png diff --git a/fritzbox.png b/topology/images/fritzbox.png similarity index 100% rename from fritzbox.png rename to topology/images/fritzbox.png diff --git a/odroid-h3.png b/topology/images/odroid-h3.png similarity index 100% rename from odroid-h3.png rename to topology/images/odroid-h3.png diff --git a/topology/nixos/extractors/kea.nix b/topology/nixos/extractors/kea.nix deleted file mode 100644 index 7c550e0..0000000 --- a/topology/nixos/extractors/kea.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ - config, - lib, - ... -}: let - inherit - (lib) - const - filter - genAttrs - hasInfix - mkEnableOption - mkIf - ; -in { - options.topology.extractors.kea.enable = mkEnableOption "topology kea extractor" // {default = true;}; - - config = let - interfaces = filter (x: !hasInfix "*" x) config.services.kea.dhcp4.settings.interfaces-config.interfaces or []; - headOrNull = xs: - if xs == [] - then null - else builtins.head xs; - subnet = headOrNull (map (x: x.subnet) (config.services.kea.dhcp4.settings.subnet4 or [])); - netName = "${config.topology.self.id}-kea"; - in - mkIf (config.topology.extractors.kea.enable && config.services.kea.dhcp4.enable && interfaces != [] && subnet != null) { - topology.networks.${netName} = { - cidrv4 = subnet; - }; - - # Do not use topology.self.interfaces here to prevent inclusion of a mkMerge which cannot be - # detected by the network propagator currently. - topology.nodes.${config.topology.id}.interfaces = genAttrs interfaces (const {network = netName;}); - }; -} diff --git a/topology/nixos/extractors/microvm.nix b/topology/nixos/extractors/microvm.nix deleted file mode 100644 index b1dd8a8..0000000 --- a/topology/nixos/extractors/microvm.nix +++ /dev/null @@ -1,39 +0,0 @@ -{ - config, - lib, - ... -}: let - inherit - (lib) - attrValues - flip - mkEnableOption - mkIf - mkMerge - mkVMOverride - mod - optionalAttrs - ; -in { - options.topology.extractors.microvm.enable = mkEnableOption "topology microvm extractor" // {default = true;}; - - config = mkIf (config.topology.extractors.microvm.enable && config ? microvm && config.microvm.host.enable) { - topology.nodes = mkMerge (flip map (attrValues config.microvm.vms) ( - vm: - optionalAttrs (vm.config.config ? topology) { - ${vm.config.config.topology.id} = { - guestType = "microvm"; - parent = config.topology.id; - hardware.info = let - ramGB10 = builtins.floor (10 * vm.config.config.microvm.mem / 1024); - ramGB = - if mod ramGB10 10 == 0 - then ramGB10 / 10 - else ramGB10 / 10.0; - in - mkVMOverride "microvm, ${toString ramGB}GB RAM"; - }; - } - )); - }; -} diff --git a/topology/nixos/extractors/services.nix b/topology/nixos/extractors/services.nix deleted file mode 100644 index 2ceb3ac..0000000 --- a/topology/nixos/extractors/services.nix +++ /dev/null @@ -1,140 +0,0 @@ -{ - config, - lib, - ... -}: let - inherit - (lib) - attrNames - concatLines - concatStringsSep - flatten - flip - mapAttrsToList - mkDefault - mkEnableOption - mkIf - mkMerge - optional - replaceStrings - ; -in { - options.topology.extractors.services.enable = mkEnableOption "topology service extractor" // {default = true;}; - - config.topology.self.services = mkIf config.topology.extractors.services.enable { - adguardhome = let - address = config.services.adguardhome.settings.bind_host or null; - port = config.services.adguardhome.settings.bind_port or null; - in - mkIf config.services.adguardhome.enable { - name = "AdGuard Home"; - icon = "services.adguardhome"; - details.listen = mkIf (address != null && port != null) {text = "${address}:${toString port}";}; - }; - - forgejo = let - address = config.services.forgejo.settings.server.HTTP_ADDR or null; - port = config.services.forgejo.settings.server.HTTP_PORT or null; - in - mkIf config.services.forgejo.enable { - name = - if config.services.forgejo.settings ? DEFAULT.APP_NAME - then "Forgejo (${config.services.forgejo.settings.DEFAULT.APP_NAME})" - else "Forgejo"; - icon = "services.forgejo"; - info = mkIf (config.services.forgejo.settings ? server.ROOT_URL) config.services.forgejo.settings.server.ROOT_URL; - details.listen = mkIf (address != null && port != null) {text = "${address}:${toString port}";}; - }; - - grafana = let - address = config.services.grafana.settings.server.http_addr or null; - port = config.services.grafana.settings.server.http_port or null; - in - mkIf config.services.grafana.enable { - name = "Grafana"; - icon = "services.grafana"; - info = config.services.grafana.settings.server.root_url; - details.listen = mkIf (address != null && port != null) {text = "${address}:${toString port}";}; - }; - - kanidm = mkIf config.services.kanidm.enableServer { - name = "Kanidm"; - icon = "services.kanidm"; - info = config.services.kanidm.serverSettings.origin; - details.listen.text = config.services.kanidm.serverSettings.bindaddress; - }; - - loki = let - address = config.services.loki.configuration.server.http_listen_address or null; - port = config.services.loki.configuration.server.http_listen_port or null; - in - mkIf config.services.loki.enable { - name = "Loki"; - icon = "services.loki"; - details.listen = mkIf (address != null && port != null) {text = "${address}:${toString port}";}; - }; - - nginx = mkIf config.services.nginx.enable { - name = "NGINX"; - icon = "services.nginx"; - details = let - reverseProxies = flatten (flip mapAttrsToList config.services.nginx.virtualHosts ( - server: vh: - flip mapAttrsToList vh.locations ( - path: location: let - upstreamName = replaceStrings ["http://" "https://"] ["" ""] location.proxyPass; - passTo = - if config.services.nginx.upstreams ? ${upstreamName} - then toString (attrNames config.services.nginx.upstreams.${upstreamName}.servers) - else location.proxyPass; - in - optional (path == "/" && location.proxyPass != null) { - ${server} = {text = passTo;}; - } - ) - )); - in - mkMerge reverseProxies; - }; - - radicale = mkIf config.services.radicale.enable { - name = "Radicale"; - icon = "services.radicale"; - details.listen = mkIf (config.services.radicale.settings ? server.hosts) {text = toString config.services.radicale.settings.server.hosts;}; - }; - - samba = mkIf config.services.samba.enable { - name = "Samba"; - icon = "services.samba"; - details.shares = let - shares = attrNames config.services.samba.shares; - in - mkIf (shares != []) {text = concatLines shares;}; - }; - - oauth2_proxy = mkIf config.services.oauth2_proxy.enable { - name = "OAuth2 Proxy"; - icon = "services.oauth2-proxy"; - info = config.services.oauth2_proxy.httpAddress; - }; - - openssh = mkIf config.services.openssh.enable { - hidden = mkDefault true; # Causes a lot of clutter - name = "OpenSSH"; - icon = "services.openssh"; - info = "port: ${concatStringsSep ", " (map toString config.services.openssh.ports)}"; - }; - - vaultwarden = let - domain = config.services.vaultwarden.config.domain or config.services.vaultwarden.config.DOMAIN or null; - address = config.services.vaultwarden.config.rocketAddress or config.services.vaultwarden.config.ROCKET_ADDRESS or null; - port = config.services.vaultwarden.config.rocketPort or config.services.vaultwarden.config.ROCKET_PORT or null; - in - mkIf config.services.vaultwarden.enable { - name = "Vaultwarden"; - icon = "services.vaultwarden"; - info = mkIf (domain != null) domain; - details.listen = mkIf (address != null && port != null) {text = "${address}:${toString port}";}; - }; - }; -} diff --git a/topology/nixos/extractors/systemd-network.nix b/topology/nixos/extractors/systemd-network.nix deleted file mode 100644 index 4b1cc6a..0000000 --- a/topology/nixos/extractors/systemd-network.nix +++ /dev/null @@ -1,82 +0,0 @@ -{ - config, - lib, - ... -}: let - inherit - (lib) - any - attrValues - concatLists - concatStringsSep - flip - init - length - listToAttrs - mapAttrsToList - mkDefault - mkEnableOption - mkIf - mkMerge - nameValuePair - optional - splitString - ; - - removeCidrMask = x: let - toks = splitString "/" x; - in - if length toks > 1 - then concatStringsSep "/" (init toks) - else builtins.head toks; -in { - options.topology.extractors.systemd-network.enable = mkEnableOption "topology systemd-network extractor" // {default = true;}; - - config = mkIf (config.topology.extractors.systemd-network.enable && config.systemd.network.enable) { - topology.self.interfaces = mkMerge ( - # Create interfaces based on systemd.network.netdevs - concatLists ( - flip mapAttrsToList config.systemd.network.netdevs ( - _unit: netdev: - optional (netdev ? netdevConfig.Name) { - ${netdev.netdevConfig.Name} = { - virtual = mkDefault true; - type = mkIf (netdev ? netdevConfig.Kind) netdev.netdevConfig.Kind; - }; - } - ) - ) - # Add interface configuration based on systemd.network.networks - ++ concatLists ( - flip mapAttrsToList config.systemd.network.networks ( - _unit: network: let - # FIXME: TODO renameInterfacesByMac is not a standard option! It's not an issue here but should proabaly not be used anyway. - macToName = listToAttrs (mapAttrsToList (k: v: nameValuePair v k) (config.networking.renameInterfacesByMac or {})); - - nameFromMac = - optional (network ? matchConfig.MACAddress && macToName ? ${network.matchConfig.MACAddress}) - macToName.${network.matchConfig.MACAddress}; - - nameFromNetdev = - optional ( - (network ? matchConfig.Name) - && flip any (attrValues config.systemd.network.netdevs) (x: - (x ? netdevConfig.Name) - && x.netdevConfig.Name == network.matchConfig.Name) - ) - network.matchConfig.Name; - - interfaceName = builtins.head (nameFromMac ++ nameFromNetdev ++ [null]); - in - optional (interfaceName != null) { - ${interfaceName} = { - mac = network.matchConfig.MACAddress or null; - addresses = map removeCidrMask (network.address ++ (network.networkConfig.Address or [])); - gateways = network.gateway ++ (network.networkConfig.Gateway or []); - }; - } - ) - ) - ); - }; -} diff --git a/topology/nixos/extractors/wireguard.nix b/topology/nixos/extractors/wireguard.nix deleted file mode 100644 index 09840a0..0000000 --- a/topology/nixos/extractors/wireguard.nix +++ /dev/null @@ -1,80 +0,0 @@ -{ - config, - lib, - inputs ? {}, - ... -}: let - inherit - (lib) - flip - mapAttrsToList - mkDefault - mkEnableOption - mkIf - mkMerge - filter - ; - - headOrNull = xs: - if xs == [] - then null - else builtins.head xs; - - networkId = wgName: "wireguard-${wgName}"; -in { - options.topology.extractors.wireguard.enable = mkEnableOption "topology wireguard extractor" // {default = true;}; - - config = mkIf (config.topology.extractors.wireguard.enable && config ? wireguard) { - # Create networks (this will be duplicated by each node, - # but it doesn't matter and will be merged anyway) - topology.networks = mkMerge ( - flip mapAttrsToList config.wireguard ( - wgName: _: let - inherit (lib.wireguard inputs wgName) networkCidrs; - in { - ${networkId wgName} = { - name = mkDefault "Wireguard network '${wgName}'"; - icon = "interfaces.wireguard"; - cidrv4 = headOrNull (filter lib.net.ip.isv4 networkCidrs); - cidrv6 = headOrNull (filter lib.net.ip.isv6 networkCidrs); - }; - } - ) - ); - - # Assign network and physical connections to related interfaces - topology.self.interfaces = mkMerge ( - flip mapAttrsToList config.wireguard ( - wgName: wgCfg: let - inherit - (lib.wireguard inputs wgName) - participatingServerNodes - wgCfgOf - ; - - isServer = wgCfg.server.host != null; - filterSelf = filter (x: x != config.node.name); - - # The list of peers that are "physically" connected in the wireguard network, - # meaning they communicate directly with each other. - connectedPeers = - if isServer - then - # Other servers in the same network - filterSelf participatingServerNodes - else [wgCfg.client.via]; - in { - ${wgCfg.linkName} = { - network = networkId wgName; - virtual = true; - renderer.hidePhysicalConnections = true; - physicalConnections = flip map connectedPeers (peer: { - node = inputs.self.nodes.${peer}.config.topology.id; - interface = (wgCfgOf peer).linkName; - }); - }; - } - ) - ); - }; -} diff --git a/topology/nixos/module.nix b/topology/nixos/module.nix deleted file mode 100644 index 6888947..0000000 --- a/topology/nixos/module.nix +++ /dev/null @@ -1,62 +0,0 @@ -{ - config, - lib, - ... -}: let - inherit - (lib) - attrNames - flip - mkAliasOptionModule - mkOption - types - ; -in { - imports = - [ - # Allow simple alias to set/get attributes of this node - (mkAliasOptionModule ["topology" "self"] ["topology" "nodes" config.topology.id]) - ] - # Include extractors - ++ map (x: ./extractors/${x}) (attrNames (builtins.readDir ./extractors)) - # Include common topology options - ++ 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 = '' - 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; - type = types.str; - }; - - isMainModule = mkOption { - description = "Whether this is the toplevel topology module."; - readOnly = true; - internal = true; - default = false; - type = types.bool; - }; - }; - - config.topology = { - # Ensure a node exists for this host - nodes.${config.topology.id}.deviceType = "nixos"; - }; -} diff --git a/topology/options/disks.nix b/topology/options/disks.nix deleted file mode 100644 index f5bef10..0000000 --- a/topology/options/disks.nix +++ /dev/null @@ -1,32 +0,0 @@ -f: { - lib, - config, - ... -}: let - inherit - (lib) - mkOption - types - ; -in - f { - options.nodes = mkOption { - type = types.attrsOf (types.submodule { - options = { - disks = mkOption { - default = {}; - type = types.attrsOf (types.submodule (submod: { - options = { - id = mkOption { - description = "The id of this disk"; - default = submod.config._module.args.name; - readOnly = true; - type = types.str; - }; - }; - })); - }; - }; - }); - }; - } diff --git a/topology/options/firewall.nix b/topology/options/firewall.nix deleted file mode 100644 index 177b297..0000000 --- a/topology/options/firewall.nix +++ /dev/null @@ -1,38 +0,0 @@ -f: { - lib, - config, - ... -}: let - inherit - (lib) - mkOption - types - ; -in - f { - options.nodes = mkOption { - type = types.attrsOf (types.submodule { - options = { - firewallRules = mkOption { - description = "TODO"; - default = {}; - type = types.attrsOf (types.submodule (submod: { - options = { - id = mkOption { - description = "The id 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; - }; - }; - })); - }; - }; - }); - }; - } diff --git a/topology/options/icons.nix b/topology/options/icons.nix deleted file mode 100644 index d2ff600..0000000 --- a/topology/options/icons.nix +++ /dev/null @@ -1,71 +0,0 @@ -f: { - lib, - config, - ... -}: let - inherit - (lib) - concatStringsSep - filterAttrs - getAttrFromPath - hasAttrByPath - hasPrefix - init - mapAttrs - mapAttrs' - mkOption - nameValuePair - splitString - types - ; - - removeExtension = filename: concatStringsSep "." (init (splitString "." filename)); - - iconsFromFiles = dir: - mapAttrs' (file: _: nameValuePair (removeExtension file) {file = dir + "/${file}";}) ( - filterAttrs (_: type: type == "regular") (builtins.readDir dir) - ); - - iconType = types.submodule { - options = { - file = mkOption { - description = "The icon file"; - type = types.path; - }; - }; - }; -in - f { - options.icons = mkOption { - description = "All predefined icons by category."; - default = {}; - type = types.attrsOf (types.attrsOf iconType); - }; - - config = { - # The necessary assertion for an icon type - lib.assertions.iconValid = icon: path: { - assertion = icon != null -> (!hasPrefix "/" icon -> hasAttrByPath (splitString "." icon) config.icons); - message = "topology: ${path} refers to an unknown icon icons.${icon}"; - }; - - # The function that returns the image path for an icon option - lib.icons.get = icon: let - attrPath = splitString "." icon; - in - if icon == null - then null - # Icon is a path - else if hasPrefix "/" icon - then icon - # Icon is a valid icon name - else if hasAttrByPath attrPath config.icons - then (getAttrFromPath attrPath config.icons).file - # Icon is not valid - else throw "Reference to unknown icon ${icon} detected. Aborting."; - - icons = - mapAttrs (n: _: iconsFromFiles ../icons/${n}) - (filterAttrs (_: v: v == "directory") (builtins.readDir ../icons)); - }; - } diff --git a/topology/options/interfaces.nix b/topology/options/interfaces.nix deleted file mode 100644 index 50d23c2..0000000 --- a/topology/options/interfaces.nix +++ /dev/null @@ -1,378 +0,0 @@ -f: { - lib, - config, - options, - ... -}: let - inherit - (lib) - attrNames - attrValues - concatLines - concatMap - concatStringsSep - const - elem - flatten - flip - foldl' - length - mapAttrsToList - mkDefault - mkIf - mkOption - optional - optionalAttrs - recursiveUpdate - reverseList - types - unique - warnIf - ; - - inherit - (import ../topology/lazy.nix lib) - isLazyValue - lazyOf - lazyValue - ; - - allNodes = attrNames config.nodes; - allInterfacesOf = node: attrNames config.nodes.${node}.interfaces; - allNodeInterfacePairs = concatMap (node: map (interface: {inherit node interface;}) (allInterfacesOf node)) allNodes; - - # Helper to add a value to a list if it doesn't already hold it - addIfNotExists = list: x: - if elem x list - then list - else list ++ [x]; - - # The list of networks that were specifically assigned by the user - # to other interfaces which are sharing their network with us. - networkDefiningInterfaces = foldl' recursiveUpdate {} (flatten ( - flip map options.nodes.definitions (mapAttrsToList ( - nodeId: node: - flip mapAttrsToList (node.interfaces or {}) ( - interfaceId: interface: - optional (interface ? network && !isLazyValue interface.network) { - ${nodeId}.${interfaceId} = interface.network; - } - ) - )) - )); - - # A list of all connections between all interfaces. Bidirectional. - connectionList = unique (flatten (flip mapAttrsToList config.nodes ( - nodeId: node: - flip mapAttrsToList node.interfaces ( - interfaceId: interface: - flip map interface.physicalConnections (conn: [ - { - src = { - node = nodeId; - interface = interfaceId; - }; - dst = conn; - } - { - src = conn; - dst = { - node = nodeId; - interface = interfaceId; - }; - } - ]) - ) - ))); - - # A map of all connections constructed from the connection list - connections = foldl' (acc: { - src, - dst, - }: - recursiveUpdate acc { - ${src.node}.${src.interface} = addIfNotExists (acc.${src.node}.${src.interface} or []) dst; - }) {} - connectionList; - - # Propagates all networks from the source interface to the destination interface - propagateNetworks = state: snode: sinterface: dnode: dinterface: - #builtins.trace " propagate all nets (${toString (attrNames state.${snode}.${sinterface})}) of ${snode}.${sinterface} to ${dnode}.${dinterface}" - ( - # Fold over each network that the source interface shares - # and propagate it if the destination doesn't already have this network - foldl' ( - acc: net: - recursiveUpdate acc ( - optionalAttrs (!(acc ? ${dnode}.${dinterface}.${net})) - #builtins.trace " adding ${net} to ${dnode}.${dinterface} via ${toString (acc.${snode}.${sinterface}.${net} ++ ["${snode}.${sinterface}"])}" - { - # Create the network on the destination interface and append our interface - # to the list which indicates from where the network was received - ${dnode}.${dinterface}.${net} = acc.${snode}.${sinterface}.${net} ++ ["${snode}.${sinterface}"]; - } - ) - ) - state (attrNames state.${snode}.${sinterface}) - ); - - # Propagates all shared networks for the given interface to other interfaces connected to it - propagateToConnections = state: src: - #builtins.trace "propagate via connections of ${src.node}.${src.interface}" - ( - # Fold over each connection of the current interface - foldl' ( - acc: dst: - propagateNetworks acc src.node src.interface dst.node dst.interface - ) - state (connections.${src.node}.${src.interface} or []) - ); - - # Propagates all shared networks for the given interface to other interfaces on the same - # node to which the network is shared - propagateLocally = state: src: - #builtins.trace "propagate locally on ${src.node}.${src.interface}" - ( - # Fold over each local interface of the current node - foldl' ( - acc: dstInterface: - if src.interface != dstInterface && config.nodes.${src.node}.interfaces.${src.interface}.sharesNetworkWith dstInterface - then propagateNetworks acc src.node src.interface src.node dstInterface - else acc - ) - state (allInterfacesOf src.node) - ); - - # Assigns each interface a list of networks that were propagated to it - # from another interface or connection. - propagatedNetworks = let - # Initially set sharedNetworks based on whether the interface has a network assigned to it. - # This list will then be expaned iteratively. - initial = foldl' recursiveUpdate {} (flatten (flip map allNodes ( - node: - flip map (allInterfacesOf node) (interface: { - ${node}.${interface} = optionalAttrs (networkDefiningInterfaces ? ${node}.${interface}) { - ${networkDefiningInterfaces.${node}.${interface}} = []; - }; - }) - ))); - - # Takes a state and propagates networks via local sharing on the same node - propagateEachLocally = state: foldl' propagateLocally state allNodeInterfacePairs; - # Takes a state and propagates networks via sharing over connections - propagateEachToConnections = state: foldl' propagateToConnections state allNodeInterfacePairs; - - # The update function that propagates state from all interfaces to all neighbors. - # The fixpoint of this function is the solution. - update = state: propagateEachToConnections (propagateEachLocally state); - converged = lib.converge update initial; - - # Extract all interfaces that were assigned multiple interfaces, to issue a warning - interfacesWithMultipleNets = flatten ( - flip mapAttrsToList converged ( - node: ifs: - flip mapAttrsToList ifs ( - interface: nets: - optional (length (attrNames nets) > 1) { - inherit node interface nets; - } - ) - ) - ); - in - warnIf (interfacesWithMultipleNets != []) '' - topology: Some interfaces have received multiple networks via network propagation! - This is an error in your network configuration that must be addressed. - Evaluation can still continue by considering only one of them (effectively at random). - The affected interfaces are: - ${concatLines (flip map interfacesWithMultipleNets ( - x: - " - ${x.node}.${x.interface}:\n" - + concatLines ( - flip mapAttrsToList x.nets ( - net: assignments: " ${net} assigned via ${concatStringsSep " -> " (reverseList assignments)}" - ) - ) - ))}'' - converged; -in - f { - options.nodes = mkOption { - type = types.attrsOf (types.submodule (nodeSubmod: { - options = { - interfaces = mkOption { - description = "TODO"; - default = {}; - type = types.attrsOf (types.submodule (submod: { - options = { - id = mkOption { - description = "The id of this interface"; - type = types.str; - readOnly = true; - default = submod.config._module.args.name; - }; - - virtual = mkOption { - description = "Whether this is a virtual interface."; - type = types.bool; - default = false; - }; - - mac = mkOption { - description = "The MAC address of this interface, if known."; - default = null; - type = types.nullOr types.str; - }; - - type = mkOption { - description = "The type of this interface"; - default = "ethernet"; - type = types.str; - }; - - icon = mkOption { - description = "The icon representing this interface's type. Must be a path to an image or a valid icon name (.). By default an icon will be selected based on the type."; - type = types.nullOr (types.either types.path types.str); - default = null; - }; - - addresses = mkOption { - description = "The configured address(es), or a descriptive string (like DHCP)."; - default = []; - type = types.listOf types.str; - }; - - gateways = mkOption { - description = "The configured gateways, if any."; - default = []; - type = types.listOf types.str; - }; - - network = - mkOption { - description = "The id of the network to which this interface belongs, if any."; - type = lazyOf (types.nullOr types.str); - } - // optionalAttrs config.topology.isMainModule { - default = let - sharedNetworks = propagatedNetworks.${nodeSubmod.config.id}.${submod.config.id} or {}; - sharedNetwork = - if sharedNetworks == {} - then null - else builtins.head (attrNames sharedNetworks); - in - lazyValue sharedNetwork; - }; - - sharesNetworkWith = mkOption { - description = '' - Defines a predicate that determines whether this interface shares its connected network with another provided local interface. - The predicates takes the name of another interface and returns true if our network should be shared with the given interface. - - Sharing here means that if a network is set on this interface, it will also be set as the network for any - shared interface. Setting the same predicate on multiple interfaces causes them to share a network regardless - on which port the network is actually defined. - - An unmanaged switch for example would set this to `const true`, effectively - propagating the network set on one port to all other ports. Having two assigned - networks within one predicate group will cause a warning to be issued. - ''; - default = const false; - defaultText = ''const false''; - type = types.functionTo types.bool; - }; - - physicalConnections = mkOption { - description = "A list of other node interfaces to which this node is physically connected."; - default = []; - type = types.listOf (types.submodule { - options = { - node = mkOption { - description = "The other node id."; - type = types.str; - }; - - interface = mkOption { - description = "The other node's interface id."; - type = types.str; - }; - - renderer.reverse = mkOption { - description = "Whether to reverse the edge. Can be useful to affect node positioning if the layouter is directional."; - type = types.bool; - default = false; - }; - }; - }); - }; - - # Rendering related hints and settings - renderer = { - hidePhysicalConnections = mkOption { - description = '' - Whether to hide physical connections of this interface in renderings. - Affects both outgoing connections defined here and incoming connections - defined on other interfaces. - - Usually only affects rendering of the main topology view, not network-centric views. - ''; - type = types.bool; - default = false; - }; - }; - }; - - config = { - # Set the default icon, if an icon exists with a matching name - icon = mkIf (config.topology.isMainModule && config.icons.interfaces ? ${submod.config.type}) ( - mkDefault ("interfaces." + submod.config.type) - ); - - # For switches: Share the network with any other interface by default - sharesNetworkWith = mkIf (config.topology.isMainModule && nodeSubmod.config.deviceType == "switch") ( - mkDefault (const true) - ); - }; - })); - }; - }; - })); - }; - - config = { - assertions = flatten (flip map (attrValues config.nodes) ( - node: - flip map (attrValues node.interfaces) ( - interface: - [ - { - assertion = interface.network != null -> config.networks ? ${interface.network}; - message = "topology: nodes.${node.id}.interfaces.${interface.id} refers to an unknown network '${interface.network}'"; - } - (config.lib.assertions.iconValid - interface.icon "nodes.${node.id}.interfaces.${interface.id}") - ] - ++ flip map interface.physicalConnections ( - physicalConnection: { - assertion = config.nodes ? ${physicalConnection.node} && config.nodes.${physicalConnection.node}.interfaces ? ${physicalConnection.interface}; - message = "topology: nodes.${node.id}.interfaces.${interface.id}.physicalConnections refers to an unknown node/interface nodes.${physicalConnection.node}.interfaces.${physicalConnection.interface}"; - } - ) - ) - )); - - warnings = flatten (flip map (attrValues config.nodes) ( - node: - flip map (attrValues node.interfaces) ( - interface: - flip map interface.physicalConnections ( - physicalConnection: let - otherNetwork = config.nodes.${physicalConnection.node}.interfaces.${physicalConnection.interface}.network or null; - in - optional (interface.network != null && otherNetwork != null && interface.network != otherNetwork) - "topology: The interface nodes.${node.id}.interfaces.${interface.id} is associated with the network (${interface.network}), but also has a physicalConnection to nodes.${physicalConnection.node}.interfaces.${physicalConnection.interface} which is associated to a different network (${otherNetwork})" - ) - ) - )); - }; - } diff --git a/topology/options/networks.nix b/topology/options/networks.nix deleted file mode 100644 index 82526c4..0000000 --- a/topology/options/networks.nix +++ /dev/null @@ -1,170 +0,0 @@ -f: { - lib, - config, - options, - ... -}: let - inherit - (lib) - attrNames - attrValues - elemAt - flatten - flip - foldl' - imap0 - length - mapAttrsToList - mkOption - mod - optional - optionalAttrs - recursiveUpdate - subtractLists - types - unique - warnIf - ; - - inherit - (import ../topology/lazy.nix lib) - isLazyValue - lazyOf - lazyValue - ; - - style = primaryColor: secondaryColor: pattern: { - inherit primaryColor secondaryColor pattern; - }; - - predefinedStyles = [ - (style "#f1cf8a" null "solid") - (style "#70a5eb" null "solid") - (style "#9dd68d" null "solid") - (style "#5fe1ff" null "solid") - (style "#e05f65" null "solid") - (style "#f9a872" null "solid") - (style "#78dba9" null "solid") - (style "#9378de" null "solid") - (style "#c68aee" null "solid") - (style "#f5a6b8" null "solid") - (style "#70a5eb" null "dashed") - (style "#9dd68d" null "dashed") - (style "#f1cf8a" null "dashed") - (style "#5fe1ff" null "dashed") - (style "#e05f65" null "dashed") - (style "#f9a872" null "dashed") - (style "#78dba9" null "dashed") - (style "#9378de" null "dashed") - (style "#c68aee" null "dashed") - (style "#f5a6b8" null "dashed") - (style "#e05f65" "#e3e6eb" "dashed") - (style "#70a5eb" "#e3e6eb" "dashed") - (style "#9378de" "#e3e6eb" "dashed") - (style "#9dd68d" "#707379" "dashed") - (style "#f1cf8a" "#707379" "dashed") - (style "#5fe1ff" "#707379" "dashed") - (style "#f9a872" "#707379" "dashed") - (style "#78dba9" "#707379" "dashed") - (style "#c68aee" "#707379" "dashed") - (style "#f5a6b8" "#707379" "dashed") - ]; - - # A map containing all networks that have an explicitly assigned style, and the style. - explicitStyles = foldl' recursiveUpdate {} (flatten ( - flip map options.networks.definitions (mapAttrsToList ( - netId: net: - optional (net ? style && !isLazyValue net.style) { - ${netId} = net.style; - } - )) - )); - - # All unused predefined styles - remainingStyles = subtractLists (unique (attrValues explicitStyles)) predefinedStyles; - # All networks without styles - remainingNets = subtractLists (attrNames explicitStyles) (attrNames config.networks); - # Fold over all networks that have no style and assign the next free one. Warn and repeat from beginning if necessary. - computedStyles = - explicitStyles - // warnIf - (length remainingNets > length remainingStyles) - "topology: There are more networks without styles than predefined styles. Some styles will have to be reused!" - ( - foldl' recursiveUpdate {} (imap0 (i: net: { - ${net} = elemAt remainingStyles (mod i (length remainingStyles)); - }) - remainingNets) - ); -in - f { - options.networks = mkOption { - default = {}; - description = "Defines logical networks that are present in your topology."; - type = types.attrsOf (types.submodule (networkSubmod: { - options = { - id = mkOption { - description = "The id of this network"; - default = networkSubmod.config._module.args.name; - readOnly = true; - type = types.str; - }; - - name = mkOption { - description = "The name of this network"; - type = types.str; - default = "Unnamed network '${networkSubmod.config.id}'"; - }; - - icon = mkOption { - description = "The icon representing this network. Must be a path to an image or a valid icon name (.)."; - type = types.nullOr (types.either types.path types.str); - default = null; - }; - - style = - mkOption { - description = '' - A style for this network, usually used to draw connections. - Must be an attrset consisting of three attributes: - - primaryColor (#rrggbb): The primary color, usually the color of edges. - - secondaryColor (#rrggbb): The secondary color, usually the background of a dashed line and only shown when pattern != solid. Set to null for transparent. - - pattern (solid, dashed, dotted): The pattern to use. - ''; - type = lazyOf types.attrs; - } - // optionalAttrs config.topology.isMainModule { - default = lazyValue computedStyles.${networkSubmod.config.id}; - }; - - cidrv4 = mkOption { - description = "The CIDRv4 address space of this network or null if it doesn't use ipv4"; - default = null; - #type = types.nullOr types.net.cidrv4; - type = types.nullOr types.str; - }; - - cidrv6 = mkOption { - description = "The CIDRv6 address space of this network or null if it doesn't use ipv6"; - default = null; - #type = types.nullOr types.net.cidrv6; - type = types.nullOr types.str; - }; - - # FIXME: vlan ids - # FIXME: nat to [other networks] (happening on node XY) - }; - })); - }; - - config = { - assertions = flatten ( - flip map (attrValues config.networks) ( - network: [ - (config.lib.assertions.iconValid - network.icon "networks.${network.id}.icon") - ] - ) - ); - }; - } diff --git a/topology/options/nodes.nix b/topology/options/nodes.nix deleted file mode 100644 index 7b28473..0000000 --- a/topology/options/nodes.nix +++ /dev/null @@ -1,192 +0,0 @@ -f: { - lib, - config, - ... -}: let - inherit - (lib) - attrValues - concatMap - elem - flatten - flip - literalExpression - mapAttrsToList - mkDefault - mkIf - mkMerge - mkOption - toList - types - ; -in - f { - options.nodes = mkOption { - default = {}; - description = '' - Defines nodes that are shown in the topology graph. - Nodes usually correspond to nixos hosts or other devices in your network. - ''; - type = types.attrsOf (types.submodule (nodeSubmod: { - options = { - id = mkOption { - description = "The id of this node"; - default = nodeSubmod.config._module.args.name; - readOnly = true; - type = types.str; - }; - - name = mkOption { - description = "The name of this node"; - type = types.str; - default = nodeSubmod.config.id; - defaultText = literalExpression ''""''; - }; - - hardware = { - info = mkOption { - description = "A single line of information about this node's hardware. Usually the model name or a description the most important components."; - type = types.str; - default = ""; - }; - - image = mkOption { - description = "An image representing this node, usually shown larger than an icon."; - type = types.nullOr types.path; - default = null; - }; - }; - - icon = mkOption { - description = "The icon representing this node. Usually shown next to the name. Must be a path to an image or a valid icon name (.)."; - type = types.nullOr (types.either types.path types.str); - default = null; - }; - - deviceType = mkOption { - description = '' - The device type of the node. This can be set to anything, but some special - values exist that will automatically set some other defaults, most notably - the deviceIcon and renderer.preferredType. - ''; - type = types.either (types.enum ["nixos" "internet" "router" "switch" "device"]) types.str; - }; - - guestType = mkOption { - description = "If the device is a guest of another device, this will tell the type of guest it is."; - default = null; - type = types.nullOr (types.either (types.enum ["microvm" "nixos-container"]) types.str); - }; - - deviceIcon = mkOption { - description = "The icon representing this node's type. Must be a path to an image or a valid icon name (.). By default an icon will be selected based on the deviceType."; - type = types.nullOr (types.either types.path types.str); - default = null; - }; - - parent = mkOption { - description = "The id of the parent node, if this node has a parent."; - default = null; - type = types.nullOr types.str; - }; - - # Rendering related hints and settings - renderer = { - preferredType = mkOption { - description = '' - An optional hint to the renderer to specify whether this node should preferrably - rendered as a full card, or just as an image with name. If there is no hardware - image, this will usually still render a small card. - ''; - type = types.enum ["card" "image"]; - default = "card"; - defaultText = ''"card" # defaults to card but is also derived from the deviceType if possible.''; - }; - }; - }; - - config = let - nodeCfg = nodeSubmod.config; - in - mkIf config.topology.isMainModule (mkMerge [ - { - # Set the default icon, if an icon exists with a matching name - deviceIcon = mkIf (config.icons.devices ? ${nodeCfg.deviceType}) ( - mkDefault ("devices." + nodeCfg.deviceType) - ); - - # Set the hardware info to the guest type if nothing else was set - hardware.info = mkIf (nodeCfg.guestType != null) (mkDefault nodeCfg.guestType); - } - - # If the device type is not a full nixos node, try to render it as an image with name. - (mkIf (elem nodeCfg.deviceType ["internet" "router" "switch" "device"]) { - renderer.preferredType = mkDefault "image"; - }) - ]); - })); - }; - - config = { - lib.helpers = rec { - # Finally we found it, the one-and-only true cloud. - mkInternet = {connections ? [], ...}: { - name = "Internet"; - deviceType = "internet"; - hardware.image = ../icons/devices/cloud.svg; - interfaces."*".physicalConnections = toList connections; - }; - - mkConnection = node: interface: {inherit node interface;}; - mkConnectionRev = node: interface: { - inherit node interface; - renderer.reverse = true; - }; - - mkSwitch = name: { - info ? null, - image ? null, - interfaceGroups ? [], - connections ? {}, - ... - }: { - inherit name; - deviceType = "switch"; - hardware = { - info = mkIf (info != null) info; - image = mkIf (image != null) image; - }; - interfaces = mkMerge ( - flip mapAttrsToList connections (interface: conns: { - ${interface}.physicalConnections = toList conns; - }) - ++ flip concatMap interfaceGroups ( - group: - flip map group ( - interface: { - ${interface}.sharesNetworkWith = x: elem x group; - } - ) - ) - ); - }; - - mkRouter = name: args: - mkSwitch name args - // { - deviceType = "router"; - }; - }; - - assertions = flatten ( - flip map (attrValues config.nodes) ( - node: [ - (config.lib.assertions.iconValid - node.icon "nodes.${node.id}.icon") - (config.lib.assertions.iconValid - node.deviceIcon "nodes.${node.id}.deviceIcon") - ] - ) - ); - }; - } diff --git a/topology/options/services.nix b/topology/options/services.nix deleted file mode 100644 index e770396..0000000 --- a/topology/options/services.nix +++ /dev/null @@ -1,106 +0,0 @@ -f: { - lib, - config, - ... -}: let - inherit - (lib) - attrValues - flatten - flip - mkDefault - mkIf - mkOption - types - ; -in - f { - options.nodes = mkOption { - type = types.attrsOf (types.submodule { - options = { - services = mkOption { - description = "Defines a service that is running on this node."; - default = {}; - type = types.attrsOf (types.submodule (submod: { - options = { - id = mkOption { - description = "The id of this service"; - type = types.str; - readOnly = true; - default = submod.config._module.args.name; - }; - - name = mkOption { - description = "The name of this service"; - type = types.str; - }; - - hidden = mkOption { - description = "Whether this service should be hidden from graphs"; - default = false; - type = types.bool; - }; - - icon = mkOption { - description = "The icon for this service. Must be a path to an image or a valid icon name (.)."; - type = types.nullOr (types.either types.path types.str); - default = null; - }; - - info = mkOption { - description = "Additional high-profile information about this service, usually the url or listen address. Most likely shown directly below the name."; - default = ""; - type = types.lines; - }; - - details = mkOption { - description = "Additional detail sections that should be shown to the user."; - default = {}; - type = types.attrsOf (types.submodule (detailSubmod: { - options = { - name = mkOption { - description = "The name of this section"; - type = types.str; - readOnly = true; - default = detailSubmod.config._module.args.name; - }; - - order = mkOption { - description = "The order determines how sections are ordered. Lower numbers first, default is 100."; - type = types.int; - default = 100; - }; - - text = mkOption { - description = "The additional information to display"; - type = types.lines; - }; - }; - })); - }; - }; - - config = { - # Set the default icon, if an icon exists with a matching name - icon = mkIf (config.topology.isMainModule && config.icons.services ? ${submod.config.id}) ( - mkDefault ("services." + submod.config.id) - ); - }; - })); - }; - }; - }); - }; - - config = { - assertions = flatten (flip map (attrValues config.nodes) ( - node: - flip map (attrValues node.services) ( - service: [ - (config.lib.assertions.iconValid - service.icon "nodes.${node.id}.services.${service.id}") - ] - ) - )); - }; - } diff --git a/topology/topology/default.nix b/topology/topology/default.nix deleted file mode 100644 index 709ef47..0000000 --- a/topology/topology/default.nix +++ /dev/null @@ -1,144 +0,0 @@ -{ - lib, - config, - ... -}: let - inherit - (lib) - attrNames - concatLines - concatLists - concatStringsSep - filter - filterAttrs - flip - getAttrFromPath - literalExpression - mapAttrsToList - mkDefault - mkIf - mkMerge - mkOption - types - warnIf - ; - - availableRenderers = attrNames (filterAttrs (_: v: v == "directory") (builtins.readDir ./renderers)); -in { - 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 { - description = '' - The list of nixos configurations to process for topology rendering. - All of these must include the relevant nixos topology module. - ''; - type = types.unspecified; - }; - - renderer = mkOption { - description = "Which renderer to use for the default output. Available options: ${toString availableRenderers}"; - type = types.nullOr (types.enum availableRenderers); - default = "elk"; - }; - - output = mkOption { - description = "The derivation containing the rendered output"; - type = types.path; - readOnly = true; - defaultText = literalExpression ''config.renderers.${config.renderer}.output''; - }; - - lib = lib.mkOption { - default = {}; - type = lib.types.attrsOf lib.types.attrs; - description = lib.mdDoc '' - This option allows modules to define helper functions, constants, etc. - ''; - }; - - assertions = mkOption { - internal = true; - default = []; - example = [ - { - assertion = false; - message = "you can't enable this for that reason"; - } - ]; - description = lib.mdDoc '' - This option allows modules to express conditions that must - hold for the evaluation of the topology configuration to - succeed, along with associated error messages for the user. - ''; - type = types.listOf (types.submodule { - options = { - assertion = mkOption { - description = "The thing to assert."; - type = types.bool; - }; - - message = mkOption { - description = "The error message."; - type = types.str; - }; - }; - }); - }; - - warnings = mkOption { - internal = true; - default = []; - example = ["This is deprecated for that reason"]; - description = lib.mdDoc '' - This option allows modules to show warnings to users during - the evaluation of the topology configuration. - ''; - type = types.listOf types.str; - }; - - topology.isMainModule = mkOption { - description = "Whether this is the toplevel topology module."; - readOnly = true; - internal = true; - default = true; - type = types.bool; - }; - }; - - 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 = let - failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions); - in - if failedAssertions != [] - then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}" - else - warnIf (config.warnings != []) - (concatLines (["The following warnings were raised during evaluation:"] ++ config.warnings)) - (mkIf (config.renderer != null) (mkDefault config.renderers.${config.renderer}.output)); - - nodes = aggregate ["nodes"]; - networks = aggregate ["networks"]; - }; -} diff --git a/topology/topology/lazy.nix b/topology/topology/lazy.nix deleted file mode 100644 index 8c68a90..0000000 --- a/topology/topology/lazy.nix +++ /dev/null @@ -1,42 +0,0 @@ -lib: let - inherit - (lib) - all - assertMsg - isAttrs - mkOptionType - showOption - types - ; - - # Checks whether the value is a lazy value without causing - # it's value to be evaluated - isLazyValue = x: isAttrs x && x ? _lazyValue; - # Constructs a lazy value holding the given value. - lazyValue = value: {_lazyValue = value;}; - - # Represents a lazy value of the given type, which - # holds the actual value as an attrset like { _lazyValue = ; }. - # This allows the option to be defined and filtered from a defintion - # list without evaluating the value. - lazyValueOf = type: - mkOptionType rec { - name = "lazyValueOf ${type.name}"; - inherit (type) description descriptionClass emptyValue getSubOptions getSubModules; - check = isLazyValue; - merge = loc: defs: - assert assertMsg - (all (x: type.check x._lazyValue) defs) - "The option `${showOption loc}` is defined with a lazy value holding an invalid type"; - types.mergeOneOption loc defs; - substSubModules = m: types.uniq (type.substSubModules m); - functor = (types.defaultFunctor name) // {wrapped = type;}; - nestedTypes.elemType = type; - }; - - # Represents a value or lazy value of the given type that will - # automatically be coerced to the given type when merged. - lazyOf = type: types.coercedTo (lazyValueOf type) (x: x._lazyValue) type; -in { - inherit isLazyValue lazyValue lazyValueOf lazyOf; -} diff --git a/topology/topology/renderers/elk/default.nix b/topology/topology/renderers/elk/default.nix deleted file mode 100644 index 51fb2db..0000000 --- a/topology/topology/renderers/elk/default.nix +++ /dev/null @@ -1,304 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: let - inherit - (lib) - any - attrValues - concatStringsSep - elem - flatten - flip - foldl' - isAttrs - last - mapAttrs - mapAttrsToList - mkOption - optional - optionalAttrs - recursiveUpdate - stringLength - types - ; - - mapAttrsRecursiveInner = f: set: let - recurse = path: set: - f path ( - flip mapAttrs set ( - name: value: - if isAttrs value - then recurse (path ++ [name]) value - else value - ) - ); - in - recurse [] set; - - attrsToListify = ["children" "labels" "ports" "edges"]; - - # Converts an attrset to a list of values with a new field id reflecting the attribute name with all parent ids appended. - listify = path: mapAttrsToList (id: v: {id = concatStringsSep "." (path ++ [id]);} // v); - - # In nix we like to use refer to named attributes using attrsets over using lists, because it - # has merging capabilities and allows easy referencing. But elk needs some attributes like - # children as a list of objects, which we need to transform. - elkifySchema = schema: - flip mapAttrsRecursiveInner schema ( - path: value: - if path != [] && elem (last path) attrsToListify - then listify path value - else value - ); - - mkEdge = from: to: reverse: extra: - if reverse - then mkEdge to from false extra - else { - edges."${from}__${to}" = - { - sources = [from]; - targets = [to]; - } - // extra; - }; - - mkPort = recursiveUpdate { - width = 8; - height = 8; - style.stroke = "#485263"; - style.fill = "#b6beca"; - }; - - mkLabel = text: scale: extraStyle: { - height = scale * 12; - width = scale * 7.2 * (stringLength text); - inherit text; - style = - { - style = "font: ${toString (scale * 12)}px JetBrains Mono;"; - } - // extraStyle; - }; - - pathStyleFromNetworkStyle = style: - { - solid = { - stroke = style.primaryColor; - }; - dashed = - { - stroke = style.primaryColor; - stroke-dasharray = "10,8"; - stroke-linecap = "round"; - } - // optionalAttrs (style.secondaryColor != null) { - background = style.secondaryColor; - }; - dotted = - { - stroke = style.primaryColor; - stroke-dasharray = "2,6"; - stroke-linecap = "round"; - } - // optionalAttrs (style.secondaryColor != null) { - background = style.secondaryColor; - }; - } - .${style.pattern}; - - netToElk = net: [ - { - children.network.children."net:${net.id}" = { - svg = { - file = config.lib.renderers.svg.net.mkCard net; - scale = 0.8; - }; - properties."portLabels.placement" = "OUTSIDE"; - - ports.default = mkPort { - labels."00-name" = mkLabel "*" 1 {}; - }; - }; - } - ]; - - idForInterface = node: interfaceId: "children.node:${node.id}.ports.interface:${interfaceId}"; - - nodeInterfaceToElk = node: interface: let - netStyle = optionalAttrs (interface.network != null) { - fill = config.networks.${interface.network}.style.primaryColor; - }; - interfaceLabels = - { - "00-name" = mkLabel interface.id 1 {}; - } - // optionalAttrs (interface.mac != null) { - "50-mac" = mkLabel interface.mac 1 netStyle; - } - // optionalAttrs (interface.addresses != []) { - "60-addrs" = mkLabel (toString interface.addresses) 1 netStyle; - }; - in - [ - # Interface for node in main view - { - children."node:${node.id}".ports."interface:${interface.id}" = mkPort { - properties = optionalAttrs (node.renderer.preferredType == "card") { - "port.side" = "WEST"; - }; - labels = interfaceLabels; - }; - } - - # Interface for node in network-centric view - { - children.network.children."node:${node.id}".ports."interface:${interface.id}" = mkPort { - labels = interfaceLabels; - }; - } - - # Edge in network-centric view - (optionalAttrs (interface.network != null) ( - mkEdge ("children.network." + idForInterface node interface.id) "children.network.children.net:${interface.network}.ports.default" interface.virtual { - style = pathStyleFromNetworkStyle config.networks.${interface.network}.style; - } - )) - ] - ++ flatten (flip map interface.physicalConnections ( - conn: let - otherInterface = config.nodes.${conn.node}.interfaces.${conn.interface}; - in - optionalAttrs ( - (!any (y: y.node == node.id && y.interface == interface.id) otherInterface.physicalConnections) - || (node.id < conn.node) - ) ( - optional (!interface.renderer.hidePhysicalConnections && !otherInterface.renderer.hidePhysicalConnections) ( - # Edge in main view - mkEdge - (idForInterface node interface.id) - (idForInterface config.nodes.${conn.node} conn.interface) - conn.renderer.reverse - { - style = optionalAttrs (interface.network != null) ( - pathStyleFromNetworkStyle config.networks.${interface.network}.style - ); - } - ) - ) - )); - - nodeToElk = node: - [ - # Add node to main view - { - children."node:${node.id}" = { - svg = { - file = config.lib.renderers.svg.node.mkPreferredRender node; - scale = 0.8; - }; - properties = - { - "portLabels.placement" = "OUTSIDE"; - } - // optionalAttrs (node.renderer.preferredType == "card") { - # "portConstraints" = "FIXED_SIDE"; - }; - }; - } - # Add node to network-centric view - { - children.network.children."node:${node.id}" = { - svg = { - file = config.lib.renderers.svg.node.mkImageWithName node; - scale = 0.8; - }; - properties."portLabels.placement" = "OUTSIDE"; - }; - } - ] - ++ optional (node.parent != null) ( - { - children."node:${node.parent}".ports.guests = mkPort { - properties."port.side" = "EAST"; - style.stroke = "#49d18d"; - style.fill = "#78dba9"; - labels."00-name" = mkLabel "guests" 1 {}; - }; - } - // mkEdge "children.node:${node.parent}.ports.guests" "children.node:${node.id}" false { - style.stroke-dasharray = "10,8"; - style.stroke-linecap = "round"; - } - ) - ++ map (nodeInterfaceToElk node) (attrValues node.interfaces); -in { - options.renderers.elk = { - output = mkOption { - description = "The derivation containing the rendered output"; - type = types.path; - readOnly = true; - }; - }; - - config.renderers.elk.output = let - graph = elkifySchema (foldl' recursiveUpdate {} ( - [ - { - id = "root"; - layoutOptions = { - "org.eclipse.elk.algorithm" = "layered"; - "org.eclipse.elk.edgeRouting" = "ORTHOGONAL"; - "org.eclipse.elk.direction" = "RIGHT"; - "org.eclipse.elk.layered.allowNonFlowPortsToSwitchSides" = true; - "org.eclipse.elk.layered.crossingMinimization.strategy" = true; - "org.eclipse.elk.layered.nodePlacement.strategy" = "NETWORK_SIMPLEX"; - "org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers" = 40; - "org.eclipse.elk.layered.spacing.edgeEdgeBetweenLayers" = 25; - "org.eclipse.elk.spacing.edgeEdge" = 50; - "org.eclipse.elk.spacing.edgeNode" = 50; - }; - } - - # Add network-centric section - { - children.network = { - style = { - stroke = "#101419"; - stroke-width = 2; - fill = "#080a0d"; - rx = 12; - }; - labels."00-name" = mkLabel "Network View" 1 {}; - }; - } - - # Add service overview - { - children.services-overview = { - svg = { - file = config.lib.renderers.svg.services.mkOverview; - scale = 0.8; - }; - }; - } - - # Add network overview - { - children.network-overview = { - svg = { - file = config.lib.renderers.svg.net.mkOverview; - scale = 0.8; - }; - }; - } - ] - ++ flatten (map netToElk (attrValues config.networks)) - ++ flatten (map nodeToElk (attrValues config.nodes)) - )); - in - pkgs.writeText "graph.elk.json" (builtins.toJSON graph); -} diff --git a/topology/topology/renderers/svg/default.nix b/topology/topology/renderers/svg/default.nix deleted file mode 100644 index ed4ac68..0000000 --- a/topology/topology/renderers/svg/default.nix +++ /dev/null @@ -1,364 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: let - inherit - (lib) - attrValues - concatLines - filter - flip - hasSuffix - head - mkOption - optionalString - splitString - tail - types - ; - - fileBase64 = file: let - out = pkgs.runCommand "base64" {} '' - ${pkgs.coreutils}/bin/base64 -w0 < ${file} > $out - ''; - in "${out}"; - - htmlToSvgCommand = inFile: outFile: args: '' - ${lib.getExe pkgs.html-to-svg} \ - --font ${pkgs.jetbrains-mono}/share/fonts/truetype/JetBrainsMono-Regular.ttf \ - --font-bold ${pkgs.jetbrains-mono}/share/fonts/truetype/JetBrainsMono-Bold.ttf \ - --width ${toString (args.width or "auto")} \ - --height ${toString (args.height or "auto")} \ - ${inFile} ${outFile} - ''; - - renderHtmlToSvg = card: name: let - out = pkgs.runCommand "${name}.svg" {} '' - ${htmlToSvgCommand (pkgs.writeText "${name}.html" card.html) "$out" card} - ''; - in "${out}"; - - html = rec { - mkImage = twAttrs: file: - if file == null - then '' -
- '' - else if hasSuffix ".svg" file - then let - withoutPrefix = head (tail (splitString "" withoutPrefix); - in '''' - else if hasSuffix ".png" file - then '''' - else if hasSuffix ".jpg" file || hasSuffix ".jpeg" file - then '''' - else builtins.throw "Unsupported icon file type: ${file}"; - - mkImageMaybeIf = cond: twAttrs: file: optionalString (cond && file != null) (mkImage twAttrs file); - mkImageMaybe = mkImageMaybeIf true; - - mkSpacer = name: - /* - html - */ - '' -
-
-
- ${name} -
-
-
- ''; - - mkRootContainer = twAttrs: contents: - /* - html - */ - '' -
-
- ${contents} -
-
- ''; - - mkCardContainer = mkRootContainer "bg-[#101419] rounded-xl"; - - spacingMt2 = '' -
- ''; - - net = { - netStylePreview = net: twAttrs: let - secondaryColor = - if net.style.secondaryColor == null - then "#00000000" - else net.style.secondaryColor; - in - { - solid = ''
''; - dashed = ''
''; - dotted = ''
''; - } - .${net.style.pattern}; - - mkCard = net: { - width = 480; - html = - mkCardContainer - /* - html - */ - '' -
- ${html.net.netStylePreview net "w-12 h-4 mr-4 rounded-md"} -

${net.name}

-
- ${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get net.icon)} -
-
- ${optionalString (net.cidrv4 != null) ''
CIDRv4${net.cidrv4}
''} - ${optionalString (net.cidrv6 != null) ''
CIDRv6${net.cidrv6}
''} - ''; - }; - - mkOverview = { - width = 480; - html = - mkCardContainer - /* - html - */ - '' -
-

Networks Overview

-
- - ${concatLines (flip map (attrValues config.networks) ( - net: - /* - html - */ - '' -
-
- ${html.net.netStylePreview net "w-12 h-4 mt-2 mr-4 rounded-md"} -
-

${net.name}

- ${optionalString (net.cidrv4 != null) ''
CIDRv4${net.cidrv4}
''} - ${optionalString (net.cidrv6 != null) ''
CIDRv6${net.cidrv6}
''} -
-
-
- '' - ))} - - ${spacingMt2} - ''; - }; - }; - - node = rec { - mkInterface = interface: - /* - html - */ - '' -
- ${mkImage "w-8 h-8 m-1" (config.lib.icons.get interface.icon)} - ${interface.id} -
- ''; - - serviceDetail = detail: - /* - html - */ - '' -
- ${detail.name} - - ${detail.text} -
- ''; - - serviceDetails = service: - optionalString (service.details != {}) ''
'' - # FIXME: order not respected - + concatLines ((map serviceDetail) (attrValues service.details)); - - mkService = { - additionalInfo ? "", - includeDetails ? true, - ... - }: service: - /* - html - */ - '' -
-
- ${mkImage "w-12 h-12 mr-4 rounded-lg" (config.lib.icons.get service.icon)} -
-

${service.name}

- ${optionalString (service.info != "") ''

${service.info}

''} - ${additionalInfo} -
-
- ${optionalString includeDetails (serviceDetails service)} -
- ''; - - mkGuest = guest: - /* - html - */ - '' -
-
- ${mkImageMaybe "w-12 h-12 mr-4" (config.lib.icons.get guest.deviceIcon)} -
-

${guest.name}

-

${guest.guestType}

-
-
-
- ''; - - mkCard = node: let - services = filter (x: !x.hidden) (attrValues node.services); - guests = filter (x: x.parent == node.id) (attrValues config.nodes); - in { - width = 480; - html = - mkCardContainer - /* - html - */ - '' -
- ${mkImageMaybe "w-8 h-8 mr-4" (config.lib.icons.get node.icon)} -
- ${node.name} - ${optionalString (node.hardware.info != null) ''${node.hardware.info}''} -
-
- ${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get node.deviceIcon)} -
- - ${optionalString (node.interfaces != {}) ''
''} - ${concatLines (map mkInterface (attrValues node.interfaces))} - ${optionalString (node.interfaces != {}) ''
''} - - ${concatLines (map mkGuest guests)} - ${optionalString (guests != []) spacingMt2} - - ${concatLines (map (mkService {}) services)} - ${optionalString (services != []) spacingMt2} - ''; - }; - - mkImageWithName = node: { - html = let - deviceIconImage = config.lib.icons.get node.deviceIcon; - in - mkRootContainer "items-center" - /* - html - */ - '' -
- ${mkImageMaybe "w-8 h-8" (config.lib.icons.get node.icon)} -
- ${node.name} - ${optionalString (node.hardware.info != null) ''${node.hardware.info}''} -
- ${optionalString (deviceIconImage != null && node.hardware.image != null -> deviceIconImage != node.hardware.image) - '' -
- ${mkImageMaybe "w-12 h-12" deviceIconImage} - ''} -
- -
- ${mkImageMaybe "h-24" node.hardware.image} -
- ''; - }; - - mkPreferredRender = node: - ( - if node.renderer.preferredType == "image" && node.hardware.image != null - then mkImageWithName - else mkCard - ) - node; - }; - - services.mkOverview = { - width = 480; - html = - mkCardContainer - /* - html - */ - '' -
-

Services Overview

-
- - ${concatLines (flip map (attrValues config.nodes) ( - node: let - services = filter (x: !x.hidden) (attrValues node.services); - in - concatLines ( - flip map services ( - html.node.mkService { - additionalInfo = ''

${node.name}

''; - includeDetails = false; - } - ) - ) - ))} - - ${spacingMt2} - ''; - }; - }; -in { - options.renderers.svg = { - # FIXME: colors.bg0 = mkColorOption "bg0" "#"; - - output = mkOption { - description = "The derivation containing the rendered output"; - type = types.path; - readOnly = true; - }; - }; - - config = { - lib.renderers.svg = { - services.mkOverview = renderHtmlToSvg html.services.mkOverview "services-overview"; - - net.mkCard = net: renderHtmlToSvg (html.net.mkCard net) "card-net-${net.id}"; - net.mkOverview = renderHtmlToSvg html.net.mkOverview "networks-overview"; - - node = { - mkImageWithName = node: renderHtmlToSvg (html.node.mkImageWithName node) "image-with-name-${node.id}"; - mkCard = node: renderHtmlToSvg (html.node.mkCard node) "card-node-${node.id}"; - mkPreferredRender = node: renderHtmlToSvg (html.node.mkPreferredRender node) "preferred-render-node-${node.id}"; - }; - }; - - renderers.svg.output = pkgs.runCommand "topology-svgs" {} '' - mkdir -p $out/nodes - ${concatLines (flip map (attrValues config.nodes) (node: '' - cp ${config.lib.renderers.svg.node.mkPreferredRender node} $out/nodes/${node.id}.svg - ''))} - ''; - }; -}