mirror of
https://github.com/nix-community/home-manager
synced 2024-12-25 19:29:47 +01:00
1743615b61
Adds a new Podman module for creating user containers and networks as systemd services. These are installed to the user's `$XDG_CONFIG/systemd/user` directory.
136 lines
5.2 KiB
Nix
136 lines
5.2 KiB
Nix
{ lib, config, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
normalizeKeyValue = k: v:
|
|
let
|
|
v' = (if builtins.isBool v then
|
|
(if v then "true" else "false")
|
|
else if builtins.isAttrs v then
|
|
(concatStringsSep ''
|
|
|
|
${k}='' (mapAttrsToList normalizeKeyValue v))
|
|
else
|
|
builtins.toString v);
|
|
in if builtins.isNull v then "" else "${k}=${v'}";
|
|
|
|
primitiveAttrs = with types; attrsOf (either primitive (listOf primitive));
|
|
primitiveList = with types; listOf primitive;
|
|
primitive = with types; nullOr (oneOf [ bool int str path ]);
|
|
|
|
toQuadletIni = generators.toINI {
|
|
listsAsDuplicateKeys = true;
|
|
mkKeyValue = normalizeKeyValue;
|
|
};
|
|
|
|
# meant for ini. favours b when two values are unmergeable
|
|
deepMerge = a: b:
|
|
foldl' (result: key:
|
|
let
|
|
aVal = if builtins.hasAttr key a then a.${key} else null;
|
|
bVal = if builtins.hasAttr key b then b.${key} else null;
|
|
|
|
# check if the types inside a list match the type of a primitive
|
|
listMatchesType = (list: val:
|
|
isList list && builtins.length list > 0
|
|
&& builtins.typeOf (builtins.head list) == builtins.typeOf val);
|
|
in if isAttrs aVal && isAttrs bVal then
|
|
result // { ${key} = deepMerge aVal bVal; }
|
|
else if isList aVal && isList bVal then
|
|
result // { ${key} = aVal ++ bVal; }
|
|
else if aVal == bVal then
|
|
result // { ${key} = aVal; }
|
|
else if aVal == null then
|
|
result // { ${key} = bVal; }
|
|
else if bVal == null then
|
|
result // { ${key} = aVal; }
|
|
else if isList aVal && listMatchesType aVal bVal then
|
|
result // { ${key} = aVal ++ [ bVal ]; }
|
|
else if isList bVal && listMatchesType bVal aVal then
|
|
result // { ${key} = [ aVal ] ++ bVal; }
|
|
else if builtins.typeOf aVal == builtins.typeOf bVal then
|
|
result // { ${key} = bVal; }
|
|
else
|
|
result // { ${key} = bVal; }) a (builtins.attrNames b);
|
|
in {
|
|
inherit primitiveAttrs;
|
|
inherit primitiveList;
|
|
inherit primitive;
|
|
inherit toQuadletIni;
|
|
inherit deepMerge;
|
|
|
|
buildConfigAsserts = quadletName: extraConfig:
|
|
let
|
|
configRules = {
|
|
Container = { ContainerName = types.enum [ quadletName ]; };
|
|
Network = { NetworkName = types.enum [ quadletName ]; };
|
|
};
|
|
|
|
# Function to build assertions for a specific section and its attributes.
|
|
buildSectionAsserts = section: attrs:
|
|
if builtins.hasAttr section configRules then
|
|
flatten (mapAttrsToList (attrName: attrValue:
|
|
if builtins.hasAttr attrName configRules.${section} then [{
|
|
assertion = configRules.${section}.${attrName}.check attrValue;
|
|
message = "In '${quadletName}' config. ${section}.${attrName}: '${
|
|
toString attrValue
|
|
}' does not match expected type: ${
|
|
configRules.${section}.${attrName}.description
|
|
}";
|
|
}] else
|
|
[ ]) attrs)
|
|
else
|
|
[ ];
|
|
|
|
# Flatten assertions from all sections in `extraConfig`.
|
|
in flatten (mapAttrsToList buildSectionAsserts extraConfig);
|
|
|
|
extraConfigType = with types;
|
|
attrsOf (attrsOf (oneOf [ primitiveAttrs primitiveList primitive ]));
|
|
|
|
# input expects a list of quadletInternalType with all the same resourceType
|
|
generateManifestText = quadlets:
|
|
let
|
|
# create a list of all unique quadlet.resourceType in quadlets
|
|
quadletTypes = unique (map (quadlet: quadlet.resourceType) quadlets);
|
|
# if quadletTypes is > 1, then all quadlets are not the same type
|
|
allQuadletsSameType = length quadletTypes <= 1;
|
|
|
|
# ensures the service name is formatted correctly to be easily read
|
|
# by the activation script and matches `podman <resource> ls` output
|
|
formatServiceName = quadlet:
|
|
let
|
|
# remove the podman- prefix from the service name string
|
|
strippedName =
|
|
builtins.replaceStrings [ "podman-" ] [ "" ] quadlet.serviceName;
|
|
# specific logic for writing the unit name goes here. It should be
|
|
# identical to what `podman <resource> ls` shows
|
|
in {
|
|
"container" = strippedName;
|
|
"network" = strippedName;
|
|
}."${quadlet.resourceType}";
|
|
in if allQuadletsSameType then ''
|
|
${concatStringsSep "\n"
|
|
(map (quadlet: formatServiceName quadlet) quadlets)}
|
|
'' else
|
|
abort ''
|
|
All quadlets must be of the same type.
|
|
Quadlet types in this manifest: ${concatStringsSep ", " quadletTypes}
|
|
'';
|
|
|
|
# podman requires setuid on newuidmad, so it cannot be provided by pkgs.shadow
|
|
# Including all possible locations in PATH for newuidmap is a workaround.
|
|
# NixOS provides a 'wrapped' variant at /run/wrappers/bin/newuidmap.
|
|
# Other distros must install the 'uidmap' package, ie for ubuntu: apt install uidmap.
|
|
# Extra paths are added to handle where distro package managers may put the uidmap binaries.
|
|
#
|
|
# Tracking for a potential solution: https://github.com/NixOS/nixpkgs/issues/138423
|
|
newuidmapPaths = "/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
|
|
removeBlankLines = text:
|
|
let
|
|
lines = splitString "\n" text;
|
|
nonEmptyLines = filter (line: line != "") lines;
|
|
in concatStringsSep "\n" nonEmptyLines;
|
|
}
|