refactor(topology): move to own repository
96
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
|
||||
*/
|
||||
{
|
||||
topology = import inputs.nix-topology {
|
||||
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
|
||||
{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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}
|
|
@ -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("<input>", "satori html file to render")
|
||||
.argument("<output>", "output svg")
|
||||
.option("--font <font>", "Sets the font")
|
||||
.option("--font-bold <font>", "Sets the bold font")
|
||||
.option("-w, --width <width>", "Sets the width of the output. Use auto for automatic scaling.", "auto")
|
||||
.option("-h, --height <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();
|
201
pkgs/html-to-svg/package-lock.json
generated
|
@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <oddlama@oddlama.org>",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"html-to-svg": "./main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^12.0.0",
|
||||
"satori": "^0.10.13",
|
||||
"satori-html": "^0.3.2"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 7 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="400" aria-hidden="true" class="iconify iconify--noto" viewBox="0 0 128 128"><path fill="#e4eaee" d="M23.45 62.3c.72-.72-1.27-9.29 7.6-15.91s14.92-2.67 15.77-2.96c.84-.28 4.79-17.6 21.4-22.1s33.93 3.94 38.01 18.02c3.73 12.87.84 21.54 1.27 22.1.42.56 8.45.28 13.09 7.74s2.96 12.11 2.96 12.11l-29.56 9.15h-47.3S5.02 79.47 4.6 77.5s.53-8.37 7.32-12.25c5.9-3.37 10.26-1.68 11.53-2.95"/><path fill="#bacdd2" d="M35.16 92.84s-15.78 3.3-26.45-4.96C2.29 82.9 4.63 74.83 4.63 74.83s4.6 4.65 13.89 5.91c9.29 1.27 19.71.84 19.71.84s2.6 4.44 12.39 6.48c12.27 2.55 18.74-3.73 18.74-3.73s3.36 4.02 15.19 4.3 18.46-7.98 19.57-8.17c.56-.09 3.82 2.87 10.28 1.83 6.15-.99 9.39-3.66 9.39-3.66s.89 6.62-5.3 10.7c-4.83 3.18-13.23 3.52-13.23 3.52s-1.28 4.91-7.05 8.48c-5.36 3.33-14.6 4.44-21.44 2.4-8.59-2.56-10.72-6.47-10.72-6.47s-6.4 3.75-16.4 2.48c-9.45-1.18-14.49-6.9-14.49-6.9"/></svg>
|
Before Width: | Height: | Size: 929 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" viewBox="0 0 16.845 14.205" style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><path d="M1.143-.807q.021 0 .041.008a.1.1 0 0 1 .033.023.1.1 0 0 1 .031.074v.561a.1.1 0 0 1-.031.075.2.2 0 0 1-.033.023.1.1 0 0 1-.041.008H.976q.012.014.019.032a.1.1 0 0 1 .007.038V.14H.371V.035q0-.019.007-.038a.1.1 0 0 1 .019-.032H.23a.1.1 0 0 1-.041-.008.2.2 0 0 1-.033-.023.107.107 0 0 1-.031-.075v-.561a.098.098 0 0 1 .031-.074.1.1 0 0 1 .033-.023.1.1 0 0 1 .041-.008z" style="fill-rule:nonzero" transform="matrix(15 0 0 15 -1.875 12.105)"/><path d="M.511.02H.476v-.145h.035zm0 0H.476v-.145h.035zm.386 0H.862v-.145h.035zm0 0H.862v-.145h.035z" style="fill:#a6a6a6;fill-rule:nonzero" transform="matrix(15 0 0 15 -1.875 12.105)"/><path d="M1.178-.141q0 .015-.011.025a.03.03 0 0 1-.024.011H.23a.03.03 0 0 1-.024-.011.03.03 0 0 1-.011-.025v-.561q0-.015.011-.025a.03.03 0 0 1 .024-.01h.913q.013 0 .024.01a.03.03 0 0 1 .011.025zM.932.07H.441V.035q0-.015.01-.025A.03.03 0 0 1 .476 0h.421q.015 0 .025.01t.01.025z" style="fill:#ccc;fill-rule:nonzero" transform="matrix(15 0 0 15 -1.875 12.105)"/><path d="M.23-.702h.912v.526H.23z" style="fill:#1f1f1f;fill-rule:nonzero" transform="matrix(15 0 0 15 -1.875 12.105)"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" viewBox="0 0 16.845 12.63" style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><path d="m1.143-.134.077.077a.1.1 0 0 1 .028.067.1.1 0 0 1-.008.037.1.1 0 0 1-.02.03.1.1 0 0 1-.03.02.1.1 0 0 1-.037.008H.22A.1.1 0 0 1 .183.097a.1.1 0 0 1-.03-.02.1.1 0 0 1-.02-.03.1.1 0 0 1-.001-.073.1.1 0 0 1 .021-.031L.23-.134v-.498q0-.021.009-.041a.1.1 0 0 1 .022-.033.097.097 0 0 1 .074-.031h.703a.1.1 0 0 1 .074.031q.014.013.022.033a.1.1 0 0 1 .009.041z" style="fill-rule:nonzero" transform="matrix(15 0 0 15 -1.875 11.055)"/><path d="m1.073-.105.097.098q.008.007.008.017a.03.03 0 0 1-.008.018q-.006.007-.017.007H.22A.02.02 0 0 1 .203.028.03.03 0 0 1 .195.01q0-.01.008-.017L.3-.105v-.527q0-.013.011-.024a.03.03 0 0 1 .024-.011h.703q.013 0 .024.011a.03.03 0 0 1 .011.024z" style="fill:#ccc;fill-rule:nonzero" transform="matrix(15 0 0 15 -1.875 11.055)"/><path d="M.335-.632h.702v.491H.335z" style="fill:#1f1f1f;fill-rule:nonzero" transform="matrix(15 0 0 15 -1.875 11.055)"/><path d="m1.02-.105.059.059.002.004q0 .007-.007.007H.81L.827 0H.546l.017-.035H.299L.294-.037.292-.042q0-.003.009-.012L.32-.073l.02-.02.013-.012z" style="fill:#a6a6a6;fill-rule:nonzero" transform="matrix(15 0 0 15 -1.875 11.055)"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><linearGradient id="a" x1="315.593" x2="382.668" y1="56.386" y2="207.038" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#51bcfe"/><stop offset="1" style="stop-color:#6c69ff"/></linearGradient><path d="M356.53 256.7c0-1.68-125.8-218.04-125.8-218.04h55.35l33.55 57.03 33.55-57.03h28.51l13.42 23.48-43.61 85.54 33.55 58.7z" style="fill:url(#a)"/><linearGradient id="b" x1="149.534" x2="370.946" y1="214.376" y2="334.146" gradientTransform="scale(-1)rotate(-59.76 -517.584 587.633)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#e4c4ff"/><stop offset="1" style="stop-color:#7a5eef"/></linearGradient><path d="M305.69 341.77c1.46-.83 126.95-217.38 126.95-217.38l27.45 48.06-32.88 57.41 66.16.85 14.14 24.76-13.74 23.3-95.91 4.56-34.34 58.24z" style="fill:url(#b)"/><linearGradient id="c" x1="297.415" x2="380.142" y1="44.519" y2="188.22" gradientTransform="scale(-1)rotate(-60.071 -442.026 425.138)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#51bcfe"/><stop offset="1" style="stop-color:#6c69ff"/></linearGradient><path d="M202.2 340.41c1.45.84 251.73-.23 251.73-.23l-27.62 47.97-66.16.62 32.69 57.52L378.61 471l-27.05-.09-52.38-80.47-67.61-.22z" style="fill:url(#c)"/><linearGradient id="d" x1="81.563" x2="302.975" y1="127.999" y2="247.769" gradientTransform="scale(-1)rotate(60.117 492.003 -432.248)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#e4c4ff"/><stop offset="1" style="stop-color:#7a5eef"/></linearGradient><path d="M153.93 254.72c0 1.68 125.23 218.37 125.23 218.37l-55.35-.14-33.4-57.11-33.69 56.94-28.51-.07-13.36-23.52 43.83-85.43-33.39-58.79z" style="fill:url(#d)"/><linearGradient id="e" x1="296.663" x2="379.39" y1="166.128" y2="309.829" gradientTransform="scale(-1)rotate(60.792 525.44 -364.264)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#51bcfe"/><stop offset="1" style="stop-color:#6c69ff"/></linearGradient><path d="M208.09 166.3c-1.46.82-128.94 216.2-128.94 216.2l-27.01-48.31 33.41-57.11-66.15-1.45-13.91-24.9 13.95-23.17 95.94-3.67 34.87-57.93z" style="fill:url(#e)"/><linearGradient id="f" x1="63.565" x2="284.977" y1="79.002" y2="198.773" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#e4c4ff"/><stop offset="1" style="stop-color:#7a5eef"/></linearGradient><path d="M306.07 167.25c-1.45-.84-251.73-.21-251.73-.21l27.7-47.92 66.16-.5-32.58-57.58 14.27-24.69 27.04.13 52.23 80.56 67.61.34z" style="fill:url(#f)"/></svg>
|
Before Width: | Height: | Size: 2.5 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" class="icon" viewBox="0 -127 1278 1278"><path fill="#4467AE" d="M186.97 390.02c249.28-143.93 654.06-143.93 903.34 0 249.28 143.92 249.28 377.62 0 521.54-249.28 143.93-654.06 143.93-903.34 0-249.29-143.92-249.29-377.62 0-521.54"/><path fill="#4467AE" d="M.005 368.72h1277.3v282.07H.005z"/><path fill="#6D8ACA" d="M186.97 107.95c249.28-143.93 654.06-143.93 903.34 0 249.28 143.92 249.28 377.62 0 521.54-249.28 143.93-654.06 143.93-903.34 0-249.29-143.92-249.29-377.62 0-521.54"/><path fill="#fff" d="m436.24 524.26 57.323 33.095-164.56-6.82-11.815-95.008 57.323 33.095 148.04-85.475 61.731 35.642-148.04 85.47zm410.08-308.04-57.323-33.09 164.56 6.82 11.815 95.008-57.323-33.095-148.04 85.47-61.731-35.637 148.04-85.475zm-400.9-16.478 57.323-33.09-164.56 6.82-11.815 95.008 57.323-33.095 148.04 85.47 61.731-35.637-148.04-85.475zm420.08 313.82-57.323 33.095 164.56-6.82 11.815-95.008-57.323 33.095-148.04-85.47-61.731 35.637 148.04 85.47z"/></svg>
|
Before Width: | Height: | Size: 1,009 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" class="icon" viewBox="0 -166 1356 1356"><path fill="#4467AE" d="M0 389.7h1353.8v242.37H0z"/><path fill="#4467AE" d="M711.71 249.3c-19.199-11.087-48.289-12.293-64.921-2.694l-635.57 366.95c-16.626 9.6-14.538 26.397 4.661 37.484l626.24 361.56c19.204 11.087 48.294 12.293 64.921 2.694l635.57-366.95c16.631-9.6 14.543-26.397-4.661-37.484z"/><path fill="#6D8ACA" d="M711.71 9.17C692.511-1.91 663.421-3.121 646.789 6.483l-635.57 366.95c-16.626 9.6-14.538 26.392 4.661 37.48l626.24 361.56c19.204 11.086 48.294 12.292 64.921 2.693l635.57-366.95c16.631-9.605 14.543-26.397-4.661-37.484z"/><path fill="#fff" d="m296.2 413.75-60.275 34.801 12.424-99.908 173.04-7.173-60.275 34.801 187.14 108.04-64.916 37.479-187.14-108.04zm237.01-136.84-60.275 34.801 12.424-99.908 173.04-7.173-60.275 34.801 187.14 108.04-64.916 37.474-187.14-108.04zM1057.6 367.89l60.275-34.801-12.424 99.908-173.05 7.173 60.28-34.801-187.14-108.04 64.916-37.479 187.14 108.04zM820.58 504.73l60.28-34.801-12.424 99.908-173.05 7.173 60.28-34.806-187.14-108.04 64.916-37.479 187.13 108.04z"/></svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="800" height="800" viewBox="0 0 512 512"><path fill="#e3e6eb" d="M168 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M256 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M212 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12"/><path fill="#e3e6eb" d="M460 0H52C23.28 0 0 23.28 0 52v408c0 28.72 23.28 52 52 52h408c28.72 0 52-23.28 52-52V52c0-28.72-23.28-52-52-52m-16 284h-88.024c-2.212 0-3.976 1.64-3.976 3.848V348H160v-60.152c0-2.208-1.616-3.848-3.828-3.848H68V68h44v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h44z"/><path fill="#e3e6eb" d="M124 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M388 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M344 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M300 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12"/></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 -5 34 34" xmlns="http://www.w3.org/2000/svg"><path d="m29.294 7.765c-.572.016-1.03.484-1.03 1.059s.458 1.043 1.029 1.059h.002c.572-.016 1.03-.484 1.03-1.059s-.458-1.043-1.029-1.059zm-1.059 7.412v.029c0 .585.474 1.059 1.059 1.059s1.059-.474 1.059-1.059c0-.01 0-.02 0-.031v.001-3.53c0-.009 0-.019 0-.029 0-.585-.474-1.059-1.059-1.059s-1.059.474-1.059 1.059v.031-.002zm-2.47-7.412c.572.016 1.03.484 1.03 1.059s-.458 1.043-1.029 1.059h-.002-3.882v1.412h2.47c.572.016 1.03.484 1.03 1.059s-.458 1.043-1.029 1.059h-.002-2.47v1.765.029c0 .585-.474 1.059-1.059 1.059s-1.059-.474-1.059-1.059c0-.01 0-.02 0-.031v.002-6.354c0-.001 0-.003 0-.005 0-.582.472-1.054 1.054-1.054h.005zm8.117 2.117v4.235c0 3.119-2.529 5.647-5.647 5.647h-2.118c-2.225 2.599-5.51 4.235-9.176 4.235s-6.951-1.636-9.163-4.219l-.014-.016h-2.118c-3.119 0-5.647-2.529-5.647-5.647v-4.235c0-3.119 2.529-5.647 5.647-5.647h2.118c2.225-2.599 5.51-4.235 9.176-4.235s6.951 1.636 9.163 4.219l.014.016h2.118c3.119 0 5.647 2.529 5.647 5.647zm-19.412-2.117c-.572.016-1.03.484-1.03 1.059s.458 1.043 1.029 1.059h.001c.572-.016 1.03-.484 1.03-1.059s-.458-1.043-1.029-1.059h-.002zm-1.059 7.412c.016.572.484 1.03 1.059 1.03s1.043-.458 1.059-1.029v-.002-3.53c-.016-.572-.484-1.03-1.059-1.03s-1.043.458-1.059 1.029v.002zm-3.177.117 1.647-6c.068-.136.107-.297.107-.466 0-.589-.478-1.067-1.067-1.067-.504 0-.927.35-1.039.821l-.001.007-.706 2.588-.706-2.588c-.138-.462-.56-.793-1.059-.793s-.92.331-1.057.786l-.002.008-.706 2.588-.706-2.588c-.124-.46-.538-.794-1.029-.794-.588 0-1.064.476-1.064 1.064 0 .158.034.307.096.442l-.003-.007 1.647 6c.072.527.518.928 1.059.928s.987-.402 1.058-.923l.001-.006.706-2.47.706 2.47c.051.544.506.967 1.059.967s1.008-.422 1.058-.962v-.004zm18-9.647h-7.411c-.004 0-.009 0-.014 0-1.747 0-3.163 1.416-3.163 3.163v.014-.001 6.353.019c0 1.669-1.292 3.037-2.93 3.157l-.01.001h13.53.018c2.329 0 4.218-1.888 4.218-4.218 0-.006 0-.012 0-.019v.001-4.236c0-.005 0-.011 0-.018 0-2.329-1.888-4.218-4.218-4.218-.006 0-.012 0-.019 0h.001z"/></svg>
|
Before Width: | Height: | Size: 2.1 KiB |
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="WireGuard" role="img"
|
||||
viewBox="0 0 512 512"><rect
|
||||
width="512" height="512"
|
||||
rx="15%"
|
||||
fill="#88171a"/><path d="m238 53l35 8 0 2c-15 2-30-4-45-5 11 7 23 11 35 15-19 16-35-5-56 9 20 10 19 8 21 27-9 1-24 10-27 16 13 3 28 0 41 8-4 3-14 7-18 10 9 2 20-2 25 1 19 16 54 38 64 60 17 37-22 77-60 83-53 11-83 66-64 117 19 50 78 72 125 46 66-40 56-108 16-145-2-2-4-2-6 0-14 9-29 17-45 24 36 8 41 35 37 54-13 48-78 37-85-4-3-19 7-38 24-46 59-26 87-30 104-97 6-38-3-58-31-80-11-11-33-18-40-35-1-2 1-6 3-6 10-2 49-3 49-1 7 7 13-4 16-9-10-2-21-1-29-1-1 0-3-2-4-3 1 -1 3-2 4-2h41c0-7-9-17-18-19v3c-8 1-16-1-24-4-4-3-7-9-11-11-16-9-33-16-54-16-10 0-17 1-23 1zm74 30l3 3-4 2c-2 1-3 0-4-1-2-3 4-5 5-4zm-120 96c-54 33-51 109-3 139 4 2 6 2 8-1 12-15 23-22 36-30-25-4-38-16-37-33-4-60 83-54 74 2-2 10-8 19-16 25 27-6 47-21 55-48 2-8 2-19-2-26-30-44-75-53-115-28zm-62 195c16-7 33-10 49-13 1-13 5-26 13-36-30 0-55 20-62 49z" fill="#ffffff"/></svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg viewBox="107.05 62.185 94.168 94.168" xmlns="http://www.w3.org/2000/svg">
|
||||
<path transform="translate(120.48 74.519)" d="M33.6-12C19.4-12 2.2-8.6-12-1.3c0 15.9-.2 55.7 45.6 82.8C79.5 54.4 79.3 14.7 79.3-1.3 65.1-8.6 47.9-12 33.6-12" fill="#68bc71"/>
|
||||
<path transform="translate(120.48 74.519)" d="M33.6 81.5C-12.2 54.4-12 14.7-12-1.3 2.1-8.6 19.3-12 33.6-12z" fill="#67b279"/>
|
||||
<path transform="translate(120.48 74.519)" d="m32 50.4 27.6-37.2c-2-1.6-3.8-.5-4.8.4l-23 23.9-8.7-10.4C18.9 22.4 13.3 26 12 27z" fill="#fff"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 533 B |
|
@ -1,7 +0,0 @@
|
|||
<svg viewBox="0 0 212 212" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 69.303 165.414 L 69.303 79.788 C 69.303 55.66 88.862 36.101 112.99 36.101 L 130.465 36.101" style="fill: none; stroke: rgb(255, 102, 0); stroke-width: 25px;"/>
|
||||
<path d="M 69.303 165.414 L 69.303 139.202 C 69.303 115.074 88.862 95.515 112.99 95.515 L 130.465 95.515" style="fill: none; stroke-width: 25px; stroke: rgb(212, 0, 0);"/>
|
||||
<circle cx="142.697" cy="36.101" r="15.727" style="fill: none; stroke: rgb(255, 102, 0); stroke-width: 15px;"/>
|
||||
<circle cx="142.697" cy="95.515" r="15.727" style="fill: none; stroke-width: 15px; stroke: rgb(212, 0, 0);"/>
|
||||
<circle cx="69.303" cy="175.899" r="15.727" style="fill: none; stroke: rgb(212, 0, 0); stroke-width: 15px;"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 755 B |
|
@ -1 +0,0 @@
|
|||
<svg viewBox="-7.00 0 365 365" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" x1="175.5" x2="175.5" y1="30%" y2="99%" gradientUnits="userSpaceOnUse"><stop stop-color="#f05a28" offset="0"/><stop stop-color="#fbca0a" offset="1"/></linearGradient><path d="M342 161.2c-.6-6.1-1.6-13.1-3.6-20.9-2-7.7-5-16.2-9.4-25s-10.1-17.9-17.5-26.8c-2.9-3.5-6.1-6.9-9.5-10.2 5.1-20.3-6.2-37.9-6.2-37.9-19.5-1.2-31.9 6.1-36.5 9.4-.8-.3-1.5-.7-2.3-1-3.3-1.3-6.7-2.6-10.3-3.7q-5.25-1.65-10.8-3c-3.7-.9-7.4-1.6-11.2-2.2-.7-.1-1.3-.2-2-.3C214.2 12.4 189.8 1 189.8 1c-27.3 17.3-32.4 41.5-32.4 41.5s-.1.5-.3 1.4c-1.5.4-3 .9-4.5 1.3-2.1.6-4.2 1.4-6.2 2.2-2.1.8-4.1 1.6-6.2 2.5-4.1 1.8-8.2 3.8-12.2 6-3.9 2.2-7.7 4.6-11.4 7.1l-1-.4c-37.8-14.4-71.3 2.9-71.3 2.9-3.1 40.2 15.1 65.5 18.7 70.1-.9 2.5-1.7 5-2.5 7.5-2.8 9.1-4.9 18.4-6.2 28.1-.2 1.4-.4 2.8-.5 4.2C18.8 192.7 8.5 228 8.5 228c29.1 33.5 63.1 35.6 63.1 35.6l.1-.1c4.3 7.7 9.3 15 14.9 21.9 2.4 2.9 4.8 5.6 7.4 8.3-10.6 30.4 1.5 55.6 1.5 55.6 32.4 1.2 53.7-14.2 58.2-17.7 3.2 1.1 6.5 2.1 9.8 2.9 10 2.6 20.2 4.1 30.4 4.5 2.5.1 5.1.2 7.6.1h3.6l1.6-.1v.1c15.3 21.8 42.1 24.9 42.1 24.9 19.1-20.1 20.2-40.1 20.2-44.4v-1.8c4-2.8 7.8-5.8 11.4-9.1 7.6-6.9 14.3-14.8 19.9-23.3l1.5-2.4c21.6 1.2 36.9-13.4 36.9-13.4-3.6-22.5-16.4-33.5-19.1-35.6 0 0-.1-.1-.3-.2s-.2-.2-.2-.2c-.1-.1-.3-.2-.5-.3.1-1.4.2-2.7.3-4.1.2-2.4.2-4.9.2-7.3v-3.8l-.1-1.5-.1-2c0-.7-.1-1.3-.2-1.9s-.1-1.3-.2-1.9l-.2-1.9-.3-1.9c-.4-2.5-.8-4.9-1.4-7.4-2.3-9.7-6.1-18.9-11-27.2-5-8.3-11.2-15.6-18.3-21.8-7-6.2-14.9-11.2-23.1-14.9-8.3-3.7-16.9-6.1-25.5-7.2-4.3-.6-8.6-.8-12.9-.7h-3.2l-1.6.1c-.6 0-1.2.1-1.7.1-2.2.2-4.4.5-6.5.9-8.6 1.6-16.7 4.7-23.8 9s-13.3 9.6-18.3 15.6-8.9 12.7-11.6 19.6-4.2 14.1-4.6 21c-.1 1.7-.1 3.5-.1 5.2v1.3l.1 1.4c.1.8.1 1.7.2 2.5.3 3.5 1 6.9 1.9 10.1 1.9 6.5 4.9 12.4 8.6 17.4s8.2 9.1 12.9 12.4c4.7 3.2 9.8 5.5 14.8 7s10 2.1 14.7 2.1h2.6c.3 0 .6 0 .9-.1.5 0 1-.1 1.5-.1.1 0 .3 0 .4-.1l.5-.1c.3 0 .6-.1.9-.1.6-.1 1.1-.2 1.7-.3s1.1-.2 1.6-.4c1.1-.2 2.1-.6 3.1-.9 2-.7 4-1.5 5.7-2.4 1.8-.9 3.4-2 5-3 .4-.3.9-.6 1.3-1 1.6-1.3 1.9-3.7.6-5.3-1.1-1.4-3.1-1.8-4.7-.9l-1.2.6c-1.4.7-2.8 1.3-4.3 1.8s-3.1.9-4.7 1.2c-.8.1-1.6.2-2.5.3-.4 0-.8.1-1.3.1h-2.4c-.5 0-1 0-1.5-.1h-.6c-.2 0-.5 0-.7-.1-.5-.1-.9-.1-1.4-.2-3.7-.5-7.4-1.6-10.9-3.2-3.6-1.6-7-3.8-10.1-6.6s-5.8-6.1-7.9-9.9-3.6-8-4.3-12.4c-.3-2.2-.5-4.5-.4-6.7 0-.6.1-1.2.1-1.8v-.8c0-.3.1-.6.1-.9.1-1.2.3-2.4.5-3.6 1.7-9.6 6.5-19 13.9-26.1 1.9-1.8 3.9-3.4 6-4.9s4.4-2.8 6.8-3.9 4.8-2 7.4-2.7c2.5-.7 5.1-1.1 7.8-1.4 1.3-.1 2.6-.2 4-.2h3.1l1.1.1c2.9.2 5.7.6 8.5 1.3 5.6 1.2 11.1 3.3 16.2 6.1 10.2 5.7 18.9 14.5 24.2 25.1 2.7 5.3 4.6 11 5.5 16.9.2 1.5.4 3 .5 4.5l.1 1.1.1 1.1v4.3c0 .7-.1 1.9-.1 2.6-.1 1.6-.3 3.3-.5 4.9s-.5 3.2-.8 4.8-.7 3.2-1.1 4.7q-1.2 4.65-3 9.3c-2.4 6-5.6 11.8-9.4 17.1-7.7 10.6-18.2 19.2-30.2 24.7-6 2.7-12.3 4.7-18.8 5.7-3.2.6-6.5.9-9.8 1h-5c-1.8 0-3.5-.1-5.3-.3-7-.5-13.9-1.8-20.7-3.7-6.7-1.9-13.2-4.6-19.4-7.8-12.3-6.6-23.4-15.6-32-26.5-4.3-5.4-8.1-11.3-11.2-17.4s-5.6-12.6-7.4-19.1c-1.8-6.6-2.9-13.3-3.4-20.1l-.1-1.3v-8.9c.1-3.3.4-6.8.8-10.2s1-6.9 1.7-10.3 1.5-6.8 2.5-10.2c1.9-6.7 4.3-13.2 7.1-19.3 5.7-12.2 13.1-23.1 22-31.8 2.2-2.2 4.5-4.2 6.9-6.2q3.6-2.85 7.5-5.4c2.5-1.7 5.2-3.2 7.9-4.6 1.3-.7 2.7-1.4 4.1-2l2.1-.9 2.1-.9c2.8-1.2 5.7-2.2 8.7-3.1.7-.2 1.5-.4 2.2-.7.7-.2 1.5-.4 2.2-.6 1.5-.4 3-.8 4.5-1.1.7-.2 1.5-.3 2.3-.5s1.5-.3 2.3-.5c.8-.1 1.5-.3 2.3-.4l1.1-.2 1.2-.2c.8-.1 1.5-.2 2.3-.3.9-.1 1.7-.2 2.6-.3.7-.1 1.9-.2 2.6-.3.5-.1 1.1-.1 1.6-.2l1.1-.1.5-.1h.6c.9-.1 1.7-.1 2.6-.2l1.3-.1h1c.7 0 1.5-.1 2.2-.1 2.9-.1 5.9-.1 8.8 0 5.8.2 11.5.9 17 1.9 11.1 2.1 21.5 5.6 31 10.3 9.5 4.6 17.9 10.3 25.3 16.5.5.4.9.8 1.4 1.2.4.4.9.8 1.3 1.2.9.8 1.7 1.6 2.6 2.4s1.7 1.6 2.5 2.4 1.6 1.6 2.4 2.5c3.1 3.3 6 6.6 8.6 10 5.2 6.7 9.4 13.5 12.7 19.9l.6 1.2.6 1.2c.4.8.8 1.6 1.1 2.4.4.8.7 1.5 1.1 2.3.3.8.7 1.5 1 2.3 1.2 3 2.4 5.9 3.3 8.6 1.5 4.4 2.6 8.3 3.5 11.7.3 1.4 1.6 2.3 3 2.1 1.5-.1 2.6-1.3 2.6-2.8.1-3.6 0-7.9-.5-12.8" fill="url(#a)"/></svg>
|
Before Width: | Height: | Size: 3.9 KiB |
|
@ -1,33 +0,0 @@
|
|||
<svg viewBox="0 -25.13 137.58 137.58" xmlns="http://www.w3.org/2000/svg">
|
||||
<path transform="translate(-44.452 -54.465)" d="M62.538 110.234c-3.363 8.127 5.785 15.619 5.824 15.74-1.67-4.902-3.437-8.859-.189-12.285M138.442 109.7c3.363 8.126-5.785 15.618-5.824 15.739 1.67-4.902 3.437-8.858.189-12.284" fill="#803300" stroke="#000" stroke-linejoin="round"/>
|
||||
<path transform="translate(-44.452 -54.465)" d="M100.247 67.212c-1.309-.015-2.56 2.353-4.89 6.987-7.053-7.756-7.413-7.85-9.843 2.65l-.34.144c-8.883-5.324-9.342-5.366-8.668 5.042q-.322.24-.646.487c-9.102-3.606-9.495-3.379-7.192 6.611l-.323.351c-9.277-2.506-9.537-2.13-6.077 7.659q-.223.321-.443.645c-10.457-1.118-10.671-.913-5.356 8.829l-.078.142c-4.284 2.048-5.702 4.544-7.732 7.894-.158 6.645 6.954 12.371 17.18 18.88-3.513-6.512-9.007-9.813-10.061-17.89 1.543-2.905 4.169-3.908 6.423-5.215 24.62 14.368 50.615 14.534 76.55-.55 2.26 1.314 4.901 2.314 6.451 5.232-1.054 8.076-6.548 11.376-10.062 17.887 10.226-6.508 17.339-12.234 17.181-18.878-1.973-3.257-3.376-5.706-7.39-7.722 5.013-9.218 4.536-9.305-5.985-8.177l-.267-.346c3.634-10.262 3.406-10.53-6.392-7.871 2.48-10.706 2.148-10.75-7.78-6.804.748-11.285.48-11.262-8.888-5.643-2.498-10.819-2.793-10.786-9.94-2.925-2.689-4.933-4.103-7.403-5.432-7.419z" fill="#f60" stroke="#000" stroke-linejoin="round"/>
|
||||
<path transform="translate(-46.772 -62.017)" d="M98.809 121.552c-.683-.056 4.469 10.176 8.052.173.241-.674-3.692 2.91-8.052-.173z" fill="#d45500" stroke="#000"/>
|
||||
<path transform="translate(-46.772 -62.017)" d="m93.213 111.56c-5.132 10.992 5.618 14.735 4.412 4.12-0.32-3.288-2.328-7.175-4.412-4.12z" stroke="#000" stroke-width=".26458px"/>
|
||||
<path transform="translate(-46.772 -62.017)" d="M95.394 112.414c-1.183 1.52-.081 3.836-.098 3.986 1.674 1.64 1.612-4.77.098-3.986z" fill="#fff" stroke="#000" stroke-width=".26458px"/>
|
||||
<path transform="translate(-46.772 -62.017)" d="m109.62 111.56c-5.132 10.992 5.618 14.735 4.412 4.12-0.319-3.288-2.328-7.175-4.412-4.12z" stroke="#000" stroke-width=".26458px"/>
|
||||
<path transform="translate(-46.772 -62.017)" d="M111.799 112.414c-1.184 1.52-.082 3.836-.1 3.986 1.675 1.64 1.614-4.77.1-3.986z" fill="#fff" stroke="#000" stroke-width=".26458px"/>
|
||||
<path transform="translate(-44.452 -54.465)" d="M75.334 106.826c-.51.004-.85.054-.794.088 0 0-9.134 11.937.31 18.66a9.747 8.277 37.794 0 0 1.437 9.423 9.747 8.277 37.794 0 0 10.565 3.967l-3.564-8.606 9.196.736a9.747 8.277 37.794 0 0-7.026-8.91 9.747 8.277 37.794 0 0-7.008.008c-1.862-1.364-4.629-4.926-.04-12.475 1.517-2.498-1.544-2.903-3.075-2.891z" fill="#f60" stroke="#000" stroke-linejoin="round"/>
|
||||
<path transform="translate(-56.516 -120.04)" d="m142.55 137.93 27.363-15.274 21.397 38.332-27.364 15.274z" fill="none" stroke="#000" stroke-linejoin="round"/>
|
||||
<path transform="translate(-56.516 -120.04)" d="m142.55 137.93 27.363-15.274 21.397 38.332-27.364 15.274-6.27-11.236c3.509-3.113 4.196-7.011 3.176-11.383l-7.391 3.833z" fill="#2a3455" stroke="#000" stroke-linejoin="round"/>
|
||||
<path transform="rotate(-6.764 -420.2 199.48) scale(.86628)" d="M142.603 99.07c-.258.105-.309.673-.384 1.774-2.022-.94-2.1-.93-1.713 1.33l-.055.057c-2.18-.313-2.274-.284-1.285 1.703l-.086.148c-2.083.043-2.141.12-.867 1.89l-.034.095c-2.027.273-2.046.368-.562 2.003l-.033.163c-2.144.642-2.169.7-.324 2.173l-.004.035c-.672.754-.744 1.36-.866 2.185.516 1.317 2.383 1.854 4.925 2.289-1.226-.988-2.575-1.183-3.447-2.68.063-.698.496-1.111.83-1.553 6.013.79 11.127-1.318 14.972-6.413.552.072 1.152.05 1.697.495.458 1.671-.348 2.771-.501 4.338 1.47-2.119 2.394-3.828 1.816-5.119-.656-.476-1.133-.84-2.086-.906.224-2.22.123-2.199-1.848-1.11l-.08-.047c-.133-2.312-.2-2.346-1.903-1.017-.395-2.305-.464-2.286-2.086-.694-.783-2.276-.834-2.25-2.209-.375-1.381-1.917-1.436-1.886-2.19.245-.935-.747-1.416-1.115-1.677-1.009z" fill="#2a3455" stroke="#ccc" stroke-linejoin="round" stroke-width=".21276"/>
|
||||
<path transform="rotate(-6.764 -420.2 199.48) scale(.86628)" d="M140.978 108.893c-.1.044-.162.081-.148.083 0 0-.81 3.094 1.597 3.635-.044.678.367 1.35 1.058 1.73.78.428 1.75.392 2.4-.091l-1.408-1.395 1.864-.613c-.374-.727-1.227-1.2-2.112-1.17-.544.02-1.039.228-1.374.58-.478-.115-1.314-.586-1.035-2.445.092-.615-.542-.442-.842-.314ZM151.165 99.349a2.4 2.4 0 0 0-.391 1.594c.081.739.505 1.344 1.105 1.579.103 2.21-1.432 2.114-1.47 2.1-.286-.078-.717 1.138.535.982 1.956-.245 2.056-2.127 1.981-3.063.496-.165.92-.567 1.164-1.103.368-.811.258-1.775-.272-2.386l-1.285 1.51-.105-.254z" fill="#2a3455" stroke="#ccc" stroke-linejoin="round" stroke-width=".21276"/>
|
||||
<path transform="translate(-46.284 -47.883)" d="M157.038 77.94c-.102.05 1.499 1.066 1.159-.66-.023-.116-.278.727-1.16.66z" fill="#b1b3b8" stroke="#b1b3b8" stroke-width=".16554"/>
|
||||
<path transform="translate(-46.284 -47.883)" d="M155.395 76.994c.203 1.998 2.049 1.618.976.211-.324-.44-.94-.822-.976-.21z" fill="#b1b3b8" stroke="#b1b3b8" stroke-width=".0438px"/>
|
||||
<path transform="translate(-46.284 -47.883)" d="M155.777 76.93c-.04.317.314.552.324.575.377.091-.175-.815-.324-.575z" fill="#fff" stroke="#fff" stroke-width=".0438px"/>
|
||||
<path transform="translate(-46.284 -47.883)" d="M157.726 75.602c.204 1.998 2.05 1.618.977.211-.324-.44-.94-.822-.977-.21z" fill="#b1b3b8" stroke="#b1b3b8" stroke-width=".0438px"/>
|
||||
<path transform="translate(-46.284 -47.883)" d="M158.109 75.538c-.04.316.314.552.324.575.377.09-.176-.815-.324-.575z" fill="#fff" stroke="#fff" stroke-width=".0438px"/>
|
||||
<path transform="rotate(-28.988 -115.76 65.58)" d="M95.132 155.82q.24 0 .35.094.11.095.11.266 0 .075-.025.145a.3.3 0 0 1-.082.125.4.4 0 0 1-.151.086.7.7 0 0 1-.231.032h-.141v.477h-.155v-1.225zM103.305 157.045h-.155v-1.09h-.382v-.135h.918v.135h-.381zM102.043 155.954h-.175v.454h.184q.15 0 .218-.059.069-.06.069-.174a.23.23 0 0 0-.032-.129.2.2 0 0 0-.097-.07.5.5 0 0 0-.167-.022" fill="#ccc" stroke-width=".042896px"/>
|
||||
<path transform="rotate(-28.988 -115.76 65.58)" d="M102.052 155.82q.153 0 .251.038a.3.3 0 0 1 .147.116.36.36 0 0 1 .048.194q0 .098-.036.163a.3.3 0 0 1-.092.104.5.5 0 0 1-.117.061l.336.549h-.18l-.297-.506h-.244v.506h-.154v-1.225zM100.487 156.431q0 .154.043.266a.35.35 0 0 0 .133.171.4.4 0 0 0 .227.06.4.4 0 0 0 .228-.06.36.36 0 0 0 .131-.171.7.7 0 0 0 .043-.266.6.6 0 0 0-.096-.363q-.096-.13-.304-.131a.4.4 0 0 0-.229.059.35.35 0 0 0-.133.17.7.7 0 0 0-.043.265" fill="#ccc" stroke-width=".042896px"/>
|
||||
<path transform="rotate(-28.988 -115.76 65.58)" d="M101.455 156.431q0 .142-.036.258a.6.6 0 0 1-.108.2.5.5 0 0 1-.176.129.6.6 0 0 1-.245.044.6.6 0 0 1-.25-.045.5.5 0 0 1-.177-.129.6.6 0 0 1-.105-.2 1 1 0 0 1-.034-.259q0-.19.062-.329a.5.5 0 0 1 .188-.22.6.6 0 0 1 .318-.079q.185 0 .309.078a.5.5 0 0 1 .189.22.8.8 0 0 1 .065.332M99.548 155.952h-.156v.484h.123a.6.6 0 0 0 .194-.025.2.2 0 0 0 .115-.08.25.25 0 0 0 .038-.144q0-.118-.076-.177-.075-.058-.238-.058" fill="#ccc" stroke-width=".042896px"/>
|
||||
<path transform="rotate(-28.988 -115.76 65.58)" d="M99.562 155.82q.24 0 .35.094a.33.33 0 0 1 .109.266q0 .075-.024.145a.3.3 0 0 1-.082.125.4.4 0 0 1-.151.086.7.7 0 0 1-.232.032h-.14v.477h-.155v-1.225zM98.792 156.717a.31.31 0 0 1-.206.304.6.6 0 0 1-.232.041 1.2 1.2 0 0 1-.245-.026.5.5 0 0 1-.091-.032v-.148a.8.8 0 0 0 .343.072q.09 0 .153-.024a.2.2 0 0 0 .092-.068.2.2 0 0 0 .031-.105.2.2 0 0 0-.026-.101.24.24 0 0 0-.088-.077 1 1 0 0 0-.174-.074 1 1 0 0 1-.138-.063.4.4 0 0 1-.099-.076.3.3 0 0 1-.059-.098.4.4 0 0 1-.02-.127q0-.098.05-.167a.3.3 0 0 1 .138-.108.5.5 0 0 1 .203-.037.84.84 0 0 1 .338.068l-.048.132a1 1 0 0 0-.138-.046.6.6 0 0 0-.155-.019.3.3 0 0 0-.129.023.17.17 0 0 0-.077.062.17.17 0 0 0-.026.094q0 .06.026.103a.2.2 0 0 0 .084.073q.058.033.158.071.108.039.183.085.075.045.113.109a.3.3 0 0 1 .039.159M97.651 156.717a.3.3 0 0 1-.053.186.34.34 0 0 1-.152.118.6.6 0 0 1-.233.041 1.2 1.2 0 0 1-.245-.026.5.5 0 0 1-.091-.032v-.148a.9.9 0 0 0 .343.072q.09 0 .153-.024a.2.2 0 0 0 .093-.068q.03-.045.03-.105a.2.2 0 0 0-.025-.101.25.25 0 0 0-.089-.077 1 1 0 0 0-.174-.074 1 1 0 0 1-.138-.063.4.4 0 0 1-.099-.076.3.3 0 0 1-.059-.098.4.4 0 0 1-.02-.127q0-.098.05-.167a.3.3 0 0 1 .138-.108.5.5 0 0 1 .204-.037.8.8 0 0 1 .338.068l-.048.132a1 1 0 0 0-.138-.046.6.6 0 0 0-.156-.019.3.3 0 0 0-.128.023.17.17 0 0 0-.078.062.2.2 0 0 0-.025.094q0 .06.025.103a.2.2 0 0 0 .084.073q.06.033.158.071.108.039.183.085.075.045.113.109a.3.3 0 0 1 .039.159M96.253 156.158a3 3 0 0 1-.059-.184 1 1 0 0 1-.036.134l-.017.05-.139.371h.388z" fill="#ccc" stroke-width=".042896px"/>
|
||||
<path transform="rotate(-28.988 -115.76 65.58)" d="m96.584 157.045-.148-.379h-.485l-.146.379h-.156l.479-1.23h.139l.477 1.23zM95.118 155.952h-.156v.484h.123a.6.6 0 0 0 .194-.025.2.2 0 0 0 .115-.08.25.25 0 0 0 .038-.144q0-.118-.075-.177t-.239-.058" fill="#ccc" stroke-width=".042896px"/>
|
||||
<path transform="rotate(-29.037 -115.59 65.42)" d="m93.173 154.547-.175-.45h-.576l-.173.45h-.185l.568-1.459h.165l.565 1.459zM105.531 153.495l-.048-.148-.021-.07-.044.159-.019.059-.165.439h.46z" fill="#ccc" stroke-width=".05088px"/>
|
||||
<path transform="rotate(-29.037 -115.59 65.42)" d="m105.924 154.547-.175-.45h-.576l-.173.45h-.186l.568-1.459h.165l.566 1.459zM104.741 154.547h-.525v-.106l.171-.038v-1.163l-.171-.04v-.106h.525v.106l-.171.04v1.163l.171.038zM103.09 154.547v-1.453h.183v1.29h.635v.163zM102.14 153.495a2 2 0 0 1-.048-.148l-.021-.07-.044.159-.019.059-.165.439h.46z" fill="#ccc" stroke-width=".05088px"/>
|
||||
<path transform="rotate(-29.037 -115.59 65.42)" d="m102.533 154.547-.175-.45h-.576l-.173.45h-.185l.568-1.459h.164l.566 1.459zM100.66 153.253h-.207v.539h.217q.178 0 .259-.07t.081-.207a.3.3 0 0 0-.037-.152.22.22 0 0 0-.115-.084.6.6 0 0 0-.198-.026" fill="#ccc" stroke-width=".05088px"/>
|
||||
<path transform="rotate(-29.037 -115.59 65.42)" d="M100.67 153.094q.182 0 .299.046a.34.34 0 0 1 .174.137.43.43 0 0 1 .057.23.36.36 0 0 1-.152.317.6.6 0 0 1-.139.072l.398.651h-.213l-.352-.6h-.289v.6h-.183v-1.453zM99.487 154.547h-.183v-1.292h-.454v-.161h1.089v.161h-.452zM98.726 154.158a.38.38 0 0 1-.063.221.4.4 0 0 1-.18.14.7.7 0 0 1-.275.048 1.5 1.5 0 0 1-.292-.031.5.5 0 0 1-.107-.038v-.175a1 1 0 0 0 .407.086q.108 0 .181-.029a.23.23 0 0 0 .11-.081.2.2 0 0 0 .036-.124.23.23 0 0 0-.03-.121.3.3 0 0 0-.105-.09 1.3 1.3 0 0 0-.207-.089 1 1 0 0 1-.163-.074.5.5 0 0 1-.117-.09.4.4 0 0 1-.071-.116.5.5 0 0 1-.023-.151.33.33 0 0 1 .059-.198.4.4 0 0 1 .164-.128.6.6 0 0 1 .241-.044q.12 0 .22.022a1 1 0 0 1 .181.059l-.057.157a1 1 0 0 0-.164-.055.7.7 0 0 0-.184-.023.4.4 0 0 0-.153.027.19.19 0 0 0-.122.185q0 .074.031.122.03.05.099.088.07.038.188.083.128.047.216.101.09.054.135.129t.045.189M97.545 154.547h-.184v-.678h-.744v.678h-.184v-1.453h.184v.615h.744v-.615h.184zM95.651 154.547h-.183v-1.292h-.454v-.161h1.089v.161h-.452zM94.828 154.034q0 .15-.062.272a.45.45 0 0 1-.186.191.65.65 0 0 1-.318.07q-.27 0-.412-.147a.54.54 0 0 1-.142-.39v-.936h.183v.942q0 .177.095.275t.286.098q.132 0 .215-.048a.3.3 0 0 0 .121-.132.5.5 0 0 0 .038-.195v-.94h.182zM92.78 153.495a2 2 0 0 1-.047-.148l-.022-.07-.044.159-.019.059-.165.439h.46z" fill="#ccc" stroke-width=".05088px"/>
|
||||
<path transform="rotate(-29.176 -115.13 64.977)" d="M94.837 128.624h-.286l-.685-.922-.197.173v.749h-.244v-1.931h.244v.952l.165-.184.167-.184.522-.584h.284l-.765.841zM102.512 128.624l-.636-1.693h-.01l.009.138a11 11 0 0 1 .01.384v1.171h-.225v-1.931h.36l.595 1.582h.011l.605-1.582h.357v1.931h-.24v-1.187q0-.095.002-.188.003-.094.009-.176l.008-.139h-.011l-.644 1.69zM101.005 127.648a.9.9 0 0 0-.085-.422.55.55 0 0 0-.249-.245.9.9 0 0 0-.396-.08h-.317v1.515h.262q.393 0 .589-.194.196-.193.196-.574" fill="#ccc" stroke-width=".067618px"/>
|
||||
<path transform="rotate(-29.176 -115.13 64.977)" d="M101.262 127.639q0 .325-.121.544a.78.78 0 0 1-.344.33 1.2 1.2 0 0 1-.544.111h-.538v-1.931h.595q.292 0 .505.108a.76.76 0 0 1 .33.318q.117.21.117.52M99.508 128.624h-.698v-.141l.228-.051v-1.544l-.228-.055v-.14h.698v.14l-.227.055v1.544l.227.051zM98.55 128.624h-.284l-1.033-1.604h-.011a7 7 0 0 1 .022.525v1.079h-.225v-1.931h.282l1.03 1.598h.011l-.007-.13-.008-.189-.004-.189v-1.09h.227zM95.912 127.226a4 4 0 0 1-.092-.29 5 5 0 0 1-.058.211l-.026.079-.219.584h.611z" fill="#ccc" stroke-width=".067618px"/>
|
||||
<path transform="rotate(-29.176 -115.13 64.977)" d="m96.434 128.624-.233-.598h-.765l-.23.598h-.246l.755-1.939h.219l.752 1.939z" fill="#ccc" stroke-width=".067618px"/>
|
||||
<path transform="translate(-44.452 -54.465)" d="m145.62 98.73c3.51-3.113 4.196-7.01 3.176-11.383l-7.391 3.834z" fill="#2a3455" stroke="#000" stroke-linejoin="round"/>
|
||||
<path transform="translate(-44.452 -54.465)" d="M136.847 83.997c-2.181 1.424-3.845 3.665-4.598 6.196-.992 3.35-.257 6.742 1.918 8.852-3.579 9.77-10.054 6.556-10.19 6.427-1.1-.856-5.178 3.63.532 5.23 8.92 2.497 12.776-5.475 14.156-9.671 2.452.187 5.02-.782 7.053-2.66 3.069-2.846 4.344-7.223 3.158-10.835l-8.312 4.204.005-1.292z" fill="#f60" stroke="#000" stroke-linejoin="round"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 12 KiB |
|
@ -1,74 +0,0 @@
|
|||
<svg fill="none" viewBox="-4.00 0 56 56" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m12.048 54.925-.664-4.459-4.459.664.76 4.459z" fill="url(#a)"/>
|
||||
<path d="m46.957 42.403-.759-4.363-19.446 3.035.569 4.364z" fill="url(#b)"/>
|
||||
<path d="m20.395 46.577 4.459-.759-.665-4.363-4.458.664z" fill="url(#c)"/>
|
||||
<path d="m19.067 53.787-.759-4.364-4.363.664.569 4.458z" fill="url(#d)"/>
|
||||
<path d="m5.881 44.206.664 4.458L11.004 48l-.664-4.459z" fill="url(#e)"/>
|
||||
<path d="m27.7 47.905.759 4.554L48 49.423l-.664-4.459z" fill="url(#f)"/>
|
||||
<path d="m21.533 53.407 4.364-.57-.664-4.553-4.459.76z" fill="url(#g)"/>
|
||||
<path d="m12.806 43.162.759 4.459 4.364-.664-.664-4.459z" fill="url(#h)"/>
|
||||
<path d="M7.4 41.455 1.991 5.976 0 6.261l5.502 35.478z" fill="url(#i)"/>
|
||||
<path d="M9.96 41.075 4.08 2.941l-1.897.38L8.063 41.36z" fill="url(#j)"/>
|
||||
<path d="M14.325 40.411 8.158 0 6.261.38l6.166 40.22z" fill="url(#k)"/>
|
||||
<path d="M16.885 40.032 11.193 3.224l-1.802.285 5.692 36.711z" fill="url(#l)"/>
|
||||
<path d="M21.25 39.273 16.22 6.64l-1.897.285 5.028 32.727z" fill="url(#m)"/>
|
||||
<path d="M23.81 38.894 18.593 5.028l-1.897.284 5.312 33.866z" fill="url(#n)"/>
|
||||
<defs>
|
||||
<linearGradient id="a" x1="11.647" x2="1.232" y1="66.877" y2="-.803" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="39.992" x2="29.577" y1="62.515" y2="-5.164" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="25.506" x2="15.091" y1="64.745" y2="-2.935" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="d" x1="18.579" x2="8.164" y1="65.811" y2="-1.869" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="e" x1="11.639" x2="1.224" y1="66.878" y2="-.801" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="f" x1="39.998" x2="29.583" y1="62.514" y2="-5.165" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="g" x1="25.506" x2="15.091" y1="64.744" y2="-2.935" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="h" x1="18.579" x2="8.164" y1="65.81" y2="-1.869" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="i" x1="10.162" x2=".285" y1="65.76" y2="1.572" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="j" x1="13.013" x2="2.408" y1="67.143" y2="-1.772" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="k" x1="17.634" x2="6.389" y1="68.033" y2="-5.037" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="l" x1="19.831" x2="9.579" y1="65.208" y2="-1.408" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="m" x1="23.735" x2="14.629" y1="61.739" y2="2.562" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="n" x1="26.447" x2="16.991" y1="62.197" y2=".752" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAED1E" offset="0"/>
|
||||
<stop stop-color="#F15B2B" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 4 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg data-name="Layer 1" viewBox="-5.04 0 73.44 73.44" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M.503 54.721a3.64 3.64 0 0 0 1.366 1.356l27.953 16.138a3.67 3.67 0 0 0 3.716 0l27.954-16.139a3.67 3.67 0 0 0 1.857-3.216V20.583l-.001-.001a3.67 3.67 0 0 0-1.858-3.217L33.536 1.226a3.66 3.66 0 0 0-3.712 0L1.87 17.365a3.67 3.67 0 0 0-1.859 3.217V52.86a3.64 3.64 0 0 0 .49 1.861" fill="#009639"/>
|
||||
<path d="M22.73 49.008a3.595 3.595 0 0 1-7.19 0h.001l.004-24.676c0-1.912 1.689-3.493 4.082-3.493a6.4 6.4 0 0 1 4.9 2.19l1.086 1.3 15.018 17.974V24.432h-.002a3.595 3.595 0 0 1 7.19 0l-.004 24.676c0 1.911-1.69 3.492-4.082 3.492a6.39 6.39 0 0 1-4.9-2.19L22.728 31.136v17.872Z" fill="#fff"/>
|
||||
<path d="M59.975 66.034h.46v-1.023h.362a1.04 1.04 0 0 1 .51.085.64.64 0 0 1 .243.595v.21l.01.078.008.033c.002.013.003.016.01.022h.426l-.016-.029a.3.3 0 0 1-.02-.133q-.009-.108-.007-.194v-.194a.74.74 0 0 0-.142-.406.66.66 0 0 0-.453-.254 1.1 1.1 0 0 0 .378-.123.57.57 0 0 0 .254-.515.64.64 0 0 0-.402-.66 2 2 0 0 0-.712-.094h-.91Zm1.306-1.403a1.2 1.2 0 0 1-.437.058h-.41v-.947h.392a1.16 1.16 0 0 1 .552.102.39.39 0 0 1 .172.38.4.4 0 0 1-.269.407m1.334-1.564a2.3 2.3 0 0 0-1.687-.69 2.373 2.373 0 0 0 0 4.747 2.377 2.377 0 0 0 1.687-4.057m-.241.236a1.94 1.94 0 0 1 .596 1.443 2.04 2.04 0 0 1-3.486 1.451 2.01 2.01 0 0 1-.593-1.45 2.046 2.046 0 0 1 2.037-2.05 1.95 1.95 0 0 1 1.446.606" fill="#999"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1,10 +0,0 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" viewBox="0 0 360 360" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style>.cls-1{fill:#06ed94}</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="m179 349.73c-93.695 0-169.92-76.227-169.92-169.92s76.226-169.92 169.92-169.92 169.92 76.226 169.92 169.92-76.226 169.92-169.92 169.92m0-327.82c-87.062 0-157.89 70.831-157.89 157.89s70.83 157.89 157.89 157.89 157.89-70.83 157.89-157.89-70.832-157.89-157.89-157.89"/>
|
||||
<path d="m208.63 90.373-4.904 18.332 39.429 32.359 37.057-4.57z" fill="#059b61"/>
|
||||
<path class="cls-1" d="m304.6 167.59v-34.035l-53.826-45.799-42.15 2.614 51.392 41.277h-206.62v38.243z"/>
|
||||
<path d="m149.38 261.5 4.904-18.332-40.596-33.993-35.889 6.203z" fill="#1957ba"/>
|
||||
<path d="m53.401 184.28v34.036l53.825 45.799 42.15-2.614-51.392-41.278h206.62v-38.242z" fill="#6ca8ff"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 826 B |
|
@ -1,7 +0,0 @@
|
|||
<svg viewBox="-9.312 -18.987 141.5 141.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style>
|
||||
</defs>
|
||||
<rect x="-9.312" y="-18.987" width="141.5" height="141.5" fill="#ebebeb"/>
|
||||
<path class="st0" d="m5.47 0h111.93c3.01 0 5.47 2.46 5.47 5.47v92.58c0 3.01-2.46 5.47-5.47 5.47h-111.93c-3.01 0-5.47-2.46-5.47-5.47v-92.58c0-3.01 2.46-5.47 5.47-5.47zm26.37 38.55 17.79 18.42 2.14 2.13-2.12 2.16-17.97 19.05-5.07-5 15.85-16.15-15.65-15.56 5.03-5.05zm62.26 40.86h-39.41v-6.84h39.41v6.84zm-55.91-69.58c3.19 0 5.78 2.59 5.78 5.78s-2.59 5.78-5.78 5.78-5.78-2.59-5.78-5.78 2.59-5.78 5.78-5.78zm-19.24 0c3.19 0 5.78 2.59 5.78 5.78s-2.59 5.78-5.78 5.78-5.78-2.59-5.78-5.78 2.58-5.78 5.78-5.78zm-11.46-4.42h107.91c1.15 0 2.09 0.94 2.09 2.09v18.32h-112.09v-18.32c0-1.15 0.94-2.09 2.09-2.09z" style=""/>
|
||||
</svg>
|
Before Width: | Height: | Size: 864 B |
|
@ -1,11 +0,0 @@
|
|||
<svg viewBox="-50.00 0 300 300" xmlns="http://www.w3.org/2000/svg">
|
||||
<script data-extension-id="{51e0c76c-7dbc-41ba-a45d-c579be84301b}"/>
|
||||
<path d="m186 188c-2-90-152-83-139 4 12 87 83 104 83 104s59-19 56-108" fill="#a40000"/>
|
||||
<path d="m73 238c46 4 67 3 104-16-5 48-46 66-46 66s-43-12-57-50z" fill="#fff"/>
|
||||
<g fill="none" stroke="#4e9a06" stroke-width="15">
|
||||
<path d="M103 137C77 69 13 62 13 62"/>
|
||||
<path d="m105 136c0-50-68-116-68-116"/>
|
||||
<path d="m105 135c7-62-22-118-22-118"/>
|
||||
</g>
|
||||
<script/>
|
||||
</svg>
|
Before Width: | Height: | Size: 509 B |
|
@ -1,13 +0,0 @@
|
|||
<svg width="800" height="800" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M13 15h38v-1a3 3 0 0 0-3-3H30c-5 0-3-4-8-4H12a3 3 0 0 0-3 3v28a3 3 0 0 0 3 3h1z" fill="#FFF"/>
|
||||
<path d="M13 15h38v-1a3 3 0 0 0-3-3H30c-5 0-3-4-8-4H12a3 3 0 0 0-3 3v28a3 3 0 0 0 3 3h1z" fill="#6C6984" fill-opacity=".25"/>
|
||||
<path d="m13 41h39a3 3 0 0 0 3-3v-23h-42zm18 8h4v-8h-4zm8 6h20v-4h-20z" fill="#FFF"/>
|
||||
<path d="m31 44h4v-3h-4zm8 11h3v-4h-3z" fill="#6C6984" fill-opacity=".25"/>
|
||||
<path d="M5 55h22v-4H5z" fill="#FFF"/>
|
||||
<path d="M27 57h12v-8H27zM37.148 23.573C36.344 22.047 34.69 21 32.778 21c-2.7 0-4.89 2.09-4.89 4.667-2.7 0-4.888 2.09-4.888 4.666C23 32.911 25.188 35 27.888 35h11C42.263 35 45 32.388 45 29.167s-2.737-5.834-6.112-5.834c-.604 0-1.189.084-1.74.24" fill="#CDCCD6"/>
|
||||
<path d="M38.889 23.333c-.43 0-.844.057-1.248.137 1.404.794 2.359 2.255 2.359 3.946C40 29.948 37.886 32 35.278 32h-8.5c-1.751 0-3.21-1.162-3.638-2.73-.084.342-.14.696-.14 1.063C23 32.91 25.189 35 27.889 35h11c3.375 0 6.11-2.611 6.11-5.834 0-3.221-2.735-5.833-6.11-5.833" fill="#6C6984" fill-opacity=".25"/>
|
||||
<path d="M27 57h12v-8H27zM31 49v-8M35 49v-8M39 51h20M39 55h20M27 51H5M5 55h22M22 7H12a3 3 0 0 0-3 3v28a3 3 0 0 0 3 3h40a3 3 0 0 0 3-3V15H13v26" stroke="#676883" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<path d="M30 11h18a3 3 0 0 1 3 3v1M22 7c5 0 3 4 8 4M37.148 23.573C36.344 22.047 34.69 21 32.778 21c-2.7 0-4.89 2.09-4.89 4.667-2.7 0-4.888 2.09-4.888 4.666C23 32.911 25.188 35 27.888 35h11C42.263 35 45 32.388 45 29.167s-2.737-5.834-6.112-5.834c-.604 0-1.189.084-1.74.24z" stroke="#676883" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.7 KiB |
|
@ -1,5 +0,0 @@
|
|||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg aria-label="imgur" role="img" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="512" height="512" rx="15%" fill="#175DDC"/>
|
||||
<path d="M372 297V131H256v294c47-28 115-74 116-128zm49-198v198c0 106-152 181-165 181S91 403 91 297V99s0-17 17-17h296s17 0 17 17z" fill="#fff"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 384 B |
|
@ -1,5 +0,0 @@
|
|||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg aria-label="WireGuard" role="img" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="512" height="512" rx="15%" fill="#88171a"/>
|
||||
<path d="m238 53 35 8v2c-15 2-30-4-45-5 11 7 23 11 35 15-19 16-35-5-56 9 20 10 19 8 21 27-9 1-24 10-27 16 13 3 28 0 41 8-4 3-14 7-18 10 9 2 20-2 25 1 19 16 54 38 64 60 17 37-22 77-60 83-53 11-83 66-64 117 19 50 78 72 125 46 66-40 56-108 16-145-2-2-4-2-6 0-14 9-29 17-45 24 36 8 41 35 37 54-13 48-78 37-85-4-3-19 7-38 24-46 59-26 87-30 104-97 6-38-3-58-31-80-11-11-33-18-40-35-1-2 1-6 3-6 10-2 49-3 49-1 7 7 13-4 16-9-10-2-21-1-29-1-1 0-3-2-4-3 1-1 3-2 4-2h41c0-7-9-17-18-19v3c-8 1-16-1-24-4-4-3-7-9-11-11-16-9-33-16-54-16-10 0-17 1-23 1zm74 30 3 3-4 2c-2 1-3 0-4-1-2-3 4-5 5-4zm-120 96c-54 33-51 109-3 139 4 2 6 2 8-1 12-15 23-22 36-30-25-4-38-16-37-33-4-60 83-54 74 2-2 10-8 19-16 25 27-6 47-21 55-48 2-8 2-19-2-26-30-44-75-53-115-28zm-62 195c16-7 33-10 49-13 1-13 5-26 13-36-30 0-55 20-62 49z" fill="#fff"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 243 KiB |
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 372 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 575 KiB After Width: | Height: | Size: 575 KiB |
|
@ -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;});
|
||||
};
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
));
|
||||
};
|
||||
}
|
|
@ -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}";};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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 []);
|
||||
};
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
|
@ -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));
|
||||
};
|
||||
}
|
|
@ -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 (<category>.<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})"
|
||||
)
|
||||
)
|
||||
));
|
||||
};
|
||||
}
|
|
@ -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 (<category>.<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")
|
||||
]
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
|
@ -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 ''"<name>"'';
|
||||
};
|
||||
|
||||
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 (<category>.<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 (<category>.<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")
|
||||
]
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
|
@ -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 (<category>.<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}")
|
||||
]
|
||||
)
|
||||
));
|
||||
};
|
||||
}
|
|
@ -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"];
|
||||
};
|
||||
}
|
|
@ -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 = <actual value>; }.
|
||||
# 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 ''
|
||||
<div tw="flex flex-none bg-[#000000] ${twAttrs}"></div>
|
||||
''
|
||||
else if hasSuffix ".svg" file
|
||||
then let
|
||||
withoutPrefix = head (tail (splitString "<svg " (builtins.readFile file)));
|
||||
content = head (splitString "</svg>" withoutPrefix);
|
||||
in ''<svg tw="${twAttrs}" ${content}</svg>''
|
||||
else if hasSuffix ".png" file
|
||||
then ''<img tw="object-contain ${twAttrs}" src="data:image/png;base64,${builtins.readFile (fileBase64 file)}"/>''
|
||||
else if hasSuffix ".jpg" file || hasSuffix ".jpeg" file
|
||||
then ''<img tw="object-contain ${twAttrs}" src="data:image/jpeg;base64,${builtins.readFile (fileBase64 file)}"/>''
|
||||
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
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row w-full items-center">
|
||||
<div tw="flex grow h-0.5 my-4 bg-[#242931] border-0"></div>
|
||||
<div tw="flex px-4">
|
||||
<span tw="text-[#b6beca] font-bold">${name}</span>
|
||||
</div>
|
||||
<div tw="flex grow h-0.5 my-4 bg-[#242931] border-0"></div>
|
||||
</div>
|
||||
'';
|
||||
|
||||
mkRootContainer = twAttrs: contents:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-col w-full h-full items-center">
|
||||
<div tw="flex flex-col w-full h-full text-[#e3e6eb] font-mono ${twAttrs}" style="font-family: 'JetBrains Mono'">
|
||||
${contents}
|
||||
</div>
|
||||
</div>
|
||||
'';
|
||||
|
||||
mkCardContainer = mkRootContainer "bg-[#101419] rounded-xl";
|
||||
|
||||
spacingMt2 = ''
|
||||
<div tw="flex mt-2"></div>
|
||||
'';
|
||||
|
||||
net = {
|
||||
netStylePreview = net: twAttrs: let
|
||||
secondaryColor =
|
||||
if net.style.secondaryColor == null
|
||||
then "#00000000"
|
||||
else net.style.secondaryColor;
|
||||
in
|
||||
{
|
||||
solid = ''<div tw="flex flex-none ${twAttrs} bg-[${net.style.primaryColor}]"></div>'';
|
||||
dashed = ''<div tw="flex flex-none ${twAttrs}" style="backgroundImage: linear-gradient(90deg, ${net.style.primaryColor} 0%, ${net.style.primaryColor} 50%, ${secondaryColor} 50.01%, ${secondaryColor} 100%);"></div>'';
|
||||
dotted = ''<div tw="flex flex-none ${twAttrs}" style="backgroundImage: radial-gradient(circle, ${net.style.primaryColor} 30%, ${secondaryColor} 30.01%);"></div>'';
|
||||
}
|
||||
.${net.style.pattern};
|
||||
|
||||
mkCard = net: {
|
||||
width = 480;
|
||||
html =
|
||||
mkCardContainer
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
${html.net.netStylePreview net "w-12 h-4 mr-4 rounded-md"}
|
||||
<h2 tw="text-2xl font-bold">${net.name}</h2>
|
||||
<div tw="flex grow min-w-8"></div>
|
||||
${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get net.icon)}
|
||||
</div>
|
||||
<div tw="flex flex-col mx-6 my-2 grow">
|
||||
${optionalString (net.cidrv4 != null) ''<div tw="flex flex-row"><span tw="text-lg m-0"><b>CIDRv4</b></span><span tw="text-lg text-[${net.style.primaryColor}] m-0 ml-4">${net.cidrv4}</span></div>''}
|
||||
${optionalString (net.cidrv6 != null) ''<div tw="flex flex-row"><span tw="text-lg m-0"><b>CIDRv6</b></span><span tw="text-lg text-[${net.style.primaryColor}] m-0 ml-4">${net.cidrv6}</span></div>''}
|
||||
'';
|
||||
};
|
||||
|
||||
mkOverview = {
|
||||
width = 480;
|
||||
html =
|
||||
mkCardContainer
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
<h2 tw="text-2xl font-bold">Networks Overview</h2>
|
||||
</div>
|
||||
|
||||
${concatLines (flip map (attrValues config.networks) (
|
||||
net:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-col mx-4 mt-2 rounded-lg p-2">
|
||||
<div tw="flex flex-row">
|
||||
${html.net.netStylePreview net "w-12 h-4 mt-2 mr-4 rounded-md"}
|
||||
<div tw="flex flex-col grow">
|
||||
<h1 tw="text-lg font-bold m-0">${net.name}</h1>
|
||||
${optionalString (net.cidrv4 != null) ''<div tw="flex flex-row"><span tw="text-md text-[#7a899f] m-0"><b>CIDRv4</b></span><span tw="text-md text-[${net.style.primaryColor}] m-0 ml-4">${net.cidrv4}</span></div>''}
|
||||
${optionalString (net.cidrv6 != null) ''<div tw="flex flex-row"><span tw="text-md text-[#7a899f] m-0"><b>CIDRv6</b></span><span tw="text-md text-[${net.style.primaryColor}] m-0 ml-4">${net.cidrv6}</span></div>''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
''
|
||||
))}
|
||||
|
||||
${spacingMt2}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
node = rec {
|
||||
mkInterface = interface:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-col flex-none items-center border-[#21262e] border-2 rounded-lg px-2 py-1 m-1">
|
||||
${mkImage "w-8 h-8 m-1" (config.lib.icons.get interface.icon)}
|
||||
<span tw="font-bold text-xs">${interface.id}</span>
|
||||
</div>
|
||||
'';
|
||||
|
||||
serviceDetail = detail:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row flex-wrap mt-1">
|
||||
<span tw="flex text-sm flex-none min-w-16 font-bold pl-1">${detail.name}</span>
|
||||
<span tw="flex grow"></span>
|
||||
<span tw="flex text-sm ml-4">${detail.text}</span>
|
||||
</div>
|
||||
'';
|
||||
|
||||
serviceDetails = service:
|
||||
optionalString (service.details != {}) ''<div tw="flex pt-2"></div>''
|
||||
# FIXME: order not respected
|
||||
+ concatLines ((map serviceDetail) (attrValues service.details));
|
||||
|
||||
mkService = {
|
||||
additionalInfo ? "",
|
||||
includeDetails ? true,
|
||||
...
|
||||
}: service:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-col mx-4 mt-2 rounded-lg p-2">
|
||||
<div tw="flex flex-row items-center">
|
||||
${mkImage "w-12 h-12 mr-4 rounded-lg" (config.lib.icons.get service.icon)}
|
||||
<div tw="flex flex-col grow">
|
||||
<h1 tw="text-lg font-bold m-0">${service.name}</h1>
|
||||
${optionalString (service.info != "") ''<p tw="text-sm m-0">${service.info}</p>''}
|
||||
${additionalInfo}
|
||||
</div>
|
||||
</div>
|
||||
${optionalString includeDetails (serviceDetails service)}
|
||||
</div>
|
||||
'';
|
||||
|
||||
mkGuest = guest:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-col mx-4 mt-2 rounded-lg p-2">
|
||||
<div tw="flex flex-row items-center">
|
||||
${mkImageMaybe "w-12 h-12 mr-4" (config.lib.icons.get guest.deviceIcon)}
|
||||
<div tw="flex flex-col grow">
|
||||
<h1 tw="text-lg font-bold m-0">${guest.name}</h1>
|
||||
<p tw="text-sm m-0">${guest.guestType}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
'';
|
||||
|
||||
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
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
${mkImageMaybe "w-8 h-8 mr-4" (config.lib.icons.get node.icon)}
|
||||
<div tw="flex flex-col min-h-18 justify-center">
|
||||
<span tw="text-2xl font-bold">${node.name}</span>
|
||||
${optionalString (node.hardware.info != null) ''<span tw="text-xs">${node.hardware.info}</span>''}
|
||||
</div>
|
||||
<div tw="flex grow min-w-8"></div>
|
||||
${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get node.deviceIcon)}
|
||||
</div>
|
||||
|
||||
${optionalString (node.interfaces != {}) ''<div tw="flex flex-row flex-wrap items-center my-2 mx-3">''}
|
||||
${concatLines (map mkInterface (attrValues node.interfaces))}
|
||||
${optionalString (node.interfaces != {}) ''</div>''}
|
||||
|
||||
${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
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
${mkImageMaybe "w-8 h-8" (config.lib.icons.get node.icon)}
|
||||
<div tw="flex flex-col min-h-18 justify-center">
|
||||
<span tw="text-2xl font-bold">${node.name}</span>
|
||||
${optionalString (node.hardware.info != null) ''<span tw="text-xs">${node.hardware.info}</span>''}
|
||||
</div>
|
||||
${optionalString (deviceIconImage != null && node.hardware.image != null -> deviceIconImage != node.hardware.image)
|
||||
''
|
||||
<div tw="flex grow min-w-4"></div>
|
||||
${mkImageMaybe "w-12 h-12" deviceIconImage}
|
||||
''}
|
||||
</div>
|
||||
|
||||
<div tw="flex flex-row w-full justify-center">
|
||||
${mkImageMaybe "h-24" node.hardware.image}
|
||||
</div>
|
||||
'';
|
||||
};
|
||||
|
||||
mkPreferredRender = node:
|
||||
(
|
||||
if node.renderer.preferredType == "image" && node.hardware.image != null
|
||||
then mkImageWithName
|
||||
else mkCard
|
||||
)
|
||||
node;
|
||||
};
|
||||
|
||||
services.mkOverview = {
|
||||
width = 480;
|
||||
html =
|
||||
mkCardContainer
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
<h2 tw="text-2xl font-bold">Services Overview</h2>
|
||||
</div>
|
||||
|
||||
${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 = ''<p tw="text-sm text-[#7a899f] m-0">${node.name}</p>'';
|
||||
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
|
||||
''))}
|
||||
'';
|
||||
};
|
||||
}
|