Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save paragbaxi/f9698d4378ff514a6c7786ddb99421b2 to your computer and use it in GitHub Desktop.
Save paragbaxi/f9698d4378ff514a6c7786ddb99421b2 to your computer and use it in GitHub Desktop.
Auto-Switching NextDNS Profiles per macOS User (Supports Fast User Switching)

πŸ›‘οΈ Auto-Switching NextDNS Profiles per macOS User (Updated)

This setup ensures that each macOS user gets their assigned NextDNS config β€” even with fast user switching and standard (non-admin) accounts β€” without requiring manual scripts, prompts, or login hooks.


🎯 Goal

Automatically install the correct NextDNS profile within 30 seconds of user login or fast user switch, but only if the current profile is incorrect.


βœ… Solution: Polling LaunchDaemon

We deploy a root-owned LaunchDaemon that runs a script every 30 seconds. The script:

  1. Detects the active console user.
  2. Maps the user to their designated NextDNS config ID.
  3. Fetches the currently active NextDNS config ID.
  4. Compares the two IDs and only runs the install command if they differ.

πŸ›  Setup Steps

1. Create the Polling Script

Create the file /usr/local/bin/nextdns-poll.sh with the updated logic.

#!/bin/bash
LOG=/var/log/nextdns-poll.log
export PATH="/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"

# Detect active console user
user=$(stat -f "%Su" /dev/console)

# Map macOS username to its NextDNS Config ID
case "<span class="math-inline">user" in
mac\-username\-1\) cfg\=nextdns\-config\-profile\-1 ;;
mac\-username\-2\) cfg\=nextdns\-config\-profile\-2 ;;
mac\-username\-3\) cfg\=nextdns\-config\-profile\-3 ;;
mac\-username\-4\) cfg\=nextdns\-config\-profile\-4 ;;
\*\)
\# Exit if the user is not in our list \(e\.g\., '\_mbsetupuser' during startup\)
exit 0
;;
esac
\# Get the currently installed NextDNS Config ID
current\=</span>(/opt/homebrew/bin/nextdns status 2>/dev/null | awk '/Config ID/ {print $3}')

# --- Conditional Check ---
# Only run the 'install' command if the current config ID does not match the
# expected config ID for the logged-in user. This prevents unnecessary work.
if [ "$current" != "<span class="math-inline">cfg" \]; then
echo "\[</span>(date)] User '$user' requires config '$cfg', but '$current' is active. Switching..." >> $LOG
  
  # Run the install command with the correct user's config
  sudo /opt/homebrew/bin/nextdns install \
    -config "$cfg" \
    -report-client-info \
    -auto-activate >> $LOG 2>&1
fi

Make it executable:

sudo chmod +x /usr/local/bin/nextdns-poll.sh

2. Grant Passwordless sudo Access

Edit sudoers using sudo visudo and add one specific NOPASSWD line for each user's exact installation command.

# Allow each user to run only the specific NextDNS install command for their own profile
mac-username-1 ALL=(root) NOPASSWD: /opt/homebrew/bin/nextdns install -config nextdns-config-profile-1 -report-client-info -auto-activate
mac-username-2 ALL=(root) NOPASSWD: /opt/homebrew/bin/nextdns install -config nextdns-config-profile-2 -report-client-info -auto-activate
mac-username-3 ALL=(root) NOPASSWD: /opt/homebrew/bin/nextdns install -config nextdns-config-profile-3 -report-client-info -auto-activate
mac-username-4 ALL=(root) NOPASSWD: /opt/homebrew/bin/nextdns install -config nextdns-config-profile-4 -report-client-info -auto-activate

Security Note: This remains a secure approach because it doesn't grant broad permissions. It only allows the execution of a single, non-interactive command.

3. Create and Load the LaunchDaemon

Create /Library/LaunchDaemons/com.nextdns.poll.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](http://www.apple.com/DTDs/PropertyList-1.0.dtd)">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.nextdns.poll</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/nextdns-poll.sh</string>
  </array>
  <key>StartInterval</key>
  <integer>30</integer>
  <key>RunAtLoad</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/var/log/nextdns-poll.log</string>
  <key>StandardErrorPath</key>
  <string>/var/log/nextdns-poll.err</string>
</dict>
</plist>

Then, install and load it:

sudo chown root:wheel /Library/LaunchDaemons/com.nextdns.poll.plist
sudo chmod 644 /Library/LaunchDaemons/com.nextdns.poll.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.nextdns.poll.plist

πŸ“¦ One-Step Install Script (Updated)

Here is the revised helper script incorporating the more clearly commented polling script. Customize the case statement with your usernames and config IDs before running.

#!/usr/bin/env bash
set -e

echo "πŸ”§ Installing NextDNS polling setup with conditional check..."

# Create the polling script
sudo tee /usr/local/bin/nextdns-poll.sh > /dev/null << 'EOF'
#!/bin/bash
LOG=/var/log/nextdns-poll.log
export PATH="/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"

# Detect active console user
user=$(stat -f "%Su" /dev/console)

# Map macOS username to its NextDNS Config ID
case "<span class="math-inline">user" in
mac\-username\-1\) cfg\=nextdns\-config\-profile\-1 ;;
mac\-username\-2\) cfg\=nextdns\-config\-profile\-2 ;;
mac\-username\-3\) cfg\=nextdns\-config\-profile\-3 ;;
mac\-username\-4\) cfg\=nextdns\-config\-profile\-4 ;;
\*\)
\# Exit if the user is not in our list
exit 0
;;
esac
\# Get the currently installed NextDNS Config ID
current\=</span>(/opt/homebrew/bin/nextdns status 2>/dev/null | awk '/Config ID/ {print $3}')

# Only run install if the active config doesn't match the expected one
if [ "$current" != "<span class="math-inline">cfg" \]; then
echo "\[</span>(date)] User '$user' requires config '$cfg', but '$current' is active. Switching..." >> $LOG
  sudo /opt/homebrew/bin/nextdns install \
    -config "$cfg" \
    -report-client-info \
    -auto-activate >> $LOG 2>&1
fi
EOF

sudo chmod +x /usr/local/bin/nextdns-poll.sh

# Create the LaunchDaemon plist
sudo tee /Library/LaunchDaemons/com.nextdns.poll.plist > /dev/null << 'EOF'
<?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](http://www.apple.com/DTDs/PropertyList-1.0.dtd)">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.nextdns.poll</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/nextdns-poll.sh</string>
  </array>
  <key>StartInterval</key>
  <integer>30</integer>
  <key>RunAtLoad</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/var/log/nextdns-poll.log</string>
  <key>StandardErrorPath</key>
  <string>/var/log/nextdns-poll.err</string>
</dict>
</plist>
EOF

# Set correct ownership and permissions, then load the service
sudo chown root:wheel /Library/LaunchDaemons/com.nextdns.poll.plist
sudo chmod 644 /Library/LaunchDaemons/com.nextdns.poll.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.nextdns.poll.plist

echo "βœ… Done. Auto-switching NextDNS is active."
echo "➑️ Next step: Edit sudoers with 'sudo visudo' to grant NOPASSWD access."

πŸ§ͺ Verify

  1. Switch between users or log out and back in.
  2. Run nextdns status in the Terminal to confirm the correct Config ID is active.
  3. Check the log file for switch events:
tail -f /var/log/nextdns-poll.log
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment