Home Manager
I recently switched to managing my dotfiles with home-manager and nix, and have slowly gone from trying to use it minimally to having it control a substantial portion of my neovim configuration.
Dotfiles
My dotfiles repository has long been a disorganized collection of scripts and
config files that get symlinked in by whichever tool I happen to be using at the
time. For a while it used rcm, but that required additional tools to be
installed before my dotfiles could be synced, and the capabilities of rcup
were a little lacking.
I switched to using dotbot after seeking out a slightly more structured tool, which gave significantly more flexibility for modularizing my config. I used dotbot as a git submodule, which meant that the tools I needed in order to bootstrap a host were only git and python3. Still, this setup still meant that I was using other tools to manage things like neovim plugins, and had implemented yet another collection of shell scripts for managing individual host configurations.
The dotbot config worked for a while, though after switching jobs and learning
that one of my coworkers was using nix to manage their home configuration, I
asked them to show me how all that worked. I was initially pretty skeptical, as
home-manager seemed like it wanted to manage things at a finer granularity
than I was comfortable with. For example, at the time I was using oh-my-zsh to
manage my zsh config. Switching to home-manager allowed for this, but changed
where the authoritative version of my ~/.zshrc
was.
I worked around this a bit by making as little use of home-manager’s builtin
configuration for programs that I relied on, but it soon became obvious that I
was missing out. For example, using eza
and having home-manager install all
the aliases necessary to use it transparently as ls
is really convenient, and
you don’t get that if you’re still trying to keep your shell config out of
home.nix
.
Neovim
This same sort of problem came into focus more recently, when I went to change from using the now unmaintained packer.nvim for lazy.nvim. As the locations for all the plugins I had installed had changed, many needed to be rebuilt. One that I wrote for managing markdown files (two-trucs) needs to build a rust binary that’s used for filtering buffers. In the past this was fine as I would use rustup to manage my rust toolchain, and it would always be available in my current environment. With home-manager though, I had started relying on per-project development environment configurations, and as an effect had been keeping the rust toolchain out of my default environment.
As I was now unable to build a plugin that I relied on for managing notes
throughout the workday, I decided to take the plunge and switch my neovim plugin
configuration over to being specified in my home-manager configuration. This
would mean that I could depend on two-trucs as a flake input to my dotfiles'
flake.nix, and build the rust binary in my neovim config. Concretely, the change
to my flake.nix
looked like the following:
I added the two-trucs repository to the inputs
section of flake.nix
, and
added the extraSpecialArgs
option to my home-manager config function:
{
description = "elliottt's home-manager configuration";
inputs = {
...
two-trucs = {
url = "github:elliottt/two-trucs";
flake = false;
};
};
outputs = { self, nixpkgs, home-manager, two-trucs, ... }: {
let
mkHostConfig = cfg:
let
pkgs = import nixpkgs {
system = cfg.system or "x86_64-linux";
overlays = [ nixgl.overlay ];
};
in home-manager.lib.homeManagerConfiguration {
inherit pkgs;
extraSpecialArgs = { inherit two-trucs; };
modules = [
cfg.home
./home.nix
];
};
in {
homeConfigurations = {
"trevor@badtz-maru" = mkConfig {
home = ./hosts/badtz-maru.nix;
};
...
};
};
};
}
After this change, I was able to start relying on the two-trucs
argument being
present in my neovim home-manager module. I modified it to build the binary, and
produce a vim plugin that could be used as input to programs.neovim.plugins
option in my home-manager config:
{ pkgs, two-trucs, ... }:
let
two-trucs-bin = pkgs.rustPlatform.buildRustPackage rec {
name = "two-trucs";
version = "0.1.0";
src = two-trucs;
doCheck = false;
cargoLock = {
lockFile = "${src}/Cargo.lock";
};
};
two-trucs-nvim-pkg = pkgs.vimUtils.buildVimPlugin {
name = "two-trucs";
version = "0.1.0";
src = two-trucs;
buildInputs = [ two-trucs-bin ];
buildPhase = ''
mkdir -p "$out/bin"
ln -s "${two-trucs-bin}/bin/two-trucs" "$out/bin/two-trucs"
'';
};
in
{
...
programs.neovim.plugins = [
two-trucs-nvim-pkg
];
...
}
Initially I tried to continue using lazy.nvim for all other plugins, only
relying on nix for the built version of two-trucs. However, I quickly ran into
problems, as lazy.nvim will take over the rtp
variable in neovim, and broke
the search path that included my nix-managed version of two-trucs. I
begrudgingly gave up on using lazy.nvim for pluging management, and it turned
out to be a great change: I could now depend on packaged versions of plugins
that had previously caused me some trouble during updates (treesitter
,
telescope-fzf-native
, etc).
Conclusions
More of my dotfiles config has been creeping into nix after my switch to using home-manager. While I was initially pretty hesitant to move more config into home-manager, it seems to be paying off by simplifying my default environment, and making updates easier. While nix and home-manager have been working really well for me recently, I’m not sure that I could recommend that anyone else follow this same path, as much of the tooling is under-documented, and current best-practices are really hard to discover.