344 lines
10 KiB
Nix
344 lines
10 KiB
Nix
![]() |
{ config, lib, ... }:
|
||
|
|
||
|
with lib;
|
||
|
|
||
|
let
|
||
|
podman-lib = import ./podman-lib.nix { inherit lib; };
|
||
|
|
||
|
createQuadletSource = name: containerDef:
|
||
|
let
|
||
|
### Definitions
|
||
|
serviceName = if containerDef.serviceName != null then containerDef.serviceName else name;
|
||
|
containerName = name; # Use the submodule name as the container name
|
||
|
mergedServiceConfig = podman-lib.serviceConfigDefaults // containerDef.serviceConfig;
|
||
|
mergedUnitConfig = podman-lib.unitConfigDefaults // containerDef.unitConfig;
|
||
|
###
|
||
|
|
||
|
### Helpers
|
||
|
ifNotNull = condition: text: if condition != null then text else "";
|
||
|
ifNotEmptyList = list: text: if list != [] then text else "";
|
||
|
ifNotEmptySet = set: text: if set != {} then text else "";
|
||
|
###
|
||
|
|
||
|
### Formatters
|
||
|
formatExtraConfig = podman-lib.formatExtraConfig;
|
||
|
formatPrimitiveValue = podman-lib.formatPrimitiveValue;
|
||
|
|
||
|
formatNetworkDependencies = networks:
|
||
|
let
|
||
|
formatElement = network: "podman-${network}-network.service";
|
||
|
in
|
||
|
concatStringsSep " " (map formatElement networks);
|
||
|
|
||
|
formatEnvironment = env:
|
||
|
if env != {} then
|
||
|
concatStringsSep " " (mapAttrsToList (k: v: "${k}=${formatPrimitiveValue v}") env)
|
||
|
else
|
||
|
"";
|
||
|
|
||
|
formatPorts = ports:
|
||
|
if ports != [] then
|
||
|
concatStringsSep "\n" (map (port: "PublishPort=${port}") ports)
|
||
|
else
|
||
|
"";
|
||
|
|
||
|
formatVolumes = volumes:
|
||
|
if volumes != [] then
|
||
|
concatStringsSep "\n" (map (volume: "Volume=${volume}") volumes)
|
||
|
else
|
||
|
"";
|
||
|
|
||
|
formatDevices = devices:
|
||
|
if devices != [] then
|
||
|
concatStringsSep "\n" (map (device: "AddDevice=${device}") devices)
|
||
|
else
|
||
|
"";
|
||
|
|
||
|
formatCapabilities = action: capabilities:
|
||
|
if capabilities != [] then
|
||
|
concatStringsSep "\n" (map (capability: "${action}Capability=${capability}") capabilities)
|
||
|
else
|
||
|
"";
|
||
|
|
||
|
formatLabels = labels:
|
||
|
if labels != [] then
|
||
|
concatStringsSep "\n" (map (label: "Label=${label}") labels)
|
||
|
else
|
||
|
"";
|
||
|
|
||
|
formatAutoUpdate = autoupdate:
|
||
|
if autoupdate == "registry" then
|
||
|
"AutoUpdate=registry"
|
||
|
else if autoupdate == "local" then
|
||
|
"AutoUpdate=local"
|
||
|
else
|
||
|
"";
|
||
|
|
||
|
# TODO: check that the user hasn't supplied both networkMode and networks
|
||
|
formatNetwork = containerDef:
|
||
|
if containerDef.networkMode != null then
|
||
|
"Network=${containerDef.networkMode}"
|
||
|
else if containerDef.networks != [] then
|
||
|
"Network=${concatStringsSep "," containerDef.networks}"
|
||
|
else
|
||
|
"";
|
||
|
|
||
|
formatPodmanArgs = containerDef:
|
||
|
let
|
||
|
networkAliasArg = if containerDef.networkAlias != null then "--network-alias ${containerDef.networkAlias}" else null;
|
||
|
entrypointArg = if containerDef.entrypoint != null then "--entrypoint ${containerDef.entrypoint}" else null;
|
||
|
allArgs = [networkAliasArg entrypointArg] ++ containerDef.extraOptions;
|
||
|
in
|
||
|
if allArgs != [] && allArgs != [""] then
|
||
|
"PodmanArgs=${concatStringsSep " " (filter (arg: arg != null && arg != "") allArgs)}"
|
||
|
else
|
||
|
"";
|
||
|
###
|
||
|
|
||
|
configText = ''
|
||
|
# Automatically generated by home-manager podman containers module
|
||
|
# DO NOT EDIT THIS FILE DIRECTLY
|
||
|
#
|
||
|
# ${serviceName}.container
|
||
|
[Unit]
|
||
|
Description=${if containerDef.description != null then containerDef.description else "Service for container ${containerName}"}
|
||
|
After=network.target
|
||
|
${ifNotEmptyList containerDef.networks "After=${formatNetworkDependencies containerDef.networks}"}
|
||
|
${formatExtraConfig mergedUnitConfig}
|
||
|
|
||
|
[Container]
|
||
|
ContainerName=${containerName}
|
||
|
Image=${containerDef.image}
|
||
|
Label=nix.home-manager.managed=true
|
||
|
${ifNotEmptySet containerDef.environment "Environment=${formatEnvironment containerDef.environment}"}
|
||
|
${ifNotNull containerDef.environmentFile "EnvironmentFile=${containerDef.environmentFile}"}
|
||
|
${ifNotNull containerDef.command "Exec=${containerDef.command}"}
|
||
|
${ifNotNull containerDef.user "User=${formatPrimitiveValue containerDef.user}"}
|
||
|
${ifNotNull containerDef.userNS "UserNS=${containerDef.userNS}"}
|
||
|
${ifNotNull containerDef.group "Group=${formatPrimitiveValue containerDef.group}"}
|
||
|
${ifNotEmptyList containerDef.ports (formatPorts containerDef.ports)}
|
||
|
${ifNotNull containerDef.networkMode "Network=${containerDef.networkMode}"}
|
||
|
${formatNetwork containerDef}
|
||
|
${ifNotNull containerDef.ip4 "IP=${containerDef.ip4}"}
|
||
|
${ifNotNull containerDef.ip6 "IP6=${containerDef.ip6}"}
|
||
|
${ifNotEmptyList containerDef.volumes (formatVolumes containerDef.volumes)}
|
||
|
${ifNotEmptyList containerDef.devices (formatDevices containerDef.devices)}
|
||
|
${formatAutoUpdate containerDef.autoupdate}
|
||
|
${ifNotEmptyList containerDef.addCapabilities (formatCapabilities "Add" containerDef.addCapabilities)}
|
||
|
${ifNotEmptyList containerDef.dropCapabilities (formatCapabilities "Drop" containerDef.dropCapabilities)}
|
||
|
${ifNotEmptyList containerDef.labels (formatLabels containerDef.labels)}
|
||
|
${formatPodmanArgs containerDef}
|
||
|
${formatExtraConfig containerDef.extraContainerConfig}
|
||
|
|
||
|
[Service]
|
||
|
Environment="PATH=/run/wrappers/bin:/run/current-system/sw/bin:${config.home.homeDirectory}/.nix-profile/bin"
|
||
|
${formatExtraConfig mergedServiceConfig}
|
||
|
|
||
|
[Install]
|
||
|
${if containerDef.autostart then "WantedBy=multi-user.target default.target" else ""}
|
||
|
'';
|
||
|
|
||
|
removeBlankLines = text:
|
||
|
let
|
||
|
lines = splitString "\n" text;
|
||
|
nonEmptyLines = filter (line: line != "") lines;
|
||
|
in
|
||
|
concatStringsSep "\n" nonEmptyLines;
|
||
|
|
||
|
in
|
||
|
removeBlankLines configText;
|
||
|
|
||
|
toQuadletInternal = name: containerDef:
|
||
|
let
|
||
|
allAssertions = (podman-lib.assertConfigTypes podman-lib.serviceConfigTypeRules containerDef.serviceConfig name) ++
|
||
|
(podman-lib.assertConfigTypes podman-lib.unitConfigTypeRules containerDef.unitConfig name);
|
||
|
in
|
||
|
{
|
||
|
serviceName = if containerDef.serviceName != null then containerDef.serviceName else "podman-${name}";
|
||
|
source = createQuadletSource name containerDef;
|
||
|
unitType = "container";
|
||
|
assertions = allAssertions;
|
||
|
};
|
||
|
in
|
||
|
|
||
|
let
|
||
|
# Define the container user type as the user interface
|
||
|
containerDefinitionType = types.submodule {
|
||
|
options = {
|
||
|
serviceName = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
description = "The name of the systemd service to generate for the container.";
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
description = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
description = "The description of the container.";
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
image = mkOption {
|
||
|
type = types.str;
|
||
|
description = "The container image.";
|
||
|
};
|
||
|
|
||
|
entrypoint = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
description = "The container entrypoint.";
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
command = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
description = "The command to run after the container specification.";
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
environment = mkOption {
|
||
|
type = podman-lib.primitiveAttrs;
|
||
|
default = {};
|
||
|
};
|
||
|
|
||
|
environmentFile = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
ports = mkOption {
|
||
|
type = with types; listOf str;
|
||
|
default = [];
|
||
|
};
|
||
|
|
||
|
user = mkOption {
|
||
|
type = with types; nullOr (oneOf [ str int ]);
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
userNS = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
group = mkOption {
|
||
|
type = with types; nullOr (oneOf [ str int ]);
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
networkMode = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
networks = mkOption {
|
||
|
type = with types; listOf str;
|
||
|
default = [];
|
||
|
};
|
||
|
|
||
|
ip4 = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
ip6 = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
networkAlias = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
default = null;
|
||
|
};
|
||
|
|
||
|
volumes = mkOption {
|
||
|
type = with types; listOf str;
|
||
|
default = [];
|
||
|
};
|
||
|
|
||
|
devices = mkOption {
|
||
|
type = types.listOf types.str;
|
||
|
default = [];
|
||
|
description = "The devices to mount into the container, in the format '/dev/<host>:/dev/<container>'.";
|
||
|
};
|
||
|
|
||
|
autoupdate = mkOption {
|
||
|
type = with types; enum [
|
||
|
""
|
||
|
"registry"
|
||
|
"local"
|
||
|
];
|
||
|
default = "";
|
||
|
};
|
||
|
|
||
|
autostart = mkOption {
|
||
|
type = types.bool;
|
||
|
default = true;
|
||
|
};
|
||
|
|
||
|
addCapabilities = mkOption {
|
||
|
type = with types; listOf str;
|
||
|
default = [];
|
||
|
};
|
||
|
|
||
|
dropCapabilities = mkOption {
|
||
|
type = with types; listOf str;
|
||
|
default = [];
|
||
|
};
|
||
|
|
||
|
labels = mkOption {
|
||
|
type = with types; listOf str;
|
||
|
default = [];
|
||
|
};
|
||
|
|
||
|
extraOptions = mkOption {
|
||
|
type = with types; listOf str;
|
||
|
default = [];
|
||
|
};
|
||
|
|
||
|
extraContainerConfig = mkOption {
|
||
|
type = podman-lib.primitiveAttrs;
|
||
|
default = {};
|
||
|
example = literalExample ''
|
||
|
extraContainerConfig = {
|
||
|
UIDMap = "0:1000:1";
|
||
|
ReadOnlyTmpfs = true;
|
||
|
EnvironmentFile = [ /etc/environment /root/.env];
|
||
|
};
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
serviceConfig = mkOption {
|
||
|
type = podman-lib.serviceConfigType;
|
||
|
default = {};
|
||
|
};
|
||
|
|
||
|
unitConfig = mkOption {
|
||
|
type = podman-lib.unitConfigType;
|
||
|
default = {};
|
||
|
};
|
||
|
|
||
|
};
|
||
|
};
|
||
|
|
||
|
in {
|
||
|
|
||
|
imports = [
|
||
|
./options.nix
|
||
|
];
|
||
|
|
||
|
options.services.podman.containers = mkOption {
|
||
|
type = types.attrsOf containerDefinitionType;
|
||
|
default = {};
|
||
|
description = "Attribute set of container definitions.";
|
||
|
};
|
||
|
|
||
|
config = let
|
||
|
containerQuadlets = mapAttrsToList toQuadletInternal config.services.podman.containers;
|
||
|
in {
|
||
|
internal.podman-quadlet-definitions = containerQuadlets;
|
||
|
assertions = lib.flatten (map (container: container.assertions) config.internal.podman-quadlet-definitions);
|
||
|
|
||
|
# manifest file
|
||
|
home.file."${config.xdg.configHome}/podman/containers.manifest".text = podman-lib.generateManifestText containerQuadlets;
|
||
|
};
|
||
|
}
|