{ 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; scopeEmailAddress = scope: user: let emails = lib.filter (email: email.scope == scope) (builtins.attrValues user.emails); in if emails == [ ] then "" else (builtins.head emails).address; primaryEmailFallback = { address = ""; primary = false; scope = null; type = "mxrouting"; }; emailProviderType = lib.types.enum [ "mxrouting" "office365" ]; 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; default = false; }; type = lib.mkOption { type = emailProviderType; }; 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"; }; scopes = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule ( { ... }: { options.email = lib.mkOption { type = lib.types.str; readOnly = true; }; } ) ); readOnly = true; description = "Derived source-control identities keyed by scope."; }; }; } ); 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; }; 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; sourceControl.scopes = lib.genAttrs (requiredSourceControlScopes config) (scope: { email = scopeEmailAddress scope config; }); }; } ); 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 = { }; }; }; } ); mkUserEmailAssertions = userName: user: [ { assertion = hasSinglePrimaryEmail user; message = "User `${userName}` must define exactly one primary email entry."; } ] ++ (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); in { flake.modules.nixos.meta = { config, ... }: { options.meta.host = lib.mkOption { type = hostType; }; config.assertions = lib.flatten (lib.mapAttrsToList mkUserEmailAssertions 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.optionals (config.meta.user != null) ( mkUserEmailAssertions config.meta.user.name config.meta.user ); }; }