{ 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/:/dev/'."; }; 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; }; }