Mac 개발 환경 정기 유지관리 — brew·언어 툴체인·SDK를 한 명령으로
주 1회 한 번의 실행으로 Homebrew, Mac App Store, npm globals, rustup, cargo binaries, pipx, Flutter, CocoaPods, oh-my-zsh를 모두 갱신하는 셸 스크립트 1편.
신규 PC를 세팅할 때 환경 구성에는 시간을 쓰지만, 그 다음부터는 다들 갱신을 잊는다. 두 달 뒤에 brew install 한 번 하면 의존성 30개가 줄줄이 따라 올라오고, 네이티브 빌드가 SDK 버전 불일치로 깨지고, 보안 인증서(ca-certificates)는 묵묵히 만료를 향해 간다.
이 글은 그걸 막는 9단계 통합 스크립트다. 주 1회 한 번 실행하면 끝.
TL;DR
brew+mas+npm -g+rustup+cargo install-update+pipx+flutter+pod+omz9개를 순서대로 돌린다- 각 단계는 도구가 없으면 자동 SKIP, 한 단계 실패해도 나머지는 진행
- 끝에 macOS 시스템 업데이트 알림만 (자동 설치는 재부팅 위험이라 제외)
- 약 100줄짜리 단일 bash 스크립트
왜 필요한가
1. 보안 인증서 만료
ca-certificates는 분기마다 새 번들이 나온다. 두 분기 묵히면 일부 HTTPS 호출이 인증 실패한다. 본인은 쓸 일 없다고 생각해도 npm registry, brew 자체, GitHub API가 다 영향권.
2. 모바일 빌드 SDK drift
Flutter·iOS 작업하는 머신은 특히 위험하다. Flutter는 한 달에 3-4번 stable이 나오고, CocoaPods의 spec repo는 일주일 안 갱신하면 pod install이 "spec not found"를 토하기 시작한다. 주 1회 pod repo update만 돌려도 80%는 막힌다.
3. 언어 툴체인 누적
rustup·pipx·cargo로 깐 글로벌 도구들은 각자 따로 갱신해야 한다. 잊으면 어느 날 cargo install 새 버전이 안 깔려서야 알게 된다 (rust-version 미스매치).
사전 조건
- macOS 12+ + Homebrew (Mac 초기 셋업)
- 9단계 전부 채우려면:
mas,pipx,cargo-update,flutter,cocoapods,oh-my-zsh미리 설치 - 도구 일부가 없어도 OK — 해당 단계만 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 ensurepathcargo install cargo-update는 컴파일 의존이라 처음엔 2-3분 걸린다. Flutter는 약 1GB 다운로드.
9단계 갱신 대상
| # | 단계 | 명령 | 비고 |
|---|---|---|---|
| 1 | Homebrew | brew update && brew upgrade && brew cleanup | formulae + casks |
| 2 | Mac App Store | mas upgrade | App Store 앱 (Xcode 등) |
| 3 | npm globals | npm update -g | 글로벌 npm 패키지 |
| 4 | Rust toolchain | rustup update | stable channel |
| 5 | Cargo binaries | cargo install-update -a | cargo-update 확장 필요 |
| 6 | pipx | pipx upgrade-all | Python global tools |
| 7 | Flutter SDK | flutter upgrade | 현재 채널의 최신 |
| 8 | CocoaPods repo | pod repo update | iOS 빌드 spec 동기화 |
| 9 | oh-my-zsh | ~/.oh-my-zsh/tools/upgrade.sh | zsh 프레임워크 |
통합 스크립트
저장: ~/bin/update-system.sh 같은 위치. chmod +x 후 사용.
#!/usr/bin/env bash
#
# 시스템 도구 일괄 업데이트 — macOS
# 사용법: ./update-system.sh
#
# 갱신 범위:
# 1. Homebrew (formulae + casks)
# 2. Mac App Store (mas, 있을 때만)
# 3. npm globals
# 4. Rust toolchain (rustup)
# 5. Cargo binaries (cargo install-update -a, cargo-update 필요)
# 6. pipx (Python global tools)
# 7. Flutter SDK (flutter upgrade)
# 8. CocoaPods repo (pod repo update — iOS 빌드 의존)
# 9. oh-my-zsh (omz update)
#
# 종료 시 softwareupdate -l 결과 알림 (자동 설치 X — 재부팅 위험)
#
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 단계에서 노드 경로 잡기 위함)
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 (formulae + casks)"
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 globals
step "npm globals"
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 toolchain
step "Rust toolchain (rustup)"
if command -v rustup >/dev/null 2>&1; then
rustup update && ok "rustup update" || fail "rustup update"
else
skip "rustup 미설치"
fi
# 5. Cargo binaries
step "Cargo binaries (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 global tools)"
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 repo
step "CocoaPods repo (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 upgrade 13개면 5분 정도), 두 번째부터는 1-2분.
━━━ 시스템 업데이트 (macOS) ━━━
[1/9] Homebrew (formulae + casks)
==> 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 globals
OK npm update -g
[4/9] Rust toolchain (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시 데스크톱 알림 한 번. 실행은 사람이 결정.
함정
- macOS 시스템 업데이트는 자동 설치 X:
softwareupdate -ia --restart는 강제 재부팅이 들어간다. 작업 중인 IDE·터미널·도커 컨테이너 모두 날아간다. 스크립트는softwareupdate -l로 사용 가능 목록만 알림, 설치는 사람이 결정. pod repo update는 느리다 (5분+ 가능): trunk repo가 거대해서. 갱신 자주 안 해뒀으면 더 길어진다.oh-my-zsh자체 자동 갱신 옵션도 있음:.zshrc에zstyle ':omz:update' mode auto. 스크립트와 중복이지만 무해.- Cargo 컴파일 의존:
cargo install-update -a로 신버전 받은 바이너리는 그 자리에서 컴파일된다. 첫 실행 시 수 분 걸릴 수 있음.
Windows 사용자도?
같은 발상으로 MSYS2·winget·언어 툴체인을 한 번에 갱신하는 PowerShell 스크립트 → Windows 개발 환경 정기 유지관리.
정리
- 도구 1개씩 손으로 갱신 → 잊는다
- 통합 스크립트 1개 → 잊어도 한 번 돌리면 끝
- 자동화는 알림까지만, 실행은 사람이
- 매주 1회면 brew 13개·SDK drift·인증서 만료 모두 막을 수 있다