diff --git a/flake.nix b/flake.nix index 9b6d1f0..581b9c8 100644 --- a/flake.nix +++ b/flake.nix @@ -107,6 +107,54 @@ inherit system; }; 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 { snowfall-attrs = builtins.attrNames lib.snowfall; has-standalone-home-placeholders = @@ -115,13 +163,33 @@ && (standalone-special-args ? systemConfig) && (standalone-special-args.systemConfig == null); 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 assert eval.success; assert eval.value.has-standalone-home-placeholders; 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"; } diff --git a/snowfall-lib/flake/default.nix b/snowfall-lib/flake/default.nix index 8c84668..9be4b17 100644 --- a/snowfall-lib/flake/default.nix +++ b/snowfall-lib/flake/default.nix @@ -86,6 +86,89 @@ let "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. ## Example Usage: ## ```nix @@ -208,30 +291,15 @@ let 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 # 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 // { inherit overlays;