{ lib, ... }: let nonEmptyStrType = lib.types.addCheck lib.types.str (value: lib.stringLength value > 0); mkNullableOption = type: lib.mkOption { type = lib.types.nullOr type; default = null; }; 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 = ""; }; sourceControlScopeType = lib.types.enum [ "personal" "work" ]; pointerAccelProfileType = lib.types.enum [ "adaptive" "flat" ]; pointerScrollMethodType = lib.types.enum [ "no-scroll" "two-finger" "edge" "on-button-down" ]; touchpadClickMethodType = lib.types.enum [ "button-areas" "clickfinger" ]; emailType = lib.types.submodule ( { ... }: { options = { address = lib.mkOption { type = lib.types.str; }; primary = lib.mkOption { type = lib.types.bool; }; type = lib.mkOption { type = lib.types.str; }; scope = mkNullableOption sourceControlScopeType; }; } ); sourceControlHostKeyType = lib.types.submodule ( { ... }: { options = { publicKey = lib.mkOption { type = lib.types.nullOr nonEmptyStrType; default = null; }; privateKeyPath = mkNullableOption nonEmptyStrType; }; } ); sourceControlHostUserType = lib.types.submodule ( { ... }: { options = { personal = mkNullableOption sourceControlHostKeyType; work = mkNullableOption sourceControlHostKeyType; }; } ); hostSourceControlType = lib.types.submodule ( { ... }: { options.users = lib.mkOption { type = lib.types.attrsOf sourceControlHostUserType; default = { }; }; } ); sourceControlType = lib.types.submodule ( { ... }: { options = { projectScope = lib.mkOption { type = sourceControlScopeType; default = "personal"; }; }; } ); userType = lib.types.submodule ( { config, ... }: let primaryEmails = lib.filter (email: email.primary) (builtins.attrValues config.emails); primaryEmail = if primaryEmails == [ ] then primaryEmailFallback else builtins.head primaryEmails; in { options = { name = lib.mkOption { type = lib.types.str; }; realName = lib.mkOption { type = lib.types.str; }; homeDirectory = lib.mkOption { type = lib.types.str; }; terminalPackagePath = lib.mkOption { type = lib.types.listOf lib.types.str; }; nixosConfigurationPath = lib.mkOption { type = lib.types.str; default = "${config.homeDirectory}/.config/nixos"; }; emails = lib.mkOption { type = lib.types.attrsOf emailType; }; primaryEmail = lib.mkOption { type = emailType; readOnly = true; description = "Derived primary email entry for this user."; }; sourceControl = lib.mkOption { type = sourceControlType; default = { }; }; }; config.primaryEmail = primaryEmail; } ); displayModeType = lib.types.submodule ( { ... }: { options = { width = lib.mkOption { type = lib.types.int; }; height = lib.mkOption { type = lib.types.int; }; refresh = lib.mkOption { type = lib.types.float; }; }; } ); displayType = lib.types.submodule ( { ... }: { options = { primary = lib.mkOption { type = lib.types.bool; default = false; }; x = lib.mkOption { type = lib.types.int; }; y = lib.mkOption { type = lib.types.int; }; scale = lib.mkOption { type = lib.types.nullOr lib.types.float; default = null; }; mode = lib.mkOption { type = lib.types.nullOr displayModeType; default = null; }; }; } ); mouseInputType = lib.types.submodule ( { ... }: { options = { accelProfile = mkNullableOption (pointerAccelProfileType); accelSpeed = mkNullableOption lib.types.float; leftHanded = mkNullableOption lib.types.bool; middleEmulation = mkNullableOption lib.types.bool; naturalScrolling = mkNullableOption lib.types.bool; scrollMethod = mkNullableOption pointerScrollMethodType; }; } ); touchpadInputType = lib.types.submodule ( { ... }: { options = { accelProfile = mkNullableOption (pointerAccelProfileType); accelSpeed = mkNullableOption lib.types.float; clickMethod = mkNullableOption touchpadClickMethodType; disableWhileTyping = mkNullableOption lib.types.bool; leftHanded = mkNullableOption lib.types.bool; middleEmulation = mkNullableOption lib.types.bool; naturalScrolling = mkNullableOption lib.types.bool; scrollMethod = mkNullableOption pointerScrollMethodType; tapping = mkNullableOption lib.types.bool; }; } ); inputType = lib.types.submodule ( { ... }: { options = { mouse = lib.mkOption { type = mouseInputType; default = { }; }; touchpad = lib.mkOption { type = touchpadInputType; default = { }; }; }; } ); hostType = lib.types.submodule ( { ... }: { options = { name = lib.mkOption { type = lib.types.str; }; displays = lib.mkOption { type = lib.types.attrsOf displayType; default = { }; }; input = lib.mkOption { type = inputType; default = { }; }; users = lib.mkOption { type = lib.types.attrsOf userType; default = { }; }; sourceControl = lib.mkOption { type = hostSourceControlType; default = { }; }; }; } ); in { flake.modules.nixos.meta = { config, ... }: { options.meta.host = lib.mkOption { type = hostType; }; config.assertions = lib.mapAttrsToList (userName: user: { assertion = hasSinglePrimaryEmail user; message = "User `${userName}` must define exactly one primary email entry."; }) 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 = { config, ... }: { options.meta = { host = lib.mkOption { type = lib.types.nullOr hostType; default = null; }; user = lib.mkOption { type = lib.types.nullOr userType; default = null; }; }; 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) ); }; }