Custom home-manager installation with NixOS
José Luis Lafuente
Posted on July 21, 2020
Recently I hear about home-manager-template. The project rationale resonates with me. In fact, I did something similiar on my NixOS configuration.nix
.
Apart of trying to make the home-manager installation more reproducible, my goals are:
- Install home-manager from my system
configuration.nix
. - Use home-manager from a git checkout. That makes easier for me to test new ideas or patches.
- Don't repeat my self. I don't want to define the same variable at the system level and in home-manager, e.g.: my username or my hostname.
To accomplish that, I have the following function in my configuration.nix
:
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";
})
];
};
}
Let's break that code to understand what is happening.
let
user = "john";
userHome = "/home/${user}";
hostName = "laptop";
First I define some variables I'll use later. It is possible to define a function where you pass them as arguments, but for now I'm just using a let block.
home-manager = { home-manager-path, config-path }:
assert builtins.typeOf home-manager-path == "string";
assert builtins.typeOf config-path == "string";
Now I define a function responsible for installing home-manager . It takes 2 arguments, home-manager-path
(path to my local home-manager git checkout) and config-path
(path to my main home-manager configuration file, usually called home.nix
). I'm also type-checking the arguments.
(
pkgs.callPackage
(/. + home-manager-path + "/home-manager") { path = "${home-manager-path}"; }
).overrideAttrs (old: {
I'm calling the derivation provided by home-manager, it is defined here:
https://github.com/rycee/home-manager/blob/master/home-manager/default.nix
Notice that it takes one argument, path
, the path to my local home-manager checkout. Since I need further customization, I use the overrideAttrs
function to produce a new derivation based on the original one.
nativeBuildInputs = [ pkgs.makeWrapper ];
buildCommand =
let
home-mananger-bootstrap = pkgs.writeTextFile {
# ...
}; in
''
${old.buildCommand}
wrapProgram $out/bin/home-manager --set HOME_MANAGER_CONFIG "${home-mananger-bootstrap}"
'';
These are the arguments for overrideAttrs
. Since I want to wrap home-manager, I need pkgs.makeWrapper
.
I'm extending the build command. I'm calling the original home-manager build command, and then wrapping it to set the environment variable HOME_MANAGER_CONFIG
to a file that I'm generating with the writeTextFile
helper function. More details on that later. Notice that HOME_MANAGER_CONFIG
is the entrypoint for home-manager, usually that file is home.nix
.
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} ];
}
'';
This is how I'm generating the main the entrypoint file for home-manager. It's a .nix
file itself, which is added to the nix store. When I generate my NixOS configuration, with nixos-rebuild build
, I know the value for some of the variables needed by home-manager, like my username or the hostname. I'm taking advange of that to generate a minimal home.nix
file, where the values for that variables are injected, and then I'm importing my real home-manager configuration (defined by the config-path
variable)
in
{
users.users.${user} = {
home = userHome;
packages = [
(home-manager {
home-manager-path = "${userHome}/home-manager";
config-path = builtins.toString ../home-manager + "/${hostName}.nix";
})
];
};
}
The last step, this is how I install home-manager from my NixOS configuration.nix
. I'm just adding the package returned by my home-manager
function. The 2 arguments to the functions are the path to my local home-manager checkout and the path to my home.nix
file.
Posted on July 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.