1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 11:36:05 +01:00
home-manager/modules/services/podman-linux/containers.nix

412 lines
12 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
inherit (lib) mkOption types;
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 (lib.hasInfix ".${type}" value) then
let
name = extractQuadletReference type value;
in
if (lib.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 = lib.findFirst (t: lib.hasInfix ".${t}" value) null types;
in
if (type != null) then
let
quadletName = extractQuadletReference type value;
quadletsOfType = lib.filterAttrs (
n: v: v.quadletData.resourceType == type
) cfg.internal.builtQuadlets;
in
if (lib.hasAttr quadletName quadletsOfType) then
[
(lib.replaceStrings [ quadletName ] [ "podman-${quadletName}" ] value)
]
else
[ value ]
else if
((lib.hasInfix "/nix/store" value) == false && lib.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 '${lib.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 = lib.optionals containerDef.autoStart [
"default.target"
"multi-user.target"
];
};
Service = {
Environment = {
PATH = (
builtins.concatStringsSep ":" [
"/run/wrappers/bin"
"/run/current-system/sw/bin"
"${pkgs.nftables}/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 = lib.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 = lib.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 lib.isString value then [ value ] else value;
example = lib.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 = lib.mapAttrsToList toQuadletInternal cfg.containers;
in
lib.mkIf cfg.enable {
services.podman.internal.quadletDefinitions = containerQuadlets;
assertions = lib.flatten (map (container: container.assertions) containerQuadlets);
# manifest file
xdg.configFile."podman/containers.manifest".text =
podman-lib.generateManifestText containerQuadlets;
};
}