Skip to content

Instantly share code, notes, and snippets.

@tsarenkotxt
Last active March 31, 2022 17:04
Show Gist options
  • Save tsarenkotxt/2146eec830d8f24727fee693598116fc to your computer and use it in GitHub Desktop.
Save tsarenkotxt/2146eec830d8f24727fee693598116fc to your computer and use it in GitHub Desktop.
Mac OS VPN auto connect and reconnect, for a specific user, with notifications
  1. Create a python script, and save as auto_vpn.py in the ~/Documents directory

it is not necessary to use this file name and directory, but it is used in the following steps

auto_vpn.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import time

try:
    import httplib  # python < 3.0
except:
    import http.client as httplib

MY_VPN = 'My VPN'  # rename as your VPN connection called
MY_USERNAME = 'My USER'  # rename as your specific user called

INTERNET_VERIFICATION_TIMEOUT = 3
VPN_SWITCH_RETRY_ATTEMPTS = 5
VPN_SWITCH_SLEEP_SECONDS = 1
LOOP_SLEEP_SECONDS = 3

VPN_CONNECTED_STATUS = 'connected\n'
VPN_DISCONNECTED_STATUS = 'disconnected\n'


def is_user_active(user):
    stream = os.popen("stat -f '%Su' /dev/console")
    try:
        result = stream.read()
        return user in result
    finally:
        stream.close()


def is_internet_connected():
    connection = httplib.HTTPSConnection('google.com', 443, timeout=INTERNET_VERIFICATION_TIMEOUT)
    try:
        connection.request('GET', '/')
        return True
    except Exception:
        return False
    finally:
        connection.close()


def is_vpn_has_status(vpn, status):
    stream = os.popen('networksetup -showpppoestatus "{}"'.format(vpn))
    try:
        result = stream.read()
        return result == status
    finally:
        stream.close()


def connect_vpn(vpn):
    stream = os.popen('networksetup -connectpppoeservice "{}"'.format(vpn))
    try:
        return stream.read()
    finally:
        stream.close()


def disconnect_vpn(vpn):
    stream = os.popen('networksetup -disconnectpppoeservice "{}"'.format(vpn))
    try:
        return stream.read()
    finally:
        stream.close()


def wait_until_vpn_switch(vpn_switch_condition):
    for attempt in range(VPN_SWITCH_RETRY_ATTEMPTS):
        if vpn_switch_condition():
            return True
        else:
            # squaring the attempt counter
            time.sleep(VPN_SWITCH_SLEEP_SECONDS * (attempt + 1) ** 2)
    else:
        return vpn_switch_condition()


def display_notification(title, text):
    os.system("""
              osascript -e 'display notification "{}" with title "{}"'
              """.format(text, title))


def connect():
    display_notification(MY_VPN, 'connecting  πŸš€')
    connect_vpn(MY_VPN)
    if wait_until_vpn_switch(lambda: is_vpn_has_status(MY_VPN, VPN_CONNECTED_STATUS)):
        display_notification(MY_VPN, 'connected  πŸš€  βœ…')
    else:
        display_notification(MY_VPN, 'connection failed  πŸš€  πŸ›‘')


def disconnect():
    display_notification(MY_VPN, 'disconnecting  πŸ’£')
    disconnect_vpn(MY_VPN)
    if wait_until_vpn_switch(lambda: is_vpn_has_status(MY_VPN, VPN_DISCONNECTED_STATUS)):
        display_notification(MY_VPN, 'disconnected  πŸ’£  βœ…')
    else:
        display_notification(MY_VPN, 'disconnection failed  πŸ’£  πŸ›‘')


def reconnect():
    display_notification(MY_VPN, 'reconnecting  🎲')
    disconnect()
    connect()


while True:
    # connect to the VPN, for the specific user
    if is_user_active(MY_USERNAME) and is_internet_connected() and is_vpn_has_status(MY_VPN, VPN_DISCONNECTED_STATUS):
        connect()

    # reconnect to the VPN, for the specific user
    elif is_user_active(MY_USERNAME) and not is_internet_connected() and is_vpn_has_status(MY_VPN, VPN_CONNECTED_STATUS):
        reconnect()

    # disconnect from the VPN, for other users
    elif not is_user_active(MY_USERNAME) and is_vpn_has_status(MY_VPN, VPN_CONNECTED_STATUS):
        disconnect()

    time.sleep(LOOP_SLEEP_SECONDS)
  1. Open Script Editor and copy and paste the following AppleScript code into the new blank script editor

AutoVPNLauncher.app

do shell script "python ~/Documents/auto_vpn.py &> /dev/null &"
  • Go to the File menu and choose Save
  • Under the File Format pulldown menu, choose Application
  • Under the Where pulldown menu, choose Documents directory
  • Save as AutoVPNLauncher.app
  1. Add AutoVPNLauncher.app to the Login items in your System Preferences

  2. Optional steps

  • Turn off notifications accumulation
    • Go to the Notification Centre in your System Preferences
    • Under the Script Editor turn off Show in Notification Centre
  1. Known issues
  • Apply Mac OS startup changes
    • Reboot your Mac (or logout & login to the specific user) to apply the changes
    • Approve/Grant access to the scripts
    • Reboot again

The specific user - is the user for which you want to use a specific VPN. For example, a workVPN for a workUser, while for other users the workVPN should be disconnected

@tsarenkotxt
Copy link
Author

FYI created the VPN Handler πŸ‘½ application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment