devAlice
← Mac

Mac 开发环境每周维护 — 一条命令更新 brew · 工具链 · SDK

一个每周运行的 Shell 脚本,一次性更新 Homebrew、Mac App Store、npm 全局包、rustup、cargo 二进制、pipx、Flutter、CocoaPods 和 oh-my-zsh。

你花时间配置好了新机器,然后忘记保持更新。两个月后,一次 brew install 就会拖来 30 个传递依赖,SDK 版本不匹配导致原生构建失败,安全证书(ca-certificates)悄悄漂向过期边缘。

本文是防止这一切的 9 步集成脚本。每周运行一次——搞定。

TL;DR

  • 按顺序运行 brew + mas + npm -g + rustup + cargo install-update + pipx + flutter + pod + omz
  • 每个步骤如果工具未安装则自动跳过,某步失败不影响其余步骤
  • 仅通知的方式检查 macOS 系统更新(不自动安装 — 重启风险太高)
  • 约 100 行纯 bash

为什么要这样做

1. 证书过期

ca-certificates 每季度发布新的证书包。跳过两个季度,部分 HTTPS 调用开始失败。你可能觉得无所谓,但 npm 注册表、Homebrew 本身以及 GitHub API 都会受影响。

2. 移动构建 SDK 漂移

Flutter/iOS 机器尤其危险。Flutter stable 每月发布 3-4 次;CocoaPods 的 spec 仓库如果一周不同步,在 pod install 期间就会报"spec not found"。单是每周 pod repo update 就能阻止 80% 的这类问题。

3. 语言工具链积累

通过 rustup、pipx 或 cargo 安装的全局工具,每个都需要单独更新。一旦忘记,某天你会发现 cargo install 无法获取最新版本,因为 rust-version 不匹配。

前提条件

  • macOS 12+ + Homebrew(Mac 初始配置
  • 完整 9 步需要:预先安装 maspipxcargo-updatefluttercocoapodsoh-my-zsh
  • 缺少某些工具没关系 — 对应步骤会优雅地跳过

一次性准备

brew install mas pipx cocoapods
brew install --cask flutter
cargo install cargo-update
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
pipx ensurepath

cargo install cargo-update 需要编译,首次运行耗时 2-3 分钟。Flutter 约 1GB 下载量。

9 个更新步骤

#步骤命令说明
1Homebrewbrew update && brew upgrade && brew cleanup公式 + cask
2Mac App Storemas upgradeApp Store 应用(Xcode 等)
3npm 全局包npm update -g全局 npm 包
4Rust 工具链rustup updatestable channel
5Cargo 二进制cargo install-update -a需要 cargo-update 扩展
6pipxpipx upgrade-allPython 全局工具
7Flutter SDKflutter upgrade当前 channel 的最新版
8CocoaPods 仓库pod repo updateiOS 构建 spec 同步
9oh-my-zsh~/.oh-my-zsh/tools/upgrade.shzsh 框架

集成脚本

保存为 ~/bin/update-system.shchmod +x 后使用。

#!/usr/bin/env bash
#
# 系统工具批量更新 — macOS
# 用法:./update-system.sh
#
# 范围:
#   1. Homebrew(公式 + cask)
#   2. Mac App Store(mas,如已安装)
#   3. npm 全局包
#   4. Rust 工具链(rustup)
#   5. Cargo 二进制(cargo install-update -a,需要 cargo-update)
#   6. pipx(Python 全局工具)
#   7. Flutter SDK(flutter upgrade)
#   8. CocoaPods 仓库(pod repo update — iOS 构建依赖)
#   9. oh-my-zsh(omz update)
#
# 以 `softwareupdate -l` 通知结束(不自动安装 — 有重启风险)
#
 
set -uo pipefail
 
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
GRAY='\033[0;90m'
NC='\033[0m'
 
TOTAL=9
STEP=0
 
step() {
    STEP=$((STEP + 1))
    printf "\n${CYAN}[%d/%d] %s${NC}\n" "$STEP" "$TOTAL" "$1"
}
 
ok()   { printf "  ${GREEN}OK${NC} %s\n" "$1"; }
skip() { printf "  ${GRAY}SKIP${NC} %s\n" "$1"; }
warn() { printf "  ${YELLOW}WARN${NC} %s\n" "$1"; }
fail() { printf "  ${RED}FAIL${NC} %s\n" "$1"; }
 
printf "${CYAN}━━━ 系统更新(macOS)━━━${NC}\n"
 
# 初始化 fnm,使 npm 步骤能找到 Node
if command -v fnm >/dev/null 2>&1; then
    eval "$(fnm env --use-on-cd --shell bash 2>/dev/null)" 2>/dev/null || true
fi
 
# 1. Homebrew
step "Homebrew(公式 + cask)"
if command -v brew >/dev/null 2>&1; then
    if brew update && brew upgrade && brew cleanup; then
        ok "brew update / upgrade / cleanup"
    else
        fail "brew 步骤部分失败 — 请查看上方日志"
    fi
else
    skip "brew 未安装"
fi
 
# 2. Mac App Store
step "Mac App Store(mas)"
if command -v mas >/dev/null 2>&1; then
    mas upgrade && ok "mas upgrade" || fail "mas upgrade"
else
    skip "mas 未安装(brew install mas)"
fi
 
# 3. npm 全局包
step "npm 全局包"
if command -v npm >/dev/null 2>&1; then
    npm update -g && ok "npm update -g" || fail "npm update -g"
else
    skip "npm 未安装"
fi
 
# 4. Rust 工具链
step "Rust 工具链(rustup)"
if command -v rustup >/dev/null 2>&1; then
    rustup update && ok "rustup update" || fail "rustup update"
else
    skip "rustup 未安装"
fi
 
# 5. Cargo 二进制
step "Cargo 二进制(cargo install-update -a)"
if command -v cargo >/dev/null 2>&1; then
    if cargo install-update --version >/dev/null 2>&1; then
        cargo install-update -a && ok "cargo install-update -a" || fail "cargo install-update -a"
    else
        warn "cargo-update 未安装(安装方法:cargo install cargo-update)"
    fi
else
    skip "cargo 未安装"
fi
 
# 6. pipx
step "pipx(Python 全局工具)"
if command -v pipx >/dev/null 2>&1; then
    pipx upgrade-all && ok "pipx upgrade-all" || fail "pipx upgrade-all"
else
    skip "pipx 未安装(brew install pipx)"
fi
 
# 7. Flutter SDK
step "Flutter SDK(flutter upgrade)"
if command -v flutter >/dev/null 2>&1; then
    flutter upgrade && ok "flutter upgrade" || fail "flutter upgrade"
else
    skip "flutter 未安装"
fi
 
# 8. CocoaPods 仓库
step "CocoaPods 仓库(pod repo update)"
if command -v pod >/dev/null 2>&1; then
    pod repo update && ok "pod repo update" || fail "pod repo update"
else
    skip "pod 未安装(brew install cocoapods)"
fi
 
# 9. oh-my-zsh
step "oh-my-zsh(omz update)"
if [ -d "$HOME/.oh-my-zsh" ]; then
    OMZ_UPDATER="$HOME/.oh-my-zsh/tools/upgrade.sh"
    if [ -x "$OMZ_UPDATER" ]; then
        zsh "$OMZ_UPDATER" && ok "oh-my-zsh 已更新" || fail "oh-my-zsh"
    else
        skip "oh-my-zsh upgrade.sh 缺失"
    fi
else
    skip "oh-my-zsh 未安装"
fi
 
# macOS 系统更新 — 仅通知
printf "\n${CYAN}━━━ macOS 系统更新检查(仅通知)━━━${NC}\n"
if command -v softwareupdate >/dev/null 2>&1; then
    SU_OUT=$(softwareupdate -l 2>&1)
    if echo "$SU_OUT" | grep -qi "no new software\|No updates"; then
        printf "${GRAY}  已是最新 — 无系统更新${NC}\n"
    else
        printf "${YELLOW}  ⚠ 有系统更新可用:${NC}\n"
        echo "$SU_OUT" | sed 's/^/    /'
        printf "${GRAY}  安装方法:sudo softwareupdate -ia --restart(会重启)${NC}\n"
    fi
fi
 
printf "\n${CYAN}━━━ 完成 ━━━${NC}\n"
printf "${GRAY}检查:brew doctor / brew outdated / npm outdated -g / flutter doctor${NC}\n"

示例输出

首次运行时间较长(如果 brew 有 13 个包待升级,约需 5 分钟)。后续运行约 1-2 分钟。

━━━ 系统更新(macOS)━━━

[1/9] Homebrew(公式 + cask)
==> Upgrading 13 outdated packages:
ca-certificates 2026-03-19 -> 2026-05-14
ruby 4.0.3 -> 4.0.4
sqlite 3.53.0 -> 3.53.1
python@3.14 3.14.4_1 -> 3.14.5
...
==> This operation has freed approximately 18.8MB of disk space.
  OK brew update / upgrade / cleanup

[2/9] Mac App Store(mas)
  OK mas upgrade

[3/9] npm 全局包
  OK npm update -g

[4/9] Rust 工具链(rustup)
  stable-aarch64-apple-darwin unchanged - rustc 1.95.0
  OK rustup update

...

[9/9] oh-my-zsh(omz update)
Hooray! Oh My Zsh has been updated!
  OK oh-my-zsh 已更新

━━━ macOS 系统更新检查(仅通知)━━━
  已是最新 — 无系统更新

━━━ 完成 ━━━

自动化程度

不建议完全自动化。brew 的主版本升级(如 vercel-cli 53→54)偶尔会带来破坏性变更,你希望在它落地的那一刻就注意到。

合理的折中方案是提醒而非自动运行

~/Library/LaunchAgents/local.update-reminder.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>local.update-reminder</string>
  <key>ProgramArguments</key>
  <array>
    <string>osascript</string>
    <string>-e</string>
    <string>display notification "Weekly update-system check" with title "Dev Maintenance"</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Weekday</key><integer>5</integer>
    <key>Hour</key><integer>10</integer>
    <key>Minute</key><integer>0</integer>
  </dict>
</dict>
</plist>

激活:

launchctl load -w ~/Library/LaunchAgents/local.update-reminder.plist

每周五 10:00 发送一条桌面通知。由你决定何时运行。

注意事项

  • macOS 系统更新不自动安装softwareupdate -ia --restart 会强制重启。IDE、终端、docker 容器——全部关闭。脚本只列出可用更新,安装由你决定。
  • pod repo update 很慢(可能超过 5 分钟):trunk 仓库体积巨大。跳过多周后会更慢。
  • oh-my-zsh 有自己的自动更新选项.zshrc 中的 zstyle ':omz:update' mode auto。与本脚本的重叠无害。
  • Cargo 需要编译cargo install-update -a 会编译已安装二进制的新版本 — 首次运行可能需要数分钟。

Windows 用户?

PowerShell 中的同类方案,涵盖 MSYS2 + winget + 语言工具链 → Windows 开发环境每周维护

总结

  • 逐一更新 → 容易遗忘
  • 单一集成脚本 → 随便忘,跑一下就行
  • 自动化程度止步于提醒,绝不自动运行
  • 每周一次,防止 brew 依赖风暴、SDK 漂移和证书过期