Stows is a simple script that makes it easy to run GNU Stow in multiple directories with one command. It uses symlinks to install and update your dotfiles, along with any other files you share between systems, while the files themselves are kept separate, organized, and under version control in as many different repositories as you want.
Here’s an example of Stows setting up dotfiles in a newly created user account. After checking out your dotfile repositories, your home folder goes from this:
~> ls -a # pretty bare Desktop/ dotfiles/ ~> ls dotfiles/* # three git repos have been checkout out dotfiles-common: bash/ emacs/ git/ misc/ mplayer/ tmux/ unison/ ... dotfiles-work: docker/ pylint/ servers/ ... dotfiles-secret: ssh/ git/ ... ~> ls dotfiles/dotfiles-common/bash/ # dotfiles are in topic directories .bash_logout .bash_profile .bashrc
~> stows -c ~/dotfiles ~> ls # the $HOME you know and love Desktop/ dotfiles/ doc/ mnt/ share/ src/ usr/ var/ ~> ls -d .* # decorated with your collection of config files .config/ .Xdefaults@ .dircolors@ .haskeline@ .mplayer@ .ssh@ .emacs.d/ .advirc@ .emacs.d.secret@ .idl@ .mrconfig@ .texmf-config@ .lftp/ .aspell.en.pws@ .ghci@ .inputrc@ .nanorc@ .tmux@ .local/ .autotest@ .gitconfig@ .ipython@ .pylintrc@ .tmux.conf@ .moc/ .bash_logout@ .gitconfig.secret@ .irbrc@ .racket@ .weatherrc@ .unison/ .bash_profile@ .gitignore@ .lesskey@ .racketrc@ .xmonad@ .Rprofile@ .bashrc@ .gnus.el@ .mailrc@ .spacemacs.d@ .xsessionrc@
Symbolic links have been created in
$HOME pointing to the files in
~/dotfiles. Those files are organized into multiple repositories of dotfiles (each repository is a “stow directory”, to use Stow terminology), where each repo contains topic subdirectories (“stow packages”) that contain the actual files that should be installed to the home directory.
Eight years ago I wanted a solution for this same configuration, but Stow was missing some functionality that I wanted. I ended up writing a similar tool, Sundae, and I was mostly happy using that until recently. The main problem was bootstrapping the configuration on new systems, partially because Sundae used Ruby and was written in a pre-Bundler era.
Rather than updating Sundae, I switched to a vcsh setup for the last year. Vcsh transparently mixes files from multiple git repositories by checking them all out into your home directory. It still seems ideal to me, in some aesthetic sense, but there was too much micromanaging and the cognitive load never decreased as much as I expected it would.
At some point, Stow gained almost all of the features I wanted. Stows just fills in the gap of not easily running stow on multiple directories.
Why not just use
One way of managing dotfiles is to keep them under version control in their own directory, and then to use to create symlinks in your
$HOME directory where the dotfiles are supposed to be. It’s also common to organize your dotfiles into directories that match the application or topic that they belong to: editors, git, shells, etc. For example,
~/stow/ fish/ .config/ fish/ completions/... functions/... config.fish git/ .gitconfig .gitignore .stow-local-ignore misc/ .config/ redshift.conf user-dirs.dirs .Xdefaults .dircolors .inputrc .nanorc # etc.
Keeping files separated into topic directories has a lot of advantages. It makes it easy to selectively install packages that are only needed on some systems. It also helps when you want to filter the version control history by topic (
git log -- just/this_dir/).
In this case, installing the dotfiles is easy:
> cd ~/stow > stow *
However, you might want to have several separate bundles of dotfiles—maybe bundles for different locations (home, work, servers, …) or for separating private files from those that can be publicly shared (your github token or server IP addresses, for example). Maybe you even want separate bundles of config files or scripts to be installed somewhere other than
That scenario ends up looking something like the following (where the actual dotfiles are inside the deepest folders shown):
~/dotfiles/ dotfiles-common/ fish/ git/ misc/ tmux/ unison/ ... dotfiles-work/ docker/ pylint/ servers/ ... dotfiles-secret/ ssh/ git/ gpg/ ...
You get only the files you want on any particular system, and you can keep certain files private or encrypted. But now, running
stow is a lot more complicated:
> cd ~/dotfiles/dotfiles-common > stow --target $HOME * > cd ../dotfiles-work > stow --target $HOME * > cd ../dotfiles-secret > stow --target $HOME *
This obviously scales badly when you add repositories. So maybe a one-liner
for loop could work? (Not shown on one line, for your sanity.)
> for dir in ~/dotfiles/*/; do \ stow --dir "$dir" --target "$HOME" --stow "$dir"*/ \ # <--- DOES NOT WORK done
This naive version doesn’t work because the glob expansion for the package directories will expand to something with slashes in it, and
stow expects just the package “names” (the plain directory names, like ‘tmux’). Even if this had worked, it wouldn’t respect the
.stowrc files that might be located in each of the
stow directories, which can provide default options.
The code that actually work isn’t much longer than that, but now you’re in shell script territory. And you might as well do a few other things while you’re at it, like making sure you don’t call
stow on plain files or on directories that aren’t stow directories. Hence, Stows.
It still took longer to write the documentation and this post than it took to write the tool itself, but having this particular dotfile strategy documented was part of the motivation. This setup is simple, useful, and flexible, and people should be able to use it without reinventing wheels.
See the GitHub page for the source and for configuration details.