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
.githooks/ Repo-local git hooks (activated via core.hooksPath)
.github/ GitHub platform config (rulesets, CI)
scripts/stow-deploy Stow wrapper with automatic conflict resolution
scripts/sync/ iCloud sync scripts
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-cryptStow >= 2.4.0 required. Versions 2.3.x have a bug with
--dotfilesand nested directories that breaks packages likessh,git, andgh. Install via Homebrew/Linuxbrew to get the latest version. Ubuntu 24.04's apt repo only has 2.3.1.
git clone git@github.com:brettdavies/dotfiles.git ~/dotfiles
cd ~/dotfiles
git-crypt unlock ~/.config/git-crypt/keySSH preferred: After the gitconfig is stowed, all GitHub URLs are rewritten to SSH via
url.insteadOf. Using SSH for the initial clone keeps things consistent. HTTPS also works for the initial clone since the rewrite rules aren't active yet.The git-crypt key must be copied from a secure backup (password manager). Without it,
stow/secrets/dot-secretsandstow/ssh/dot-ssh/configremain encrypted.
Use the stow-deploy wrapper for automatic conflict resolution:
cd ~/dotfiles
scripts/stow-deploy shell zsh bash git ssh ghostty gh claude codex cursor opencode pip brew secretsThe wrapper handles non-stow symlinks, existing plain files (--adopt), and always uses --no-folding. It also auto-configures core.hooksPath=.githooks for repo-local hooks (pre-commit branch protection, git-crypt auto-unlock, Git LFS chaining). For headless servers, add --headless to auto-restore repo versions after adopt.
Manual alternative (without conflict resolution):
cd ~/dotfiles/stow
stow --dotfiles --no-folding --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-Library to .Library:
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 .githooks/ 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) - SSH config uses
Match execfor platform-conditional 1Password agent paths (macOS and Linux) - Git signing uses
op-ssh-sign-wrapperwith cross-platform fallback (1Password on macOS,ssh-keygenon Linux) - All GitHub/Gist URLs are rewritten from HTTPS to SSH via
url.insteadOfin.gitconfig - SSH key must be named
~/.ssh/brett_ed25519on all machines - oh-my-zsh plugins: brew symlinks on macOS, git clones on Linux
Personal dotfiles — use at your own risk.