{ config, lib, pkgs, ... }: with lib; let cfg = config.services.podman; podman-lib = import ./podman-lib.nix { inherit pkgs lib config; }; createQuadletSource = name: containerDef: let # formatServiceNameForType = type: name: # { # image = "${name}-image.service"; # build = "${name}-build.service"; # network = "${name}-network.service"; # volume = "${name}-volume.service"; # }."${type}"; dependencyBySuffix = type: name: if (hasInfix ".${type}" name) then let baseName = elemAt (splitString ".${type}" name) 0; in if (hasAttr (builtins.trace (baseName) baseName) cfg.internal.builtQuadlets) then [ (cfg.internal.builtQuadlets.${baseName}) ] else [ ] else [ ]; withResolverFor = type: value: let resolve = v: dependencyBySuffix type v; in if builtins.isList value then builtins.concatLists (map resolve value) # Flatten list of lists else resolve value; dependencyServices = (withResolverFor "image" containerDef.image) ++ (withResolverFor "build" containerDef.image) ++ (withResolverFor "network" containerDef.network) ++ (withResolverFor "volume" containerDef.volumes); getActualImage = if (builtins.hasAttr containerDef.image cfg.images) then cfg.images."${containerDef.image}".image else if (builtins.hasAttr containerDef.image cfg.builds) then "localhost/homemanager/${containerDef.image}" else containerDef.image; quadlet = (podman-lib.deepMerge { Container = { AddCapability = containerDef.addCapabilities; AddDevice = containerDef.devices; AutoUpdate = containerDef.autoUpdate; ContainerName = name; DropCapability = containerDef.dropCapabilities; Entrypoint = containerDef.entrypoint; Environment = containerDef.environment; EnvironmentFile = containerDef.environmentFile; Exec = containerDef.exec; Group = containerDef.group; Image = resolvedImage; IP = containerDef.ip4; IP6 = containerDef.ip6; Label = (containerDef.labels // { "nix.home-manager.managed" = true; }); Network = containerDef.network; NetworkAlias = containerDef.networkAlias; PodmanArgs = containerDef.extraPodmanArgs; PublishPort = containerDef.ports; UserNS = containerDef.userNS; User = containerDef.user; Volume = containerDef.volumes; }; Install = { WantedBy = optionals containerDef.autoStart [ "default.target" "multi-user.target" ]; }; Service = { Environment = { PATH = (builtins.concatStringsSep ":" [ "/run/wrappers/bin" "/run/current-system/sw/bin" "${config.home.homeDirectory}/.nix-profile/bin" ]); }; Restart = "always"; TimeoutStopSec = 30; }; Unit = { Description = (if (builtins.isString containerDef.description) then containerDef.description else "Service for container ${name}"); }; } containerDef.extraConfig); in { dependencies = dependencyServices; text = '' # Automatically generated by home-manager podman container configuration # DO NOT EDIT THIS FILE DIRECTLY # # ${name}.container ${podman-lib.toQuadletIni quadlet} ''; }; toQuadletInternal = name: containerDef: let quadletSrc = createQuadletSource name containerDef; in { assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig; dependencies = quadletSrc.dependencies; resourceType = "container"; serviceName = "podman-${name}"; # quadlet service name: 'podman-.service' source = podman-lib.removeBlankLines quadletSrc.text; }; # Define the container user type as the user interface containerDefinitionType = types.submodule { options = { addCapabilities = mkOption { type = with types; listOf str; default = [ ]; example = [ "CAP_DAC_OVERRIDE" "CAP_IPC_OWNER" ]; description = "The capabilities to add to the container."; }; autoStart = mkOption { type = types.bool; default = true; description = '' Whether to start the container on boot (requires user lingering). ''; }; autoUpdate = mkOption { type = types.enum [ null "registry" "local" ]; default = null; example = "registry"; description = "The autoupdate policy for the container."; }; description = mkOption { type = with types; nullOr str; default = null; example = "My Container"; description = "The description of the container."; }; devices = mkOption { type = with types; listOf str; default = [ ]; example = [ "/dev/:/dev/" ]; description = "The devices to mount into the container"; }; dropCapabilities = mkOption { type = with types; listOf str; default = [ ]; example = [ "CAP_DAC_OVERRIDE" "CAP_IPC_OWNER" ]; description = "The capabilities to drop from the container."; }; entrypoint = mkOption { type = with types; nullOr str; default = null; example = "/foo.sh"; description = "The container entrypoint."; }; environment = mkOption { type = podman-lib.primitiveAttrs; default = { }; example = literalExpression '' { VAR1 = "0:100"; VAR2 = true; VAR3 = 5; } ''; description = "Environment variables to set in the container."; }; environmentFile = mkOption { type = with types; listOf str; default = [ ]; example = [ "/etc/environment" "/etc/other-env" ]; description = '' Paths to files containing container environment variables. ''; }; exec = mkOption { type = with types; nullOr str; default = null; example = "sleep inf"; description = "The command to run after the container start."; }; extraPodmanArgs = mkOption { type = types.listOf types.str; default = [ ]; example = [ "--security-opt=no-new-privileges" "--security-opt=seccomp=unconfined" ]; description = "Extra arguments to pass to the podman run command."; }; extraConfig = mkOption { type = podman-lib.extraConfigType; default = { }; example = literalExpression '' { Container = { User = 1000; }; Service = { TimeoutStartSec = 15; }; } ''; description = '' INI sections and values to populate the Container Quadlet. ''; }; group = mkOption { type = with types; nullOr (either int str); default = null; description = "The group ID inside the container."; }; image = mkOption { type = types.str; example = "registry.access.redhat.com/ubi9-minimal:latest"; description = "The container image."; }; ip4 = mkOption { type = with types; nullOr str; default = null; description = "Set an IPv4 address for the container."; }; ip6 = mkOption { type = with types; nullOr str; default = null; description = "Set an IPv6 address for the container."; }; labels = mkOption { type = with types; attrsOf str; default = { }; example = { app = "myapp"; some-label = "somelabel"; }; description = "The labels to apply to the container."; }; network = mkOption { type = with types; either str (listOf str); default = [ ]; apply = value: if isString value then [ value ] else value; example = literalMD '' `"host"` or `"bridge_network_1"` or `[ "bridge_network_1" "bridge_network_2" ]` ''; description = '' The network mode or network/s to connect the container to. Equivalent to `podman run --network=