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 = {
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 = {
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;

View File

@@ -8,10 +8,25 @@
# Host Generator
# ============================================================================
# This file contains the logic to generate NixOS configurations for all hosts
# defined in inventory.nix. It handles:
# 1. Common module imports (boot, users, software).
# 2. Host-specific overrides (filesystem, enabled users).
# 3. External flake integration for system overrides.
# 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 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
nixpkgs = inputs.nixpkgs;
@@ -21,11 +36,11 @@ let
{
hostName,
system ? "x86_64-linux",
extraModules ? [ ],
hostType,
configOverrides ? { },
}:
let
# Load users.nix to find external user flakes
# We use legacyPackages to evaluate the simple data structure of users.nix
pkgs = nixpkgs.legacyPackages.${system};
usersData = import ../users.nix { inherit pkgs; };
accounts = usersData.ugaif.users.accounts or { };
@@ -39,111 +54,177 @@ let
{ }
) 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 =
userFlakeModules
++ extraModules
++ [
typeModule
overrideModule
{ networking.hostName = hostName; }
];
]
++ lib.optional (configOverrides ? flakeUrl) externalFlakeModule;
in
{
system = lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs; };
modules = allModules;
};
modules = allModules;
};
# Function to generate a set of hosts based on inventory count and overrides
mkHostGroup =
{
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:
# Process inventory entries - top-level keys are always prefixes
processInventory = lib.mapAttrs (
prefix: config:
let
typeFile = ./types + "/${type}.nix";
modules =
if builtins.pathExists typeFile then
[ (import typeFile { inherit inputs; }) ]
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
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
mkHostGroup {
prefix = type;
inherit (config) count;
extraModules = modules;
deviceOverrides = config.devices or { };
}
lib.recursiveUpdate deviceHosts countHosts
) hosts;
allHosts = lib.foldl' lib.recursiveUpdate { } hostGroups;
# Flatten the nested structure
allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues processInventory);
in
{
nixosConfigurations = lib.mapAttrs (n: v: v.system) allHosts;

View File

@@ -19,7 +19,8 @@
config = {
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
ugaif.sw.enable = lib.mkDefault true;

View File

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

View File

@@ -1,14 +1,14 @@
# ============================================================================
# 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.
#
# 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
#
#
# # Software-only configurations (for custom hardware setups)
# inputs.nixos-systems.nixosModules.sw-desktop
# inputs.nixos-systems.nixosModules.sw-headless
@@ -16,15 +16,30 @@
{ inputs }:
let
# Software modules with their dependencies bundled
mkSwModule = swType: { config, lib, pkgs, ... }: {
imports = [
../sw/ghostty.nix
../sw/nvim.nix
../sw/python.nix
../sw/theme.nix
(import ../sw/${swType} { inherit config lib pkgs inputs; })
];
};
mkSwModule =
swType:
{
config,
lib,
pkgs,
...
}:
{
imports = [
../sw/ghostty.nix
../sw/nvim.nix
../sw/python.nix
../sw/theme.nix
(import ../sw/${swType} {
inherit
config
lib
pkgs
inputs
;
})
];
};
in
{
# Host type modules (full system configurations)

View File

@@ -2,77 +2,105 @@
# ============================================================================
# Fleet Inventory
# ============================================================================
# This file defines the types of hosts and their counts. It is used by
# hosts/default.nix to generate the full set of NixOS configurations.
# Top-level keys are ALWAYS hostname prefixes. Actual hostnames are generated
# from the devices map or count.
#
# Structure:
# <host-type> = {
# count = <number>; # Number of hosts to generate (e.g., nix-laptop1, nix-laptop2)
# devices = { # Per-device overrides
# "<index>" = {
# extraUsers = [ ... ]; # Users enabled on this specific device
# flakeUrl = "..."; # Optional external system flake for full override
# ... # Other hardware/filesystem overrides
# 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 ugaif.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)
# 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
# Base specs: NVMe drive, 34G Swap
#
# Convenience options:
# 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 = {
count = 2;
devices = {
# 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"; };
};
devices = 2;
overrides.extraUsers = [ "hdh20267" ];
};
# Desktop Configuration
# Base specs: NVMe drive, 16G Swap
nix-desktop.count = 1;
# ========== Desktop ==========
# Creates: nix-desktop1
nix-desktop = {
devices = 1;
};
# Surface Tablet Configuration (Kiosk Mode)
# Base specs: eMMC drive, 8G Swap
# ========== Surface Tablets (Kiosk Mode) ==========
# Creates: nix-surface1 (custom), nix-surface2, nix-surface3 (via defaultCount)
nix-surface = {
count = 3;
defaultCount = 3;
devices = {
"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 = {
count = 1;
devices = {
"1" = {
hostname = "nix-builder";
"nix-builder" = {
ugaif.host.useHostPrefix = false;
};
};
};
# WSL Configuration
# ========== WSL Instances ==========
# Creates: nix-wsl-alireza
nix-wsl = {
count = 1;
devices = {
"1" = {
hostname = "nix-wsl-alireza";
extraUsers = [ "sv22900" ];
wslUser = "sv22900";
};
"alireza".ugaif.forUser = "sv22900";
};
};
# Ephemeral Configuration (Live ISO / Netboot)
nix-ephemeral.count = 1;
# ========== Ephemeral/Netboot System ==========
# Creates: nix-ephemeral1
nix-ephemeral.devices = 1;
}

View File

@@ -73,7 +73,7 @@ in
zsh
git
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
];
}

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
@@ -6,8 +21,10 @@
# This module configures Neovim, specifically setting up TreeSitter parsers
# 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
xdg.configFile."nvim/parser".source =
xdg.configFile."nvim/parser".source = lib.mkIf user.useNvimPlugins (
let
parsers = pkgs.symlinkJoin {
name = "treesitter-parsers";
@@ -20,5 +37,6 @@
)).dependencies;
};
in
"${parsers}/parser";
"${parsers}/parser"
);
}