mirror of
https://github.com/nix-community/home-manager.git
synced 2025-11-30 22:21:02 +01:00
Podman uses systemd-run to setup transient systemd timers, e.g. for healthchecks. On systems where systemd is not present in /run/current-system/sw/bin or ~/.nix-profile/bin (like one of my Ubuntu hosts), setting up the transient timers will fail. For containers with healthchecks configured, this results in the container being stuck in starting state. Relevant issue here: containers/podman#25034
361 lines
11 KiB
Nix
361 lines
11 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
|
|
extractQuadletReference = type: value:
|
|
let
|
|
regex = "([a-zA-Z0-9_-]+\\." + type + ").*";
|
|
parts = builtins.match regex value;
|
|
in if parts == null then value else builtins.elemAt parts 0;
|
|
|
|
dependencyBySuffix = type: value:
|
|
if (hasInfix ".${type}" value) then
|
|
let name = extractQuadletReference type value;
|
|
in if (hasAttr name cfg.internal.builtQuadlets) then
|
|
[ (cfg.internal.builtQuadlets.${name}) ]
|
|
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
|
|
quadletName = extractQuadletReference type value;
|
|
quadletsOfType =
|
|
filterAttrs (n: v: v.quadletData.resourceType == type)
|
|
cfg.internal.builtQuadlets;
|
|
in if (hasAttr quadletName quadletsOfType) then
|
|
[
|
|
(replaceStrings [ quadletName ] [ "podman-${quadletName}" ]
|
|
value)
|
|
]
|
|
else
|
|
[ value ]
|
|
else if ((hasInfix "/nix/store" value) == false
|
|
&& hasAttr value cfg.internal.builtQuadlets) then
|
|
lib.warn ''
|
|
A value for Podman container '${name}' might use a reference to another quadlet: ${value}.
|
|
Append the type '.${
|
|
cfg.internal.builtQuadlets.${value}.quadletData.resourceType
|
|
}' to '${baseName value}' if this is intended.
|
|
'' [ 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"
|
|
"${pkgs.systemd}/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;
|
|
};
|
|
}
|