tlaternet-server/modules/virtualisation/pods.nix

223 lines
6.8 KiB
Nix

{
lib,
config,
options,
...
}:
with lib; let
cfg = config.virtualisation.pods;
list-to-args = arg: list:
concatStringsSep " " (map (e: "--${arg}=${escapeShellArg e}") list);
possibly-unset-arg = arg: val: (optionalString (val != null) "--${arg}=${escapeShellArg val}");
mkPod = name: pod: rec {
path = [config.virtualisation.podman.package];
wants = ["network.target"];
after = ["network-online.target"];
wantedBy = ["multi-user.target" "default.target"];
environment.PODMAN_SYSTEMD_UNIT = "%n";
preStart = concatStringsSep " " [
"mkdir -p /run/podman/pods/ ;"
"podman pod create"
"--infra-conmon-pidfile=${escapeShellArg "/run/podman/pods/${name}.pid"}"
"--name=${escapeShellArg name}"
"--replace"
(list-to-args "add-host" pod.added-hosts)
(possibly-unset-arg "cgroup-parent" pod.cgroup-parent)
(list-to-args "dns" pod.dns)
(list-to-args "dns-opt" pod.dns-opt)
(list-to-args "dns-search" pod.dns-search)
(possibly-unset-arg "hostname" pod.hostname)
(possibly-unset-arg "infra" pod.infra)
(possibly-unset-arg "infra-command" pod.infra-command)
(possibly-unset-arg "infra-image" pod.infra-image)
(possibly-unset-arg "ip" pod.ip)
(possibly-unset-arg "mac-address" pod.mac-address)
(possibly-unset-arg "network" pod.network)
(possibly-unset-arg "network-alias" pod.network-alias)
(possibly-unset-arg "no-hosts" pod.no-hosts)
(list-to-args "publish" pod.publish)
(list-to-args "share" pod.share)
];
script = "podman pod start ${escapeShellArg name}";
preStop = "podman pod stop ${escapeShellArg name}";
# `podman generate systemd` generates a second stop after the
# first; not sure why but clearly it's recommended.
postStop = preStop;
serviceConfig = rec {
Type = "forking";
TimeoutStopSec = 70;
Restart = "on-failure";
PIDFile = "/run/podman/pods/${name}.pid";
};
};
in {
options.virtualisation.pods = mkOption {
type = with types;
attrsOf (submodule {
options = {
added-hosts = mkOption {
type = listOf str;
default = [];
description = "Additional hosts to add to /etc/hosts for each container.";
example = literalExample ''
[ "database:10.0.0.1" ]
'';
};
cgroup-parent = mkOption {
type = nullOr str;
default = null;
description = "The cgroups path under which the pod cgroup will be created.";
};
dns = mkOption {
type = listOf str;
default = [];
description = "The dns servers to set in /etc/resolv.conf.";
};
dns-opt = mkOption {
type = listOf str;
default = [];
description = "dns options to set in /etc/resolv.conf.";
};
dns-search = mkOption {
type = listOf str;
default = [];
description = "Search domains to set in /etc/resolv.conf.";
};
hostname = mkOption {
type = nullOr str;
default = null;
description = "The pod hostname.";
};
infra = mkOption {
type = nullOr bool;
default = null;
description = "Whether to create the infra container for the pod.";
};
infra-command = mkOption {
type = nullOr str;
default = null;
description = "The command to run in the infra container.";
};
infra-image = mkOption {
type = nullOr str;
default = null;
description = "The image to use for the infra container.";
};
ip = mkOption {
type = nullOr str;
default = null;
description = "A static IP address for the pod network.";
};
# TODO: set up label file stuff.
#
# labels = mkOption {};
mac-address = mkOption {
type = nullOr str;
default = null;
description = "A static mac address for the pod network.";
};
network = mkOption {
type = nullOr str;
default = null;
description = "Network configuration for the pod.";
};
network-alias = mkOption {
type = nullOr str;
default = null;
description = "DNS alias for the pod.";
};
no-hosts = mkOption {
type = nullOr bool;
default = null;
description = "Whether to disable /etc/hosts creation for the pod.";
};
publish = mkOption {
type = listOf str;
default = [];
description = "List of ports to publish from the pod.";
};
share = mkOption {
type = listOf str;
default = [];
description = "List of kernel namespaces to share.";
};
containers = options.virtualisation.oci-containers.containers;
};
});
default = {};
description = "Podman pods to run as systemd services.";
};
config = let
# Merge a list of attribute sets together
#
# TODO: See if there's a generic version for this somewhere in the
# pkgs lib?
mergeAttrs = attrList: foldr (a: b: a // b) {} attrList;
# Create services for all defined pods
pod-services = mapAttrs' (n: v: nameValuePair "pod-${n}" (mkPod n v)) cfg;
# Override the systemd-specific settings of containers defined in
# pods.
#
# I.e., make a systemd unit dependency on the pod service.
pod-container-services = mergeAttrs (mapAttrsToList (pname: pod:
mapAttrs' (cname: container:
nameValuePair "podman-${pname}-${cname}" rec {
after = ["pod-${pname}.service"];
requires = after;
})
pod.containers)
cfg);
# Override the oci-container settings for containers defined in pods.
#
# I.e., set the --pod=podname setting, and update the dependsOn so
# it points to containers in the same pod.
podifyContainer = container: podname:
container
// {
dependsOn =
map (dependency: "${podname}-${dependency}") container.dependsOn;
extraOptions = container.extraOptions ++ ["--pod=${podname}"];
};
in
lib.mkIf (cfg != {}) {
virtualisation.podman.enable = true;
virtualisation.oci-containers.backend = "podman";
systemd.services = pod-services // pod-container-services;
virtualisation.oci-containers.containers = mergeAttrs (mapAttrsToList
(pname: pod:
mapAttrs' (cname: container:
nameValuePair "${pname}-${cname}" (podifyContainer container pname))
pod.containers)
cfg);
};
}