In a recent update to the vpn-profile-switcher.sh script, I have added support for WireGuard, expanding its functionality beyond the initial OpenVPN compatibility. This update allows users to choose between OpenVPN and WireGuard for their VPN configurations on OpenWRT. Below, I will walk you through the changes and explain the new options and functionalities added to the script.

Overview of the Script

The vpn-profile-switcher.sh script automates the process of retrieving the recommended NordVPN server, downloading its configuration file, setting the credentials, and configuring OpenWRT to use this server. Initially designed for OpenVPN, the script now includes support for WireGuard, a faster and more efficient VPN protocol.

Key Updates for WireGuard Support

New Options in the Usage Function

To accommodate WireGuard, new command-line options have been added:

printf "  -t | --type < openvpn | wireguard >,\t\tSelect either OpenVPN or WireGuard. Default is OpenVPN.\n"
printf "  -i | --interface < interface >,\t\tSpecify interface to use for WireGuard. Default is 'nordlynx'. Applicable only for WireGuard.\n"

The --type option allows the user to select the VPN type, either openvpn or wireguard. The --interface option specifies the interface to use for WireGuard, with nordlynx as the default.

Verification Functions

Two new functions ensure that valid values are provided for the --type and --interface options:

function verify_vpn_type() {
    if [ ! "$1" == "openvpn" ] && [ ! "$1" == "wireguard" ]; then
        logger -s "($0) VPN type must be either openvpn or wireguard, your input was: $1."
        exit 1
    else
        echo $1
    fi
}

function verify_protocol() {
    if [ ! "$1" == "tcp" ] && [ ! "$1" == "udp" ]; then
        logger -s "($0) Protocol must be either udp or tcp, your input was: $1."
        exit 1
    else
        echo $1
    fi
}

Fetching Recommendations

The get_recommended function fetches the recommended server configuration. If WireGuard is selected, it also retrieves the server’s public key:

function get_recommended() {
    _url="https://api.nordvpn.com/v1/servers/recommendations?"
    if [ ! -z "$group_identifier" ]; then
        _url=${_url}"filters[servers_groups][identifier]="${group_identifier}"&"
    fi
    if [ ! -z "$country_id" ]; then
        _url=${_url}"filters[country_id]="${country_id}"&"
    fi
    _url=${_url}"filters[servers_technologies][identifier]="${vpn_type}"_"${protocol}"&limit="${recommendations_n}
    logger -s "($0) Fetching VPN recommendations from: $_url"
    _json=$(wget -q -O - "$_url") || true
    recommended=$(jsonfilter -s "$_json" -e '$[0].hostname') || true
    recommendations=$(jsonfilter -s "$_json" -e '$[*].hostname') || true
    loads=$(jsonfilter -s "$_json" -e '$[*].load') || true
    if [ "$vpn_type" == "wireguard" ]; then
        public_key=$(jsonfilter -s "$_json" -e '$[0].technologies[*].metadata[*].value') || true
    fi
}

Configuration Handling

The script includes functions for checking existing configurations and enabling or disabling them as needed:

function check_in_configs() {
    if [ "$vpn_type" == "openvpn" ]; then
        server_name=$(uci show openvpn | grep $recommended.$protocol | awk -F '\.' '/config/{print $2}')
    elif [ "$vpn_type" == "wireguard" ]; then
        server_name=$(uci show network | grep "${wg_iface}.*endpoint_host.*${recommended}" | sed -E 's/.*_([a-z]{0,2}[0-9]{1,3}).*='\''(.*)'\''$/\1/')
    fi
}

function check_enabled() {
    if [ "$vpn_type" == "openvpn" ]; then
        enabled_server=$(uci show openvpn | grep "enabled='1'" | awk -F '\.' '/.*/{print $2}')
    elif [ "$vpn_type" == "wireguard" ]; then
        enabled_server=$(uci show network | grep "${wg_iface}.*disabled='0'" | sed -E 's/.*_([a-z]{0,2}[0-9]{1,3}).*='\''.*'\''$/\1/')
    fi
}

Creating and Enabling Entries

For WireGuard, the script creates a new entry and configures it with the appropriate parameters:

function create_new_entry() {
    if [ "$vpn_type" == "openvpn" ] then
        new_server=$(echo "$recommended" | sed 's/[\.\ -]/_/g' | sed "s/com/$protocol/g")
        uci set openvpn.$new_server=openvpn
        uci set openvpn.$new_server.config="/etc/openvpn/$recommended.$protocol.ovpn"
    elif [ "$vpn_type" == "wireguard" ] then
        new_server=$(echo "$recommended" | sed -E 's/([a-z]{2}[0-9]{1,3}).*''$/\1/')
        uci set network.${wg_iface}_peer_${new_server}="wireguard_$wg_iface"
        uci set network.${wg_iface}_peer_${new_server}.public_key="$public_key"
        uci set network.${wg_iface}_peer_${new_server}.endpoint_host="$recommended"
        uci set network.${wg_iface}_peer_${new_server}.endpoint_port="51820"
        uci set network.${wg_iface}_peer_${new_server}.persistent_keepalive="25"
        uci set network.${wg_iface}_peer_${new_server}.route_allowed_ips="1"
        uci add_list network.${wg_iface}_peer_${new_server}.allowed_ips="0.0.0.0/0"
        uci add_list network.${wg_iface}_peer_${new_server}.allowed_ips="::/0"
        uci set network.${wg_iface}_peer_${new_server}.description="$recommended"
    fi
}

Restarting Services

Depending on the VPN type selected, the script restarts the appropriate service:

function restart_openvpn() {
    uci commit openvpn
    /etc/init.d/openvpn restart
}

function restart_wireguard() {
    uci commit network
    /etc/init.d/network restart
}

Execution

Finally, the script’s main execution block processes the provided command-line arguments and invokes the necessary functions to configure the VPN:

check_required

while [ ! -z "$1" ]; do
    case "$1" in
    -h | --help)
        show_usage
        ;;
    -t | --type)
        shift
        vpn_type=$(verify_vpn_type $1)
        ;;
    -p | --protocol)
        shift
        protocol=$(verify_protocol $1)
        ;;
    -c | --country)
        shift
        country_id=$(country_code $1)
        ;;
    -g | --group)
        shift
        group_identifier=$(server_groups $1)
        ;;
    -r | --recommendations)
        shift
        recommendations_n=$(check_is_num $1)
        ;;
    -d | --distance)
        shift
        load_distance=$(check_is_num $1)
        ;;
    -l | --login-info)
        shift
        secret="$1"
        ;;
    -i | --interface)
        shift
        wg_iface="$1"
        ;;
    -b | --db-location)
        shift
        db_location=$(verify_db_location $1)
        ;;
    *)
        show_usage
        ;;
    esac
    shift
done

# Fetch the recommended server
get_recommended

# Check if the recommended server is already configured
check_in_configs

# Disable the current server configuration if it exists
if [ ! -z "$server_name" ]; then
    disable_current_entry $server_name
fi

# Create a new entry for the recommended server if it doesn't already exist
if [ -z "$server_name" ]; then
    create_new_entry
else
    enable_existing_entry $server_name
fi

# Restart the appropriate VPN service
if [ "$vpn_type" == "openvpn" ]; then
    restart_openvpn
elif [ "$vpn_type" == "wireguard" ]; then
    restart_wireguard
fi

# Clean up and exit
unset_variables

Conclusion

The vpn-profile-switcher.sh script now offers robust support for both OpenVPN and WireGuard, providing flexibility and efficiency for users. This update simplifies the process of configuring VPN profiles on OpenWRT, ensuring a seamless experience regardless of the chosen VPN protocol. For more details, refer to the dev branch on the repository on GitHub.

In the next post, I will cover adding some nice to have features as separate scripts, and perhaps add some test results. Stay tuned for more updates and enhancements to the VPN Profile Switcher script.