fix: Lazily fetch external modules only if needed

This commit is contained in:
UGA Innovation Factory
2026-01-27 15:05:52 -05:00
parent 1a7bf29448
commit 540f5feb78
6 changed files with 86 additions and 22 deletions

View File

@@ -36,8 +36,20 @@ let
externalModulePath = externalModulePath =
if externalModuleThunk != null then if externalModuleThunk != null then
let let
# Force evaluation of the thunk (fetchGit, fetchTarball, etc.) # Force evaluation of the thunk
fetchedPath = externalModuleThunk; fetchedPath =
if
builtins.isAttrs externalModuleThunk
&& externalModuleThunk ? _type
&& externalModuleThunk._type == "lazy-fetchGit"
then
# New format: lazy fetchGit - only execute when needed
(builtins.fetchGit {
inherit (externalModuleThunk) url rev submodules;
}).outPath
else
# Legacy: pre-fetched derivation or path
externalModuleThunk;
# Extract outPath from fetchGit/fetchTarball results # Extract outPath from fetchGit/fetchTarball results
extractedPath = extractedPath =
if builtins.isAttrs fetchedPath && fetchedPath ? outPath then fetchedPath.outPath else fetchedPath; if builtins.isAttrs fetchedPath && fetchedPath ? outPath then fetchedPath.outPath else fetchedPath;
@@ -61,10 +73,19 @@ let
name: user: name: user:
if (user ? external && user.external != null) then if (user ? external && user.external != null) then
let let
# Resolve external path (lazy fetchGit if needed)
externalPath = externalPath =
if builtins.isAttrs user.external && user.external ? outPath then if builtins.isAttrs user.external && user.external ? url && user.external ? rev then
# New format: lazy fetchGit
(builtins.fetchGit {
inherit (user.external) url rev;
submodules = user.external.submodules or false;
}).outPath
else if builtins.isAttrs user.external && user.external ? outPath then
# Legacy: pre-fetched
user.external.outPath user.external.outPath
else else
# Direct path
user.external; user.external;
nixosModulePath = externalPath + "/nixos.nix"; nixosModulePath = externalPath + "/nixos.nix";
in in
@@ -205,8 +226,24 @@ let
# Check if deviceConfig has an 'external' field for lazy evaluation # Check if deviceConfig has an 'external' field for lazy evaluation
hasExternalField = builtins.isAttrs deviceConfig && deviceConfig ? external; hasExternalField = builtins.isAttrs deviceConfig && deviceConfig ? external;
# Extract external module thunk if present (don't evaluate yet!) # Extract external module spec (don't evaluate fetchGit yet!)
externalModuleThunk = if hasExternalField then deviceConfig.external else null; externalModuleThunk =
if hasExternalField then
let
ext = deviceConfig.external;
in
# New format: { url, rev, submodules? } - create lazy fetchGit thunk
if builtins.isAttrs ext && ext ? url && ext ? rev then
{
_type = "lazy-fetchGit";
inherit (ext) url rev;
submodules = ext.submodules or false;
}
# Legacy: pre-fetched or path
else
ext
else
null;
# Remove 'external' from config to avoid conflicts # Remove 'external' from config to avoid conflicts
cleanDeviceConfig = cleanDeviceConfig =

View File

@@ -125,21 +125,45 @@ let
type = lib.types.nullOr ( type = lib.types.nullOr (
lib.types.oneOf [ lib.types.oneOf [
lib.types.path lib.types.path
lib.types.package (lib.types.submodule {
lib.types.attrs options = {
url = lib.mkOption {
type = lib.types.str;
description = "Git repository URL to fetch user configuration from.";
example = "https://github.com/username/dotfiles";
};
rev = lib.mkOption {
type = lib.types.str;
description = "Git commit hash, tag, or branch to fetch.";
example = "abc123def456...";
};
submodules = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to fetch Git submodules.";
};
};
})
] ]
); );
default = null; default = null;
description = '' description = ''
External user configuration module from Git or local path. External user configuration module from Git or local path.
Can be either:
- A local path: /path/to/config
- A Git repository: { url = "..."; rev = "..."; submodules? = false; }
The Git repository is only fetched when the user is actually enabled.
Should contain user.nix (user options + home-manager config) Should contain user.nix (user options + home-manager config)
and optionally nixos.nix (system-level config). and optionally nixos.nix (system-level config).
''; '';
example = lib.literalExpression '' example = lib.literalExpression ''
builtins.fetchGit { {
url = "https://github.com/username/dotfiles"; url = "https://github.com/username/dotfiles";
rev = "abc123..."; rev = "abc123def456789abcdef0123456789abcdef012";
submodules = false;
}''; }'';
}; };
opensshKeys = lib.mkOption { opensshKeys = lib.mkOption {

View File

@@ -13,13 +13,21 @@
# Options are defined in fleet-option.nix for early availability. # Options are defined in fleet-option.nix for early availability.
let let
# Helper: Resolve external module path from fetchGit/fetchTarball/path # Helper: Resolve external module path (with lazy Git fetching)
resolveExternalPath = resolveExternalPath =
external: external:
if external == null then if external == null then
null null
# New format: { url, rev, submodules? } - only fetch when needed
else if builtins.isAttrs external && external ? url && external ? rev then
(builtins.fetchGit {
inherit (external) url rev;
submodules = external.submodules or false;
}).outPath
# Legacy: pre-fetched derivation/package
else if builtins.isAttrs external && external ? outPath then else if builtins.isAttrs external && external ? outPath then
external.outPath external.outPath
# Direct path
else else
external; external;

View File

@@ -45,7 +45,7 @@
# External modules (instead of config): # External modules (instead of config):
# Device values can be a config attrset with an optional 'external' field: # Device values can be a config attrset with an optional 'external' field:
# devices."hostname" = { # devices."hostname" = {
# external = builtins.fetchGit { ... }; # Lazy: only fetched when building this host # external = { url = "..."; rev = "..."; submodules? = false; }; # Lazy: only fetched when building this host
# # ... additional config options # # ... additional config options
# }; # };
# The external module will be imported and evaluated only when this specific host is built. # The external module will be imported and evaluated only when this specific host is built.
@@ -65,7 +65,7 @@
# devices."alice".athenix.forUser = "alice123"; # Sets up for user alice123 # devices."alice".athenix.forUser = "alice123"; # Sets up for user alice123
# }; # };
# "external" = { # "external" = {
# devices."remote".external = builtins.fetchGit { # External module via Git (lazy) # devices."remote".external = { url = "..."; rev = "..."; }; # External module via Git (lazy)
# url = "https://github.com/example/config"; # url = "https://github.com/example/config";
# rev = "e1ccd7cc3e709afe4f50b0627e1c4bde49165014"; # rev = "e1ccd7cc3e709afe4f50b0627e1c4bde49165014";
# }; # };
@@ -127,7 +127,7 @@
}; };
}; };
}; };
"usda-dash".external = builtins.fetchGit { "usda-dash".external = {
url = "https://git.factory.uga.edu/MODEL/usda-dash-config.git"; url = "https://git.factory.uga.edu/MODEL/usda-dash-config.git";
rev = "dab32f5884895cead0fae28cb7d88d17951d0c12"; rev = "dab32f5884895cead0fae28cb7d88d17951d0c12";
submodules = true; submodules = true;

View File

@@ -37,9 +37,6 @@ in
]; ];
options.athenix.sw = { options.athenix.sw = {
# Software submodule for the Athenix system suite. sw.enable enables
# base packages and common configuration. Each sw.<type>.enable enables
# additional packages and services for that system type.
enable = mkOption { enable = mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
@@ -57,8 +54,6 @@ in
}; };
type = mkOption { type = mkOption {
# DEPRECATED: Backwards compatibility for external modules
# Use athenix.sw.<type>.enable instead
type = lib.types.nullOr (lib.types.either lib.types.str (lib.types.listOf lib.types.str)); type = lib.types.nullOr (lib.types.either lib.types.str (lib.types.listOf lib.types.str));
default = null; default = null;
description = "DEPRECATED: Use athenix.sw.<type>.enable instead. Legacy type selection."; description = "DEPRECATED: Use athenix.sw.<type>.enable instead. Legacy type selection.";
@@ -66,7 +61,6 @@ in
}; };
extraPackages = mkOption { extraPackages = mkOption {
# Additional packages to install beyond the defaults
type = lib.types.listOf lib.types.package; type = lib.types.listOf lib.types.package;
default = [ ]; default = [ ];
description = '' description = ''
@@ -77,7 +71,6 @@ in
}; };
excludePackages = mkOption { excludePackages = mkOption {
# Packages to exclude from the default package list
type = lib.types.listOf lib.types.package; type = lib.types.listOf lib.types.package;
default = [ ]; default = [ ];
description = '' description = ''

View File

@@ -13,8 +13,9 @@
# #
# External User Configuration: # External User Configuration:
# Users can specify external configuration modules via the 'external' attribute: # Users can specify external configuration modules via the 'external' attribute:
# external = builtins.fetchGit { url = "..."; rev = "..."; }; # external = { url = "..."; rev = "..."; submodules? = false; };
# external = /path/to/local/config; # external = /path/to/local/config;
# external = builtins.fetchGit { ... }; # legacy, still supported
# #
# External repositories should contain: # External repositories should contain:
# - user.nix (required): Defines athenix.users.<name> options AND home-manager config # - user.nix (required): Defines athenix.users.<name> options AND home-manager config
@@ -47,9 +48,10 @@
enable = true; # Default user, enabled everywhere enable = true; # Default user, enabled everywhere
}; };
hdh20267 = { hdh20267 = {
external = builtins.fetchGit { external = {
url = "https://git.factory.uga.edu/hdh20267/hdh20267-nix"; url = "https://git.factory.uga.edu/hdh20267/hdh20267-nix";
rev = "dbdf65c7bd59e646719f724a3acd2330e0c922ec"; rev = "dbdf65c7bd59e646719f724a3acd2330e0c922ec";
# submodules = false; # optional, defaults to false
}; };
}; };
sv22900 = { sv22900 = {