diff --git a/README.md b/README.md index f1bd819..292fdce 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,17 @@ Compared to alternatives like [`virtualisation.oci-containers`](https://github.c containers = { nginx.containerConfig.image = "docker.io/library/nginx:latest"; nginx.containerConfig.networks = [ "host" "internal.network" ]; + nginx.containerConfig.pod = config.virtualisation.quadlet.pods.nginx-pod._configName; nginx.serviceConfig.TimeoutStartSec = "60"; }; networks = { internal.networkConfig.subnets = [ "10.0.123.1/24" ]; }; + pods = { + nginx-pod = { }; + }; }; } ``` -See [`container.nix`](./container.nix) and [`network.nix`](./network.nix) for all options. +See [`container.nix`](./container.nix), [`network.nix`](./network.nix), and [`pod.nix`](./pod.nix) for all options. diff --git a/container.nix b/container.nix index e76d933..6135ba1 100644 --- a/container.nix +++ b/container.nix @@ -5,9 +5,7 @@ lib, ... }: - with lib; - let containerOpts = { addCapabilities = quadletUtils.mkOption { @@ -259,7 +257,7 @@ let type = types.listOf types.str; default = [ ]; example = [ "host" ]; - description = "--network"; + description = "--net"; property = "Network"; }; @@ -285,6 +283,13 @@ let property = "Notify"; }; + pod = quadletUtils.mkOption { + type = types.nullOr types.str; + default = null; + description = "The full name of the pod to link to."; + property = "Pod"; + }; + podmanArgs = quadletUtils.mkOption { type = types.listOf types.str; default = [ ]; @@ -474,6 +479,7 @@ in default = serviceConfigDefault; }; + _name = mkOption { internal = true; }; _configName = mkOption { internal = true; }; _unitName = mkOption { internal = true; }; _configText = mkOption { internal = true; }; @@ -481,7 +487,6 @@ in config = let - configRelPath = "containers/systemd/${name}.container"; containerName = if config.containerConfig.name != null then config.containerConfig.name else name; containerConfig = config.containerConfig // { name = containerName; @@ -496,9 +501,9 @@ in Container = quadletUtils.configToProperties containerConfig containerOpts; Service = serviceConfigDefault // config.serviceConfig; }; - unitConfigText = quadletUtils.unitConfigToText unitConfig; in { + _name = containerName; _configName = "${name}.container"; _unitName = "${name}.service"; _configText = quadletUtils.unitConfigToText unitConfig; diff --git a/network.nix b/network.nix index c6c1392..10997c7 100644 --- a/network.nix +++ b/network.nix @@ -1,13 +1,14 @@ -{ quadletUtils, pkgs }: +{ + quadletUtils, + pkgs, +}: { config, name, lib, ... }: - with lib; - let networkOpts = { disableDns = quadletUtils.mkOption { @@ -135,13 +136,13 @@ in }; _configName = mkOption { internal = true; }; + _name = mkOption { internal = true; }; _unitName = mkOption { internal = true; }; _configText = mkOption { internal = true; }; }; config = let - configRelPath = "containers/systemd/${name}.network"; networkName = if config.networkConfig.name != null then config.networkConfig.name else "systemd-${name}"; networkConfig = config.networkConfig; @@ -159,6 +160,7 @@ in }; in { + _name = networkName; _configName = "${name}.network"; _unitName = "${name}-network.service"; _configText = quadletUtils.unitConfigToText unitConfig; diff --git a/nixos-module.nix b/nixos-module.nix index 7e9807a..bf27e43 100644 --- a/nixos-module.nix +++ b/nixos-module.nix @@ -5,9 +5,7 @@ pkgs, ... }@attrs: - with lib; - let cfg = config.virtualisation.quadlet; quadletUtils = import ./utils.nix { @@ -22,6 +20,7 @@ let containerOpts = types.submodule (import ./container.nix { inherit quadletUtils; }); networkOpts = types.submodule (import ./network.nix { inherit quadletUtils pkgs; }); + podOpts = types.submodule (import ./pod.nix { inherit quadletUtils; }); in { options = { @@ -35,15 +34,44 @@ in type = types.attrsOf networkOpts; default = { }; }; + + pods = mkOption { + type = types.attrsOf podOpts; + default = { }; + }; }; }; config = let - allObjects = (attrValues cfg.containers) ++ (attrValues cfg.networks); + containerAndPodObjects = (attrValues cfg.containers) ++ (attrValues cfg.pods); + allObjects = (attrValues cfg.containers) ++ (attrValues cfg.networks) ++ (attrValues cfg.pods); in { virtualisation.podman.enable = true; + assertions = + let + count_occurances = + str_list: + lib.lists.foldl' ( + acc: el: if acc ? ${el} then acc // { ${el} = acc.${el} + 1; } else acc // { ${el} = 1; } + ) { } str_list; + find_duplicate_elements = + str_l: lib.attrsets.attrNames (lib.attrsets.filterAttrs (_: v: v > 1) (count_occurances str_l)); + # assuming that only `name` defines the final name without the suffix! + # Containers and pods cannot have the same name! + duplicate_elements = find_duplicate_elements (map (x: x._name) containerAndPodObjects); + in + [ + { + assertion = duplicate_elements == [ ]; + message = '' + The container/pod names should be unique! + See: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#podname + The following names are not unique: ${lib.strings.concatStringsSep " " duplicate_elements} + ''; + } + ]; environment.etc = mergeAttrsList ( map (p: { "containers/systemd/${p._configName}" = { diff --git a/pod.nix b/pod.nix new file mode 100644 index 0000000..5d5f7fa --- /dev/null +++ b/pod.nix @@ -0,0 +1,219 @@ +{ quadletUtils }: +{ + config, + name, + lib, + ... +}: +with lib; +let + podOpts = { + name = quadletUtils.mkOption { + type = types.nullOr types.str; + default = null; + example = "name"; + description = "--name"; + property = "PodName"; + }; + + addHosts = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "hostname:192.168.10.11" ]; + description = "--add-host"; + property = "AddHost"; + }; + + dns = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "192.168.55.1" ]; + description = "--dns"; + property = "DNS"; + }; + + dnsOptions = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "ndots:1" ]; + description = "--dns-option"; + property = "DNSOption"; + }; + + dnsSearches = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "foo.com" ]; + description = "--dns-search"; + property = "DNSSearch"; + }; + + gidMaps = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "0:10000:10" ]; + description = "--gidmap"; + property = "GIDMap"; + }; + + # Not recommended to use by upstream: + # globalArgs = quadletUtils.mkOption { + # type = types.listOf types.str; + # default = [ ]; + # example = [ "--log-level=debug" ]; + # description = ""; + # property = "GlobalArgs"; + # }; + + ip = quadletUtils.mkOption { + type = types.nullOr types.str; + default = null; + example = "192.5.0.1"; + description = "--ip"; + property = "IP"; + }; + + ip6 = quadletUtils.mkOption { + type = types.nullOr types.str; + default = null; + example = "2001:db8::1"; + description = "--ip6"; + property = "IP6"; + }; + + networks = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "host" ]; + description = "--network"; + property = "Network"; + }; + + networkAliases = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "name" ]; + description = "--network-alias"; + property = "NetworkAlias"; + }; + + podmanArgs = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--cpus=2" ]; + description = "Additional podman arguments"; + property = "PodmanArgs"; + }; + + publishPorts = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "50-59" ]; + description = "--publish"; + property = "PublishPort"; + }; + + serviceName = quadletUtils.mkOption { + type = types.nullOr types.str; + default = null; + example = "service-name"; + description = "Instructs Quadlet to use the provided name."; + property = "ServiceName"; + }; + + subGIDMap = quadletUtils.mkOption { + type = types.nullOr types.str; + default = null; + example = "gtest"; + description = "--subgidname"; + property = "SubGIDMap"; + }; + + subUIDMap = quadletUtils.mkOption { + type = types.nullOr types.str; + default = null; + example = "utest"; + description = "--subuidname"; + property = "SubUIDMap"; + }; + + uidMaps = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "0:10000:10" ]; + description = "--uidmap"; + property = "UIDMap"; + }; + + userns = quadletUtils.mkOption { + type = types.nullOr types.str; + default = null; + example = "keep-id:uid=200,gid=210"; + description = "--userns"; + property = "UserNS"; + }; + + volumes = quadletUtils.mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ ]; + description = "--volume"; + property = "Volume"; + }; + }; +in +{ + options = { + podConfig = podOpts; + + autoStart = mkOption { + type = types.bool; + default = true; + example = true; + description = "When enabled, the pod is automatically started on boot."; + }; + + unitConfig = mkOption { + type = types.attrs; + default = { }; + }; + + serviceConfig = mkOption { + type = types.attrs; + default = { }; + }; + + _name = mkOption { internal = true; }; + _configName = mkOption { internal = true; }; + _unitName = mkOption { internal = true; }; + _configText = mkOption { internal = true; }; + }; + + config = + let + serviceConfigDefault = { + Restart = "always"; + TimeoutStartSec = 900; + }; + podName = if config.podConfig.name != null then config.podConfig.name else name; + podConfig = config.podConfig // { + name = podName; + }; + unitConfig = { + Unit = { + Description = "Podman pod ${name}"; + } // config.unitConfig; + Install = { + WantedBy = if config.autoStart then [ "default.target" ] else [ ]; + }; + Pod = quadletUtils.configToProperties podConfig podOpts; + Service = serviceConfigDefault // config.serviceConfig; + }; + in + { + _name = podName; + _configName = "${name}.pod"; + _unitName = "${name}-pod.service"; + _configText = quadletUtils.unitConfigToText unitConfig; + }; +}