Nix and Home manager setup
Goal
In this post, we will set up nix and home-manager configuration that can be used in any development machine. At the end, I would like to achieve following goals:
- Nix and Home Manager setup that can be easily trasnferred into different machines.
- Replace 99% of my usual
homebrew
orapt
usage with home manager.
For those of you that are impatient, a full snippets can be found at https://github.com/rexk/dotfiles/tree/nix-flake-install-stage-01
Installing Nix
Nix’s official installer is more then enough to get started.
https://nixos.org/download.html#download-nix
I won’t go too much detail on each installation options. I chose its recommended installation instructions.
Setting up dotfiles git repository
In order for the setup to be (somewhat) universal, I need way to share my configurations between different machines. For that, we are going to track and share dotfiles with git.
There are numerous articles explaning on how we can achieve this. My setup is particularly inspired by two articles:
In summary, our dotfiles git repository will have following characteristics:
- All files will be ignored with .gitignore to prevent accidental commit on sensitive information
- Git directory will be placed different from its work tree. This prevents nix’s flake honoring gitignore file.
Detailed instructions how to set up this dotfiles repository will be covered on a different post.
For now, consider that we are going to work on $HOME/.config
directory
Before we set up anything add following file to the $HOME/.config
directory
cat <<EOF>$HOME/.config/.gitignore
*
!.gitignore
EOF
Installing Nix-Darwin
While you don’t have to, it is good idea to install nix-darwin, if you are using a mac. We will go over nix-darwin setup using flake, in an another post.
Installing Home Manager with flake
If we follow instructions from the official home manager’s manual, we end up with following flake file.
# flake.nix
{
description = "Home Manager configuration of Jane Doe";
inputs = {
# Specify the source of Home Manager and Nixpkgs.
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11";
home-manager = {
url = "github:nix-community/home-manager/release-22.11";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, home-manager, ... }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
homeConfigurations.jdoe = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
# Specify your home configuration modules here, for example,
# the path to your home.nix.
modules = [
./home.nix
];
# Optionally use extraSpecialArgs
# to pass through arguments to home.nix
};
};
}
# home.nix
{
# Set following two fields in your home.user.nix properly
home.username = "jdoe";
home.homeDirectory = "/home/jdoe";
home.stateVersion = "22.11";
}
The installation instructs to update user specific information on home.nix and flake.nix. However, this isn’t enough for our use case. We need to be able to use same flake file on our each dev machines. Unfortunately, You might have different user names for each dev machines, like I do. Not only I have different user name, I have different system configurations for each machine.
For the flake.nix and home.nix to be used on each of my machine, I need to abstract user specific information out of flake.nix, and home.nix.
For that, I’ve made following changes.
{
- description = "Home Manager configuration of Jane Doe";
+ description = "Home Manager configuration";
inputs = {
# Specify the source of Home Manager and Nixpkgs.
- nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11";
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11";
+ nixpkgs-unstable.url = "github:NixOs/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager/release-22.11";
inputs.nixpkgs.follows = "nixpkgs";
};
+ flake-utils.url = "github:numtide/flake-utils";
};
- outputs = { nixpkgs, home-manager, ... }:
+ outputs = { nixpkgs, flake-utils, home-manager, nixpkgs-unstable, ... }:
let
- system = "x86_64-linux";
- pkgs = nixpkgs.legacyPackages.${system};
- in {
- homeConfigurations.jdoe = home-manager.lib.homeManagerConfiguration {
- inherit pkgs;
+ utils = flake-utils;
+ user = import ./user.nix;
+ in
+ utils.lib.eachDefaultSystem (system:
+ let
+ stable-pkgs = nixpkgs.legacyPackages.${system};
+ unstable-pkgs = nixpkgs-unstable.legacyPackages.${system};
+ pkgs = stable-pkgs // {
+ # provides alias for all ustable pkgs
+ unstable = unstable-pkgs;
- # Specify your home configuration modules here, for example,
- # the path to your home.nix.
- modules = [
- ./home.nix
- ];
+ starship = unstable-pkgs.starship;
+ git = unstable-pkgs.git;
+ };
+ in
+ {
+ formatter = pkgs.nixpkgs-fmt;
- # Optionally use extraSpecialArgs
- # to pass through arguments to home.nix
- };
- };
+ packages = {
+ homeConfigurations.${user.name} = home-manager.lib.homeManagerConfiguration {
+ inherit pkgs;
+
+ # Specify your home configuration modules here, for example,
+ # the path to your home.nix.
+ modules = [
+ ./home.nix
+ ];
+
+ # Optionally use extraSpecialArgs
+ # to pass through arguments to home.nix
+ extraSpecialArgs = {
+ inherit pkgs;
+ };
+ };
+ };
+ });
}
+let
+ user = import ./user.nix;
{
- home.username = "jdoe";
- home.homeDirectory = "/home/jdoe";
+ home.username = user.name;
+ home.homeDirectory = user.homeDir;
home.stateVersion = "22.11";
}
where user.nix
is rendered output of user.nix.tpl
# user.nix.tpl
{
name = "$USER";
hostname = "$(hostname)";
homeDir = "$HOME";
}
Above can be rendered with following script:
#!/usr/bin/env sh
#-------------------------------------------------
# render-user-nix
#-------------------------------------------------
# render user specific nix files for home-manager
# If the current environment is darwin,
# we will also render configurations for nix-darwin (osx)
render_user_nix() {
local file="$1"
local dest="$2"
if [ -f "$file" ];
then
local tmp="/tmp/$(basename $1)"
# substitutes sh experssions
(echo "cat <<EOF"; cat $file; echo EOF) | sh > $tmp
cp $tmp $dest
fi
}
CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}
render_user_nix $CONFIG_DIR/home-manager/user.nix.tpl $CONFIG_DIR/home-manager/user.nix
case "$(uname -s)" in
Darwin)
render_user_nix $CONFIG_DIR/nix-darwin/user.nix.tpl $CONFIG_DIR/nix-darwin/user.nix
;;
esac
# no need to keep the function
unset render_user_nix
Since I need this script for my other machines, I am going to place this script under ~/.config/bin
folder.
Additionally, I’ve pulled https://github.com/numtide/flake-utils into my flake. This lets me define flake configuration per each of systems that I use.
Applying home configuration
Since we are using flake installation, usual home-manager
command is not available in our path.
We need to explicitly run nix run
command.
If my user name is jdoe, the command will look like the following:
nix run /home/jdoe/home-manager#homeConfigurations.jdoe.activationPackage
However, I want to make these configurations work on any machine, so let’s provide another script for home-manager activation
#!/usr/bin/env sh
set -e;
#-------------------------------------------------
# hms
#-------------------------------------------------
# aliasing "home-manager switch" command for flake
CONFIG_DIR=${XDG_CONFIG_HOME:=$HOME/.config}
/nix/var/nix/profiles/default/bin/nix \
--extra-experimental-features "nix-command flakes" \
run $CONFIG_DIR/home-manager#homeConfigurations.\"$USER\".activationPackage
Final file tree
├── bin
│ ├── render-user-nix
│ └── hms
└── home-manager
├── flake.nix
├── flake.lock
├── home.nix
├── user.nix <-- generated by render-user-nix. Not checked in
└── user.nix.tpl