summary refs log tree commit diff
path: root/modules/foundation/services/networks.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/foundation/services/networks.nix')
-rw-r--r--modules/foundation/services/networks.nix215
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);
+  };
+}