每次重灌 macOS 最煩的不是灌系統,是灌系統之後把一堆軟體一個一個裝回來。以前都是憑記憶,裝到一半才想起漏了什麼。後來養成習慣,把整台機器在用的套件全部丟進一份 Brewfile,重灌完只要一行 brew bundle install 就復原九成。

這篇整理目前這台機器實際在跑的 Brewfile,順便記一下怎麼產生跟還原,還有幾個容易漏掉的地方。

產生自己的 Brewfile

不用手動維護清單,brew bundle dump 會直接把目前裝好的 formula、cask、tap、npm 套件全部掃出來寫成檔案:

brew bundle dump --file=~/Brewfile

重灌新機之後,先裝 Homebrew,順手把分析回報關掉,再把這份 Brewfile 丟回去:

brew analytics off
brew bundle install --file=~/Brewfile

CLI 工具、GUI App(cask)、甚至 npm install -g 的套件都會照著清單一次裝完,不用一個一個回想裝過什麼。

目前在用的 Brewfile

tap "7a6163/tap", trusted: { formulae: ["rubyfast"] }

# CLI 工具
brew "atuin"       # 改良版 shell history,支援搜尋、同步
brew "bat"         # cat 的替代品,有語法高亮
brew "bottom"      # 跨平台系統資源監控
brew "broot"       # 樹狀瀏覽 + 互動式檔案管理
brew "btop"        # htop 的替代品,圖形化系統資源監控
brew "dust"        # du 的替代品,磁碟用量一目瞭然
brew "eza"         # ls 的現代化替代品
brew "fd"          # find 的更快替代品
brew "fzf"         # 命令列模糊搜尋
brew "git"
brew "gping"       # ping 的替代品,即時繪製延遲折線圖
brew "helix"       # 模態編輯器
brew "hyperfine"   # 指令效能 benchmark 工具
brew "jq"          # JSON 處理
brew "libyaml"
brew "mas"         # Mac App Store CLI,讓 brew bundle 也能抓 App Store 裝的軟體
brew "mise"        # 語言版本管理(asdf 的 Rust 版)
brew "nomino"      # 批次檔案重新命名,用 regex 或 CSV 對照表一次改一整批
brew "procs"       # ps 的替代品,輸出更好讀
brew "ripgrep"      # grep 的更快替代品
brew "starship"    # 跨 shell 的 prompt
brew "tldr"        # 精簡版 man page
brew "zellij"      # terminal multiplexer
brew "zimfw"       # zsh plugin manager
brew "zoxide"      # cd 的更快替代品
brew "7a6163/tap/sigilo", trusted: true  # SSH agent,簽章要 Touch ID

# GUI App
cask "alfred"                       # 應用程式啟動器
cask "bitwarden"                    # 密碼管理
cask "font-jetbrains-mono-nerd-font"
cask "ghostty"                      # GPU 加速的終端機
cask "iina"                         # 影片播放器
cask "keka"                         # 解壓縮工具
cask "microsoft-teams"
cask "mole-app"                     # 系統清理優化
cask "navicat-premium"              # 資料庫管理工具
cask "obsidian"                     # 筆記
cask "orbstack"                     # Docker Desktop 替代品
cask "plex"                         # 家庭媒體伺服器播放端
cask "setapp"
cask "shottr"                       # 截圖標註工具
cask "squirrel-app"                 # Rime 輸入法引擎
cask "steam"
cask "sublime-text"                 # 文字編輯器
cask "surge"                        # 網路工具箱
cask "telegram"
cask "thebrowsercompany-dia"        # 瀏覽器
cask "zed"                          # 多人協作編輯器
cask "zen"                          # Gecko 核心瀏覽器

# Mac App Store 軟體(裝了 mas 之後自動掃出來)
mas "Bob", id: 1630034110
mas "LINE", id: 539883307
mas "OpenCat", id: 6445999201
mas "PasteNow", id: 1552536109
mas "Things", id: 904280696
mas "UpNote", id: 1398373917
mas "Wipr", id: 1662217862

# npm 全域套件
npm "aicommits"                     # AI 產生 commit message

Shell:zimfw、starship,還有幾個 alias

zsh 的 plugin 交給 zimfw 管,設定寫在 ~/.zimrc,只列真的會用到的模組:

# ~/.zimrc(節錄)
zmodule environment
zmodule git
zmodule input
zmodule termtitle
zmodule utility

zmodule zsh-users/zsh-completions --fpath src
zmodule completion

zmodule zsh-users/zsh-syntax-highlighting
zmodule zsh-users/zsh-history-substring-search
zmodule zsh-users/zsh-autosuggestions

~/.zshrc 裡接進來只要幾行,init.zsh 會依 .zimrc 的修改時間自動判斷要不要重建:

ZIM_HOME=${ZDOTDIR:-${HOME}}/.zim
if [[ ! ${ZIM_HOME}/init.zsh -nt ${ZIM_CONFIG_FILE:-${ZDOTDIR:-${HOME}}/.zimrc} ]]; then
  source /opt/homebrew/opt/zimfw/share/zimfw.zsh init
fi
source ${ZIM_HOME}/init.zsh

prompt 交給 starship,一行 eval 就解決:

eval "$(starship init zsh)"

沒另外寫 ~/.config/starship.toml,吃預設 preset 就夠用,git 分支狀態、指令執行時間都內建。

ezabat 裝上之後,順手把原生的 lscat 蓋掉,反正這幾行只在互動式 shell 生效,script 用 /bin/sh 跑不會被影響:

alias ls='eza --icons'
alias ll='eza -l --icons'
alias la='eza -la --icons'
alias lt='eza --tree --icons'
alias cat='bat'

broot 比較特別,裝完還要接一行 shell hook 才能用 br 直接切換目錄,跑過 broot --install 它會自動幫你加進 ~/.zshrc

source /Users/zac/.config/broot/launcher/bash/br

工具取捨

CLI 工具能找到 Rust 重寫版的話幾乎都會換:batezaripgrepzoxidefddustprocsgpingnomino,速度跟體驗都比原生好一截,指令幾乎不用改習慣。hyperfine 拿來比較指令效能,改完設定或寫腳本想知道有沒有變快,跑一次就有統計數字,不用自己土法煉鋼算時間。系統監控用 btop,圖形化的 CPU、記憶體、網路流量一目瞭然,比 htop 好看也好讀。

終端機編輯器換成 Helix,內建 LSP、多游標、tree-sitter,開箱即用幾乎不用額外設定。mise 取代掉以前用的 asdf,管 Ruby / Node 版本快很多。容器這塊用 orbstack 直接換掉 Docker Desktop,開機資源佔用差很多。中文輸入法靠 squirrel-app(Rime),這個不裝的話中文輸入會很痛苦。

私有的小工具像 sigilo 直接放進自己的 tap,一起被 brew bundle 管起來,不用另外裝。裝了 mas 之後,只要登入過 App Store,brew bundle dump 會自動把裝過的 App Store 軟體列成 mas "App 名稱", id: 12345,重灌時一起裝回來,不用再手動一個個回想以前裝過什麼。brew analytics off 這行也不能漏,裝完 Homebrew 就先跑一次,不然每次 brew install 都會送統計資料出去。

git 的部分也順手處理掉:~/.gitconfig 的 email 用 GitHub 配發的 noreply 信箱,commit 紀錄不會外洩真實 email;另外設一份全域 ignore,.DS_Store.idea/.vscode/ 這類跟專案無關的檔案不用每個 repo 各自寫一次:

git config --global core.excludesfile ~/.gitignore_global

~/.gitignore_global 內容:

# macOS
.DS_Store
.AppleDouble
.LSOverride

# Editor
.idea/
.vscode/
*.swp

# OS generated files
Thumbs.db

重灌後容易漏掉的地方

brew bundle install 能復原軟體本身,但有幾件事它幫不上忙。

mas install <id> 只能重裝同一個 Apple ID 已經買過的 App,mas 本身不處理登入,重灌後得先在 App Store.app 裡登入帳號,它才抓得到東西。setappnavicat-premium 這類裝好還要手動登入訂閱或輸入序號,Brewfile 只負責把 App 本體裝上。自己的 tap(sigilorubyfast)放在 GitHub,重灌新機時要先確認網路跟 DNS 正常,brew tap 才抓得到 formula。

最容易漏掉的其實是 dotfiles:.zshrc.zimrc.gitconfig.gitignore_global、Ghostty 的 config.ghostty,這些設定檔完全不在 brew bundle 的管理範圍內,得另外用 git 版控或 chezmoi / stow 這類工具備份,不然軟體裝完,這些細部設定還是一片空白。Ghostty 的 font-family 要填 JetBrainsMono Nerd Font,跟 ~/Library/Fonts 裡實際的字體家族名稱對上,字型才吃得到。

收尾

重灌機器最花時間的往往不是裝系統,是把環境調回原本習慣的樣子。有了 Brewfile,這件事變成一個指令的事,dotfiles 另外顧好,剩下的細節就不多了。清單也會隨著實際使用調整,之後重灌前先跑一次 brew bundle dump 更新,永遠是當下這台機器最新的狀態。