Ramblings of an aging IT geek
← Ramblings of an aging IT geek
tooling

my dotfiles, finally in order

Tidying years of accreted shell and editor config into a single git repo managed with GNU Stow, and the rule that finally made it stick.

A keyboard and a terminal

I have been meaning to sort my dotfiles out for about four years. Every new machine, I'd copy my .zshrc and .vimrc across, lose a couple of tweaks, and slowly let the two copies drift apart until neither was the canonical one. This fortnight I finally did it properly, and the thing that made it stick wasn't a clever tool. It was one rule.

The rule: if a config change is worth making, it's worth committing. Nothing lives only on a machine. Either it's in the repo or it doesn't really exist.

stow, not a bespoke install script

I'd tried this before with a hand-rolled install.sh that symlinked everything into place, and I always abandoned it because the script rotted faster than the configs did. This time I used GNU Stow, which is older than most of my career and does exactly one thing well.

The idea is that your repo mirrors the directory structure of your home folder, grouped into packages. So the layout looks like this:

dotfiles/
  zsh/
    .zshrc
    .zshenv
  vim/
    .vimrc
    .vim/
  git/
    .gitconfig
  tmux/
    .tmux.conf

A screen of source code

Then from inside the repo you run stow zsh vim git tmux and it symlinks each file into $HOME at the right place, refusing to clobber anything that's already a real file. Removing a package is stow -D zsh. That's the whole tool. No DSL, no YAML, no install script to maintain. On a fresh machine it's clone, run stow, done.

The package idea turned out to matter more than I expected. Because each tool is its own folder, I can put my heavy vim setup on a development box and skip it entirely on a server where I only ever need a sane shell and a couple of git aliases. Same repo, different subset stowed. I used to maintain a "minimal" branch for servers and a "full" one for my laptop, and keeping them in sync was its own little misery. Now there are no branches, just a different argument to stow.

The one wrinkle worth a warning: if a real file already exists where stow wants to put a symlink, it refuses, loudly, and rightly so. The first time you adopt an existing machine you'll spend a few minutes moving your current .zshrc out of the way so stow can take over. Do it once per box and you never think about it again.

the bits worth keeping

A few things I'm glad I did while I was in there:

  • Split .zshrc into a aliases, functions and path file that get sourced, so the main file stays readable and I can actually find things.
  • Moved every secret and machine-specific bit out into a ~/.localrc that is gitignored and sourced at the end if it exists. The repo is public; nothing private goes near it.
  • Wrote a two-line README reminding future-me how to bootstrap a new box, because I will absolutely forget.

The reward came a week later when I set up a new VM. Clone, stow, and my whole environment was there, prompt and aliases and vim plugins and all, in under a minute. Four years of "I'll do it properly later" undone in an evening. The only mystery is why I let it nag at me for so long when the actual job was this small.