Personal configuration files managed with GNU Stow and organized following the XDG Base Directory Specification.
- GNU Stow (
brew install stow)
git clone <repository-url> ~/Code/dotfiles
cd ~/Code/dotfiles && ./setupThis stows all packages and symlinks .stowrc to $HOME so that stow can be run from any directory. After installation, a wrapper at ~/.local/bin/stow extends GNU Stow with subcommands for managing dotfile packages, and unstow provides a shorthand for removing them. Run stow --help to see both wrapper and GNU Stow documentation.
Each top-level directory is a stow package. Contents mirror the target directory structure relative to $HOME.
| Package | Description | Config path |
|---|---|---|
bash |
Bash shell | ~/.config/bash/ |
nushell |
Nushell | ~/.config/nushell/ |
zshell |
Z Shell | ~/.config/zsh/ |
| Package | Description | Config path |
|---|---|---|
git |
Git | ~/.config/git/ |
ruby |
IRB, Pry, RuboCop, Gem | ~/.config/{irb,pry,rubocop,gem}/ |
python |
Python startup | ~/.config/python/ |
node |
npm | ~/.config/node/ |
postgres |
psql | ~/.config/postgres/ |
| Package | Description | Config path |
|---|---|---|
cmux |
tmux/Zellij session manager | ~/.config/cmux/ |
curl |
curl | ~/.config/curl/ |
eza |
eza ls replacement | ~/.config/eza/ |
ghostty |
Ghostty terminal | ~/.config/ghostty/ |
mise |
mise version manager | ~/.config/mise/ |
readline |
GNU Readline | ~/.config/readline/ |
ripgrep |
ripgrep | ~/.config/ignore |
starship |
Starship prompt | ~/.config/starship.toml |
wget |
wget | ~/.config/wget/ |
yazi |
Yazi file manager | ~/.config/yazi/ |
zellij |
Zellij terminal multiplexer | ~/.config/zellij/ |
| Package | Description | Config path |
|---|---|---|
home |
Shell profile, aliases, and dotfiles in $HOME |
~/.* |
stow |
Stow wrapper and unstow | ~/.local/bin/{stow,unstow} |
Use stow --simulate (dry-run) to preview what stow will do before making changes. The --simulate flag applies to stow commands passed through to GNU Stow, not to wrapper subcommands.
stow --simulate git # preview
stow git # applyStow creates symlinks from $HOME into the dotfile repository package directory. For example, stow git symlinks ~/.config/git/ to Code/dotfiles/git/.config/git/.
The path is resolved relative to the current directory. The package name is derived from the basename, stripping a leading dot if present.
stow add ~/.config/gradoo # from anywhere
stow add .config/gradoo # from $HOME
cd ~/.config && stow add gradoo # from ~/.config
stow add ~/.foo # dotfile in $HOME (package: foo)List directories in ~/.config not managed by any stow package:
stow scanThen adopt any listed package with stow add ~/.config/<name>.
When setting up a machine that already has config files where stow wants to create symlinks, --adopt moves those existing target files into the package and replaces them with symlinks. Review with git diff afterward — adopted files overwrite the repo versions.
stow --adopt --simulate git # preview
stow --adopt git # applyunstow git # prompts for confirmation
unstow --force git # skip confirmationStow reads .stowrc for default options including --dir (the dotfiles repository location) and --target=$HOME. The setup script symlinks .stowrc to $HOME so these options apply when running stow from any directory.
Most packages store config under ~/.config/ (XDG_CONFIG_HOME). Tools without native XDG support are redirected via environment variables in .profile (e.g. PYTHONSTARTUP, PSQLRC, INPUTRC, GNUPGHOME, CARGO_HOME, RUSTUP_HOME). See .profile for the full list.
Git identity and machine-specific settings go in ~/.config/git/config.local (not tracked). The git config includes this via [include] path = config.local.
Stow was chosen for the simplicity of its mental model where the dotfiles repository mirrors the source $HOME directory with individual packages at the repository root. Of special note, using stow in this way, the dotfiles repository does not take over ~/.config, which allows ~/.config to contain both managed and unmanaged dotfiles.
- No abstraction layer. Config files are stored as-is in the repository — what you see is what gets linked. There is no templating language, no compilation step, no intermediate format to learn or debug.
- The repository is the documentation. The directory structure mirrors
$HOME, no mapping file or manifest is needed. Each dotfiles package mirrors its structure in$HOMEand each can be symlinked independently,git/.config/git/configbecomes~/.config/git/config. - Single responsibility. Stow only manages symlinks. Version control is
git, files can be edited using any editor, and the shell setup is the current shell. Each tool does one thing. - No other dependencies beyond stow itself (
brew install stoworapt install stow).
The tradeoff is that Stow does not provide templating for per-machine differences. This is handled instead by convention: tools that need machine-specific config use include directives pointing to untracked local files (e.g. git/config.local).
Following the XDG Base Directory Specification keeps $HOME clean by grouping configuration files under ~/.config/, which aligns naturally with stow's package-per-directory model. Tools without native XDG support are configured to use the XDG base directories via environment variables in .profile.
macOS does not set XDG environment variables by default. These are set on login by a user-specific Launch Agent (~/Library/LaunchAgents/org.freedesktop.xdg-basedir.plist):
XDG_CACHE_HOME = "$HOME/Library/Caches"
XDG_CONFIG_HOME = "$HOME/.config"
XDG_DATA_DIRS = "/usr/local/share/:/usr/share/"
XDG_DATA_HOME = "$HOME/.local/share"
XDG_STATE_HOME = "$HOME/.local/state"