A Developer's Mac Setup Playbook
Source code: github.com/frankmanley/mac-setup — clone it, edit the variables, run it.
Interactive guide: Migration Guide — step-by-step walkthrough with navigation.
Every time I set up a new Mac, I go through the same ritual. Install Homebrew, symlink dotfiles, tweak 90 system defaults, realize I forgot something, start over. So I automated the whole thing into a single script that handles 12 steps and a set of modular configs that turn a fresh machine into a working dev environment in under an hour.
This post walks through everything the script does, why each piece is there, and how to make it yours.
What’s in the Repo
mac-setup/
├── mac-setup.sh # 12-step installer — the main event
├── pre-migrate.sh # Run on your OLD machine first
├── verify.sh # Post-setup health check (10 categories)
├── MIGRATION-GUIDE.html # Interactive reference guide
└── dotfiles/
├── link.sh # Symlinks all dotfiles to $HOME
├── .zshrc # Shell config (Starship, Zoxide, fzf, Mise)
├── .gitconfig # Git aliases, defaults, pager
├── .gitignore_global # System-wide git ignores
├── .editorconfig # Cross-editor formatting rules
├── .macos # 90+ macOS system defaults
├── Brewfile # Homebrew package manifest
├── folder-actions.sh # File automation daemon
├── starship.toml # Prompt theme
└── .wezterm.lua # Terminal config
The key design decision: configs are canonical in dotfiles/ and symlinked to $HOME via link.sh. Change a file in one place, it updates everywhere.
Quick Start
git clone https://github.com/frankmanley/mac-setup.git
cd mac-setup
Edit the variables at the top of mac-setup.sh:
GIT_NAME="Your Name"
GIT_EMAIL="your-email@example.com"
WORKSPACE_NAME="workspace"
NODE_VERSION="22"
PYTHON_VERSION="3.12"
Then run it:
chmod +x mac-setup.sh && ./mac-setup.sh
If you’re migrating from an old machine, run pre-migrate.sh there first — it exports your Brewfile, SSH/GPG keys, git repo health, app preferences, and macOS settings into a portable bundle.
The 12 Steps
The script runs through these in sequence, skipping anything already installed:
- Xcode Command Line Tools — prerequisite for everything
- Homebrew — package manager (detects Apple Silicon automatically)
- PARA folder structure —
~/Desktop/workspace/{01_projects, 02_areas, 03_resources, 04_archive}plus~/Screenshotsand~/Screen Recordings - Core apps via Homebrew — Raycast, WezTerm, JetBrains Mono, plus all CLI tools
- Dotfile symlinks — runs
link.shto connect everything - Shell configuration — Starship prompt, Zoxide, fzf keybindings, Mise, project aliases
- macOS defaults — 90+ system preferences via
defaults write - Git configuration — identity, rebase by default, bat as pager
- Mise runtimes — Node and Python global versions
- SSH key — generates ed25519, adds to agent, prompts for GitHub
- GUI apps — reminder to install the rest manually
- Verification — checks everything installed correctly
Each step is idempotent. Run it again and it skips what’s already done.
What Gets Installed
CLI Tools (via Brewfile)
The Brewfile is organized into logical groups:
| Group | Packages | Why |
|---|---|---|
| Shell & nav | starship, zoxide, fzf | Cross-shell prompt, smart cd, fuzzy everything |
| Dev tooling | mise, gh, git-lfs | Version manager, GitHub CLI, large file support |
| Modern CLI | bat, eza, ripgrep, fd, jq, tree, tldr, htop | Better cat, ls, grep, find, and friends |
| GNU utils | coreutils, gnu-sed | Match Linux syntax (every Stack Overflow answer assumes GNU) |
| File & media | wget, imagemagick, rename, pigz, p7zip, moreutils | Batch processing, compression, extra utils |
| Automation | mas, fswatch | Mac App Store CLI, file system watcher |
GUI Apps
The defaults: Raycast (Spotlight replacement), WezTerm (GPU-accelerated terminal), JetBrains Mono (dev font with ligatures), Obsidian, Spotify, Syncthing.
Add your own to the EXTRA_CASKS array in mac-setup.sh.
Shell Setup
The .zshrc replaces default CLI tools with modern alternatives:
alias cat="bat --style=plain"
alias ls="eza --icons --group-directories-first"
alias ll="eza --icons --group-directories-first -la"
alias lt="eza --icons --tree --level=2"
alias grep="rg"
alias find="fd"
fzf Configuration
fzf gets a Catppuccin Mocha color scheme and uses fd for file finding (which respects .gitignore):
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_ALT_C_COMMAND='fd --type d --hidden --follow --exclude .git'
The keybindings worth memorizing:
| Shortcut | Action |
|---|---|
Ctrl+R | Fuzzy search shell history |
Ctrl+T | Fuzzy find files |
Alt+C | Fuzzy cd into directories |
Project Aliases
The script generates workspace shortcuts from the PROJECT_ALIASES array:
alias ws="cd ~/Desktop/workspace"
alias proj="cd ~/Desktop/workspace/01_projects"
Add your own — one entry per project you want fast access to.
Git Configuration
The .gitconfig has some defaults that eliminate daily friction:
[push]
autoSetupRemote = true # no more --set-upstream
[pull]
rebase = true # clean linear history
[core]
pager = bat --style=plain --paging=always
excludesFile = ~/.gitignore_global
[merge]
conflictstyle = diff3 # shows base in conflicts
Git Aliases
git st # status -sb (short, branch info)
git lg # pretty graph log (last 20)
git sync # pull --rebase && push
git cb <name> # checkout -b (create branch)
git ca # commit --amend --no-edit
git df / git ds # diff / diff --staged
git cleanup # delete merged branches
git whoami # show current identity
Global Gitignore
The .gitignore_global covers the things you never want committed: .DS_Store, .env files, node_modules/, __pycache__/, IDE folders, credentials, and key files.
macOS Defaults
The .macos script applies 90+ defaults write commands organized into categories. The high-impact ones:
Keyboard — Key repeat rate at 2 (fastest), initial delay at 15 (shortest). Disables auto-correct, smart quotes, and smart dashes. Full keyboard access in all dialogs.
Finder — Show all file extensions, show hidden files, path bar and status bar visible, list view by default, search current folder first. Disable .DS_Store on network and USB volumes.
Dock — Auto-hide enabled, minimize to application icon, don’t rearrange Spaces by most recent use.
Screenshots — Save to ~/Screenshots (not Desktop), PNG format, no floating thumbnail, no shadow on window captures.
Security — Firewall enabled, file descriptor limits raised to 65536.
After applying defaults, the script restarts Finder, Dock, and SystemUIServer. Some settings need a logout to take effect.
Folder Actions
folder-actions.sh is an optional automation daemon powered by fswatch. It watches directories and acts on new files:
- Downloads sorter — automatically moves files into subfolders by type (images, docs, audio, video, archives, code)
- Screen recording redirect — moves
.movfiles from Desktop to~/Screen Recordings/ - Screenshot archiver — archives old screenshots into monthly subfolders
Set it up with the interactive wizard: ./dotfiles/folder-actions.sh setup
Verification
After setup, run the verification script:
chmod +x verify.sh && ./verify.sh
It tests 10 categories and gives a pass/fail/warn summary: CLI tools, symlinks, git config, shell tools, SSH, folder structure, macOS defaults, and more.
Quick one-liner health check:
command -v brew && command -v git && command -v starship && \
test -L ~/.zshrc && test -L ~/.gitconfig && \
echo "✓ Core setup OK" || echo "✗ Something's missing"
Customization
The whole thing is designed to be forked. The main customization points:
| What | Where | What to change |
|---|---|---|
| Git identity | mac-setup.sh variables | GIT_NAME, GIT_EMAIL |
| Runtime versions | mac-setup.sh variables | NODE_VERSION, PYTHON_VERSION |
| GUI apps | mac-setup.sh EXTRA_CASKS | Add Homebrew cask names |
| CLI tools | dotfiles/Brewfile | Add/remove formulae |
| Shell aliases | dotfiles/.zshrc | Add project shortcuts |
| System prefs | dotfiles/.macos | Tweak any defaults write |
| Terminal | dotfiles/.wezterm.lua | Font, colors, keybindings |
| Prompt | dotfiles/starship.toml | Starship theme |
| File automation | dotfiles/folder-actions.sh | Configure watched folders |
The modular structure means you can use any piece independently — just the dotfiles, just the macOS defaults, just the Brewfile. Take what’s useful, ignore the rest.
Troubleshooting
Defaults not taking effect — Log out and back in. Some need killall Finder or killall Dock.
Homebrew permissions — sudo chown -R $(whoami) /opt/homebrew
Broken symlink — Delete the file in $HOME, re-run link.sh.
fzf keybindings missing — Run /opt/homebrew/opt/fzf/install.
Mise runtime not found — Make sure eval "$(mise activate zsh)" is in your .zshrc.
The full repo is at github.com/frankmanley/mac-setup. You can also browse the interactive migration guide right here on the site, or download the setup script directly. Clone it, make it yours.