Consider adding osConfig to extraSpecialArgs for standalone home manager configurations
Is your feature request related to a problem? Please describe.
Module-based home manager configurations will have access to an extra extraSpecialArg called osConfig which will alias the hosts' nixosConfiguration/nix-darwin configuration. This option is typically unavailable (set to null) by default in standalone configs however because home manager does not have built-in knowledge of the host system. Since blueprint explicitly maps users to hosts, I see no reason why it couldn't provide this missing functionality to standalone configurations.
It also might cause confusion if a blueprint user creates a new user which depends on osConfig and then later decides to move to standalone home manager. In this case, I think the hm config will not evaluate in standalone due to osConfig being null.
Describe the solution you'd like
Add osConfig to extraSpecialArgs when the hm config is intended to be used in standalone. osConfig should reference the users host config.
Describe alternatives you've considered
You could hard-code each user to depend on flake.outputs.nixosConfigurations.<host>, but that does not seem ideal. No idea if there is a more elegant solution that is already accessible.
Additional context
You might want to consider investigating if there are any other differences in behaviors between module-based and standalone that can be papered over with blueprint
Example config that uses osConfig
{ pkgs, osConfig, ... }:
{
home.packages = [ pkgs.atool pkgs.httpie ];
programs.bash.enable = true;
home.file."test".text = osConfig.system.stateVersion;
# The state version is required and should stay at the version you
# originally installed.
home.stateVersion = "24.11";
}
Module-based build
> nixos-rebuild build --flake .#
building the system configuration...
Standalone build
> home-manager build --flake .
...
… while selecting an attribute
at /nix/store/hash-source/hosts/host/users/user/home-configuration.nix:7:27:
6|
7| home.file."test".text = osConfig.system.stateVersion;
| ^
8|
error: expected a set but found null: null
That's kind of cursed, but it's an interesting idea. Seems like a powerful feature that blueprint could expose in a simple way, and the asymmetry you showed between building a system and building its home configuration right now isn't lovely. But it also feels like it could be a footgun for build speed if you reference osConfig, since it's not already evaluated.
Do you have a particular use-case for this in mind? How did this idea pop up? Does this unblock something for you? I want to get an understanding of how this could be used.
I don't have a tonne of time free over the next few days, so feel free to ping me if I've forgotten about this :P
That's kind of cursed, but it's an interesting idea. Seems like a powerful feature that blueprint could expose in a simple way, and the asymmetry you showed between building a system and building its home configuration right now isn't lovely. But it also feels like it could be a footgun for build speed if you reference
osConfig, since it's not already evaluated.Do you have a particular use-case for this in mind? How did this idea pop up? Does this unblock something for you? I want to get an understanding of how this could be used.
I don't have a tonne of time free over the next few days, so feel free to ping me if I've forgotten about this :P
Probably the best use case I have currently is if I need to enable a program/service at the system level, but reference the package at the user level. Since packages are configurable at the nixos-module level, the most correct way to do this is to reference the nixos config itself.
One place I do this in my own setup is with noisetorch. It needs to be enabled at the system level, but I only want some users to run it by default. So I have a hm-module which creates a user service which references programs.noisetorch.package in the nixosConfig.
However none of this really precludes me from moving back to module-based home-manager. It's just personal preference basically. I'm just not really fond of calling sudo anytime I want to add a package to my user environment. And my setups aren't so complicated that eval time has become a concern. So keep that in mind.
Not yet convinced that passing osConfig would be the best way to handle that tbh.
Couldn't one still use non-standalone home-manager configs in the NixOS config if both are tightly coupled?
standalone home-manager configs can also be used on non-nixos hosts where we might not really have a meaningful osConfig available.
So I have a hm-module which creates a user service which references programs.noisetorch.package in the nixosConfig.
For that alone, flake.outputs.nixosConfigurations.<host> as you suggested above doesn't sound too bad?
Couldn't one still use non-standalone home-manager configs in the NixOS config if both are tightly coupled?
I could, but it's just my preference not to. The ability to edit/activate user configs without dealing with privileges/sudo is a much nicer experience IMO.
For that alone, flake.outputs.nixosConfigurations.
as you suggested above doesn't sound too bad?
It's probably not too bad at my scale tbh. More of a convenience. I could see that being annoying though to someone with similar preferences and more hosts.
Also, I understand my use case is probably pretty niche, so I'm not going to push very hard. I'd appreciate however if it (or some similar feature) came around at some point, even if it gave a warning about how it impacts eval times. If you maintainers aren't interested in doing that at this moment, feel free to close this issue.
It ends up being a rather elegant patch at least!
I still don't know where on the spectrum of ideas this lies, but I've made a test branch you can use to test it out: github:clo4/blueprint/standalone-osconfig (https://github.com/clo4/blueprint/commit/0b0001780745fe0c98b85fadd3e32b28f22fc14b)
diff --git a/lib/default.nix b/lib/default.nix
index 5b84b36..e8cf77b 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -215,12 +215,15 @@ let
mkHomeConfiguration =
{
username,
+ hostname,
modulePath,
pkgs,
}:
home-manager.lib.homeManagerConfiguration {
inherit pkgs;
- extraSpecialArgs = specialArgs;
+ extraSpecialArgs = specialArgs // {
+ osConfig = hosts.${hostname}.value.config or { };
+ };
modules = [
perSystemModule
modulePath
@@ -261,7 +264,7 @@ let
homeConfigurations = lib.mapAttrs (
_name: homeData:
mkHomeConfiguration {
- inherit (homeData) modulePath username;
+ inherit (homeData) modulePath username hostname;
inherit pkgs;
}
) homesFlat;
Referencing flake.nixosConfigurations.${hostname}.config feels much more explicit, which is arguably better, but it doesn't feel great that the hostname has to be hard-coded since there's no (good) way to get it "dynamically" at the moment.
Could make the hostname a module argument, which would also give users the power to build osConfig themselves.
I still don't know where on the spectrum of ideas this lies, but I've made a test branch you can use to test it out:
github:clo4/blueprint/standalone-osconfig(clo4@0b00017)
Appreciate you looking into this 🙂, but we have a subtle difference! If the idea was to match homeManager behavior as much as possible, I think what you'd want is hosts.${hostname}.value.config or null rather than hosts.${hostname}.value.config or { }. Otherwise we get this similar but different output on evaluation of a blueprint user without a system configuration.nix (see attribute missing vs null).
error: attribute 'system' missing
at /nix/store/y8fplrsj6ncqv68p1npk10gm3jjpxah2-source/hosts/smk-desktop/users/smkuehnhold/home-configuration.nix:7:27:
6|
7| home.file."test".text = osConfig.system.stateVersion;
| ^
8|
Not sure how that would impact downstream, but it definitely makes my original idea seem more dangerous.
Could make the hostname a module argument, which would also give users the power to build osConfig themselves.
This would be more than sufficient for my needs!
Testing branch now uses or null instead. Good catch.
Will also make a branch to play with adding the hostname arg
Can be tested out: https://github.com/clo4/blueprint/commit/6eeb62b1568da4c534595b88be5826e1974510b2 (github:clo4/blueprint/standalone-hostname)
I can't think of a good reason not to expose the hostname, but to be honest, I also can't think of a good reason not to also expose osConfig either. The more I think about it, the more I don't think it would actually be an issue, but I might be missing something. Can't shake the feeling that it's a "cute" feature rather than something that needs to be built in, but also, it seems very powerful.
Exposing the hostname allows you to build osConfig in userland, but that requires importing the module in your configuration :/
# modules/home/osConfig.nix
{ hostname, flake, ... }:
{
_module.args.osConfig =
(flake.nixosConfigurations // flake.darwinConfigurations).${hostname}.config or null;
}
# hosts/foo/users/bar.nix
{ flake, osConfig, ... }:
{
imports = [ flake.homeModules.osConfig ];
# home.file."state-version".text = osConfig.system.stateVersion;
}
... so at that point, you're probably better off with
# hosts/foo/users/bar.nix
let
osConfig = flake.nixosConfigurations.foo.config;
in
{
# home.file."state-version".text = osConfig.system.stateVersion;
}
Bikeshedding: should it be hostname (match the noun) or hostName (match networking.hostName)?
Bikeshedding: should it be hostname (match the noun) or hostName (match networking.hostName)?
I don't have strong opinions about this personally.