换硬盘、做迁移这种事情,真是每年都要来一次。尤其当你的 Mac 空间吃紧,扩完容把数据导回来,满心欢喜地打开终端准备继续写代码,结果发现环境一团糟:git还能提交但 SSH 连不上私有仓库,node能跑但npm装包报权限错误,版本号看起来都对,行为却完全不对。这种状态的根源,不是系统出了问题,而是扩容前旧环境里散落各地的git、node残留没有被真正清理干净,新系统只是继承了其中的一部分。
这篇文章记录的就是我最近处理“Mac 硬盘扩容后重装 git 和 node”的完整过程。核心思路是先盘清楚旧环境到底有哪些安装来源、哪些配置值得备份,再按“卸载、递归扫尾、重装、验证”的顺序让环境回到一个可控的干净状态。整个方案不依赖特定硬盘或工具版本,适合任何刚搬完家、准备重建开发环境的 Mac 用户参考。
1. 换盘之后,git 和 node 为什么总是“阴魂不散”
平时我们很少会去追问“我的 git 是哪来的”“我的 node 是从哪里被识别到的”。但在硬盘扩容这种全局性的环境迁移之后,这些问题会一次性暴露出来。因为 git 和 node 在 macOS 上的安装途径实在太多了,每一条途径都会往不同的目录写文件,而且它们之间还会相互干扰。
1.1 你的 git 可能根本不是一个 git
很多人的 Mac 上同时存在好几份 git,只是自己不知道。
最常遇到的是这三种来源:第一,Xcode 命令行工具自带的系统 git,位于/usr/bin/git,这是最容易被人忽略的一份;第二,通过 Homebrew 安装的 git,Apple Silicon 上位于/opt/homebrew/bin/git,Intel 上位于/usr/local/bin/git;第三,从官网下载 pkg 安装包装的 git,默认装到/usr/local/git目录,并往/usr/local/bin里塞一个软链接。
你原本以为自己只装了一个 git,实际上可能是“系统自带 + Homebrew”两份并存。平时因为 PATH 顺序稳定,which git永远只返回前面那个,所以你感觉不到。扩容之后,环境变量文件被迁移、目录结构有变化、PATH 里的路径顺序发生调整,系统里隐藏的另外一个 git 就被翻出来了。结果就是你卸载了一份,终端里却还能敲出旧版本号,这就是典型的“尸体还在”。
还有更隐蔽的情况:如果你以前用源码编译过 git,它会直接落到/usr/local/bin,与版本管理器、包管理器都没有建立关联,清理起来需要手动根据时间戳和路径去判断。
1.2 node 的来源更多,残留也更散
node 比 git 更容易脏,因为它的安装方式五花八门,而且每份安装都会在多个位置留下痕迹。常见来源包括:官网 pkg 安装包、Homebrew 的node或node@18这类版本化 formula、nvm 下载到用户目录的版本、fnm 管理的版本。
先看官网 pkg。它会往/usr/local/bin/node、/usr/local/lib/node_modules、/usr/local/include/node、/usr/local/share/man/man1/node.1等位置写文件,属于一次性落地、卸载工具还不存在的类型。Homebrew 则会把真实文件放在/opt/homebrew/Cellar/node/xx/bin/node,然后在/opt/homebrew/bin/node建立软链接。nvm 把一切都放在~/.nvm/versions/node/vxx/bin下,卸载只需要删目录,但环境变量文件里会残留一堆 shell 配置。
这三者的“同名文件”会同时存在于系统里。以前你安装新版本时没有清理旧版本,扩容后 shell 环境又没刷新,node -v输出的可能是某一个版本,但which node指向的却是另一份,而npm install实际调用的模块路径又来自第三份。三套副本互相错位,你看到任何奇怪的报错都不应该惊讶。
所以,想让环境“更干净”,第一步不是急着卸载,而是先把所有安装来源盘点清楚。我们要做到的是:重装之前,系统里不再存在任何一份可执行的 git 和 node 残留。
2. 动手清理前,先把该留的留好:备份清单
在卸载任何东西之前,先花十分钟做备份。很多人换完盘急着清环境,结果把所有配置一并删了,装上新版之后又花大量时间重新配别名、重新配 SSH、重新拉取全局包。备份这件事,做得好能把重建时间压缩到半小时以内。
2.1 git 侧必须备份的四样东西
第一,~/.gitconfig文件。这里面保存了你的全局用户名、邮箱、提交别名、credential helper 配置,有时候还有diff/merge工具设置。它决定了你提交代码时“我是谁”以及“我怎么签名”。
第二,~/.ssh目录。这个目录里的id_ed25519私钥和对应的.pub公钥是你访问代码托管平台的通行证。此外还包括known_hosts,用于记录你信任过的主机指纹。换盘之后如果丢了私钥,或者私钥权限不对,重装 git 反而会引来一堆 SSH 权限问题。
第三,如果有 GPG 签名需求的,备份~/.gnupg目录。这里面的密钥环和信任数据库导出之后,在新环境里继续用同一个 GPG 密钥签名提交,别人看到的提交记录才是一致的。
第四,检查一下是否存在系统级 git 配置/usr/local/etc/gitconfig或/etc/gitconfig。某些安装包会往这里写代理、默认行为配置,这个文件也属于需要确认去留的对象,不要只盯着用户的.gitconfig。
备份时建议把这些文件整个拷贝到外置盘或云盘,而不是就地改名。有一次我图省事只改了.gitconfig后缀,结果后面测试新配置时把新文件也覆盖乱了,只能翻旧历史找回来。正确的做法是单独建一个目录,比如~/env-backup/,把这些配置原样放进去,保留权限属性用cp -Rp。
2.2 node 侧备份的不是目录,而是清单
node 环境真正值得备份的,几乎不包含任何目录。你需要备份的是两样东西。
第一,全局包清单。执行npm ls -g --depth=0,输出的每一行都是一个全局安装的包名和版本号,把它保存到一个文本文件里。重装完 node 之后,你就可以对照这个文件重新安装全局工具,比如 typescript、pnpm、nodemon 等。只备份这个清单,比复制几百兆的node_modules靠谱得多,因为旧包目录里的二进制文件很可能不匹配新架构。
第二,~/.npmrc文件的合理部分。这里面可能有你配置的 registry 地址、镜像源、代理信息。迁移时重点确认 registry 部分是否有价值,如果里面残留了过期的认证 token,建议备份后在新环境里重写而不是原样复制。
除此之外,我强烈建议不要备份~/.npm缓存目录,也不要备份任何旧项目的node_modules。缓存目录在系统迁移后经常因为路径变化而失效,扫描它反而拖慢新环境的首次安装速度。你的目标不是恢复旧缓存,而是在新环境里得到一个可以重新生成一切的可执行基础。
备份做完之后,就可以放心地把旧环境当“垃圾”来清理了。
3. 第一拆:git 卸载到什么程度才算干净
先处理 git,因为它相对独立,不涉及版本切换器的复杂性。清理之前先确定现任 git 是谁,再决定怎么卸。
3.1 用三条命令定位当前 git 的全部安装来源
打开终端,执行下面这三个命令:
type -a git which git brew list --formula 2>/dev/null | grep -E '^git$'第一条type -a git会列出 PATH 里所有能找到的 git 路径,顺序靠前的优先被调用。第二条which git只是第一条的简化版。第三条用来确认 Homebrew 是否管理了一个 git formula。
如果你之前在官网下载过 pkg 版 git,执行这两个命令检查目录是否存在:
ls -la /usr/local/git/bin/git ls -la /usr/local/bin/git把上述输出放在一起归类:哪些是系统 CLT 自带的、哪些是 Homebrew 装的、哪些是 pkg 装的。搞清楚这份清单,你才知道任务量有多大。
3.2 卸载 Homebrew 版 git 的正确姿势
多数人实际使用的主 git 是 Homebrew 这一份。卸载命令很直接:
brew uninstall git brew autoremovebrew autoremove这一步很多人会漏掉,它负责清除那些曾作为 git 依赖被装进来、现在已经不再被任何公式引用的库包。
之后手动检查系统里还残不残留 git 的可执行文件:
ls -la /usr/local/bin/git ls -la /opt/homebrew/bin/git如果ls报“没有这个文件”或显示软链接断裂,说明卸载基本成功。trap 已经干得比较干净,但路径下可能还有git-lfs、git-flow这类配套工具,它们不是同一个包,需要单独决定去留。我的习惯是,既然要干净重来,与 git 相关的扩展工具也一并卸载,等主程序装好后再重新按需安装。
3.3 系统 CLT 自带的 git 要不要卸?
这里我建议不要强行卸载,因为 Xcode 命令行工具内部的 git 是系统组件的组成部分,很多系统维护脚本和第三方工具会依赖它,强行删除可能导致其他组件出现异常。它只会安静地待在/usr/bin/git作为后备。
你可以接受的干净状态有两种:一是/usr/bin/git作为唯一 git 存在;二是/opt/homebrew/bin/git(或/usr/local/bin/git)作为唯一版本,但 PATH 顺序要保证它永远优先于/usr/bin/git。
3.4 别忘了钥匙串和 SSH 权限的整理
git 卸载到这一步,不少人就停下来了。其实还有两个容易被忽略的角落。
第一个是钥匙串访问。打开“钥匙串访问”,搜索代码托管平台相关的条目,把过期的个人访问令牌、旧密码条目删掉。特别是你扩容前如果切换过多个托管平台账号,钥匙串里很可能存了一批已经失效的凭据,它们会在新环境里干扰提交认证。
第二个是~/.ssh目录的权限。重装 git 后 SSH 连不上的高发原因,不是私钥丢了,而是权限过宽或过紧。正确的设置是:
chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub chmod 600 ~/.ssh/config备份出来的.ssh目录可能因为拷贝过程被改了权限,所以恢复后必须重新校正一遍。
4. 第二拆:node 的残留目录比你想的更难缠
node 的卸载工作量远大于 git,因为它涉及“真实文件 + 软链接 + shell 配置 + 缓存目录”四个层面的清理。如果你想做到“更干净”,就要一层层来。
4.1 先判断 node 是由谁安装的
老规矩,先定位:
which node ls -l $(which node)如果ls -l的结果里有->箭头,说明它是一个软链接。箭头指向/opt/homebrew/opt/node@xx/bin/node,说明来自 Homebrew;指向~/.nvm/versions/node/vxx/bin/node,说明来自 nvm;如果which node直接指向/usr/local/bin/node且没有软链接,多半来自官网 pkg。
还有一种可能性:which node输出的是/opt/local/bin/node,这代表你曾经用过 MacPorts 这类包管理器。遇到这种情况,要用对应的管理器卸载。
4.2 各种安装方式的卸载与扫尾
Homebrew 安装的 node,对应执行:
brew uninstall --ignore-dependencies node node@18 node@20 2>/dev/null brew autoremove注意--ignore-dependencies参数,它避免某个包因依赖 node 而阻止卸载。对于版本化 formula,要把所有相关版本一并写出。
nvm 安装的 node,直接删除目录:
rm -rf ~/.nvm然后打开~/.zshrc,删除所有与NVM_DIR、nvm.sh相关的行。这里一定不能只删目录,否则每次打开终端都会报nvm: command not found。
官网 pkg 安装的 node,要手动处理下面这一串路径:
sudo rm -rf /usr/local/lib/node_modules sudo rm -rf /usr/local/include/node sudo rm -rf /usr/local/share/doc/node sudo rm -rf /usr/local/share/man/man1/node.1 sudo rm -rf /usr/local/bin/node sudo rm -rf /usr/local/bin/npm sudo rm -rf /usr/local/bin/npx sudo rm -rf /usr/local/bin/corepack这些路径是官网 pkg 版 node 最常见的落点。不同版本的 pkg 安装器安装路径略有差异,如果node源码目录还出现在/usr/local/opt下,也要检查并删除。
4.3 用户目录下的缓存和配置才是残留重灾区
可执行文件清理干净后,把目光放到~目录下。以下是 node 生态几乎一定会产生的隐藏目录:
rm -rf ~/.npm rm -rf ~/.node-gyp rm -rf ~/.corepack rm -rf ~/.config/yarn rm -rf ~/.config/pnpm~/.npm是 npm 的下载缓存,里面可能躺了几百兆的 tar 包;~/.node-gyp是 node-gyp 编译时用到的头文件缓存,在切换架构后会产生大量“幽灵文件”;~/.corepack是 corepack 下载的 yarn 和 pnpm 二进制版本;后两个分别是 yarn 和 pnpm 的配置与缓存目录。
删除之前建议快速浏览一下~/.npmrc,把 registry 配置记住几秒钟,后面重装时直接写回去。然后果断删掉这些缓存目录。
4.4 shell 配置文件里的 PATH 残留
这是最容易被忽略的一层。打开~/.zshrc、~/.zprofile、~/.bash_profile等文件,逐行检查有没有类似下面这样的残留:
export PATH="$HOME/.nvm/versions/node/v18.16.0/bin:$PATH" export PATH="/usr/local/bin:$PATH" source/usr/local/nvm/nvm.sh扩容前这些配置可能是必要的,但现在它们指向的目录已经被删了,继续保留只会让你的 PATH 里多出一堆无效项,还会造成“明明卸载了却还能找到命令”的假象。把它们注释掉或删除,然后执行:
hash -r这条命令会清空 shell 对命令路径的缓存。
5. 重装策略:git 走包管理器,node 走版本切换器
清理完之后,系统里已经没有 git 和 node 了。这时候重装,最重要的是选对工具。我强烈建议 git 用 Homebrew,node 用版本管理工具,而不是直接在官网下载跑起来就不管。
5.1 git 重装:只保留一份可控的安装
执行:
brew update brew install git装完后立刻检查:
which gitApple Silicon 机器上,输出应该是/opt/homebrew/bin/git;Intel 机器上应该是/usr/local/bin/git。必须保证 Homebrew 路径在 PATH 里排在/usr/bin之前。一般 Homebrew 安装时会自动把/opt/homebrew/bin加进 PATH,如果which git显示的是/usr/bin/git,说明 PATH 顺序有问题,需要在~/.zshrc里把 Homebrew 路径前置。
然后恢复配置。把之前备份的~/.gitconfig拷贝回来,同时恢复~/.ssh并校正权限。最后验收:
git config --global --list ssh -T git@你的托管平台地址第二条命令会尝试与托管平台建立 SSH 会话,返回欢迎信息说明密钥链路是通的。
这里有一个小建议:新装的 git 记得确认一下默认分支名。很多人以前设置过init.defaultBranch main,但换了新配置后忘记补,导致新建仓库默认分支变成 master。写入.gitconfig就能固定下来。
5.2 node 重装:用 nvm 接管一切
相比 git,我见过太多人在 node 上翻车,就是因为“直接装全局”。一旦你后来想切版本,或者想彻底卸载,全局安装是最难反悔的选择。
我更推荐 nvm。它不往系统目录写文件,所有版本都集中在~/.nvm,卸载、切换、降级都非常干净。
安装 nvm 和执行默认 LTS 版本:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.zshrc nvm install --lts nvm alias default 'lts/*'安装脚本会自动在~/.zshrc写入配置。如果source ~/.zshrc之后提示nvm: command not found,检查~/.zshrc里是否真的写入了NVM_DIR导出语句和nvm.sh的 source 行。注意,source ~/.zshrc有时候在非交互式 shell 里可能不生效,直接重开终端窗口最稳妥。
装完 node 之后,先设置 registry,然后按清单恢复全局包:
npm config set registry https://registry.npmjs.org/ npm install -g pnpm typescript nodemon全局包数量控制在可数的范围内。我的列表常年不超过十个,“越少越干净”是重装几次后的真实体会。
5.3 为什么不推荐直接装官网 pkg 版 node
如果你直接从官网下载 pkg 安装,确实三步搞定,但下一次想升级小版本时,你会发现旧版本留在了/usr/local/include和/usr/local/lib/node_modules,卸载又得手动删除。等你再用上一年,残留目录里的头文件和缓存基本没办法分辨归属了。
相比之下,nvm 的版本目录规则是~/.nvm/versions/node/vXX,结构几乎透明。硬盘空间充裕之后,这种透明结构反而更重要,因为你可以在不破坏环境的前提下尝试多个版本。
6. 重装后的验证清单与高频翻车现场
装好不等于装对。环境重装后最忌“看着差不多就继续开发”,而是要花十分钟走一遍验证清单,把问题在项目开工前全部暴露出来。
6.1 一套从底到顶的验证顺序
按下面的顺序在新开一个终端窗口后重新执行:
| 检查项 | 期望结果 | 异常提示 |
|---|---|---|
which git | /opt/homebrew/bin/git或/usr/local/bin/git | 指向系统/usr/bin/git时说明 PATH 顺序不对 |
git --version | 新版本号,如 2.39.3 以上 | 无输出说明没装好 |
git config --global --list | 包含 user.name、user.email 等 | 配置缺失是因为没有恢复.gitconfig |
ssh -T git@托管平台 | 显示欢迎语 | 报权限拒绝说明密钥/known_hosts 有问题 |
which node | ~/.nvm/versions/node/vxx/bin/node | 指向别处说明旧残留污染了 PATH |
node -v | 预期的 LTS 版本号 | 版本不对说明 nvm 默认版本没设对 |
npm -v | 新版本号 | 无输出说明 npm 链路断裂 |
npm config get registry | 官方源或你预期的源 | 配置不对会影响后续镜像下载 |
特别注意:验证时一定要开一个全新的终端窗口,旧的终端窗口里的 shell 可能还缓存着旧命令路径。新窗口显示的结果才是真实环境。
6.2 五个我在现场排查过的高频问题
第一个,npm install报EACCES: permission denied。这通常是因为旧 npm 残留的缓存目录或 node_modules 的属主不是当前用户。先确认是不是用sudo npm装过全局包,如果是,新环境里改用普通权限,不要再用 sudo。然后清理一遍~/.npm再试。
第二个,node 版本在项目目录里突然“回退”。这是 nvm 没有针对项目目录执行nvm use,或者目录里有一个.nvmrc文件但 shell 没有自动加载。在~/.zshrc里追加一段自动读取.nvmrc的逻辑,进入目录时会自动切换版本。
第三个,git 提交到远端时报fatal: unable to access。这种错误通常不是 git 本身的问题,而是现有证书或代理配置有误。检查git config --global --list中是否有残留的http.proxy,把不需要的代理配置删掉。
第四个,SSH 连不上托管平台,报Permission denied (publickey)。Mac 上最常见的不是密钥丢了,而是权限不对或ssh-agent没加载。执行ssh-add ~/.ssh/id_ed25519后,用ssh-add -l确认密钥已进 agent,再测一遍连接。
第五个,运行依赖 node-gyp 的原生模块时报编译错误。这种基本在新系统上没装完整版命令行工具导致缺编译链,执行:
xcode-select --install装完之后重新npm install。
7. 把“干净重装”固化成一套可复用的操作习惯
环境重装不是一件做完就忘的事,真正有价值的是把流程沉淀下来。在经历两三次迁移之后,我发现自己已经从“每次都求救”变成“按固定顺序半小时搞定”。这里分享几个操作性很强的习惯。
7.1 一张可以照着执行的清理顺序卡
我把整个过程浓缩成下面这份顺序清单,下次扩容或换机时直接照做。
- 记录旧环境:
git config --global --list和npm ls -g --depth=0输出保存到文本。 - 备份配置目录:
~/.gitconfig、~/.ssh、~/.gnupg,用cp -Rp保留权限。 - 卸载 git:
brew uninstall git加brew autoremove,并清理对应软链接。 - 卸载 node:先删
~/.nvm,再按残余路径清/usr/local下的 node 文件和/usr/local/lib/node_modules。 - 清理缓存:
rm -rf ~/.npm ~/.node-gyp ~/.corepack ~/.config/yarn ~/.config/pnpm。 - 处理 shell 配置:删除
.zshrc、.zprofile里的所有旧 export 行,关掉所有终端窗口。 - 重装 git:
brew install git,恢复.gitconfig和.ssh。 - 重装 node:nvm 安装 LTS,设置默认版本,恢复全局包清单。
- 验证:执行第六节中的验证表,逐项打勾。
7.2 日常维护的三个小习惯
第一,全局包越少越好。用npx临时调用脚手架工具,不要图省事把工具都装成全局包。全局安装的每一个包,都是下一次迁移需要重新确认的变量。
第二,定期清理无用的缓存。每季度执行一次npm cache clean --force,加上brew autoremove。清理完之后你会发现磁盘占用少了一截,环境也少了很多“历史包袱”。
第三,每次环境变动(比如装了一个重要的全局包),顺手把它记录在一个笔记文件里。这样下次扩容时,你手里已经有一份从零重建环境的操作手册,而不是靠回忆。
对我来说,扩容后重装 git 和 node 最核心的经验是:整个流程的重点不在“装”,而在“清”。你把旧环境的每个来源都摸清、把每一层残留都清理干净,重装本身反而变成了最简单的一步。这套方法也许不能让你免于折腾,但至少能让折腾变得有迹可循,下次再遇到,你完全可以二十分钟内搞定。