1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-30 22:21:02 +01:00
home-manager/modules/services/podman-linux/containers.nix
Nicholas Hassan 81bf639da7 podman: link dependent quadlets during build
podman's systemd generator can automatically resolve unit dependencies, so instead of us guessing these links to create them, we provide the sources during generation
2025-03-19 14:08:12 -05:00

343 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
dependencyBySuffix = type: name:
if (hasInfix ".${type}" name) then
let baseName = elemAt (splitString ".${type}" name) 0;
in if (hasAttr 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);
checkQuadletReference = types: value:
if builtins.isList value then
builtins.concatLists (map (checkQuadletReference types) value)
else
let type = findFirst (t: hasInfix ".${t}" value) null types;
in if (type != null) then
let
baseName = elemAt (splitString ".${type}" value) 0;
quadletsOfType =
filterAttrs (n: v: v.quadletData.resourceType == type)
cfg.internal.builtQuadlets;
in if (hasAttr baseName quadletsOfType) then
[ (replaceStrings [ baseName ] [ "podman-${baseName}" ] value) ]
else
[ value ]
else
[ value ];
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 = checkQuadletReference [ "build" "image" ] containerDef.image;
IP = containerDef.ip4;
IP6 = containerDef.ip6;
Label =
(containerDef.labels // { "nix.home-manager.managed" = true; });
Network = checkQuadletReference [ "network" ] containerDef.network;
NetworkAlias = containerDef.networkAlias;
PodmanArgs = containerDef.extraPodmanArgs;
PublishPort = containerDef.ports;
UserNS = containerDef.userNS;
User = containerDef.user;
Volume = checkQuadletReference [ "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;
attrs = quadlet;
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 src = createQuadletSource name containerDef;
in {
assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig;
dependencies = src.dependencies;
resourceType = "container";
serviceName =
"podman-${src.attrs.Container.ContainerName}"; # generated service name: 'podman-<name>.service'
source = podman-lib.removeBlankLines src.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/<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;
};
}