macOS SMB 自动重连方案:睡眠/断网后共享挂载不再丢失
macOS SMB 自动重连方案:睡眠/断网后共享挂载不再丢失
Mac mini 通过 SMB 挂载了 Windows PC(C/D/E 盘)和极空间 NAS。核心痛点是 macOS 睡眠、重启、网络断开后,SMB 挂载会自动断开且不会自动恢复,每次都要手动在 Finder 里重新连接。本文记录了一套完整的自动重连方案。
旧方案为什么不行
之前有两个 LaunchAgent 各管各的,存在以下问题:
- 只在登录时触发一次 — 睡眠唤醒后不会重新运行
- 没有网络状态检测 — 断网重连后无感知
- 没有重试机制 — 网络还没就绪就跑了,挂载失败就放弃了
新方案架构
┌─────────────────────────────────────────────────┐
│ com.checo.smb-auto (LaunchAgent) │
│ KeepAlive=true → 崩溃自动重启 │
│ RunAtLoad=true → 登录自动启动 │
└──────────────┬──────────────────────────────────┘
│
┌──────────▼──────────┐
│ smb-watcher.sh │ 后台守护进程
│ (常驻运行) │
│ │
│ 网关 Ping 检测 │ 每 2 分钟检查网关
│ 离线→在线 触发重挂 │ 网络恢复时 --force
│ │
│ 挂载完整性检查 │ 缺失→自动补挂
└──────────┬──────────┘
│ 调用
┌──────────▼──────────┐
│ smb-mount.sh │ 统一挂载脚本
│ (支持 --force) │
└─────────────────────┘实现细节
1. 统一挂载脚本 ~/bin/smb-mount.sh
#!/bin/bash
# Unified SMB auto-mount script
# Usage: smb-mount.sh [--force]
FORCE=0
[[ "$1" == "--force" ]] && FORCE=1
# 共享定义表:IP|用户名|密码|共享名|显示名
SHARES=(
"192.168.x.x|administrator|PASSWORD|C|Win-C"
"192.168.x.x|administrator|PASSWORD|D|Win-D"
"192.168.x.x|administrator|PASSWORD|E|Win-E"
"192.168.x.x|NAS_USER|NAS_PASS|share|NAS"
)挂载检测关键函数:
is_mounted() {
local ip="$1" share="$2"
# macOS mount 输出格式: //user@IP/share on /Volumes/...
# ⚠️ 必须用 //.*@IP 匹配,因为有用户名前缀
mount | grep "smbfs" | grep -q "//.*@$ip/$share "
}踩坑: macOS
mount输出 SMB 挂载时格式是//[email protected]/C on /Volumes/...,中间有username@。用//192.168.x.x/C匹配会永远返回 false。
挂载方式 — osascript(Finder 方式):
osascript -e "tell application \"Finder\" to mount volume \"smb://user:pass@IP/share\""为什么不用
mount -t smbfs?mount_smbfs需要 root 权限,sudo 挂载后挂载点属于 root,普通用户 ls 会 Permission denied。osascript 走 Finder 挂载,权限正确。
Finder 挂载的路径命名规则:
| 挂载顺序 | 路径 |
|---|---|
| 第一个 | /Volumes/<IP> |
| 第二个 | /Volumes/<IP>-1 |
| 第三个 | /Volumes/<IP>-2 |
同一台 Windows 的 C/D/E 盘挂载后路径分别是 /Volumes/192.168.x.x、/Volumes/192.168.x.x-1、/Volumes/192.168.x.x-2,不是 /Volumes/C。
2. 守护进程 ~/bin/smb-watcher.sh
#!/bin/bash
CHECK_INTERVAL=120 # 每 2 分钟检查一次
GATEWAY="192.168.x.1"
RECONNECT_WAIT=8 # 网络恢复后等 8 秒再重挂
was_network_up=true
while true; do
sleep "$CHECK_INTERVAL"
# Ping 网关检测网络状态
network_up=false
ping -c 1 -W 2 "$GATEWAY" &>/dev/null && network_up=true
# 状态转换:离线→在线 = 网络恢复,强制重挂
if [[ "$was_network_up" = false && "$network_up" = true ]]; then
do_remount "网络恢复" # 内部调用 smb-mount.sh --force
fi
# 周期检查:挂载完整性
if [[ "$network_up" = true ]] && any_smb_missing; then
do_remount "挂载缺失"
fi
was_network_up="$network_up"
done触发重挂的三种场景:
| 场景 | 检测方式 | 响应 |
|---|---|---|
| 睡眠唤醒 | 网关从不通→通了 | --force 重挂全部 |
| 网络断开重连 | 同上 | --force 重挂全部 |
| 挂载意外断开 | mount | grep smbfs 缺失 | 非强制补挂缺失项 |
3. LaunchAgent
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/> <!-- 崩溃/退出后自动重启 -->
</dict>
<key>ThrottleInterval</key>
<integer>10</integer> <!-- 重启间隔最短 10 秒 -->文件清单
| 文件 | 作用 | 权限 |
|---|---|---|
~/bin/smb-mount.sh | 统一挂载脚本 | rwx--x--x |
~/bin/smb-watcher.sh | 后台守护进程 | rwx--x--x |
~/Library/LaunchAgents/com.checo.smb-auto.plist | LaunchAgent | rw------- |
运维命令
# 查看当前 SMB 挂载
mount | grep smbfs
# 手动触发挂载
bash ~/bin/smb-mount.sh
# 强制重挂所有共享
bash ~/bin/smb-mount.sh --force
# 查看 watcher 进程
ps aux | grep smb-watcher
launchctl list | grep smb-auto
# 重启 watcher
launchctl unload ~/Library/LaunchAgents/com.checo.smb-auto.plist
launchctl load ~/Library/LaunchAgents/com.checo.smb-auto.plist
# 查看日志
tail -f /tmp/smb-watcher.log
tail -f /tmp/smb-mount.log经验总结
1. RunAtLoad 不等于「一直运行」
RunAtLoad 只在 LaunchAgent 加载时(通常是登录时)执行一次。睡眠唤醒不会重新触发。需要守护进程(长驻循环)或 KeepAlive 来保证持续运行。
2. scutil --monitor 在脚本中不可靠
macOS 的 scutil --monitor 在 LaunchAgent 的 shell 环境中可能无法访问动态存储,会立即退出。更可靠的方案是 ping 网关检测网络状态变化。
3. mount 输出中 SMB 有用户名前缀
mount | grep smbfs 的输出格式是 //username@IP/share on /Volumes/...,不是 //IP/share。写 grep 模式时必须用 //.*@IP/share。
4. Finder 挂载 vs mount_smbfs
| 方式 | 权限 | 需要 root | 挂载路径 |
|---|---|---|---|
osascript (Finder) | 当前用户 | 否 | /Volumes/<IP> |
mount -t smbfs | root | 是 | 自定义 |
推荐 osascript 方式,权限正确,不需要 sudo。
5. SMB 挂载必然断开的场景
以下场景 SMB 挂载一定会断开,无法预防,只能自动恢复:
- macOS 睡眠/休眠
- 网络接口切换(WiFi ↔ 有线)
- 路由器重启
- Windows / NAS 重启
- 网线拔插
核心策略不是「防止断开」,而是「断开后秒级自动恢复」。
