summary refs log tree commit diff
path: root/modules/foundation
diff options
context:
space:
mode:
Diffstat (limited to 'modules/foundation')
-rw-r--r--modules/foundation/default.nix5
-rw-r--r--modules/foundation/services.nix231
2 files changed, 236 insertions, 0 deletions
diff --git a/modules/foundation/default.nix b/modules/foundation/default.nix
new file mode 100644
index 0000000..10ec503
--- /dev/null
+++ b/modules/foundation/default.nix
@@ -0,0 +1,5 @@
+{ ... }:
+
+{
+  imports = [ ./services.nix ];
+}
diff --git a/modules/foundation/services.nix b/modules/foundation/services.nix
new file mode 100644
index 0000000..871c9e5
--- /dev/null
+++ b/modules/foundation/services.nix
@@ -0,0 +1,231 @@
+{
+  lib,
+  config,
+  pkgs,
+  ...
+}:
+
+let
+  inherit (lib) mkOption types;
+
+  serviceOptions = {
+    options = {
+      image = mkOption {
+        type = types.submodule {
+          options = {
+            image = mkOption { type = types.str; };
+            imageFile = mkOption { type = types.package; };
+            base = mkOption { type = types.nullOr types.anything; };
+          };
+        };
+      };
+
+      ports = mkOption {
+        type = with types; listOf (listOf ints.u16);
+        default = [ ];
+      };
+
+      volumes = mkOption {
+        type = with types; listOf (listOf str);
+        default = [ ];
+      };
+
+      entrypoint = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+      };
+
+      cmd = mkOption {
+        type = with types; listOf str;
+        default = [ ];
+      };
+
+      workdir = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+      };
+
+      environment = mkOption {
+        type = types.attrsOf types.str;
+        default = { };
+      };
+    };
+  };
+
+  cfg = config.foundation;
+
+  mkName =
+    {
+      group,
+      name,
+      full ? false,
+    }:
+    let
+      isGroup = group != "";
+      isDefault = name == "default" || name == group;
+
+      shortName = if isGroup && isDefault then group else name;
+
+      fullName = if isGroup then (if isDefault then group else "${group}-${name}") else name;
+    in
+    assert name != "";
+    assert isDefault -> isGroup;
+    if full then fullName else shortName;
+
+  processServices =
+    group: serviceSet:
+    lib.mapAttrsToList (
+      name: c:
+      {
+        name = mkName { inherit name group; };
+        fullName = mkName {
+          inherit name group;
+          full = true;
+        };
+        inherit group;
+      }
+      // c
+    ) serviceSet;
+
+  singleServices = processServices "" cfg.services;
+
+  groupedServices = lib.mapAttrs (
+    group: groupServices: processServices group groupServices
+  ) cfg.service;
+
+  allServices =
+    let
+      allSingleServices = singleServices;
+      allGroupedServices = lib.flatten (lib.attrValues groupedServices);
+    in
+    allSingleServices ++ allGroupedServices;
+
+  groupStructure = lib.mapAttrs (
+    group: groupServices: lib.catAttrs "fullName" groupServices
+  ) groupedServices;
+in
+{
+  options.foundation = {
+    service = mkOption {
+      type = with types; attrsOf (attrsOf (submodule serviceOptions));
+      default = { };
+    };
+
+    services = mkOption {
+      type = with types; attrsOf (submodule serviceOptions);
+      default = { };
+    };
+  };
+
+  config = lib.mkIf (cfg.service != { } || cfg.services != { }) {
+    virtualisation.oci-containers.containers =
+      let
+        mkOciPort =
+          portTuple:
+          let
+            host = builtins.elemAt portTuple 0;
+            container = builtins.elemAt portTuple 1;
+          in
+          "127.0.0.1:${toString host}:${toString container}";
+
+        mkOciVolume =
+          volumeTuple:
+          let
+            hostPath = builtins.elemAt volumeTuple 0;
+            containerPath = builtins.elemAt volumeTuple 1;
+          in
+          "${hostPath}:${containerPath}";
+
+        mkOciContainer =
+          {
+            name,
+            image,
+            ports,
+            volumes,
+            entrypoint ? null,
+            cmd ? null,
+            workdir ? null,
+            environment ? null,
+            group,
+            ...
+          }:
+          {
+            inherit (image) image imageFile;
+            inherit
+              entrypoint
+              cmd
+              workdir
+              environment
+              ;
+            ports = map mkOciPort ports;
+            volumes = map mkOciVolume volumes;
+            extraOptions = lib.mkIf (group != "") [
+              "--network-alias=${name}"
+              "--network=${group}"
+            ];
+          };
+      in
+      builtins.listToAttrs
+        (map (v: lib.nameValuePair v.fullName (mkOciContainer v)) allServices);
+
+    systemd =
+      let
+        mkGroupRootTargetName = group: "docker-${group}-root";
+        mkServiceName = fullName: "docker-${fullName}";
+        mkNetworkServiceName = group: "docker-${group}-network";
+
+        genericContainerService =
+          group:
+          let
+            network = "${mkNetworkServiceName group}.service";
+            root = "${mkGroupRootTargetName group}.target";
+          in
+          {
+            serviceConfig = {
+              Restart = lib.mkOverride 90 "always";
+              RestartMaxDelaySec = lib.mkOverride 90 "1m";
+              RestartSec = lib.mkOverride 90 "100ms";
+              RestartSteps = lib.mkOverride 90 9;
+            };
+            after = [ network ];
+            requires = [ network ];
+            partOf = [ root ];
+            wantedBy = [ root ];
+          };
+
+        networkService =
+          group:
+          let
+            root = "${mkGroupRootTargetName group}.target";
+          in
+          {
+            path = [ pkgs.docker ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStop = "docker network rm -f ${group}";
+            };
+            script = ''
+              docker network inspect ${group} || docker network create ${group} --driver=bridge
+            '';
+            partOf = [ root ];
+            wantedBy = [ root ];
+          };
+      in
+      {
+        services = lib.concatMapAttrs (
+          group: serviceNames:
+          {
+            ${mkNetworkServiceName group} = networkService group;
+          }
+          // lib.genAttrs (map mkServiceName serviceNames) (_v: genericContainerService group)
+        ) groupStructure;
+
+        targets = lib.mapAttrs' (
+          group: _v: lib.nameValuePair
+            (mkGroupRootTargetName group)
+            { wantedBy = [ "multi-user.target" ]; }
+        ) groupStructure;
+      };
+  };
+}