Hello, friends! Today I’m letting you in on a project I cooked up for our (fabulously fictional) IT powerhouse, Abracadabra Industries.
If you’ve ever tried to keep proxy settings and Wi-Fi states tamed on a fleet of MacBooks—with users bouncing between corporate, café, and who-knows-where networks—you’ll know it’s less “push-button magic” and more “herding caffeinated cats.” So I decided to automate it, and now I’m sharing everything—warts, workarounds, and wisdom gained the hard way.
Oh, and fair warning: This is still a work in progress! If you spot a better way or an edge case I’ve missed, tell me. I’m learning as I go, and this script has already had more versions than macOS itself.
The Problem (aka “Why Won’t This Just Work?”)
- I wanted to auto-apply a PAC file when a Mac is on our corporate network (Ethernet or Wi-Fi).
- Remove the PAC file the moment it’s on a non-corporate network.
- Auto-switch Wi-Fi off when docked (Ethernet active), but turn it back on when the user undocks.
- Never override users who turn Wi-Fi off on purpose.
- Deploy with Jamf Pro so it just works, everywhere.
Jamf Pro is brilliant for most things, but it doesn’t natively handle this combo of network magic. Plus, network interfaces change names when people use different docks and adapters. “en5” today, “en7” tomorrow, and who knows what when you visit the wizarding office.
The Solution: Bash + Jamf + a Dash of Abracadabra
I wrote a Bash script that:
- Detects current Ethernet and Wi-Fi interfaces, no matter their names.
- Watches network state, logs changes, and manages PAC config.
- Plays nicely with users who toggle Wi-Fi manually.
- Is designed for easy deployment using Jamf Pro.
I’ll walk you through the logic, the gotchas, and even drop the full code so you can copy, fork, or adapt as needed.
Step-by-Step: From Cat Herding to Click-and-Forget
1. How Does the Script Work?
Key Variables
- PAC_URL: The address of your proxy PAC file (make it your own!).
- CORP_PREFIXES: IP prefixes for your corporate network(s).
- LOGFILE: Where logs go for troubleshooting (so you don’t go blind in the terminal).
PAC_URL="http://pac.abracadabra-industries.local/magic.pac"
CORP_PREFIXES=("10.77." "192.0.2." "203.0.113.")
LOGFILE="/Library/Logs/abracadabra-network-watcher.log"
Interface Detection
The script works out which Ethernet and Wi-Fi interfaces are currently active (no hardcoding “en5” ever again):
WIFI_IF="en0" # Typically Wi-Fi on Macs
get_service_by_device(){
networksetup -listallhardwareports | \
awk -v dev="$1" '
/^Hardware Port:/ {port=$3}
$0 == "Device: " dev {print port}
'
}
get_ip(){ ipconfig getifaddr "$1" 2>/dev/null || echo ""; }
# Find first active Ethernet (ignoring Wi-Fi)
for dev in $(ifconfig -l | tr ' ' '\n' | grep '^en' | grep -v "^${WIFI_IF}$"); do
ip=$(get_ip "$dev")
[[ -n $ip ]] && {
ETH_IF=$dev
ETH_SVC=$(get_service_by_device "$dev")
eth_ip=$ip
break
}
done
Result: Works with any dock, any port, any day.
PAC Application and Removal Logic
This is the heart of the script—when to apply, when to clear, and how to avoid double-setting or missing a change.
apply_pac(){
local svc=$1 cur enabled
cur=$(networksetup -getautoproxyurl "$svc" 2>/dev/null | awk '/URL/ {print $2}')
enabled=$(networksetup -getautoproxyurl "$svc" 2>/dev/null | awk '/Enabled/ {print $2}')
log "DEBUG: PAC check for $svc - URL='$cur', Enabled='$enabled'"
if [[ "$enabled" == "Yes" && "$cur" == "$PAC_URL" ]]; then
log "ℹ️ PAC already present on $svc; skipping."
else
networksetup -setautoproxyurl "$svc" "$PAC_URL" 2>/dev/null \
&& networksetup -setautoproxystate "$svc" on 2>/dev/null \
&& log "✅ Applied PAC on $svc"
fi
}
clear_pac(){
local svc=$1 cur enabled
cur=$(networksetup -getautoproxyurl "$svc" 2>/dev/null | awk '/URL/ {print $2}')
enabled=$(networksetup -getautoproxyurl "$svc" 2>/dev/null | awk '/Enabled/ {print $2}')
log "DEBUG: Clear PAC check for $svc - URL='$cur', Enabled='$enabled'"
if [[ "$enabled" != "Yes" || -z $cur || $cur == "(null)" ]]; then
log "ℹ️ No PAC to clear on $svc; skipping."
else
networksetup -setautoproxystate "$svc" off 2>/dev/null \
&& networksetup -setautoproxyurl "$svc" "" 2>/dev/null \
&& log "✅ Cleared PAC from $svc"
fi
}
Result: No more “ghost” PAC settings or missed clears.
State Management and User Respect
- Script records the current/previous state in a temp file.
- If the user manually turns off Wi-Fi, the script won’t force it back on.
- When you unplug Ethernet, Wi-Fi comes back automatically (unless user switched it off themselves).
2. What It Fixes (and What Broke Along the Way)
Problems I Ran Into:
- PAC not applying: Script would say PAC was set, but it wasn’t.
Fix: Robust checks for both URL and “Enabled” state, with extra debug logging. - Wi-Fi toggling too aggressively: Users who wanted Wi-Fi off were getting frustrated.
Fix: Only auto-restore if script turned Wi-Fi off; otherwise, respect user preference. - Network interfaces changing all the time:
Fix: Detect the currently active one, don’t assume a name.
What Still Isn’t Perfect (aka “The Known Quirks”):
- Network state changes sometimes lag by a few seconds.
- If you’re on a super-locked-down network, Jamf or the script might need extra permissions.
- Please test in your own environment before rolling out everywhere!
3. How to Deploy with Jamf (The Quick Version)
a. Place the script at /usr/local/bin/abracadabra-network-watcher.sh
b. Add the LaunchDaemon at /Library/LaunchDaemons/com.abracadabra.networkwatcher.plist
**c. Use Jamf Pro to deploy both files and (optionally) run once to test.
**d. Logs live at /Library/Logs/abracadabra-network-watcher.log
4. The Full Script (Copy, Fork, Adapt, Enjoy!)
#!/bin/bash
LOGFILE="/Library/Logs/abracadabra-network-watcher.log"
STATEFILE="/var/tmp/abracadabra-net-state"
PAC_URL="http://pac.abracadabra-industries.local/magic.pac"
CORP_PREFIXES=("10.77." "192.0.2." "203.0.113.")
WIFI_IF="en0"
MAX_LOG_SIZE=5242880
MAX_LOG_ROTATIONS=3
rotate_logs(){
[[ -f $LOGFILE && $(stat -f%z "$LOGFILE") -gt $MAX_LOG_SIZE ]] || return
for ((i=MAX_LOG_ROTATIONS; i>=1; i--)); do
[[ -f "$LOGFILE.$i" ]] && {
(( i == MAX_LOG_ROTATIONS )) && rm -f "$LOGFILE.$i" \
|| mv "$LOGFILE.$i" "$LOGFILE.$((i+1))"
}
done
mv "$LOGFILE" "$LOGFILE.1"
touch "$LOGFILE"
}
log(){ echo "[$(date '+%d-%m-%Y %H:%M:%S')] $*" >> "$LOGFILE"; }
get_service_by_device(){
networksetup -listallhardwareports | \
awk -v dev="$1" '
/^Hardware Port:/ {port=$3}
$0 == "Device: " dev {print port}
'
}
get_ip(){ ipconfig getifaddr "$1" 2>/dev/null || echo ""; }
get_wifi_power(){
local out status power
out=$(networksetup -getairportpower "$WIFI_IF" 2>&1); status=$?
if (( status != 0 )); then
power="Off"
else
power=$(printf '%s' "$out" | awk -F': ' '{print $2}')
[[ -z $power ]] && power="Off"
fi
echo "$power"
}
is_corp(){
for p in "${CORP_PREFIXES[@]}"; do
[[ $1 == "$p"* ]] && return 0
done
return 1
}
apply_pac(){
local svc=$1 cur enabled
cur=$(networksetup -getautoproxyurl "$svc" 2>/dev/null | awk '/URL/ {print $2}')
enabled=$(networksetup -getautoproxyurl "$svc" 2>/dev/null | awk '/Enabled/ {print $2}')
log "DEBUG: PAC check for $svc - URL='$cur', Enabled='$enabled'"
if [[ "$enabled" == "Yes" && "$cur" == "$PAC_URL" ]]; then
log "ℹ️ PAC already present on $svc; skipping."
else
networksetup -setautoproxyurl "$svc" "$PAC_URL" 2>/dev/null \
&& networksetup -setautoproxystate "$svc" on 2>/dev/null \
&& log "✅ Applied PAC on $svc"
fi
}
clear_pac(){
local svc=$1 cur enabled
cur=$(networksetup -getautoproxyurl "$svc" 2>/dev/null | awk '/URL/ {print $2}')
enabled=$(networksetup -getautoproxyurl "$svc" 2>/dev/null | awk '/Enabled/ {print $2}')
log "DEBUG: Clear PAC check for $svc - URL='$cur', Enabled='$enabled'"
if [[ "$enabled" != "Yes" || -z $cur || $cur == "(null)" ]]; then
log "ℹ️ No PAC to clear on $svc; skipping."
else
networksetup -setautoproxystate "$svc" off 2>/dev/null \
&& networksetup -setautoproxyurl "$svc" "" 2>/dev/null \
&& log "✅ Cleared PAC from $svc"
fi
}
rotate_logs
# Wi-Fi detection
WIFI_SVC=$(get_service_by_device "$WIFI_IF")
wifi_ip=$(get_ip "$WIFI_IF")
wifi_power=$(get_wifi_power)
# Ethernet detection (first active en* except Wi-Fi)
ETH_IF="" ; ETH_SVC="" ; eth_ip=""
for dev in $(ifconfig -l | tr ' ' '\n' | grep '^en' | grep -v "^${WIFI_IF}\$"); do
ip=$(get_ip "$dev")
[[ -n $ip ]] && {
ETH_IF=$dev
ETH_SVC=$(get_service_by_device "$dev")
eth_ip=$ip
break
}
done
log "ℹ️ Detected services: Wi-Fi='$WIFI_SVC', Ethernet='$ETH_SVC'"
# Load previous state
if [[ -f $STATEFILE ]]; then
IFS=',' read -r old_wifi_ip old_wifi_type old_eth_ip old_eth_type old_autooff old_wifi_power \
< "$STATEFILE"
else
old_wifi_ip=""; old_wifi_type=""; old_eth_ip=""; old_eth_type=""
old_autooff="0"; old_wifi_power="On"
fi
log "DEBUG: wifi_power='$wifi_power' old_wifi_power='$old_wifi_power' old_autooff='$old_autooff'"
# Classify network types
if is_corp "$wifi_ip"; then wifi_type="corp"
elif [[ -n $wifi_ip ]]; then wifi_type="ext"
else wifi_type=""; fi
if is_corp "$eth_ip"; then eth_type="corp"
elif [[ -n $eth_ip ]]; then eth_type="ext"
else eth_type=""; fi
# Respect manual Wi-Fi Off
if [[ $wifi_power == "Off" && $old_wifi_power == "On" && $old_autooff == "0" && $eth_type != "corp" ]]; then
log "🔇 Manual Wi-Fi OFF by user; exiting without touching PAC."
printf "%s,%s,%s,%s,%s,%s\n" \
"$wifi_ip" "$wifi_type" "$eth_ip" "$eth_type" "$old_autooff" "$wifi_power" \
> "$STATEFILE"
exit 0
fi
# Exit if nothing changed
if [[ "$wifi_ip" == "$old_wifi_ip" && "$wifi_type" == "$old_wifi_type" && \
"$eth_ip" == "$old_eth_ip" && "$eth_type" == "$old_eth_type" && \
"$wifi_power" == "$old_wifi_power" ]]; then
exit 0
fi
# Network change!
log "=== Network change detected ==="
[[ -n $ETH_IF ]] && log "Ethernet: $ETH_SVC, $eth_ip ($eth_type)"
[[ -n $WIFI_IF ]] && log "Wi-Fi: $WIFI_SVC, $wifi_ip ($wifi_type) (Power: $wifi_power)"
if [[ $eth_type == "corp" ]]; then
# Corporate Ethernet always wins
log "🔒 Corporate Ethernet"
apply_pac "$ETH_SVC"; apply_pac "$WIFI_SVC"
if [[ $wifi_power == "On" ]]; then
networksetup -setairportpower "$WIFI_IF" off 2>/dev/null
log "✂️ Auto-turned Wi-Fi OFF for Corporate Ethernet"
autooff="1"
else
autooff="$old_autooff"
fi
elif [[ $eth_type == "ext" ]]; then
# External Ethernet: clear proxy and auto-off Wi-Fi once
log "🌐 External Ethernet"
clear_pac "$ETH_SVC"; clear_pac "$WIFI_SVC"
if [[ $old_autooff == "0" && $wifi_power == "On" ]]; then
networksetup -setairportpower "$WIFI_IF" off 2>/dev/null
log "✂️ Auto-turned Wi-Fi OFF for External Ethernet"
autooff="1"
else
autooff="$old_autooff"
fi
elif [[ $wifi_type == "corp" ]]; then
# Corporate Wi-Fi only
log "🔒 Corporate Wi-Fi"
apply_pac "$WIFI_SVC"
autooff="0"
elif [[ $wifi_type == "ext" ]]; then
# External Wi-Fi only: clear proxy, restore Wi-Fi if we auto-off earlier
log "🌐 External Wi-Fi"
clear_pac "$WIFI_SVC"
if [[ $old_autooff == "1" ]]; then
networksetup -setairportpower "$WIFI_IF" on 2>/dev/null
log "🔌 Restored Wi-Fi ON after Ethernet unplug"
fi
autooff="0"
else
# No interfaces at all: clear PACs, restore Wi-Fi if needed
log "❌ No active interfaces"
clear_pac "$WIFI_SVC"; clear_pac "$ETH_SVC"
if [[ $old_autooff == "1" ]]; then
networksetup -setairportpower "$WIFI_IF" on 2>/dev/null
log "🔌 Restored Wi-Fi ON after all interfaces down"
fi
autooff="0"
fi
log "=== End of change ==="
# Save new state
printf "%s,%s,%s,%s,%s,%s\n" \
"$wifi_ip" "$wifi_type" "$eth_ip" "$eth_type" "$autooff" "$wifi_power" \
> "$STATEFILE"
5. The LaunchDaemon (com.abracadabra.networkwatcher.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>com.abracadabra.networkwatcher</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/abracadabra-network-watcher.sh</string>
</array>
<key>WatchPaths</key>
<array>
<string>/Library/
Final Thoughts
This script is working well for me (so far!) at Abracadabra Industries. But it’s a living project, and I know it’ll need tweaks as real users (and real networks) do unpredictable things. If you use this, adapt it, or improve it, please let me know—I’d love to keep making it better.
Stay magical, Mac admins!
— George