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)


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:
| Prompt | Answer |
|---|---|
| fuzzy auto-completion | yes |
| key bindings | yes |
| update shell config files | no |
(“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).








