Personal zsh configuration for macOS with Oh My Zsh.
.zprofile # Homebrew bootstrap (runs at login, before .zshrc)
.zshrc # Oh My Zsh config + sources modular scripts
.zsh/
prompt.zsh # Prompt overrides (full relative path instead of dir name)
environment.zsh # LANG, EDITOR, PATH entries
gnu-utils.zsh # GNU coreutils/findutils/etc. PATH setup
asdf.zsh # asdf version manager + custom `which` extension
aliases.zsh # Custom shell aliases
brew-check.zsh # Daily Homebrew update check with upgrade prompt
asdf-check.zsh # Daily check for newer stable python/ruby versions
.ssh/
config # Include directive and global Host * defaults
config.d/
github # SSH config for github.com (add work hosts locally, not here)
.gitconfig # Global git configuration
.gitignore_global # Global gitignore (symlinked to ~/.gitignore)
Brewfile # Homebrew packages (GNU utils, git, asdf, pdm, fonts)
install.sh # Bootstrap script for a fresh machine
update.sh # Sync packages/tool versions after pulling new dotfile commits
Clone to ~/dotfiles and run the install script:
git clone https://github.com/jpstroop/dotfiles.git ~/dotfiles
~/dotfiles/install.sh
exec zshThe install script will:
- Install Xcode Command Line Tools (if not already present)
- Install Homebrew packages from the Brewfile
- Install Oh My Zsh (if not already present)
- Symlink
.zprofile,.zshrc, and.zsh/into your home directory - Back up any existing files before overwriting
- Install asdf plugins (python, ruby), generate
.tool-versionswith latest versions on first run, and install them - Symlink
.gitignore_globalto~/.gitignore - Symlink
.gitconfig - Symlink
.ssh/config.d/githuband add anIncludedirective to~/.ssh/config - Make deployed dotfiles read-only to prevent accidental edits
Homebrew itself must be installed first: https://brew.sh
GNU coreutils, findutils, grep, sed, tar, and others are more full-featured
than the BSD versions that ship with macOS. We nstall them via Homebrew and
prepend them to PATH so they take precedence. LS_COLORS is set via
dircolors for colored ls output.
Adds asdf shims to PATH and sets up completions. We also extends asdf which
with an optional third argument to look up the executable path for a specific
version:
This makes it possible to run scripts with a specific version of python without
messing around with .tool-versions, e.g.:
$(asdf which python 3.14.3) -c "from sys import version_info as v; print(f'Hello from Python {v.major}.{v.minor}.{v.micro}')"
Hello from Python 3.14.3
$(asdf which python 3.11.14) -c "from sys import version_info as v; print(f'Hello from Python {v.major}.{v.minor}.{v.micro}')"
Hello from Python 3.11.14On shell startup, two checks run if it's been more than 24 hours since they last ran (each has its own stamp file in ~/.cache/):
brew-check.zsh— runsbrew updateand lists outdated packages with an interactive upgrade promptasdf-check.zsh— checks for newer stable python and ruby releases and prompts to update~/.tool-versionsand install
After pulling new commits, run update.sh to sync everything:
brew bundle— installs any packages added to the Brewfile- SSH public key symlinks — picks up any new
.pubfiles added to the repo asdf install— installs any tool versions added to.tool-versions
git -C ~/dotfiles pull
~/dotfiles/update.shOverrides the robbyrussell theme to show the full path relative to $HOME
(%~) instead of just the current directory name.
Overrides Oh My Zsh's ls alias to use GNU-compatible flags (--color=auto)
and show hidden files (-a).
The deployed copy lives at ~/dotfiles. Make changes in a separate working
copy (e.g. ~/workspace/dotfiles), then push to GitHub and pull into the
deployed copy. The install script makes deployed files read-only, so accidental
edits there will fail with a permission error.
Create a new .zsh file in the .zsh/ directory and add a source line to .zshrc:
source "${0:A:h}/.zsh/my-new-config.zsh"The ${0:A:h} pattern resolves symlinks, so source paths work whether
.zshrc is accessed directly or via the ~/.zshrc symlink.
~/.ssh/config uses the Include directive to load fragments from ~/.ssh/config.d/.
Only github is versioned here. Machine-specific hosts go in additional files in
~/.ssh/config.d/ and are never committed:
# Create a new fragment for work hosts (example)
cat > ~/.ssh/config.d/work << 'EOF'
Host bastion
HostName bastion.example.com
User jstroop
IdentityFile ~/.ssh/id_ed25519
ForwardAgent yes
Host dev
HostName dev.example.com
User jstroop
ProxyJump bastion
EOFPublic keys (.pub) are versioned for reference. Private keys are never committed
and must be set up manually after running install.sh.
On a new machine (generate fresh keys, then register the public key with GitHub/servers):
ssh-keygen -t ed25519 -C "your@email.com"
ssh-add --apple-use-keychain ~/.ssh/id_ed25519On a replacement machine (transfer keys from the old machine):
scp old-machine:~/.ssh/id_ed25519 ~/.ssh/
chmod 600 ~/.ssh/id_ed25519
ssh-add --apple-use-keychain ~/.ssh/id_ed25519--apple-use-keychain stores the passphrase in macOS Keychain so you are not
prompted on every reboot.
Security warning: Never add private keys or known_hosts to this repo. The root
.gitignore ignores everything in .ssh/ except config.d/github and *.pub files.
~/.secrets is sourced at the end of .zshrc for private environment
variables. It lives outside this repo and is created empty (mode 600)
by the install script.
# ~/.secrets
export GITHUB_TOKEN='ghp_...'
export AWS_ACCESS_KEY_ID='...'Security warning: Do not add ~/.secrets or its contents to this repo.
MIT