Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Admin · new Mac (Homebrew)

You have admin rights on this Mac, so Homebrew does everything — CLI tools, GUI apps (casks), and fonts — from one Brewfile. Set up your terminal + font first, then install the rest from inside iTerm.

1. Terminal + font first

brew install --cask iterm2 font-fira-code-nerd-font

iTerm2 configuration

(Once iTerm2 is installed — by brew cask on the admin path, or into ~/Applications by manual-tools.sh on the non-admin paths — configure it as below.)

Global options (non-admin friendly)

show tabs

hide from dock

New profile (visor, font, colors, scrollback, keybindings)

create new profile

name the profile when created

name visor profile

set visor profile to default

set color presets

change font

change window style

unlimited scrollback

z / alt_c binding + hotkey button

hotkey popup options

2. Install everything — all brew

base=https://raw.githubusercontent.com/teeckoo/mac-setup/main
curl -L -o Brewfile       "$base/Brewfile"
curl -L -o Brewfile.admin "$base/Brewfile.admin"
brew bundle --verbose                          # the Brewfile ("the rest"): casks + formulae
brew bundle --verbose --file=Brewfile.admin    # extras: coursier, direnv, wget, awscli, bat,
                                               #   gnupg, eza, mdbook, colima, docker, MacTeX
rm Brewfile Brewfile.admin

100% brew, no manual steps. The Brewfile is “the rest”; Brewfile.admin holds the packages kept out of it for the non-admin path (they’d source-build under a non-admin ~/brew, but bottle fine here). Container dev: colima start --vm-type vz, then docker …. LaTeX comes from the mactex-no-gui cask (TinyTeX has no Homebrew formula). The no-admin helper scripts (perfect-bottles.sh, manual-tools.sh) are not needed on this path.

Shell config (~/.zprofile + ~/.zshrc)

Homebrew is on PATH already (its shellenv), so you only need the interactive block.

~/.zshrc:

# ---- System ----
ulimit -n 4096            # prevent "too many open files"
setopt AUTO_CD            # cd by typing a folder name
chpwd() { ls -C; }        # listing after each cd
export DIRENV_LOG_FORMAT=""

# ---- Completions (before plugins/SDKMAN, which call compdef) ----
autoload -Uz compinit
compinit -i

# ---- Zsh plugins (antidote) ----
source "$(brew --prefix)/opt/antidote/share/antidote/antidote.zsh"
antidote load < ~/.zsh_plugins.txt

# ---- Prompt (starship) ----
eval "$(starship init zsh)"

# ---- fzf ----
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh

# ---- direnv ----
command -v direnv >/dev/null 2>&1 && eval "$(direnv hook zsh)"

# ---- SDKMAN ----
[ -d ~/.sdkman ] && export SDKMAN_DIR="$HOME/.sdkman"
[ -d ~/.sdkman ] || export SDKMAN_DIR="$(brew --prefix sdkman-cli)/libexec"
[[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]] && source "$SDKMAN_DIR/bin/sdkman-init.sh"

Open a new shell to auto-install the plugins.

starship prompt

Create ~/.config/starship.toml:

format = """
${env_var.OPTIONAL_PROMPT_HEADER}\
$directory $git_branch$git_status$all${env_var.OPTIONAL_PROMPT_FOOTER}
$character"""

[line_break]
disabled = false

[directory]
truncation_length = 0
truncate_to_repo = false

[env_var.OPTIONAL_PROMPT_HEADER]
variable = "OPTIONAL_PROMPT_HEADER"
# The \n is placed inside the string format so it only triggers if text exists
format = "[$env_value]($style)\n"
style = "bold blue"

[env_var.OPTIONAL_PROMPT_FOOTER]
variable = "OPTIONAL_PROMPT_FOOTER"
format = "[$env_value]($style)\n" # Newline at the START
style = "bold yellow"

(starship reads this automatically; the ~/.zshrc block below runs starship init zsh.)

Zsh plugins (antidote)

Create ~/.zsh_plugins.txt:

zsh-users/zsh-autosuggestions
zsh-users/zsh-syntax-highlighting

antidote loads this on every interactive shell (see the ~/.zshrc block). Add more plugins here as you like.

fzf (fuzzy finder)

When the fzf installer prompts, answer:

PromptAnswer
fuzzy auto-completionyes
key bindingsyes
update shell config filesno

(“no” on the last one because the ~/.zshrc block already sources ~/.fzf.zsh.)

git log pager

Keep git log on one screen (git ships with the Xcode Command Line Tools):

git config --global --replace-all core.pager "less -F -X"

Troubleshooting

Gatekeeper “cannot be opened”. macOS may quarantine a downloaded binary on first run. Clear it:

xattr -dr com.apple.quarantine ~/.local/bin

compinit prompts every shell / command not found: compdef. A leftover Homebrew site-functions dir owned by the old admin makes a bare compinit refuse to run. The ~/.zshrc block uses compinit -i (ignore insecure dirs) before the plugin/SDKMAN lines — keep that order.

npm i -g permission denied. Global installs are redirected to ~/.local (npm config set prefix ~/.local), so packages land in ~/.local/bin (already on PATH). After a major Node LTS bump, packages with native bindings may need a npm i -g reinstall to match the new ABI.

Re-running is safe (idempotent)

Both perfect-bottles.sh and manual-tools.sh re-run safely — to finish a partial install or refresh to latest. CLI binaries and vendor installers overwrite in place (no duplicates); Node wipes/reinstalls latest LTS (global packages survive); GUI apps are replaced in place (a failed download leaves the old .app); antidote/fzf are left alone if present; an unreachable source keeps its existing copy and is logged. There’s no local cache, so a refresh re-downloads (~200 MB Node, ~2 GB GUI apps).