From 03f532e86772def2d52b3c9dfb3e1eacc13a9622 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Tue, 6 Jan 2026 14:43:45 -0500 Subject: [PATCH 1/8] refactor: define options where they are used --- hosts/boot.nix | 223 +++++++++++++--------------------- hosts/common.nix | 83 +++++++++++-- hosts/default.nix | 1 + hosts/types/nix-desktop.nix | 1 - hosts/types/nix-ephemeral.nix | 1 - hosts/types/nix-laptop.nix | 1 - hosts/types/nix-lxc.nix | 1 - hosts/types/nix-surface.nix | 1 - hosts/types/nix-wsl.nix | 1 - hosts/types/nix-zima.nix | 1 - 10 files changed, 155 insertions(+), 159 deletions(-) diff --git a/hosts/boot.nix b/hosts/boot.nix index a186612..8e81434 100644 --- a/hosts/boot.nix +++ b/hosts/boot.nix @@ -5,29 +5,18 @@ # - Disko partition layout (EFI, swap, root) # - Bootloader configuration (systemd-boot with Plymouth) # - Filesystem options (device, swap size) -# - Build method options (ISO, iPXE, LXC, Proxmox) -# - Garbage collection settings +# - Build method options (used by installer/artifacts.nix) # - Convenience options (forUser, useHostPrefix) { config, lib, ... }: { options.athenix = { - forUser = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - Convenience option to configure a host for a specific user. - Automatically enables the user (sets athenix.users.username.enable = true). - Value should be a username from athenix.users.accounts. - ''; - }; - host = { useHostPrefix = lib.mkOption { type = lib.types.bool; default = true; - description = "Whether to prepend the host prefix to the hostname (used in inventory)."; + description = "Whether to prepend the host prefix to the hostname (used in inventory and hosts/default.nix)."; }; filesystem = { device = lib.mkOption { @@ -44,151 +33,105 @@ description = "The size of the swap partition."; }; }; - buildMethods = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ "installer-iso" ]; - description = '' - List of allowed build methods for this host. - Supported methods: - - "installer-iso": Generates an auto-install ISO that installs this configuration to disk. - - "iso": Generates a live ISO (using nixos-generators). - - "ipxe": Generates iPXE netboot artifacts (kernel, initrd, script). - - "lxc": Generates an LXC container tarball. - - "proxmox": Generates a Proxmox VMA archive. - ''; - }; - }; - - system.gc = { - enable = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to enable automatic garbage collection."; - }; - frequency = lib.mkOption { - type = lib.types.str; - default = "weekly"; - description = "How often to run garbage collection (systemd timer format)."; - }; - retentionDays = lib.mkOption { - type = lib.types.int; - default = 30; - description = "Number of days to keep old generations before deletion."; - }; - optimise = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to automatically optimize the Nix store."; - }; }; }; - config = lib.mkMerge [ - # Enable forUser if specified - (lib.mkIf (config.athenix.forUser != null) { - athenix.users.${config.athenix.forUser}.enable = true; - }) + config = { + # ========== Disk Partitioning (Disko) ========== + disko.enableConfig = lib.mkDefault true; - # Main configuration - { - # ========== Disk Partitioning (Disko) ========== - disko.enableConfig = lib.mkDefault true; - - disko.devices = { - disk.main = { - type = "disk"; - device = config.athenix.host.filesystem.device; - content = { - type = "gpt"; - partitions = { - # EFI System Partition - ESP = { - name = "ESP"; - label = "BOOT"; - size = "1G"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = [ "umask=0077" ]; - extraArgs = [ - "-n" - "BOOT" - ]; - }; + disko.devices = { + disk.main = { + type = "disk"; + device = config.athenix.host.filesystem.device; + content = { + type = "gpt"; + partitions = { + # EFI System Partition + ESP = { + name = "ESP"; + label = "BOOT"; + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + extraArgs = [ + "-n" + "BOOT" + ]; }; + }; - # Swap Partition (size configurable per host) - swap = lib.mkIf config.athenix.host.filesystem.useSwap { - name = "swap"; - label = "swap"; - size = config.athenix.host.filesystem.swapSize; - content = { - type = "swap"; - }; + # Swap Partition (size configurable per host) + swap = lib.mkIf config.athenix.host.filesystem.useSwap { + name = "swap"; + label = "swap"; + size = config.athenix.host.filesystem.swapSize; + content = { + type = "swap"; }; + }; - # Root Partition (takes remaining space) - root = { - name = "root"; - label = "root"; - size = "100%"; - content = { - type = "filesystem"; - format = "ext4"; - mountpoint = "/"; - extraArgs = [ - "-L" - "ROOT" - ]; - }; + # Root Partition (takes remaining space) + root = { + name = "root"; + label = "root"; + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + extraArgs = [ + "-L" + "ROOT" + ]; }; }; }; }; }; + }; - # Bootloader Configuration - boot = { - loader.systemd-boot.enable = true; - loader.efi.canTouchEfiVariables = true; - plymouth.enable = true; + # Bootloader Configuration + boot = { + loader.systemd-boot.enable = true; + loader.efi.canTouchEfiVariables = true; + plymouth.enable = true; - # Enable "Silent boot" - consoleLogLevel = 3; - initrd.verbose = false; + # Enable "Silent boot" + consoleLogLevel = 3; + initrd.verbose = false; - # Hide the OS choice for bootloaders. - # It's still possible to open the bootloader list by pressing any key - # It will just not appear on screen unless a key is pressed - loader.timeout = lib.mkDefault 0; - }; + # Hide the OS choice for bootloaders. + # It's still possible to open the bootloader list by pressing any key + # It will just not appear on screen unless a key is pressed + loader.timeout = lib.mkDefault 0; + }; - # Set your time zone. - time.timeZone = "America/New_York"; + # Set your time zone. + time.timeZone = "America/New_York"; - # Select internationalisation properties. - i18n.defaultLocale = "en_US.UTF-8"; + # Select internationalisation properties. + i18n.defaultLocale = "en_US.UTF-8"; - i18n.extraLocaleSettings = { - LC_ADDRESS = "en_US.UTF-8"; - LC_IDENTIFICATION = "en_US.UTF-8"; - LC_MEASUREMENT = "en_US.UTF-8"; - LC_MONETARY = "en_US.UTF-8"; - LC_NAME = "en_US.UTF-8"; - LC_NUMERIC = "en_US.UTF-8"; - LC_PAPER = "en_US.UTF-8"; - LC_TELEPHONE = "en_US.UTF-8"; - LC_TIME = "en_US.UTF-8"; - }; + i18n.extraLocaleSettings = { + LC_ADDRESS = "en_US.UTF-8"; + LC_IDENTIFICATION = "en_US.UTF-8"; + LC_MEASUREMENT = "en_US.UTF-8"; + LC_MONETARY = "en_US.UTF-8"; + LC_NAME = "en_US.UTF-8"; + LC_NUMERIC = "en_US.UTF-8"; + LC_PAPER = "en_US.UTF-8"; + LC_TELEPHONE = "en_US.UTF-8"; + LC_TIME = "en_US.UTF-8"; + }; - systemd.sleep.extraConfig = '' - SuspendState=freeze - HibernateDelaySec=2h - ''; - - system.stateVersion = "25.11"; # Did you read the comment? - } - ]; + systemd.sleep.extraConfig = '' + SuspendState=freeze + HibernateDelaySec=2h + ''; + }; } diff --git a/hosts/common.nix b/hosts/common.nix index 908faeb..ba8b4d7 100644 --- a/hosts/common.nix +++ b/hosts/common.nix @@ -28,20 +28,79 @@ inputs.disko.nixosModules.disko ]; - system.stateVersion = "25.11"; + # Define garbage collection options here since they're consumed in this module + options.athenix = { + forUser = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Convenience option to configure a host for a specific user. + Automatically enables the user (sets athenix.users.username.enable = true). + Value should be a username from athenix.users.accounts. + ''; + }; - nix.settings.experimental-features = [ - "nix-command" - "flakes" - ]; + system.gc = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to enable automatic garbage collection."; + }; + frequency = lib.mkOption { + type = lib.types.str; + default = "weekly"; + description = "How often to run garbage collection (systemd timer format)."; + }; + retentionDays = lib.mkOption { + type = lib.types.int; + default = 30; + description = "Number of days to keep old generations before deletion."; + }; + optimise = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically optimize the Nix store."; + }; + }; - # Automatic Garbage Collection - nix.gc = lib.mkIf config.athenix.system.gc.enable { - automatic = true; - dates = config.athenix.system.gc.frequency; - options = "--delete-older-than ${toString config.athenix.system.gc.retentionDays}d"; + host.buildMethods = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "installer-iso" ]; + description = '' + List of allowed build methods for this host (used by installer/artifacts.nix). + Supported methods: + - "installer-iso": Generates an auto-install ISO that installs this configuration to disk. + - "iso": Generates a live ISO (using nixos-generators). + - "ipxe": Generates iPXE netboot artifacts (kernel, initrd, script). + - "lxc": Generates an LXC container tarball. + - "proxmox": Generates a Proxmox VMA archive. + ''; + }; }; - # Optimize storage - nix.optimise.automatic = config.athenix.system.gc.optimise; + config = lib.mkMerge [ + # Enable forUser if specified + (lib.mkIf (config.athenix.forUser != null) { + athenix.users.${config.athenix.forUser}.enable = true; + }) + + { + system.stateVersion = "25.11"; + + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ]; + + # Automatic Garbage Collection + nix.gc = lib.mkIf config.athenix.system.gc.enable { + automatic = true; + dates = config.athenix.system.gc.frequency; + options = "--delete-older-than ${toString config.athenix.system.gc.retentionDays}d"; + }; + + # Optimize storage + nix.optimise.automatic = config.athenix.system.gc.optimise; + } + ]; } diff --git a/hosts/default.nix b/hosts/default.nix index 9ca57fd..f664aed 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -132,6 +132,7 @@ let allModules = userNixosModules ++ [ + (import ./common.nix { inherit inputs; }) typeModule overrideModule { networking.hostName = hostName; } diff --git a/hosts/types/nix-desktop.nix b/hosts/types/nix-desktop.nix index fc5e0e2..2455eba 100644 --- a/hosts/types/nix-desktop.nix +++ b/hosts/types/nix-desktop.nix @@ -13,7 +13,6 @@ }: { imports = [ - (import ../common.nix { inherit inputs; }) (modulesPath + "/installer/scan/not-detected.nix") ]; diff --git a/hosts/types/nix-ephemeral.nix b/hosts/types/nix-ephemeral.nix index f4f7504..51e46c0 100644 --- a/hosts/types/nix-ephemeral.nix +++ b/hosts/types/nix-ephemeral.nix @@ -14,7 +14,6 @@ }: { imports = [ - (import ../common.nix { inherit inputs; }) (modulesPath + "/installer/scan/not-detected.nix") ]; diff --git a/hosts/types/nix-laptop.nix b/hosts/types/nix-laptop.nix index c439ceb..6d9ed5c 100644 --- a/hosts/types/nix-laptop.nix +++ b/hosts/types/nix-laptop.nix @@ -13,7 +13,6 @@ }: { imports = [ - (import ../common.nix { inherit inputs; }) (modulesPath + "/installer/scan/not-detected.nix") ]; diff --git a/hosts/types/nix-lxc.nix b/hosts/types/nix-lxc.nix index fb7574e..cecb409 100644 --- a/hosts/types/nix-lxc.nix +++ b/hosts/types/nix-lxc.nix @@ -13,7 +13,6 @@ }: { imports = [ - (import ../common.nix { inherit inputs; }) inputs.vscode-server.nixosModules.default "${modulesPath}/virtualisation/proxmox-lxc.nix" ]; diff --git a/hosts/types/nix-surface.nix b/hosts/types/nix-surface.nix index 78a8fe8..53f74c4 100644 --- a/hosts/types/nix-surface.nix +++ b/hosts/types/nix-surface.nix @@ -22,7 +22,6 @@ let in { imports = [ - (import ../common.nix { inherit inputs; }) (modulesPath + "/installer/scan/not-detected.nix") inputs.nixos-hardware.nixosModules.microsoft-surface-go ]; diff --git a/hosts/types/nix-wsl.nix b/hosts/types/nix-wsl.nix index 14e0313..f13787a 100644 --- a/hosts/types/nix-wsl.nix +++ b/hosts/types/nix-wsl.nix @@ -12,7 +12,6 @@ }: { imports = [ - (import ../common.nix { inherit inputs; }) inputs.nixos-wsl.nixosModules.default inputs.vscode-server.nixosModules.default ]; diff --git a/hosts/types/nix-zima.nix b/hosts/types/nix-zima.nix index 40a9cec..1174ff0 100644 --- a/hosts/types/nix-zima.nix +++ b/hosts/types/nix-zima.nix @@ -13,7 +13,6 @@ }: { imports = [ - (import ../common.nix { inherit inputs; }) (modulesPath + "/installer/scan/not-detected.nix") ]; -- 2.39.5 From cb37fad70ea60d7264fbf072155238e7aaedaf58 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Tue, 6 Jan 2026 18:31:40 -0500 Subject: [PATCH 2/8] refactor: create glue/ and variants/ directories - Add glue/ for fleet generation logic and common configuration - Add variants/ for hardware type modules - Improves separation of concerns and module organization --- glue/boot.nix | 137 ++++++++++++++++++ glue/common.nix | 99 +++++++++++++ glue/fleet.nix | 278 +++++++++++++++++++++++++++++++++++++ glue/user-config.nix | 276 ++++++++++++++++++++++++++++++++++++ variants/default.nix | 29 ++++ variants/nix-desktop.nix | 51 +++++++ variants/nix-ephemeral.nix | 67 +++++++++ variants/nix-laptop.nix | 64 +++++++++ variants/nix-lxc.nix | 61 ++++++++ variants/nix-surface.nix | 69 +++++++++ variants/nix-wsl.nix | 53 +++++++ variants/nix-zima.nix | 50 +++++++ 12 files changed, 1234 insertions(+) create mode 100644 glue/boot.nix create mode 100644 glue/common.nix create mode 100644 glue/fleet.nix create mode 100644 glue/user-config.nix create mode 100644 variants/default.nix create mode 100644 variants/nix-desktop.nix create mode 100644 variants/nix-ephemeral.nix create mode 100644 variants/nix-laptop.nix create mode 100644 variants/nix-lxc.nix create mode 100644 variants/nix-surface.nix create mode 100644 variants/nix-wsl.nix create mode 100644 variants/nix-zima.nix diff --git a/glue/boot.nix b/glue/boot.nix new file mode 100644 index 0000000..8e81434 --- /dev/null +++ b/glue/boot.nix @@ -0,0 +1,137 @@ +# ============================================================================ +# Boot & Storage Configuration +# ============================================================================ +# This module defines: +# - Disko partition layout (EFI, swap, root) +# - Bootloader configuration (systemd-boot with Plymouth) +# - Filesystem options (device, swap size) +# - Build method options (used by installer/artifacts.nix) +# - Convenience options (forUser, useHostPrefix) + +{ config, lib, ... }: + +{ + options.athenix = { + host = { + useHostPrefix = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to prepend the host prefix to the hostname (used in inventory and hosts/default.nix)."; + }; + filesystem = { + device = lib.mkOption { + type = lib.types.str; + description = "The main disk device to use for installation."; + }; + useSwap = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to create and use a swap partition."; + }; + swapSize = lib.mkOption { + type = lib.types.str; + description = "The size of the swap partition."; + }; + }; + }; + }; + + config = { + # ========== Disk Partitioning (Disko) ========== + disko.enableConfig = lib.mkDefault true; + + disko.devices = { + disk.main = { + type = "disk"; + device = config.athenix.host.filesystem.device; + content = { + type = "gpt"; + partitions = { + # EFI System Partition + ESP = { + name = "ESP"; + label = "BOOT"; + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + extraArgs = [ + "-n" + "BOOT" + ]; + }; + }; + + # Swap Partition (size configurable per host) + swap = lib.mkIf config.athenix.host.filesystem.useSwap { + name = "swap"; + label = "swap"; + size = config.athenix.host.filesystem.swapSize; + content = { + type = "swap"; + }; + }; + + # Root Partition (takes remaining space) + root = { + name = "root"; + label = "root"; + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + extraArgs = [ + "-L" + "ROOT" + ]; + }; + }; + }; + }; + }; + }; + + # Bootloader Configuration + boot = { + loader.systemd-boot.enable = true; + loader.efi.canTouchEfiVariables = true; + plymouth.enable = true; + + # Enable "Silent boot" + consoleLogLevel = 3; + initrd.verbose = false; + + # Hide the OS choice for bootloaders. + # It's still possible to open the bootloader list by pressing any key + # It will just not appear on screen unless a key is pressed + loader.timeout = lib.mkDefault 0; + }; + + # Set your time zone. + time.timeZone = "America/New_York"; + + # Select internationalisation properties. + i18n.defaultLocale = "en_US.UTF-8"; + + i18n.extraLocaleSettings = { + LC_ADDRESS = "en_US.UTF-8"; + LC_IDENTIFICATION = "en_US.UTF-8"; + LC_MEASUREMENT = "en_US.UTF-8"; + LC_MONETARY = "en_US.UTF-8"; + LC_NAME = "en_US.UTF-8"; + LC_NUMERIC = "en_US.UTF-8"; + LC_PAPER = "en_US.UTF-8"; + LC_TELEPHONE = "en_US.UTF-8"; + LC_TIME = "en_US.UTF-8"; + }; + + systemd.sleep.extraConfig = '' + SuspendState=freeze + HibernateDelaySec=2h + ''; + }; +} diff --git a/glue/common.nix b/glue/common.nix new file mode 100644 index 0000000..c7ad4ae --- /dev/null +++ b/glue/common.nix @@ -0,0 +1,99 @@ +# ============================================================================ +# Common Host Module +# ============================================================================ +# This module contains all the common configuration shared by all host types. +# It is automatically imported by the fleet generator for every host. + +{ inputs }: +{ + config, + lib, + ... +}: +{ + imports = [ + ./boot.nix + ./user-config.nix + ../sw + ../users.nix + inputs.home-manager.nixosModules.home-manager + inputs.agenix.nixosModules.default + inputs.disko.nixosModules.disko + ]; + + # Define garbage collection options here since they're consumed in this module + options.athenix = { + forUser = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Convenience option to configure a host for a specific user. + Automatically enables the user (sets athenix.users.username.enable = true). + Value should be a username from athenix.users.accounts. + ''; + }; + + system.gc = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to enable automatic garbage collection."; + }; + frequency = lib.mkOption { + type = lib.types.str; + default = "weekly"; + description = "How often to run garbage collection (systemd timer format)."; + }; + retentionDays = lib.mkOption { + type = lib.types.int; + default = 30; + description = "Number of days to keep old generations before deletion."; + }; + optimise = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically optimize the Nix store."; + }; + }; + + host.buildMethods = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "installer-iso" ]; + description = '' + List of allowed build methods for this host (used by installer/artifacts.nix). + Supported methods: + - "installer-iso": Generates an auto-install ISO that installs this configuration to disk. + - "iso": Generates a live ISO (using nixos-generators). + - "ipxe": Generates iPXE netboot artifacts (kernel, initrd, script). + - "lxc": Generates an LXC container tarball. + - "proxmox": Generates a Proxmox VMA archive. + ''; + }; + }; + + config = lib.mkMerge [ + # Enable forUser if specified + (lib.mkIf (config.athenix.forUser != null) { + athenix.users.${config.athenix.forUser}.enable = true; + }) + + { + system.stateVersion = "25.11"; + + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ]; + + # Automatic Garbage Collection + nix.gc = lib.mkIf config.athenix.system.gc.enable { + automatic = true; + dates = config.athenix.system.gc.frequency; + options = "--delete-older-than ${toString config.athenix.system.gc.retentionDays}d"; + }; + + # Optimize storage + nix.optimise.automatic = config.athenix.system.gc.optimise; + } + ]; +} diff --git a/glue/fleet.nix b/glue/fleet.nix new file mode 100644 index 0000000..e0714b2 --- /dev/null +++ b/glue/fleet.nix @@ -0,0 +1,278 @@ +{ + inputs, + fleet ? import ../inventory.nix, + ... +}: + +# ============================================================================ +# Fleet Generator +# ============================================================================ +# This file contains the logic to generate NixOS configurations for all hosts +# defined in inventory.nix. It supports both hostname-based and count-based +# configurations with flexible type associations. + +let + nixpkgs = inputs.nixpkgs; + lib = nixpkgs.lib; + + # Load all available host types from hosts/ + hostTypes = import ../variants { inherit inputs; }; + + # Helper to create a single NixOS system configuration + mkHost = + { + hostName, + system ? "x86_64-linux", + hostType, + configOverrides ? { }, + externalModuleThunk ? null, + }: + let + # Lazy evaluation: only fetch external module when building this host + externalModulePath = + if externalModuleThunk != null then + let + # Force evaluation of the thunk (fetchGit, fetchTarball, etc.) + fetchedPath = externalModuleThunk; + # Extract outPath from fetchGit/fetchTarball results + extractedPath = + if builtins.isAttrs fetchedPath && fetchedPath ? outPath then fetchedPath.outPath else fetchedPath; + in + if builtins.isPath extractedPath then + extractedPath + "/default.nix" + else if lib.isDerivation extractedPath then + extractedPath + "/default.nix" + else + extractedPath + "/default.nix" + else + null; + + # Load users.nix to find external user modules + pkgs = nixpkgs.legacyPackages.${system}; + usersData = import ../users.nix { inherit pkgs; }; + accounts = usersData.athenix.users or { }; + + # Build a map of user names to their nixos module paths (if they exist) + # We'll use this to conditionally import modules based on user.enable + userNixosModulePaths = lib.filterAttrs (_: v: v != null) ( + lib.mapAttrs ( + name: user: + if (user ? external && user.external != null) then + let + externalPath = + if builtins.isAttrs user.external && user.external ? outPath then + user.external.outPath + else + user.external; + nixosModulePath = externalPath + "/nixos.nix"; + in + if + (builtins.isPath externalPath || (builtins.isString externalPath && lib.hasPrefix "/" externalPath)) + && builtins.pathExists nixosModulePath + then + nixosModulePath + else + null + else + null + ) accounts + ); + + # Create conditional wrapper modules for each user's nixos.nix + # Each wrapper checks if the user is enabled before applying the module content + userNixosModules = lib.mapAttrsToList ( + name: modulePath: + { + config, + lib, + pkgs, + ... + }@args: + let + # Import the user's nixos module - it returns a function or attrset + importedModuleFunc = import modulePath { inherit inputs; }; + # If it's a function, call it with the module args; otherwise use as-is + importedModule = + if lib.isFunction importedModuleFunc then importedModuleFunc args else importedModuleFunc; + in + { + config = lib.mkIf (config.athenix.users.${name}.enable or false) importedModule; + } + ) userNixosModulePaths; + + # Get the host type module from the hostTypes attribute set + typeModule = + hostTypes.${hostType} + or (throw "Host type '${hostType}' not found. Available types: ${lib.concatStringsSep ", " (lib.attrNames hostTypes)}"); + + # External module from fetchGit/fetchurl + externalPathModule = + if externalModulePath != null then import externalModulePath { inherit inputs; } else { }; + + # Config override module - translate special keys to athenix options + overrideModule = + { ... }: + let + cleanConfig = lib.removeAttrs configOverrides [ + "type" + "count" + "devices" + "overrides" + "defaultCount" + "buildMethods" + ]; + specialConfig = lib.optionalAttrs (configOverrides ? buildMethods) { + athenix.host.buildMethods = configOverrides.buildMethods; + }; + in + { + config = lib.mkMerge [ + cleanConfig + specialConfig + ]; + }; + + allModules = + userNixosModules + ++ [ + (import ./common.nix { inherit inputs; }) + typeModule + overrideModule + { networking.hostName = hostName; } + ] + ++ lib.optional (externalModulePath != null) externalPathModule; + in + { + system = lib.nixosSystem { + inherit system; + specialArgs = { inherit inputs; }; + modules = allModules; + }; + modules = allModules; + }; + + # Process inventory entries - top-level keys are always prefixes + processInventory = lib.mapAttrs ( + prefix: config: + let + hostType = config.type or prefix; + system = config.system or "x86_64-linux"; + devices = config.devices or { }; + hasCount = config ? count; + + # Helper to generate hostname from prefix and suffix + # Numbers get no dash: "nix-surface1", "nix-surface2" + # Letters get dash: "nix-surface-alpha", "nix-surface-beta" + mkHostName = + prefix: suffix: usePrefix: + if !usePrefix then + suffix + else if lib.match "[0-9]+" suffix != null then + "${prefix}${suffix}" # numeric: no dash + else + "${prefix}-${suffix}"; # non-numeric: add dash + + # Extract common overrides and default count + overrides = config.overrides or { }; + defaultCount = config.defaultCount or 0; + + # If devices is a number, treat it as count + devicesValue = config.devices or { }; + actualDevices = if lib.isInt devicesValue then { } else devicesValue; + actualCount = if lib.isInt devicesValue then devicesValue else (config.count or 0); + + # Clean base config - remove inventory management keys + baseConfig = lib.removeAttrs config [ + "type" + "system" + "count" + "devices" + "overrides" + "defaultCount" + ]; + + # Generate hosts from explicit device definitions + deviceHosts = lib.listToAttrs ( + lib.mapAttrsToList ( + deviceKey: deviceConfig: + let + # Check if deviceConfig has an 'external' field for lazy evaluation + hasExternalField = builtins.isAttrs deviceConfig && deviceConfig ? external; + + # Extract external module thunk if present (don't evaluate yet!) + externalModuleThunk = if hasExternalField then deviceConfig.external else null; + + # Remove 'external' from config to avoid conflicts + cleanDeviceConfig = + if hasExternalField then lib.removeAttrs deviceConfig [ "external" ] else deviceConfig; + + # Merge: base config -> overrides -> device-specific config + mergedConfig = lib.recursiveUpdate (lib.recursiveUpdate baseConfig overrides) cleanDeviceConfig; + + # Check useHostPrefix from the merged config + usePrefix = mergedConfig.athenix.host.useHostPrefix or true; + hostName = mkHostName prefix deviceKey usePrefix; + in + { + name = hostName; + value = mkHost { + inherit + hostName + system + hostType + externalModuleThunk + ; + configOverrides = mergedConfig; + }; + } + ) actualDevices + ); + + # Generate numbered hosts from count or defaultCount + # If devices are specified, defaultCount fills in the gaps + countToUse = if actualCount > 0 then actualCount else defaultCount; + + # Get which numbered keys are already defined in devices + existingNumbers = lib.filter (k: lib.match "[0-9]+" k != null) (lib.attrNames actualDevices); + + countHosts = + if countToUse > 0 then + lib.listToAttrs ( + lib.filter (x: x != null) ( + map ( + i: + let + deviceKey = toString i; + # Skip if this number is already in explicit devices + alreadyDefined = lib.elem deviceKey existingNumbers; + in + if alreadyDefined then + null + else + let + hostName = mkHostName prefix deviceKey true; + mergedConfig = lib.recursiveUpdate baseConfig overrides; + in + { + name = hostName; + value = mkHost { + inherit hostName system hostType; + configOverrides = mergedConfig; + }; + } + ) (lib.range 1 countToUse) + ) + ) + else + { }; + in + lib.recursiveUpdate deviceHosts countHosts + ) fleet; + + # Flatten the nested structure + allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues processInventory); +in +{ + nixosConfigurations = lib.mapAttrs (n: v: v.system) allHosts; + modules = lib.mapAttrs (n: v: v.modules) allHosts; +} diff --git a/glue/user-config.nix b/glue/user-config.nix new file mode 100644 index 0000000..299acef --- /dev/null +++ b/glue/user-config.nix @@ -0,0 +1,276 @@ +{ + pkgs, + config, + lib, + inputs, + ... +}: + +# ============================================================================ +# User Configuration Module +# ============================================================================ +# This module defines the schema for user accounts and handles their creation. +# It bridges the gap between the data in 'users.nix' and the actual NixOS +# and Home Manager configuration. + +let + # Load users.nix to get account definitions + usersData = import ../users.nix { inherit pkgs; }; + accounts = usersData.athenix.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 athenix.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 athenix.users. options + athenixUsers = evaluatedModule.athenix.users or { }; + in + athenixUsers.${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 = { + isNormalUser = lib.mkOption { + type = lib.types.bool; + default = true; + }; + description = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + extraGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + hashedPassword = lib.mkOption { + type = lib.types.str; + default = "!"; + }; + extraPackages = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + }; + excludePackages = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + }; + homePackages = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + }; + extraImports = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + }; + external = lib.mkOption { + type = lib.types.nullOr ( + lib.types.oneOf [ + lib.types.path + lib.types.package + lib.types.attrs + ] + ); + default = null; + description = '' + External user configuration module. Can be: + - A path to a local module directory + - A fetchGit/fetchTarball result pointing to a repository + + The external module can contain: + - user.nix (optional): Sets athenix.users. options AND home-manager config + - nixos.nix (optional): System-level NixOS configuration + + Example: builtins.fetchGit { url = "https://github.com/user/dotfiles"; rev = "..."; } + ''; + }; + opensshKeys = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "List of SSH public keys for the user."; + }; + shell = lib.mkOption { + type = lib.types.nullOr lib.types.package; + default = null; + description = "The shell for this user."; + }; + editor = lib.mkOption { + type = lib.types.nullOr lib.types.package; + default = null; + description = "The default editor for this user."; + }; + useZshTheme = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to apply the system Zsh theme."; + }; + useNvimPlugins = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to apply the system Neovim configuration."; + }; + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether this user account is enabled on this system."; + }; + }; + }; +in +{ + + options.athenix.users = lib.mkOption { + type = lib.types.attrsOf userSubmodule; + default = { }; + description = "User accounts configuration. Set enable=true for users that should exist on this system."; + }; + + 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) + athenix.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 + enabledAccounts = lib.filterAttrs (_: user: user.enable) config.athenix.users; + in + lib.mapAttrs ( + name: user: + let + isPlasma6 = config.services.desktopManager.plasma6.enable; + defaultPackages = lib.optionals (isPlasma6 && name != "root") [ pkgs.kdePackages.kate ]; + finalPackages = lib.subtractLists user.excludePackages (defaultPackages ++ user.extraPackages); + in + { + inherit (user) isNormalUser extraGroups hashedPassword; + description = if user.description != null then user.description else lib.mkDefault ""; + openssh.authorizedKeys.keys = user.opensshKeys; + packages = finalPackages; + shell = if user.shell != null then user.shell else pkgs.bash; + } + ) enabledAccounts; + + # Home Manager configs per user + home-manager = { + useGlobalPkgs = true; + useUserPackages = true; + extraSpecialArgs = { + osConfig = config; + inherit inputs; + }; + + users = + let + enabledAccounts = lib.filterAttrs (_: user: user.enable) config.athenix.users; + in + lib.mapAttrs ( + name: user: + let + # 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; + + # Import external user.nix for home-manager (filter out athenix.* options) + externalUserModule = + if hasExternalUser then + let + fullModule = import userNixPath { inherit inputs; }; + in + # Only pass through non-athenix options to home-manager + { + config, + lib, + pkgs, + osConfig, + ... + }: + let + evaluated = fullModule { + inherit + config + lib + pkgs + osConfig + ; + }; + in + lib.filterAttrs (attrName: _: attrName != "athenix") evaluated + else + { }; + + # 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 + lib.mkMerge [ + { + imports = allImports; + + # Always set these required options + home.username = name; + home.homeDirectory = if name == "root" then "/root" else "/home/${name}"; + home.stateVersion = "25.11"; + } + (lib.mkIf (!hasExternal) { + # For local users only, add their packages + home.packages = user.homePackages; + }) + ] + ) enabledAccounts; + }; + }; +} diff --git a/variants/default.nix b/variants/default.nix new file mode 100644 index 0000000..8c4fcc7 --- /dev/null +++ b/variants/default.nix @@ -0,0 +1,29 @@ +# ============================================================================ +# Host Types Module +# ============================================================================ +# This module exports all available host types as an attribute set. +# Each type is a NixOS module function that takes { inputs } and returns +# a module configuration. + +{ inputs }: +let + inherit (builtins) readDir attrNames; + lib = inputs.nixpkgs.lib; + inherit (lib) filterAttrs removeSuffix genAttrs; + + files = readDir ./.; + + # Keep only regular *.nix files except default.nix + nixFiles = + filterAttrs + (name: type: + type == "regular" + && lib.hasSuffix ".nix" name + && name != "default.nix") + files; + + moduleNames = map (name: removeSuffix ".nix" name) (attrNames nixFiles); +in +genAttrs moduleNames + (name: + import ./${name}.nix { inherit inputs; }) diff --git a/variants/nix-desktop.nix b/variants/nix-desktop.nix new file mode 100644 index 0000000..2455eba --- /dev/null +++ b/variants/nix-desktop.nix @@ -0,0 +1,51 @@ +# ============================================================================ +# Desktop Configuration +# ============================================================================ +# Hardware and boot configuration for standard desktop workstations. +# Includes Intel CPU support and NVMe storage. + +{ inputs, ... }: +{ + config, + lib, + modulesPath, + ... +}: +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + # ========== Boot Configuration ========== + + boot.initrd.availableKernelModules = [ + "xhci_pci" # USB 3.0 support + "nvme" # NVMe SSD support + "usb_storage" # USB storage devices + "sd_mod" # SD card support + "sdhci_pci" # SD card host controller + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support + boot.extraModulePackages = [ ]; + boot.kernelParams = [ + "quiet" # Minimal boot messages + "splash" # Show Plymouth boot splash + "boot.shell_on_fail" # Emergency shell on boot failure + "udev.log_priority=3" # Reduce udev logging + "rd.systemd.show_status=auto" # Show systemd status during boot + ]; + + # ========== Filesystem Configuration ========== + athenix.host.filesystem.swapSize = lib.mkDefault "16G"; + athenix.host.filesystem.device = lib.mkDefault "/dev/nvme0n1"; + athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ]; + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + + # ========== Hardware Configuration ========== + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + + # ========== Software Profile ========== + athenix.sw.enable = lib.mkDefault true; + athenix.sw.type = lib.mkDefault "desktop"; +} diff --git a/variants/nix-ephemeral.nix b/variants/nix-ephemeral.nix new file mode 100644 index 0000000..51e46c0 --- /dev/null +++ b/variants/nix-ephemeral.nix @@ -0,0 +1,67 @@ +# ============================================================================ +# Ephemeral/Diskless System Configuration +# ============================================================================ +# Configuration for systems that run entirely from RAM without persistent storage. +# Suitable for kiosks, netboot clients, and stateless workstations. +# All data is lost on reboot. + +{ inputs, ... }: +{ + config, + lib, + modulesPath, + ... +}: +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + # ========== Boot Configuration ========== + boot.initrd.availableKernelModules = [ + "xhci_pci" # USB 3.0 support + "nvme" # NVMe support + "usb_storage" # USB storage devices + "sd_mod" # SD card support + "sdhci_pci" # SD card host controller + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support + boot.extraModulePackages = [ ]; + boot.kernelParams = [ + "quiet" # Minimal boot messages + "splash" # Show Plymouth boot splash + "boot.shell_on_fail" # Emergency shell on boot failure + "udev.log_priority=3" # Reduce udev logging + "rd.systemd.show_status=auto" # Show systemd status during boot + ]; + + # ========== Ephemeral Configuration ========== + # No persistent storage - everything runs from RAM + athenix.host.filesystem.swapSize = lib.mkForce "0G"; + athenix.host.filesystem.device = lib.mkForce "/dev/null"; # Dummy device + athenix.host.buildMethods = lib.mkDefault [ + "iso" # Live ISO image + "ipxe" # Network boot + ]; + + # Disable disk management for RAM-only systems + disko.enableConfig = lib.mkForce false; + + # Define tmpfs root filesystem + fileSystems."/" = { + device = "none"; + fsType = "tmpfs"; + options = [ + "defaults" + "size=50%" + "mode=755" + ]; + }; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + + athenix.sw.enable = lib.mkDefault true; + athenix.sw.type = lib.mkDefault "stateless-kiosk"; +} diff --git a/variants/nix-laptop.nix b/variants/nix-laptop.nix new file mode 100644 index 0000000..6d9ed5c --- /dev/null +++ b/variants/nix-laptop.nix @@ -0,0 +1,64 @@ +# ============================================================================ +# Laptop Configuration +# ============================================================================ +# Hardware and boot configuration for laptop systems with mobile features. +# Includes power management, lid switch handling, and Intel graphics fixes. + +{ inputs, ... }: +{ + config, + lib, + modulesPath, + ... +}: +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + # ========== Boot Configuration ========== + + boot.initrd.availableKernelModules = [ + "xhci_pci" # USB 3.0 support + "thunderbolt" # Thunderbolt support + "nvme" # NVMe SSD support + "usb_storage" # USB storage devices + "sd_mod" # SD card support + "sdhci_pci" # SD card host controller + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support + boot.extraModulePackages = [ ]; + boot.kernelParams = [ + "quiet" # Minimal boot messages + "splash" # Show Plymouth boot splash + "boot.shell_on_fail" # Emergency shell on boot failure + "udev.log_priority=3" # Reduce udev logging + "rd.systemd.show_status=auto" # Show systemd status during boot + "i915.enable_psr=0" # Disable Panel Self Refresh (stability) + "i915.enable_dc=0" # Disable display power saving + "i915.enable_fbc=0" # Disable framebuffer compression + ]; + + # ========== Hardware Configuration ========== + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + + # ========== Filesystem Configuration ========== + athenix.host.filesystem.device = lib.mkDefault "/dev/nvme0n1"; + athenix.host.filesystem.swapSize = lib.mkDefault "34G"; # Larger swap for hibernation + athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ]; + + # ========== Power Management ========== + services.upower.enable = lib.mkDefault true; + services.logind.settings = { + Login = { + HandleLidSwitch = "suspend"; + HandleLidSwitchExternalPower = "suspend"; + HandleLidSwitchDocked = "ignore"; + }; + }; + + athenix.sw.enable = lib.mkDefault true; + athenix.sw.type = lib.mkDefault "desktop"; +} diff --git a/variants/nix-lxc.nix b/variants/nix-lxc.nix new file mode 100644 index 0000000..cecb409 --- /dev/null +++ b/variants/nix-lxc.nix @@ -0,0 +1,61 @@ +# ============================================================================ +# Proxmox LXC Container Configuration +# ============================================================================ +# Configuration for lightweight Linux containers running in Proxmox. +# Disables boot/disk management and enables remote development support. + +{ inputs, ... }: +{ + config, + lib, + modulesPath, + ... +}: +{ + imports = [ + inputs.vscode-server.nixosModules.default + "${modulesPath}/virtualisation/proxmox-lxc.nix" + ]; + + # ========== Nix Configuration ========== + nix.settings.trusted-users = [ + "root" + "engr-ugaif" + ]; + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ]; + + # ========== Container-Specific Configuration ========== + boot.isContainer = true; + boot.loader.systemd-boot.enable = lib.mkForce false; # No bootloader in container + disko.enableConfig = lib.mkForce false; # No disk management in container + console.enable = true; + + # Allow getty to work in containers + systemd.services."getty@".unitConfig.ConditionPathExists = [ + "" + "/dev/%I" + ]; + + # Suppress unnecessary systemd units for containers + systemd.suppressedSystemUnits = [ + "dev-mqueue.mount" + "sys-kernel-debug.mount" + "sys-fs-fuse-connections.mount" + ]; + + # ========== Remote Development ========== + services.vscode-server.enable = true; + + # ========== System Configuration ========== + system.stateVersion = "25.11"; + athenix.host.buildMethods = lib.mkDefault [ + "lxc" # LXC container tarball + "proxmox" # Proxmox VMA archive + ]; + + athenix.sw.enable = lib.mkDefault true; + athenix.sw.type = lib.mkDefault "headless"; +} diff --git a/variants/nix-surface.nix b/variants/nix-surface.nix new file mode 100644 index 0000000..53f74c4 --- /dev/null +++ b/variants/nix-surface.nix @@ -0,0 +1,69 @@ +# ============================================================================ +# Microsoft Surface Tablet Configuration +# ============================================================================ +# Hardware configuration for Surface Go tablets in kiosk mode. +# Uses nixos-hardware module and older kernel for Surface-specific drivers. + +{ inputs, ... }: +{ + config, + lib, + pkgs, + modulesPath, + ... +}: +let + # Use older kernel version for better Surface Go compatibility + refSystem = inputs.nixpkgs-old-kernel.lib.nixosSystem { + system = pkgs.stdenv.hostPlatform.system; + modules = [ inputs.nixos-hardware.nixosModules.microsoft-surface-go ]; + }; + refKernelPackages = refSystem.config.boot.kernelPackages; +in +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + inputs.nixos-hardware.nixosModules.microsoft-surface-go + ]; + + # ========== Boot Configuration ========== + + boot.initrd.availableKernelModules = [ + "xhci_pci" # USB 3.0 support + "nvme" # NVMe support (though Surface uses eMMC) + "usb_storage" # USB storage devices + "sd_mod" # SD card support + "sdhci_pci" # SD card host controller + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support + boot.extraModulePackages = [ ]; + boot.kernelParams = [ + "quiet" # Minimal boot messages + "splash" # Show Plymouth boot splash + "boot.shell_on_fail" # Emergency shell on boot failure + "udev.log_priority=3" # Reduce udev logging + "rd.systemd.show_status=auto" # Show systemd status during boot + "intel_ipu3_imgu" # Intel camera image processing + "intel_ipu3_isys" # Intel camera sensor interface + "fbcon=map:1" # Framebuffer console mapping + "i915.enable_psr=0" # Disable Panel Self Refresh (breaks resume) + "i915.enable_dc=0" # Disable display power saving + ]; + + # Use older kernel for better Surface hardware support + boot.kernelPackages = lib.mkForce refKernelPackages; + + # ========== Filesystem Configuration ========== + athenix.host.filesystem.swapSize = lib.mkDefault "8G"; + athenix.host.filesystem.device = lib.mkDefault "/dev/mmcblk0"; # eMMC storage # eMMC storage + athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ]; + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + + # ========== Hardware Configuration ========== + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + + # ========== Software Profile ========== + athenix.sw.enable = lib.mkDefault true; + athenix.sw.type = lib.mkDefault "tablet-kiosk"; # Touch-optimized kiosk mode +} diff --git a/variants/nix-wsl.nix b/variants/nix-wsl.nix new file mode 100644 index 0000000..f13787a --- /dev/null +++ b/variants/nix-wsl.nix @@ -0,0 +1,53 @@ +# ============================================================================ +# Windows Subsystem for Linux (WSL) Configuration +# ============================================================================ +# Configuration for NixOS running in WSL2 on Windows. +# Integrates with nixos-wsl for WSL-specific functionality. + +{ inputs, ... }: +{ + lib, + config, + ... +}: +{ + imports = [ + inputs.nixos-wsl.nixosModules.default + inputs.vscode-server.nixosModules.default + ]; + + # ========== Options ========== + options.athenix.host.wsl.user = lib.mkOption { + type = lib.types.str; + default = "engr-ugaif"; + description = "The default user to log in as in WSL."; + }; + + config = { + # ========== WSL Configuration ========== + wsl.enable = true; + # Use forUser if set, otherwise fall back to wsl.user option + wsl.defaultUser = + if config.athenix.forUser != null then config.athenix.forUser else config.athenix.host.wsl.user; + + # ========== Software Profile ========== + athenix.sw.enable = lib.mkDefault true; + athenix.sw.type = lib.mkDefault "headless"; + + # ========== Remote Development ========== + services.vscode-server.enable = true; + + # ========== Disable Irrelevant Systems ========== + # WSL doesn't use traditional boot or disk management + disko.enableConfig = lib.mkForce false; + boot.loader.systemd-boot.enable = lib.mkForce false; + boot.loader.grub.enable = lib.mkForce false; + + # WSL manages its own networking + systemd.network.enable = lib.mkForce false; + + # Provide dummy values for required options from boot.nix + athenix.host.filesystem.device = "/dev/null"; + athenix.host.filesystem.swapSize = "0G"; + }; +} diff --git a/variants/nix-zima.nix b/variants/nix-zima.nix new file mode 100644 index 0000000..1174ff0 --- /dev/null +++ b/variants/nix-zima.nix @@ -0,0 +1,50 @@ +# ============================================================================ +# Desktop Configuration +# ============================================================================ +# Hardware and boot configuration for standard desktop workstations. +# Includes Intel CPU support and NVMe storage. + +{ inputs, ... }: +{ + config, + lib, + modulesPath, + ... +}: +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + # ========== Boot Configuration ========== + + boot.initrd.availableKernelModules = [ + "xhci_pci" # USB 3.0 support + "usb_storage" # USB storage devices + "sd_mod" # SD card support + "sdhci_pci" # SD card host controller + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support + boot.extraModulePackages = [ ]; + boot.kernelParams = [ + "quiet" # Minimal boot messages + "splash" # Show Plymouth boot splash + "boot.shell_on_fail" # Emergency shell on boot failure + "udev.log_priority=3" # Reduce udev logging + "rd.systemd.show_status=auto" # Show systemd status during boot + ]; + + # ========== Filesystem Configuration ========== + athenix.host.filesystem.useSwap = lib.mkDefault false; + athenix.host.filesystem.device = lib.mkDefault "/dev/mmcblk0"; + athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ]; + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + + # ========== Hardware Configuration ========== + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + + # ========== Software Profile ========== + athenix.sw.enable = lib.mkDefault true; + athenix.sw.type = lib.mkDefault "desktop"; +} -- 2.39.5 From 77cea838a1014c23d296ca2414f2cbee7f436279 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Tue, 6 Jan 2026 18:31:50 -0500 Subject: [PATCH 3/8] chore: remove old hosts/ directory - Replaced by glue/ and variants/ structure - Fleet generation moved to glue/fleet.nix - Hardware types moved to variants/ --- hosts/boot.nix | 137 ---------------- hosts/common.nix | 106 ------------ hosts/default.nix | 301 ---------------------------------- hosts/types/nix-desktop.nix | 51 ------ hosts/types/nix-ephemeral.nix | 67 -------- hosts/types/nix-laptop.nix | 64 -------- hosts/types/nix-lxc.nix | 61 ------- hosts/types/nix-surface.nix | 69 -------- hosts/types/nix-wsl.nix | 53 ------ hosts/types/nix-zima.nix | 50 ------ hosts/user-config.nix | 276 ------------------------------- 11 files changed, 1235 deletions(-) delete mode 100644 hosts/boot.nix delete mode 100644 hosts/common.nix delete mode 100644 hosts/default.nix delete mode 100644 hosts/types/nix-desktop.nix delete mode 100644 hosts/types/nix-ephemeral.nix delete mode 100644 hosts/types/nix-laptop.nix delete mode 100644 hosts/types/nix-lxc.nix delete mode 100644 hosts/types/nix-surface.nix delete mode 100644 hosts/types/nix-wsl.nix delete mode 100644 hosts/types/nix-zima.nix delete mode 100644 hosts/user-config.nix diff --git a/hosts/boot.nix b/hosts/boot.nix deleted file mode 100644 index 8e81434..0000000 --- a/hosts/boot.nix +++ /dev/null @@ -1,137 +0,0 @@ -# ============================================================================ -# Boot & Storage Configuration -# ============================================================================ -# This module defines: -# - Disko partition layout (EFI, swap, root) -# - Bootloader configuration (systemd-boot with Plymouth) -# - Filesystem options (device, swap size) -# - Build method options (used by installer/artifacts.nix) -# - Convenience options (forUser, useHostPrefix) - -{ config, lib, ... }: - -{ - options.athenix = { - host = { - useHostPrefix = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to prepend the host prefix to the hostname (used in inventory and hosts/default.nix)."; - }; - filesystem = { - device = lib.mkOption { - type = lib.types.str; - description = "The main disk device to use for installation."; - }; - useSwap = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to create and use a swap partition."; - }; - swapSize = lib.mkOption { - type = lib.types.str; - description = "The size of the swap partition."; - }; - }; - }; - }; - - config = { - # ========== Disk Partitioning (Disko) ========== - disko.enableConfig = lib.mkDefault true; - - disko.devices = { - disk.main = { - type = "disk"; - device = config.athenix.host.filesystem.device; - content = { - type = "gpt"; - partitions = { - # EFI System Partition - ESP = { - name = "ESP"; - label = "BOOT"; - size = "1G"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = [ "umask=0077" ]; - extraArgs = [ - "-n" - "BOOT" - ]; - }; - }; - - # Swap Partition (size configurable per host) - swap = lib.mkIf config.athenix.host.filesystem.useSwap { - name = "swap"; - label = "swap"; - size = config.athenix.host.filesystem.swapSize; - content = { - type = "swap"; - }; - }; - - # Root Partition (takes remaining space) - root = { - name = "root"; - label = "root"; - size = "100%"; - content = { - type = "filesystem"; - format = "ext4"; - mountpoint = "/"; - extraArgs = [ - "-L" - "ROOT" - ]; - }; - }; - }; - }; - }; - }; - - # Bootloader Configuration - boot = { - loader.systemd-boot.enable = true; - loader.efi.canTouchEfiVariables = true; - plymouth.enable = true; - - # Enable "Silent boot" - consoleLogLevel = 3; - initrd.verbose = false; - - # Hide the OS choice for bootloaders. - # It's still possible to open the bootloader list by pressing any key - # It will just not appear on screen unless a key is pressed - loader.timeout = lib.mkDefault 0; - }; - - # Set your time zone. - time.timeZone = "America/New_York"; - - # Select internationalisation properties. - i18n.defaultLocale = "en_US.UTF-8"; - - i18n.extraLocaleSettings = { - LC_ADDRESS = "en_US.UTF-8"; - LC_IDENTIFICATION = "en_US.UTF-8"; - LC_MEASUREMENT = "en_US.UTF-8"; - LC_MONETARY = "en_US.UTF-8"; - LC_NAME = "en_US.UTF-8"; - LC_NUMERIC = "en_US.UTF-8"; - LC_PAPER = "en_US.UTF-8"; - LC_TELEPHONE = "en_US.UTF-8"; - LC_TIME = "en_US.UTF-8"; - }; - - systemd.sleep.extraConfig = '' - SuspendState=freeze - HibernateDelaySec=2h - ''; - }; -} diff --git a/hosts/common.nix b/hosts/common.nix deleted file mode 100644 index ba8b4d7..0000000 --- a/hosts/common.nix +++ /dev/null @@ -1,106 +0,0 @@ -# ============================================================================ -# Common Modules -# ============================================================================ -# This module contains all the common configuration shared by all host types. -# It includes: -# - Boot and user configuration -# - Software configurations -# - User management (users.nix) -# - Home Manager integration -# - Secret management (agenix) -# - Disk partitioning (disko) -# - System-wide Nix settings (experimental features, garbage collection) - -{ inputs }: -{ - config, - lib, - ... -}: -{ - imports = [ - ./boot.nix - ./user-config.nix - ../sw - ../users.nix - inputs.home-manager.nixosModules.home-manager - inputs.agenix.nixosModules.default - inputs.disko.nixosModules.disko - ]; - - # Define garbage collection options here since they're consumed in this module - options.athenix = { - forUser = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - Convenience option to configure a host for a specific user. - Automatically enables the user (sets athenix.users.username.enable = true). - Value should be a username from athenix.users.accounts. - ''; - }; - - system.gc = { - enable = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to enable automatic garbage collection."; - }; - frequency = lib.mkOption { - type = lib.types.str; - default = "weekly"; - description = "How often to run garbage collection (systemd timer format)."; - }; - retentionDays = lib.mkOption { - type = lib.types.int; - default = 30; - description = "Number of days to keep old generations before deletion."; - }; - optimise = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to automatically optimize the Nix store."; - }; - }; - - host.buildMethods = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ "installer-iso" ]; - description = '' - List of allowed build methods for this host (used by installer/artifacts.nix). - Supported methods: - - "installer-iso": Generates an auto-install ISO that installs this configuration to disk. - - "iso": Generates a live ISO (using nixos-generators). - - "ipxe": Generates iPXE netboot artifacts (kernel, initrd, script). - - "lxc": Generates an LXC container tarball. - - "proxmox": Generates a Proxmox VMA archive. - ''; - }; - }; - - config = lib.mkMerge [ - # Enable forUser if specified - (lib.mkIf (config.athenix.forUser != null) { - athenix.users.${config.athenix.forUser}.enable = true; - }) - - { - system.stateVersion = "25.11"; - - nix.settings.experimental-features = [ - "nix-command" - "flakes" - ]; - - # Automatic Garbage Collection - nix.gc = lib.mkIf config.athenix.system.gc.enable { - automatic = true; - dates = config.athenix.system.gc.frequency; - options = "--delete-older-than ${toString config.athenix.system.gc.retentionDays}d"; - }; - - # Optimize storage - nix.optimise.automatic = config.athenix.system.gc.optimise; - } - ]; -} diff --git a/hosts/default.nix b/hosts/default.nix deleted file mode 100644 index f664aed..0000000 --- a/hosts/default.nix +++ /dev/null @@ -1,301 +0,0 @@ -{ - inputs, - hosts ? import ../inventory.nix, - ... -}: - -# ============================================================================ -# Host Generator -# ============================================================================ -# This file contains the logic to generate NixOS configurations for all hosts -# defined in inventory.nix. It supports both hostname-based and count-based -# configurations with flexible type associations. -# -# Inventory format: -# { -# "my-hostname" = { -# type = "nix-desktop"; # Host type module to use -# system = "x86_64-linux"; # Optional -# # ... any athenix.* options or device-specific config -# }; -# -# "lab-prefix" = { -# type = "nix-laptop"; -# count = 5; # Generates lab-prefix1, lab-prefix2, ... lab-prefix5 -# devices = { -# "machine-1" = { ... }; # Override for lab-prefix1 -# }; -# }; -# } - -let - nixpkgs = inputs.nixpkgs; - lib = nixpkgs.lib; - # Helper to create a single NixOS system configuration - mkHost = - { - hostName, - system ? "x86_64-linux", - hostType, - configOverrides ? { }, - externalModulePath ? null, - }: - let - # Load users.nix to find external user modules - pkgs = nixpkgs.legacyPackages.${system}; - usersData = import ../users.nix { inherit pkgs; }; - accounts = usersData.athenix.users or { }; - - # Build a map of user names to their nixos module paths (if they exist) - # We'll use this to conditionally import modules based on user.enable - userNixosModulePaths = lib.filterAttrs (_: v: v != null) ( - lib.mapAttrs ( - name: user: - if (user ? external && user.external != null) then - let - externalPath = - if builtins.isAttrs user.external && user.external ? outPath then - user.external.outPath - else - user.external; - nixosModulePath = externalPath + "/nixos.nix"; - in - if - (builtins.isPath externalPath || (builtins.isString externalPath && lib.hasPrefix "/" externalPath)) - && builtins.pathExists nixosModulePath - then - nixosModulePath - else - null - else - null - ) accounts - ); - - # Create conditional wrapper modules for each user's nixos.nix - # Each wrapper checks if the user is enabled before applying the module content - userNixosModules = lib.mapAttrsToList ( - name: modulePath: - { - config, - lib, - pkgs, - ... - }@args: - let - # Import the user's nixos module - it returns a function or attrset - importedModuleFunc = import modulePath { inherit inputs; }; - # If it's a function, call it with the module args; otherwise use as-is - importedModule = - if lib.isFunction importedModuleFunc then importedModuleFunc args else importedModuleFunc; - in - { - config = lib.mkIf (config.athenix.users.${name}.enable or false) importedModule; - } - ) userNixosModulePaths; - - # Load the host type module - typeFile = ./types + "/${hostType}.nix"; - typeModule = - if builtins.pathExists typeFile then - import typeFile { inherit inputs; } - else - throw "Host type '${hostType}' not found in hosts/types/"; - - # External module from fetchGit/fetchurl - externalPathModule = - if externalModulePath != null then import externalModulePath { inherit inputs; } else { }; - - # Config override module - translate special keys to athenix options - overrideModule = - { ... }: - let - cleanConfig = lib.removeAttrs configOverrides [ - "type" - "count" - "devices" - "overrides" - "defaultCount" - "buildMethods" - ]; - specialConfig = lib.optionalAttrs (configOverrides ? buildMethods) { - athenix.host.buildMethods = configOverrides.buildMethods; - }; - in - { - config = lib.mkMerge [ - cleanConfig - specialConfig - ]; - }; - - allModules = - userNixosModules - ++ [ - (import ./common.nix { inherit inputs; }) - typeModule - overrideModule - { networking.hostName = hostName; } - ] - ++ lib.optional (externalModulePath != null) externalPathModule; - in - { - system = lib.nixosSystem { - inherit system; - specialArgs = { inherit inputs; }; - modules = allModules; - }; - modules = allModules; - }; - - # Process inventory entries - top-level keys are always prefixes - processInventory = lib.mapAttrs ( - prefix: config: - let - hostType = config.type or prefix; - system = config.system or "x86_64-linux"; - devices = config.devices or { }; - hasCount = config ? count; - - # Helper to generate hostname from prefix and suffix - # Numbers get no dash: "nix-surface1", "nix-surface2" - # Letters get dash: "nix-surface-alpha", "nix-surface-beta" - mkHostName = - prefix: suffix: usePrefix: - if !usePrefix then - suffix - else if lib.match "[0-9]+" suffix != null then - "${prefix}${suffix}" # numeric: no dash - else - "${prefix}-${suffix}"; # non-numeric: add dash - - # Extract common overrides and default count - overrides = config.overrides or { }; - defaultCount = config.defaultCount or 0; - - # If devices is a number, treat it as count - devicesValue = config.devices or { }; - actualDevices = if lib.isInt devicesValue then { } else devicesValue; - actualCount = if lib.isInt devicesValue then devicesValue else (config.count or 0); - - # Clean base config - remove inventory management keys - baseConfig = lib.removeAttrs config [ - "type" - "system" - "count" - "devices" - "overrides" - "defaultCount" - ]; - - # Generate hosts from explicit device definitions - deviceHosts = lib.listToAttrs ( - lib.mapAttrsToList ( - deviceKey: deviceConfig: - let - # Check if deviceConfig is a path/derivation (from fetchGit, fetchurl, etc.) - # fetchGit/fetchTarball return an attrset with outPath attribute - isExternalModule = - (builtins.isPath deviceConfig) - || (builtins.isString deviceConfig && lib.hasPrefix "/" deviceConfig) - || (lib.isDerivation deviceConfig) - || (builtins.isAttrs deviceConfig && deviceConfig ? outPath); - - # Extract the actual path from fetchGit/fetchTarball results - extractedPath = - if builtins.isAttrs deviceConfig && deviceConfig ? outPath then - deviceConfig.outPath - else - deviceConfig; - - # If external module, we use base config + overrides as the config - # and pass the module path separately - actualConfig = - if isExternalModule then (lib.recursiveUpdate baseConfig overrides) else deviceConfig; - - # Merge: base config -> overrides -> device-specific config (only if not external module) - mergedConfig = - if isExternalModule then - actualConfig - else - lib.recursiveUpdate (lib.recursiveUpdate baseConfig overrides) deviceConfig; - - # Check useHostPrefix from the merged config - usePrefix = mergedConfig.athenix.host.useHostPrefix or true; - hostName = mkHostName prefix deviceKey usePrefix; - - # If external module, also add a default.nix path for import - externalModulePath = - if isExternalModule then - if builtins.isPath extractedPath then - extractedPath + "/default.nix" - else if lib.isDerivation extractedPath then - extractedPath + "/default.nix" - else - extractedPath + "/default.nix" - else - null; - in - { - name = hostName; - value = mkHost { - inherit - hostName - system - hostType - externalModulePath - ; - configOverrides = mergedConfig; - }; - } - ) actualDevices - ); - - # Generate numbered hosts from count or defaultCount - # If devices are specified, defaultCount fills in the gaps - countToUse = if actualCount > 0 then actualCount else defaultCount; - - # Get which numbered keys are already defined in devices - existingNumbers = lib.filter (k: lib.match "[0-9]+" k != null) (lib.attrNames actualDevices); - - countHosts = - if countToUse > 0 then - lib.listToAttrs ( - lib.filter (x: x != null) ( - map ( - i: - let - deviceKey = toString i; - # Skip if this number is already in explicit devices - alreadyDefined = lib.elem deviceKey existingNumbers; - in - if alreadyDefined then - null - else - let - hostName = mkHostName prefix deviceKey true; - mergedConfig = lib.recursiveUpdate baseConfig overrides; - in - { - name = hostName; - value = mkHost { - inherit hostName system hostType; - configOverrides = mergedConfig; - }; - } - ) (lib.range 1 countToUse) - ) - ) - else - { }; - in - lib.recursiveUpdate deviceHosts countHosts - ) hosts; - - # Flatten the nested structure - allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues processInventory); -in -{ - nixosConfigurations = lib.mapAttrs (n: v: v.system) allHosts; - modules = lib.mapAttrs (n: v: v.modules) allHosts; -} diff --git a/hosts/types/nix-desktop.nix b/hosts/types/nix-desktop.nix deleted file mode 100644 index 2455eba..0000000 --- a/hosts/types/nix-desktop.nix +++ /dev/null @@ -1,51 +0,0 @@ -# ============================================================================ -# Desktop Configuration -# ============================================================================ -# Hardware and boot configuration for standard desktop workstations. -# Includes Intel CPU support and NVMe storage. - -{ inputs, ... }: -{ - config, - lib, - modulesPath, - ... -}: -{ - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; - - # ========== Boot Configuration ========== - - boot.initrd.availableKernelModules = [ - "xhci_pci" # USB 3.0 support - "nvme" # NVMe SSD support - "usb_storage" # USB storage devices - "sd_mod" # SD card support - "sdhci_pci" # SD card host controller - ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support - boot.extraModulePackages = [ ]; - boot.kernelParams = [ - "quiet" # Minimal boot messages - "splash" # Show Plymouth boot splash - "boot.shell_on_fail" # Emergency shell on boot failure - "udev.log_priority=3" # Reduce udev logging - "rd.systemd.show_status=auto" # Show systemd status during boot - ]; - - # ========== Filesystem Configuration ========== - athenix.host.filesystem.swapSize = lib.mkDefault "16G"; - athenix.host.filesystem.device = lib.mkDefault "/dev/nvme0n1"; - athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ]; - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - - # ========== Hardware Configuration ========== - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - - # ========== Software Profile ========== - athenix.sw.enable = lib.mkDefault true; - athenix.sw.type = lib.mkDefault "desktop"; -} diff --git a/hosts/types/nix-ephemeral.nix b/hosts/types/nix-ephemeral.nix deleted file mode 100644 index 51e46c0..0000000 --- a/hosts/types/nix-ephemeral.nix +++ /dev/null @@ -1,67 +0,0 @@ -# ============================================================================ -# Ephemeral/Diskless System Configuration -# ============================================================================ -# Configuration for systems that run entirely from RAM without persistent storage. -# Suitable for kiosks, netboot clients, and stateless workstations. -# All data is lost on reboot. - -{ inputs, ... }: -{ - config, - lib, - modulesPath, - ... -}: -{ - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; - - # ========== Boot Configuration ========== - boot.initrd.availableKernelModules = [ - "xhci_pci" # USB 3.0 support - "nvme" # NVMe support - "usb_storage" # USB storage devices - "sd_mod" # SD card support - "sdhci_pci" # SD card host controller - ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support - boot.extraModulePackages = [ ]; - boot.kernelParams = [ - "quiet" # Minimal boot messages - "splash" # Show Plymouth boot splash - "boot.shell_on_fail" # Emergency shell on boot failure - "udev.log_priority=3" # Reduce udev logging - "rd.systemd.show_status=auto" # Show systemd status during boot - ]; - - # ========== Ephemeral Configuration ========== - # No persistent storage - everything runs from RAM - athenix.host.filesystem.swapSize = lib.mkForce "0G"; - athenix.host.filesystem.device = lib.mkForce "/dev/null"; # Dummy device - athenix.host.buildMethods = lib.mkDefault [ - "iso" # Live ISO image - "ipxe" # Network boot - ]; - - # Disable disk management for RAM-only systems - disko.enableConfig = lib.mkForce false; - - # Define tmpfs root filesystem - fileSystems."/" = { - device = "none"; - fsType = "tmpfs"; - options = [ - "defaults" - "size=50%" - "mode=755" - ]; - }; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - - athenix.sw.enable = lib.mkDefault true; - athenix.sw.type = lib.mkDefault "stateless-kiosk"; -} diff --git a/hosts/types/nix-laptop.nix b/hosts/types/nix-laptop.nix deleted file mode 100644 index 6d9ed5c..0000000 --- a/hosts/types/nix-laptop.nix +++ /dev/null @@ -1,64 +0,0 @@ -# ============================================================================ -# Laptop Configuration -# ============================================================================ -# Hardware and boot configuration for laptop systems with mobile features. -# Includes power management, lid switch handling, and Intel graphics fixes. - -{ inputs, ... }: -{ - config, - lib, - modulesPath, - ... -}: -{ - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; - - # ========== Boot Configuration ========== - - boot.initrd.availableKernelModules = [ - "xhci_pci" # USB 3.0 support - "thunderbolt" # Thunderbolt support - "nvme" # NVMe SSD support - "usb_storage" # USB storage devices - "sd_mod" # SD card support - "sdhci_pci" # SD card host controller - ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support - boot.extraModulePackages = [ ]; - boot.kernelParams = [ - "quiet" # Minimal boot messages - "splash" # Show Plymouth boot splash - "boot.shell_on_fail" # Emergency shell on boot failure - "udev.log_priority=3" # Reduce udev logging - "rd.systemd.show_status=auto" # Show systemd status during boot - "i915.enable_psr=0" # Disable Panel Self Refresh (stability) - "i915.enable_dc=0" # Disable display power saving - "i915.enable_fbc=0" # Disable framebuffer compression - ]; - - # ========== Hardware Configuration ========== - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - - # ========== Filesystem Configuration ========== - athenix.host.filesystem.device = lib.mkDefault "/dev/nvme0n1"; - athenix.host.filesystem.swapSize = lib.mkDefault "34G"; # Larger swap for hibernation - athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ]; - - # ========== Power Management ========== - services.upower.enable = lib.mkDefault true; - services.logind.settings = { - Login = { - HandleLidSwitch = "suspend"; - HandleLidSwitchExternalPower = "suspend"; - HandleLidSwitchDocked = "ignore"; - }; - }; - - athenix.sw.enable = lib.mkDefault true; - athenix.sw.type = lib.mkDefault "desktop"; -} diff --git a/hosts/types/nix-lxc.nix b/hosts/types/nix-lxc.nix deleted file mode 100644 index cecb409..0000000 --- a/hosts/types/nix-lxc.nix +++ /dev/null @@ -1,61 +0,0 @@ -# ============================================================================ -# Proxmox LXC Container Configuration -# ============================================================================ -# Configuration for lightweight Linux containers running in Proxmox. -# Disables boot/disk management and enables remote development support. - -{ inputs, ... }: -{ - config, - lib, - modulesPath, - ... -}: -{ - imports = [ - inputs.vscode-server.nixosModules.default - "${modulesPath}/virtualisation/proxmox-lxc.nix" - ]; - - # ========== Nix Configuration ========== - nix.settings.trusted-users = [ - "root" - "engr-ugaif" - ]; - nix.settings.experimental-features = [ - "nix-command" - "flakes" - ]; - - # ========== Container-Specific Configuration ========== - boot.isContainer = true; - boot.loader.systemd-boot.enable = lib.mkForce false; # No bootloader in container - disko.enableConfig = lib.mkForce false; # No disk management in container - console.enable = true; - - # Allow getty to work in containers - systemd.services."getty@".unitConfig.ConditionPathExists = [ - "" - "/dev/%I" - ]; - - # Suppress unnecessary systemd units for containers - systemd.suppressedSystemUnits = [ - "dev-mqueue.mount" - "sys-kernel-debug.mount" - "sys-fs-fuse-connections.mount" - ]; - - # ========== Remote Development ========== - services.vscode-server.enable = true; - - # ========== System Configuration ========== - system.stateVersion = "25.11"; - athenix.host.buildMethods = lib.mkDefault [ - "lxc" # LXC container tarball - "proxmox" # Proxmox VMA archive - ]; - - athenix.sw.enable = lib.mkDefault true; - athenix.sw.type = lib.mkDefault "headless"; -} diff --git a/hosts/types/nix-surface.nix b/hosts/types/nix-surface.nix deleted file mode 100644 index 53f74c4..0000000 --- a/hosts/types/nix-surface.nix +++ /dev/null @@ -1,69 +0,0 @@ -# ============================================================================ -# Microsoft Surface Tablet Configuration -# ============================================================================ -# Hardware configuration for Surface Go tablets in kiosk mode. -# Uses nixos-hardware module and older kernel for Surface-specific drivers. - -{ inputs, ... }: -{ - config, - lib, - pkgs, - modulesPath, - ... -}: -let - # Use older kernel version for better Surface Go compatibility - refSystem = inputs.nixpkgs-old-kernel.lib.nixosSystem { - system = pkgs.stdenv.hostPlatform.system; - modules = [ inputs.nixos-hardware.nixosModules.microsoft-surface-go ]; - }; - refKernelPackages = refSystem.config.boot.kernelPackages; -in -{ - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - inputs.nixos-hardware.nixosModules.microsoft-surface-go - ]; - - # ========== Boot Configuration ========== - - boot.initrd.availableKernelModules = [ - "xhci_pci" # USB 3.0 support - "nvme" # NVMe support (though Surface uses eMMC) - "usb_storage" # USB storage devices - "sd_mod" # SD card support - "sdhci_pci" # SD card host controller - ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support - boot.extraModulePackages = [ ]; - boot.kernelParams = [ - "quiet" # Minimal boot messages - "splash" # Show Plymouth boot splash - "boot.shell_on_fail" # Emergency shell on boot failure - "udev.log_priority=3" # Reduce udev logging - "rd.systemd.show_status=auto" # Show systemd status during boot - "intel_ipu3_imgu" # Intel camera image processing - "intel_ipu3_isys" # Intel camera sensor interface - "fbcon=map:1" # Framebuffer console mapping - "i915.enable_psr=0" # Disable Panel Self Refresh (breaks resume) - "i915.enable_dc=0" # Disable display power saving - ]; - - # Use older kernel for better Surface hardware support - boot.kernelPackages = lib.mkForce refKernelPackages; - - # ========== Filesystem Configuration ========== - athenix.host.filesystem.swapSize = lib.mkDefault "8G"; - athenix.host.filesystem.device = lib.mkDefault "/dev/mmcblk0"; # eMMC storage # eMMC storage - athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ]; - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - - # ========== Hardware Configuration ========== - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - - # ========== Software Profile ========== - athenix.sw.enable = lib.mkDefault true; - athenix.sw.type = lib.mkDefault "tablet-kiosk"; # Touch-optimized kiosk mode -} diff --git a/hosts/types/nix-wsl.nix b/hosts/types/nix-wsl.nix deleted file mode 100644 index f13787a..0000000 --- a/hosts/types/nix-wsl.nix +++ /dev/null @@ -1,53 +0,0 @@ -# ============================================================================ -# Windows Subsystem for Linux (WSL) Configuration -# ============================================================================ -# Configuration for NixOS running in WSL2 on Windows. -# Integrates with nixos-wsl for WSL-specific functionality. - -{ inputs, ... }: -{ - lib, - config, - ... -}: -{ - imports = [ - inputs.nixos-wsl.nixosModules.default - inputs.vscode-server.nixosModules.default - ]; - - # ========== Options ========== - options.athenix.host.wsl.user = lib.mkOption { - type = lib.types.str; - default = "engr-ugaif"; - description = "The default user to log in as in WSL."; - }; - - config = { - # ========== WSL Configuration ========== - wsl.enable = true; - # Use forUser if set, otherwise fall back to wsl.user option - wsl.defaultUser = - if config.athenix.forUser != null then config.athenix.forUser else config.athenix.host.wsl.user; - - # ========== Software Profile ========== - athenix.sw.enable = lib.mkDefault true; - athenix.sw.type = lib.mkDefault "headless"; - - # ========== Remote Development ========== - services.vscode-server.enable = true; - - # ========== Disable Irrelevant Systems ========== - # WSL doesn't use traditional boot or disk management - disko.enableConfig = lib.mkForce false; - boot.loader.systemd-boot.enable = lib.mkForce false; - boot.loader.grub.enable = lib.mkForce false; - - # WSL manages its own networking - systemd.network.enable = lib.mkForce false; - - # Provide dummy values for required options from boot.nix - athenix.host.filesystem.device = "/dev/null"; - athenix.host.filesystem.swapSize = "0G"; - }; -} diff --git a/hosts/types/nix-zima.nix b/hosts/types/nix-zima.nix deleted file mode 100644 index 1174ff0..0000000 --- a/hosts/types/nix-zima.nix +++ /dev/null @@ -1,50 +0,0 @@ -# ============================================================================ -# Desktop Configuration -# ============================================================================ -# Hardware and boot configuration for standard desktop workstations. -# Includes Intel CPU support and NVMe storage. - -{ inputs, ... }: -{ - config, - lib, - modulesPath, - ... -}: -{ - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; - - # ========== Boot Configuration ========== - - boot.initrd.availableKernelModules = [ - "xhci_pci" # USB 3.0 support - "usb_storage" # USB storage devices - "sd_mod" # SD card support - "sdhci_pci" # SD card host controller - ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support - boot.extraModulePackages = [ ]; - boot.kernelParams = [ - "quiet" # Minimal boot messages - "splash" # Show Plymouth boot splash - "boot.shell_on_fail" # Emergency shell on boot failure - "udev.log_priority=3" # Reduce udev logging - "rd.systemd.show_status=auto" # Show systemd status during boot - ]; - - # ========== Filesystem Configuration ========== - athenix.host.filesystem.useSwap = lib.mkDefault false; - athenix.host.filesystem.device = lib.mkDefault "/dev/mmcblk0"; - athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ]; - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - - # ========== Hardware Configuration ========== - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - - # ========== Software Profile ========== - athenix.sw.enable = lib.mkDefault true; - athenix.sw.type = lib.mkDefault "desktop"; -} diff --git a/hosts/user-config.nix b/hosts/user-config.nix deleted file mode 100644 index 299acef..0000000 --- a/hosts/user-config.nix +++ /dev/null @@ -1,276 +0,0 @@ -{ - pkgs, - config, - lib, - inputs, - ... -}: - -# ============================================================================ -# User Configuration Module -# ============================================================================ -# This module defines the schema for user accounts and handles their creation. -# It bridges the gap between the data in 'users.nix' and the actual NixOS -# and Home Manager configuration. - -let - # Load users.nix to get account definitions - usersData = import ../users.nix { inherit pkgs; }; - accounts = usersData.athenix.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 athenix.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 athenix.users. options - athenixUsers = evaluatedModule.athenix.users or { }; - in - athenixUsers.${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 = { - isNormalUser = lib.mkOption { - type = lib.types.bool; - default = true; - }; - description = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - }; - extraGroups = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - }; - hashedPassword = lib.mkOption { - type = lib.types.str; - default = "!"; - }; - extraPackages = lib.mkOption { - type = lib.types.listOf lib.types.package; - default = [ ]; - }; - excludePackages = lib.mkOption { - type = lib.types.listOf lib.types.package; - default = [ ]; - }; - homePackages = lib.mkOption { - type = lib.types.listOf lib.types.package; - default = [ ]; - }; - extraImports = lib.mkOption { - type = lib.types.listOf lib.types.path; - default = [ ]; - }; - external = lib.mkOption { - type = lib.types.nullOr ( - lib.types.oneOf [ - lib.types.path - lib.types.package - lib.types.attrs - ] - ); - default = null; - description = '' - External user configuration module. Can be: - - A path to a local module directory - - A fetchGit/fetchTarball result pointing to a repository - - The external module can contain: - - user.nix (optional): Sets athenix.users. options AND home-manager config - - nixos.nix (optional): System-level NixOS configuration - - Example: builtins.fetchGit { url = "https://github.com/user/dotfiles"; rev = "..."; } - ''; - }; - opensshKeys = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "List of SSH public keys for the user."; - }; - shell = lib.mkOption { - type = lib.types.nullOr lib.types.package; - default = null; - description = "The shell for this user."; - }; - editor = lib.mkOption { - type = lib.types.nullOr lib.types.package; - default = null; - description = "The default editor for this user."; - }; - useZshTheme = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to apply the system Zsh theme."; - }; - useNvimPlugins = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to apply the system Neovim configuration."; - }; - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Whether this user account is enabled on this system."; - }; - }; - }; -in -{ - - options.athenix.users = lib.mkOption { - type = lib.types.attrsOf userSubmodule; - default = { }; - description = "User accounts configuration. Set enable=true for users that should exist on this system."; - }; - - 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) - athenix.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 - enabledAccounts = lib.filterAttrs (_: user: user.enable) config.athenix.users; - in - lib.mapAttrs ( - name: user: - let - isPlasma6 = config.services.desktopManager.plasma6.enable; - defaultPackages = lib.optionals (isPlasma6 && name != "root") [ pkgs.kdePackages.kate ]; - finalPackages = lib.subtractLists user.excludePackages (defaultPackages ++ user.extraPackages); - in - { - inherit (user) isNormalUser extraGroups hashedPassword; - description = if user.description != null then user.description else lib.mkDefault ""; - openssh.authorizedKeys.keys = user.opensshKeys; - packages = finalPackages; - shell = if user.shell != null then user.shell else pkgs.bash; - } - ) enabledAccounts; - - # Home Manager configs per user - home-manager = { - useGlobalPkgs = true; - useUserPackages = true; - extraSpecialArgs = { - osConfig = config; - inherit inputs; - }; - - users = - let - enabledAccounts = lib.filterAttrs (_: user: user.enable) config.athenix.users; - in - lib.mapAttrs ( - name: user: - let - # 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; - - # Import external user.nix for home-manager (filter out athenix.* options) - externalUserModule = - if hasExternalUser then - let - fullModule = import userNixPath { inherit inputs; }; - in - # Only pass through non-athenix options to home-manager - { - config, - lib, - pkgs, - osConfig, - ... - }: - let - evaluated = fullModule { - inherit - config - lib - pkgs - osConfig - ; - }; - in - lib.filterAttrs (attrName: _: attrName != "athenix") evaluated - else - { }; - - # 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 - lib.mkMerge [ - { - imports = allImports; - - # Always set these required options - home.username = name; - home.homeDirectory = if name == "root" then "/root" else "/home/${name}"; - home.stateVersion = "25.11"; - } - (lib.mkIf (!hasExternal) { - # For local users only, add their packages - home.packages = user.homePackages; - }) - ] - ) enabledAccounts; - }; - }; -} -- 2.39.5 From c3bbf6f8be3ff96680cb277bf6377304f1ab5adf Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Tue, 6 Jan 2026 18:31:58 -0500 Subject: [PATCH 4/8] refactor: update imports to use glue/ and variants/ - flake.nix: import glue/fleet.nix instead of hosts/ - installer/artifacts.nix: use 'fleet' parameter instead of 'hosts' - installer/modules.nix: auto-import from variants/ directory --- flake.nix | 10 ++++----- installer/artifacts.nix | 18 ++++++++-------- installer/modules.nix | 47 +++++++++++++---------------------------- 3 files changed, 29 insertions(+), 46 deletions(-) diff --git a/flake.nix b/flake.nix index 1bc6f2b..74acc6a 100644 --- a/flake.nix +++ b/flake.nix @@ -69,10 +69,10 @@ ... }: let - hosts = import ./hosts { inherit inputs; }; + fleet = import ./glue/fleet.nix { inherit inputs; }; linuxSystem = "x86_64-linux"; artifacts = import ./installer/artifacts.nix { - inherit inputs hosts self; + inherit inputs fleet self; system = linuxSystem; }; forAllSystems = nixpkgs.lib.genAttrs [ @@ -86,13 +86,13 @@ # Formatter for 'nix fmt' formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixfmt-rfc-style); - # Generate NixOS configurations from hosts/default.nix - nixosConfigurations = hosts.nixosConfigurations; + # Generate NixOS configurations from fleet generator + nixosConfigurations = fleet.nixosConfigurations; # Expose artifacts to all systems, but they are always built for x86_64-linux packages = forAllSystems (_: artifacts); - # Expose modules for external use + # Expose host type modules and installer modules for external use nixosModules = import ./installer/modules.nix { inherit inputs; }; # Templates for external configurations diff --git a/installer/artifacts.nix b/installer/artifacts.nix index 3e835b6..877721a 100644 --- a/installer/artifacts.nix +++ b/installer/artifacts.nix @@ -1,6 +1,6 @@ { inputs, - hosts, + fleet, self, system, }: @@ -45,7 +45,7 @@ let nixos-generators.nixosGenerate { inherit system; specialArgs = { inherit inputs; }; - modules = hosts.modules.${hostName} ++ [ + modules = fleet.modules.${hostName} ++ [ { disko.enableConfig = lib.mkForce false; services.upower.enable = lib.mkForce false; @@ -61,7 +61,7 @@ let nixpkgs.lib.nixosSystem { inherit system; specialArgs = { inherit inputs; }; - modules = hosts.modules.${hostName} ++ [ + modules = fleet.modules.${hostName} ++ [ "${nixpkgs}/nixos/modules/installer/netboot/netboot.nix" { disko.enableConfig = lib.mkForce false; @@ -70,14 +70,14 @@ let ]; }; - hostNames = builtins.attrNames hosts.nixosConfigurations; + hostNames = builtins.attrNames fleet.nixosConfigurations; # Generate installer ISOs for hosts that have "installer-iso" in their buildMethods installerPackages = lib.listToAttrs ( lib.concatMap ( name: let - cfg = hosts.nixosConfigurations.${name}; + cfg = fleet.nixosConfigurations.${name}; in if lib.elem "installer-iso" cfg.config.athenix.host.buildMethods then [ @@ -96,7 +96,7 @@ let lib.concatMap ( name: let - cfg = hosts.nixosConfigurations.${name}; + cfg = fleet.nixosConfigurations.${name}; in if lib.elem "iso" cfg.config.athenix.host.buildMethods then [ @@ -115,7 +115,7 @@ let lib.concatMap ( name: let - cfg = hosts.nixosConfigurations.${name}; + cfg = fleet.nixosConfigurations.${name}; in if lib.elem "ipxe" cfg.config.athenix.host.buildMethods then [ @@ -145,7 +145,7 @@ let lib.concatMap ( name: let - cfg = hosts.nixosConfigurations.${name}; + cfg = fleet.nixosConfigurations.${name}; in if lib.elem "lxc" cfg.config.athenix.host.buildMethods then [ @@ -164,7 +164,7 @@ let lib.concatMap ( name: let - cfg = hosts.nixosConfigurations.${name}; + cfg = fleet.nixosConfigurations.${name}; in if lib.elem "proxmox" cfg.config.athenix.host.buildMethods then [ diff --git a/installer/modules.nix b/installer/modules.nix index fae1d78..d69a837 100644 --- a/installer/modules.nix +++ b/installer/modules.nix @@ -6,40 +6,23 @@ # # Usage in another flake: # # Full host type configurations (includes hardware + software + system config) -# inputs.nixos-systems.nixosModules.nix-desktop -# inputs.nixos-systems.nixosModules.nix-laptop +# inputs.athenix.nixosModules.nix-desktop +# inputs.athenix.nixosModules.nix-laptop # -# # Software-only configurations (for custom hardware setups) -# # Note: These include theme.nix in home-manager.sharedModules automatically -# inputs.nixos-systems.nixosModules.sw-desktop -# inputs.nixos-systems.nixosModules.sw-headless -# -# # Home Manager modules (user-level configuration) -# # Theme module (no parameters): -# home-manager.users.myuser.imports = [ inputs.nixos-systems.homeManagerModules.theme ]; -# -# # Neovim module (requires user parameter): -# home-manager.users.myuser.imports = [ -# (inputs.nixos-systems.homeManagerModules.nvim { -# user = config.athenix.users.accounts.myuser; -# }) -# ]; +# # Software-only configuration (for custom hardware setups) +# inputs.athenix.nixosModules.sw { inputs }: -{ - # ========== Full Host Type Modules ========== - # Complete system configurations including hardware, boot, and software - nix-desktop = import ../hosts/types/nix-desktop.nix { inherit inputs; }; # Desktop workstations - nix-laptop = import ../hosts/types/nix-laptop.nix { inherit inputs; }; # Laptop systems - nix-surface = import ../hosts/types/nix-surface.nix { inherit inputs; }; # Surface tablets - nix-lxc = import ../hosts/types/nix-lxc.nix { inherit inputs; }; # Proxmox containers - nix-wsl = import ../hosts/types/nix-wsl.nix { inherit inputs; }; # WSL2 systems - nix-ephemeral = import ../hosts/types/nix-ephemeral.nix { inherit inputs; }; # Diskless/RAM-only - - # ========== Software Configuration Module ========== - # Main software module with all athenix.sw options +# Automatically import all variant modules from variants/ directory +# This returns an attribute set like: { nix-desktop = ...; nix-laptop = ...; nix-lxc = ...; sw = ...; } +(import ../variants { inherit inputs; }) +// { + # Software configuration module - main module with all athenix.sw options # Use athenix.sw.type to select profile: "desktop", "tablet-kiosk", "headless", "stateless-kiosk" - # Use athenix.sw.extraPackages to add additional packages - # Use athenix.sw.kioskUrl to set kiosk mode URL - sw = { inputs, ... }@args: (import ../sw/default.nix (args // { inherit inputs; })); + sw = + { + inputs, + ... + }@args: + (import ../sw/default.nix (args // { inherit inputs; })); } -- 2.39.5 From faf7bb635eee6aa619cd87ba2a3aa4877d4e12d3 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Tue, 6 Jan 2026 18:32:06 -0500 Subject: [PATCH 5/8] feat: add lazy evaluation for external modules in inventory - External modules now use 'external' field for lazy evaluation - Only fetched when building specific host (not during flake check) - Improves rebuild performance for unrelated hosts - Update examples and documentation in inventory.nix header --- inventory.nix | 174 ++++++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 99 deletions(-) diff --git a/inventory.nix b/inventory.nix index f292de7..f4763d0 100644 --- a/inventory.nix +++ b/inventory.nix @@ -1,72 +1,78 @@ +# ============================================================================ +# Fleet Inventory +# ============================================================================ +# Top-level keys are ALWAYS hostname prefixes. Actual hostnames are generated +# from the devices map or count. +# +# Hostname generation rules: +# - Numeric suffixes: no dash (e.g., "nix-surface1", "nix-surface2") +# - Non-numeric suffixes: add dash (e.g., "nix-surface-alpha", "nix-surface-beta") +# - Set athenix.host.useHostPrefix = false to use suffix as full hostname +# +# Format: +# "prefix" = { +# type = "nix-desktop"; # Optional: defaults to prefix name +# system = "x86_64-linux"; # Optional: default is x86_64-linux +# +# # Option 1: Simple count (quick syntax) +# devices = 5; # Creates: prefix1, prefix2, ..., prefix5 +# +# # Option 2: Explicit count +# count = 5; # Creates: prefix1, prefix2, ..., prefix5 +# +# # Option 3: Default count (for groups with mixed devices) +# defaultCount = 3; # Creates default numbered hosts +# +# # Option 4: Named device configurations +# devices = { +# "1" = { ... }; # Creates: prefix1 +# "alpha" = { ... }; # Creates: prefix-alpha +# "custom" = { # Creates: custom (no prefix) +# athenix.host.useHostPrefix = false; +# }; +# }; +# +# # Common config for all devices in this group +# overrides = { +# athenix.users.user1.enable = true; # Applied to all devices in this group +# # ... any other config +# }; +# }; +# +# Convenience options: +# athenix.forUser = "username"; # Automatically enables user (sets athenix.users.username.enable = true) +# +# External modules (instead of config): +# Device values can be a config attrset with an optional 'external' field: +# devices."hostname" = { +# external = builtins.fetchGit { ... }; # Lazy: only fetched when building this host +# # ... additional config options +# }; +# The external module will be imported and evaluated only when this specific host is built. +# +# Examples: +# "lab" = { devices = 3; }; # Quick: lab1, lab2, lab3 +# "lab" = { count = 3; }; # Same as above +# "kiosk" = { +# defaultCount = 2; # kiosk1, kiosk2 (default) +# devices."special" = {}; # kiosk-special (custom) +# }; +# "laptop" = { +# devices = 5; +# overrides.athenix.users.student.enable = true; # All 5 laptops get this user +# }; +# "wsl" = { +# devices."alice".athenix.forUser = "alice123"; # Sets up for user alice123 +# }; +# "external" = { +# devices."remote".external = builtins.fetchGit { # External module via Git (lazy) +# url = "https://github.com/example/config"; +# rev = "e1ccd7cc3e709afe4f50b0627e1c4bde49165014"; +# }; +# }; + { - # ============================================================================ - # Fleet Inventory - # ============================================================================ - # Top-level keys are ALWAYS hostname prefixes. Actual hostnames are generated - # from the devices map or count. - # - # Hostname generation rules: - # - Numeric suffixes: no dash (e.g., "nix-surface1", "nix-surface2") - # - Non-numeric suffixes: add dash (e.g., "nix-surface-alpha", "nix-surface-beta") - # - Set athenix.host.useHostPrefix = false to use suffix as full hostname - # - # Format: - # "prefix" = { - # type = "nix-desktop"; # Optional: defaults to prefix name - # system = "x86_64-linux"; # Optional: default is x86_64-linux - # - # # Option 1: Simple count (quick syntax) - # devices = 5; # Creates: prefix1, prefix2, ..., prefix5 - # - # # Option 2: Explicit count - # count = 5; # Creates: prefix1, prefix2, ..., prefix5 - # - # # Option 3: Default count (for groups with mixed devices) - # defaultCount = 3; # Creates default numbered hosts - # - # # Option 4: Named device configurations - # devices = { - # "1" = { ... }; # Creates: prefix1 - # "alpha" = { ... }; # Creates: prefix-alpha - # "custom" = { # Creates: custom (no prefix) - # athenix.host.useHostPrefix = false; - # }; - # }; - # - # # Common config for all devices in this group - # overrides = { - # athenix.users.user1.enable = true; # Applied to all devices in this group - # # ... any other config - # }; - # }; - # - # Convenience options: - # athenix.forUser = "username"; # Automatically enables user (sets athenix.users.username.enable = true) - # - # External modules (instead of config): - # Device values can be either a config attrset OR a fetchGit/fetchurl call - # that points to an external Nix module. The module will be imported and evaluated. - # - # Examples: - # "lab" = { devices = 3; }; # Quick: lab1, lab2, lab3 - # "lab" = { count = 3; }; # Same as above - # "kiosk" = { - # defaultCount = 2; # kiosk1, kiosk2 (default) - # devices."special" = {}; # kiosk-special (custom) - # }; - # "laptop" = { - # devices = 5; - # overrides.athenix.users.student.enable = true; # All 5 laptops get this user - # }; - # "wsl" = { - # devices."alice".athenix.forUser = "alice123"; # Sets up for user alice123 - # }; - # "external" = { - # devices."remote" = builtins.fetchGit { # External module via Git - # url = "https://github.com/example/config"; - # rev = "e1ccd7cc3e709afe4f50b0627e1c4bde49165014"; - # }; - # }; # ========== Lab Laptops ========== + # ========== Lab Laptops ========== # Creates: nix-laptop1, nix-laptop2 # Both get hdh20267 user via overrides nix-laptop = { @@ -120,7 +126,7 @@ }; }; }; - "usda-dash" = builtins.fetchGit { + "usda-dash".external = builtins.fetchGit { url = "https://git.factory.uga.edu/MODEL/usda-dash-config.git"; rev = "dab32f5884895cead0fae28cb7d88d17951d0c12"; submodules = true; @@ -146,34 +152,4 @@ # ========== Ephemeral/Netboot System ========== # Creates: nix-ephemeral1 nix-ephemeral.devices = 1; - - # ========== Example: External Module Configurations ========== - # Uncomment to use external modules from Git repositories: - # - # external-systems = { - # devices = { - # # Option 1: fetchGit with specific revision (recommended for reproducibility) - # "prod-server" = builtins.fetchGit { - # url = "https://github.com/example/server-config"; - # rev = "e1ccd7cc3e709afe4f50b0627e1c4bde49165014"; # Full commit hash - # ref = "main"; # Optional: branch/tag name - # }; - # - # # Option 2: fetchGit with latest from branch (less reproducible) - # "dev-server" = builtins.fetchGit { - # url = "https://github.com/example/server-config"; - # ref = "develop"; - # }; - # - # # Option 3: fetchTarball for specific release - # "test-server" = builtins.fetchTarball { - # url = "https://github.com/example/server-config/archive/v1.0.0.tar.gz"; - # sha256 = "sha256:0000000000000000000000000000000000000000000000000000"; - # }; - # - # # Option 4: Mix external module with local overrides - # # Note: The external module's default.nix should export a NixOS module - # # that accepts { inputs, ... } as parameters - # }; - # }; } -- 2.39.5 From 6972a999ca2e8cfc7683d2e2abb65a431d59fb13 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Tue, 6 Jan 2026 18:32:18 -0500 Subject: [PATCH 6/8] docs: update all references from hosts/ to glue/ and variants/ - Update README.md structure section - Update DEVELOPMENT.md, EXTERNAL_MODULES.md, INVENTORY.md - Update GitHub Copilot instructions - Update PROXMOX_LXC.md references - Clarify new directory organization and purpose --- .github/copilot-instructions.md | 5 +++-- README.md | 25 ++++++++++++++----------- docs/DEVELOPMENT.md | 4 ++-- docs/EXTERNAL_MODULES.md | 28 ++++++++++++++++++++++------ docs/INVENTORY.md | 4 ++-- installer/PROXMOX_LXC.md | 2 +- 6 files changed, 44 insertions(+), 24 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c6a0cb6..ddf2e70 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -26,8 +26,9 @@ This is a **NixOS system configuration repository** that uses: - **`flake.nix`**: Entry point - inputs and outputs only - **`inventory.nix`**: Fleet definitions - host configurations - **`users.nix`**: User account definitions -- **`hosts/`**: Host generation logic and hardware types -- **`sw/`**: Software configurations organized by system type +- **`variants/`**: Hardware type modules (desktop, laptop, surface, lxc, wsl, etc.) +- **`glue/`**: Fleet generation logic and common system configuration +- **`sw/`**: Software configurations by system type - **`installer/`**: Build artifact generation (ISO, LXC, etc.) - **`templates/`**: Templates for external configurations diff --git a/README.md b/README.md index 0d1dc3b..8e2d0e7 100644 --- a/README.md +++ b/README.md @@ -54,18 +54,21 @@ users.nix # User account definitions flake.lock # Locked dependency versions -hosts/ # Host generation logic -├── default.nix # Main host generator +variants/ # Hardware type modules (exportable as nixosModules) +├── default.nix # Auto-exports all variant types +├── nix-desktop.nix # Desktop workstations +├── nix-laptop.nix # Laptop systems +├── nix-surface.nix # Surface Pro tablets +├── nix-lxc.nix # LXC containers +├── nix-wsl.nix # WSL instances +├── nix-zima.nix # ZimaBoard systems +└── nix-ephemeral.nix # Diskless/netboot systems + +glue/ # Fleet generation and common configuration +├── fleet.nix # Processes inventory.nix to generate all hosts +├── common.nix # Common NixOS configuration (all hosts) ├── boot.nix # Boot and filesystem configuration -├── common.nix # Common system configuration -├── user-config.nix # User configuration integration -└── types/ # Hardware type modules - ├── nix-desktop.nix - ├── nix-laptop.nix - ├── nix-surface.nix - ├── nix-lxc.nix - ├── nix-wsl.nix - └── nix-ephemeral.nix +└── user-config.nix # User account and home-manager integration sw/ # Software configurations by system type ├── default.nix # Software module entry point diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 4ad8558..316b3ae 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -87,7 +87,7 @@ athenix.users.newuser = { }; ``` -2. Enable on hosts in `inventory.nix`: +2. Enable on fleet in `inventory.nix`: ```nix nix-laptop = { @@ -294,7 +294,7 @@ For system config: ```nix # inventory.nix nix-lxc = { - devices."server" = builtins.fetchGit { + devices."server".external = builtins.fetchGit { url = "https://git.factory.uga.edu/org/server-config"; rev = "abc123..."; }; diff --git a/docs/EXTERNAL_MODULES.md b/docs/EXTERNAL_MODULES.md index 48203e1..a94dfef 100644 --- a/docs/EXTERNAL_MODULES.md +++ b/docs/EXTERNAL_MODULES.md @@ -28,26 +28,42 @@ External system modules provide host-specific NixOS configurations. ### Usage -In `inventory.nix`, reference an external module as a device: +In `inventory.nix`, reference an external module using the `external` field: ```nix nix-lxc = { devices = { - # Inline configuration + # Inline configuration (traditional method) "local-server" = { athenix.sw.type = "headless"; services.nginx.enable = true; }; - # External module - "remote-server" = builtins.fetchGit { + # External module (lazy evaluation - fetched only when building this host) + "remote-server".external = builtins.fetchGit { url = "https://git.factory.uga.edu/org/server-config"; rev = "abc123def456..."; # Must pin to specific commit }; + + # External module with additional local config + "mixed-server" = { + external = builtins.fetchGit { + url = "https://git.factory.uga.edu/org/server-config"; + rev = "abc123def456..."; + }; + # Additional local overrides + athenix.users.admin.enable = true; + services.openssh.permitRootLogin = "no"; + }; }; }; ``` +**Key Features:** +- **Lazy Evaluation**: External modules are only fetched when building the specific host +- **Efficient Rebuilds**: Other hosts can be rebuilt without fetching unrelated external modules +- **Submodule Support**: Works with Git submodules without affecting other hosts + ### Repository Structure ``` @@ -99,8 +115,8 @@ server-config/ When a host is built, modules load in this order: -1. Hardware type module (from `hosts/types/nix-*.nix`) -2. Host common configuration (from `hosts/common.nix`) +1. Hardware type module (from `variants/nix-*.nix`) +2. Common system configuration (from `glue/common.nix`) 3. Software type module (from `sw/{type}/`) 4. User NixOS modules (from `users.nix` - `nixos.nix` files) 5. Device-specific overrides (from `inventory.nix`) diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index f83b5e8..20eaacd 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -123,11 +123,11 @@ nix-surface = { ### Method 4: External Module -Reference a Git repository instead of inline configuration: +Reference a Git repository using the `external` field (lazy evaluation): ```nix nix-lxc = { - devices."builder" = builtins.fetchGit { + devices."builder".external = builtins.fetchGit { url = "https://git.factory.uga.edu/org/builder-config"; rev = "abc123..."; }; diff --git a/installer/PROXMOX_LXC.md b/installer/PROXMOX_LXC.md index f573405..057e3c9 100644 --- a/installer/PROXMOX_LXC.md +++ b/installer/PROXMOX_LXC.md @@ -46,7 +46,7 @@ Add the host to `inventory.nix` with the `nix-lxc` type or ensure it has the app } ``` -Your host type configuration (`hosts/types/nix-lxc.nix`) should include: +Your host type configuration (`variants/nix-lxc.nix`) should include: ```nix { -- 2.39.5 From 55c49d84b50c92b2cc61e2c26f3915f157dc9e18 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Tue, 6 Jan 2026 18:34:21 -0500 Subject: [PATCH 7/8] chore: run nix fmt --- variants/default.nix | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/variants/default.nix b/variants/default.nix index 8c4fcc7..d91f415 100644 --- a/variants/default.nix +++ b/variants/default.nix @@ -14,16 +14,10 @@ let files = readDir ./.; # Keep only regular *.nix files except default.nix - nixFiles = - filterAttrs - (name: type: - type == "regular" - && lib.hasSuffix ".nix" name - && name != "default.nix") - files; + nixFiles = filterAttrs ( + name: type: type == "regular" && lib.hasSuffix ".nix" name && name != "default.nix" + ) files; moduleNames = map (name: removeSuffix ".nix" name) (attrNames nixFiles); in -genAttrs moduleNames - (name: - import ./${name}.nix { inherit inputs; }) +genAttrs moduleNames (name: import ./${name}.nix { inherit inputs; }) -- 2.39.5 From b3e274484f50051ff00bf30a42343f640b0f62eb Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Tue, 6 Jan 2026 18:36:01 -0500 Subject: [PATCH 8/8] chore: remove unused assets directory --- assets/plymouth-theme | 1 - 1 file changed, 1 deletion(-) delete mode 160000 assets/plymouth-theme diff --git a/assets/plymouth-theme b/assets/plymouth-theme deleted file mode 160000 index 8658f4f..0000000 --- a/assets/plymouth-theme +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8658f4fb40039ab859f3ec166e0452bb7389e8ee -- 2.39.5