1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-03-27 18:31:12 +00:00
home-manager/modules/services/podman-linux/containers.nix
bamhm182 ce9cb2496c
podman: added volume, image, and build quadlets (#6137)
Added support for build, image, and volume quadlets
Resolved test failures due to podman 5.3.0 upgrade
Replaced several instances of pkgs.podman with services.podman.package
2025-03-09 23:02:05 -05:00

348 lines
10 KiB
Nix

{ 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 = "podman-${name}-image.service";
build = "podman-${name}-build.service";
network = "podman-${name}-network.service";
volume = "podman-${name}-volume.service";
}."${type}";
dependencyByHomeManagerQuadlet = type: name:
let
definitionsOfType =
filter (q: q.resourceType == type) cfg.internal.quadletDefinitions;
matchingName =
filter (q: q.serviceName == "podman-${name}") definitionsOfType;
in if ((length matchingName) == 1) then
[ (formatServiceNameForType type name) ]
else
[ ];
forEachValue = type: value:
let resolve = v: dependencyByHomeManagerQuadlet type v;
in if isList value then
concatLists (map resolve value)
else
resolve value;
withResolverFor = type: value:
{
"image" = forEachValue "image" value;
"build" = forEachValue "build" value;
"network" = forEachValue "network" value;
"volume" = let
a = if isList value then value else [ value ];
volumes = map (v: elemAt (splitString ":" v) 0) a;
in forEachValue "volume" volumes;
}.${type};
dependencyServices = (withResolverFor "image" containerDef.image)
++ (withResolverFor "build" containerDef.image)
++ (withResolverFor "network" containerDef.network)
++ (withResolverFor "volume" containerDef.volumes);
resolvedImage = 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 = {
After = dependencyServices;
Requires = dependencyServices;
Description = (if (builtins.isString containerDef.description) then
containerDef.description
else
"Service for container ${name}");
};
} containerDef.extraConfig);
in ''
# Automatically generated by home-manager podman container configuration
# DO NOT EDIT THIS FILE DIRECTLY
#
# ${name}.container
${podman-lib.toQuadletIni quadlet}
'';
toQuadletInternal = name: containerDef: {
assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig;
resourceType = "container";
serviceName =
"podman-${name}"; # quadlet service name: 'podman-<name>.service'
source =
podman-lib.removeBlankLines (createQuadletSource name containerDef);
};
# 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/<host>:/dev/<container>" ];
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=<option>`.
'';
};
networkAlias = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "mycontainer" "web" ];
description = "Network aliases for the container.";
};
ports = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "8080:80" "8443:443" ];
description = "A mapping of ports between host and container";
};
userNS = mkOption {
type = with types; nullOr str;
default = null;
description = "Use a user namespace for the container.";
};
user = mkOption {
type = with types; nullOr (either int str);
default = null;
description = "The user ID inside the container.";
};
volumes = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "/tmp:/tmp" "/var/run/test.secret:/etc/secret:ro" ];
description = "The volumes to mount into the container.";
};
};
};
in {
imports = [ ./options.nix ];
options.services.podman.containers = mkOption {
type = types.attrsOf containerDefinitionType;
default = { };
description = "Defines Podman container quadlet configurations.";
};
config =
let containerQuadlets = mapAttrsToList toQuadletInternal cfg.containers;
in mkIf cfg.enable {
services.podman.internal.quadletDefinitions = containerQuadlets;
assertions =
flatten (map (container: container.assertions) containerQuadlets);
# manifest file
xdg.configFile."podman/containers.manifest".text =
podman-lib.generateManifestText containerQuadlets;
};
}