mirror of
https://github.com/nix-community/home-manager
synced 2024-06-14 02:33:38 +02:00
refactor + allow primitive types and 'escape hatch' config values with extensible validation rules
This commit is contained in:
parent
5d8cdba815
commit
721f458d04
|
@ -333,7 +333,7 @@ let
|
|||
./services/plan9port.nix
|
||||
./services/playerctld.nix
|
||||
./services/plex-mpv-shim.nix
|
||||
./services/podman-linux/podman.nix
|
||||
./services/podman-linux/default.nix
|
||||
./services/polybar.nix
|
||||
./services/poweralertd.nix
|
||||
./services/pueue.nix
|
||||
|
|
|
@ -2,87 +2,104 @@
|
|||
|
||||
with lib;
|
||||
|
||||
|
||||
let
|
||||
createQuadletSource = name: containerDef:
|
||||
let
|
||||
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 "";
|
||||
|
||||
serviceName = if containerDef.serviceName != null then containerDef.serviceName else name;
|
||||
containerName = name; # Use the submodule name as the container name
|
||||
podman-lib = import ./podman-lib.nix { inherit lib; };
|
||||
|
||||
formatEnvironment = env:
|
||||
if env != {} then
|
||||
concatStringsSep " " (mapAttrsToList (k: v: "${k}=${v}") env)
|
||||
else
|
||||
"";
|
||||
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;
|
||||
###
|
||||
|
||||
formatPorts = ports:
|
||||
if ports != [] then
|
||||
concatStringsSep "\n" (map (port: "PublishPort=${port}") ports)
|
||||
else
|
||||
"";
|
||||
### 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 "";
|
||||
###
|
||||
|
||||
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
|
||||
"";
|
||||
### Formatters
|
||||
formatExtraConfig = podman-lib.formatExtraConfig;
|
||||
|
||||
formatCapabilities = action: capabilities:
|
||||
if capabilities != [] then
|
||||
concatStringsSep "\n" (map (capability: "${action}Capability=${capability}") capabilities)
|
||||
else
|
||||
"";
|
||||
formatNetworkDependencies = networks:
|
||||
let
|
||||
formatElement = network: "podman-${network}-network.service";
|
||||
in
|
||||
concatStringsSep " " (map formatElement networks);
|
||||
|
||||
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
|
||||
"";
|
||||
|
||||
formatUnitTags = tagList:
|
||||
if tagList != [] then
|
||||
concatStringsSep " " (map (tag: "${tag}") tagList)
|
||||
else
|
||||
"";
|
||||
|
||||
formatExtraArgs = containerDef:
|
||||
let
|
||||
networkAliasArg = if containerDef.networkAlias != null then "--network-alias ${containerDef.networkAlias}" else "";
|
||||
entrypointArg = if containerDef.entrypoint != null then "--entrypoint '${containerDef.entrypoint}'" else "";
|
||||
allArgs = [networkAliasArg entrypointArg] ++ containerDef.extraOptions;
|
||||
in
|
||||
if allArgs != [] && allArgs != [""] then
|
||||
"PodmanArgs=${concatStringsSep " " (filter (arg: arg != null && arg != "") allArgs)}"
|
||||
formatEnvironment = env:
|
||||
if env != {} then
|
||||
concatStringsSep " " (mapAttrsToList (k: v: "${k}=${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
|
||||
"";
|
||||
|
||||
formatUnitTags = tagList:
|
||||
if tagList != [] then
|
||||
concatStringsSep " " (map (tag: "${tag}") tagList)
|
||||
else
|
||||
"";
|
||||
|
||||
formatPodmanArgs = containerDef:
|
||||
let
|
||||
networkAliasArg = if containerDef.networkAlias != null then "--network-alias ${containerDef.networkAlias}" else "";
|
||||
entrypointArg = if containerDef.entrypoint != null then "--entrypoint '${containerDef.entrypoint}'" else "";
|
||||
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
|
||||
|
@ -90,8 +107,9 @@ let
|
|||
# ${serviceName}.container
|
||||
[Unit]
|
||||
Description=${if containerDef.description != null then containerDef.description else "Service for container ${containerName}"}
|
||||
After=network.target ${formatUnitTags containerDef.unitConfig.After}
|
||||
${ifNotEmptyList containerDef.networks "After=podman-networks-hm.service"}
|
||||
After=network.target
|
||||
${ifNotEmptyList containerDef.networks "After=${formatNetworkDependencies containerDef.networks}"}
|
||||
${formatExtraConfig mergedUnitConfig}
|
||||
|
||||
[Container]
|
||||
ContainerName=${containerName}
|
||||
|
@ -112,13 +130,12 @@ let
|
|||
${ifNotEmptyList containerDef.addCapabilities (formatCapabilities "Add" containerDef.addCapabilities)}
|
||||
${ifNotEmptyList containerDef.dropCapabilities (formatCapabilities "Drop" containerDef.dropCapabilities)}
|
||||
${ifNotEmptyList containerDef.labels (formatLabels containerDef.labels)}
|
||||
${formatExtraArgs containerDef}
|
||||
${formatPodmanArgs containerDef}
|
||||
${formatExtraConfig containerDef.extraContainerConfig}
|
||||
|
||||
[Service]
|
||||
Environment="PATH=/run/wrappers/bin:/run/current-system/sw/bin:${config.home.homeDirectory}/.nix-profile/bin"
|
||||
Restart=${containerDef.serviceConfig.Restart}
|
||||
TimeoutStopSec=${toString containerDef.serviceConfig.TimeoutStopSec}
|
||||
${ifNotNull containerDef.serviceConfig.ExecStartPre "ExecStartPre=${containerDef.serviceConfig.ExecStartPre}"}
|
||||
${formatExtraConfig mergedServiceConfig}
|
||||
|
||||
[Install]
|
||||
${if containerDef.autostart then "WantedBy=multi-user.target default.target" else ""}
|
||||
|
@ -135,11 +152,16 @@ let
|
|||
removeBlankLines configText;
|
||||
|
||||
toQuadletInternal = name: containerDef:
|
||||
{
|
||||
serviceName = if containerDef.serviceName != null then containerDef.serviceName else "podman-${name}";
|
||||
source = createQuadletSource name containerDef;
|
||||
unitType = "container";
|
||||
};
|
||||
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
|
||||
|
@ -264,41 +286,33 @@ let
|
|||
default = [];
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
TimeoutStopSec = mkOption {
|
||||
type = types.int;
|
||||
default = 30;
|
||||
};
|
||||
|
||||
ExecStartPre = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
Restart = mkOption {
|
||||
type = types.enum [
|
||||
"no"
|
||||
"always"
|
||||
"on-failure"
|
||||
"unless-stopped"
|
||||
];
|
||||
default = "always";
|
||||
description = "The restart policy of the container.";
|
||||
};
|
||||
};
|
||||
|
||||
unitConfig = {
|
||||
After = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
type = types.listOf types.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 = {};
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -316,5 +330,6 @@ in {
|
|||
|
||||
config = {
|
||||
internal.podman-quadlet-definitions = mapAttrsToList toQuadletInternal config.services.podman.containers;
|
||||
assertions = lib.flatten (map (container: container.assertions) config.internal.podman-quadlet-definitions);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
{ pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
meta.maintainers = [ maintainers.n-hass ];
|
||||
|
||||
imports =
|
||||
imports =
|
||||
[ ./services.nix ./networks.nix ./containers.nix ./install-quadlet.nix ];
|
||||
|
||||
config = {
|
||||
assertions =
|
||||
[ (lib.hm.assertions.assertPlatform "podman" pkgs lib.platforms.linux) ];
|
||||
};
|
||||
}
|
||||
}
|
|
@ -2,18 +2,15 @@
|
|||
|
||||
with lib;
|
||||
|
||||
# Helper function to safely escape strings
|
||||
let
|
||||
podman-lib = import ./podman-lib.nix { inherit lib; };
|
||||
|
||||
createQuadletSource = name: networkDef:
|
||||
let
|
||||
formatNetworkOption = k: v: "${k}=${v}";
|
||||
networkOptions = mapAttrsToList formatNetworkOption networkDef;
|
||||
in
|
||||
''
|
||||
# Automatically generated by home-manager for podman network configuration
|
||||
# DO NOT EDIT THIS FILE DIRECTLY
|
||||
[Network]
|
||||
${concatStringsSep "\n" networkOptions}
|
||||
${podman-lib.formatExtraConfig networkDef}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
|
@ -21,7 +18,7 @@ let
|
|||
|
||||
toQuadletInternal = name: networkDef:
|
||||
{
|
||||
serviceName = "podman-network-${name}";
|
||||
serviceName = "podman-${name}"; # becomes podman-<netname>-network.service because of quadlet
|
||||
source = createQuadletSource name networkDef;
|
||||
unitType = "network";
|
||||
};
|
||||
|
@ -30,7 +27,7 @@ in
|
|||
{
|
||||
options = {
|
||||
services.podman.networks = mkOption {
|
||||
type = types.attrsOf (types.attrsOf types.str);
|
||||
type = types.attrsOf (podman-lib.primitiveAttrs);
|
||||
default = {};
|
||||
example = literalExample ''
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{lib, ...}:
|
||||
|
||||
let
|
||||
# Define the type which the systemd services will be derived from
|
||||
# Define the systemd service type
|
||||
quadletInternalType = lib.types.submodule {
|
||||
options = {
|
||||
serviceName = lib.mkOption {
|
||||
|
@ -19,8 +19,14 @@ let
|
|||
type = lib.types.str;
|
||||
description = "The quadlet source file content.";
|
||||
};
|
||||
|
||||
assertions = lib.mkOption {
|
||||
type = with lib.types; listOf unspecified;
|
||||
default = [];
|
||||
description = "List of Nix type assertions.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
options.internal.podman-quadlet-definitions = lib.mkOption {
|
||||
type = lib.types.listOf quadletInternalType;
|
||||
|
|
58
modules/services/podman-linux/podman-lib.nix
Normal file
58
modules/services/podman-linux/podman-lib.nix
Normal file
|
@ -0,0 +1,58 @@
|
|||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
primitive = with types; nullOr (oneOf [ bool int str path ]);
|
||||
primitiveAttrs = with types; attrsOf (either primitive (listOf primitive));
|
||||
|
||||
formatPrimitiveValue = value:
|
||||
if isBool value then
|
||||
(if value then "true" else "false")
|
||||
else if isList value then
|
||||
concatStringsSep " " (map toString value)
|
||||
else
|
||||
toString value;
|
||||
in {
|
||||
primitive = primitive; # export
|
||||
primitiveAttrs = primitiveAttrs; # export
|
||||
|
||||
serviceConfigTypeRules = {
|
||||
Restart = types.enum [ "no" "always" "on-failure" "unless-stopped" ];
|
||||
TimeoutStopSec = types.int;
|
||||
};
|
||||
serviceConfigDefaults = {
|
||||
Restart = "always";
|
||||
TimeoutStopSec = 30;
|
||||
ExecStartPre = null;
|
||||
};
|
||||
serviceConfigType = with types; attrsOf (either primitive (listOf primitive));
|
||||
|
||||
unitConfigTypeRules = {
|
||||
After = with types; nullOr (listOf str);
|
||||
};
|
||||
unitConfigDefaults = {
|
||||
After = null;
|
||||
};
|
||||
unitConfigType = with types; attrsOf (either primitive (listOf primitive));
|
||||
|
||||
assertConfigTypes = configTypeRules: config: containerName:
|
||||
lib.flatten (lib.mapAttrsToList (name: value:
|
||||
if lib.hasAttr name configTypeRules then
|
||||
[{
|
||||
assertion = configTypeRules.${name}.check value;
|
||||
message = "in '${containerName}' config. ${name}: '${toString value}' does not match expected type: ${configTypeRules.${name}.description}";
|
||||
}]
|
||||
else []
|
||||
) config);
|
||||
|
||||
formatPrimitiveValue = formatPrimitiveValue; # export
|
||||
|
||||
formatExtraConfig = extraConfig:
|
||||
let
|
||||
nonNullConfig = lib.filterAttrs (name: value: value != null) extraConfig;
|
||||
in
|
||||
concatStringsSep "\n" (
|
||||
mapAttrsToList (name: value: "${name}=${formatPrimitiveValue value}") nonNullConfig
|
||||
);
|
||||
}
|
|
@ -18,7 +18,6 @@ in {
|
|||
type = types.str;
|
||||
default = "Sun *-*-* 00:00";
|
||||
description = "Systemd OnCalendar expression for the update";
|
||||
example = "Sun *-*-* 00:00";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user