macOS SMB Auto-Reconnect: Never Lose Shared Mounts After Sleep/Network Drop
macOS SMB Auto-Reconnect: Never Lose Shared Mounts After Sleep/Network Drop
A Mac mini mounts Windows PC drives (C/D/E) and a NAS via SMB. The core pain point: after macOS sleep, reboot, or network disconnection, SMB mounts drop silently and never auto-recover — requiring manual Finder reconnection every time. This article documents a complete auto-reconnect solution.
Why the Old Approach Failed
Two separate LaunchAgents existed, each managing different mounts, with these problems:
- RunAtLoad only fires once — doesn't re-trigger after sleep wake
- No network state detection — can't sense network recovery
- No retry mechanism — runs before network is ready, fails silently
New Architecture
┌─────────────────────────────────────────────────┐
│ com.checo.smb-auto (LaunchAgent) │
│ KeepAlive=true → auto-restart on crash │
│ RunAtLoad=true → start on login │
└──────────────┬──────────────────────────────────┘
│
┌──────────▼──────────┐
│ smb-watcher.sh │ Background daemon
│ (always running) │
│ │
│ Gateway Ping │ Check every 2 min
│ offline→online │ Trigger --force remount
│ │
│ Mount Integrity │ Missing→auto-remount
└──────────┬──────────┘
│ calls
┌──────────▼──────────┐
│ smb-mount.sh │ Unified mount script
│ (supports --force) │
└─────────────────────┘Implementation Details
1. Unified Mount Script ~/bin/smb-mount.sh
#!/bin/bash
# Unified SMB auto-mount script
# Usage: smb-mount.sh [--force]
FORCE=0
[[ "$1" == "--force" ]] && FORCE=1
# Share definition table: IP|user|password|share|display_name
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"
)Mount detection key function:
is_mounted() {
local ip="$1" share="$2"
# macOS mount output: //user@IP/share on /Volumes/...
# ⚠️ Must use //.*@IP pattern due to username prefix
mount | grep "smbfs" | grep -q "//.*@$ip/$share "
}Gotcha: macOS
mountoutput for SMB includes a username prefix://[email protected]/C on /Volumes/.... Using//192.168.x.x/Cas a pattern will always return false.
Mount method — osascript (Finder way):
osascript -e "tell application \"Finder\" to mount volume \"smb://user:pass@IP/share\""Why not
mount -t smbfs?mount_smbfsrequires root. Sudo-mounted volumes belong to root, causing Permission denied for regular users. osascript mounts via Finder with correct permissions.
Finder mount path naming convention:
| Mount Order | Path |
|---|---|
| First | /Volumes/<IP> |
| Second | /Volumes/<IP>-1 |
| Third | /Volumes/<IP>-2 |
C/D/E drives from the same Windows machine mount as /Volumes/192.168.x.x, /Volumes/192.168.x.x-1, /Volumes/192.168.x.x-2 — not /Volumes/C.
2. Watcher Daemon ~/bin/smb-watcher.sh
#!/bin/bash
CHECK_INTERVAL=120 # Check every 2 minutes
GATEWAY="192.168.x.1"
RECONNECT_WAIT=8 # Wait 8s after network recovery
was_network_up=true
while true; do
sleep "$CHECK_INTERVAL"
# Ping gateway to detect network state
network_up=false
ping -c 1 -W 2 "$GATEWAY" &>/dev/null && network_up=true
# State transition: offline→online = network recovery, force remount
if [[ "$was_network_up" = false && "$network_up" = true ]]; then
do_remount "Network recovery" # Calls smb-mount.sh --force
fi
# Periodic check: mount integrity
if [[ "$network_up" = true ]] && any_smb_missing; then
do_remount "Missing mount"
fi
was_network_up="$network_up"
doneThree remount trigger scenarios:
| Scenario | Detection | Response |
|---|---|---|
| Sleep wake | Gateway unreachable→reachable | --force remount all |
| Network reconnect | Same as above | --force remount all |
| Unexpected mount drop | mount | grep smbfs missing | Non-force remount missing |
3. LaunchAgent
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/> <!-- Auto-restart on crash/exit -->
</dict>
<key>ThrottleInterval</key>
<integer>10</integer> <!-- Min 10s between restarts -->File Inventory
| File | Purpose | Permissions |
|---|---|---|
~/bin/smb-mount.sh | Unified mount script | rwx--x--x |
~/bin/smb-watcher.sh | Background daemon | rwx--x--x |
~/Library/LaunchAgents/com.checo.smb-auto.plist | LaunchAgent config | rw------- |
Operations Commands
# View current SMB mounts
mount | grep smbfs
# Manual mount (skips already mounted)
bash ~/bin/smb-mount.sh
# Force remount all shares
bash ~/bin/smb-mount.sh --force
# Check watcher process
ps aux | grep smb-watcher
launchctl list | grep smb-auto
# Restart watcher
launchctl unload ~/Library/LaunchAgents/com.checo.smb-auto.plist
launchctl load ~/Library/LaunchAgents/com.checo.smb-auto.plist
# View logs
tail -f /tmp/smb-watcher.log
tail -f /tmp/smb-mount.logLessons Learned
1. RunAtLoad ≠ "Always Running"
RunAtLoad only fires once when the LaunchAgent loads (typically at login). Sleep wake does not re-trigger it. A daemon (long-running loop) or KeepAlive is needed for continuous operation.
2. scutil --monitor Is Unreliable in Scripts
macOS scutil --monitor may fail to access dynamic storage in a LaunchAgent shell environment, exiting immediately. Pinging the gateway to detect network state changes is far more reliable.
3. mount Output Has Username Prefix for SMB
mount | grep smbfs outputs //username@IP/share on /Volumes/..., not //IP/share. Always use //.*@IP/share in grep patterns.
4. Finder Mount vs mount_smbfs
| Method | Permissions | Requires Root | Mount Path |
|---|---|---|---|
osascript (Finder) | Current user | No | /Volumes/<IP> |
mount -t smbfs | Root | Yes | Custom |
osascript is recommended — correct permissions, no sudo needed.
5. Scenarios Where SMB Mounts Will Always Drop
These scenarios guarantee SMB disconnection — prevention is impossible, only auto-recovery works:
- macOS sleep/hibernate
- Network interface switch (WiFi ↔ Ethernet)
- Router reboot
- Windows / NAS reboot
- Cable unplug
The core strategy is not "prevent disconnection" but "auto-recover within seconds after disconnection."
