From f7e5d29029399af167f868ef3ae0a6cc34f4a4ad Mon Sep 17 00:00:00 2001 From: Mel Date: Sat, 16 Nov 2024 04:24:38 +0100 Subject: Prototype foundation service configration library Signed-off-by: Mel --- modules/common.nix | 2 + modules/foundation/default.nix | 5 + modules/foundation/services.nix | 231 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 modules/foundation/default.nix create mode 100644 modules/foundation/services.nix (limited to 'modules') diff --git a/modules/common.nix b/modules/common.nix index 937c7ef..7d46a37 100644 --- a/modules/common.nix +++ b/modules/common.nix @@ -2,6 +2,8 @@ { imports = [ + ./foundation + ./nix.nix ./user.nix ./locale.nix 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; + }; + }; +} -- cgit 1.4.1