Nix series: my home-manager configuration

As part of my series on nix, I wanted to write about home-manager. Home-manager is a tool that allows for declarative user environment configuration in nix. What this means, is that you can specify your user installed programs and dotfiles, and home-manager handles calling the appropriate nix commands to instantiate that state. Home-manager can be used indpendently of NixOS, or can be imported as a module in a NixOS configuration. I prefer the former, as I run home-manager on nixpkgs-unstable, and keep my system (which essentially only manages my kernel and DE on the latest stable release.

Home-manager provides modules, which contain configuration options for nixpkgs. Generally, I prefer manually crafting the configuration files and having home-manager source these configurations.

My home-manager configuration is located here. Let’s dissect some of this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
{ config, pkgs, libs, ... }:
let
  sources = import ../../nix/sources.nix;
  pkgs = import sources.nixpkgs-unstable { };
  nixos-unstable = import sources.nixos-unstable { };
  powerlevel10k = pkgs.fetchFromGitHub {
    owner = "romkatv";
    repo = "powerlevel10k";
    rev = "b7d90c84671183797bdec17035fc2d36b5d12292";
    sha256 = "0nzvshv3g559mqrlf4906c9iw4jw8j83dxjax275b2wi8ix0wgmj";
  };
in

{
  imports = [
    ../../modules/cli.nix
    ../../modules/cuda.nix
    ../../modules/editors.nix
    ../../modules/git.nix
    ../../modules/nix-utilities.nix
    ../../modules/ssh.nix
  ];

  home.packages = with pkgs; [
      mu
      isync
    ];

  programs.zsh = {
    enable = true;
    enableCompletion = false;
    initExtraBeforeCompInit = builtins.readFile ../../configs/zsh/fedora_zshrc.zsh;
    plugins = [{
      name = "powerlevel10k";
      src = powerlevel10k;
    }];
  };

  programs.emacs = {
    enable = true;
    package = nixos-unstable.emacsGcc;
    extraPackages = (epkgs: [ epkgs.vterm] );
  };

  xdg.configFile."alacritty/alacritty.yml".source = ../../configs/terminal/alacritty.yml;
  services.lorri.enable = true;
  programs.home-manager.enable = true;
  programs.bash.enable = true;

  home.stateVersion = "20.03";
}

Niv

The first thing you’ll notice is that I am importing from sources.nix. This is a byproduct of my use of niv to manage my nixpkgs; niv allows me to “pin” my nixpkgs to specific commits, while also making it easy to automatically bump the commits to the latest revision from nixpkgs. This makes it very easy to rollback to a specific state, because I also keep the niv managed package versions checked into git here.

With nix, it’s easy to follow multiple channels, I merely import two channels managed by niv, and I can install packages from nixpkgs-unstable and nixos-unstable (more on that later)

1
2
  pkgs = import sources.nixpkgs-unstable { };
  nixos-unstable = import sources.nixos-unstable { };

Manually pinning nixpkgs

Sometimes, I get frustrated by upstream breakages and decide to permanently pin a package for awhile. Niv doesn’t facilitate this (yet), so instead, I manually import the package source using a nix fetcher with a set rev/sha256.

1
2
3
4
5
6
  powerlevel10k = pkgs.fetchFromGitHub {
    owner = "romkatv";
    repo = "powerlevel10k";
    rev = "b7d90c84671183797bdec17035fc2d36b5d12292";
    sha256 = "0nzvshv3g559mqrlf4906c9iw4jw8j83dxjax275b2wi8ix0wgmj";
  };

When I want to update this, I use:

nix-prefetch-github romkatv powerlevel10k

And copy the latest rev/sha into the fetcher to update the pinned package.

Modularizing your home.nix

I use four different variants of my home.nix depending on which OS I’m on. I’m trying to consolidate, but currently I use a laptop (macOS), workstation (Fedora), home (NixOS), and cluster/aws (Ubuntu mostly).

Therefore, I’ve modularized my home.nix, such that I can flexible combine different nix files depending on which operating system I’m on.

1
2
3
4
5
6
7
8
  imports = [
    ../../modules/cli.nix
    ../../modules/cuda.nix
    ../../modules/editors.nix
    ../../modules/git.nix
    ../../modules/nix-utilities.nix
    ../../modules/ssh.nix
  ];

Predominantly, what changes is the addition/subtraction of cuda/GUI related packages.

Installing packages declaratively with home-manager

Home-manager lets you declaratively specify which packages you would like installed, like so.

1
2
3
4
 home.packages = with pkgs; [
      mu
      isync
    ];

Note, that these packages are bing installed from the nixpkgs-unstable channel imported via niv. Furthermore, I can specify furhter packages in each of the modules that I imported previously. For example, in editors.nix, I append to the packagse specified in my top level home.nix.

1
2
3
4
5
6
7
8
9
{ config, pkgs, libs, ... }:

{
  home.packages = with pkgs;  with stdenv.lib; [
    neovim
    rnix-lsp
  ] ++ optionals stdenv.isLinux [ python-language-server ] ;
  xdg.configFile."nvim/init.vim".source = ../configs/neovim/init.vim;
}

Using home-manager modules

I personally prefer mostly to avoid modules and install packages declaritively while providing my own configs, but there are currently two exceptions zsh and emacs.

The following module enables zshrc, installs the powerlevel10k plugin from the pinned version, and sources my .zshrc, substituting it into the home-manager generated configuration.

1
2
3
4
5
6
7
8
9
  programs.zsh = {
    enable = true;
    enableCompletion = false;
    initExtraBeforeCompInit = builtins.readFile ../../configs/zsh/fedora_zshrc.zsh;
    plugins = [{
      name = "powerlevel10k";
      src = powerlevel10k;
    }];
  };

The following module enables emacs, note that I’m using the nixos-unstable channel here to hit the cachix cache provided my nix-community, such taht I don’t have to compile emacs from scratch. I’m also using the extraPackages attribute from home-manager to provide a nix-compiled vterm, to avoid having to recompile vterm on each emacs update (also, to ensure compatibility with NixOS).

1
2
3
4
5
  programs.emacs = {
    enable = true;
    package = nixos-unstable.emacsGcc;
    extraPackages = (epkgs: [ epkgs.vterm] );
  };

Sourcing configuration files

Finally, I’m sourcing my alacritty configuration file. This acts as basically a glorified GNU stow.

1
  xdg.configFile."alacritty/alacritty.yml".source = ../../configs/terminal/alacritty.yml;

in cli.nix, I do something very similar for most of my shared configuration:

1
2
3
4
5
  home.file.".gitconfig".source = ../configs/git/gitconfig;
  home.file.".aws/config".source = ../configs/aws/aws_config;
  home.file.".dircolors".source = sources.LS_COLORS.outPath + "/LS_COLORS";
  home.file.".tmux.conf".source = ../configs/tmux/tmux.conf;
  xdg.configFile."direnv/direnvrc".source = ../configs/direnv/direnvrc;

Conclusion

There you have it! That’s my nix config. Feel free to peruse my github and check it out. Let me know if you have any questions.