Replacing GNU Stow with Dotbot

One of my guilty pleasures is maintaining my dotfiles. This is one project I can yak-shave and play with as much as I want without caring about the actual impact or in general the usefulness of the whole endeavour. So this weekend I decided to indulge myself.

Part of the process was cleaning up the older dotfiles and updating them to newer preferences. And the other part (which is the subject of this blog post) was the installation process, which is basically the process of how these files get from my dotfiles repository to my home folder.

There are many different ways you can do this. So far, I've been using GNU Stow for this purpose.

GNU Stow

Stow is a useful little program to help you manage symbolic links.

Given a source directory and a target directory, it is able to add symbolic links into the target directory that point back to the source. So if you're maintaining your dotfiles in a git repository and want to have your home folder link back to them, Stow is perfect for this.

As an example, assume your dotfiles project contains a nvim folder for your Neovim configuration files and that it should be linked to ~/.config/nvim, Stow can do that for you if you run stow nvim --target $HOME/.config.

If you maintain multiple dotfiles, your setup process might end up in a shell script resembling the following:

mkdir -p ~/.config/kitty
stow kitty --target $HOME/.config

mkdir -p ~/.config/nvim
stow nvim --target $HOME/.config

stow zsh --target $HOME/

While this does the job, the one thing that never really sat well with me (no actual impact, remember?) was the fact that it's a shell script.

Generally speaking, I'm allergic to shell scripts. I don't write them on a regular basis, so when I do have to work with one, I constantly need to look up the documentation of the most primitive things (eg. "bash if else syntax") which is really unproductive. Debugging shell scripts is also not the most fun activity. So in general, I try to avoid them as long as I can.

Dotbot

This weekend I was looking for a dotfiles manager (again, no actual impact) and found dotbot.

Dotbot is a dotfile bootstrapper that takes in a (YAML) configuration file that describes the symbolic link definitions (and some other metadata) and updates those definitions on disk. The functionality it offered looked almost exactly like what I wanted so I decided to give it a try.

The installation feels a bit clunky (the default instructions recommend installing it as a git submodule) but luckily it's published on PyPI as a package so we can use pipx to install it.

$ pipx install dotbot

The next step was to remove the stow invocations in favor of a dotbot configuration file. The most recent version of my config file is available here, but essentially it looks something like this:

- defaults:
    link:
      relink: true

- link:
    ~/.config/kitty/kitty.conf: kitty/kitty.conf
    # ...
    ~/.zshrc: zsh/zshrc

- create:
    - ~/.config/kitty

The defaults section contains some default configuration options which I'm not going to write about here as the README does a much better job of explaining what it is for.

The real work happens inside link, which is basically a key/value map of the dotfile's destination and the source. This is where you can tell dotbot what should be linked and where. Lastly, the create section contains a list of directories that should be created prior to any linking taking place.

With this place, dotbot can be invoked on the command line to set up all the symbolic links in one go:

$ dotbot --config-file install.conf.yaml

Link exists ~/.config/kitty/kitty.conf -> /home/siddhant/Work/projects/dotfiles/kitty/kitty.conf
...
Link exists ~/.zshrc -> /home/siddhant/Work/projects/dotfiles/zsh/zshrc
All links have been set up
Path exists /home/siddhant/.config/kitty
...
All paths have been set up

==> All tasks executed successfully

This feels much cleaner than the original shell script. Of course, given how many dotfiles I have (which is not that many), the original script would have sufficed. But as I mentioned before, none of this is an exercise in usefulness, so I personally consider this change perfectly acceptable. 🌝

Encrypted files

The one thing I'm missing from dotbot is support for encrypted files. It would be nice if you could add encrypted files to the git repository and have dotbot decrypt them automatically before putting them in place.

Note that there is a shell directive for running shell commands. So in theory, we could add a shell command for decrypting a sensitive file before the symbolic links are established. Unfortunately, it seems like these commands are run after the linking has been done, while we want the decryption to happen before.

This means that I've been forced to add back a setup.sh file which looks like this:

# decrypt crestic password
age --decrypt --identity ~/.age/keys.txt --output crestic/password crestic/password.enc

# install
dotbot --config-file install.conf.yaml

The first line uses age to decrypt a password file and put it in a location where dotbot can read it and eventually place the link in the right location. This is less than ideal, but it works.

Conclusion

Overall I have really come to like dotbot. It does everything I need it to do and some more.

There is also support for plugins and there are a bunch of nice plugins out there as well. For instance, you can manage your crontab schedule or specify a list of apt/snap packages to be installed automatically. While not a strict necessity, it's nice to know that these things are there.

In any case, I do see myself using dotbot going forward. At least until the next time I feel the need to yak-shave.