summaryrefslogtreecommitdiff
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;
+ };
+ };
+}