For years my "dotfiles" were a folder of copies, half of them stale, the rest contradicting each other across three machines. Setting up a new box meant an afternoon of cargo-culting .zshrc lines I no longer understood. This week I finally fixed it properly, and the fix was much smaller than the years of avoidance suggested.
The whole thing now lives in one git repo, laid out so that GNU stow can symlink it into place. The structure mirrors your home directory, one folder per "package":
dotfiles/
├── zsh/
│ └── .zshrc
├── git/
│ └── .gitconfig
├── nvim/
│ └── .config/nvim/init.vim
└── tmux/
└── .tmux.conf
Then stow zsh git nvim tmux from inside the repo creates all the symlinks, respecting the directory tree. New machine, fresh checkout, one command, done. Unstowing is just as clean, which matters more than I expected: being able to back a change out without leaving orphaned files everywhere is what makes me willing to experiment.
The technical part took an evening. The discipline took longer to settle on. A few rules I've given myself:
- Machine-specific config lives in a
~/.zshrc.localthat's sourced at the end and is git-ignored. The repo holds nothing about any one host. The moment a hostname leaks into the shared config, the repo rots. - Secrets never go in. Not even "harmless" tokens. If something needs an API key it reads it from the environment or from a file the repo doesn't track.
- Every non-obvious line gets a one-line comment saying why it exists. The number of arcane settings I'd been copying forward without understanding was genuinely embarrassing.
There's a bootstrap.sh at the root that installs stow if it's missing, clones nothing (it assumes you've already cloned it), and runs the stow commands for the packages I always want. It's deliberately dumb. I've been burned by clever bootstrap scripts that try to detect your OS and do nineteen things, then fail on the twentieth and leave you halfway. This one does the symlinks and gets out of the way.
The payoff was immediate. I rebuilt my laptop at the weekend and was back to a familiar shell, editor, and git config in about two minutes of actual work. The real win, though, is subtler: because everything is now version-controlled and visible, I actually tidy it. A messy config you can see is a config you'll eventually fix. A messy config spread across six untracked files is one you'll keep apologising for. I should have done this in 2014.