Table of Contents

WireGuard extras

Introduction

Extras

References

Web interface

If you want to manage VPN settings and view VPN status using web interface. Install the necessary packages.

# Install packages
opkg update
opkg install luci-proto-wireguard qrencode
service rpcd restart

Connection probe / VPN automation failover script

No current VPN providers have a Luci app for OpenWrt that acts like their OS-specific desktop, laptop & mobile VPN clients. Those apps can dynamically pull VPN server info, and connect on an as-needed basis, to the closest/fastest VPN server, or other preferences you have (example). When moving your VPN client connection from your individual devices to the router, you must initially make your own (wireguard) interface(s) on the router. ( ProtonVPN examples: see here, and here ). However, since individual VPN servers can go down for maintenance, or have other issues, and you would otherwise lose the oversight, redundancy and error-checking provided by using a direct client-based app, one way to improve the conditions at the router, is to have a boot script periodically checking your VPN profile for connectivity. If the check fails, it either addresses the basic wan connectivity, if that failed, or if that is ok, it tries bringing up each VPN profile, one at a time. If none of them are able to connect, it logs that information so you can take corrective action.

ABOUT This script is an upgraded version of the script here, and was originally built from it, adding VPN, then SQM and CAKE-AUTORATE, on top of devices that have a cell modem, particularly Quectel cellular devices that seem to have firmware stability issues, but are also one of the most common devices. SIERRA support was added later.

REQUIREMENTS and PREREQUISITES

wan-watchdog.sh
#!/bin/sh
#wan-watchdog.sh v.20251201.01
 
# Settings:
 
# Less than 45 will run-once then exit:
BOOTWAIT="5" # Under 45 seconds, automatically sets TESTMODE=1: Script will run once in Testing Mode and then quit (not loop).
 
# Re-test connection interval: How many seconds to wait after a completed process of checking connectivity, to do it again?
LOOPWAIT="30"
 
WAITFOR="2" # Seconds to wait in between steps (adds momentary pauses between steps, useful for watching the script when debugging in test mode)
 
MODEMTYPE="SIERRA" # Quectel (QUECTEL) or Sierra Wireless (SIERRA). At this time, different reset procedures are done depending on the modem. They may be merged in future versions. It's found at present testing, that both types of modems exhibit different lock-up characteristics, and different procedures based on actual experiences, are used to recover.
 
# Put here, your wwan/cell connection INTERFACE name & wwan DEVICE name. The INTERFACE name is the name at the top of the box in LuCI. It is usually designated by YOU when you created it, and is typically 'qmi'.
WWANNAME="qmi" # The WWAN Interface Name as 'ifup' recognizes it. This is different than the WWANDEVICE below, usually 'wwan0'.
WWANDEVICE="wwan0" # The WWAN DEVICE: as used by 'ifconfig'. Primarily for the cake/sqm portion of the script when VPN=0
MODEMUSBDEVNODE="/dev/cdc-wdm0" # This setting is normally /dev/cdc-wdm0 on single-modem systems.
 
# Requires the SQM and Cake-Autorate setup steps, first. See further below. If you do NOT have SQM and CAKE-AUTORATE configured, choose '0'
DOYOULIKECAKE="0" # Enable Cake-Autorate (requires SQM). Works with WWAN or VPN.
 
# Requires the SQM module and setup steps, first. See below notes (too long for here). If you do NOT have SQM configured, or are not using it, choose '0'.
# SQM? 1 - Yes, turn-on and use SQM. 0 - No. If No, you can turn on Hardware flow control in Firewall.
FEELINGSQMISH="0"
 
# SQM does not work with Hardware Offloading in your Firewall settings: Turn it to Software Offloading if using SQM.
# Low-performance routers, like the ZBT WE826 (MOFI4500), cannot do SQM, let alone CAKE-AUTORATE. Real-world performance is better simply with both disabled and Hardware Offloading enabled.
 
if [[ "$DOYOULIKECAKE" == "1" ]] ; then
	FEELINGSQMISH="1" # SQM _must_ be enabled if Cake-Autorate is turned-on.
fi
 
# Where do you want the logfile to write to?
log_file="/root/wan_watchdoglog.txt"
 
# VPN (1 - Yes / 0 - No):
# Define whether you want to use VPN's: 1 for yes 0 for no. You may, for whatever reason, not want to use VPN's on your router at some time, e.g. during testing.
# ATTENTION: Reminder when you set-up all your VPN profiles, to configure them to NOT 'Start on boot'. Let this script start them instead.
VPN="0"
 
# Put here the names of your VPN Interfaces (Luci->Network->Interfaces):
# ATTENTION: None of your VPN profiles (interfaces) should be set to 'Start at boot' - UNCHECK that. And manually test each one to make sure they work.
VPN1="protonvpn"
VPN2="protonvpn2"
VPN3="protonvpn3"
VPN4="protonvpn4"
 
# Also look at the hosts in check_fping: pre-set to well-known dns sites. Use your local DNS names or addresses for best results.
 
# Changelog: Newest comments at the top #
# v20251201: re-wrote the check_fping script. Was having too much trouble in the prior method with detecting success or failure.
# minor updates will be noted with x.xx where .xx is the incremental. The general logic and process has not changed.
# Reordered check_fping to do the ipv6 test first, primarily because it's the more important protocol, but also because QMI brings it up first.
# Removed/fixed: removed dns.opendns.com from the ipv6 fping query. It reports a false positive for an unknown reason, when device actually has no connectivity.
# Fixed: --fast-reachable is just --reachable. check_fping was generating a failure, because certain versions don't use --fast-reachable.
# Added: the MODEMUSBDEVNODE variable for what is typically /dev/cdc-wdm0.
# Moved: the modem reset procedure (ifdown/up, AT!RESET) to a function: modem_reset
# Change: Swapped-in dns.cloudflare.com dns.google dns.opendns.com for the test sites to ping. You can use different ones, but the DNS names should resolve with 'ping name -4' and 'ping name -6', and respond normally via both IPv4 and IPv6 addresses. You should use DNS NAMES, not IP addresses.
# Change: Added commands for resetting a hung SIERRA wireless modem. Script originally designed only for QUECTEL.
# Change: moved a number of common configurable variables to the top of this script.
# Fixed: added a command prior to reboot, to do a proper shutdown of SQM which without it will leave a ghost /tmp/run/sqm/sqm-run.lock/pid and the .lock folder, which would otherwise give an
# ...'SQM: WARNING: Unable to get run lock - already held by xxxx' error.
# Fixed: a small script startup logging bug.
# Added: a short sleep period after the sysntpd restart, to make sure the service has a few seconds (5) to retrieve the current time and date.
# Removed: 'service sysntpd restart' instructions from /etc/rc.local (Router boot actions), and moved that command to this script,
# ...as Cell-connected routers can take up to 45 seconds to establish a link, and 
# ...therefore require that much time to be able to get the accurate local time on which to write to the watchdog log.
# ...If you have 'service sysntpd restart' in your /etc/rc.local as per earlier instructions, you should delete that line.
# Slight logging changes for clarification.
# VPN=0 handling improvement/fixes.
# Enabled SQM and cake to run on the plain WWAN device, e.g. wwan0, if VPN=0.
# Cleaned up: the use of /lib/functions/network.sh
# Cleaned up: the bootwait logic. Funciontionally, the previous version of the script works fine, but it makes more sense to do it this way.
# Fixed the cake-autorate logic. Cake-autorate responds variously 'not running' or 'inactive' when it is not running (why?? who knows?).
# Added/changed/updated: clearing SQM interfaces after changing/starting VPN
# Added: Exporting CURRENTVPN value to file for use by cake-autorate.sh.
# Changed: write CURRENTVPN for use by outside scripts to /tmp not /root. It doesn't need to be /root / persistent between restarts.
# Added: logfile as variable instead of explicitly 
# Improved: FPING function to test IPv4 and IPv6 both, and issue a failure if either one fails.
 
# Setup: Call this script from rc.local with '/root/wan-watchdog.sh &'
# On cell-connected routers, what this is designed-for, your rc.local calls this script at startup:
#
# sleep 15 # Gives the router a little more time to complete boot-up processes.
# /root/wan-watchdog.sh & # Starts this script and allows it to run in the background.
 
# Check for existence of a logfile. Create one if there isn't one:
echo -e "\n" # new blank line
if [ -f ${log_file} ]
	then 
	echo "Logfile check. Logfile ${log_file} exists, continuing..."
elif ! [ -f ${log_file} ]
	then
	echo "Logfile ${log_file} does not exist... creating."
	echo "Logfile created." >> $log_file
fi
 
# BOOTWAIT TESTMODE determination:
# When BOOTWAIT is less than 45 seconds, this script runs in Testing Mode:
# Ordinary mode: designed for start-on-boot and loop, checking the connection, etc.
# Testing mode: Will cause this script to run 1 time then exit, and if there is e.g. a total wwan failure, it will echo to the screen 'reboot' but not actually reboot the router.
# The wait period gives your cell modem time to connect. Should be at least 45 seconds under ordinary circumstances.
# Usually 45-360 seconds. Initial waiting period before testing.
# Controlling it via BOOTWAIT is done in case you reboot your router manually with the script in testing mode: the script does not automatically keep running, reboot or go into a boot-loop.
if [ $BOOTWAIT -ge 45 ] ; then # Test BOOTWAIT value if less/more than 60 to determine test run or normal run mode.
	echo "Watchdog script running in normal mode. (This message not logged)"
	TESTMODE="0"
elif [ $BOOTWAIT -lt 45 ] ; then
	echo "Bootwait less than 45 seconds. Running in test Mode on $(date)"
	echo "Watchdog script running in test-mode, run once and exit."
	echo "Verbose responses to screen / not logged, and no reboot."
	TESTMODE="1"
fi
 
 
# DOYOULIKECAKE: SQM and Cake-autorate (cell wan bufferbloat)
# To add Cake-Autorate on top of SQM, for variable-speed internet uplinks (cellular, typically):
# 1. You must have SQM installed (luci-app-sqm).
# 2. Cake-autorotate must be already set-up according to: https://github.com/lynxthecat/cake-autorate/blob/master/INSTALLATION.md
# 3. Initially you must manually start each VPN profile, one at a time, and go to the Network->SQM QoS menu, and 'Enable this SQM Instance', set the DL/UL, and Queue Discipline to Cake/Layer-of-cake 
# 4. Do that with all 4 VPN instances individually.
# 5. Do it also with the plain wwan0 interface/device. Shut off all VPN's then configure Cake/layer-of-cake using the Luci SQM menu on it.
#	SQM will remember the configurations for each device: VPN's and raw wwan0. When it is restarted in this script, during testing
#	mode you will see errors for all the OTHER devices that are not active. That is normal.
# 6. Note: There are also some lines that need to be modified in /root/cake-autorate/config.primary.sh Cake-Autorate config, as follows:
#   Add:
#    read -r CAKEWAN < /tmp/currentvpn.txt
#   Change:
#    dl_if="ifb4${CAKEWAN}" # This will show up in current versions of cake-autorate's config.primary.sh as "dl_if=ifb-wan" instead of dl_if=ifb4wan. They should be changed as specified here.
#    ul_if="${CAKEWAN}"
# 6. After you manually get cake-autorate working, DISABLE the cake-autorate service from auto-start: 
#	Choose 'disabled' (from System->Startup) for cake-autorate. Instead, this script will manually start cake-autorate at the appropriate stage.
# 7. If you have a working configuration with this script and Cake-Autorate, and:
#	Your router has gone into TFTP recovery mode after an attempted -sysupgrade, (an ongoing issue when attempting to upgrade an LBR20), and you have a restored backup, and
#	prior to the backup you had the following lines in System->Backup->Configuration:
# 	/etc/firewall.user # for custom firewall commands to change the Hop and TTL
#	/root # to back-up the entire root folder, including this script, log and cake-autorate folder.
#
#	You need to re-run the cake-autorate's 'setup.sh', and choose Y to keep your existing configuration (which is config.primary.sh),
#	to get cake-autorate actually working again. It will already be disabled from auto-starting on boot, in the System->Startup list.  
 
# include functions needed for retrieving current gateway, ipv4 wan and ipv6 wan IP addresses:
. /lib/functions/network.sh
 
# Below is the custom FPING function: (not from /lib/functions/network.sh, but made specifically for this script instead) 
check_fping() {
	# Run the fping command and check if it succeeds
	# Interval in ms, count number is number of failures before function returns FAILURE. You can adjust these numbers as-needed.
	# This pings your provider's DNS servers or those sites of your choosing,
	# If 1 response each from the IPV4 and IPV6 is successful, then it registers SUCCESS.
	# If either all IPv4 or all IPv6 completely fail, or both fail, it responds with FAILURE.
	local result1=0
	local result2=0
	echo "Pinging your selected ipv4 and ipv6 sites:"
	# generic dns global sites, prefer you edit the lines to use your provider's ipv4 and ipv6 dns servers
	# IPV6 sample hosts:
	fping --alive --interval=400 --count=3 --reachable=1 --addr -6 dns.cloudflare.com dns.google || result1=$?
	# IPV4 sample hosts:
	fping --alive --interval=400 --count=3 --reachable=1 --addr -4 dns.cloudflare.com dns.google dns.opendns.com || result2=$?
	if [ $result1 -eq 0 ] && [ $result2 -eq 0 ]; then
		return 0 # Return success
	else
		return 1 # Return failure
	fi
}
 
modem_reset() {
if [[ "$MODEMTYPE" == "QUECTEL" ]] ; then
	echo "Quectel mode restart procedure:" | tee -a "$log_file"
	echo "Attempting to use ifdown/up to restart ${WWANNAME} WWAN interface." | tee -a "$log_file"
	echo "Turning off the ${WWANNAME} WWAN interface in ${WAITFOR} seconds.."
	sleep $WAITFOR
	echo "ifdown ${WWANNAME}" | tee -a "$log_file"
	ifdown $WWANNAME
	echo "Wait 5 seconds then bringing up the ${WWANNAME} interface.." | tee -a "$log_file"
	sleep 5 # Mandatory 5 seconds
	echo "ifup ${WWANNAME}" | tee -a "$log_file"
	ifup $WWANNAME
	echo "Wait 60 seconds to give the modem time to reconnect..." | tee -a "$log_file"
	sleep 60 # give the modem time to reconnect (takes at least 25 seconds for a typical QMI Quectel config. YMMV)
elif [[ "$MODEMTYPE" == "SIERRA" ]] ; then
	echo "SIERRA WIRELESS mode restart:" | tee -a "$log_file"
	echo "Attempting to send a reset command using qmi through ${MODEMUSBDEVNODE}." | tee -a "$log_file"
	sleep $WAITFOR
	# on very old SIERRA firmware, it may be necessary to send an 'offline' operating mode first, then 'reset', to do a reset:
	# echo "Sending: uqmi -d ${MODEMUSBDEVNODE} --set-device-operating-mode offline" | tee -a "$log_file"
	# uqmi -d ${MODEMUSBDEVNODE} --set-device-operating-mode offline | tee -a "$log_file"
	echo "Sending: uqmi -d ${MODEMUSBDEVNODE} --set-device-operating-mode reset" | tee -a "$log_file"
	uqmi -d ${MODEMUSBDEVNODE} --set-device-operating-mode reset | tee -a "$log_file"
	echo "Wait 25 seconds to give the modem time to reconnect..." | tee -a "$log_file"
	echo "then re-querying the ${MODEMTYPE}'s ${MODEMUSBDEVNODE}'s status:" | tee -a "$log_file"
	sleep 25 # Mandatory 25 seconds
	uqmi -d ${MODEMUSBDEVNODE} --get-device-operating-mode | tee -a "$log_file"
	echo "Waiting another 30 seconds for the actual IP data interface to come up..." | tee -a "$log_file"
	sleep 30
	# Note: We are not testing based on what uqmi reports (usually as) 'online', but instead,
	# using multiple fpings to actually see if ipv4 & 6 are working. The 'get operating mode' of uqmi is just for logging/reporting purposes.
	sleep $WAITFOR
else
	echo "MODEMTYPE is not set correctly in script" | tee -a "$log_file"
	echo "aborting...."
	break
fi
}
 
########################### start ####################### 
echo -e "\n" # new blank line
echo "Waiting ${BOOTWAIT} seconds..."
sleep $BOOTWAIT # The startup delay must be first, to ensure the time and date recorded in the log is correct.
 
if [[ "$TESTMODE" == "1" ]] ; then
	echo "Wan-watchdog script started in testing mode. Will wait ${BOOTWAIT} seconds before checking connectivity."
	echo "BOOTWAIT set to: ${BOOTWAIT}"
	echo "WAITFOR pause length set to: ${WAITFOR}"
	echo "Output to screen, no logging. Run once and exit. No loop, and no reboot if total wan failure."
	echo "Increase the BOOTWAIT value for ordinary start-up."
elif [[ "$TESTMODE" == "0" ]] ; then
	service sysntpd restart # restart the time-server on the router, only after BOOTWAIT.
	sleep 5 # give 5 seconds for sysntpd to retrieve and reset the clock to current time.
	echo | tee -a "$log_file" # Blank line to delineate new logging sequence (on restart, startup of router)
	echo "Wan-Watchdog script started / Router booted on $(date)" | tee -a "$log_file"
fi
 
 
if [[ "$DOYOULIKECAKE" == "1" && "$VPN" == "1" ]]
	then
	# The next line addresses the automatically-applied fq_codel on the WWAN interface. Since we will be running cake-autorate inside the VPN usually, this should be removed/set to noqueue. This is a one-time check after every script start / boot-up. 
	echo "Cake-autorate selected ON. VPN turned ON. Therefore, removing default fq_codel from basic ${WWANDEVICE}/${WWANNAME} interface.." | tee -a "$log_file"
	tc qdisc replace dev ${WWANDEVICE} root noqueue | tee -a "$log_file"
fi
 
echo -e "\n" # new blank line
echo "Entering wan & vpn testing:"
network_flush_cache
network_find_wan NET_IF_NAME
network_find_wan6 NET6_IF_NAME
# ATTENTION: Look at the following output during testing, to determine if you correctly set WWANDEVICE & WWANNAME, assuming you are connected in a basic way to your default internet connection when you test-run this 
echo "WWANDEVICE device is set to ${WWANDEVICE}"
echo "WANNNAME interface is set to ${WWANNAME}"
echo "MODEMUSBDEVNODE is set to ${MODEMUSBDEVNODE}"
echo "NET_IF_NAME reports as ${NET_IF_NAME}" # if NIN is null, then the WAN is not connected (usually during testing where WWANNAME has been manually stopped).
echo "NET6_IF_NAME reports as ${NET6_IF_NAME}" # same as above
# Ordinarily either NIN or N6IN must match WWANNAME, if the router is connected to the internet when you test-run this script, and you have no VPN profiles active.
# If neither NET or NET6 IF match WWANNAME, stop this script and correct the value for $WWANNAME stored at the top.
echo "Sleeping for ${WAITFOR} seconds..."
sleep $WAITFOR
# Connection Status: 0 Not connected-connection attempt failure, exit and reboot.
# Connection Status: 1 Unknown State, try to take corrective action
# Connection Status: 2 WWAN interface connected, fping success
# Connection Status: 3 WWAN & VPN connected, fping success
NEEDREBOOT="0" # Initialize
RELOADCAKE="0" # Initialize
VPNCHANGED="0" # Initialize
CONNECTIONSTATUS="1" # Initialize
ACTIVEVPN="none" # Initialize the -active and tested- VPN profile, to 'none'.
sleep $WAITFOR
 
while [[ $CONNECTIONSTATUS -ge 1 ]]; do # While CONNECTION STATUS is 1 or greater, do the following loop:
	# if1
	if check_fping ; then
		echo
		echo "Successful fping response."
		echo
		# if2
		if [[ "$VPN" -eq "1" ]] ; then # If VPN selector is turned ON, try/test VPN connections 1 through 4.
			echo "VPN Option selector turned-on '1'. Getting current interface connection values..."
			network_flush_cache
			network_find_wan NET_IF_NAME
			network_find_wan NET6_IF_NAME
			echo "Current values for:"
			echo "NET_IF_NAME: ${NET_IF_NAME}"
			echo "NET6_IF_NAME: ${NET6_IF_NAME}"
			echo "WWANNAME interface: ${WWANNAME}"
			echo "WWANDEVICE device: ${WWANDEVICE}"
			echo "Checking if any VPN connections are currently up..."
			sleep $WAITFOR
			# if3
			if [[ "$NET_IF_NAME" == "$VPN1" ]] || [[ "$NET_IF_NAME" == "$VPN2" ]] || [[ "$NET_IF_NAME" == "$VPN3" ]] || [[ "$NET_IF_NAME" == "$VPN4" ]]; then
				echo "ACTIVEVPN is ${NET_IF_NAME}, is reported as up, and fping is successful!"
				echo "Basic WWAN Connection is up, and 1 out of 4 possible VPN profiles/interfaces are connected."
				echo "Setting value of ACTIVEVPN to ${NET_IF_NAME}.."
				ACTIVEVPN=$NET_IF_NAME
				echo "Setting CONNECTIONSTATUS to 3.."
				CONNECTIONSTATUS="3"
				sleep $WAITFOR
				# if4
				if [[ -f /tmp/currentvpn.txt ]] ; then 
					echo "/tmp/currentvpn.txt exists reading value into PREVIOUSVPN.."
					read -r PREVIOUSVPN < /tmp/currentvpn.txt
				else
					echo "/tmp/currentvpn.txt does not exist. Initializing PREVIOUSVPN to 'none'."
					PREVIOUSVPN="none"
				fi # fi4
				echo "Comparing ACTIVEVPN to PREVIOUSVPN:"
				echo "PREVIOUSVPN is: ${PREVIOUSVPN}.."
				# if5
				if [[ "$ACTIVEVPN" == "$PREVIOUSVPN" ]] ; then
					# If ACTIVEVPN matches PREVIOUSVPN in this loop or run, then do nothing and continue
					echo "Current VPN is the same as the previously-connected VPN profile, continuing..."
					VPNCHANGED="0"
					sleep $WAITFOR
				else # if the values do not match, then maybe the VPN has changed from previous, write the currentvpn to 'currentvpn.txt'
					echo "VPN has changed. Previous VPN was ${PREVIOUSVPN}. Current VPN is ${CURRENTVPN}" | tee -a "$log_file"
					echo "Writing changes to currentvpn.txt..."
					VPNCHANGED="1"
					echo "$ACTIVEVPN" > /tmp/currentvpn.txt # write the current vpn value to a file, e.g. to be used by the cake autorate script.
					sleep $WAITFOR
				fi # fi5 Finished sensing and reacting to a match or mismatch between ACTIVEVPN and PREVIOUSVPN
			# if3 elif
			# else if the WWAN interface is up, & VPN selector is turned-on, but no VPN is up yet.
			elif [[ "$WWANNAME" == "$NET_IF_NAME" || "$NET6_IF_NAME" ]] ; then 
				echo "VPN not up yet; VPN selector is ON. WWAN network interface is up. Trying to get a VPN connection up..." | tee -a "$log_file"
				sleep $WAITFOR
				echo "Trying $VPN1..." | tee -a "$log_file"
				echo "ifup ${VPN1}" | tee -a "$log_file"
				ifup $VPN1 | tee -a "$log_file"
				sleep 7 # wait for it to connect
				network_flush_cache
				network_find_wan NET_IF_NAME
				if [[ "$NET_IF_NAME" == "$VPN1" ]] && check_fping ; then
					ACTIVEVPN=$VPN1
					CONNECTIONSTATUS="3"
					echo "${VPN1} is up, and fping success!" | tee -a "$log_file"
				else
					echo "Unable to connect to ${VPN1}... trying ${VPN2} profile..." | tee -a "$log_file"
					echo "ifdown ${VPN1}" | tee -a "$log_file"
					ifdown $VPN1 | tee -a "$log_file"
					sleep $WAITFOR
					echo "ifup ${VPN2}" | tee -a "$log_file"
					ifup $VPN2 | tee -a "$log_file"
					sleep 7
					network_flush_cache
					network_find_wan NET_IF_NAME
					if [[ "$NET_IF_NAME" == "$VPN2" ]] && check_fping ; then
						ACTIVEVPN=$VPN2
						CONNECTIONSTATUS="3"
						echo "${VPN2} is up and fping success!" | tee -a "$log_file"
					else
						echo "Unable to connect to ${VPN2}, trying ${VPN3} profile..." | tee -a "$log_file"
						echo "ifdown ${VPN2}" | tee -a "$log_file"
						ifdown $VPN2 | tee -a "$log_file"
						sleep $WAITFOR
						echo "ifup ${VPN3}" | tee -a "$log_file"
						ifup $VPN3 | tee -a "$log_file"
						sleep 7
						network_flush_cache
						network_find_wan NET_IF_NAME
						if [[ "$NET_IF_NAME" == "$VPN3" ]] && check_fping ; then
							ACTIVEVPN=$VPN3
							CONNECTIONSTATUS="3"
							echo "${VPN3} is up, and fping success!" | tee -a "$log_file"
						else 
							echo "Unable to connect to ${VPN3}, trying ${VPN4} profile..." | tee -a "$log_file"
							echo "ifdown ${VPN3}" | tee -a "$log_file"
							ifdown $VPN3 | tee -a "$log_file"
							sleep $WAITFOR
							echo "ifup ${VPN4}" | tee -a "$log_file"
							ifup $VPN4 | tee -a "$log_file"
							sleep 7
							network_flush_cache
							network_find_wan NET_IF_NAME
							if [[ "$NET_IF_NAME" == "$VPN4" ]] && check_fping ; then
								ACTIVEVPN=$VPN4
								CONNECTIONSTATUS="3"
								echo "${VPN4} is up and fping success!" | tee -a "$log_file"
							else
								echo "Unable to start VPN4, or connect to any previous VPN1-3 profiles on $(date)" | tee -a "$log_file"
							fi # Finished VPN4 connect/all VPNx attempts
						fi # Finished VPN3 connect attempt
					fi # Finished VPN2 connect attempt
				fi # Finished VPN1 connect attempt
			else # something is wrong with the basic configuration of the script / interfaces mismatch
				echo "Whoops. Something went wrong!" | tee -a "$log_file"
				echo "Most likely this scripts VPN/WWAN variables are not matched to your router's interface names." | tee -a "$log_file"
				echo "Check that your script VPN/WWAN values match the actual interfaces. Exiting..." | tee -a "$log_file"
				break
			fi # Finished check if WWAN or any VPN connection is connected, and establishing required VPN connection if not active already.
		# Else if VPN Selector is set to 0 / no VPN, then
		elif [[ $VPN -eq 0 ]] ; then
			echo "VPN=${VPN}. VPN Selector is turned-off."
			echo "Checking to see if the current NET_IF_NAME or NET6_IF_NAME matches WWANNAME:"
			network_flush_cache
			network_find_wan NET_IF_NAME
			network_find_wan6 NET6_IF_NAME
			echo "Current values:"
			echo "NET_IF_NAME: ${NET_IF_NAME}"
			echo "NET6_IF_NAME: ${NET6_IF_NAME}"
			echo "WWANNAME interface: ${WWANNAME}"
			echo "WWANDEVICE device: ${WWANDEVICE}"
			if [[ "$WWANNAME" == "$NET_IF_NAME" || "$WWANNAME" == "$NET6_IF_NAME" ]] ; then
				echo "They do and VPN is selected OFF. Do nothing here/continue."
			else
				echo "The NET_IF_NAME or NET6_IF_NAME do not match the WWANNAME" | tee -a "$log_file"
				echo "Turning off VPN1-4 interfaces (ifdown), e.g. a VPN was started manually prior to this script run, but VPN selector is OFF." | tee -a "$log_file"
				# VPN may have been manually started prior to current script invocation.
				ifdown $VPN1
				ifdown $VPN2
				ifdown $VPN3
				ifdown $VPN4
				VPNCHANGED="1"
			fi			
			echo "No VPN active: Setting 'CONNECTIONSTATUS' = 2"
			CONNECTIONSTATUS="2"
			sleep $WAITFOR
		fi # finished for VPN 1/0 Selector check, establishing VPN connection, determining if active VPN has changed from prior VPN interface.
 
	else # No connectivity: initial or subsequent fping test, whether through VPN or without. These responses should always be logged.
		echo | tee -a "$log_file"
		echo "$(date) ALERT: No WAN or VPN connectivity: Fpings failed." | tee -a "$log_file" # Log when no connectivity.
		echo "Querying via uqmi:" | tee -a "$log_file"
		echo "Sending: uqmi -d ${MODEMUSBDEVNODE} --get-device-operating-mode..." | tee -a "$log_file"
		uqmi -d ${MODEMUSBDEVNODE} --get-device-operating-mode | tee -a "$log_file" # Log what uqmi cli *thinks* the modem status is (for reference).
		echo "Note! An 'online' response does not actually mean the modem is working. Typically, they crash-out in the 'online' state" | tee -a "$log_file"
		echo "but there is no actual connectivity." | tee -a "$log_file"
		if [[ $VPN -eq 1 ]] ; then # If VPN is selected, and basic connectivity is failing,
			echo "VPN Selector is ON, and yet WWAN/VPN is failing at initial fping test." | tee -a "$log_file"
			echo "This would ordinarily be the case, e.g. if you forcibly stopped your WWAN prior to running this script, to test." | tee -a "$log_file"
		elif [[ $VPN -eq 0 ]] ; then
			echo "VPN Selector is turned-off in this script and yet there is no WAN connectivity." | tee -a "$log_file"
		fi
		echo "Script will now attempt to (re)connect cell link, according to the appropriate ${MODEMTYPE} procedure..." | tee -a "$log_file"
		CONNECTIONSTATUS="1" # reset to unknown
		echo "Attempting to fix connectivity by running modem_reset function..." | tee -a "$log_file"
		echo "Shutting down VPN interfaces, in case they have been manually actived..."
		ifdown $VPN1
		ifdown $VPN2
		ifdown $VPN3
		ifdown $VPN4
		ACTIVEVPN="none"
		sleep $WAITFOR
		modem_reset # Modem reset function defined above.
		if check_fping ; then
			CONNECTIONSTATUS="2"
			echo "SUCCESS: ${MODEMTYPE} restart procedure worked, no need for reboot." | tee -a "$log_file"
			sleep $WAITFOR
			NEEDREBOOT="0"
		else
			echo "FAILURE: reboot required." | tee -a "$log_file"
			CONNECTIONSTATUS="0"
			NEEDREBOOT="1"
		fi # end of follow-up probe to see if WWAN interface restart worked...
	fi # End of initial fping test
 
 
	# Now let's start or restart, as needed, cake-autorate to run:
	# probably should check cake-autorate status and correct if necessary, here
	# Check if cake-autorate is running, and what it is currently using for it's 'ul_if' value:
	echo
	echo "CAKE-AUTORATE check / DOYOULIKECAKE & SQM portion of wan-watchdog.sh:"
	echo
	echo "Detecting interface/SQM/Cake-Autorate status & preferences:"
	echo "CONNECTIONSTATUS is ${CONNECTIONSTATUS}."
	echo "'service cake-autorate status' output:"
	service cake-autorate status
	echo "'service sqm status' output:"
	service sqm status
	echo "If sqm status is 'inactive', SQM is not running."
	echo "If sqm status 'active with no instances', SQM service is running."
	echo
	echo "VPNCHANGED/RELOADCAKE is ${VPNCHANGED}."
	echo "DOYOULIKECAKE is ${DOYOULIKECAKE}."
	sleep $WAITFOR
	echo
 
	# if the VPN is selected (1), enabled (on) and running (working) (therefore connectionstatus=3), VPN has not changed from prior, and cake-autorate/sqm are already running:
	# The most typical state when the VPN is selected, and script is running in a loop the second, third etc times.
	if [[ "$CONNECTIONSTATUS" == "3" ]] && [[ "$(service cake-autorate status)" == "running" ]] && [[ "$(service sqm status)" == "active with no instances" ]] && [[ "$DOYOULIKECAKE" == "1" ]] && [[ "$VPNCHANGED" == "0" ]] ; then 
		echo "Connection Status is 3: VPN ON & active. SQM is set ON and running, Cake-Autorate is set ON and running. VPN has NOT changed: Do nothing."
	# else if all of the above is true but the VPN has changed, and cake-autorate/sqm are already running, restart them:
	elif [[ "$CONNECTIONSTATUS" == "3" ]] && [[ "$(service cake-autorate status)" == "running" ]] && [[ "$(service sqm status)" == "active with no instances" ]] && [[ "$DOYOULIKECAKE" == "1" ]] && [[ "$VPNCHANGED" == "1" ]] ; then
		echo "Connection Status is 3: VPN ON & Active. SQM is set to ON and running, Cake-Autorate is set ON and running. But VPN has changed: restart SQM/Cake-autorate" | tee -a "$log_file"
		echo "Stopping cake-autorate..."
		echo "service cake-autorate stop"
		service cake-autorate stop
		sleep $WAITFOR
		echo "(Re)starting SQM to verify it's running the correct profile on the correct interface:"
		echo "service sqm stop"
		service sqm stop
		sleep $WAITFOR
		echo "service sqm start"
		service sqm start
		sleep $WAITFOR
		# Config.primary.sh in /root/cake-autorate/ will pull the current VPN connection /tmp/currentvpn.txt, and operate on that.
		echo "service cake-autorate start"
		service cake-autorate start
		VPNCHANGED="0"
		sleep $WAITFOR
	# else if the VPN has not changed, the VPN is active, but cake-autorate is not yet running (typically due to set to 'disabled' (from autostart) in System, Startup), then start it:
	elif [[ "$CONNECTIONSTATUS" == "3" ]] && [[ "$(service cake-autorate status)" == "not running" || "$(service cake-autorate status)" == "inactive" ]] && [[ "$DOYOULIKECAKE" == "1" ]] ; then
		echo "CONNECTIONSTATUS is 3: VPN is ON & active, Cake-autorate is NOT running, yet cake-autorate selector is ON. Starting Cake-Autorate..."
		if [[ "$(service sqm status)" == "active with no instances" ]] ; then # Probing if SQM is running first, instead of just doing a 'service sqm restart', avoids the alarming but normal 'Command failed: Not found' error.
			echo "SQM is running. Stopping then starting to make sure its on the active interface..."
			echo "service sqm stop"
			service sqm stop
			sleep $WAITFOR
		fi
		echo "service sqm start"
		service sqm start
		sleep $WAITFOR
		echo "service cake-autorate start"
		# Config.primary.sh in /root/cake-autorate/ will pull the current VPN connection /tmp/currentvpn.txt, and operate on that.
		service cake-autorate start
		# Initialize VPNCHANGED to 0, because regardless of whether it changed, cake-autorate was not running
		# This can also be the situation during testing sometimes, that VPNCHANGED=1 due to subsequent manual runs of the script.
		VPNCHANGED="0"
		sleep $WAITFOR
	# atypical: if script was halted/killed manually, a VPN was on, and VPN was changed to '0', then script was re-executed:
	elif [[ "$CONNECTIONSTATUS" == "2" ]] && [[ "$(service cake-autorate status)" == "running" ]] && [[ "$(service sqm status)" == "active with no instances" ]] && [[ "$DOYOULIKECAKE" == "1" ]] && [[ "$VPN" == "0" ]] && [[ "$VPNCHANGED" == "1" ]] ; then 
		echo "VPN active, but VPN selected OFF in script. Restarting SQM/Cake-Autorate to set to ${WWANDEVICE} / ${WWANNAME}:"
		echo "Connection Status is 2, SQM is running, Cake is running, Cake selector is enabled, but this script was probably killed, then re-run while there was a (previous) VPN running:"
		echo "Stopping cake-autorate, restart sqm, restart cake-autorate."
		echo "service cake-autorate stop"
		service cake-autorate stop
		echo "Restarting SQM to get SQM running on the current active WWAN interface..."
		echo "service sqm restart"
		service sqm restart
		sleep $WAITFOR
		echo "Writing WWANDEVICE to /tmp/currentvpn.txt for Cake-Autorate config.primary.sh to pick-up active interface"
		echo "${WWANDEVICE}" > /tmp/currentvpn.txt # write the current WWAN device (not interface!) to a file, to be used by the cake autorate script.
		sleep $WAITFOR
		echo "service cake-autorate start"
		# Config.primary.sh in /root/cake-autorate/ will pull the current VPN connection /tmp/currentvpn.txt, and operate on that.
		service cake-autorate start
		# Initialize VPNCHANGED to 0
		VPNCHANGED="0"
		sleep $WAITFOR
	# typical when script running in second, third etc loop and VPN selected OFF
	# else if VPN is selected OFF (0), Connection = 2 (good connection, no vpn), and cake selector is ON, and running, and SQM is running, do nothing:
	elif [[ "$CONNECTIONSTATUS" == "2" ]] && [[ "$(service cake-autorate status)" == "running" ]] && [[ "$(service sqm status)" == "active with no instances" ]] && [[ "$DOYOULIKECAKE" == "1" ]] && [[ "$VPN" == "0" ]] && [[ "$VPNCHANGED" == "0" ]] ; then 
		echo "Connection Status is 2: VPN is not active, VPN selected OFF, DOYOULIKECAKE is ON, CAKE and SQM running.. No change in interfaces: Do Nothing."
	# typical on firstboot with VPN off:
	# else if VPN is selected OFF (0), connection = 2 (good connection), and cake selector is ON, but not yet running:
	elif [[ "$CONNECTIONSTATUS" == "2" ]] && [[ "$(service cake-autorate status)" == "not running" || "$(service cake-autorate status)" == "inactive" ]] && [[ "$DOYOULIKECAKE" == "1" ]] && [[ "$VPN" == "0" ]] ; then 
		echo "Connection status is 2: No VPN Active, VPN is selected OFF, Cake is NOT running, and cake-autorate selector is ON:"
		echo "(Re)starting SQM to verify that SQM is running on the current active WWAN interface..."
		if [[ "$(service sqm status)" == "active with no instances" ]] ; then # Probing if SQM is running first, instead of just doing a 'service sqm restart', avoids the alarming but normal 'Command failed: Not found' error.
			echo "SQM is running. Stopping..."
			echo "service sqm stop"
			service sqm stop
			sleep $WAITFOR
		fi
		echo "service sqm start"
		service sqm start
		sleep $WAITFOR
		echo "service cake-autorate start"
		echo "$WWANDEVICE" > /tmp/currentvpn.txt # write the current WWAN device (not interface!) to a file, to be used by the cake autorate script.
		sleep $WAITFOR
		# Config.primary.sh in /root/cake-autorate/ will pull the current VPN connection /tmp/currentvpn.txt, and operate on that.
		service cake-autorate start
		sleep $WAITFOR
	# typical when script has been stopped, CAKE selector has been turned off, and script is re-run
	# else if cake-autorate is running, but cake selector is set to OFF, then shut down cake-autorate:
	elif [[ "$CONNECTIONSTATUS" = "2" || "$CONNECTIONSTATUS" == "3" ]] && [[ "$(service cake-autorate status)" == "running" ]] && [[ "$DOYOULIKECAKE" == "0" ]] ; then
		echo "Connection Status is ${CONNECTIONSTATUS}, Cake-autorate is running, but is selected OFF. Stopping cake-autorate:"
		echo "service cake-autorate stop"
		service cake-autorate stop
		# This can be the case during manually running the script, or if you decided not to use cake-autorate for now, but forgot to disable it from startup
		# or it had been started previously but you don't want it running rn.
		sleep $WAITFOR
		# And if SQM is running, but FEELINGSQMISH SQM selector is also set to OFF, then also shut down SQM:
		if [[ "$(service sqm status)" == "active with no instances" ]] && [[ "$FEELINGSQMISH" == "0" ]] ; then
			echo "SQM is also turned-off, but SQM is running. Stopping SQM:"
			echo "Stopping SQM..."
			echo "service sqm stop"
			service sqm stop
			# This can be the case during manually running the script, or if you decided not to use SQM for now, but forgot to disable it from startup
			# or it had been started previously but you don't want it running rn.
			sleep $WAITFOR
		elif [[ "$(service sqm status)" == "active with no instances" ]] && [[ "$FEELINGSQMISH" == "1" ]] ; then
			echo "SQM is selected ON, and is running. Do nothing"
			sleep $WAITFOR
		fi
	# else if Connection is good, Cake is selected OFF, is not running, do nothing
	elif [[ "$CONNECTIONSTATUS" = "2" || "$CONNECTIONSTATUS" == "3" ]] && [[ "$(service cake-autorate status)" == "not running" || "$(service cake-autorate status)" == "inactive" ]] && [[ "$DOYOULIKECAKE" == "0" ]] ; then
		echo "CONNECTIONSTATUS is ${CONNECTIONSTATUS}. DOYOULIKECAKE is selected OFF and Cake-Autorate is not running. Do nothing."
		sleep $WAITFOR
		# and test SQM within that:
		if [[ "$(service sqm status)" == "inactive" ]] && [[ "$FEELINGSQMISH" == "0" ]] ; then
			echo "and SQM selector is also turned OFF and SQM not running. Do nothing."
			sleep $WAITFOR
		elif [[ "$(service sqm status)" == "inactive" ]] && [[ "$FEELINGSQMISH" == "1" ]] ; then
			echo "and SQM is selected ON, but not running. Starting..."
			echo "service sqm start"
			service sqm start
			sleep $WAITFOR
		fi
	fi # Finished starting Cake-autorate for current connection, if selected, and SQM. Restarting services or terminating as necessary.
 
echo
# Pause for LOOPWAIT then re-test connection, or in testing-mode go directly to exit:
	if [ $TESTMODE -eq 0 ]
	then # now check to see if you should loop or break
		echo "Pausing ${LOOPWAIT} seconds and testing again..."			
		sleep $LOOPWAIT
	elif [ $TESTMODE -eq 1 ]
	then
		echo "Script is in Testing mode, as the BOOTWAIT value of ${BOOTWAIT} is too low. Exiting.."
		echo "Test run ended: $(date)"
		break
	fi
 
echo
done # When CONNECTION STATUS no longer 1 or greater, or break
 
if [[ $TESTMODE -eq 0 && $NEEDREBOOT -eq 1 ]]
	then
	echo "Preparing to reboot router:" | tee -a "$log_file"
	echo "Shutting down CAKE..." | tee -a "$log_file"
	service cake-autorate stop
	echo "Shutdown down SQM..." | tee -a "$log_file"
	service sqm stop
	echo "Rebooting router in 5 seconds..." | tee -a "$log_file"
	sleep 5
	reboot
elif [[ $TESTMODE -eq 1 && $NEEDREBOOT -eq 1 ]]
	then
	echo "Failed WWAN connectivity check. Reboot required, but script in testing mode... no auto-reboot. Exit."
fi

Dynamic connection

Preserve default route to restore WAN connectivity when VPN is disconnected.

# Preserve default route
uci set network.wan.metric="1024"
uci commit network
service network restart

Dynamic address

Periodically re-resolve inactive peer hostnames for VPN peers with dynamic IP addresses.

# Periodically re-resolve inactive peers
cat << "EOF" >> /etc/crontabs/root
* * * * * /usr/bin/wireguard_watchdog
EOF
uci set system.@system[0].cronloglevel="9"
uci commit system
service cron restart

Race conditions

Resolve the race condition with sysntpd service when RTC is missing.

# Resolve race conditions
cat << "EOF" >> /etc/crontabs/root
* * * * * date -s 2030-01-01; service sysntpd restart
EOF
uci set system.@system[0].cronloglevel="9"
uci commit system
service cron restart

Site-to-site

Implement plain routing between server side LAN and client side LAN assuming that:

Add route to client side LAN on VPN server.

uci set network.wgclient.route_allowed_ips="1"
uci add_list network.wgclient.allowed_ips="192.168.2.0/24"
uci commit network
service network restart

Add route to server side LAN on VPN client.

uci set network.wgserver.route_allowed_ips="1"
uci add_list network.wgserver.allowed_ips="192.168.1.0/24"
uci commit network
service network restart

Consider VPN network as private and assign VPN interface to LAN zone on VPN client.

uci del_list firewall.wan.network="vpn"
uci add_list firewall.lan.network="vpn"
uci commit firewall
service firewall restart

IPv6 site-to-site

Provide IPv6 site-to-site connectivity assuming that:

Add route to client side LAN on VPN server.

uci set network.lan.ip6assign="64"
uci set network.lan.ip6hint="1"
uci set network.vpn.ip6prefix="fd00::/48"
uci add_list network.wgclient.allowed_ips="fd00:0:0:2::/64"
uci commit network
service network restart

Add route to server side LAN on VPN client.

uci set network.lan.ip6assign="64"
uci set network.lan.ip6hint="2"
uci set network.vpn.ip6prefix="fd00::/48"
uci add_list network.wgserver.allowed_ips="fd00:0:0:1::/64"
uci commit network
service network restart

Default gateway

If you do not need to route all traffic to VPN. Disable gateway redirection on VPN client.

uci del_list network.wgserver.allowed_ips="0.0.0.0/0"
uci del_list network.wgserver.allowed_ips="::/0"
uci commit network
service network restart

If you want to disable automatic routes for allowed IPs.

uci -q delete network.wgserver.route_allowed_ips
uci commit network
service network restart

Split gateway

If VPN gateway is separate from your LAN gateway. Implement plain routing between LAN and VPN networks assuming that:

Add port forwarding for VPN server on LAN gateway.

uci -q delete firewall.wg
uci set firewall.wg="redirect"
uci set firewall.wg.name="Redirect-WireGuard"
uci set firewall.wg.src="wan"
uci set firewall.wg.src_dport="51820"
uci set firewall.wg.dest="lan"
uci set firewall.wg.dest_ip="192.168.1.2"
uci set firewall.wg.family="ipv4"
uci set firewall.wg.proto="udp"
uci set firewall.wg.target="DNAT"
uci commit firewall
service firewall restart

Add route to VPN network via VPN gateway on LAN gateway.

uci -q delete network.vpn
uci set network.vpn="route"
uci set network.vpn.interface="lan"
uci set network.vpn.target="192.168.9.0/24"
uci set network.vpn.gateway="192.168.1.2"
uci commit network
service network restart

IPv6 gateway

Set up IPv6 tunnel broker or use IPv6 NAT or NPT if necessary.

Disable ISP prefix delegation to prevent IPv6 leaks on VPN client.

DNS over VPN

Serve DNS for VPN clients on OpenWrt server when using point-to-point topology.

Route DNS over VPN to prevent DNS leaks on VPN client.

Replace peer DNS with public or VPN-specific DNS provider on OpenWrt client.

Modify the VPN connection using NetworkManager on Linux desktop client.

nmcli connection modify id VPN_CON \
ipv4.dns-search ~. ipv4.dns-priority -50 \
ipv6.dns-search ~. ipv6.dns-priority -50

Kill switch

Prevent traffic leaks on OpenWrt client isolating VPN interface in a separate firewall zone.

uci -q delete firewall.vpn
uci set firewall.vpn="zone"
uci set firewall.vpn.name="vpn"
uci set firewall.vpn.input="REJECT"
uci set firewall.vpn.output="ACCEPT"
uci set firewall.vpn.forward="REJECT"
uci set firewall.vpn.masq="1"
uci set firewall.vpn.mtu_fix="1"
uci add_list firewall.vpn.network="vpn"
uci del_list firewall.wan.network="vpn"
uci -q delete firewall.@forwarding[0]
uci set firewall.lan_vpn="forwarding"
uci set firewall.lan_vpn.src="lan"
uci set firewall.lan_vpn.dest="vpn"
uci commit firewall
service firewall restart

Multi-client

Set up multi-client VPN server. Generate client keys and profiles. Configure VPN peers.

# Configuration parameters
VPN_IDS="wgserver wgclient wglaptop wgmobile"
VPN_PKI="."
VPN_IF="vpn"
VPN_PORT="$(uci -q get network.${VPN_IF}.listen_port)"
read -r VPN_ADDR VPN_ADDR6 \
< <(uci -q get network.${VPN_IF}.addresses)
 
# Fetch server address
NET_FQDN="$(uci -q get ddns.@service[0].lookup_host)"
. /lib/functions/network.sh
network_flush_cache
network_find_wan NET_IF
network_get_ipaddr NET_ADDR "${NET_IF}"
if [ -n "${NET_FQDN}" ]
then VPN_SERV="${NET_FQDN}"
else VPN_SERV="${NET_ADDR}"
fi
 
# Generate client keys
umask go=
mkdir -p ${VPN_PKI}
for VPN_ID in ${VPN_IDS#* }
do
wg genkey \
| tee ${VPN_PKI}/${VPN_ID}.key \
| wg pubkey > ${VPN_PKI}/${VPN_ID}.pub
wg genpsk > ${VPN_PKI}/${VPN_ID}.psk
done
 
# Generate client profiles
VPN_SFX="1"
for VPN_ID in ${VPN_IDS#* }
do
let VPN_SFX++
cat << EOF > ${VPN_PKI}/${VPN_ID}.conf
[Interface]
PrivateKey = $(cat ${VPN_PKI}/${VPN_ID}.key)
Address = ${VPN_ADDR%.*}.${VPN_SFX}/24
Address = ${VPN_ADDR6%:*}:${VPN_SFX}/64
DNS = ${VPN_ADDR%/*}
DNS = ${VPN_ADDR6%/*}
[Peer]
PublicKey = $(cat ${VPN_PKI}/${VPN_IDS%% *}.pub)
PresharedKey = $(cat ${VPN_PKI}/${VPN_ID}.psk)
PersistentKeepalive = 25
Endpoint = ${VPN_SERV}:${VPN_PORT}
AllowedIPs = 0.0.0.0/0
AllowedIPs = ::/0
EOF
done
ls ${VPN_PKI}/*.conf
 
# Back up client profiles
cat << EOF >> /etc/sysupgrade.conf
$(pwd ${VPN_PKI})
EOF
 
# Add VPN peers
VPN_SFX="1"
for VPN_ID in ${VPN_IDS#* }
do
let VPN_SFX++
uci -q delete network.${VPN_ID}
uci set network.${VPN_ID}="wireguard_${VPN_IF}"
uci set network.${VPN_ID}.description="${VPN_ID}"
uci set network.${VPN_ID}.private_key="$(cat ${VPN_PKI}/${VPN_ID}.key)"
uci set network.${VPN_ID}.public_key="$(cat ${VPN_PKI}/${VPN_ID}.pub)"
uci set network.${VPN_ID}.preshared_key="$(cat ${VPN_PKI}/${VPN_ID}.psk)"
uci add_list network.${VPN_ID}.allowed_ips="${VPN_ADDR%.*}.${VPN_SFX}/32"
uci add_list network.${VPN_ID}.allowed_ips="${VPN_ADDR6%:*}:${VPN_SFX}/128"
done
uci commit network
service network restart

Perform OpenWrt backup. Extract client profiles from the archive and import them to your clients.

Automated

Automated VPN server installation and client profiles generation.

URL="https://openwrt.org/_export/code/docs/guide-user/services/vpn/wireguard/server"
cat << EOF > wireguard-server.sh
$(wget -U "" -O - "${URL}?codeblock=0")
$(wget -U "" -O - "${URL}?codeblock=1")
$(wget -U "" -O - "${URL}?codeblock=2")
$(wget -U "" -O - "${URL}?codeblock=3")
$(wget -U "" -O - "${URL}/../extras?codeblock=15")
EOF
sh wireguard-server.sh