diff --git a/docs/USER_CONFIGURATION.md b/docs/USER_CONFIGURATION.md index 0e7321d..820ae64 100644 --- a/docs/USER_CONFIGURATION.md +++ b/docs/USER_CONFIGURATION.md @@ -90,8 +90,8 @@ username = { "ssh-rsa AAAA... user@otherhost" ]; - # === Home Configuration === - home = builtins.fetchGit { ... }; # External home-manager config (see below) + # === External Configuration === + external = builtins.fetchGit { ... }; # External user module (see below) # OR (if not using external config): homePackages = with pkgs; [ # User packages @@ -120,12 +120,13 @@ In `users.nix`: ```nix myuser = { + # Basic options can be set here OR in the external module's user.nix description = "My Name"; extraGroups = [ "wheel" ]; hashedPassword = "$6$..."; - # Point to external dotfiles repository - home = builtins.fetchGit { + # Point to external configuration repository + external = builtins.fetchGit { url = "https://github.com/username/dotfiles"; rev = "abc123..."; # Pin to specific commit }; @@ -136,7 +137,7 @@ myuser = { ``` dotfiles/ -├── home.nix # Required: Home-manager configuration +├── user.nix # Optional: User options AND home-manager config ├── nixos.nix # Optional: System-level configuration └── config/ # Optional: Your dotfiles ├── bashrc @@ -144,13 +145,28 @@ dotfiles/ └── ... ``` -**home.nix (required):** +**Both `.nix` files are optional, but at least one should be present.** + +**user.nix (optional):** ```nix { inputs, ... }: -{ config, lib, pkgs, osConfig, ... }: +{ config, lib, pkgs, ... }: + { - home.packages = with pkgs; [ vim git htop ]; - + # User account options (imported as NixOS module) + ugaif.users.myuser = { + description = "My Full Name"; + extraGroups = [ "wheel" "docker" ]; + shell = pkgs.zsh; + useZshTheme = true; + }; + + # Home-manager configuration (imported into home-manager) + home.packages = with pkgs; [ + vim + ripgrep + ]; + programs.git = { enable = true; userName = "My Name"; @@ -174,12 +190,22 @@ dotfiles/ ### What External Modules Receive -**In home.nix:** +**In user.nix:** - `inputs` - Flake inputs (nixpkgs, home-manager, etc.) -- `config` - Home-manager configuration +- `config` - Configuration (NixOS or home-manager depending on import context) - `lib` - Nixpkgs library functions - `pkgs` - Package set -- `osConfig` - OS-level configuration (read-only) +- `osConfig` - (home-manager context only) OS-level configuration + +### How External Modules Are Loaded + +The `user.nix` module is used in two ways: + +1. **User Options (Data Extraction)**: The `ugaif.users.` options are extracted and loaded as **data**. The module is evaluated with minimal arguments to extract just the ugaif.users options, which override any defaults set in `users.nix` (which uses `lib.mkDefault`). + +2. **Home-Manager Configuration**: The entire module (including `home.*`, `programs.*`, `services.*` options) is imported into home-manager as a configuration module. + +This means you can define both user account settings AND home-manager configuration in a single file. **In nixos.nix:** - `inputs` - Flake inputs @@ -191,23 +217,21 @@ dotfiles/ **Local path (for testing):** ```nix -home = /home/username/dev/dotfiles; +external = /home/username/dev/dotfiles; ``` -**Inline configuration:** +**Note:** User options can be set in users.nix OR in the external module's user.nix file. + +**No external config:** ```nix -home = { - home.packages = with pkgs; [ vim ]; - programs.git.enable = true; +# Configure everything directly in users.nix +myuser = { + description = "My Name"; + homePackages = with pkgs; [ vim git ]; + # external is null by default }; ``` -**No external config (legacy):** -```nix -homePackages = with pkgs; [ vim git ]; -# home = null; # Default -``` - ### Create User Template ```bash diff --git a/hosts/default.nix b/hosts/default.nix index b0fac1d..12b4b33 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -51,14 +51,17 @@ let userNixosModulePaths = lib.filterAttrs (_: v: v != null) ( lib.mapAttrs ( name: user: - if (user ? home && user.home != null) then + if (user ? external && user.external != null) then let - homePath = - if builtins.isAttrs user.home && user.home ? outPath then user.home.outPath else user.home; - nixosModulePath = homePath + "/nixos.nix"; + externalPath = + if builtins.isAttrs user.external && user.external ? outPath then + user.external.outPath + else + user.external; + nixosModulePath = externalPath + "/nixos.nix"; in if - (builtins.isPath homePath || (builtins.isString homePath && lib.hasPrefix "/" homePath)) + (builtins.isPath externalPath || (builtins.isString externalPath && lib.hasPrefix "/" externalPath)) && builtins.pathExists nixosModulePath then nixosModulePath diff --git a/hosts/user-config.nix b/hosts/user-config.nix index 3618bbd..722e2f3 100644 --- a/hosts/user-config.nix +++ b/hosts/user-config.nix @@ -14,6 +14,58 @@ # and Home Manager configuration. let + # Load users.nix to get account definitions + usersData = import ../users.nix { inherit pkgs; }; + accounts = usersData.ugaif.users or { }; + + # Helper: Resolve external module path from fetchGit/fetchTarball/path + resolveExternalPath = + external: + if external == null then + null + else if builtins.isAttrs external && external ? outPath then + external.outPath + else + external; + + # Helper: Check if path exists and is valid + isValidPath = + path: + path != null + && (builtins.isPath path || (builtins.isString path && lib.hasPrefix "/" path)) + && builtins.pathExists path; + + # Extract ugaif.users options from external user.nix modules + # First, build a cache of options per user from their external user.nix (if any). + externalUserModuleOptions = lib.genAttrs (lib.attrNames accounts) ( + name: + let + user = accounts.${name}; + externalPath = resolveExternalPath (user.external or null); + userNixPath = if externalPath != null then externalPath + "/user.nix" else null; + in + if isValidPath userNixPath then + let + # Import and evaluate the module with minimal args + outerModule = import userNixPath { inherit inputs; }; + evaluatedModule = outerModule { + config = { }; + inherit lib pkgs; + osConfig = null; + }; + # Extract just the ugaif.users. options + ugaifUsers = evaluatedModule.ugaif.users or { }; + in + ugaifUsers.${name} or { } + else + { } + ); + + # externalUserOptions only contains users that actually have options defined + externalUserOptions = lib.filterAttrs ( + _: moduleOptions: moduleOptions != { } + ) externalUserModuleOptions; + # Submodule defining the structure of a user account userSubmodule = lib.types.submodule { options = { @@ -49,7 +101,7 @@ let type = lib.types.listOf lib.types.path; default = [ ]; }; - home = lib.mkOption { + external = lib.mkOption { type = lib.types.nullOr ( lib.types.oneOf [ lib.types.path @@ -59,10 +111,14 @@ let ); default = null; description = '' - External home-manager configuration. Can be: - - A path to a local module + External user configuration module. Can be: + - A path to a local module directory - A fetchGit/fetchTarball result pointing to a repository - - An attribute set with home-manager configuration + + The external module can contain: + - user.nix (optional): Sets ugaif.users. options AND home-manager config + - nixos.nix (optional): System-level NixOS configuration + Example: builtins.fetchGit { url = "https://github.com/user/dotfiles"; rev = "..."; } ''; }; @@ -100,6 +156,7 @@ let }; in { + options.ugaif.users = lib.mkOption { type = lib.types.attrsOf userSubmodule; default = { }; @@ -107,6 +164,19 @@ in }; config = { + # Merge user definitions from users.nix with options from external user.nix modules + # External options take precedence over users.nix (which uses lib.mkDefault) + ugaif.users = lib.mapAttrs ( + name: user: + user + // { + description = lib.mkDefault (user.description or null); + shell = lib.mkDefault (user.shell or null); + extraGroups = lib.mkDefault (user.extraGroups or [ ]); + } + // (externalUserOptions.${name} or { }) + ) accounts; + # Generate NixOS users users.users = let @@ -143,52 +213,63 @@ in in lib.mapAttrs ( name: user: - { ... }: let - # Check if user has external home configuration - hasExternalHome = user.home != null; + # Resolve external module paths + hasExternal = user.external != null; + externalPath = resolveExternalPath user.external; + userNixPath = if externalPath != null then externalPath + "/user.nix" else null; + hasExternalUser = isValidPath userNixPath; - # Extract path from fetchGit/fetchTarball if needed - externalHomePath = - if hasExternalHome then - if builtins.isAttrs user.home && user.home ? outPath then user.home.outPath else user.home - else - null; - - # Import external module if it's a path - externalHomeModule = - if - externalHomePath != null - && ( - builtins.isPath externalHomePath - || (builtins.isString externalHomePath && lib.hasPrefix "/" externalHomePath) - ) - then - import (externalHomePath + "/home.nix") { inherit inputs; } - else if builtins.isAttrs user.home && !(user.home ? outPath) then - user.home # Direct attrset configuration + # Import external user.nix for home-manager (filter out ugaif.* options) + externalUserModule = + if hasExternalUser then + let + fullModule = import userNixPath { inherit inputs; }; + in + # Only pass through non-ugaif options to home-manager + { + config, + lib, + pkgs, + osConfig, + ... + }: + let + evaluated = fullModule { + inherit + config + lib + pkgs + osConfig + ; + }; + in + lib.filterAttrs (attrName: _: attrName != "ugaif") evaluated else { }; - # Common imports based on flags + # Common imports based on user flags commonImports = lib.optional user.useZshTheme ../sw/theme.nix ++ [ (import ../sw/nvim.nix { inherit user; }) ]; + + # Build imports list + allImports = user.extraImports ++ commonImports ++ lib.optional hasExternalUser externalUserModule; in - if hasExternalHome then + lib.mkMerge [ { - # External users: Merge external config with common imports - imports = commonImports ++ [ externalHomeModule ]; - } - else - { - # Local users: Apply full configuration. - imports = user.extraImports ++ commonImports; + imports = allImports; + + # Always set these required options home.username = name; home.homeDirectory = if name == "root" then "/root" else "/home/${name}"; home.stateVersion = "25.11"; - home.packages = user.homePackages; } + (lib.mkIf (!hasExternal) { + # For local users only, add their packages + home.packages = user.homePackages; + }) + ] ) enabledAccounts; }; }; diff --git a/templates/user/README.md b/templates/user/README.md index 2845a78..bd1934e 100644 --- a/templates/user/README.md +++ b/templates/user/README.md @@ -10,12 +10,14 @@ External user modules allow users to maintain their personal configurations (dot ``` user-dotfiles-repo/ -├── home.nix # Required: Home-manager configuration +├── user.nix # Optional: User options AND home-manager configuration ├── nixos.nix # Optional: System-level NixOS configuration ├── README.md # Documentation └── dotfiles/ # Optional: Dotfiles to symlink ``` +**Note:** Both `.nix` files are optional, but at least one should be present for the module to be useful. + ## Usage ### 1. Create Your User Configuration Repository @@ -30,24 +32,22 @@ Copy the templates from this directory to your own Git repository: { ugaif.users = { myusername = { + # Option 1: Set user options in users.nix description = "My Name"; extraGroups = [ "wheel" "networkmanager" ]; shell = pkgs.zsh; - # Option 1: External module from Git - home = builtins.fetchGit { + # Option 2: Or let the external module's user.nix set these options + + # Reference external dotfiles module + external = builtins.fetchGit { url = "https://github.com/username/dotfiles"; rev = "abc123def456..."; # Full commit hash for reproducibility ref = "main"; # Optional: branch/tag name }; - # Option 2: Local path for testing - # home = /path/to/local/dotfiles; - - # Option 3: Inline configuration - # home = { - # home.packages = [ pkgs.vim ]; - # programs.git.enable = true; + # Or use local path for testing + # external = /path/to/local/dotfiles; # }; }; }; @@ -63,7 +63,7 @@ Enable the user in `inventory.nix`: "my-system" = { devices = { "hostname" = { - extraUsers = [ "myusername" ]; + ugaif.users.myusername.enable = true; }; }; }; @@ -72,18 +72,32 @@ Enable the user in `inventory.nix`: ## File Descriptions -### home.nix (Required) +### user.nix (Optional) -This file contains your home-manager configuration. It must be a valid NixOS module that accepts `{ inputs, ... }` and returns a home-manager configuration. +This file serves dual purpose: +1. Sets `ugaif.users.` options (description, shell, extraGroups, etc.) +2. Provides home-manager configuration (programs.*, home.*, services.*) -**Must export:** -- Home-manager options (programs.*, home.packages, etc.) +**How it works:** +- The `ugaif.users.` options are extracted and loaded as **data** during module evaluation +- These options override any defaults set in `users.nix` (which uses `lib.mkDefault`) +- The home-manager options (`home.*`, `programs.*`, etc.) are imported as a module for home-manager +- External module options take precedence over `users.nix` base configuration + +The same file is imported in two contexts: +- As a NixOS module to read ugaif.users options +- As a home-manager module for home.*, programs.*, services.*, etc. + +Simply include both types of options in the same file. **Receives:** - `inputs` - Flake inputs (nixpkgs, home-manager, etc.) -- `config` - Home-manager config +- `config` - Config (NixOS or home-manager depending on context) +- `lib` - Nixpkgs library - `pkgs` - Nixpkgs package set -- `osConfig` - Access to OS-level configuration +- `osConfig` - (home-manager context only) OS-level configuration + +**Example:** See `user.nix` template ### nixos.nix (Optional) @@ -92,16 +106,29 @@ This file contains system-level NixOS configuration. Only needed for: - System packages requiring root - Special permissions or system settings +**Receives:** +- `inputs` - Flake inputs (nixpkgs, home-manager, etc.) +- `config` - NixOS config +- `lib` - Nixpkgs library +- `pkgs` - Nixpkgs package set + ## Examples -### Minimal home.nix +### Minimal user.nix ```nix { inputs, ... }: - { config, lib, pkgs, ... }: { + # User account options (imported as NixOS module) + ugaif.users.myuser = { + description = "My Name"; + shell = pkgs.zsh; + extraGroups = [ "wheel" "networkmanager" ]; + }; + + # Home-manager configuration (imported into home-manager) home.packages = with pkgs; [ vim git @@ -120,10 +147,14 @@ This file contains system-level NixOS configuration. Only needed for: ```nix { inputs, ... }: - { config, lib, pkgs, ... }: { + ugaif.users.myuser = { + description = "My Name"; + shell = pkgs.zsh; + }; + home.packages = with pkgs; [ ripgrep fd bat ]; # Symlink dotfiles @@ -158,20 +189,21 @@ This file contains system-level NixOS configuration. Only needed for: External user modules: - Receive the same flake inputs as nixos-systems -- Can use all home-manager options +- Can set user options via user.nix (description, shell, home-manager, etc.) - Optionally provide system-level configuration (nixos.nix) - System zsh theme applied if `useZshTheme = true` (default) - System nvim config applied if `useNvimPlugins = true` (default) -- Merged with inventory.nix user settings (groups, shell, etc.) +- Settings from user.nix override base users.nix definitions ## Development Workflow -1. Create your user config repository with `home.nix` -2. Test locally: `home = /path/to/local/repo;` -3. Build: `nix build .#nixosConfigurations.hostname.config.system.build.toplevel` -4. Commit and push changes -5. Update users.nix with commit hash -6. Deploy to systems +1. Create your user config repository with `user.nix` and/or `nixos.nix` +2. Set user options in user.nix OR in the main users.nix +3. Test locally: `external = /path/to/local/repo;` +4. Build: `nix build .#nixosConfigurations.hostname.config.system.build.toplevel` +5. Commit and push changes +6. Update users.nix with commit hash +7. Deploy to systems ## Benefits diff --git a/templates/user/home.nix b/templates/user/home.nix deleted file mode 100644 index 3717854..0000000 --- a/templates/user/home.nix +++ /dev/null @@ -1,91 +0,0 @@ -{ inputs, ... }: - -# ============================================================================ -# User Home Manager Configuration Template -# ============================================================================ -# This file provides home-manager configuration for a user. -# It will be imported into the NixOS system's home-manager configuration. -# -# Usage in users.nix: -# myusername = { -# description = "My Name"; -# home = builtins.fetchGit { -# url = "https://github.com/username/dotfiles"; -# rev = "commit-hash"; -# }; -# }; -# -# This module receives the same `inputs` flake inputs as the main -# nixos-systems configuration (nixpkgs, home-manager, etc.). - -{ - config, - lib, - pkgs, - osConfig, # Access to the OS-level config - ... -}: - -{ - # ========== Home Manager Configuration ========== - - # User identity (required) - home.username = lib.mkDefault config.home.username; # Set by system - home.homeDirectory = lib.mkDefault config.home.homeDirectory; # Set by system - home.stateVersion = lib.mkDefault "25.11"; - - # ========== Packages ========== - home.packages = with pkgs; [ - # Add your preferred packages here - # htop - # ripgrep - # fd - # bat - ]; - - # ========== Programs ========== - - # Git configuration - programs.git = { - enable = true; - userName = "Your Name"; - userEmail = "your.email@example.com"; - extraConfig = { - init.defaultBranch = "main"; - }; - }; - - # Zsh configuration - programs.zsh = { - enable = true; - # System theme is applied automatically if useZshTheme = true in users.nix - # Add your custom zsh config here - }; - - # Neovim configuration - # programs.neovim = { - # enable = true; - # # System nvim config is applied automatically if useNvimPlugins = true - # # Add your custom neovim config here - # }; - - # ========== Shell Environment ========== - - home.sessionVariables = { - EDITOR = "vim"; - # Add your custom environment variables - }; - - # ========== Dotfiles ========== - - # You can manage dotfiles with home.file - # home.file.".bashrc".source = ./dotfiles/bashrc; - # home.file.".vimrc".source = ./dotfiles/vimrc; - - # Or use programs.* options for better integration - - # ========== XDG Configuration ========== - - xdg.enable = true; - # xdg.configFile."app/config.conf".source = ./config/app.conf; -} diff --git a/templates/user/nixos.nix b/templates/user/nixos.nix index d88819d..b38080c 100644 --- a/templates/user/nixos.nix +++ b/templates/user/nixos.nix @@ -11,6 +11,10 @@ # - Special system permissions or configurations # - Installing system packages that require root # +# Note: User options (description, shell, extraGroups, etc.) should be set +# in your external module's user.nix or in the main users.nix file, not in +# this nixos.nix. +# # This module receives the same `inputs` flake inputs as the main # nixos-systems configuration. diff --git a/templates/user/user.nix b/templates/user/user.nix new file mode 100644 index 0000000..2fee065 --- /dev/null +++ b/templates/user/user.nix @@ -0,0 +1,94 @@ +{ inputs, ... }: + +# ============================================================================ +# User Configuration (Optional) +# ============================================================================ +# This file can configure BOTH: +# 1. User account options (ugaif.users.) when imported as NixOS module +# 2. Home-manager configuration (home.*, programs.*, services.*) when imported +# into home-manager +# +# This file is optional - if not present, the system will use the defaults +# from the main users.nix file. Use this file to override or extend those +# default user and home-manager options for this user. +# +# This module receives the same `inputs` flake inputs as the main +# nixos-systems configuration (nixpkgs, home-manager, etc.). + +{ + config, + lib, + pkgs, + osConfig ? null, # Only available in home-manager context + ... +}: + +{ + # ========== User Account Configuration ========== + # These are imported as a NixOS module to set ugaif.users options + # Replace "myusername" with your actual username + + ugaif.users.myusername = { + description = "Your Full Name"; + + extraGroups = [ + "wheel" # Sudo access + "networkmanager" # Network configuration + # "docker" # Docker access (if needed) + ]; + + shell = pkgs.zsh; + + # Optional: Override editor + # editor = pkgs.helix; + + # Optional: Disable system theme/nvim plugins + # useZshTheme = false; + # useNvimPlugins = false; + + # Optional: Add system-level packages + # extraPackages = with pkgs; [ docker ]; + }; + + # Note: You don't need to set 'enable = true' - that's controlled + # per-host in inventory.nix + + # ========== Home Manager Configuration ========== + # These are imported into home-manager for user environment + # System theme (zsh) and nvim config are applied automatically based on flags above + + # Packages + home.packages = with pkgs; [ + # Add your preferred packages here + # ripgrep + # fd + # bat + ]; + + # ========== Programs ========== + + # Git configuration + programs.git = { + enable = true; + userName = "Your Name"; + userEmail = "your.email@example.com"; + extraConfig = { + init.defaultBranch = "main"; + }; + }; + + # ========== Shell Environment ========== + + home.sessionVariables = { + # EDITOR is set automatically based on ugaif.users.*.editor + # Add your custom environment variables here + }; + + # ========== Dotfiles ========== + + # You can manage dotfiles with home.file + # home.file.".bashrc".source = ./dotfiles/bashrc; + # home.file.".vimrc".source = ./dotfiles/vimrc; + + # Or use programs.* options for better integration +} diff --git a/users.nix b/users.nix index 1e95c7f..63e5fc3 100644 --- a/users.nix +++ b/users.nix @@ -11,15 +11,16 @@ # To generate a password hash, run: mkpasswd -m sha-512 # Set enabled = true on systems where the user should exist # - # External Home Configuration: - # Users can specify external home-manager configuration via the 'home' attribute: - # home = builtins.fetchGit { url = "..."; rev = "..."; }; - # home = /path/to/local/config; - # home = { home.packages = [ ... ]; }; # Direct attrset + # External User Configuration: + # Users can specify external configuration modules via the 'external' attribute: + # external = builtins.fetchGit { url = "..."; rev = "..."; }; + # external = /path/to/local/config; # - # External repositories should contain: - # - home.nix (required): Home-manager configuration + # External repositories can contain: + # - user.nix (optional): Sets ugaif.users. options AND home-manager config # - nixos.nix (optional): System-level NixOS configuration + # + # User options can be set either in users.nix OR in the external module's user.nix. ugaif.users = { root = { isNormalUser = false; @@ -41,14 +42,9 @@ enable = true; # Default user, enabled everywhere }; hdh20267 = { - description = "Hunter Halloran"; - extraGroups = [ - "networkmanager" - "wheel" - ]; - home = builtins.fetchGit { + external = builtins.fetchGit { url = "https://git.factory.uga.edu/hdh20267/hdh20267-nix"; - rev = "ea99aa55680cc937f186aef0efc0df307e79d56f"; + rev = "db96137bb4cb16acefcf59d58c9f848924f2ad43"; }; }; sv22900 = {