{ pkgs, lib, outputs, inputs, config, ... }: let userOpts = { name, config, ... }: { options = with lib; { allowUnfree = mkOption { type = with types; bool; default = true; description = "Whether or not to allow unfree packages for this user."; }; username = mkOption { type = with types; nullOr str; default = null; description = "The username of the user, if undefined it uses the name of the attribute set."; }; homeDirectory = mkOption { type = with types; nullOr str; default = null; description = "The home directory of the user."; }; description = mkOption { type = with types; nullOr str; default = null; description = "The user's description. It is usually their full name."; }; uid = mkOption { type = with types; nullOr int; default = null; description = "The user id of the user."; }; initialPassword = mkOption { type = with types; nullOr str; default = null; description = "The initial password of the user."; }; hashedPassword = mkOption { type = with types; nullOr str; default = null; description = "The hashed password of the user."; }; initialHashedPassword = mkOption { type = with types; nullOr str; default = null; description = "The initial hashed password of the user."; }; normalUser = mkOption { type = with types; bool; default = false; description = "Whether the user is a system user."; }; desktopEnvironment = mkOption { type = with types; nullOr (enum ["gnome" "hyprland" "bspwm"]); default = null; description = "The desktop environment of the user."; }; developer = mkOption { type = with types; bool; default = false; description = "Whether or not the user is a developer."; }; sudoer = mkOption { type = with types; bool; default = false; description = "Whether or not the user has access to sudo."; }; obs = mkOption { type = with types; bool; default = false; description = "Whether or not the user requires OBS."; }; authorizedKeys = mkOption { type = with types; listOf str; default = []; description = "List of Authorized SSH Keys for the user."; }; extraGroups = mkOption { type = with types; listOf str; default = []; description = "Extra groups to put the user in."; }; packages = mkOption { type = with types; listOf package; default = []; description = "The user's packages."; }; shell = mkOption { type = with types; enum ["bash" "zsh"]; default = "zsh"; description = "The user's shell."; }; }; config = with lib; mkMerge [ { shell = mkDefault "zsh"; } ]; }; in { options = with lib; { user = mkOption { type = with types; attrsOf (submodule userOpts); default = {}; example = { deva = { shell = "zsh"; sudoer = true; developer = true; desktopEnvironment = "gnome"; obs = true; name = "Deva Waves"; }; root = { shell = "zsh"; authorizedKeys = []; }; }; }; }; config = let mkIfNoValue = lib.mkOverride 1500; in { users.users = lib.mkIf (config.user != null) (lib.attrsets.mapAttrs (name: userConfig: { uid = ( if userConfig.uid != null then userConfig.uid else mkIfNoValue null ); initialPassword = ( if userConfig.initialPassword != null then userConfig.initialPassword else mkIfNoValue null ); hashedPassword = ( if userConfig.hashedPassword != null then userConfig.hashedPassword else mkIfNoValue null ); initialHashedPassword = ( if userConfig.initialHashedPassword != null then userConfig.initialHashedPassword else mkIfNoValue null ); description = ( if userConfig.description != null then userConfig.description else mkIfNoValue "${name}" ); shell = pkgs.${userConfig.shell}; openssh.authorizedKeys.keys = userConfig.authorizedKeys or []; isNormalUser = if userConfig.normalUser then true else mkIfNoValue false; name = if userConfig.username != null then userConfig.username else mkIfNoValue name; extraGroups = ( if userConfig.extraGroups != null then userConfig.extraGroups else mkIfNoValue [] ) ++ ( if userConfig.sudoer then ["wheel"] else [] ); }) config.user); home-manager.users = lib.mkIf (config.user != null) (lib.attrsets.mapAttrs (name: userConfig: { imports = [ inputs.nix-index-db.hmModules.nix-index ] ++ ( if name != "root" then [ ../services.nix ] else [] ) ++ ( if userConfig.developer then [ ../dev ] else [] ) ++ ( if userConfig.shell == "zsh" then [ ../shell/zsh.nix ] else [] ) ++ ( if userConfig.shell == "bash" then [ ../shell/bash.nix ] else [] ) ++ ( if userConfig.obs then [ { programs.obs-studio = { enable = true; plugins = with pkgs.obs-studio-plugins; [ wlrobs ]; }; } ] else [] ) ++ ( if userConfig.desktopEnvironment == "gnome" then [ ../desktop/gnome.nix ] else [] ) ++ ( if userConfig.desktopEnvironment == "hyprland" then [ ../desktop/hyprland.nix ] else [] ) ++ ( if userConfig.desktopEnvironment == "bspwm" then [ ../desktop/bspwm.nix ] else [] ); home = { username = if userConfig.username != null then userConfig.username else mkIfNoValue name; homeDirectory = if userConfig.homeDirectory != null then userConfig.homeDirectory else mkIfNoValue "/home/${( if userConfig.username != null then userConfig.username else name )}"; extraOutputsToInstall = ["doc" "devdoc"]; packages = [ inputs.nh.packages.x86_64-linux.default ] ++ userConfig.packages or []; sessionVariables = { NIX_AUTO_RUN = "1"; FLAKE = "${config.home-manager.users.${name}.home.homeDirectory}/nix"; }; stateVersion = "24.05"; }; nixpkgs = if config.home-manager.useGlobalPkgs != true then { overlays = [ outputs.overlays.additions outputs.overlays.modifications outputs.overlays.unstable-packages ]; config = { allowUnfree = if userConfig.allowUnfree != true then false else mkIfNoValue true; }; } else mkIfNoValue {}; nix.package = lib.mkForce pkgs.unstable.nixVersions.latest; nix.extraOptions = '' experimental-features = nix-command flakes ''; manual = { html.enable = false; json.enable = false; manpages.enable = false; }; programs.home-manager.enable = true; programs.git.enable = true; programs.nix-index.enable = true; programs.nix-index.symlinkToCacheHome = true; programs.nix-index-database.comma.enable = true; systemd.user.startServices = "sd-switch"; }) config.user); }; }