Installing home-manager from NixOS

I want to share how I’m installing home-manager from my NixOS configuration.nix. I think my solution is a similar to home-manager-template, but focused on NixOS users.

The installation is done from the following function, for more details I wrote a blog post about it (Custom home-manager installation with NixOS | Lafuente):

let
  user = "john";
  userHome = "/home/${user}";
  hostName = "laptop";

  home-manager = { home-manager-path, config-path }:
    assert builtins.typeOf home-manager-path == "string";
    assert builtins.typeOf config-path == "string";
    (
      pkgs.callPackage
        (/. + home-manager-path + "/home-manager") { path = "${home-manager-path}"; }
    ).overrideAttrs (old: {
      nativeBuildInputs = [ pkgs.makeWrapper ];
      buildCommand =
        let
          home-mananger-bootstrap = pkgs.writeTextFile {
            name = "home-manager-bootstrap.nix";
            text = ''
              { config, pkgs, ... }:
              {
                # Home Manager needs a bit of information about you and the
                # paths it should manage.
                home.username = "${user}";
                home.homeDirectory = "${userHome}";
                home.sessionVariables.HOSTNAME = "${hostName}";
                imports = [ ${config-path} ];
              }
            '';
          }; in
        ''
          ${old.buildCommand}
          wrapProgram $out/bin/home-manager --set HOME_MANAGER_CONFIG "${home-mananger-bootstrap}"
        '';
    });
in
{
  users.users.${user} = {
    home = userHome;
    packages = [
      (home-manager {
        home-manager-path = "${userHome}/home-manager";
        config-path = builtins.toString ../home-manager + "/${hostName}.nix";
      })
    ];
  };
}

Maybe someone else will find it useful. Questions and feedback are welcome :slight_smile:

7 Likes

also related: Home Manager - NixOS Wiki

I managed to get the setup partly working on my system but ran into some problems with installing packages using the home.nix file ($(hostname).nix above). Problem is as follows:

When trying to install programs with eg. programs.zsh = { enable = true }; in the home.nix file, the home-manager switch command installs the relevant .zshrc file as read-only in the home folder (with some content I can’t really make sense of). Same issue with neovim

Removing the programs.zsh = { enable = true }; line from home.nix, adding zsh to home.packages section and running home-manage switch doesn’t generate those dotfiles at all and then I can add my own ones. Those seem to work just fine, but I think some options should be set in the home.nix with the programs.foo = {}; syntax.

If I happen to already have a dotfile in my home folder while using the first install method, I get the following error: Existing file '/home/lasse/.zshrc' is in the way of '/nix/store/b48r9asis6sawypq1h9dw002jpx28q5n-home-manager-files/.zshrc' Please move the above files and try again or use -b <ext> to move automatically.

Any idea what might cause the creation of those config files with the first install method?

programs.zsh.enable tells home manager you also want it to manage your
zsh dot files, which you can do through other
progrms.zsh.*
options.

It also enables home.sessionVariables to take effect in zsh, which
is the majority of the content.

1 Like

That makes sense, but raises other questions.

If I use the programs.zhs = { some configuration }; method, do I have to put all of the configuration of that program there? Or can I also have other (editable) dotfiles that are imported to complement that some configuration part?

Considering the two install methods (programs.foo = { enable = true; } ; and home.packages = [ foo ];), are there limitations on what can be done in terms of configuration that should be considered, or can exactly same configurations be made regardless of which install method is used?

Also, could you clarify what you mean by home.sessionVariables being the majority of the content?

If I use the programs.zhs = { some configuration }; method, do I have
to put all of the configuration of that program there? Or can I also
have other (editable) dotfiles that are imported to complement that
some configuration part?

In terms of zsh, you could add import <path to editable file> in the
programs.zsh.initExtra option, but this does kind of defeat the
purpose of using home-manager, as the idea is to be able to have
everything managed in nix. However, it might make some sense to use it
as a staging ground before moving things into nix. Note that nix paths
like source ${./path/to/file} are imported to the nix store and will
not be writable.

You do kind of have to look at the definition of each module to see what
is available. Most modules have an extra option, so as long as they
can import other files there should be opportunity to add a file.

Considering the two install methods (programs.foo = { enable = true; }
; and home.packages = [ foo ];), are there limitations on what can be
done in terms of configuration that should be considered, or can
exactly same configurations be made regardless of which install method
is used?

programs.foo would normally add home.packages = [ foo ]; as part of
their definition. See the zsh module
source.
.
If you want to configure via home-manager you can use programs.*, if
you don’t, then simply adding it to home.packages will work fine.

Also, could you clarify what you mean by home.sessionVariables being
the majority of the content?

I guess I mispoke a bit here as it looks like there’s a little bit more
than just session variables by default, and it would depend on what
options you have enabled, but judging from the module source it looks
like

  1. typeset is called to enable a few options. I am not sure what these
    options do however :smiley:
  2. All nix profiles are searched to add built in zsh functions and
    completions
  3. HELPDIR is corrected to point to the nix store.
  4. Any programs.zsh.localVariables that are defined are added.
  5. Any programs.zsh.sessionVarables that are defined are added.
  6. Completions are initialized, if enabled.
  7. hm-session-vars.sh is sourced, which contains session related
    things managed by home maanger, such as
    home.sessionVariables
  8. There’s some history settings
  9. The rest os user defined config.

Another thing to note is that if you have other modules enabled, like
gpg-agent,
it might add
things

as well, which is probably the second main reason to utilize
programs.zsh.

1 Like

There are a few problems with the programs.whatever = { ... } configuration management style in home-manager.

  1. For certain things, it’s just not practical to translate the utility’s natural config file into this format. For example powerlevel10k has a configuration wizard which spits out the complex configuration file. I just want to use that file, as is, without having to think about how it fits into the home-manager syntax. My zsh huge config predates my use of Nix, and I really don’t have the time or energy to try to migrate it into my home-manager config.

  2. Tweaking just one tiny thing requires a whole home-manager switch, which can be excruciatingly slow if you’re need to go through a rapid tweak-observe-adjust loop.

  3. Some programs, at times, modify their config files.

So I tend to keep most of my configurations in plain configuration files in my configuration repo, which also contains my home-manager configuration, and I ask home manager to link these to the locations where they are expected to be, thus:

{ config
, pkgs
, ...}:

let link = config.lib.file.mkOutOfStoreSymlink;
in
{
  home.file.".some-config-file".source = ../some-config-file;
}

Now, some-config-file gets copied into the nix store, and the copy seen at ~/.some-config-file is therefore immutable. This is great for preventing inadvertent modifications of the configuration state, but makes tweak-observe-adjust loops unbearable. That’s the purpose of link: by changing the relevant line to

  home.file.".some-config-file".source = link ../some-config-file;

home-manager creates a link to a mutable file outside of the nix store (inside my config repo), and I can now go through the tweak-observe-adjust process without having to home-manager switch every blessed time. When I’m done, I can remove link and go back to having an immutable config file.

However, there is still point 3. above. There will be trouble if a program that modifies its config files has an immutable config file. In that case I keep link in the file’s home-manager declaration permanently. That way we lose the Nix immutability guarantee, but at least I’ll notice that something has changed when I check the git status of my configuration repo.

4 Likes

I use powerlevel10k as well. Fortunately there is the extraInit parameter for doing whatever you want:

initExtra = ''
      source ${./p10k.zsh}
    '';

A call to link in there would work fine as well :wink:

I understand not wanting to take the time to migrate everything over though.

I think you’ve laid out a pretty good workflow here, but honestly it does all depend on how often you change things. I think if I was configuring something new, I would use link until it is in a reasonable state and then remove the call to link, especially for things that don’t have a home manager module with good config options (vim, emacs, etc.)

2 Likes

I generated it once and then use it as “zsh plugin”:

Though to be honest… P10K config isn’t that complicated, its just the generator that makes it appear like that.

It is a handfull of shell variables, that do not even need to be exported. If you can make sure they are set before loading P10K, then P10K will be fine.

1 Like

I surprised myself and actually managed to get pretty much everything working earlier today. Thank you for the explanations. Now the zsh-part of my home.nix looks like this:

  programs.zsh = {
    enable = true;
    enableCompletion = true;
    enableAutosuggestions = true;
    initExtraBeforeCompInit = "source ${pkgs.zsh-powerlevel10k}/share/zsh-powerlevel10k/powerlevel10k.zsh-theme";
    initExtra = "[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh";

    oh-my-zsh = {
      enable = true;
      plugins = [
        "colored-man-pages"
        "themes"
        "vi-mode"
        "vscode"
      ];
    };
    plugins = [
      {
        name = "zsh-syntax-highlighting";
        src = builtins.fetchGit {
          url = https://github.com/zsh-users/zsh-syntax-highlighting.git;
        };
      }  
    ];
  }

p10k configure can’t save to .zshrc but it doesn’t seem to matter as it generates the .p10k.zsh file anyway. Do you think the config looks fine or is there something unnecessary or wonky?

I might leave the neovim config outside of home manager for now because I just got it working and it’s so massive (GitHub - ChristianChiarulli/nvim: My neovim config) that I don’t know where to even begin moving it to home.nix

This looks awesome!

Do you think the config looks fine or is there something unnecessary
or wonky?

I think you could take some inspiration from @jacg and add

nix home.file.".p10k.zsh".source = link ./path/to/p10k.zsh;

somewhere if you would like to keep your powerlevel theme with your
home-manager configuration, although it sounds like maybe it is already
linked elsewhere?

I might leave the neovim config outside of home manager for now
because I just got it working and it’s so massive (GitHub -
ChristianChiarulli/nvim: Truly the Ultimate Neovim Config NVCode) that
I don’t know where to even begin moving it to home.nix

Yeah that is definitely the ultimate configuration, a daunting task to
be sure :grinning: