fix(home): forward exported hm args

Resolve exported home configurations against their parent system config so hosted and bare-name homes keep the same Home Manager specialArgs on package and homeConfiguration export paths. This restores arbitrary home-manager.extraSpecialArgs keys instead of only hardcoded aliases and adds regression coverage for the export resolver.
This commit is contained in:
anntnzrb
2026-03-07 22:51:26 -05:00
committed by mjallen18
parent 0c9d9c5919
commit 13bdc883da
2 changed files with 160 additions and 24 deletions

View File

@@ -107,6 +107,54 @@
inherit system; inherit system;
}; };
standalone-special-args = standalone-home.specialArgs; standalone-special-args = standalone-home.specialArgs;
exported-home-name = "test@${system}";
resolved-exported-home = lib.snowfall.flake.resolve-exported-home {
home-name = exported-home-name;
home = {
modules = [ ];
inherit system;
specialArgs = {
host = "";
user = "test";
standaloneOnly = true;
};
builder = args: args.specialArgs;
};
systems = {
test-host = {
output = "nixosConfigurations";
inherit system;
};
};
flake-outputs = {
nixosConfigurations = {
test-host = {
config = {
hostName = "test-host";
home-manager = {
users.test = { };
extraSpecialArgs = {
arbitraryName = "ok";
};
};
};
};
};
homeConfigurations = {
${exported-home-name} = {
fallback = true;
};
};
};
};
bare-name-package-alias =
"homeConfigurations-"
+ (
if pkgs.lib.hasSuffix "@${system}" exported-home-name then
pkgs.lib.removeSuffix "@${system}" exported-home-name
else
exported-home-name
);
eval = builtins.tryEval { eval = builtins.tryEval {
snowfall-attrs = builtins.attrNames lib.snowfall; snowfall-attrs = builtins.attrNames lib.snowfall;
has-standalone-home-placeholders = has-standalone-home-placeholders =
@@ -115,13 +163,33 @@
&& (standalone-special-args ? systemConfig) && (standalone-special-args ? systemConfig)
&& (standalone-special-args.systemConfig == null); && (standalone-special-args.systemConfig == null);
has-system-config-aliases = has-system-config-aliases =
(builtins.length (builtins.split "systemConfig = config;" (builtins.readFile ./modules/nixos/user/default.nix)) > 1) (
&& (builtins.length (builtins.split "systemConfig = config;" (builtins.readFile ./modules/darwin/user/default.nix)) > 1); builtins.length (
builtins.split "systemConfig = config;" (builtins.readFile ./modules/nixos/user/default.nix)
) > 1
)
&& (
builtins.length (
builtins.split "systemConfig = config;" (builtins.readFile ./modules/darwin/user/default.nix)
) > 1
);
resolves-exported-home-extra-special-args =
resolved-exported-home ? arbitraryName
&& (resolved-exported-home.arbitraryName == "ok")
&& (resolved-exported-home ? systemConfig)
&& (resolved-exported-home.systemConfig.hostName == "test-host")
&& (resolved-exported-home ? osConfig)
&& (resolved-exported-home.osConfig.hostName == "test-host")
&& (resolved-exported-home ? standaloneOnly)
&& resolved-exported-home.standaloneOnly;
strips-bare-home-package-alias = bare-name-package-alias == "homeConfigurations-test";
}; };
in in
assert eval.success; assert eval.success;
assert eval.value.has-standalone-home-placeholders; assert eval.value.has-standalone-home-placeholders;
assert eval.value.has-system-config-aliases; assert eval.value.has-system-config-aliases;
assert eval.value.resolves-exported-home-extra-special-args;
assert eval.value.strips-bare-home-package-alias;
{ {
snowfall-lib-eval = pkgs.runCommand "snowfall-lib-eval" { } "mkdir -p $out"; snowfall-lib-eval = pkgs.runCommand "snowfall-lib-eval" { } "mkdir -p $out";
} }

View File

@@ -86,6 +86,89 @@ let
"snowfall" "snowfall"
]; ];
## Resolve the parent system config for an exported home configuration.
## Explicit `user@host` names map directly. Hostless names end up normalized
## to `user@system`, so fall back to a unique system-target match.
#@ Attrs -> Attrs | Null
resolve-exported-home-host-config =
{
home,
systems,
flake-outputs,
}:
let
get-host-config =
host-name:
let
system-output = systems.${host-name}.output;
in
flake-outputs.${system-output}.${host-name}.config;
requested-host = home.specialArgs.host or "";
requested-system = home.system or "";
requested-user = home.specialArgs.user or "";
host-candidates =
if requested-host != "" && systems ? ${requested-host} then
[ requested-host ]
else
pipe systems [
(filterAttrs (
host-name: system-config:
let
host-config = get-host-config host-name;
matches-explicit-target =
requested-host != ""
&& system-config.system == requested-host
&& requested-user != ""
&& builtins.hasAttr requested-user (host-config.home-manager.users or { });
matches-hostless-home =
requested-host == ""
&& system-config.system == requested-system
&& requested-user != ""
&& builtins.hasAttr requested-user (host-config.home-manager.users or { });
in
matches-explicit-target || matches-hostless-home
))
builtins.attrNames
];
in
if builtins.length host-candidates == 1 then
get-host-config (builtins.head host-candidates)
else
null;
## Rebuild a hosted home export with the resolved parent system config so
## Home Manager module arguments stay consistent on export paths.
#@ Attrs -> Attrs
resolve-exported-home =
{
home-name,
home,
systems,
flake-outputs,
}:
let
host-config = flake.resolve-exported-home-host-config {
inherit home systems flake-outputs;
};
hosted-special-args =
if host-config != null then
{
osConfig = host-config;
systemConfig = host-config;
}
// (host-config.home-manager.extraSpecialArgs or { })
else
{ };
in
if host-config != null then
home.builder {
inherit (home) modules;
specialArgs = home.specialArgs // hosted-special-args;
}
else
flake-outputs.homeConfigurations.${home-name};
## Transform an attribute set of inputs into an attribute set where the values are the inputs' `lib` attribute. Entries without a `lib` attribute are removed. ## Transform an attribute set of inputs into an attribute set where the values are the inputs' `lib` attribute. Entries without a `lib` attribute are removed.
## Example Usage: ## Example Usage:
## ```nix ## ```nix
@@ -208,30 +291,15 @@ let
flake-utils-plus-outputs = core-inputs.flake-utils-plus.lib.mkFlake flake-options; flake-utils-plus-outputs = core-inputs.flake-utils-plus.lib.mkFlake flake-options;
resolve-hosted-home =
home-name: home:
let
host = home.specialArgs.host or "";
has-hosted-system = host != "" && systems ? ${host};
in
if has-hosted-system then
let
system-output = systems.${host}.output;
host-config = flake-utils-plus-outputs.${system-output}.${host}.config;
in
home.builder {
inherit (home) modules;
specialArgs = home.specialArgs // {
osConfig = host-config;
systemConfig = host-config;
};
}
else
flake-utils-plus-outputs.homeConfigurations.${home-name};
# Hosted homes need their parent system config injected before standalone # Hosted homes need their parent system config injected before standalone
# homeConfigurations or activation packages are forced. # homeConfigurations or activation packages are forced.
home-configurations = mapAttrs resolve-hosted-home homes; home-configurations = mapAttrs (
home-name: home:
flake.resolve-exported-home {
inherit home-name home systems;
flake-outputs = flake-utils-plus-outputs;
}
) homes;
flake-outputs = flake-utils-plus-outputs // { flake-outputs = flake-utils-plus-outputs // {
inherit overlays; inherit overlays;