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:

  1. Xcode Command Line Tools — prerequisite for everything
  2. Homebrew — package manager (detects Apple Silicon automatically)
  3. PARA folder structure~/Desktop/workspace/{01_projects, 02_areas, 03_resources, 04_archive} plus ~/Screenshots and ~/Screen Recordings
  4. Core apps via Homebrew — Raycast, WezTerm, JetBrains Mono, plus all CLI tools
  5. Dotfile symlinks — runs link.sh to connect everything
  6. Shell configuration — Starship prompt, Zoxide, fzf keybindings, Mise, project aliases
  7. macOS defaults — 90+ system preferences via defaults write
  8. Git configuration — identity, rebase by default, bat as pager
  9. Mise runtimes — Node and Python global versions
  10. SSH key — generates ed25519, adds to agent, prompts for GitHub
  11. GUI apps — reminder to install the rest manually
  12. 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:

GroupPackagesWhy
Shell & navstarship, zoxide, fzfCross-shell prompt, smart cd, fuzzy everything
Dev toolingmise, gh, git-lfsVersion manager, GitHub CLI, large file support
Modern CLIbat, eza, ripgrep, fd, jq, tree, tldr, htopBetter cat, ls, grep, find, and friends
GNU utilscoreutils, gnu-sedMatch Linux syntax (every Stack Overflow answer assumes GNU)
File & mediawget, imagemagick, rename, pigz, p7zip, moreutilsBatch processing, compression, extra utils
Automationmas, fswatchMac 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:

ShortcutAction
Ctrl+RFuzzy search shell history
Ctrl+TFuzzy find files
Alt+CFuzzy 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 .mov files 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:

WhatWhereWhat to change
Git identitymac-setup.sh variablesGIT_NAME, GIT_EMAIL
Runtime versionsmac-setup.sh variablesNODE_VERSION, PYTHON_VERSION
GUI appsmac-setup.sh EXTRA_CASKSAdd Homebrew cask names
CLI toolsdotfiles/BrewfileAdd/remove formulae
Shell aliasesdotfiles/.zshrcAdd project shortcuts
System prefsdotfiles/.macosTweak any defaults write
Terminaldotfiles/.wezterm.luaFont, colors, keybindings
Promptdotfiles/starship.tomlStarship theme
File automationdotfiles/folder-actions.shConfigure 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 permissionssudo 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.