Reproducible personal-environment setup for macOS (WSL and devcontainer support coming later). One command turns a fresh Mac into a working machine β and re-running it is always safe.
git clone git@github.com:cvalong/dotfiles.git ~/dev/dotfiles
~/dev/dotfiles/install.shAfter install, edit ~/.gitconfig and fill in your name and email.
- Symlinks
~/.dotfilesto wherever you cloned the repo - Symlinks
~/.zshrc,~/.bashrc,~/.zprofile,~/.zshenvto the committed shell files (existing files backed up to~/.<name>.backup-<timestamp>) - Symlinks XDG-style configs from
config/<tool>/into$XDG_CONFIG_HOME/<tool>/(defaults to~/.config/<tool>/); currentlytmuxandaerospace - Sets up
~/.gitconfigwith three-case logic:- missing β copies from template, prompts you to fill in identity
- already references the dotfiles include β no-op
- exists without the include β appends
[include], leaves your identity untouched
- Idempotent: running it twice does nothing the second time
The script never overwrites your ~/.gitconfig once it has identity in it. Re-running on an existing machine is safe.
Most edits don't require re-running install.sh β symlinks mean every change is immediately live. Just reload your shell.
| Change | What to run |
|---|---|
Edit a symlinked file (zshrc, personal/functions.sh, etc.) |
exec zsh (or open a new tab) |
Edit personal/.gitconfig |
nothing β git reads it on every invocation |
Add a new file under personal/*.sh |
exec zsh β the glob picks it up |
Edit Brewfile |
brew bundle install --file=~/.dotfiles/Brewfile (add --cleanup --force to also uninstall removed entries) |
Add a new top-level config file (e.g. tmux.conf) |
edit install.sh to add a symlink_safe call, then re-run it |
Add a new XDG config (e.g. config/nvim/init.lua) |
drop the file under config/<tool>/, add a line to install_xdg_configs in install.sh, then re-run it |
The mental model: install.sh is for structural changes (the set of symlinks the installer manages). Content changes are immediate β just reload.
cd ~/.dotfiles
git pull
brew bundle install --file=Brewfile # if Brewfile changed
exec zsh # if any shell file changed
~/.dotfiles/install.sh # only if install.sh / file set changed$EDITOR ~/.dotfiles/personal/functions.sh
source ~/.dotfiles/personal/functions.sh # reload just this file
# test in current shell, commit when satisfieddotfiles/
βββ install.sh # idempotent installer, OS-aware
βββ zshrc # symlinked to ~/.zshrc
βββ bashrc # symlinked to ~/.bashrc
βββ zprofile # symlinked to ~/.zprofile
βββ zshenv # symlinked to ~/.zshenv
βββ Brewfile # `brew bundle install` packages
βββ config/ # XDG-style configs, mirrors ~/.config/
β βββ tmux/
β β βββ tmux.conf # symlinked to ~/.config/tmux/tmux.conf
β βββ aerospace/
β βββ aerospace.toml # symlinked to ~/.config/aerospace/aerospace.toml
βββ personal/
βββ .gitconfig # universal git preferences (no identity)
βββ gitconfig.local.template # template copied to ~/.gitconfig on first install
βββ functions.sh # `rebase_onto`, `delete_merged_branches`
- Symlinks for everything. Edit a file in this repo, change applies everywhere. No drift between repo and
$HOME. - Identity stays out of the repo.
personal/.gitconfigcarries aliases and behavior; the per-machine~/.gitconfigshim carries name/email. Same idea as.env.examplevs.env.local. - Idempotent install. Codespaces re-runs
install.shon every container spin-up, so non-idempotency is a real bug, not a smell. - Re-runnable everywhere. Existing files get backed up before being replaced. Existing
~/.gitconfigidentity is never overwritten.
- Phase 1.5 β SSH commit signing (
gpg.format=ssh) - Phase 2 β wire
brew bundle installintoinstall.sh; capture editor settings/extensions - Phase 3 (partial) β XDG configs for
tmuxandaerospacecaptured. Still TODO:~/.config/{nvim,ghostty},.ssh/config - Phase 4 β Codespaces / devcontainer integration
- Phase 5 β Windows / WSL support (chezmoi for cross-OS templating if needed)