fix: Respect nvim user config option

This commit is contained in:
UGA Innovation Factory
2025-12-16 13:47:50 -05:00
committed by Hunter Halloran
parent 1c71bf099e
commit 3b0c147b3f
8 changed files with 321 additions and 159 deletions

View File

@@ -13,7 +13,22 @@
{ {
options.ugaif = { options.ugaif = {
forUser = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Convenience option to configure a host for a specific user.
Automatically adds the user to extraUsers and sets wslUser for WSL hosts.
Value should be a username from ugaif.users.accounts.
'';
};
host = { host = {
useHostPrefix = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to prepend the host prefix to the hostname (used in inventory).";
};
filesystem = { filesystem = {
device = lib.mkOption { device = lib.mkOption {
type = lib.types.str; type = lib.types.str;

View File

@@ -8,10 +8,25 @@
# Host Generator # Host Generator
# ============================================================================ # ============================================================================
# This file contains the logic to generate NixOS configurations for all hosts # This file contains the logic to generate NixOS configurations for all hosts
# defined in inventory.nix. It handles: # defined in inventory.nix. It supports both hostname-based and count-based
# 1. Common module imports (boot, users, software). # configurations with flexible type associations.
# 2. Host-specific overrides (filesystem, enabled users). #
# 3. External flake integration for system overrides. # Inventory format:
# {
# "my-hostname" = {
# type = "nix-desktop"; # Host type module to use
# system = "x86_64-linux"; # Optional
# # ... any ugaif.* 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 let
nixpkgs = inputs.nixpkgs; nixpkgs = inputs.nixpkgs;
@@ -21,11 +36,11 @@ let
{ {
hostName, hostName,
system ? "x86_64-linux", system ? "x86_64-linux",
extraModules ? [ ], hostType,
configOverrides ? { },
}: }:
let let
# Load users.nix to find external user flakes # Load users.nix to find external user flakes
# We use legacyPackages to evaluate the simple data structure of users.nix
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
usersData = import ../users.nix { inherit pkgs; }; usersData = import ../users.nix { inherit pkgs; };
accounts = usersData.ugaif.users.accounts or { }; accounts = usersData.ugaif.users.accounts or { };
@@ -39,111 +54,177 @@ let
{ } { }
) accounts; ) accounts;
# 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 flake override if specified
externalFlakeModule =
if configOverrides ? flakeUrl then
(builtins.getFlake configOverrides.flakeUrl).nixosModules.default
else
{ };
# Config override module - translate special keys to ugaif options
overrideModule =
{ ... }:
let
cleanConfig = lib.removeAttrs configOverrides [
"type"
"count"
"devices"
"overrides"
"defaultCount"
"extraUsers"
"flakeUrl"
"hostname"
"buildMethods"
"wslUser"
];
specialConfig = lib.mkMerge [
(lib.optionalAttrs (configOverrides ? extraUsers) {
ugaif.users.enabledUsers = configOverrides.extraUsers;
})
(lib.optionalAttrs (configOverrides ? buildMethods) {
ugaif.host.buildMethods = configOverrides.buildMethods;
})
(lib.optionalAttrs (configOverrides ? wslUser) {
ugaif.host.wsl.user = configOverrides.wslUser;
})
];
in
{
config = lib.mkMerge [
cleanConfig
specialConfig
];
};
allModules = allModules =
userFlakeModules userFlakeModules
++ extraModules
++ [ ++ [
typeModule
overrideModule
{ networking.hostName = hostName; } { networking.hostName = hostName; }
]; ]
++ lib.optional (configOverrides ? flakeUrl) externalFlakeModule;
in in
{ {
system = lib.nixosSystem { system = lib.nixosSystem {
inherit system; inherit system;
specialArgs = { inherit inputs; }; specialArgs = { inherit inputs; };
modules = allModules; modules = allModules;
}; };
modules = allModules; modules = allModules;
}; };
# Function to generate a set of hosts based on inventory count and overrides # Process inventory entries - top-level keys are always prefixes
mkHostGroup = processInventory = lib.mapAttrs (
{ prefix: config:
prefix,
count,
system ? "x86_64-linux",
extraModules ? [ ],
deviceOverrides ? { },
}:
lib.listToAttrs (
lib.concatMap (
i:
let
defaultName = "${prefix}${toString i}";
devConf = deviceOverrides.${toString i} or { };
hasOverride = builtins.hasAttr (toString i) deviceOverrides;
hostName =
if hasOverride && (builtins.hasAttr "hostname" devConf) then devConf.hostname else defaultName;
# Extract flakeUrl if it exists
externalFlake =
if hasOverride && (builtins.hasAttr "flakeUrl" devConf) then
builtins.getFlake devConf.flakeUrl
else
null;
# Module from external flake
externalModule = if externalFlake != null then externalFlake.nixosModules.default else { };
# Config override module (filesystem, users)
overrideModule =
{ ... }:
let
# Extract device-specific config, removing special keys that need custom handling
baseConfig = lib.removeAttrs devConf [
"extraUsers"
"flakeUrl"
"hostname"
"buildMethods"
"wslUser"
];
# Handle special keys that map to specific ugaif options
specialConfig = lib.mkMerge [
(lib.optionalAttrs (devConf ? extraUsers) { ugaif.users.enabledUsers = devConf.extraUsers; })
(lib.optionalAttrs (devConf ? buildMethods) { ugaif.host.buildMethods = devConf.buildMethods; })
(lib.optionalAttrs (devConf ? wslUser) { ugaif.host.wsl.user = devConf.wslUser; })
];
in
lib.mkIf hasOverride (lib.recursiveUpdate baseConfig specialConfig);
config = mkHost {
hostName = hostName;
inherit system;
extraModules =
extraModules ++ [ overrideModule ] ++ (lib.optional (externalFlake != null) externalModule);
};
aliasNames = lib.optional (hostName != defaultName) hostName;
names = lib.unique ([ defaultName ] ++ aliasNames);
in
lib.map (name: {
inherit name;
value = config;
}) names
) (lib.range 1 count)
);
# Generate host groups based on the input hosts configuration
hostGroups = lib.mapAttrsToList (
type: config:
let let
typeFile = ./types + "/${type}.nix"; hostType = config.type or prefix;
modules = system = config.system or "x86_64-linux";
if builtins.pathExists typeFile then devices = config.devices or { };
[ (import typeFile { inherit inputs; }) ] 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 else
throw "Host type '${type}' not found in hosts/types/"; "${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
usePrefix = deviceConfig.ugaif.host.useHostPrefix or true;
hostName = mkHostName prefix deviceKey usePrefix;
# Merge: base config -> overrides -> device-specific config
mergedConfig = lib.recursiveUpdate (lib.recursiveUpdate baseConfig overrides) deviceConfig;
in
{
name = hostName;
value = mkHost {
inherit hostName system hostType;
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 in
mkHostGroup { lib.recursiveUpdate deviceHosts countHosts
prefix = type;
inherit (config) count;
extraModules = modules;
deviceOverrides = config.devices or { };
}
) hosts; ) hosts;
allHosts = lib.foldl' lib.recursiveUpdate { } hostGroups; # Flatten the nested structure
allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues processInventory);
in in
{ {
nixosConfigurations = lib.mapAttrs (n: v: v.system) allHosts; nixosConfigurations = lib.mapAttrs (n: v: v.system) allHosts;

View File

@@ -19,7 +19,8 @@
config = { config = {
wsl.enable = true; wsl.enable = true;
wsl.defaultUser = config.ugaif.host.wsl.user; wsl.defaultUser =
if config.ugaif.forUser != null then config.ugaif.forUser else config.ugaif.host.wsl.user;
# Enable the headless software profile # Enable the headless software profile
ugaif.sw.enable = lib.mkDefault true; ugaif.sw.enable = lib.mkDefault true;

View File

@@ -2,6 +2,7 @@
pkgs, pkgs,
config, config,
lib, lib,
inputs,
... ...
}: }:
@@ -100,7 +101,8 @@ in
ugaif.users.enabledUsers = [ ugaif.users.enabledUsers = [
"root" "root"
"engr-ugaif" "engr-ugaif"
]; ]
++ lib.optional (config.ugaif.forUser != null) config.ugaif.forUser;
# Generate NixOS users # Generate NixOS users
users.users = users.users =
@@ -131,6 +133,7 @@ in
useUserPackages = true; useUserPackages = true;
extraSpecialArgs = { extraSpecialArgs = {
osConfig = config; osConfig = config;
inherit inputs;
}; };
users = users =
@@ -146,8 +149,9 @@ in
isExternal = user.flakeUrl != ""; isExternal = user.flakeUrl != "";
# Common imports based on flags # Common imports based on flags
commonImports = commonImports = lib.optional user.useZshTheme ../sw/theme.nix ++ [
lib.optional user.useZshTheme ../sw/theme.nix ++ lib.optional user.useNvimPlugins ../sw/nvim.nix; (import ../sw/nvim.nix { inherit user; })
];
in in
if isExternal then if isExternal then
{ {

View File

@@ -1,14 +1,14 @@
# ============================================================================ # ============================================================================
# NixOS Modules Export # NixOS Modules Export
# ============================================================================ # ============================================================================
# This file exposes host types and software configurations as reusable NixOS # This file exposes host types and software configurations as reusable NixOS
# modules that can be imported by external flakes or configurations. # modules that can be imported by external flakes or configurations.
# #
# Usage in another flake: # Usage in another flake:
# # Full host type configurations (includes hardware + software + system config) # # Full host type configurations (includes hardware + software + system config)
# inputs.nixos-systems.nixosModules.nix-desktop # inputs.nixos-systems.nixosModules.nix-desktop
# inputs.nixos-systems.nixosModules.nix-laptop # inputs.nixos-systems.nixosModules.nix-laptop
# #
# # Software-only configurations (for custom hardware setups) # # Software-only configurations (for custom hardware setups)
# inputs.nixos-systems.nixosModules.sw-desktop # inputs.nixos-systems.nixosModules.sw-desktop
# inputs.nixos-systems.nixosModules.sw-headless # inputs.nixos-systems.nixosModules.sw-headless
@@ -16,15 +16,30 @@
{ inputs }: { inputs }:
let let
# Software modules with their dependencies bundled # Software modules with their dependencies bundled
mkSwModule = swType: { config, lib, pkgs, ... }: { mkSwModule =
imports = [ swType:
../sw/ghostty.nix {
../sw/nvim.nix config,
../sw/python.nix lib,
../sw/theme.nix pkgs,
(import ../sw/${swType} { inherit config lib pkgs inputs; }) ...
]; }:
}; {
imports = [
../sw/ghostty.nix
../sw/nvim.nix
../sw/python.nix
../sw/theme.nix
(import ../sw/${swType} {
inherit
config
lib
pkgs
inputs
;
})
];
};
in in
{ {
# Host type modules (full system configurations) # Host type modules (full system configurations)

View File

@@ -2,77 +2,105 @@
# ============================================================================ # ============================================================================
# Fleet Inventory # Fleet Inventory
# ============================================================================ # ============================================================================
# This file defines the types of hosts and their counts. It is used by # Top-level keys are ALWAYS hostname prefixes. Actual hostnames are generated
# hosts/default.nix to generate the full set of NixOS configurations. # from the devices map or count.
# #
# Structure: # Hostname generation rules:
# <host-type> = { # - Numeric suffixes: no dash (e.g., "nix-surface1", "nix-surface2")
# count = <number>; # Number of hosts to generate (e.g., nix-laptop1, nix-laptop2) # - Non-numeric suffixes: add dash (e.g., "nix-surface-alpha", "nix-surface-beta")
# devices = { # Per-device overrides # - Set ugaif.host.useHostPrefix = false to use suffix as full hostname
# "<index>" = { #
# extraUsers = [ ... ]; # Users enabled on this specific device # Format:
# flakeUrl = "..."; # Optional external system flake for full override # "prefix" = {
# ... # Other hardware/filesystem overrides # 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)
# ugaif.host.useHostPrefix = false;
# }; # };
# }; # };
#
# # Common config for all devices in this group
# overrides = {
# extraUsers = [ "user1" ]; # Applied to all devices in this group
# # ... any other config
# };
# }; # };
#
# Laptop Configuration # Convenience options:
# Base specs: NVMe drive, 34G Swap # ugaif.forUser = "username"; # Automatically adds user to extraUsers and sets wslUser for WSL
#
# 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.extraUsers = [ "student" ]; # All 5 laptops get this user
# };
# "wsl" = {
# devices."alice".ugaif.forUser = "alice123"; # Sets up for user alice123
# }; # ========== Lab Laptops ==========
# Creates: nix-laptop1, nix-laptop2
# Both get hdh20267 user via overrides
nix-laptop = { nix-laptop = {
count = 2; devices = 2;
devices = { overrides.extraUsers = [ "hdh20267" ];
# Override example:
# "2" = { swapSize = "64G"; };
# Enable specific users for this device index
"1" = {
extraUsers = [ "hdh20267" ];
};
"2" = {
extraUsers = [ "hdh20267" ];
};
# Example of using an external flake for system configuration:
# "2" = { flakeUrl = "github:user/system-flake"; };
};
}; };
# Desktop Configuration # ========== Desktop ==========
# Base specs: NVMe drive, 16G Swap # Creates: nix-desktop1
nix-desktop.count = 1; nix-desktop = {
devices = 1;
};
# Surface Tablet Configuration (Kiosk Mode) # ========== Surface Tablets (Kiosk Mode) ==========
# Base specs: eMMC drive, 8G Swap # Creates: nix-surface1 (custom), nix-surface2, nix-surface3 (via defaultCount)
nix-surface = { nix-surface = {
count = 3; defaultCount = 3;
devices = { devices = {
"1".ugaif.sw.kioskUrl = "https://google.com"; "1".ugaif.sw.kioskUrl = "https://google.com";
}; };
overrides = {
ugaif.sw.kioskUrl = "https://yahoo.com";
};
}; };
# LXC Container Configuration # ========== LXC Containers ==========
# Creates: nix-builder (without lxc prefix)
nix-lxc = { nix-lxc = {
count = 1;
devices = { devices = {
"1" = { "nix-builder" = {
hostname = "nix-builder"; ugaif.host.useHostPrefix = false;
}; };
}; };
}; };
# WSL Configuration # ========== WSL Instances ==========
# Creates: nix-wsl-alireza
nix-wsl = { nix-wsl = {
count = 1;
devices = { devices = {
"1" = { "alireza".ugaif.forUser = "sv22900";
hostname = "nix-wsl-alireza";
extraUsers = [ "sv22900" ];
wslUser = "sv22900";
};
}; };
}; };
# Ephemeral Configuration (Live ISO / Netboot) # ========== Ephemeral/Netboot System ==========
nix-ephemeral.count = 1; # Creates: nix-ephemeral1
nix-ephemeral.devices = 1;
} }

View File

@@ -73,7 +73,7 @@ in
zsh zsh
git git
oh-my-posh oh-my-posh
inputs.lazyvim-nixvim.packages.${stdenv.hostPlatform.system}.nvim # inputs.lazyvim-nixvim.packages.${stdenv.hostPlatform.system}.nvim
inputs.agenix.packages.${stdenv.hostPlatform.system}.default inputs.agenix.packages.${stdenv.hostPlatform.system}.default
]; ];
} }

View File

@@ -1,4 +1,19 @@
{ pkgs, ... }: { user }:
{
pkgs,
lib,
inputs,
...
}:
let
nvimPackages =
if user.useNvimPlugins then
[
inputs.lazyvim-nixvim.packages.${pkgs.stdenv.hostPlatform.system}.nvim
]
else
[ pkgs.neovim ];
in
{ {
# ============================================================================ # ============================================================================
# Neovim Configuration # Neovim Configuration
@@ -6,8 +21,10 @@
# This module configures Neovim, specifically setting up TreeSitter parsers # This module configures Neovim, specifically setting up TreeSitter parsers
# to ensure syntax highlighting works correctly. # to ensure syntax highlighting works correctly.
home.packages = nvimPackages;
# https://github.com/nvim-treesitter/nvim-treesitter#i-get-query-error-invalid-node-type-at-position # https://github.com/nvim-treesitter/nvim-treesitter#i-get-query-error-invalid-node-type-at-position
xdg.configFile."nvim/parser".source = xdg.configFile."nvim/parser".source = lib.mkIf user.useNvimPlugins (
let let
parsers = pkgs.symlinkJoin { parsers = pkgs.symlinkJoin {
name = "treesitter-parsers"; name = "treesitter-parsers";
@@ -20,5 +37,6 @@
)).dependencies; )).dependencies;
}; };
in in
"${parsers}/parser"; "${parsers}/parser"
);
} }