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/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 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/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/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/hosts/default.nix b/glue/fleet.nix similarity index 75% rename from hosts/default.nix rename to glue/fleet.nix index 9ca57fd..e0714b2 100644 --- a/hosts/default.nix +++ b/glue/fleet.nix @@ -1,36 +1,23 @@ { inputs, - hosts ? import ../inventory.nix, + fleet ? import ../inventory.nix, ... }: # ============================================================================ -# Host Generator +# 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. -# -# 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; + + # Load all available host types from hosts/ + hostTypes = import ../variants { inherit inputs; }; + # Helper to create a single NixOS system configuration mkHost = { @@ -38,9 +25,28 @@ let system ? "x86_64-linux", hostType, configOverrides ? { }, - externalModulePath ? null, + 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; }; @@ -94,13 +100,10 @@ let } ) userNixosModulePaths; - # Load the host type module - typeFile = ./types + "/${hostType}.nix"; + # Get the host type module from the hostTypes attribute set typeModule = - if builtins.pathExists typeFile then - import typeFile { inherit inputs; } - else - throw "Host type '${hostType}' not found in hosts/types/"; + hostTypes.${hostType} + or (throw "Host type '${hostType}' not found. Available types: ${lib.concatStringsSep ", " (lib.attrNames hostTypes)}"); # External module from fetchGit/fetchurl externalPathModule = @@ -132,6 +135,7 @@ let allModules = userNixosModules ++ [ + (import ./common.nix { inherit inputs; }) typeModule overrideModule { networking.hostName = hostName; } @@ -192,48 +196,22 @@ let 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); + # Check if deviceConfig has an 'external' field for lazy evaluation + hasExternalField = builtins.isAttrs deviceConfig && deviceConfig ? external; - # Extract the actual path from fetchGit/fetchTarball results - extractedPath = - if builtins.isAttrs deviceConfig && deviceConfig ? outPath then - deviceConfig.outPath - else - deviceConfig; + # Extract external module thunk if present (don't evaluate yet!) + externalModuleThunk = if hasExternalField then deviceConfig.external else null; - # 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; + # Remove 'external' from config to avoid conflicts + cleanDeviceConfig = + if hasExternalField then lib.removeAttrs deviceConfig [ "external" ] 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; + # 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; - - # 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; @@ -242,7 +220,7 @@ let hostName system hostType - externalModulePath + externalModuleThunk ; configOverrides = mergedConfig; }; @@ -289,7 +267,7 @@ let { }; in lib.recursiveUpdate deviceHosts countHosts - ) hosts; + ) fleet; # Flatten the nested structure allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues processInventory); diff --git a/hosts/user-config.nix b/glue/user-config.nix similarity index 100% rename from hosts/user-config.nix rename to glue/user-config.nix diff --git a/hosts/boot.nix b/hosts/boot.nix deleted file mode 100644 index a186612..0000000 --- a/hosts/boot.nix +++ /dev/null @@ -1,194 +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 (ISO, iPXE, LXC, Proxmox) -# - Garbage collection settings -# - 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)."; - }; - 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."; - }; - }; - 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; - }) - - # 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" - ]; - }; - }; - - # 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 - ''; - - system.stateVersion = "25.11"; # Did you read the comment? - } - ]; -} diff --git a/hosts/common.nix b/hosts/common.nix deleted file mode 100644 index 908faeb..0000000 --- a/hosts/common.nix +++ /dev/null @@ -1,47 +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 - ]; - - 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/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 { 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; })); } 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 - # }; - # }; } diff --git a/variants/default.nix b/variants/default.nix new file mode 100644 index 0000000..d91f415 --- /dev/null +++ b/variants/default.nix @@ -0,0 +1,23 @@ +# ============================================================================ +# 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/hosts/types/nix-desktop.nix b/variants/nix-desktop.nix similarity index 97% rename from hosts/types/nix-desktop.nix rename to variants/nix-desktop.nix index fc5e0e2..2455eba 100644 --- a/hosts/types/nix-desktop.nix +++ b/variants/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/variants/nix-ephemeral.nix similarity index 97% rename from hosts/types/nix-ephemeral.nix rename to variants/nix-ephemeral.nix index f4f7504..51e46c0 100644 --- a/hosts/types/nix-ephemeral.nix +++ b/variants/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/variants/nix-laptop.nix similarity index 97% rename from hosts/types/nix-laptop.nix rename to variants/nix-laptop.nix index c439ceb..6d9ed5c 100644 --- a/hosts/types/nix-laptop.nix +++ b/variants/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/variants/nix-lxc.nix similarity index 97% rename from hosts/types/nix-lxc.nix rename to variants/nix-lxc.nix index fb7574e..cecb409 100644 --- a/hosts/types/nix-lxc.nix +++ b/variants/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/variants/nix-surface.nix similarity index 98% rename from hosts/types/nix-surface.nix rename to variants/nix-surface.nix index 78a8fe8..53f74c4 100644 --- a/hosts/types/nix-surface.nix +++ b/variants/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/variants/nix-wsl.nix similarity index 97% rename from hosts/types/nix-wsl.nix rename to variants/nix-wsl.nix index 14e0313..f13787a 100644 --- a/hosts/types/nix-wsl.nix +++ b/variants/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/variants/nix-zima.nix similarity index 97% rename from hosts/types/nix-zima.nix rename to variants/nix-zima.nix index 40a9cec..1174ff0 100644 --- a/hosts/types/nix-zima.nix +++ b/variants/nix-zima.nix @@ -13,7 +13,6 @@ }: { imports = [ - (import ../common.nix { inherit inputs; }) (modulesPath + "/installer/scan/not-detected.nix") ];