In this post, I will walk you through the process of updating and upgrading a shell script I originally wrote a few years ago for changing the VPN profile on OpenWRT routers. This script connects to NordVPN’s recommended servers, and I’ve recently added support for WireGuard (NordLynx) alongside the existing OpenVPN functionality.

The original script was intended for OpenWRT installations on TP-Link Archer C20i and Raspberry Pi 3B+ with 2019 firmware. You can find the VPN Profile Switcher script here.

The script queries NordVPN’s API for the recommended server and then updates the OpenVPN or WireGuard configuration on the router accordingly.

Here, I’ll discuss the upgrade process, including adding support for WireGuard, and share the essential parts of the script without exposing application-specific details.

Understanding the Script’s Purpose

The primary goal of the script is to:

  1. Fetch the recommended NordVPN server using their API.
  2. Check if the recommended profile exists on the router.
  3. If the profile does not exist, download it and configure it with the saved username and password.
  4. Update the router’s VPN configuration to use the new profile.
  5. Clean up old profiles, keeping only the currently connected and previously connected profiles.

Research and Preparation

Since NordVPN started offering WireGuard servers (NordLynx) but didn’t provide a tutorial for manual connection on OpenWRT, I had to spend some time researching how to set it up. I found an unofficial documentation for NordVPN’s API long time ago, and used a comment on a GitHub gist as a starting point for extracting the necessary details.

Setting Up WireGuard

First, I set up WireGuard on my development machine (a Mac) and then ported it to OpenWRT. Since I was unfamiliar with WireGuard, this involved a lot of trial and error. I used Claude (Anthropic’s AI) to help convert cURL commands to the lighter wget command suitable for OpenWRT. Despite Claude’s help, I had to install the wget-ssl package to avoid errors from NordVPN’s servers.

With the necessary data extracted using jsonfilter, I created a WireGuard interface using LuCI, OpenWRT’s web interface. This initial success allowed me to formalize my approach in a script.

Script Details

Below is a simplified description of the script’s structure and logic. The actual script is more detailed and includes error handling and logging.

  1. Check for Required Packages:
     function check_packages() {
         opkg list-installed wget-ssl | grep -q . || opkg list-installed curl | grep -q .
         if [ $? -ne 0 ]; then
             logger -s "($0) Error: either wget-ssl or curl are required to run this script. Please install either one of the packages."
             exit 1
         fi
     }
    
  2. Fetch Credentials:
     function get_credentials() {
         if which curl >/dev/null 2>&1; then
             response_json=$(curl -s -u token:"$access_token" https://api.nordvpn.com/v1/users/services/credentials)
         else
             response_json=$(wget -q -O - --auth-no-challenge --user=token --password="$access_token" https://api.nordvpn.com/v1/users/services/credentials)
         fi
         if [ $? -ne 0 ]; then
             logger -s "$(0) Error: Failed to fetch credentials. Please check your access token and internet connection."
             exit 1
         fi
     }
    
  3. Save Credentials:
     function save_credentials() {
         if [ "$vpn_type" == "openvpn" ]; then
             _username=$(jsonfilter -s "$response_json" -e '@.username')
             _password=$(jsonfilter -s "$response_json" -e '@.password')
             echo "$_username" > /etc/openvpn/secret
             echo "$_password" >> /etc/openvpn/secret
         else
             _key=$(jsonfilter -s "$response_json" -e "@.nordlynx_private_key")
             uci set network.$wg_iface=interface
             uci set network.$wg_iface.proto='wireguard'
             uci set network.$wg_iface.private_key=$_key
             uci set network.$wg_iface.listen_port='51820'
             uci set network.$wg_iface.addresses='10.5.0.2/32'
             uci commit network
         fi
     }
    
  4. Script Execution:
     check_packages
     get_credentials
     save_credentials
    

For more details, you can review the full script in the GitHub repository.

In the next post, I’ll cover the final integration of the updated script into the VPN Profile Switcher, ensuring seamless switching between OpenVPN and WireGuard profiles.