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  

to this:

~> 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.

History

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 stow?

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 $HOME.

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.

Code

See the GitHub page for the source and for configuration details.