stows: Using stow with multiple dotfile repositories

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.

Keepass global autotype in Xmonad

I’m remigrating to xmonad and wanted to set up a key in my config to perform global auto-type for keepass2. No problem, right? Something like this:

myKeymap = [
            -- ...
            , (prefix "a", spawn "/usr/bin/keepass2 --auto-type")

Well, it works on the xmonad side of things, because keepass responds with the error,

"For global auto-type, the 'xdotool' utility/package is required (version 2.20100818.3004 or higher!)"

Read more...

An alias to apt-get a missing command

Debian’s apt-get and related tools are pretty fantastic. The command_not_found_handle tells you the package to install if the command you’re trying to run is not found.

> pacman
The program 'pacman' is currently not installed. You can install it by typing:
sudo apt-get install pacman
> # okay, fine, let me type or copy/paste that
> sudo apt-get install pacman

I don’t want that message to have an interactive prompt at the end (“Go ahead and install it? (y/n):”) but I do want a quick way of doing what it says. The computer just told me exactly what needs to be typed, so why do I need to type it myself?

This alias reruns your last command, grabs the last line from the error message, and runs it.

Read more...

lossfully: a music library transcoder

I’ll just explain my situation, but say “you” instead of “I” because I’ll assume it applies to you, too. If it doesn’t, you won’t have much use for this library anyway.

You have a large collection of music. After several misguided attempts at ripping all your CDs to mp3s or something, it’s finally mostly ripped (“for the last time”) to a lossless format like FLAC. But you still have a bunch of other lossy formats thrown in because…well, just because.

Now you want to take some of that music, shrink it in size, and put it on a portable device. You fire up some GUI program, drag the folders you want onto it, and ask it to give you back some Oggs. Or mp3s. Or whatever.

Ah yes, but now you’ve incurred the wrath of the information theory gods.

Read more...

relisp: Rubyfied Emacs Lisp

Okay, look. If you use Emacs extensively and have never wished you could use something to customize it besides Lisp…well, then, 100 hacker points for you. But not this project. This project is not for you.

This purpose of Relisp is to:

  • call Ruby from Emacs
  • call Elisp from Ruby
  • manipulate Emacs without using Elisp to some extent (Ruby wrappers around some Elisp functions and objects)
  • reduce the number of blog entries titled “Is Ruby an acceptable Lisp?” and flame wars under the title “ruby vs. lisp vs. scheme vs. haskell vs. …”

How about some examples?

Read more...