Personal dotfiles managed with GNU Stow for syncing across macOS and Linux machines.
Note: Active development of the dotfiles CLI tool has moved to dotfiles-cli (Rust). This repository is now a configuration store.
stow/ Stow packages — each subdirectory symlinks into $HOME
config/shell/ Shell environment fragments sourced by .profile
scripts/ Sync scripts and git hooks
Each directory under stow/ is a stow package. Files prefixed with dot- are converted to dotfiles (. prefix) when symlinked via stow --dotfiles.
| Package | What it manages |
|---|---|
bash |
.bashrc, .bash_profile, .bash_aliases |
brew |
Brewfile, Brewfile.optional |
claude |
.claude/ (settings, hooks, statusline, CLAUDE.md) |
codex |
.codex/config.toml |
cursor |
.cursor/, extensions.txt |
gh |
.config/gh/ (GitHub CLI) |
ghostty |
.config/ghostty/config |
git |
.gitconfig, .config/git/ (ignore, allowed_signers) |
local |
.local/bin/env, macOS LaunchAgent (requires special handling) |
opencode |
.config/opencode/config.json |
pip |
.config/pip/ |
secrets |
.secrets (encrypted via git-crypt) |
shell |
.profile |
ssh |
.ssh/config (encrypted via git-crypt) |
zsh |
.zshrc, .zprofile, .p10k.zsh |
.profile resolves the repo root via its own symlink, then sources every *.sh file in config/shell/. Adding a new file to this directory automatically picks it up — no manifest to maintain.
| File | Purpose |
|---|---|
caches.sh |
XDG cache directory locations |
claude-code.sh |
Claude Code environment variables |
github.sh |
GitHub CLI aliases |
litellm.sh |
LiteLLM proxy configuration |
lm-studio.sh |
LM Studio PATH setup |
models.sh |
AI/ML model storage locations (~/models) |
python.sh |
Python tooling config (Poetry, etc.) |
telemetry.sh |
Telemetry opt-out environment variables |
shell-functions |
Shell utilities (no .sh extension — sourced by bashrc/zshrc directly) |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/opt/homebrew/bin/brew shellenv)" # Apple Siliconbrew install stow git-cryptgit clone <your-repo-url> ~/dotfiles
cd ~/dotfiles
git-crypt unlock ~/.config/git-crypt/keyThe git-crypt key must be copied from a secure backup (password manager). Without it,
stow/secrets/dot-secretsandstow/ssh/dot-ssh/configremain encrypted.
Most packages use --dotfiles to convert dot- prefixes to . prefixes:
cd ~/dotfiles/stow
stow --dotfiles --target="$HOME" \
shell zsh bash git ssh ghostty gh claude codex cursor opencode pip brew secretsThe local package requires separate handling because --dotfiles would incorrectly convert dot-local and dot-Library to hidden directories:
# .local/bin/env (this one is fine with --dotfiles)
cd ~/dotfiles/stow/local
stow --dotfiles --target="$HOME" dot-localbrew bundle --file=~/dotfiles/stow/brew/BrewfileOptional packages:
brew bundle --file=~/dotfiles/stow/brew/Brewfile.optionalInstall oh-my-zsh:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattendedHomebrew installs the plugins and theme as formulae. They need to be symlinked into oh-my-zsh's custom directory:
BREW_SHARE="$(brew --prefix)/share"
OMZ_CUSTOM="$HOME/.oh-my-zsh/custom"
# Plugins
mkdir -p "$OMZ_CUSTOM/plugins"
ln -sf "$BREW_SHARE/zsh-autosuggestions" "$OMZ_CUSTOM/plugins/zsh-autosuggestions"
ln -sf "$BREW_SHARE/zsh-syntax-highlighting" "$OMZ_CUSTOM/plugins/zsh-syntax-highlighting"
ln -sf "$BREW_SHARE/zsh-completions" "$OMZ_CUSTOM/plugins/zsh-completions"
# Theme
mkdir -p "$OMZ_CUSTOM/themes"
ln -sf "$BREW_SHARE/powerlevel10k" "$OMZ_CUSTOM/themes/powerlevel10k"OMZ_CUSTOM="$HOME/.oh-my-zsh/custom"
# Plugins
git clone https://github.com/zsh-users/zsh-autosuggestions "$OMZ_CUSTOM/plugins/zsh-autosuggestions"
git clone https://github.com/zsh-users/zsh-syntax-highlighting "$OMZ_CUSTOM/plugins/zsh-syntax-highlighting"
git clone https://github.com/zsh-users/zsh-completions "$OMZ_CUSTOM/plugins/zsh-completions"
# Theme
git clone --depth=1 https://github.com/romkatv/powerlevel10k "$OMZ_CUSTOM/themes/powerlevel10k"Ghostty on macOS checks both ~/.config/ghostty/ (created by stow) and ~/Library/Application Support/com.mitchellh.ghostty/. Create the second symlink manually:
mkdir -p "$HOME/Library/Application Support/com.mitchellh.ghostty"
ln -sf ~/dotfiles/stow/ghostty/dot-config/ghostty/config \
"$HOME/Library/Application Support/com.mitchellh.ghostty/config"The LaunchAgent cannot be stowed with --dotfiles (it would create ~/.Library/ instead of ~/Library/). Symlink it manually:
mkdir -p "$HOME/Library/LaunchAgents"
ln -sf ~/dotfiles/stow/local/dot-Library/LaunchAgents/com.user.devtosync.plist \
"$HOME/Library/LaunchAgents/com.user.devtosync.plist"
launchctl load "$HOME/Library/LaunchAgents/com.user.devtosync.plist"while IFS= read -r ext; do
[[ "$ext" =~ ^[[:space:]]*#|^$ ]] && continue
cursor --install-extension "$(echo "$ext" | xargs)"
done < ~/dotfiles/stow/cursor/extensions.txtexec zshcd ~/dotfiles/stow
stow --dotfiles --target="$HOME" -R <package>Sensitive files are encrypted with git-crypt:
stow/secrets/dot-secrets— API keys and tokensstow/ssh/dot-ssh/config— SSH host configurationsstow/git/dot-config/git/allowed_signers— SSH allowed signers
Git hooks in scripts/git-hooks/ auto-unlock on checkout and merge.
Back up the key file (~/.config/git-crypt/key) in a password manager. If lost, encrypted files cannot be recovered.
- Shell configs use
$HOMEand conditional$OSTYPEchecks - Homebrew setup in
.profileis gated behinddarwin*detection - VS Code stow targets
Library/Application Support/(macOS only) - 1Password SSH agent paths in
.ssh/configand.gitconfigare macOS-specific - oh-my-zsh plugins: brew symlinks on macOS, git clones on Linux
Personal dotfiles — use at your own risk.