1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-10 23:00:39 +02:00

refactor(topology): move to own repository

This commit is contained in:
oddlama 2024-04-02 01:57:00 +02:00
parent 78b0ad5ddc
commit 8612c339f0
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
53 changed files with 77 additions and 2894 deletions

View file

@ -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;
};
};

View file

@ -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";

View file

@ -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 = [

View file

@ -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 {};

View file

@ -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";
};
}

View file

@ -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();

View file

@ -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=="
}
}
}

View file

@ -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"
}
}

View file

@ -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

View file

@ -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";
};
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7 KiB

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 243 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 372 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 575 KiB

After

Width:  |  Height:  |  Size: 575 KiB

Before After
Before After

View file

@ -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;});
};
}

View file

@ -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";
};
}
));
};
}

View file

@ -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}";};
};
};
}

View file

@ -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 []);
};
}
)
)
);
};
}

View file

@ -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;
});
};
}
)
);
};
}

View file

@ -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";
};
}

View file

@ -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;
};
};
}));
};
};
});
};
}

View file

@ -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;
};
};
}));
};
};
});
};
}

View file

@ -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));
};
}

View file

@ -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})"
)
)
));
};
}

View file

@ -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")
]
)
);
};
}

View file

@ -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")
]
)
);
};
}

View file

@ -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}")
]
)
));
};
}

View file

@ -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"];
};
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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
''))}
'';
};
}