Back to Config Library
Dev Asset
v1.0.0

macOS Bootstrap Script

Idempotent macOS setup — Homebrew packages, Zsh, Neovim, Node.js, Claude Code, and dev tools.

Files Included

226 linesEnter email to access
#!/usr/bin/env bash
# =============================================================================
# macOS Bootstrap Script
#
# Prerequisites (run manually before this script):
#   xcode-select --install
#   /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
#   eval "$(/opt/homebrew/bin/brew shellenv)"
#   brew install gh
#   gh auth login
#   git clone --bare https://github.com/starmorph/dotfiles.git $HOME/.dotfiles
#   git --git-dir=$HOME/.dotfiles --work-tree=$HOME checkout main -f
#   ~/.bootstrap-mac.sh
#
# Re-run (already set up):
#   ~/.bootstrap-mac.sh
# =============================================================================
set -euo pipefail

LOG_PREFIX="\033[1;34m==>\033[0m"

log() { echo -e "${LOG_PREFIX} $1"; }
warn() { echo -e "\033[1;33m==> WARNING:\033[0m $1"; }
err() { echo -e "\033[1;31m==> ERROR:\033[0m $1" >&2; }

# ---------------------------------------------------------------------------
# 0. Pre-flight checks
# ---------------------------------------------------------------------------
if [[ "$(uname)" != "Darwin" ]]; then
  err "This script is designed for macOS. Detected: $(uname)"
  exit 1
fi

if ! command -v brew &>/dev/null; then
  err "Homebrew not found. Install it first:"
  err '  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
  exit 1
fi

log "Starting macOS bootstrap..."
log "Detected: macOS $(sw_vers -productVersion) on $(uname -m)"

# ---------------------------------------------------------------------------
# 1. Homebrew packages
# ---------------------------------------------------------------------------
log "Installing Homebrew packages..."

BREW_PACKAGES=(
  # CLI tools
  bat
  eza
  gh
  git-delta
  glow
  jq
  neovim
  ripgrep
  tmux
  zoxide

  # File management
  fd
  fzf
  tree
  midnight-commander

  # Media
  ffmpeg
  imagemagick

  # Networking
  nmap

  # Dev tools
  nvm
  pnpm
  pyenv-virtualenv
  stripe/stripe-cli/stripe
  supabase/tap/supabase
  tree-sitter

  # macOS specific
  terminal-notifier
  gnu-sed
  gnu-tar
  gnu-which
  grep
)

for pkg in "${BREW_PACKAGES[@]}"; do
  if brew list "$pkg" &>/dev/null; then
    log "  $pkg — already installed"
  else
    log "  Installing $pkg..."
    brew install "$pkg" || warn "Failed to install $pkg — skipping"
  fi
done

# Casks
BREW_CASKS=(
  visual-studio-code
)

for cask in "${BREW_CASKS[@]}"; do
  if brew list --cask "$cask" &>/dev/null; then
    log "  $cask — already installed"
  else
    log "  Installing $cask..."
    brew install --cask "$cask" || warn "Failed to install $cask — skipping"
  fi
done

# ---------------------------------------------------------------------------
# 2. Zsh + Oh My Zsh + Powerlevel10k
# ---------------------------------------------------------------------------
log "Setting up zsh..."

# Oh My Zsh
if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
  log "Installing Oh My Zsh..."
  sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
else
  log "Oh My Zsh — already installed"
fi

# Powerlevel10k
if [[ ! -d "$HOME/powerlevel10k" ]]; then
  log "Installing Powerlevel10k..."
  git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
else
  log "Powerlevel10k — already installed"
fi

# ---------------------------------------------------------------------------
# 3. Node.js via nvm + pnpm
# ---------------------------------------------------------------------------
log "Setting up Node.js..."

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"

if command -v nvm &>/dev/null; then
  if ! nvm ls --no-colors 2>/dev/null | grep -q "lts"; then
    log "Installing Node.js LTS via nvm..."
    nvm install --lts
    nvm alias default lts/*
  else
    log "Node.js LTS — already installed"
  fi
else
  warn "nvm not loaded — run 'source ~/.zshrc' then 'nvm install --lts'"
fi

# pnpm is already installed via Homebrew above

# ---------------------------------------------------------------------------
# 4. Dotfiles bare repo config
# ---------------------------------------------------------------------------
alias dotfiles='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'

if [[ -d "$HOME/.dotfiles" ]]; then
  log "Dotfiles bare repo found at ~/.dotfiles"
  dotfiles config --local status.showUntrackedFiles no
else
  warn "Bare repo not found at ~/.dotfiles — skipping dotfiles config"
  warn "Clone it first: git clone --bare https://github.com/starmorph/dotfiles.git \$HOME/.dotfiles"
fi

# ---------------------------------------------------------------------------
# 5. Secrets file
# ---------------------------------------------------------------------------
if [[ ! -f "$HOME/.zsh_secrets" ]]; then
  log "Creating empty ~/.zsh_secrets — add your API keys here"
  touch "$HOME/.zsh_secrets"
  chmod 600 "$HOME/.zsh_secrets"
else
  log "~/.zsh_secrets — already exists"
fi

# ---------------------------------------------------------------------------
# 6. SSH key
# ---------------------------------------------------------------------------
if [[ ! -f "$HOME/.ssh/id_ed25519" ]]; then
  log "Generating SSH key..."
  mkdir -p "$HOME/.ssh"
  chmod 700 "$HOME/.ssh"
  ssh-keygen -t ed25519 -C "$USER@$(hostname -s)" -f "$HOME/.ssh/id_ed25519" -N ""
  echo ""
  log "Add this public key to GitHub: gh ssh-key add ~/.ssh/id_ed25519.pub"
  cat "$HOME/.ssh/id_ed25519.pub"
  echo ""
else
  log "SSH key — already exists"
fi

# ---------------------------------------------------------------------------
# 7. Claude Code
# ---------------------------------------------------------------------------
if ! command -v claude &>/dev/null; then
  log "Installing Claude Code..."
  npm install -g @anthropic-ai/claude-code
else
  log "Claude Code — already installed"
fi

# ---------------------------------------------------------------------------
# Done
# ---------------------------------------------------------------------------
echo ""
log "\033[1;32mBootstrap complete!\033[0m"
echo ""
echo "  What was set up:"
echo "    - zsh + Oh My Zsh + Powerlevel10k (cypher theme)"
echo "    - neovim + LazyVim config"
echo "    - tmux with custom config (prefix: Ctrl-a)"
echo "    - Node.js LTS (via nvm) + pnpm"
echo "    - Claude Code"
echo "    - bat, eza, ripgrep, fd, fzf, jq, glow, delta, zoxide"
echo "    - 25 shell scripts in ~/bin/"
echo ""
echo "  Next steps:"
echo "    1. Restart your terminal (or run: exec zsh)"
echo "    2. Add API keys to ~/.zsh_secrets"
echo "    3. Run: gh auth login (if not already authenticated)"
echo "    4. Install Nerd Font for terminal icons (e.g., MesloLGS NF)"
echo ""

How to use

Run chmod +x bootstrap-mac.sh && ./bootstrap-mac.sh in Terminal. Requires macOS with admin access. Installs Homebrew if not present. Safe to re-run.

About

A bootstrap script for macOS that installs and configures everything a developer needs. Homebrew CLI tools (bat, eza, ripgrep, fd, fzf, jq, glow, delta, zoxide), Oh My Zsh + Powerlevel10k, Node.js via nvm + pnpm, Neovim, tmux, SSH key generation, and Claude Code. Idempotent — skips anything already installed.

macOSHomebrewAutomationFree
Free

Get notified about new configs. No spam.

What’s Included

  • Homebrew CLI tools (30+ packages)
  • Brewfile reference with quick install script
  • Zsh + Oh My Zsh + Powerlevel10k
  • Node.js via nvm + pnpm
  • SSH key generation
  • Claude Code installation

2 files included

    macOS Bootstrap Script | Starmorph Config | Starmorph