Managing dotfiles with GNU Stow
Many developers manage their user-specific application configuration – alsoknown as dotfiles – in a version control system such as git. This allowsfor keeping track of changes and synchronizing the dotfiles across differentmachines. Searching on github, you’ll find thousands of dotfilerepositories.
As your dotfiles are sprinkled all over your home directory, managing them ina single repository is not trivial, i.e. how do you make sure that your.bashrc
, .tmux.conf
, etc. that life in your dotfile repository appear inthe proper places in your home directory? The most common solution is to usesymlinks so that the .tmux.conf
in your home directory is just a symlinkpointing to the appropriate file in your dotfile repository:
$ ls -l ~/.tmux.conflrwxrwxrwx 1 venthur venthur 34 18. Dez 22:53 /home/venthur/.tmux.conf -> git/dotfiles/tmux/.tmux.conf
This leads immediately to another problem: how do you manage the symlinks? Forthe longest time I just manually maintained the symlinks on the variousmachines, but this approach does not scale well with the number of dotfilesand machines you’re using this repository on. Often, people write their ownshell scripts that help them with the maintenance of the symlinks, but atleast the solutions I’ve seen so far did not convince me.
Last year I stumbled upon GNU Stow, an unpretentious little tool thatdoes not reveal at first sight how useful it would be for the job. Thedescription on the website says:
GNU Stow is a symlink farm manager which takes distinct packages of softwareand/or data located in separate directories on the filesystem, and makesthem appear to be installed in the same place.
Right.
How does it work?
In stow
’s terminology, a package is a set of files and directories thatneed to be “installed” in a particular directory structure. The targetdirectory is the root of the tree in which the package appear to beinstalled.
When you “stow” a package, stow
creates symlinks in the target directorythat point into the package.
Let’s say I have my dotfiles repository in ~/git/dotfiles/
. Within thisrepository, I have a tmux
package, containing the .tmux.conf
dotfile:
$ pwd/home/venthur/git/dotfiles$ find tmuxtmux # the packagetmux/.tmux.conf # the dotfile
The target directory is my home directory, as this is where the symlinksneed to be created. I can now stow the tmux
package into the targetdirectory like so:
$ stow --target=/home/venthur tmux
and stow
will create the appropriate symlinks to the contents of the packageinto the target directory:
$ ls -l ~/.tmux.conflrwxrwxrwx 1 venthur venthur 34 2. Jun 2021 /home/venthur/.tmux.conf -> git/dotfiles/tmux/.tmux.conf
Note that the name of the package (i.e. the name of the directory) does notmatter as stow
points the symlinks into the package, so you can chooseit freely. I usually use the name of the program that the configurationbelongs to as the package name.
Your package can also contain several files or even a complex directorystructure. Let’s look at the configuration for neovim, which lives below~/.config/nvim/
:
$ pwd/home/venthur/git/dotfiles$ find neovimneovimneovim/.configneovim/.config/nvimneovim/.config/nvim/init.vim$ stow --target=/home/venthur neovim$ ls -l ~/.config/nvimlrwxrwxrwx 1 venthur venthur 41 2. Jun 2021 /home/venthur/.config/nvim -> ../git/dotfiles/neovim/.config/nvim
At this point we should mention that the target directory for my dotfiles willalways be my home directory, so the contents of the packages are either theconfiguration files or the directory structure as they live in my homedirectory.
Deleting a package from the parent directory
You can also remove (unstow) a package from the target directory again, usingthe --delete
parameter:
$ ls -l ~/.tmux.conflrwxrwxrwx 1 venthur venthur 34 18. Dez 22:53 /home/venthur/.tmux.conf -> git/dotfiles/tmux/.tmux.conf$ stow --target=/home/venthur --delete tmux/$ ls -l ~/.tmux.confls: cannot access '/home/venthur/.tmux.conf': No such file or directory
Stowing several packages at once
Since your dotfile repository will likely contain more than one package, itmakes sense to combine the individual stow
commands into one, so instead ofstowing everything individually,
$ stow --target=/home/venthur tmux$ stow --target=/home/venthur vim$ stow --target=/home/venthur neovim
you can stow everything at once:
$ stow --target=/home/venthur */
Note that I use */
instead of *
to match all directories (i.e. packages),since my dotfiles repository also contains a README.md
and a makefile
.
Putting it all together
My dotfiles repository contains a makefile
that allows me tocreate/update or delete all symlinks at once:
all: stow --verbose --target=$$HOME --restow */delete: stow --verbose --target=$$HOME --delete */
The --restow
parameter tells stow
to unstow the packages first beforestowing them again, which is useful for pruning obsolete symlinks from thetarget directory.
Et voilà! Whenever I make a change in my dotfiles repository that involvescreating or deleting a dotfile (or a package), I simply call:
$ make
and everything is updated. To delete all dotfile-related symlinks from thismachine, I simply run:
$ make delete