devAlice
← Mac

Mac 開発環境の週次メンテナンス — brew・ツールチェーン・SDK を 1 コマンドで更新

Homebrew、Mac App Store、npm グローバル、rustup、cargo バイナリ、pipx、Flutter、CocoaPods、oh-my-zsh を一括更新する週次シェルスクリプト。

新しいマシンのセットアップに時間をかけた後、更新は忘れがちだ。2 か月後には brew install 1 つで 30 個の推移的依存関係が引き込まれ、ネイティブビルドが SDK の不一致で壊れ、セキュリティ証明書(ca-certificates)がひっそりと期限切れに近づいている。週次メンテナンスの本質は最新バージョンを追うことではなく、環境の劣化を防ぐことにあると考える — 問題が起きてから対処するよりも、定期的な予防の方がコストが低いからだ。

この記事はそれを防ぐ 9 ステップ統合スクリプトだ。週に一度実行するだけでよい。

TL;DR

  • brewmasnpm -grustupcargo install-updatepipxflutterpodomz を順番に実行
  • 各ステップはツールがインストールされていなければ自動スキップし、1 つのステップが失敗しても残りは続行
  • 最後に macOS システムアップデートの通知のみチェック(自動インストールなし — 再起動リスクがあるため)
  • 約 100 行のシンプルな bash

なぜやるのか

1. 証明書の期限切れ

ca-certificates は四半期ごとに新しいバンドルを出す。2 四半期スキップすると一部の HTTPS 呼び出しが失敗し始める。気にしないつもりでも、npm レジストリ、Homebrew 自体、GitHub API がすべて影響を受ける。

2. モバイルビルド SDK のドリフト

Flutter/iOS マシンで特にリスクが高い。Flutter stable は月に 3〜4 回リリースされる。CocoaPods の spec リポジトリが 1 週間同期されないと pod install で「spec not found」が出始める。週次の pod repo update だけでこの問題の 80% を防げる。

3. 言語ツールチェーンの蓄積

rustup、pipx、cargo でインストールしたグローバルツールはそれぞれ個別の更新が必要だ。忘れると、ある日 cargo installrust-version の不一致で最新版を取得できないことに気づく。

前提条件

  • macOS 12 以降+Homebrew(Mac 初期セットアップ
  • 9 ステップをフルに使う場合: maspipxcargo-updatefluttercocoapodsoh-my-zsh がプリインストール
  • 一部のツールがなくても問題ない — そのステップは SKIP される

初回準備

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 チャンネル
5Cargo バイナリcargo install-update -acargo-update 拡張が必要
6pipxpipx upgrade-allPython グローバルツール
7Flutter SDKflutter upgrade現在のチャンネルの最新版
8CocoaPods リポジトリpod repo updateiOS ビルド spec の同期
9oh-my-zsh~/.oh-my-zsh/tools/upgrade.shzsh フレームワーク

統合スクリプト

~/bin/update-system.sh として保存し、chmod +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"
 
# npm ステップで Node を見つけられるように fnm を初期化
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 分以上)。2 回目以降は 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 "週次 update-system チェック" 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 にデスクトップ通知が 1 つ届く。いつ実行するかは自分で決められる。

注意点

  • macOS システムアップデートの自動インストールなし: softwareupdate -ia --restart は強制再起動する。IDE、ターミナル、docker コンテナがすべて終了する。スクリプトは利用可能なアップデートを一覧表示するだけ。インストールは自分のタイミングで行うこと。
  • pod repo update は遅い(5 分以上かかる場合がある): trunk リポジトリは巨大だ。長期間スキップしていた場合はさらに長くなる。
  • oh-my-zsh には独自の自動更新オプションがある: .zshrczstyle ':omz:update' mode auto で設定できる。このスクリプトと重複しても問題ない。
  • Cargo はコンパイルが必要: cargo install-update -a はインストール済みバイナリを新しいバージョンでコンパイルする — 初回は数分かかることがある。

Windows ユーザー向け

PowerShell での同様のアイデア、MSYS2 + winget + 言語ツールチェーンをカバー → Windows 開発環境の週次メンテナンス

まとめ

  • ツールごとに個別更新 → 必ず忘れる
  • 単一の統合スクリプト → 忘れてもよい、ただ実行するだけ
  • 自動化はリマインダーまで。自動実行はしない
  • 週に一度実行するだけで、依存関係の嵐・SDK ドリフト・証明書の期限切れを防げる