Personal macOS dotfiles — zsh, tmux, git, and a full developer toolchain.
📖 Read the docs → — full documentation site (MkDocs Material, deployed to GitHub Pages).
Security Note: This repository contains safe-to-share configuration templates. Machine-specific configs with actual GPG keys, work emails, and infrastructure URLs are stored in
~/.config/git/*.gitconfigand are never committed. See Git Commit Signing for details.
git clone https://github.com/bradbergeron-us/dotfiles.git ~/dotfiles
bash ~/dotfiles/bootstrap.shbootstrap.sh runs once on a fresh Mac and handles everything: Homebrew, all packages, runtimes (Ruby, Node, Java, Python, Go, Rust), dotfile symlinks, and macOS defaults. Open a new terminal when it finishes.
Day-to-day tasks run through one command, dotfiles (added to your PATH from ~/dotfiles/bin):
dotfiles status # repo state + last update
dotfiles doctor # read-only health check (status + verify + shell/terminal)
dotfiles update # pull, re-symlink, upgrade packages/runtimes, verify
dotfiles profile # show or set this machine's profile
dotfiles cleanup # remove backups, cache, and legacy configsIt's a thin wrapper over the scripts below — same flags, same behavior. See docs/cli.md. Equivalent direct form: bash ~/dotfiles/scripts/dotfiles.sh <command>.
bash ~/dotfiles/bootstrap.sh --dry-runShows what will be installed without making any changes. See docs/DRY_RUN_AND_PREFLIGHT.md for details.
What bootstrap.sh does, step by step
- Xcode Command Line Tools
- Homebrew (auto-detects Apple Silicon vs Intel)
brew bundle— all packages, casks, fonts fromBrewfile- fzf shell integration
- GitHub CLI auth (
gh auth loginif needed) - SSH key for commit signing — generated, added to Keychain, copied to clipboard
- Ruby 3.3.6, Node 22, Java 21, Python 3.12, Go 1.24 via mise
- Rust stable via rustup (+ rustfmt, clippy)
- TPM (tmux plugin manager)
install.sh— symlinks all dotfiles~/.zshrc.localfrom template- Work-specific configurations —
.m2,.yarnrc,.continue,.claude,.aws(optional prompt;workprofile only) - macOS developer defaults (optional prompt;
personal/workonly)
bash ~/dotfiles/update.shPulls the latest dotfiles, re-symlinks, upgrades all Homebrew packages, updates mise runtimes, Rust toolchain, and global gems. Finishes with a health check. Safe to run any time.
Preview first, or keep a machine deterministic by skipping package upgrades:
bash ~/dotfiles/update.sh --dry-run # preview everything; change nothing
bash ~/dotfiles/update.sh --no-upgrade # pull + re-symlink + verify only (no brew/mise/rustup/gem upgrades)--no-upgrade is handy on a work laptop where tooling is version-sensitive. If the dotfiles repo has uncommitted local changes, update.sh skips the git pull to avoid a rebase conflict (override with --force-pull); a failed rebase is aborted automatically so the repo is left untouched. See docs/work-machine.md for the work-machine workflow.
To schedule daily automatic runs via launchd (9 AM): bash ~/dotfiles/scripts/setup-scheduler.sh (add --no-upgrade so the scheduled run skips upgrades too). For a machine-wide default honored by both manual and scheduled runs, set NO_UPGRADE=true in ~/.config/dotfiles/update.conf (see home/examples/update.conf.example) — the launchd job reads it directly since it can't see your shell rc.
To run the health check standalone: bash ~/dotfiles/verify.sh
For a quick read-only snapshot (repo git state + last update.sh result): bash ~/dotfiles/scripts/status.sh (aliased as dotstatus).
To re-symlink without upgrading packages: zsh ~/dotfiles/install.sh
Each machine has a profile selecting which packages, dotfiles, and steps apply: personal (default, full GUI Mac), work, minimal, or server. Show or set it without re-bootstrapping:
bash ~/dotfiles/scripts/profile.sh # show the active profile (aliased: dotprofile)
bash ~/dotfiles/scripts/profile.sh set work # persist this machine's profileStored at ~/.config/dotfiles/profile; precedence is --profile flag > DOTFILES_PROFILE env > that file > personal. The profile drives which packages (Brewfile overlays) and dotfiles install, and which optional bootstrap steps run: work configs only on work, and macOS defaults only on personal/work (skipped on minimal/server). status.sh and verify.sh show the active profile in their banner.
What bootstrap.sh looks like when it runs
🚀 dotfiles bootstrap — macOS developer setup
─────────────────────────────────────────────────
Machine your-machine-name
Date Mon Jun 01 2026 08:00
─────────────────────────────────────────────────
▸ [1/14] 🛠️ Xcode Command Line Tools
✓ Xcode CLI Tools
▸ [2/14] 🍺 Homebrew
✓ Homebrew 4.5.2
▸ [3/14] 📦 Packages (brew bundle)
→ Installing packages from Brewfile...
✓ Brew packages installed
▸ [4/14] 🔍 fzf shell integration
✓ fzf configured
▸ [5/14] 🔑 SSH key for commit signing
✓ SSH key already exists at ~/.ssh/id_ed25519 — skipping
▸ [6/14] 🐙 GitHub CLI authentication
✓ GitHub CLI already authenticated
▸ [7/14] ⚡ Runtimes via mise (Ruby · Node · Java · Python · Go)
→ Installing Ruby, Node, Java, Python, and Go via mise...
✓ Ruby, Node, Java, Python, and Go installed via mise
▸ [8/14] 🧶 Yarn via Corepack (from Node)
→ Enabling Corepack (yarn + pnpm shims)...
✓ Corepack enabled — yarn 4.9.1
▸ [9/14] 🦀 Rust (rustup)
✓ rustup already installed: rustc 1.86.0
▸ [10/14] 🖥️ tmux plugin manager (TPM)
✓ TPM already installed
▸ [11/14] 📁 git-lfs
✓ git-lfs
▸ [12/14] 🔗 Dotfile symlinks
🔗 dotfiles ─ symlinking from ~/dotfiles
─────────────────────────────────────────────────
✓ current ~/.zshrc
✓ current ~/.gitconfig
✓ linked ~/.irbrc
✓ linked ~/.pryrc
...
─────────────────────────────────────────────────
✓ 3 linked · 14 current · 0 backed up
✓ 🎉 Done — open a new shell or: source ~/.zshrc
▸ [13/14] 🏢 Work-specific configurations
Setup work configs (.m2, .yarnrc, .continue, .claude, .aws)?
Run work configuration setup? [y/N]: y
✓ Work configurations installed
▸ [14/14] ⚙️ macOS developer defaults
Apply recommended macOS defaults? [y/N]: y
─────────────────────────────────────────────────
🎉 Bootstrap complete in 4m 23s
─────────────────────────────────────────────────
Next steps
1. Edit ~/.zshrc.local with machine-specific config
2. Open a new terminal (or: source ~/.zshrc)
3. Keep everything current: bash ~/dotfiles/update.sh
| File | Symlinked to | What it does |
|---|---|---|
home/zshrc + home/zsh/*.zsh |
~/.zshrc |
Modular shell config — PATH, aliases, functions, plugins, integrations (docs) |
home/zprofile |
~/.zprofile |
Login profile — Homebrew PATH setup |
home/gitconfig |
~/.gitconfig (include) |
Git defaults, delta pager, SSH signing — loaded via a thin ~/.gitconfig (not a symlink) so git config --global / tool writes stay out of the repo |
home/gitignore_global |
~/.gitignore_global |
Global ignores — macOS, editors, Java, Go |
home/tmux.conf |
~/.tmux.conf |
tmux — C-a prefix, vim keys, TPM plugins |
config/ghostty/config |
~/.config/ghostty/config |
Ghostty (preferred terminal) — Tokyo Night, JetBrains Mono Nerd Font (setup) |
home/hyper.js |
~/.hyper.js |
Hyper (fallback terminal) — Tokyo Night theme, JetBrains Mono |
config/starship.toml |
~/.config/starship.toml |
Starship prompt config |
config/mise.toml |
~/.config/mise/config.toml |
Global runtime versions (Ruby, Node, Java, Python, Go) |
config/direnvrc |
~/.config/direnv/direnvrc |
Shared layout python / layout node helpers |
home/ssh_config |
~/.ssh/config |
SSH agent + macOS Keychain |
home/gemrc |
~/.gemrc |
--no-document — faster gem installs |
home/irbrc |
~/.irbrc |
IRB/rails console defaults |
home/pryrc |
~/.pryrc |
Pry REPL defaults |
home/psqlrc |
~/.psqlrc |
psql defaults — \x auto, \timing, per-DB history |
home/editorconfig |
~/.editorconfig |
Global EditorConfig fallback |
config/nvim/init.lua |
~/.config/nvim/init.lua |
Neovim — dependency-free baseline (Space leader, 2-space indent, diagnostics) |
config/sheldon/plugins.toml |
~/.config/sheldon/plugins.toml |
Zsh plugins via sheldon — fast-syntax-highlighting, zsh-autosuggestions |
vscode/settings.json |
~/Library/.../Code/User/settings.json |
VS Code settings |
vscode/extensions.txt |
(auto-installed) | Core VS Code extensions |
Brewfile · Brewfile.personal · Brewfile.work |
(used by bootstrap) | Core CLI formulae + per-profile overlays (GUI casks/fonts/apps; work additions) |
home/npmrc |
~/.npmrc |
npm defaults — save-exact, no fund/update noise |
update.sh |
(run to update) | Upgrade all packages, runtimes, and gems; runs health check at end |
verify.sh |
(run to verify) | Health check — symlinks, missing tools, installed runtimes, stale backups |
uninstall.sh |
(run to uninstall) | Reverse install.sh — remove tracked symlinks, restore backed-up originals (--dry-run to preview) |
scripts/setup-scheduler.sh |
(run once) | Install launchd job to run update.sh daily at 9 AM |
scripts/dotfiles.sh · bin/dotfiles |
(run / dotfiles) |
Unified CLI — status, verify, doctor, update, profile, cleanup (docs) |
scripts/status.sh |
(run / dotstatus) |
Quick read-only health snapshot — repo git state + last update.sh result |
scripts/profile.sh |
(run / dotprofile) |
Show or set this machine's profile (personal/work/minimal/server) |
scripts/macos.sh |
(run once) | macOS developer defaults |
home/examples/zshrc.local.example |
(template) | Template for machine-specific overrides |
Quick reference — full descriptions, rationale, and usage in docs/tools.md
| Tool | What it does |
|---|---|
| Starship | Fast cross-shell prompt — git, language versions, command duration |
| zoxide | Smart cd — z proj jumps to frecent dirs |
| fzf | Fuzzy finder — Ctrl+R history, Ctrl+T file picker |
| Tool | What it does |
|---|---|
| bat | cat with syntax highlighting — aliased as cat |
ripgrep (rg) |
5–10× faster grep, respects .gitignore |
| fd | Faster find with sensible defaults |
| tree | Visual directory tree |
| Tool | What it does |
|---|---|
| git-delta | Syntax-highlighted diffs for git diff, log, show |
| lazygit | Terminal UI for git — rebase, cherry-pick, staged hunks |
| gh | GitHub CLI — PRs, issues, Actions from the terminal |
| Git Credential Manager | Cross-platform credential helper — GitHub, GitHub Enterprise, Bitbucket from one machine |
| Tool | What it does |
|---|---|
| jq | CLI JSON processor — curl ... | jq . |
| shellcheck | Shell script linter |
| tldr | Community cheat sheets — tldr curl vs man curl |
| httpie | Human-friendly HTTP client (http, https) |
| watch | Re-run a command on an interval |
| direnv | Per-directory env vars via .envrc — auto-loads on cd |
| newman | CLI runner for Insomnia/Postman collections |
| redis | In-memory data store — background jobs, caching, sessions |
| imagemagick | Image conversion — resize, crop, format conversion |
| pdftk-java | PDF toolkit — merge, split, fill forms, extract pages |
| pre-commit | Git hook framework — templates for Ruby, JS, Java |
| Tool | What it does |
|---|---|
| mise | Polyglot version manager — replaces chruby, nvm, pyenv |
| uv | Fast Python packages — replaces pip, virtualenv, pipx |
| ruff | Fast Python linter + formatter — replaces flake8, black, isort |
| Go (via mise) | Go 1.24 — ~/go/bin in PATH for go install tools |
| Rust (via rustup) | Rust stable + rustfmt + clippy |
| Yarn (via Corepack) | JS package manager — enabled from the mise-managed Node, no separate install |
| App | What it does |
|---|---|
| Ghostty | Preferred terminal — GPU-accelerated, native macOS; Tokyo Night + JetBrains Mono Nerd Font (config/ghostty/config) |
| Hyper | Fallback terminal — kept installed during the Ghostty transition (home/hyper.js) |
| Raycast | Launcher, clipboard history, window management — replaces Spotlight |
| Insomnia | GUI REST/GraphQL client — API design, collections, team sharing |
| VS Code | Editor — settings and extensions tracked in vscode/ |
| Neovim | Terminal editor — dependency-free baseline in config/nvim/init.lua |
| Postgres.app | PostgreSQL with a macOS GUI |
| DBeaver | Universal database GUI |
Packages are split by profile (see Machine profile):
Brewfile— core CLI formulae, installed for every profile.Brewfile.personal— GUI casks, fonts, and apps (personal,work).Brewfile.work— work-only additions: Gradle, kubectl, Helm (work).
bootstrap.sh installs the core file plus the active profile's overlays automatically; minimal/server get core only.
brew bundle --file=~/dotfiles/Brewfile # core CLI tools (all profiles)
brew bundle --file=~/dotfiles/Brewfile.personal # GUI casks/fonts/apps (personal/work)
brew bundle --file=~/dotfiles/Brewfile.work # work-only additions
brew bundle check --file=~/dotfiles/Brewfile # check what's missingTo add a package: put CLI formulae in Brewfile, GUI casks/fonts/apps in Brewfile.personal, then run brew bundle. To upgrade everything, use update.sh.
~/.zshrc.local is sourced last and never committed. Each machine has its own:
cp ~/dotfiles/home/examples/zshrc.local.example ~/.zshrc.localCovers Go/Rust overrides, Maven aliases, Java switching, direnv examples, corporate proxy, work git email, Sidekiq license keys, and more. See docs/work-machine.md for additional work-specific topics.
Commit signing is wired up but off by default in the committed gitconfig (commit.gpgsign = false). Enable it per machine in ~/.config/git/local.gitconfig, or sign automatically per organization for repositories under ~/Code/workN/ via Git's includeIf directives.
# Generate or select a GPG key and record it in ~/.config/git/local.gitconfig
zsh ~/dotfiles/scripts/setup_gpg_signing.sh
# Verify per-organization signing once configured
bash ~/dotfiles/scripts/verify_git_signing.shSee docs/GPG_SIGNING.md for the full guide — GPG and SSH signing, per-organization (work1/work2) conditional configs, uploading keys to GitHub/GitLab/Bitbucket, and troubleshooting.
For work laptops with corporate proxy, internal registries, and AWS Bedrock access:
# 1. Run standard bootstrap
bash ~/dotfiles/bootstrap.sh
# 2. Setup work configurations (.m2, .yarnrc, .continue, .claude, .aws)
bash ~/dotfiles/scripts/setup_work_configs.sh
# 3. Install Zscaler certificate
bash ~/dotfiles/scripts/install_zscaler_cert.sh
# 4. Install Claude Code CLI
bash ~/dotfiles/scripts/install_claude_code.sh
# 5. Configure AWS credentials
aws configure sso --profile bedrock
# 6. Install VS Code work extensions
bash ~/dotfiles/scripts/install_vscode_work_extensions.sh
# 7. Verify everything
bash ~/dotfiles/verify.shThe work configuration setup creates templates for:
- Maven (
~/.m2/settings.xml) — Nexus mirror and repository profiles - Yarn (
~/.yarnrc) — JFrog registry and auth settings - Bundle (
~/.bundle/config) — JFrog registry for Ruby gems - Continue IDE (
~/.continue/config.yaml) — AWS Bedrock models (Claude 4.5 & 3.7 Sonnet) - Claude Code (
~/.claude/settings.json) — AWS Bedrock environment and certificate path - AWS (
~/.aws/config) — Profile and region configuration - Certificates (
~/.continue/certs/) — Zscaler root certificate for corporate proxy - Claude CLI (
~/.local/bin/claude) — Claude Code command-line interface
All templates are safe to commit. Sensitive data (credentials, secrets, actual certificates) is stored locally and git-ignored:
system/certs/*.crt— Actual certificate files (ignored)system/installers/*— Binary installers (ignored)vscode/extensions/*.vsix— Extension files (ignored)~/.aws/credentials— Never created by scripts (use SSO or aws-vault)
See docs/work-setup-complete.md for:
- Prerequisites and required files
- Step-by-step installation guide
- Troubleshooting common issues
- Security best practices
- Advanced configuration
Or for additional work-specific topics: docs/work-machine.md
Reverse install.sh when you want to step a machine off the dotfiles. It is the mirror image of install — driven by the same config/symlinks.map SSOT and honoring the active profile.
bash ~/dotfiles/uninstall.sh --dry-run # preview everything; change nothing
bash ~/dotfiles/uninstall.sh # remove symlinks + restore backups (asks to confirm)
bash ~/dotfiles/uninstall.sh --yes # skip the confirmation prompt (for non-interactive use)What it does:
- Removes the tracked dotfile symlinks for the active profile — but only links that actually point into the repo. Symlinks that point elsewhere, and plain (non-symlink) files, are left alone.
- Restores the most recent
~/.dotfiles_backup/<timestamp>/entry for each destination it clears (best effort, matched by filename), so any original fileinstall.shmoved aside comes back. - Removes the thin
~/.gitconfiginclude thatinstall.shwrites (a real file, not a symlink — removed only when it includeshome/gitconfig), then restores any backed-up~/.gitconfig.
What it deliberately leaves alone (safe by design): it never deletes the dotfiles repo or your ~/.dotfiles_backup, never uninstalls Homebrew/mise/Rust packages, and never touches untracked local config such as ~/.zshrc.local or ~/.config/git/local.gitconfig. Removing a symlink only unlinks it; the tracked file in the repo is untouched. The script is idempotent — running it again is a no-op — and --dry-run prints exactly what a real run would remove and restore without changing anything. Re-link any time with zsh ~/dotfiles/install.sh.
Because most dotfiles are symlinked, editing ~/.zshrc edits ~/dotfiles/home/zshrc directly. (Exception: ~/.gitconfig is a thin include, not a symlink — edit home/gitconfig for shared settings; machine-specific or tool-written git config stays in ~/.gitconfig / ~/.config/git/local.gitconfig.)
cd ~/dotfiles
git add -A && git commit -m "describe change"
git pushThe full documentation site is published at https://bradbergeron-us.github.io/dotfiles/. Every page is also browsable in docs/:
| Doc | Contents |
|---|---|
| Home | Documentation landing page and site overview |
| Tutorials | Guided walkthroughs — your first hour, work laptop, adopting a profile |
| How-to Guides | Task recipes — dotfiles, packages, secrets, zsh plugins, tests, scheduling, recovery |
| Dry-Run & Pre-flight | Previewing changes and read-only system checks before bootstrap |
| GPG Commit Signing | SSH/GPG signing, per-organization configs, uploading keys, troubleshooting |
| Encrypted Secrets | sops + age workflow for encrypting and managing secrets |
| Machine Profiles | personal/work/minimal/server profiles, selection precedence, and how they drive packages, symlinks, and bootstrap steps |
| Usage & Lifecycle | Day-to-day lifecycle — bootstrap, dry-run, update, verify, status, and scheduling |
| dotfiles CLI | The unified dotfiles command — subcommands, delegation, and doctor |
| Zsh Configuration | Modular zsh layout — home/zsh/ modules, loading, local overrides, validation |
| Ghostty | Preferred terminal — install, validation, Hyper fallback, local overrides |
| Work Machine | Additional work topics — Brewfile.work, zshrc.local, direnv, NVM migration |
| Complete Work Setup Guide | End-to-end work machine setup — from fresh macOS to production-ready |
| Claude Code SSL Fix | Resolving Claude Code SSL/certificate issues behind a corporate proxy |
| Tool Reference | Full descriptions, rationale, and commands for every tool |
| Shell Performance | Shell startup optimization history and benchmarks |
| Architecture | How bootstrap, install, update, verify, status, and the SSOT files fit together |
| Troubleshooting | FAQ and fixes for common failures (updates, symlinks, tools, secrets, docs) |
| Glossary | Definitions of terms used across the docs |
| Personal Workstation Roadmap | Future backlog for the dotfiles CLI (not yet implemented) |
| Contributing | Contribution workflow — bats-core tests, shellcheck, CI jobs, and conventional commits |