refactor: simplify host composition and shared feature config

This commit is contained in:
2026-04-22 03:29:19 +02:00
parent 5eec5689f4
commit 503c1fe9bc
16 changed files with 327 additions and 238 deletions
+54 -19
View File
@@ -1,5 +1,7 @@
{ lib, ... }:
let
nonEmptyStrType = lib.types.addCheck lib.types.str (value: lib.stringLength value > 0);
mkNullableOption =
type:
lib.mkOption {
@@ -10,9 +12,26 @@ let
hasSinglePrimaryEmail =
user: builtins.length (lib.filter (email: email.primary) (builtins.attrValues user.emails)) == 1;
scopeEmailCount =
scope: user:
builtins.length (lib.filter (email: email.scope == scope) (builtins.attrValues user.emails));
hasAtMostOneScopedEmail = scope: user: scopeEmailCount scope user <= 1;
requiredSourceControlScopes =
user:
lib.unique [
"personal"
user.sourceControl.projectScope
];
hasRequiredScopedEmail =
scope: user: scopeEmailCount scope user == 1;
primaryEmailFallback = {
address = "";
primary = false;
scope = null;
type = "";
};
@@ -53,6 +72,8 @@ let
type = lib.mkOption {
type = lib.types.str;
};
scope = mkNullableOption sourceControlScopeType;
};
}
);
@@ -62,13 +83,11 @@ let
{
options = {
publicKey = lib.mkOption {
type = lib.types.str;
};
privateKeyPath = lib.mkOption {
type = lib.types.nullOr lib.types.str;
type = lib.types.nullOr nonEmptyStrType;
default = null;
};
privateKeyPath = mkNullableOption nonEmptyStrType;
};
}
);
@@ -93,22 +112,10 @@ let
}
);
sourceControlProfileType = lib.types.submodule (
{ ... }:
{
options = { };
}
);
sourceControlType = lib.types.submodule (
{ ... }:
{
options = {
profiles = lib.mkOption {
type = lib.types.attrsOf sourceControlProfileType;
default = { };
};
projectScope = lib.mkOption {
type = sourceControlScopeType;
default = "personal";
@@ -305,7 +312,22 @@ in
config.assertions = lib.mapAttrsToList (userName: user: {
assertion = hasSinglePrimaryEmail user;
message = "User `${userName}` must define exactly one primary email entry.";
}) config.meta.host.users;
}) config.meta.host.users
++ lib.flatten (
lib.mapAttrsToList (userName: user:
(map (scope: {
assertion = hasAtMostOneScopedEmail scope user;
message = "User `${userName}` may define at most one `${scope}` scoped email entry.";
}) [
"personal"
"work"
])
++ map (scope: {
assertion = hasRequiredScopedEmail scope user;
message = "User `${userName}` must define exactly one `${scope}` scoped email entry.";
}) (requiredSourceControlScopes user)
) config.meta.host.users
);
};
flake.modules.homeManager.meta =
@@ -326,6 +348,19 @@ in
config.assertions = lib.optional (config.meta.user != null) {
assertion = hasSinglePrimaryEmail config.meta.user;
message = "User `${config.meta.user.name}` must define exactly one primary email entry.";
};
}
++ lib.optionals (config.meta.user != null) (
(map (scope: {
assertion = hasAtMostOneScopedEmail scope config.meta.user;
message = "User `${config.meta.user.name}` may define at most one `${scope}` scoped email entry.";
}) [
"personal"
"work"
])
++ map (scope: {
assertion = hasRequiredScopedEmail scope config.meta.user;
message = "User `${config.meta.user.name}` must define exactly one `${scope}` scoped email entry.";
}) (requiredSourceControlScopes config.meta.user)
);
};
}