Access nixos system config within HM "standalone" flake setup; infinite recursion in overrideAttr

I’m having some trouble with accessing system config settings from HM imports in my flake setup.
For flexibility, (multi-user and multi-host), I’m using a single flake but HM in standalone mode, like this:

outputs = inputs @ { self, nixpkgs, nurpkgs, nixpkgsUnstable, musnix, home-manager }:
    let
      system = "x86_64-linux";
    in
    {
      homeConfigurations = (
        import ./outputs/home-conf.nix {
          inherit system;
          inherit (inputs) nixpkgs nurpkgs nixpkgsUnstable home-manager;
        }
      );

      nixosConfigurations = (
        import ./outputs/nixos-conf.nix {
          inherit (nixpkgs) lib;
          inherit inputs system;
        }
      );
    };

And then from home-conf.nix I import specific user home.nix (based on the executing user) and want to access system config settings, e.g. something lile:

...
let
    flags = if nixosConfig.hardware.video.hidpi.enable then "--force-device-scale-factor=2.0" else "";
    spotifycmd = "${pkgs.spotify}/bin/spotify ${flags} %U";
in
{
    config.xdg.desktopEntries.spotify = config.xdg.desktopEntries.spotify.overideAttrs (_: {
        exec = "${spotifycmd}";
    });
}

However, I’m not sure where/how I can get nixosConfig to access system config values within HM declarations. (I suppose the implicit nixosConfig I read about somewhere is only available for HM as a module?)

Second issue: I just want to override the exec attribute of the spotify desktop entry, and take the remaining attrs from the default. Doing the above however leads to an infinite recursion. Any ideas?

1 Like

Correct. You can always pull it out of self, however, assuming you’re fine with a potential mismatch between the commit of the flake your system was built from and the commit you’re building home-manager from.

inputs.self.nixosConfigurations.hostname.config.hardware.video.hidpi.enable, for example, would get you the value of the hardware.video.hidpi.enable option for hostname hostname.

2 Likes

@tejing
Thanks, that works well. Summarising:

in the flake.nix

...
      homeConfigurations = (
        import ./outputs/home-conf.nix {
          inherit system;
          inherit (inputs) nixpkgs nurpkgs nixpkgsUnstable home-manager;
          nixosConfigs = inputs.self.nixosConfigurations;
        }
      );...

and in home-conf.nix

...
    let
      conf = (
        import ../home/${username}/home.nix {
        inherit nur pkgs unstable username hostname homeDirectory;
        inherit (pkgs) config lib stdenv;
      });
    in
...
    home-manager.lib.homeManagerConfiguration rec {
      inherit pkgs system username homeDirectory;
      extraSpecialArgs = { inherit unstable; nixosConfig = nixosConfigs."${hostname}".config; };
      stateVersion = "22.05";
      configuration = conf;
    });
...

(which is part of the mkHome function that is called using the standard flake match myuser@myhost = mkHome "myuser" "myhost")

I had to take the “all configs” detour because I can’t access the hostname generically in the flake’s output block, so I can only access the specific hostname in the expression that produces "myuser@myhost". That begs the question: if the flake “knows” about user@host per default (i.e. when not using the .#user@host selector), why doesn’t it expose it (and system too for that matter) e.g. in inputs.self, or am I missing something?

Now I’m just left with the infinite recursion; for now I just re-defined the whole desktopEntries.spotify, which works.

I don’t entirely follow the question, but it sounds like you’re attributing far too much smarts to flakes. They just gather locked inputs and produce an attrset of outputs with pure evaluation. That’s it. Anything smarter than that is a function of the tools accessing the flake. In particular, all the attribute paths with special meanings are just conventions followed by the tools that interact with the flake. homeConfigurations.user@host means something because the home-manager cli tool looks there automatically. To the flake, it’s just another attribute path.

Ah yes, of course, as I now understand

home-manager switch --flake .

basically is the same as something like

home-manager switch --flake my-flake-uri#${USER}@$(hostname -s)

(which is interpreted by HM like that)
and it then assigns the “home derivation” to the corresponding attribute for

homeConfigurations = { myuser@myhost = home-manager.lib.homeManagerConfiguration { configuration=mywholeconfig; }; }

I guess what I really mean is then the question whether the argument passed to the flake (behind the #) is somehow available as a variable within the flake (i.e. in inputs), which would enable us to use a variable for the attribute name and call the function that builds the derivation with corresponding parameters.

(Now I have the { myuserx@myhosty = makeHomeConfig { username="myuserx", hostname="myhosty" , ... }; } (pseudo code) for all user/host combinations, which seems like an anti-pattern.)

No, but if the set of possible arguments is reasonably enumerable, you can fake it with genAttrs.

1 Like

@tejing
Thanks, that gives me a cool idea: one could scan subdirs in the flake repo for machine definitions and user definitions, and use genAttrs on that (or use some kind of table expression specifying which users have to be defined for which hosts). Probably that should also include “profiles” of some sort, then one could manage differences between e.g. servers, desktops and laptops.

That’s basically what devos and flake-utils-plus do, scan your folders and generate attributes from their contents.

Also they do apply a lot of more implicit things on top, to make it appear even more magic.

I’m a friend of explicitly spelling it out.

1 Like

I’m with you on “avoiding too much implicit magic”. The sweet spot is probably where it can “just” avoid boiler plate. Thanks for pointing to devos, wasn’t even aware of that, I might check it out (if only for some inspiration).