1
0
mirror of https://github.com/nix-community/home-manager synced 2024-06-01 04:23:34 +02:00
home-manager/modules/services/podman-linux/containers.nix
Nicholas Hassan de53c7ccfb
podman: add new module 'podman'
Adds a new Podman module for creating user containers and networks as
systemd services. These are installed to the user's XDG_CONFIG/systemd/user directory.
2024-04-30 16:53:38 +09:30

344 lines
11 KiB
Nix

{ config, lib, ... }:
with lib;
let
podman-lib = import ./podman-lib.nix { inherit lib; };
createQuadletSource = name: containerDef:
let
### Definitions
serviceName = if containerDef.serviceName != null then containerDef.serviceName else name;
containerName = name; # Use the submodule name as the container name
mergedServiceConfig = podman-lib.serviceConfigDefaults // containerDef.serviceConfig;
mergedUnitConfig = podman-lib.unitConfigDefaults // containerDef.unitConfig;
###
### Helpers
ifNotNull = condition: text: if condition != null then text else "";
ifNotEmptyList = list: text: if list != [] then text else "";
ifNotEmptySet = set: text: if set != {} then text else "";
###
### Formatters
formatExtraConfig = podman-lib.formatExtraConfig;
formatPrimitiveValue = podman-lib.formatPrimitiveValue;
formatNetworkDependencies = networks:
let
formatElement = network: "podman-${network}-network.service";
in
concatStringsSep " " (map formatElement networks);
formatEnvironment = env:
if env != {} then
concatStringsSep " " (mapAttrsToList (k: v: "${k}=${formatPrimitiveValue v}") env)
else
"";
formatPorts = ports:
if ports != [] then
concatStringsSep "\n" (map (port: "PublishPort=${port}") ports)
else
"";
formatVolumes = volumes:
if volumes != [] then
concatStringsSep "\n" (map (volume: "Volume=${volume}") volumes)
else
"";
formatDevices = devices:
if devices != [] then
concatStringsSep "\n" (map (device: "AddDevice=${device}") devices)
else
"";
formatCapabilities = action: capabilities:
if capabilities != [] then
concatStringsSep "\n" (map (capability: "${action}Capability=${capability}") capabilities)
else
"";
formatLabels = labels:
if labels != [] then
concatStringsSep "\n" (map (label: "Label=${label}") labels)
else
"";
formatAutoUpdate = autoupdate:
if autoupdate == "registry" then
"AutoUpdate=registry"
else if autoupdate == "local" then
"AutoUpdate=local"
else
"";
# TODO: check that the user hasn't supplied both networkMode and networks
formatNetwork = containerDef:
if containerDef.networkMode != null then
"Network=${containerDef.networkMode}"
else if containerDef.networks != [] then
"Network=${concatStringsSep "," containerDef.networks}"
else
"";
formatPodmanArgs = containerDef:
let
networkAliasArg = if containerDef.networkAlias != null then "--network-alias ${containerDef.networkAlias}" else null;
entrypointArg = if containerDef.entrypoint != null then "--entrypoint ${containerDef.entrypoint}" else null;
allArgs = [networkAliasArg entrypointArg] ++ containerDef.extraOptions;
in
if allArgs != [] && allArgs != [""] then
"PodmanArgs=${concatStringsSep " " (filter (arg: arg != null && arg != "") allArgs)}"
else
"";
###
configText = ''
# Automatically generated by home-manager podman containers module
# DO NOT EDIT THIS FILE DIRECTLY
#
# ${serviceName}.container
[Unit]
Description=${if containerDef.description != null then containerDef.description else "Service for container ${containerName}"}
After=network.target
${ifNotEmptyList containerDef.networks "After=${formatNetworkDependencies containerDef.networks}"}
${formatExtraConfig mergedUnitConfig}
[Container]
ContainerName=${containerName}
Image=${containerDef.image}
Label=nix.home-manager.managed=true
${ifNotEmptySet containerDef.environment "Environment=${formatEnvironment containerDef.environment}"}
${ifNotNull containerDef.environmentFile "EnvironmentFile=${containerDef.environmentFile}"}
${ifNotNull containerDef.command "Exec=${containerDef.command}"}
${ifNotNull containerDef.user "User=${formatPrimitiveValue containerDef.user}"}
${ifNotNull containerDef.userNS "UserNS=${containerDef.userNS}"}
${ifNotNull containerDef.group "Group=${formatPrimitiveValue containerDef.group}"}
${ifNotEmptyList containerDef.ports (formatPorts containerDef.ports)}
${ifNotNull containerDef.networkMode "Network=${containerDef.networkMode}"}
${formatNetwork containerDef}
${ifNotNull containerDef.ip4 "IP=${containerDef.ip4}"}
${ifNotNull containerDef.ip6 "IP6=${containerDef.ip6}"}
${ifNotEmptyList containerDef.volumes (formatVolumes containerDef.volumes)}
${ifNotEmptyList containerDef.devices (formatDevices containerDef.devices)}
${formatAutoUpdate containerDef.autoupdate}
${ifNotEmptyList containerDef.addCapabilities (formatCapabilities "Add" containerDef.addCapabilities)}
${ifNotEmptyList containerDef.dropCapabilities (formatCapabilities "Drop" containerDef.dropCapabilities)}
${ifNotEmptyList containerDef.labels (formatLabels containerDef.labels)}
${formatPodmanArgs containerDef}
${formatExtraConfig containerDef.extraContainerConfig}
[Service]
Environment="PATH=/run/wrappers/bin:/run/current-system/sw/bin:${config.home.homeDirectory}/.nix-profile/bin"
${formatExtraConfig mergedServiceConfig}
[Install]
${if containerDef.autostart then "WantedBy=multi-user.target default.target" else ""}
'';
removeBlankLines = text:
let
lines = splitString "\n" text;
nonEmptyLines = filter (line: line != "") lines;
in
concatStringsSep "\n" nonEmptyLines;
in
removeBlankLines configText;
toQuadletInternal = name: containerDef:
let
allAssertions = (podman-lib.assertConfigTypes podman-lib.serviceConfigTypeRules containerDef.serviceConfig name) ++
(podman-lib.assertConfigTypes podman-lib.unitConfigTypeRules containerDef.unitConfig name);
in
{
serviceName = if containerDef.serviceName != null then containerDef.serviceName else "podman-${name}";
source = createQuadletSource name containerDef;
unitType = "container";
assertions = allAssertions;
};
in
let
# Define the container user type as the user interface
containerDefinitionType = types.submodule {
options = {
serviceName = mkOption {
type = with types; nullOr str;
description = "The name of the systemd service to generate for the container.";
default = null;
};
description = mkOption {
type = with types; nullOr str;
description = "The description of the container.";
default = null;
};
image = mkOption {
type = types.str;
description = "The container image.";
};
entrypoint = mkOption {
type = with types; nullOr str;
description = "The container entrypoint.";
default = null;
};
command = mkOption {
type = with types; nullOr str;
description = "The command to run after the container specification.";
default = null;
};
environment = mkOption {
type = podman-lib.primitiveAttrs;
default = {};
};
environmentFile = mkOption {
type = with types; nullOr str;
default = null;
};
ports = mkOption {
type = with types; listOf str;
default = [];
};
user = mkOption {
type = with types; nullOr (oneOf [ str int ]);
default = null;
};
userNS = mkOption {
type = with types; nullOr str;
default = null;
};
group = mkOption {
type = with types; nullOr (oneOf [ str int ]);
default = null;
};
networkMode = mkOption {
type = with types; nullOr str;
default = null;
};
networks = mkOption {
type = with types; listOf str;
default = [];
};
ip4 = mkOption {
type = with types; nullOr str;
default = null;
};
ip6 = mkOption {
type = with types; nullOr str;
default = null;
};
networkAlias = mkOption {
type = with types; nullOr str;
default = null;
};
volumes = mkOption {
type = with types; listOf str;
default = [];
};
devices = mkOption {
type = types.listOf types.str;
default = [];
description = "The devices to mount into the container, in the format '/dev/<host>:/dev/<container>'.";
};
autoupdate = mkOption {
type = with types; enum [
""
"registry"
"local"
];
default = "";
};
autostart = mkOption {
type = types.bool;
default = true;
};
addCapabilities = mkOption {
type = with types; listOf str;
default = [];
};
dropCapabilities = mkOption {
type = with types; listOf str;
default = [];
};
labels = mkOption {
type = with types; listOf str;
default = [];
};
extraOptions = mkOption {
type = with types; listOf str;
default = [];
};
extraContainerConfig = mkOption {
type = podman-lib.primitiveAttrs;
default = {};
example = literalExample ''
extraContainerConfig = {
UIDMap = "0:1000:1";
ReadOnlyTmpfs = true;
EnvironmentFile = [ /etc/environment /root/.env];
};
'';
};
serviceConfig = mkOption {
type = podman-lib.serviceConfigType;
default = {};
};
unitConfig = mkOption {
type = podman-lib.unitConfigType;
default = {};
};
};
};
in {
imports = [
./options.nix
];
options.services.podman.containers = mkOption {
type = types.attrsOf containerDefinitionType;
default = {};
description = "Attribute set of container definitions.";
};
config = let
containerQuadlets = mapAttrsToList toQuadletInternal config.services.podman.containers;
in {
internal.podman-quadlet-definitions = containerQuadlets;
assertions = lib.flatten (map (container: container.assertions) config.internal.podman-quadlet-definitions);
# manifest file
home.file."${config.xdg.configHome}/podman/containers.manifest".text = podman-lib.generateManifestText containerQuadlets;
};
}