diff options
| author | Mel <mel@rnrd.eu> | 2025-08-31 17:24:03 +0200 |
|---|---|---|
| committer | Mel <mel@rnrd.eu> | 2025-08-31 17:24:03 +0200 |
| commit | fcbc0446f11b8555c1204081c23fbd1442534aa0 (patch) | |
| tree | 4479bef5a2bd80987add6c34440fee4ba894abed /modules/foundation/services/networks.nix | |
| parent | 72ed2e170f32698f8a8596532c1d7655591267c3 (diff) | |
| download | network-fcbc0446f11b8555c1204081c23fbd1442534aa0.tar.zst network-fcbc0446f11b8555c1204081c23fbd1442534aa0.zip | |
Clean up & integrate service network configuration into foundation module
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'modules/foundation/services/networks.nix')
| -rw-r--r-- | modules/foundation/services/networks.nix | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/modules/foundation/services/networks.nix b/modules/foundation/services/networks.nix new file mode 100644 index 0000000..d1f1a92 --- /dev/null +++ b/modules/foundation/services/networks.nix @@ -0,0 +1,215 @@ +{ + lib, + config, + pkgs, + ... +}: + +let + utils = import ./utils.nix { inherit lib; }; + + inherit (lib) + mkOption + types + assertMsg + optional + getExe + concatStringsSep + filterAttrs + listToAttrs + attrsToList + imap0 + attrNames + nameValuePair + ; + inherit (utils) naming; + + cfg = config.foundation; +in +{ + options.foundation = + let + networkSubmodule = + with types; + submodule { + options = { + enable = mkOption { + type = types.bool; + default = true; + description = "Should this network be created?"; + }; + + default = mkOption { + type = types.bool; + default = false; + description = '' + Should this network be the default for all services + that don't have an explicit network defined? + ''; + }; + + subnet = mkOption { + type = types.nullOr types.str; + description = '' + IPv6 subnet for this network in CIDR notation. + Don't set to get a random subnet assigned to you within + the subnet defined in `defaultSubnetPrefix`. + ''; + example = "2001:d0c:123::/64"; + default = null; + }; + + driver = mkOption { + type = types.str; + default = "bridge"; + description = "Docker network driver to use"; + }; + + options = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Additional options to pass to docker network create"; + }; + + serviceGroup = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The group of the network, if it belongs to one. + This makes the network service a part of the + group target. + ''; + }; + }; + }; + in + { + networks = mkOption { + type = types.attrsOf networkSubmodule; + description = "List of service networks to create"; + default = { }; + }; + + defaultNetwork = mkOption { + type = types.nullOr types.str; + description = '' + The default network for services. + If at least one network is marked as default, you can + find it's name here. + ''; + }; + + defaultSubnetPrefix = mkOption { + type = types.str; + description = '' + Default network subnet assigned to networks without + a set subnet. + Prefix length defined by `defaultSubnetLength`. + ''; + default = "2001:d0c"; + }; + + defaultSubnetLength = mkOption { + type = types.int; + description = '' + Default network subnet length assigned to networks without + a set subnet. + This should always be the length of the `defaultSubnetPrefix` + 16. + ''; + default = 48; + }; + }; + + config = lib.mkIf (cfg.networks != { }) { + # other modules can use this to look up which network + # they can use as the default! + foundation.defaultNetwork = + let + networksLabeledDefault = attrNames (filterAttrs (name: net: net.default) cfg.networks); + defaultsCount = builtins.length networksLabeledDefault; + in + assert assertMsg (defaultsCount <= 1) "multiple service networks labeled default"; + assert assertMsg (defaultsCount != 0) "no default service network"; + builtins.head networksLabeledDefault; + + # we need these settings to allow our networks to be IPv6-enabled. + # we also ignore the default bridge, because it's setup + # is a lot more complicated... + virtualisation.docker.daemon.settings = { + experimental = true; + ipv6 = true; + ip6tables = true; + fixed-cidr-v6 = "${cfg.defaultSubnetPrefix}:255::/${toString cfg.defaultSubnetLength}"; + }; + + boot.kernel.sysctl = { + "net.ipv6.conf.all.forwarding" = 1; + "net.ipv6.conf.default.forwarding" = 1; + }; + + systemd.services = + let + subnetOffset = 100; + subnetByIndex = + i: + "${cfg.defaultSubnetPrefix}:${toString (subnetOffset + i)}::/${toString cfg.defaultSubnetLength}"; + + # this could be moved out into library functions, it's pretty useful. + # mapAttrsIndexed' :: (Int -> String -> AttrSet -> { name:: String; value :: Any; }) -> AttrSet -> AttrSet + mapAttrsIndexed' = f: attrs: listToAttrs (imap0 (i: v: f i v.name v.value) (attrsToList attrs)); + # mapAttrsIndexed :: (Int -> String -> AttrSet -> Any) -> AttrSet -> AttrSet + mapAttrsIndexed = + f: attrs: + mapAttrsIndexed' ( + i: n: v: + nameValuePair v.name (f i v.name v.value) + ) attrs; + + networkService = + index: name: network: + let + docker = getExe pkgs.docker; + options = concatStringsSep " " network.options; + subnet = if network.subnet == null then subnetByIndex index else network.subnet; + in + { + description = "Docker service network '${name}'"; + after = [ "docker.service" ]; + requires = [ "docker.service" ]; + + wantedBy = [ "multi-user.target" ]; + partOf = optional ( + network.serviceGroup != null + ) "${naming.groupTarget network.serviceGroup}.target"; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + + ExecStart = pkgs.writeShellScript "create-docker-network-${name}" '' + if ${docker} network inspect ${name} >/dev/null 2>&1; then + ${docker} network rm ${name} + fi + + ${docker} network create \ + --ipv6 \ + --subnet=${subnet} \ + --driver=${network.driver} \ + ${options} \ + ${name} + ''; + + ExecStop = pkgs.writeShellScript "delete-docker-network-${name}" '' + ${docker} network rm -f ${name} + ''; + }; + }; + + mkNetwork = index: name: network: { + name = naming.networkService name; + value = networkService index name network; + }; + in + mapAttrsIndexed' mkNetwork (filterAttrs (name: net: net.enable) cfg.networks); + }; +} |
