mirror of
https://github.com/nix-community/home-manager.git
synced 2025-11-08 19:46:05 +01:00
221 lines
6.7 KiB
Nix
221 lines
6.7 KiB
Nix
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
inherit (lib)
|
|
concatStringsSep
|
|
isList
|
|
mapAttrsToList
|
|
types
|
|
;
|
|
|
|
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 = lib.generators.toINI {
|
|
listsAsDuplicateKeys = true;
|
|
mkKeyValue = normalizeKeyValue;
|
|
};
|
|
|
|
# meant for ini. favours b when two values are unmergeable
|
|
deepMerge =
|
|
a: b:
|
|
lib.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 lib.isAttrs aVal && lib.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 = {
|
|
Build = {
|
|
ImageTag = with types; listOf str;
|
|
};
|
|
Container = {
|
|
ContainerName = types.enum [ quadletName ];
|
|
};
|
|
Network = {
|
|
NetworkName = types.enum [ quadletName ];
|
|
};
|
|
Volume = {
|
|
VolumeName = types.enum [ quadletName ];
|
|
};
|
|
};
|
|
|
|
# Function to build assertions for a specific section and its attributes.
|
|
buildSectionAsserts =
|
|
section: attrs:
|
|
if builtins.hasAttr section configRules then
|
|
lib.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
|
|
[ ];
|
|
|
|
checkImageTag =
|
|
extraConfig:
|
|
let
|
|
imageTags = (extraConfig.Build or { }).ImageTag or [ ];
|
|
containsRequiredTag = builtins.elem "homemanager/${quadletName}" imageTags;
|
|
imageTagsStr = lib.concatMapStringsSep ''" "'' toString imageTags;
|
|
in
|
|
[
|
|
{
|
|
assertion = imageTags == [ ] || containsRequiredTag;
|
|
message = ''In '${quadletName}' config. Build.ImageTag: '[ "${imageTagsStr}" ]' does not contain 'homemanager/${quadletName}'.'';
|
|
}
|
|
];
|
|
# Flatten assertions from all sections in `extraConfig`.
|
|
in
|
|
lib.flatten (
|
|
lib.concatLists [
|
|
(mapAttrsToList buildSectionAsserts extraConfig)
|
|
(checkImageTag 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 = lib.unique (map (quadlet: quadlet.resourceType) quadlets);
|
|
# if quadletTypes is > 1, then all quadlets are not the same type
|
|
allQuadletsSameType = lib.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 = lib.removePrefix "podman-" quadlet.serviceName;
|
|
# specific logic for writing the unit name goes here. It should be
|
|
# identical to what `podman <resource> ls` shows
|
|
in
|
|
{
|
|
"build" = "localhost/homemanager/${strippedName}";
|
|
"container" = strippedName;
|
|
"network" = strippedName;
|
|
"volume" = 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 = lib.splitString "\n" text;
|
|
nonEmptyLines = lib.filter (line: line != "") lines;
|
|
in
|
|
concatStringsSep "\n" nonEmptyLines;
|
|
|
|
awaitPodmanUnshare = pkgs.writeShellScript "await-podman-unshare" ''
|
|
until ${config.services.podman.package}/bin/podman unshare ${pkgs.coreutils}/bin/true; do
|
|
${pkgs.coreutils}/bin/sleep 1
|
|
done
|
|
'';
|
|
}
|