#!/bin/sh /etc/rc.common
# Copyright (C) 2018-2026 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
# Released under GPL 3. See LICENSE for the full terms.

START=85

USE_PROCD=1

EXTRA_COMMANDS="set_pihole backup_send backup_get backup_list set_vps_firewall get_openvpn_key set_gre_tunnel set_vxlan token set_bypass_ips set_vpn_ip set_mptcp_dscp_vps set_mptcp_weight_vps set_dscp_classify_vps"

. /usr/lib/unbound/iptools.sh

if [ -e /usr/sbin/iptables-nft ]; then
	IPTABLES="/usr/sbin/iptables-nft"
	IPTABLESRESTORE="/usr/sbin/iptables-nft-restore"
	IPTABLESSAVE="/usr/sbin/iptables-nft-save"
	IP6TABLES="/usr/sbin/ip6tables-nft"
	IP6TABLESRESTORE="/usr/sbin/ip6tables-nft-restore"
	IP6TABLESSAVE="/usr/sbin/ip6tables-nft-save"
else
	IPTABLES="/usr/sbin/iptables"
	IPTABLESRESTORE="/usr/sbin/iptables-restore"
	IPTABLESSAVE="/usr/sbin/iptables-save"
	IP6TABLES="/usr/sbin/ip6tables"
	IP6TABLESRESTORE="/usr/sbin/ip6tables-restore"
	IP6TABLESSAVE="/usr/sbin/ip6tables-save"
fi

# Overridable so the test suite can point this at a mock instead of shelling
# out to the real init script
FIREWALL_INIT="${FIREWALL_INIT:-/etc/init.d/firewall}"


_parse_result() {
	result=$(echo "$1" | jsonfilter -q -e '@.result')
	echo $result
}

_login() {
	local username password auth
	#server="$(uci -q get openmptcprouter.${servername}.ip)"
	#[ -z "$server" ] && server="$(uci -q get shadowsocks-libev.sss0.server)"
	username="$(uci -q get openmptcprouter.${servername}.username)"
	password="$(uci -q get openmptcprouter.${servername}.password)"
	serverport="$(uci -q get openmptcprouter.${servername}.port)"
	#[ -z "$server" ] && server="$(uci -q get openmptcprouter.${servername}.ip)"
	if [ -z "$token" ]; then
		login_on_server() {
			[ -n "$token" ] && return
			server=$1
			#auth=`curl --max-time 10 -s -k -H "Content-Type: application/json" -X POST -d '{"username":"'$username'","password":"'$password'"}' https://$server:$serverport/login`
			#resolve="$(resolveip -t 5 $server)"
			valid_ip6=$(valid_subnet6 $server)
			if [ "$valid_ip6" = "ok" ]; then
				auth=`curl -6 --max-time 10 -s -k -H "accept: application/json" -H "Content-Type: application/x-www-form-urlencoded" -X POST -d 'username='$username'&password='$password https://[$server]:$serverport/token`
			else
				auth=`curl --max-time 10 -s -k -H "accept: application/json" -H "Content-Type: application/x-www-form-urlencoded" -X POST -d 'username='$username'&password='$password https://$server:$serverport/token`
			fi
			if [ -z "$auth" ]; then
				for intf in $(multipath 2>/dev/null | awk '/default/ { print $1 }'); do
					if [ "$valid_ip6" = "ok" ]; then
						auth=`curl -6 --max-time 10 -s -k --interface $intf -H "accept: application/json" -H "Content-Type: application/x-www-form-urlencoded" -X POST -d 'username='$username'&password='$password https://[$server]:$serverport/token`
					else
						auth=`curl --max-time 10 -s -k --interface $intf -H "accept: application/json" -H "Content-Type: application/x-www-form-urlencoded" -X POST -d 'username='$username'&password='$password https://$server:$serverport/token`
					fi
					[ -n "$auth" ] && break
				done
			fi
			[ -n "$auth" ] && {
				token="$(echo "$auth" | jsonfilter -q -e '@.access_token')"
				uci -q set openmptcprouter.${servername}.token="$token"
			}
		}
		config_load openmptcprouter
		config_list_foreach ${servername} ip login_on_server
	fi
}

_ping_server() {
	server="$1"
	ret=$(ping -c 3 -w 3 -Q 184 $server 2>&1) && echo "$ret" | grep -sq "bytes from" && return
	false
}

_get_json() {
	local route result
	route=$1
	[ -z "$token" ] && _login
	[ -n "$token" ] && {
		#resolve="$(resolveip -t 5 $server)"
		valid_ip6=$(valid_subnet6 $server)
		if [ "$valid_ip6" = "ok" ]; then
			result=`curl -6 --max-time 10 -s -k -H "accept: application/json" -H "Authorization: Bearer $token" https://[$server]:$serverport/$route`
		else
			result=`curl --max-time 10 -s -k -H "accept: application/json" -H "Authorization: Bearer $token" https://$server:$serverport/$route`
		fi
		if [ -z "$result" ]; then
			for intf in $(multipath 2>/dev/null | awk '/default/ { print $1 }'); do
				if [ "$valid_ip6" = "ok" ]; then
					result=`curl -6 --max-time 10 -s -k --interface $intf -H "accept: application/json" -H "Authorization: Bearer $token" https://[$server]:$serverport/$route`
				else
					result=`curl --max-time 10 -s -k --interface $intf -H "accept: application/json" -H "Authorization: Bearer $token" https://$server:$serverport/$route`
				fi
				[ -n "$result" ] && break
			done
		fi
		if [ "$(echo $result | grep 'Could not validate credentials')" ]; then
			token=''
			_login
			if [ "$valid_ip6" = "ok" ]; then
				result=`curl -6 --max-time 10 -s -k -H "accept: application/json" -H "Authorization: Bearer $token" https://[$server]:$serverport/$route`
			else
				result=`curl --max-time 10 -s -k -H "accept: application/json" -H "Authorization: Bearer $token" https://$server:$serverport/$route`
			fi
			[ "$(echo $result | grep 'Could not validate credentials')" ] && result=''
		fi
		echo $result
	} || {
		echo ''
	}
}

_set_json() {
	local route result settings
	route=$1
	settings="$2"
	[ -z "$token" ] && _login
	[ -n "$token" ] && {
		#resolve="$(resolveip -t 5 $server)"
		valid_ip6=$(valid_subnet6 $server)
		if [ "$valid_ip6" = "ok" ]; then
			result=`curl -6 --max-time 10 -s -k -H "Authorization: Bearer $token" -H "Content-Type: application/json" -X POST -d "$settings" https://[$server]:$serverport/$route`
		else
			result=`curl --max-time 10 -s -k -H "Authorization: Bearer $token" -H "Content-Type: application/json" -X POST -d "$settings" https://$server:$serverport/$route`
		fi
		if [ -z "$result" ]; then
			for intf in $(multipath 2>/dev/null | awk '/default/ { print $1 }'); do
				if [ "$valid_ip6" = "ok" ]; then
					result=`curl -6 --max-time 10 -s -k --interface $intf -H "Authorization: Bearer $token" -H "Content-Type: application/json" -X POST -d "$settings" https://[$server]:$serverport/$route`
				else
					result=`curl --max-time 10 -s -k --interface $intf -H "Authorization: Bearer $token" -H "Content-Type: application/json" -X POST -d "$settings" https://$server:$serverport/$route`
				fi
				[ -n "$result" ] && break
			done
		fi
		if [ "$(echo $result | grep 'Could not validate credentials')" ]; then
			token=''
			_login
			if [ "$valid_ip6" = "ok" ]; then
				result=`curl -6 --max-time 10 -s -k -H "Authorization: Bearer $token" -H "Content-Type: application/json" -X POST -d "$settings" https://[$server]:$serverport/$route`
			else
				result=`curl --max-time 10 -s -k -H "Authorization: Bearer $token" -H "Content-Type: application/json" -X POST -d "$settings" https://$server:$serverport/$route`
			fi
			[ "$(echo $result | grep 'Could not validate credentials')" ] && result=''
		fi
		echo $result
	} || {
		echo ''
	}
}

_set_glorytun_vps() {
	local enabled port key
	enabled="$(uci -q get glorytun.vpn.enable)"
	[ "$enabled" != "1" ] && enabled="$(uci -q get glorytun-udp.vpn.enable)"
	[ "$enabled" != "1" ] && echo "Glorytun disabled" && return
	port="$(uci -q get glorytun.vpn.port)"
	key="$(uci -q get glorytun.vpn.key)"
	chacha="$(uci -q get glorytun.vpn.chacha20)"
	if [ "$chacha" = "1" ]; then
		chacha="true"
	else
		chacha="false"
	fi
	[ -z "$key" ] && echo "Glorytun key not set" && return
	local current_port current_key current_chacha
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	current_port="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.port')"
	current_key="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.key')"
	current_chacha="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.chacha')"
	if [ "$current_port" != "$port" ] || [ "$current_key" != "$key" ] || [ "$current_chacha" != "$chacha" ]; then
		local settings
		settings='{"port": '$port',"key":"'$key'", "chacha": "'$chacha'"}'
		echo $(_set_json "glorytun" "$settings")
	else
		echo 1
	fi
}

_set_openvpn_vps() {
	local enabled port key
	enabled="$(uci -q get openvpn.omr.enabled)"
	[ "$enabled" != "1" ] && echo "OpenVPN disabled" && return
	port="$(uci -q get openvpn.omr.port)"
	cipher="$(uci -q get openvpn.omr.cipher)"
	key="$(base64 /etc/luci-uploads/client.key | tr -d "\n")"
	local current_port current_cipher current_key
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	current_key="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.client_key')"
	current_port="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.port')"
	current_cipher="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.cipher')"
	if [ "$current_key" != "$key" ]; then
		uci -q set openmptcprouter.${servername}.get_config="1"
	fi
	if [ "$current_port" != "$port" ] || [ "$current_cipher" != "$cipher" ]; then
		local settings
		settings='{"port": '$port', "cipher": "'$cipher'"}'
		echo $(_set_json "openvpn" "$settings")
	else
		echo 1
	fi
}

_set_softethervpn_vps() {
	local enabled port key
	enabled="$(uci -q get softethervpn.openmptcprouter.enable)"
	[ "$enabled" != "1" ] && echo "SoftEtherVPN disabled" && return
	#port="$(uci -q get softethervpn.openmptcprouter.port)"
	cipher="$(uci -q get softethervpn.openmptcprouter.encryption)"
	password="$(uci -q get softethervpn.openmptcprouter.password)"
	local current_port current_cipher
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	#current_port="$(echo "$vps_config" | jsonfilter -q -e '@.softethervpn.port')"
	current_cipher="$(echo "$vps_config" | jsonfilter -q -e '@.softethervpn.cipher')"
	current_password="$(echo "$vps_config" | jsonfilter -q -e '@.softethervpn.password')"
	if [ "$current_password" != "$password" ]; then
		uci -q set openmptcprouter.${servername}.get_config="1"
	fi
	#if [ "$current_port" != "$port" ] || [ "$current_cipher" != "$cipher" ]; then
	if [ "$current_cipher" != "$cipher" ]; then
		local settings
		#settings='{"port": '$port', "cipher": "'$cipher'"}'
		settings='{"cipher": "'$cipher'", "password": "'$password'"}'
		echo $(_set_json "softethervpn" "$settings")
	else
		echo 1
	fi
}

_set_mlvpn_vps() {
	local enabled port key
	enabled="$(uci -q get mlvpn.general.enable)"
	[ "$enabled" != "1" ] && echo "MLVPN disabled" && return
	timeout="$(uci -q get mlvpn.general.timeout)"
	reorder_buffer_size="$(uci -q get mlvpn.general.reorder_buffer_size)"
	loss_tolerence="$(uci -q get mlvpn.general.loss_tolerence)"
	password="$(uci -q get mlvpn.general.password)"
	cleartext_data="$(uci -q get mlvpn.general.cleartext_data)"
	local current_timeout current_reorder_buffer_size current_loss_tolerence current_password current_cleartext_data
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	current_timeout="$(echo "$vps_config" | jsonfilter -q -e '@.mlvpn.timeout')"
	current_reorder_buffer_size="$(echo "$vps_config" | jsonfilter -q -e '@.mlvpn.reorder_buffer_size')"
	current_loss_tolerence="$(echo "$vps_config" | jsonfilter -q -e '@.mlvpn.loss_tolerence')"
	current_password="$(echo "$vps_config" | jsonfilter -q -e '@.mlvpn.key')"
	current_cleartext_data="$(echo "$vps_config" | jsonfilter -q -e '@.mlvpn.cleartext_data')"
	if [ "$current_timeout" != "$timeout" ] || [ "$current_reorder_buffer_size" != "$reorder_buffer_size" ] || [ "$current_loss_tolerence" != "$loss_tolerence" ] || [ "$current_password" != "$password" ] || [ "$current_cleartext_data" != "$cleartext_data" ]; then
		local settings
		settings='{"timeout": '$timeout', "reorder_buffer_size": "'$reorder_buffer_size'", "loss_tolerence": "'$loss_tolerence'", "password": "'$password'", "cleartext_data": "'$cleartext_data'"}'
		echo $(_set_json "mlvpn" "$settings")
	else
		echo 1
	fi
}

_set_mqvpn_vps() {
	local enabled port key scheduler cc fec_enable fec_scheme reinjection_control reinjection_mode
	local current_port
	enabled="$(uci -q get mqvpn.settings.enable)"
	[ "$enabled" != "1" ] && echo "MQVPN disabled" && return
	# mqvpn.server.port is the user intent: push it to the server when it differs
	port="$(uci -q get mqvpn.server.port)"
	[ -z "$port" ] && echo 1 && return
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	current_port="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.port')"
	[ -z "$current_port" ] && echo 1 && return
	key="$(uci -q get mqvpn.auth.key)"
	[ -z "$key" ] && echo "MQVPN key not set" && return
	if [ "$current_port" != "$port" ]; then
		scheduler="$(uci -q get mqvpn.multipath.scheduler)"
		[ -z "$scheduler" ] && scheduler="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.scheduler')"
		[ -z "$scheduler" ] && scheduler="wlb"
		cc="$(uci -q get mqvpn.multipath.cc)"
		[ -z "$cc" ] && cc="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.cc')"
		[ -z "$cc" ] && cc="bbr2"
		fec_enable="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.fec_enable')"
		[ -z "$fec_enable" ] && fec_enable="false"
		fec_scheme="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.fec_scheme')"
		[ -z "$fec_scheme" ] && fec_scheme="xor"
		reinjection_control="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.reinjection_control')"
		[ -z "$reinjection_control" ] && reinjection_control="false"
		reinjection_mode="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.reinjection_mode')"
		[ -z "$reinjection_mode" ] && reinjection_mode="default"
		local settings result
		settings='{"key": "'$key'", "port": '$port', "scheduler": "'$scheduler'", "cc": "'$cc'", "fec_enable": '$fec_enable', "fec_scheme": "'$fec_scheme'", "reinjection_control": '$reinjection_control', "reinjection_mode": "'$reinjection_mode'"}'
		result=$(_set_json "mqvpn" "$settings")
		[ -n "$result" ] && uci -q set openmptcprouter.${servername}.get_config="1"
		# The server moved its QUIC listener: restart the client so it
		# reconnects on the new port instead of retrying the stale session
		[ -n "$result" ] && /etc/init.d/mqvpn restart >/dev/null 2>&1
		echo $result
	else
		echo 1
	fi
}

_set_wireguard_vps() {
	local enabled port key
	ipskey=""
	_get_wg_ipskey() {
		local interface=$1
		proto=$(uci -q get network.${interface}.proto)
		if [ "$proto" = "wireguard" ]; then
			ip="$(uci -q get network.${interface}.addresses | cut -d/ -f1)"
			key="$(uci -q get network.${interface}.public_key)"
			if [ -z "$ipskey" ]; then
				ipskey='{"ip": "'$ip'", "key": "'$key'"}'
			else
				ipskey=$ipskey',{"ip": "'$ip'", "key": "'$key'"}'
			fi
		fi
	}
	config_load network
	config_foreach _get_wg_ipskey interface
	local settings
	if [ -n "$ipskey" ]; then
		settings='{"peers": ['$ipskey']}'
		echo $(_set_json "wireguard" "$settings")
	else
		echo 1
	fi
}

get_openvpn_key() {
	servername=$2
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	openvpn_client_key="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.client_key')"
	[ -n "$openvpn_client_key" ] && {
		echo $openvpn_client_key | base64 -d > /etc/luci-uploads/client.key
	}
	openvpn_client_crt="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.client_crt')"
	[ -n "$openvpn_client_crt" ] && {
		echo $openvpn_client_crt | base64 -d > /etc/luci-uploads/client.crt
	}
	openvpn_client_ca="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.client_ca')"
	[ -n "$openvpn_client_ca" ] && {
		echo $openvpn_client_ca | base64 -d > /etc/luci-uploads/ca.crt
	}
	[ -n "$openvpn_client_key" ] && [ -n "$openvpn_client_crt" ] && [ -n "$openvpn_client_ca" ] && {
		[ "$(uci -q get openvpn.omr.enabled)" ] && /etc/init.d/openvpn restart
	}
}

_get_ss_redir() {
	config_get cf_ebpf $1 ebpf
	[ "$cf_ebpf" = "1" ] && ebpf="true"
	config_get cf_fast_open $1 fast_open
	[ "$cf_fast_open" = "1" ] && fast_open="true"
	config_get cf_no_delay $1 no_delay
	[ "$cf_no_delay" = "1" ] && no_delay="true"
	config_get cf_mptcp $1 mptcp
	[ "$cf_mptcp" = "1" ] && mptcp="true"
}

_get_ss_server() {
	config_get cf_obfs $1 obfs
	[ "$cf_obfs" = "1" ] && obfs="true"
	config_get obfs_plugin $1 obfs_plugin "v2ray"
	config_get obfs_type $1 obfs_type "http"
}

_set_ss_server_vps() {
	local disabled port key method
	config_load shadowsocks-libev
	config_get disabled sss0 disabled
	[ "$disabled" = "1" ] && return
	config_get port sss0 server_port
	#config_get server $1 server
	config_get key sss0 key
	key="$(echo $key | sed 's/+/-/g; s/\//_/g;')"
	[ -z "$key" ] && return
	config_get method sss0 method
	local current_port current_key current_method
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	current_port="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.port')"
	current_key="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.key')"
	current_method="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.method')"
	current_ebpf="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.ebpf')"
	current_obfs="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.obfs')"
	current_obfs_plugin="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.obfs_plugin')"
	current_obfs_type="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.obfs_type')"
	current_fast_open="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.fast_open')"
	current_no_delay="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.no_delay')"
	current_mptcp="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.mptcp')"
	
	ebpf="false"
	fast_open="false"
	no_delay="false"
	mptcp="false"
	obfs="false"
	obfs_plugin="v2ray"
	obfs_type="http"
	config_load shadowsocks-libev
	config_foreach _get_ss_redir ss_redir
	config_foreach _get_ss_server server

	if [ "$current_mptcp" != "$mptcp" ] || [ "$current_obfs_plugin" != "$obfs_plugin" ] || [ "$current_obfs_type" != "$obfs_type" ] || [ "$current_port" != "$port" ] || [ "$current_method" != "$method" ] || [ "$current_key" != "$key" ] || [ "$current_ebpf" != "$ebpf" ] || [ "$current_obfs" != "$obfs" ] || [ "$current_fast_open" != "$fast_open" ] || [ "$current_no_delay" != "$no_delay" ]; then
		local settings
		settings='{"port": '$port',"method":"'$method'","fast_open":'$fast_open',"reuse_port":true,"no_delay":'$no_delay',"mptcp":'$mptcp',"key":"'$key'","ebpf":'$ebpf',"obfs":'$obfs',"obfs_plugin":"'$obfs_plugin'","obfs_type":"'$obfs_type'"}'
		result=$(_set_json "shadowsocks" "$settings")
	fi
}

_set_ssgo_server_vps() {
	local disabled port key method
	config_load shadowsocks-rust
	config_get disabled sss0 disabled
	[ "$disabled" = "1" ] && return
	config_get port sss0 server_port
	#config_get server $1 server
	config_get key sss0 key
	key="$(echo $key | sed 's/+/-/g; s/\//_/g;')"
	[ -z "$key" ] && return
	config_get method sss0 method
	local current_port current_key current_method
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	current_port="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks_go.config.port')"
	current_key="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks_go.config.password')"
	current_method="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks_go.config.protocol')"
#	current_fast_open="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks_go.config.fast_open')"
#	current_mptcp="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks_go.config.mptcp')"
	
	ebpf="false"
	fast_open="false"
	no_delay="false"
	mptcp="false"
	config_load shadowsocks-rust
	config_foreach _get_ss_redir ss_redir
	config_foreach _get_ss_server server
	# Force disable fast open for now du to problem on 6.1 with MPTCP
	fast_open="false"

	#if [ "$current_mptcp" != "$mptcp" ] || [ "$current_port" != "$port" ] || [ "$current_method" != "$method" ] || [ "$current_fast_open" != "$fast_open" ]; then
	if [ "$current_port" != "$port" ] || [ "$current_method" != "$method" ]; then
		local settings
		settings='{"port": '$port',"method":"'$method'","fast_open":'$fast_open',"reuse_port":false,"mptcp":'$mptcp'}'
		result=$(_set_json "shadowsocks-go" "$settings")
	fi
}

_set_v2ray_server_vps() {
	enabled=$(uci -q get v2ray.main.enabled)
	[ "$enabled" != "1" ] && return
	userid=$(uci -q get v2ray.omrout.s_vless_user_id)
	[ -z "$userid" ] && return
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	current_userid="$(echo "$vps_config" | jsonfilter -q -e '@.v2ray.config.key')"

	if [ "$current_userid" != "$userid" ]; then
		local settings
		settings='{"userid": "'$userid'"}'
		echo $(_set_json "v2ray" "$settings")
	fi
}

_set_xray_server_vps() {
	enabled=$(uci -q get xray.main.enabled)
	userid=$(uci -q get xray.omrout.s_vless_user_id)
	protocol=$(uci -q get xray.omrout.protocol)
	ss_method=$(uci -q get xray.omrout.s_shadowsocks_method)
	# An empty method makes the xray-server.json shadowsocks inbound invalid
	# and prevents the whole xray service from starting on the VPS
	[ -z "$ss_method" ] && ss_method="2022-blake3-aes-256-gcm"
	transport=$(uci -q get xray.omrout.ss_network)
	[ -z "$transport" ] && transport="tcp"
	if [ "$protocol" = "vless-reality" ] && [ "$enabled" = "1" ]; then
		vless_reality='true'
	else
		vless_reality='false'
	fi
	[ -z "$userid" ] && return
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	current_userid="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.key')"
	current_vlessreality="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.vless_reality')"
	current_method="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.ss_method')"
	current_transport="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.transport')"

	if [ "$current_userid" != "$userid" ] || [ "$current_vlessreality" != "$vless_reality" ] || [ "$current_method" != "$ss_method" ] || [ "$current_transport" != "$transport" ]; then
		local settings
		settings='{"userid": "'$userid'","vless_reality": '$vless_reality',"ss_method": "'$ss_method'","transport": "'$transport'"}'
		echo $(_set_json "xray" "$settings")
	fi
}

_get_vps_config() {
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	vps_lastchange="$(echo "$vps_config" | jsonfilter -q -e '@.vps.lastchange')"
	if [ -n "$(uci -q get openmptcprouter.${servername}.lastchange)" ] && [ -n "$vps_lastchange" ] && [ "$vps_lastchange" -gt "$(uci -q get openmptcprouter.${servername}.lastchange)" ]; then
		_set_config_from_vps
	fi

	piholeomr="$(uci -q get openmptcprouter.${servername}.pihole)"
	pihole="$(echo "$vps_config" | jsonfilter -q -e '@.pihole.state')"
	if [ "$pihole" = "true" ] && [ "$piholeomr" != "1" ]; then
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.pihole='1'
			commit openmptcprouter
		EOF
	elif [ "$pihole" = "false" ] && [ "$piholeomr" != "0" ]; then
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.pihole='0'
			commit openmptcprouter
		EOF
	fi

	vpsinternet="$(echo "$vps_config" | jsonfilter -q -e '@.network.internet')"
	if [ "$vpsinternet" = "false" ] && [ "$(uci -q get openmptcprouter.settings.external_check)" != "0" ]; then
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.settings.external_check='0'
			commit openmptcprouter
		EOF
	fi

	vps_kernel="$(echo "$vps_config" | jsonfilter -q -e '@.vps.kernel')"
	vps_machine="$(echo "$vps_config" | jsonfilter -q -e '@.vps.machine')"
	vps_omr_version="$(echo "$vps_config" | jsonfilter -q -e '@.vps.omr_version')"
	uci -q batch <<-EOF >/dev/null
		set openmptcprouter.${servername}.kernel=$vps_kernel
		set openmptcprouter.${servername}.machine=$vps_machine
		set openmptcprouter.${servername}.omr_version=$vps_omr_version
	EOF
	vpn="$(uci -q get openmptcprouter.settings.vpn)"

	glorytun_state=0
	glorytun_change=0
	if [ "$vpn" = "glorytun_tcp" ]; then
		glorytun_state=1
		uci -q set glorytun.vpn.proto='tcp'
		client_ip="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.tcp.client_ip')"
		host_ip="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.tcp.host_ip')"
		port="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.port')"
		if [ "$(uci -q get glorytun.vpn.port)" != "$port" ] && [ "$port" != "" ]; then
			uci -q batch <<-EOF >/dev/null
				set glorytun.vpn.port=$port
			EOF
			glorytun_change=1
		fi
		if [ "$client_ip" != "dhcp" ] && [ -n "$client_ip" ]; then
			if [ "$host_ip" != "$(uci -q get glorytun.vpn.remoteip)" ] || [ "$client_ip" != "$(uci -q get glorytun.vpn.localip)" ]; then
				uci -q batch <<-EOF >/dev/null
					set glorytun.vpn.localip=$client_ip
					set glorytun.vpn.remoteip=$host_ip
				EOF
				glorytun_change=1
			fi
			if [ "$(uci -q get network.omrvpn.proto)" != 'none' ]; then
				uci -q batch <<-EOF >/dev/null
					set network.omrvpn.proto='none'
					commit network
				EOF
				ifup omrvpn >/dev/null 2>&1
				glorytun_change=1
			fi
		else
			if [ "$(uci -q get glorytun.vpn.remoteip)" != "" ] || [ "$(uci -q get glorytun.vpn.localip)" != "" ]; then
				uci -q batch <<-EOF >/dev/null
					delete glorytun.vpn.localip
					delete glorytun.vpn.remoteip
				EOF
				glorytun_change=1
			fi
			if [ "$(uci -q get network.omrvpn.proto)" != 'dhcp' ]; then
				uci -q batch <<-EOF >/dev/null
					set network.omrvpn.proto='dhcp'
					commit network
				EOF
				ifup omrvpn >/dev/null 2>&1
				glorytun_change=1
			fi
		fi
	fi
	if [ "$vpn" = "glorytun_udp" ]; then
		glorytun_state=1
		#uci -q set glorytun.vpn.proto='udp'
		client_ip="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.udp.client_ip')"
		host_ip="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.udp.host_ip')"
		port="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.port')"
		if [ "$(uci -q get glorytun-udp.vpn.port)" != "$port" ] && [ "$port" != "" ]; then
			uci -q batch <<-EOF >/dev/null
				set glorytun-udp.vpn.port=$port
			EOF
		fi
		if [ "$client_ip" != "dhcp" ] && [ -n "$client_ip" ]; then
			if [ "$host_ip" != "$(uci -q get glorytun-udp.vpn.remoteip)" ] || [ "$client_ip" != "$(uci -q get glorytun-udp.vpn.localip)" ]; then
				uci -q batch <<-EOF >/dev/null
					set glorytun-udp.vpn.localip=$client_ip
					set glorytun-udp.vpn.remoteip=$host_ip
				EOF
				glorytun_change=1
			fi
			if [ "$(uci -q get network.omrvpn.proto)" = "dhcp" ]; then
				uci -q batch <<-EOF >/dev/null
					set network.omrvpn.proto='none'
					commit network
				EOF
				ifup omrvpn >/dev/null 2>&1
				glorytun_change=1
			fi
		else
			if [ "$(uci -q get glorytun-udp.vpn.remoteip)" != "" ] || [ "$(uci -q get glorytun-udp.vpn.localip)" != "" ]; then
				uci -q batch <<-EOF >/dev/null
					delete glorytun-udp.vpn.localip
					delete glorytun-udp.vpn.remoteip
				EOF
				glorytun_change=1
			fi
			if [ "$(uci -q get network.omrvpn.proto)" != "dhcp" ]; then
				uci -q batch <<-EOF >/dev/null
					set network.omrvpn.proto='dhcp'
					commit network
				EOF
				ifup omrvpn >/dev/null 2>&1
				glorytun_change=1
			fi
		fi
	fi
	if [ "$vpn" = "mqvpn" ]; then
		# mqvpn assigns the tunnel IP itself: a leftover dhcp proto (softether
		# or glorytun-dhcp era) keeps omrvpn pending forever in netifd, which
		# breaks everything that depends on it (VXLAN tunlink, ...)
		if [ "$(uci -q get network.omrvpn.proto)" = "dhcp" ]; then
			uci -q batch <<-EOF >/dev/null
				set network.omrvpn.proto='none'
				commit network
			EOF
			ifup omrvpn >/dev/null 2>&1
		fi
	fi
	vpsip="$(uci -q get openmptcprouter.${servername}.ip | awk '{print $1}')"
	ipresolve="$(resolveip -t 5 -4 $vpsip | head -n 1)"
	if [ -n "$ipresolve" ]; then
		vpsip="$ipresolve"
	else
		ip6resolve="$(resolveip -t 5 -6 $vpsip | head -n 1)"
		[ -n "$ip6resolve" ] && vpsip="$ip6resolve"
	fi
	if [ -n "$(uci -q get shadowsocks-libev.sss0)" ] && [ "$(uci -q get shadowsocks-libev.sss0.server)" != "127.0.0.1" ] && [ "$(uci -q get shadowsocks-libev.sss0.server)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; then
		config_foreach _set_ss_server server "server" $vpsip
		uci -q batch <<-EOF >/dev/null
			set shadowsocks-libev.sss0.server="$vpsip"
			commit shadowsocks-libev
		EOF
		if [ "$(uci -q get shadowsocks-libev.sss0.disabled)" = "0" ]; then
			logger -t "OMR-VPS" "Restart shadowsocks..."
			/etc/init.d/shadowsocks-libev restart
		fi
	fi
	if [ -n "$(uci -q get shadowsocks-rust.sss0)" ] && [ "$(uci -q get shadowsocks-rust.sss0.server)" != "127.0.0.1" ] && [ "$(uci -q get shadowsocks-rust.sss0.server)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; then
		config_foreach _set_ssrust_server server "server" $vpsip
		uci -q batch <<-EOF >/dev/null
			set shadowsocks-rust.sss0.server="$vpsip"
			commit shadowsocks-rust
		EOF
		if [ "$(uci -q get shadowsocks-rust.sss0.disabled)" = "0" ]; then
			logger -t "OMR-VPS" "Restart shadowsocks Rust..."
			/etc/init.d/shadowsocks-rust restart
		fi
	fi
	if [ -n "$(uci -q get v2ray.omrout)" ] && [ "$(uci -q get v2ray.omrout.s_vmess_address)" != "127.0.0.1" ] && [ "$(uci -q get v2ray.omrout.s_vmess_address)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; then
		uci -q batch <<-EOF >/dev/null
			set v2ray.omrout.s_vmess_address="$vpsip"
			set v2ray.omrout.s_vless_address="$vpsip"
			set v2ray.omrout.s_trojan_address="$vpsip"
			set v2ray.omrout.s_socks_address="$vpsip"
			commit v2ray
		EOF
		if [ "$(uci -q get v2ray.main.enabled)" = "1" ]; then
			logger -t "OMR-VPS" "Restart V2Ray..."
			/etc/init.d/v2ray restart
		fi
	fi
	if [ -n "$(uci -q get xray.omrout)" ] && [ "$(uci -q get xray.omrout.s_vmess_address)" != "127.0.0.1" ] && [ "$(uci -q get xray.omrout.s_vmess_address)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; then
		uci -q batch <<-EOF >/dev/null
			set xray.omrout.s_vmess_address="$vpsip"
			set xray.omrout.s_vless_address="$vpsip"
			set xray.omrout.s_trojan_address="$vpsip"
			set xray.omrout.s_socks_address="$vpsip"
			set xray.omrout.s_shadowsocks_address="$vpsip"
			set xray.omrout.s_vless_reality_address="$vpsip"
			commit xray
		EOF
		if [ "$(uci -q get xray.main.enabled)" = "1" ]; then
			logger -t "OMR-VPS" "Restart XRay..."
			/etc/init.d/xray restart
		fi
	fi
	if [ -n "$(uci -q get openvpn.omr)" ] && [ "$(uci -q get openvpn.omr.remote)" != "127.0.0.1" ] && [ -z "$(uci -q get openvpn.omr.remote | grep $vpsip)" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; then
		uci -q batch <<-EOF >/dev/null
			delete openvpn.omr.remote
			add_list openvpn.omr.remote="$vpsip"
			commit openvpn
		EOF
		if [ "$(uci -q get openvpn.omr.enabled)" = "1" ]; then
			logger -t "OMR-VPS" "Restart OpenVPN..."
			/etc/init.d/openvpn restart
		fi
	fi
	port="$(echo "$vps_config" | jsonfilter -q -e '@.dsvpn.port')"
	localip="$(echo "$vps_config" | jsonfilter -q -e '@.dsvpn.client_ip')"
	remoteip="$(echo "$vps_config" | jsonfilter -q -e '@.dsvpn.host_ip')"
	if [ -n "$(uci -q get dsvpn.vpn)" ] && ([ "$(uci -q get dsvpn.vpn.host)" != "127.0.0.1" ] && [ "$(uci -q get dsvpn.vpn.host)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]) || [ "$(uci -q get dsvpn.vpn.port)" != "$port" ] || [ "$(uci -q get dsvpn.vpn.localip)" != "$localip" ] || [ "$(uci -q get dsvpn.vpn.remoteip)" != "$remoteip" ]; then
		uci -q batch <<-EOF >/dev/null
			set dsvpn.vpn.port=$port
			set dsvpn.vpn.localip=$localip
			set dsvpn.vpn.remoteip=$remoteip
			set dsvpn.vpn.host="$vpsip"
			commit dsvpn
		EOF
		if [ "$(uci -q get dsvpn.vpn.enable)" = "1" ]; then
			logger -t "OMR-VPS" "Restart DSVPN..."
			/etc/init.d/dsvpn restart
		fi
	fi

	mqvpn_port_tmp="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.port')"
	[ -z "$mqvpn_port_tmp" ] && mqvpn_port_tmp="65443"
	# mqvpn.server.port is user intent (pushed to the VPS by _set_mqvpn_vps):
	# only default it from the VPS when not set locally
	[ -n "$(uci -q get mqvpn.server.port)" ] && mqvpn_port_tmp="$(uci -q get mqvpn.server.port)"
	if [ -n "$(uci -q get mqvpn.server)" ] && { [ "$(uci -q get mqvpn.server.ip)" != "$vpsip" ] || [ "$(uci -q get mqvpn.server.port)" != "$mqvpn_port_tmp" ]; } && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; then
		uci -q batch <<-EOF >/dev/null
			set mqvpn.server.ip="$vpsip"
			set mqvpn.server.port="$mqvpn_port_tmp"
			commit mqvpn
		EOF
		if [ "$(uci -q get mqvpn.settings.enable)" = "1" ]; then
			logger -t "OMR-VPS" "Restart MQVPN..."
			/etc/init.d/mqvpn restart
		fi
	fi

	port="$(echo "$vps_config" | jsonfilter -q -e '@.softethervpn.port')"
	if [ -n "$(uci -q get softethervpn.openmptcprouter)" ] && [ -n "$port" ] && { { [ "$(uci -q get softethervpn.openmptcprouter.host)" != "127.0.0.1" ] && [ "$(uci -q get softethervpn.openmptcprouter.host)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; } || [ "$(uci -q get softethervpn.openmptcprouter.port)" != "$port" ]; }; then
		uci -q batch <<-EOF >/dev/null
			set softethervpn.openmptcprouter.port=$port
			set softethervpn.openmptcprouter.host="$vpsip"
			commit softethervpn
		EOF
		# omrvpn is shared by all VPNs: only switch it to DHCP when
		# SoftEtherVPN is really the VPN in use
		if [ "$vpn" = "softethervpn" ] || [ "$vpn" = "softether" ]; then
			uci -q batch <<-EOF >/dev/null
				set network.omrvpn.proto='dhcp'
				commit network
			EOF
			ifup omrvpn >/dev/null 2>&1
		fi
		if [ "$(uci -q get softethervpn.openmptcprouter.enable)" = "1" ] && [ -f /etc/init.d/softethervpnclient ]; then
			logger -t "OMR-VPS" "Restart SoftEtherVPN..."
			/etc/init.d/softethervpnclient restart
		fi
	fi

	if [ -n "$(uci -q get mlvpn.general)" ] && [ -z "$(uci -q get openmptcprouter.settings.mptcpovervpn)" ] && [ "$(uci -q get mlvpn.general.host)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ] && [ -f /etc/init.d/mlvpn ]; then
		uci -q batch <<-EOF >/dev/null
			set mlvpn.general=mlvpn
			set mlvpn.general.host="$vpsip"
			commit mlvpn
		EOF
		if [ "$(uci -q get mlvpn.general.enable)" = "1" ]; then
			logger -t "OMR-VPS" "Restart MLVPN..."
			/etc/init.d/mlvpn restart
		fi
	fi
	if [ -n "$(uci -q get glorytun.vpn)" ] && [ -z "$(uci -q get openmptcprouter.settings.mptcpovervpn)" ] && [ "$(uci -q get glorytun.vpn.host)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; then
		uci -q batch <<-EOF >/dev/null
			set glorytun.vpn.host="$vpsip"
		EOF
		glorytun_change=1
	fi
	if [ -n "$(uci -q get glorytun-udp.vpn)" ] && [ -z "$(uci -q get openmptcprouter.settings.mptcpovervpn)" ] && [ "$(uci -q get glorytun-udp.vpn.host)" != "$vpsip" ] && [ "$(uci -q get openmptcprouter.settings.ha)" != "1" ]; then
		uci -q batch <<-EOF >/dev/null
			set glorytun-udp.vpn.host="$vpsip"
		EOF
		glorytun_change=1
	fi

	if [ "$glorytun_change" != "0" ]; then
		uci -q batch <<-EOF >/dev/null
			commit glorytun
			commit glorytun-udp
		EOF
		if [ "$(uci -q get glorytun.vpn.enable)" = "1" ]; then
			logger -t "OMR-VPS" "Restart glorytun..."
			/etc/init.d/glorytun restart >/dev/null 2>&1
		fi
		if [ "$(uci -q get glorytun-udp.vpn.enable)" = "1" ]; then
			logger -t "OMR-VPS" "Restart glorytun-udp..."
			/etc/init.d/glorytun-udp restart >/dev/null 2>&1
		fi
	fi
}

_get_gre_tunnel() {
	[ -z "$servername" ] && servername=$1
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	gre_tunnel_state="$(echo "$vps_config" | jsonfilter -q -e '@.gre_tunnel.enabled')"
	vpnip_local="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.remoteip')"
	vpnip_remote="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.localip')"
	if [ "$gre_tunnel_state" = "true" ]; then
		i=0
		echo "$vps_config" | jsonfilter -q -e '@.gre_tunnel.config[*]' |
		while IFS= read -r tunnel; do
			peeraddr="$(echo $tunnel | jsonfilter -q -e '@.remote_ip')"
			ipaddr="$(echo $tunnel | jsonfilter -q -e '@.local_ip')"
			publicaddr="$(echo $tunnel | jsonfilter -q -e '@.public_ip')"
			if [ "$peeraddr" != "" ] && [ "$ipaddr" != "" ] && [ "$publicaddr" != "" ] && [ "$vpnip_local" != "" ] && ([ "$(uci -q get network.oip${i}.ipaddr)" != "$peeraddr" ] || [ "$(uci -q get network.oip${i}.gateway)" != "$ipaddr" ] || [ "$(uci -q get network.oip${i}gre.ipaddr)" != "$vpnip_local" ] || ([ -n "$(uci -q get xray)" ] && [ -z "$(uci -q get xray.oip${i}server.s_vless_user_id)" ]) || ([ -n "$(uci -q get xray.oip${i}server_tunnel.protocol)" ] && [ "$(uci -q get xray.oip${i}server_tunnel.protocol)" != "$(uci -q get xray.omrout.protocol)" ])); then
				uci -q batch <<-EOF >/dev/null
					set network.oip${i}gre=interface
					set network.oip${i}gre.label="GRE tunnel for $publicaddr"
					set network.oip${i}gre.proto=gre
					set network.oip${i}gre.nohostroute='1'
					set network.oip${i}gre.ipv6='0'
					set network.oip${i}gre.defaultroute='0'
					set network.oip${i}gre.multipath='off'
					set network.oip${i}gre.peerdns='0'
					set network.oip${i}gre.ttl='255'
					set network.oip${i}gre.ip4table='vpn'
					set network.oip${i}gre.peeraddr="$publicaddr"
					set network.oip${i}gre.ipaddr="$vpnip_local"
					set network.oip${i}gre.tunlink='omrvpn'
					set network.oip${i}=interface
					set network.oip${i}.label="Tunnel for $publicaddr"
					set network.oip${i}.proto=static
					set network.oip${i}.nohostroute='1'
					set network.oip${i}.device="@oip${i}gre"
					set network.oip${i}.ipv6='0'
					set network.oip${i}.defaultroute='0'
					set network.oip${i}.multipath='off'
					set network.oip${i}.peerdns='0'
					set network.oip${i}.ip4table='vpn'
					set network.oip${i}.gateway="$ipaddr"
					set network.oip${i}.ipaddr="$peeraddr"
					set network.oip${i}.netmask="255.255.255.252"
					set network.oip${i}.lookup="667${i}"
				EOF
				allintf=$(uci -q get firewall.zone_vpn.network)
				uci -q del firewall.zone_vpn.network
				for intf in $allintf; do
					uci -q add_list firewall.zone_vpn.network=$intf
				done
				[ -z "$(uci -q get firewall.zone_vpn.network | grep oip${i}gre)" ] && {
					uci -q add_list firewall.zone_vpn.network="oip${i}gre"
					uci -q add_list firewall.zone_vpn.network="oip${i}"
				}
				if [ -n "$(uci -q get shadowsocks-libev)" ]; then
					ssport="$(echo $tunnel | jsonfilter -q -e '@.shadowsocks_port')"
					if [ -n "$ssport" ]; then
						uci -q batch <<-EOF >/dev/null
							set shadowsocks-libev.oip${i}server=server
							set shadowsocks-libev.oip${i}server.label="Server with public IP $publicaddr"
							set shadowsocks-libev.oip${i}server.server_port="$ssport"
							set shadowsocks-libev.oip${i}server.server="$(uci -q get shadowsocks-libev.sss0.server)"
							set shadowsocks-libev.oip${i}server.method="$(uci -q get shadowsocks-libev.sss0.method)"
							set shadowsocks-libev.oip${i}server.key="$(uci -q get shadowsocks-libev.sss0.key)"
							set shadowsocks-libev.oip${i}=ss_redir
							set shadowsocks-libev.oip${i}.label="ss-redir for public IP $publicaddr"
							set shadowsocks-libev.oip${i}.server="oip${i}server"
							set shadowsocks-libev.oip${i}.local_port="230$i"
							set shadowsocks-libev.oip${i}.local_address="$(uci -q get shadowsocks-libev.hi.local_address)"
							set shadowsocks-libev.oip${i}.mode='tcp_and_udp'
							set shadowsocks-libev.oip${i}.reuse_port='1'
							set shadowsocks-libev.oip${i}.mptcp='1'
							set shadowsocks-libev.oip${i}.ipv6_first='1'
							set shadowsocks-libev.oip${i}.timeout="$(uci -q get shadowsocks-libev.hi.timeout)"
							set shadowsocks-libev.oip${i}.fast_open="$(uci -q get shadowsocks-libev.hi.fast_open)"
							set shadowsocks-libev.oip${i}.no_delay="$(uci -q get shadowsocks-libev.hi.no_delay)"
							set shadowsocks-libev.oip${i}_rule=ss_rules
							set shadowsocks-libev.oip${i}_rule.label="Rules for public IP $publicaddr"
							set shadowsocks-libev.oip${i}_rule.server="oip${i}server"
							set shadowsocks-libev.oip${i}_rule.disabled='1'
							set shadowsocks-libev.oip${i}_rule.src_default='bypass'
							set shadowsocks-libev.oip${i}_rule.dst_default='bypass'
							set shadowsocks-libev.oip${i}_rule.local_default='bypass'
							set shadowsocks-libev.oip${i}_rule.redir_tcp="oip${i}"
						EOF
						if [ "$(uci -q get shadowsocks-libev.oip${i}server.disabled)" = "" ]; then
							uci -q set shadowsocks-libev.oip${i}server.disabled="1"
						fi
					fi
				fi
				if [ -n "$(uci -q get xray)" ]; then
					xray_user="$(echo $tunnel | jsonfilter -q -e '@.xray.uuid')"
					uci -q batch <<-EOF >/dev/null
						delete xray.oip${i}server
						delete xray.oip${i}server_tunnel
						delete xray.oip${i}_redir
						delete xray.oip${i}_routing
						delete xray.oip${i}_transparent_proxy
						del_list xray.main.outbounds="oip${i}server_tunnel"
						del_list xray.main_routing.rules="oip${i}_routing"
					EOF
					if [ -n "$xray_user" ]; then
						xray_ss2022="$(echo $tunnel | jsonfilter -q -e '@.xray.ss2022')"
						xray_reality_user="$(echo $tunnel | jsonfilter -q -e '@.xray.vless_reality')"
						xray_reality_key="$(echo $tunnel | jsonfilter -q -e '@.xray.vless_reality_key')"
						echo "xray; $xray_reality_user - key: $xray_reality_key"
						uci -q batch <<-EOF >/dev/null
							delete xray.oip${i}server
							set xray.oip${i}server_tunnel=outbound
							set xray.oip${i}server_tunnel.tag='oip${i}server_tunnel'
							set xray.oip${i}server_tunnel.protocol="$(uci -q get xray.omrout.protocol)"
							set xray.oip${i}server_tunnel.s_vmess_port='65250'
							set xray.oip${i}server_tunnel.s_vmess_user_security='aes-128-gcm'
							set xray.oip${i}server_tunnel.s_vmess_user_alter_id='0'
							set xray.oip${i}server_tunnel.s_vless_port='65248'
							set xray.oip${i}server_tunnel.s_vless_user_security='aes-128-gcm'
							set xray.oip${i}server_tunnel.s_vless_user_encryption='none'
							set xray.oip${i}server_tunnel.s_vless_user_alter_id='0'
							set xray.oip${i}server_tunnel.s_trojan_port='65249'
							set xray.oip${i}server_tunnel.s_trojan_user_security='aes-128-gcm'
							set xray.oip${i}server_tunnel.s_trojan_user_encryption='none'
							set xray.oip${i}server_tunnel.s_trojan_user_alter_id='0'
							set xray.oip${i}server_tunnel.s_socks_port='65251'
							set xray.oip${i}server_tunnel.s_socks_user_security='aes-128-gcm'
							set xray.oip${i}server_tunnel.s_socks_user_encryption='none'
							set xray.oip${i}server_tunnel.s_socks_user_alter_id='0'
							set xray.oip${i}server_tunnel.ss_network='tcp'
							set xray.oip${i}server_tunnel.ss_security='tls'
							set xray.oip${i}server_tunnel.ss_tls_allow_insecure='1'
							set xray.oip${i}server_tunnel.ss_tls_disable_system_root='1'
							set xray.oip${i}server_tunnel.ss_tls_cert_usage='verify'
							set xray.oip${i}server_tunnel.ss_tls_cert_file='/etc/luci-uploads/client.crt'
							set xray.oip${i}server_tunnel.ss_tls_key_file='/etc/luci-uploads/client.key'
							set xray.oip${i}server_tunnel.s_shadowsocks_port='65252'
							set xray.oip${i}server_tunnel.mux_concurrency='8'
							set xray.oip${i}server_tunnel.ss_sockopt_mptcp='1'
							set xray.oip${i}server_tunnel.s_vless_reality_port='443'
							set xray.oip${i}server_tunnel.s_vless_reality_flow='xtls-rprx-vision'
							set xray.oip${i}server_tunnel.s_vless_reality_user_security='aes-128-gcm'
							set xray.oip${i}server_tunnel.s_vless_reality_user_encryption='none'
							set xray.oip${i}server_tunnel.s_vless_reality_user_alter_id='0'
							set xray.oip${i}server_tunnel.s_shadowsocks_email='openmptcprouter'
							set xray.oip${i}server_tunnel.s_vmess_address="$(uci -q get xray.omrout.s_vless_address)"
							set xray.oip${i}server_tunnel.s_vless_address="$(uci -q get xray.omrout.s_vless_address)"
							set xray.oip${i}server_tunnel.s_vless_reality_address="$(uci -q get xray.omrout.s_vless_address)"
							set xray.oip${i}server_tunnel.s_trojan_address="$(uci -q get xray.omrout.s_vless_address)"
							set xray.oip${i}server_tunnel.s_socks_address="$(uci -q get xray.omrout.s_vless_address)"
							set xray.oip${i}server_tunnel.s_shadowsocks_address="$(uci -q get xray.omrout.s_vless_address)"
							set xray.oip${i}server_tunnel.s_shadowsocks_method='2022-blake3-aes-256-gcm'
							set xray.oip${i}server_tunnel.s_shadowsocks_password="$(uci -q get xray.omrout.s_shadowsocks_password | cut -d':' -f 1):$xray_ss2022"
							set xray.oip${i}server_tunnel.s_socks_user_id="${xray_user}"
							set xray.oip${i}server_tunnel.s_trojan_user_id="${xray_user}"
							set xray.oip${i}server_tunnel.s_vmess_user_id="${xray_user}"
							set xray.oip${i}server_tunnel.s_vless_user_id="${xray_user}"
							set xray.oip${i}server_tunnel.s_vless_reality_user_id="${xray_reality_user}"
							set xray.oip${i}server_tunnel.s_vless_reality_public_key="${xray_reality_key}"
							add_list xray.main.outbounds="oip${i}server_tunnel"
							set xray.oip${i}_redir=inbound
							set xray.oip${i}_redir.tag='oip${i}_redir'
							set xray.oip${i}_redir.listen='0.0.0.0'
							set xray.oip${i}_redir.port='180${i}'
							set xray.oip${i}_redir.protocol='dokodemo-door'
							set xray.oip${i}_redir.s_dokodemo_door_network='tcp'
							add_list xray.oip${i}_redir.s_dokodemo_door_network='udp'
							set xray.oip${i}_redir.ss_sockopt_tproxy='redirect'
							set xray.oip${i}_redir.ss_sockopt_tcp_fast_open='1'
							set xray.oip${i}_redir.ss_sockopt_mptcp='1'
							set xray.oip${i}_redir.s_dokodemo_door_follow_redirect='1'
							set xray.oip${i}_routing=routing_rule
							set xray.oip${i}_routing.tag="oip${i}_routing"
							set xray.oip${i}_routing.type='field'
							set xray.oip${i}_routing.inbound_tag="oip${i}_redir"
							set xray.oip${i}_routing.outbound_tag="oip${i}server_tunnel"
							add_list xray.main_routing.rules="oip${i}_routing"
							set xray.oip${i}_transparent_proxy=transparent_proxy
							set xray.oip${i}_transparent_proxy.proxy_mode='default'
							set xray.oip${i}_transparent_proxy.redirect_udp='1'
							set xray.oip${i}_transparent_proxy.redirect_port="180${i}"
						EOF
					fi
				fi
			fi
			i=$((i+1))
		done
		uci -q batch <<-EOF >/dev/null
			commit network
			commit firewall
			commit shadowsocks-libev
			commit xray
		EOF
	fi
}

set_gre_tunnel() {
	config_load openmptcprouter
	config_foreach _get_gre_tunnel server
}

_set_vxlan_vps() {
	local settings result vxlan_enabled vxlan_current vxlan_mode vxlan_mode_current
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	if [ "$(uci -q get openmptcprouter.settings.vxlan)" = "1" ]; then
		vxlan_enabled="true"
	else
		vxlan_enabled="false"
	fi
	vxlan_mode="$(uci -q get openmptcprouter.settings.vxlan_mode)"
	[ "$vxlan_mode" = "l2" ] || vxlan_mode="l3"
	vxlan_current="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.enabled')"
	[ -z "$vxlan_current" ] && vxlan_current="false"
	vxlan_mode_current="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.mode')"
	[ -z "$vxlan_mode_current" ] && vxlan_mode_current="l3"
	if [ "$vxlan_current" != "$vxlan_enabled" ] || [ "$vxlan_mode_current" != "$vxlan_mode" ]; then
		settings='{"enable": '$vxlan_enabled', "mode": "'$vxlan_mode'"}'
		result=$(_set_json "vxlan" "$settings")
		[ "$(_parse_result "$result")" = "done" ] && vps_config=$(_get_json "config")
	fi
}

_bridge_name="omrvxlanbr"
_bridge_ifname="br-omrvxlan"
_vxlan_bridge_lookup=""
_vxlan_bridge_section=""
_vxlan_active_bridge_section=""

_vxlan_bridge_section_by_name_cb() {
	local section type name
	section="$1"
	[ -n "$_vxlan_bridge_section" ] && return
	config_get type "$section" type
	[ "$type" = "bridge" ] || return
	config_get name "$section" name
	if [ "$name" = "$_vxlan_bridge_lookup" ] || [ "$section" = "$_vxlan_bridge_lookup" ]; then
		_vxlan_bridge_section="$section"
	fi
}

_vxlan_bridge_section_by_name() {
	_vxlan_bridge_lookup="$1"
	_vxlan_bridge_section=""
	config_load network
	config_foreach _vxlan_bridge_section_by_name_cb device
	echo "$_vxlan_bridge_section"
}

_vxlan_active_bridge_section_cb() {
	local section target
	section="$1"
	[ -n "$_vxlan_active_bridge_section" ] && return
	config_get target "$section" omrvxlan_target_if
	[ -n "$target" ] && _vxlan_active_bridge_section="$section"
}

_vxlan_active_bridge_section() {
	if [ -n "$(uci -q get network.${_bridge_name})" ]; then
		echo "$_bridge_name"
		return
	fi
	_vxlan_active_bridge_section=""
	config_load network
	config_foreach _vxlan_active_bridge_section_cb device
	echo "$_vxlan_active_bridge_section"
}

# Which local network interface to bridge omrvxlan into (L2 mode); any
# configured interface is allowed, not just the LAN zone's. Falls back to
# lan when unset, malformed, nonexistent, or one of the tunnel's own
# interfaces (bridging into those would break the tunnel itself)
_vxlan_bridge_target_if() {
	local target
	target="$(uci -q get openmptcprouter.settings.vxlan_bridge_if)"
	case "$target" in
		''|*[!A-Za-z0-9_-]*|loopback|omrvpn|omrvxlan|omrvxlanip) target="lan" ;;
	esac
	[ -n "$(uci -q get network.$target)" ] || target="lan"
	echo "$target"
}

# Bridge omrvxlan into the configured interface's device (L2 mode): the
# tunnel carries that interface's broadcast domain instead of a routed P2P
# link. Defaults to lan when unset.
_vxlan_join_lan_bridge() {
	local target_if lan_device lan_ifname bridge_section
	target_if="$(_vxlan_bridge_target_if)"
	lan_device="$(uci -q get network.${target_if}.device)"
	lan_ifname="$(uci -q get network.${target_if}.ifname)"
	bridge_section="$(_vxlan_bridge_section_by_name "$lan_device")"
	if [ -n "$bridge_section" ]; then
		uci -q batch <<-EOF >/dev/null
			set network.${bridge_section}.omrvxlan_lan_device="$lan_device"
			set network.${bridge_section}.omrvxlan_target_if="$target_if"
			set network.${bridge_section}.omrvxlan_existing_bridge='1'
		EOF
		if [ -z "$(uci -q get network.${bridge_section}.ports | grep 'omrvxlan')" ]; then
			uci -q add_list network.${bridge_section}.ports='omrvxlan'
		fi
		return
	fi
	if [ "$lan_device" != "$_bridge_name" ]; then
		# Wrap the target interface's current device in a bridge so omrvxlan
		# can join it as a port; the original device and interface name are
		# stashed to unwind on disable or on a later target change
		uci -q batch <<-EOF >/dev/null
			set network.${_bridge_name}=device
			set network.${_bridge_name}.type='bridge'
			set network.${_bridge_name}.name="$_bridge_ifname"
			set network.${_bridge_name}.omrvxlan_lan_device="$lan_device"
			set network.${_bridge_name}.omrvxlan_target_if="$target_if"
		EOF
		[ -n "$lan_ifname" ] && uci -q set network.${_bridge_name}.omrvxlan_lan_ifname="$lan_ifname"
		uci -q add_list network.${_bridge_name}.ports="$lan_device"
		uci -q set network.${target_if}.device="$_bridge_ifname"
		uci -q delete network.${target_if}.ifname
	fi
	if [ -z "$(uci -q get network.${_bridge_name}.ports | grep 'omrvxlan')" ]; then
		uci -q add_list network.${_bridge_name}.ports='omrvxlan'
	fi
}

# Undo _vxlan_join_lan_bridge: drop omrvxlan from the bridge and, once it was
# the only extra port, restore the stashed interface's original device
_vxlan_leave_lan_bridge() {
	local bridge_section lan_device lan_ifname target_if existing_bridge
	bridge_section="$(_vxlan_active_bridge_section)"
	[ -z "$bridge_section" ] && bridge_section="$_bridge_name"
	lan_device="$(uci -q get network.${bridge_section}.omrvxlan_lan_device)"
	lan_ifname="$(uci -q get network.${bridge_section}.omrvxlan_lan_ifname)"
	target_if="$(uci -q get network.${bridge_section}.omrvxlan_target_if)"
	existing_bridge="$(uci -q get network.${bridge_section}.omrvxlan_existing_bridge)"
	[ -z "$target_if" ] && target_if="lan"
	uci -q del_list network.${bridge_section}.ports='omrvxlan'
	if [ "$existing_bridge" = "1" ]; then
		uci -q batch <<-EOF >/dev/null
			delete network.${bridge_section}.omrvxlan_lan_device
			delete network.${bridge_section}.omrvxlan_target_if
			delete network.${bridge_section}.omrvxlan_existing_bridge
		EOF
	elif [ -n "$lan_device" ]; then
		uci -q set network.${target_if}.device="$lan_device"
		if [ -n "$lan_ifname" ]; then
			uci -q set network.${target_if}.ifname="$lan_ifname"
		else
			uci -q delete network.${target_if}.ifname
		fi
		uci -q delete network.${bridge_section}
	fi
}

# Routed P2P mode: omrvxlan gets no address itself, omrvxlanip carries the
# tunnel IPs, both networks join the vpn firewall zone like any other VPN link
_vxlan_apply_l3() {
	local vxlan_ip6addr
	vxlan_ip6addr="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.remoteip6')"
	[ "$(uci -q get openmptcprouter.settings.disable_ipv6)" = "1" ] && vxlan_ip6addr=""
	uci -q batch <<-EOF >/dev/null
		set network.omrvxlanip=interface
		set network.omrvxlanip.label="VXLAN tunnel IPs"
		set network.omrvxlanip.proto=static
		set network.omrvxlanip.device="@omrvxlan"
		set network.omrvxlanip.ipaddr="$vxlan_ipaddr"
		set network.omrvxlanip.multipath='off'
		set network.omrvxlanip.defaultroute='0'
		set network.omrvxlanip.peerdns='0'
		delete network.omrvxlanip.ip6addr
	EOF
	[ -n "$vxlan_ip6addr" ] && uci -q set network.omrvxlanip.ip6addr="$vxlan_ip6addr"
	if [ -z "$(uci -q get firewall.zone_vpn.network | grep omrvxlan)" ]; then
		uci -q add_list firewall.zone_vpn.network="omrvxlan"
		uci -q add_list firewall.zone_vpn.network="omrvxlanip"
	fi
	ifup omrvxlanip >/dev/null 2>&1
}

_vxlan_teardown_l3() {
	ifdown omrvxlanip >/dev/null 2>&1
	uci -q batch <<-EOF >/dev/null
		delete network.omrvxlanip
		commit network
	EOF
	uci -q del_list firewall.zone_vpn.network="omrvxlan"
	uci -q del_list firewall.zone_vpn.network="omrvxlanip"
}

_get_vxlan() {
	local vxlan_state vxlan_vni vxlan_port vxlan_mtu vxlan_peeraddr vxlan_mode vxlan_ipaddr vxlan_bridge_if vxlan_bridge_section vxlan_bridge_if_stored vxlan_bridge_status
	[ -z "$servername" ] && servername=$1
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	vxlan_state="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.enabled')"
	if [ "$vxlan_state" = "true" ] && [ "$(uci -q get openmptcprouter.settings.vpn)" != "none" ]; then
		vxlan_vni="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.vni')"
		vxlan_port="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.port')"
		vxlan_mtu="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.mtu')"
		vxlan_peeraddr="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.localip')"
		vxlan_mode="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.mode')"
		[ "$vxlan_mode" = "l2" ] || vxlan_mode="l3"
		vxlan_ipaddr="$(echo "$vps_config" | jsonfilter -q -e '@.vxlan.remoteip')"
		vxlan_bridge_if="$(_vxlan_bridge_target_if)"
		vxlan_bridge_section="$(_vxlan_active_bridge_section)"
		# A bridge from before this option existed has no stashed target;
		# it was always lan back then, so treat missing as lan rather than
		# as a mismatch (avoids an unnecessary rejoin on first run post-upgrade)
		vxlan_bridge_if_stored="$(uci -q get network.${vxlan_bridge_section:-$_bridge_name}.omrvxlan_target_if)"
		[ -z "$vxlan_bridge_if_stored" ] && vxlan_bridge_if_stored="lan"
		[ -z "$vxlan_vni" ] && return
		[ -z "$vxlan_peeraddr" ] && return
		[ "$vxlan_mode" = "l3" ] && [ -z "$vxlan_ipaddr" ] && return
		if [ "$(uci -q get network.omrvxlan.peeraddr)" != "$vxlan_peeraddr" ] || [ "$(uci -q get network.omrvxlan.vid)" != "$vxlan_vni" ] || [ "$(uci -q get network.omrvxlan.port)" != "$vxlan_port" ] || [ "$(uci -q get network.omrvxlan.mtu)" != "$vxlan_mtu" ] || { [ "$vxlan_mode" = "l2" ] && [ -z "$(uci -q get network.${vxlan_bridge_section:-$_bridge_name}.ports | grep 'omrvxlan')" ]; } || { [ "$vxlan_mode" = "l2" ] && [ -n "$vxlan_bridge_section" ] && [ "$vxlan_bridge_if_stored" != "$vxlan_bridge_if" ]; } || { [ "$vxlan_mode" = "l3" ] && [ "$(uci -q get network.omrvxlanip.ipaddr)" != "$vxlan_ipaddr" ]; }; then
			# If the mode changed since the last run, unwind whichever mode's
			# state is currently on disk before applying the new one. Also
			# unwind an L2 bridge whose target interface changed, so the
			# next join re-wraps the newly selected interface instead.
			[ -n "$(uci -q get network.omrvxlanip)" ] && [ "$vxlan_mode" = "l2" ] && _vxlan_teardown_l3
			[ -n "$vxlan_bridge_section" ] && [ "$vxlan_mode" = "l3" ] && _vxlan_leave_lan_bridge
			[ -n "$vxlan_bridge_section" ] && [ "$vxlan_mode" = "l2" ] && [ "$vxlan_bridge_if_stored" != "$vxlan_bridge_if" ] && _vxlan_leave_lan_bridge
			uci -q batch <<-EOF >/dev/null
				set network.omrvxlan=interface
				set network.omrvxlan.label="VXLAN tunnel over the VPN"
				set network.omrvxlan.proto=vxlan
				set network.omrvxlan.tunlink=omrvpn
				set network.omrvxlan.peeraddr="$vxlan_peeraddr"
				set network.omrvxlan.port="$vxlan_port"
				set network.omrvxlan.vid="$vxlan_vni"
				set network.omrvxlan.mtu="$vxlan_mtu"
				set network.omrvxlan.ttl='255'
				set network.omrvxlan.multipath='off'
				set network.omrvxlan.defaultroute='0'
				set network.omrvxlan.peerdns='0'
			EOF
			if [ "$vxlan_mode" = "l2" ]; then
				_vxlan_join_lan_bridge
			else
				_vxlan_apply_l3
			fi
			uci -q commit network
			# The vpn zone input chain only accepts ICMP: without this rule the
			# VXLAN UDP encap replies arriving on the VPN tunnel are rejected
			uci -q batch <<-EOF >/dev/null
				set firewall.omrvxlan=rule
				set firewall.omrvxlan.enabled='1'
				set firewall.omrvxlan.target='ACCEPT'
				set firewall.omrvxlan.name='Allow-VXLAN'
				set firewall.omrvxlan.proto='udp'
				set firewall.omrvxlan.src='vpn'
				set firewall.omrvxlan.dest_port='${vxlan_port:-4789}'
				commit firewall
			EOF
			logger -t "OMR-VPS" "VXLAN tunnel configured over the VPN (mode: $vxlan_mode)"
			ifup omrvxlan >/dev/null 2>&1
			ubus call network reload >/dev/null 2>&1
			"$FIREWALL_INIT" reload >/dev/null 2>&1
			if [ "$vxlan_mode" = "l2" ]; then
				# The target interface's device now points at the bridge. A
				# bridge can report kernel-level "up" even when the actual L3
				# path is broken (e.g. tunnel not really passing traffic), so
				# also require a live IPv4 address -- that's what an interface
				# doubling as the management/LAN path actually needs to keep
				# working. Unwind rather than strand the router.
				sleep 2
				vxlan_bridge_status="$(ubus call network.interface.${vxlan_bridge_if} status 2>/dev/null)"
				if [ -n "$vxlan_bridge_status" ] && { [ "$(echo "$vxlan_bridge_status" | jsonfilter -q -e '@.up')" != "true" ] || \
				   [ -z "$(echo "$vxlan_bridge_status" | jsonfilter -q -e '@["ipv4-address"][0].address')" ]; }; then
					logger -t "OMR-VPS" "VXLAN L2 bridge on ${vxlan_bridge_if} did not come up cleanly -- rolling back to avoid stranding the router"
					_vxlan_leave_lan_bridge
					uci -q commit network
					ubus call network reload >/dev/null 2>&1
				fi
			fi
		fi
	elif [ -n "$(uci -q get network.omrvxlan)" ]; then
		[ -n "$(uci -q get network.omrvxlanip)" ] && _vxlan_teardown_l3
		[ -n "$(_vxlan_active_bridge_section)" ] && _vxlan_leave_lan_bridge
		ifdown omrvxlan >/dev/null 2>&1
		uci -q batch <<-EOF >/dev/null
			delete network.omrvxlan
			commit network
		EOF
		uci -q delete firewall.omrvxlan
		uci -q commit firewall
		ubus call network reload >/dev/null 2>&1
		"$FIREWALL_INIT" reload >/dev/null 2>&1
		logger -t "OMR-VPS" "VXLAN tunnel removed"
	fi
}

_set_vxlan_service() {
	local tokenserver user_permission
	servername=$1
	[ "$(uci -q get openmptcprouter.${servername}.disabled)" = "1" ] && return
	# VXLAN rides on top of the VPN of the master/current server only
	([ "$(uci -q get openmptcprouter.${servername}.master)" = "1" ] || [ "$(uci -q get openmptcprouter.${servername}.current)" = "1" ]) || return
	vps_config=""
	tokenserver=$(_get_token $servername)
	server="$(echo $tokenserver | cut -f1 -d:)"
	serverport="$(echo $tokenserver | cut -f2 -d:)"
	token="$(echo $tokenserver | cut -f3 -d:)"
	[ -z "$token" ] && return
	vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	user_permission="$(echo "$vps_config" | jsonfilter -q -e '@.user.permission')"
	[ "$user_permission" != "ro" ] && _set_vxlan_vps
	_get_vxlan
}

set_vxlan() {
	config_load openmptcprouter
	config_foreach _set_vxlan_service server
}


_get_pihole() {
	[ "$(uci -q get openmptcprouter.settings.pihole_auto_conf)" = "0" ] && return
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	vpn="$(uci -q get openmptcprouter.settings.vpn)"
	piholeomr="$(uci -q get openmptcprouter.${servername}.pihole)"
	pihole="$(echo "$vps_config" | jsonfilter -q -e '@.pihole.state')"
	if [ "$pihole" = "true" ] && [ "$piholeomr" != "1" ]; then
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.pihole='1'
			commit openmptcprouter
		EOF
	elif [ "$pihole" = "false" ] && [ "$piholeomr" != "0" ]; then
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.pihole='0'
			commit openmptcprouter
		EOF
	fi

	if [ "$vpn" = "glorytun_tcp" ]; then
		host_ip="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.tcp.host_ip')"
		if [ "$pihole" = "true" ] && [ -z "$(uci -q get dhcp.@dnsmasq[0].server | grep $host_ip)" ]; then
			uci -q del_list dhcp.@dnsmasq[0].server="$(uci -q get dhcp.@dnsmasq[0].server | tr ' ' '\n' | grep '#53' | grep '10.2')"
			uci -q batch <<-EOF >/dev/null
				del_list dhcp.@dnsmasq[0].server="127.0.0.1#5353"
				add_list dhcp.@dnsmasq[0].server="$host_ip#53"
				set dhcp.@dnsmasq[0].rebind_protection='0'
				commit dhcp
			EOF
			logger -t "OMR-VPS" "Restart dnsmasq..."
			/etc/init.d/dnsmasq restart >/dev/null 2>&1
		fi
	fi
	if [ "$vpn" = "glorytun_udp" ]; then
		host_ip="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.udp.host_ip')"
		if [ "$pihole" = "true" ] && [ -z "$(uci -q get dhcp.@dnsmasq[0].server | grep $host_ip)" ]; then
			uci -q del_list dhcp.@dnsmasq[0].server="$(uci -q get dhcp.@dnsmasq[0].server | tr ' ' '\n' | grep '#53' | grep '10.2')"
			uci -q batch <<-EOF >/dev/null
				del_list dhcp.@dnsmasq[0].server="127.0.0.1#5353"
				add_list dhcp.@dnsmasq[0].server="$host_ip#53"
				set dhcp.@dnsmasq[0].rebind_protection='0'
				commit dhcp
			EOF
			logger -t "OMR-VPS" "Restart dnsmasq..."
			/etc/init.d/dnsmasq restart >/dev/null 2>&1
		fi
	fi
	if [ "$vpn" = "openvpn" ]; then
		host_ip="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.host_ip')"
		if [ "$pihole" = "true" ] && [ -z "$(uci -q get dhcp.@dnsmasq[0].server | grep $host_ip)" ]; then
			uci -q del_list dhcp.@dnsmasq[0].server="$(uci -q get dhcp.@dnsmasq[0].server | tr ' ' '\n' | grep '#53' | grep '10.2')"
			uci -q batch <<-EOF >/dev/null
				del_list dhcp.@dnsmasq[0].server="127.0.0.1#5353"
				add_list dhcp.@dnsmasq[0].server="$host_ip#53"
				set dhcp.@dnsmasq[0].rebind_protection='0'
				commit dhcp
			EOF
			logger -t "OMR-VPS" "Restart dnsmasq..."
			/etc/init.d/dnsmasq restart >/dev/null 2>&1
		fi
	fi
	if [ "$vpn" = "mlvpn" ]; then
		host_ip="$(echo "$vps_config" | jsonfilter -q -e '@.mlvpn.host_ip')"
		if [ "$pihole" = "true" ] && [ -z "$(uci -q get dhcp.@dnsmasq[0].server | grep $host_ip)" ]; then
			uci -q del_list dhcp.@dnsmasq[0].server="$(uci -q get dhcp.@dnsmasq[0].server | tr ' ' '\n' | grep '#53' | grep '10.2')"
			uci -q batch <<-EOF >/dev/null
				del_list dhcp.@dnsmasq[0].server="127.0.0.1#5353"
				add_list dhcp.@dnsmasq[0].server="$host_ip#53"
				set dhcp.@dnsmasq[0].rebind_protection='0'
				commit dhcp
			EOF
			logger -t "OMR-VPS" "Restart dnsmasq..."
			/etc/init.d/dnsmasq restart >/dev/null 2>&1
		fi
	fi
	if [ "$vpn" = "dsvpn" ]; then
		host_ip="$(echo "$vps_config" | jsonfilter -q -e '@.dsvpn.host_ip')"
		if [ "$pihole" = "true" ] && [ -z "$(uci -q get dhcp.@dnsmasq[0].server | grep $host_ip)" ]; then
			uci -q del_list dhcp.@dnsmasq[0].server="$(uci -q get dhcp.@dnsmasq[0].server | tr ' ' '\n' | grep '#53' | grep '10.2')"
			uci -q batch <<-EOF >/dev/null
				del_list dhcp.@dnsmasq[0].server="127.0.0.1#5353"
				add_list dhcp.@dnsmasq[0].server="$host_ip#53"
				set dhcp.@dnsmasq[0].rebind_protection='0'
				commit dhcp
			EOF
			logger -t "OMR-VPS" "Restart dnsmasq..."
			/etc/init.d/dnsmasq restart >/dev/null 2>&1
		fi
	fi
}

_set_redirect_ports_from_vps() {
	redirect_ports=$1
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	redirect_ports_current="$(echo "$vps_config" | jsonfilter -q -e '@.shorewall.redirect_ports')"
	[ "$redirect_ports" = "1" ] && redirect_ports_request="enable"
	[ "$redirect_ports" = "0" ] && redirect_ports_request="disable"
	if [ "$redirect_ports_request" != "$redirect_ports_current" ]; then
		settings='{"redirect_ports": "'$redirect_ports_request'"}'
		echo $(_set_json "shorewall" "$settings")
	else
		echo 1
	fi

}

_set_mptcp_vps() {
	local settings
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	mptcp_enabled_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.enabled')"
	checksum_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.checksum')"
	path_manager_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.path_manager')"
	scheduler_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.scheduler')"
	syn_retries_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.syn_retries')"
	congestion_control_current="$(echo "$vps_config" | jsonfilter -q -e '@.network.congestion_control')"
	mptcp_version_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.version')"
	close_timeout_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.close_timeout')"
	pm_type_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.pm_type')"
	stale_loss_cnt_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.stale_loss_cnt')"
	syn_retrans_current="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.syn_retrans_before_tcp_fallback')"
	mptcp_enabled="$(uci -q get network.globals.multipath)"
	if [ "$mptcp_enabled" = "disable" ]; then
		mptcp_enabled="0"
	else
		mptcp_enabled="1"
	fi
	checksum="$(uci -q get network.globals.mptcp_checksum)"
	path_manager="$(uci -q get network.globals.mptcp_path_manager)"
	scheduler="$(uci -q get network.globals.mptcp_scheduler)"
	syn_retries="$(uci -q get network.globals.mptcp_syn_retries)"
	congestion="$(uci -q get network.globals.congestion)"
	mptcp_version="$(uci -q get network.globals.mptcp_version)"
	close_timeout="$(uci -q get network.globals.mptcp_close_timeout)"
	pm_type="$(uci -q get network.globals.mptcp_pm_type)"
	stale_loss_cnt="$(uci -q get network.globals.mptcp_stale_loss_cnt)"
	syn_retrans_before_tcp_fallback="$(uci -q get network.globals.mptcp_syn_retrans_before_tcp_fallback)"
	[ -z "$mptcp_version" ] && mptcp_version="0"
	[ ! -f /proc/sys/net/mptcp/mptcp_enabled ] && mptcp_version="1"
	[ -z "$congestion" ] && congestion="bbr"
	# These four v1-only knobs are recent additions: on a router that
	# predates them, or where the user never touched them, their uci option
	# is simply absent and `uci -q get` returns "". Left as "", the JSON
	# below would post e.g. "close_timeout": "" for an int field the VPS
	# side declares as `int = 0`, which the VPS's admin API rejects with a
	# bare 422 before it ever reaches its own error handling -- see
	# https://github.com/Ysurac/openmptcprouter/issues/4350.
	[ -z "$close_timeout" ] && close_timeout="0"
	[ -z "$pm_type" ] && pm_type="0"
	[ -z "$stale_loss_cnt" ] && stale_loss_cnt="0"
	[ -z "$syn_retrans_before_tcp_fallback" ] && syn_retrans_before_tcp_fallback="0"
	if [ "$mptcp_enabled_current" != "$mptcp_enabled" ] || [ "$checksum_current" != "$checksum" ] || ([ "$path_manager_current" != "" ] && [ "$path_manager_current" != "kernel" ] && [ "$path_manager_current" != "$path_manager" ]) || ([ "$scheduler_current" != "" ] && [ "$scheduler_current" != "$scheduler" ]) || ([ "$syn_retries_current" != "" ] && [ "$syn_retries_current" != "$syn_retries" ]) || [ "$congestion_control_current" != "$congestion" ] || [ "$mptcp_version_current" != "$mptcp_version" ] || ([ -n "$close_timeout_current" ] && [ "$close_timeout_current" != "0" ] && [ "$close_timeout_current" != "$close_timeout" ]) || ([ -n "$pm_type_current" ] && [ "$pm_type_current" != "0" ] && [ "$pm_type_current" != "$pm_type" ]) || ([ -n "$stale_loss_cnt_current" ] && [ "$stale_loss_cnt_current" != "0" ] && [ "$stale_loss_cnt_current" != "$stale_loss_cnt" ]) || ([ -n "$syn_retrans_current" ] && [ "$syn_retrans_current" != "0" ] && [ "$syn_retrans_current" != "$syn_retrans_before_tcp_fallback" ]); then
		settings='{"enabled" : "'$mptcp_enabled'", "checksum": "'$checksum'","path_manager": "'$path_manager'","scheduler": "'$scheduler'","syn_retries": "'$syn_retries'","congestion_control": "'$congestion'","version": "'$mptcp_version'","close_timeout": "'$close_timeout'","pm_type": "'$pm_type'","stale_loss_cnt": "'$stale_loss_cnt'","syn_retrans_before_tcp_fallback": "'$syn_retrans_before_tcp_fallback'"}'
		echo $(_set_json "mptcp" "$settings")
	else
		echo 1
	fi
}

_get_dscp_vps_pin() {
	local section="$1" dscp remote_id download_iface
	# Reads network's anonymous `config dscp_pin` sections (added by
	# luci-app-mptcp's DSCP/Weight Routing page), one row per DSCP class
	# with independent upload_interface/download_interface. Only
	# download_interface matters here -- the download side is deliberately
	# free to point at a different WAN than the upload pin
	# (041-multipath-dscp/dscp_iface handles upload, locally, and never
	# looks at this field at all).
	download_iface="$(uci -q get network.${section}.download_interface)"
	if [ -z "$download_iface" ]; then
		# No explicit download pin for this class -- always fall back to
		# the row's own upload_interface instead of pushing nothing. This
		# mirrors mqvpn's own downlink, which always follows upload with
		# no separate field at all.
		download_iface="$(uci -q get network.${section}.upload_interface)"
		[ -z "$download_iface" ] && return
	fi
	dscp="$(uci -q get network.${section}.dscp)"
	[ -z "$dscp" ] && return
	remote_id="$(uci -q get network.${download_iface}.ip4table)"
	[ -z "$remote_id" ] && return
	[ -n "$dscp_pins" ] && dscp_pins="${dscp_pins},"
	dscp_pins="${dscp_pins}{\"dscp\":\"${dscp}\",\"remote_id\":${remote_id}}"
}

# Pushes network's dscp_pin rows' download_interface (router-side pick of
# which WAN should carry each DSCP class on the way *down*, see
# mptcp-dscp-manager) to the VPS as {dscp -> MPTCP remote endpoint id}
# pairs, so the VPS's own bpf_dscp scheduler can pin its send-side subflow
# choice for the same DSCP classes via dscp_remote_id -- see
# mptcp-bpf-dscp/debian/README.Debian for why that map is keyed by
# remote_id instead of local IP on the VPS. Closes the "no automated
# sync... copy the number over manually" gap that doc used to describe.
# download_interface is independent of the same row's upload_interface
# (applied locally, separately, by 041-multipath-dscp) -- a class's
# upload and download sides can legitimately be pinned to different WANs.
# When a row has no download_interface at all, _get_dscp_vps_pin always
# falls back to that row's upload_interface instead, so a class only
# pinned on Upload still gets a gateway-side pin rather than none.
#
# Skips the round trip to the VPS entirely (not even a GET) when the
# locally-desired pin set matches the last one we successfully pushed,
# cached in $dscp_vps_pins_cache -- cheap enough to call unconditionally
# from a tracker hook every cycle. The cache is process-ephemeral (/tmp),
# which is intentional: it forces one real push after every reboot,
# matching the BPF map itself also resetting on reboot.
_set_mptcp_dscp_vps() {
	local dscp_pins="" desired dscp_vps_pins_cache="/tmp/.mptcp_dscp_vps_pins-${servername}"
	config_load network
	config_foreach _get_dscp_vps_pin dscp_pin
	desired="[${dscp_pins}]"
	if [ "$(cat "$dscp_vps_pins_cache" 2>/dev/null)" = "$desired" ]; then
		echo 1
		return
	fi
	result=$(_set_json "mptcp_dscp" "{\"pins\":${desired}}")
	if [ -n "$result" ]; then
		echo "$desired" > "$dscp_vps_pins_cache"
		echo "$result"
	fi
}

# Standalone entry point (see EXTRA_COMMANDS) so the mptcp-dscp-manager
# tracker hook can trigger a sync without going through the full
# _config_service()/start_service() cycle. Bootstraps servername/token
# itself, same pattern as _set_vps_firewall's standalone use.
set_mptcp_dscp_vps() {
	local dscpservername=$1
	if [ -z "$dscpservername" ]; then
		config_load openmptcprouter
		config_foreach set_mptcp_dscp_vps server
		return
	fi
	servername=$dscpservername
	[ "$(uci -q get openmptcprouter.${servername}.disabled)" = "1" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.port)" ] && return
	token=""
	_login
	[ -z "$token" ] && return
	_set_mptcp_dscp_vps
}

_get_weight_vps_pin() {
	local iface="$1" weight remote_id
	weight="$(uci -q get network.${iface}.multipath_weight)"
	[ -z "$weight" ] && return
	remote_id="$(uci -q get network.${iface}.ip4table)"
	[ -z "$remote_id" ] && return
	[ -n "$weight_pins" ] && weight_pins="${weight_pins},"
	weight_pins="${weight_pins}{\"remote_id\":${remote_id},\"weight\":${weight}}"
}

# VPS-side counterpart of _set_mptcp_dscp_vps, same reasoning: pushes
# network.<iface>.multipath_weight (router-side bpf_weight/bpf_weight_rr
# weights, see mptcp-weight-manager) to the VPS as
# {MPTCP remote endpoint id -> weight} pairs, so the VPS's own scheduler
# can weight its send-side subflow choice for the same WANs via
# weight_remote_id instead of defaulting to neutral 100 for everything.
_set_mptcp_weight_vps() {
	local weight_pins="" desired weight_vps_pins_cache="/tmp/.mptcp_weight_vps_pins-${servername}"
	config_load network
	config_foreach _get_weight_vps_pin interface
	desired="[${weight_pins}]"
	if [ "$(cat "$weight_vps_pins_cache" 2>/dev/null)" = "$desired" ]; then
		echo 1
		return
	fi
	result=$(_set_json "mptcp_weight" "{\"weights\":${desired}}")
	if [ -n "$result" ]; then
		echo "$desired" > "$weight_vps_pins_cache"
		echo "$result"
	fi
}

# Standalone entry point (see EXTRA_COMMANDS), same bootstrap pattern as
# set_mptcp_dscp_vps.
set_mptcp_weight_vps() {
	local wservername=$1
	if [ -z "$wservername" ]; then
		config_load openmptcprouter
		config_foreach set_mptcp_weight_vps server
		return
	fi
	servername=$wservername
	[ "$(uci -q get openmptcprouter.${servername}.disabled)" = "1" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.port)" ] && return
	token=""
	_login
	[ -z "$token" ] && return
	_set_mptcp_weight_vps
}

# Reads one DSCP class's current ipset/nftables-set members on this router
# ($2 = "4" or "6"), trying both the legacy iptables+ipset naming (omr-dscp:
# omr_dscp-<class> / omr_dscp6-<class>) and the fw4/nftables naming
# (omr-dscp-nft: omr_dscp_<class>_4 / omr_dscp_<class>_6) -- exactly one of
# the two variants is installed, the other command is just a silent no-op.
# `nft list set`'s plain text output (not -j/JSON) is used deliberately: its
# "elements = { a, b, c }" line has been a stable format for years and is a
# single sed+tr away from a token list, whereas the JSON element shape
# differs (bare string vs {"prefix":...} object) depending on whether an
# entry was inserted as a single IP or a CIDR, which would need two
# different jsonfilter paths to cover reliably.
_get_dscp_class_members() {
	local class="$1" fam="$2" legacy_name

	[ "$fam" = "6" ] && legacy_name="omr_dscp6-${class}" || legacy_name="omr_dscp-${class}"

	ipset -q -o save list "$legacy_name" 2>/dev/null | awk '/^add/ {print $3}'
	nft list set inet fw4 "omr_dscp_${class}_${fam}" 2>/dev/null \
		| sed -n 's/.*elements = { *\(.*[^ ]\) *}.*/\1/p' \
		| tr ',' '\n' | sed 's/^ *//;s/ *$//' | grep -v '^$'
}

_get_dscp_classify_entries() {
	local class="$1" cidr
	for cidr in $(_get_dscp_class_members "$class" 4) $(_get_dscp_class_members "$class" 6); do
		[ -n "$dscp_classify_entries" ] && dscp_classify_entries="${dscp_classify_entries},"
		dscp_classify_entries="${dscp_classify_entries}{\"dscp\":\"${class}\",\"cidr\":\"${cidr}\"}"
	done
}

# Pushes the router's live omr-dscp/omr-dscp-nft classification (which
# destination IPs are currently resolved into which DSCP class, e.g. by
# domain lists such as config/dscp's `config domains` and
# videochatipv4.list/videochatipv6.list) to the VPS, so the VPS's own
# outbound traffic to those same destinations -- e.g. ssserver's connection
# on behalf of a proxied session, see shadowsocks-rust's
# 002-add-dynamic-per-connection-dscp-reflection.patch for the router-side
# half of this -- can be marked with the same DSCP via a mirrored
# ipset+mangle rule on the VPS, without ever needing the DSCP bits
# themselves to survive the router->VPS hop on the wire.
#
# Unlike _set_mptcp_dscp_vps/_set_mptcp_weight_vps (a handful of pins that
# rarely change), this table can run into hundreds of resolved IPs and
# grows continuously as LAN devices browse to newly-resolved destinations,
# so on top of the same "skip if unchanged" cache this also self-throttles
# to at most one real attempt (enumerating up to 9 classes x 2 families of
# ipset/nft lookups, then an HTTPS round trip) per
# DSCP_CLASSIFY_MIN_INTERVAL seconds, independent of how often the calling
# hook itself fires.
_set_dscp_classify_vps() {
	local dscp_classify_entries="" desired class now last \
		dscp_classify_cache="/tmp/.dscp_classify_vps-${servername}" \
		dscp_classify_ts="/tmp/.dscp_classify_vps_ts-${servername}"

	now=$(date +%s)
	last=$(cat "$dscp_classify_ts" 2>/dev/null)
	if [ -n "$last" ] && [ $((now - last)) -lt "${DSCP_CLASSIFY_MIN_INTERVAL:-300}" ]; then
		echo 1
		return
	fi
	echo "$now" > "$dscp_classify_ts"

	for class in cs0 cs1 cs2 cs3 cs4 cs5 cs6 cs7 ef; do
		_get_dscp_classify_entries "$class"
	done
	desired="[${dscp_classify_entries}]"
	if [ "$(cat "$dscp_classify_cache" 2>/dev/null)" = "$desired" ]; then
		echo 1
		return
	fi
	result=$(_set_json "dscp_classify" "{\"entries\":${desired}}")
	if [ -n "$result" ]; then
		echo "$desired" > "$dscp_classify_cache"
		echo "$result"
	fi
}

# Standalone entry point (see EXTRA_COMMANDS), same bootstrap pattern as
# set_mptcp_dscp_vps/set_mptcp_weight_vps.
set_dscp_classify_vps() {
	local dcservername=$1
	if [ -z "$dcservername" ]; then
		config_load openmptcprouter
		config_foreach set_dscp_classify_vps server
		return
	fi
	servername=$dcservername
	[ "$(uci -q get openmptcprouter.${servername}.disabled)" = "1" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.port)" ] && return
	token=""
	_login
	[ -z "$token" ] && return
	_set_dscp_classify_vps
}

_set_vpn_vps() {
	local settings
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	vpn_current="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.current')"
	vpn="$(uci -q get openmptcprouter.settings.vpn)"
	if [ "$vpn_current" != "$vpn" ]; then
		settings='{"vpn" : "'$vpn'"}'
		echo $(_set_json "vpn" "$settings")
	else
		echo 1
	fi
}

_set_proxy_vps() {
	local settings
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	proxy_current="$(echo "$vps_config" | jsonfilter -q -e '@.proxy.current')"
	proxy="$(uci -q get openmptcprouter.settings.proxy)"
	if [ "$proxy_current" != "$proxy" ]; then
		settings='{"proxy" : "'$proxy'"}'
		echo $(_set_json "proxy" "$settings")
	else
		echo 1
	fi
}

_get_local_wan_ip() {
	wanip="$(uci -q get openmptcprouter.$1.publicip)"
	[ -n "$wanip" ] && {
		[ -z "$wanips" ] && wanips=$wanip || wanips="$wanips"'\n'"$wanip"
	}
}

_set_wan_ip() {
	local settings
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	wanip_current="$(echo "$vps_config" | jsonfilter -q -e '@.wan.ips')"
	if [ -n "$wanips" ] && [ "$wanip_current" != "$wanips" ]; then
		settings='{"ips" : "'$wanips'"}'
		result=$(_set_json "wan" "$settings")
		#echo $(_set_json "wan" "$settings")
	#else
	#	echo 1
	fi
}

_get_lan_ip() {
	local intf=$1
	if [ "$(uci -q get firewall.zone_lan.network | grep $intf)" != "" ]; then
		lanip="$(uci -q get network.${intf}.ipaddr)/$(uci -q get network.${intf}.netmask)"
		if [ "$lanip" != "/" ]; then
			if [ -z "$lanips" ]; then
				lanips='"'${lanip}'"'
			else
				lanips='"'$lanips'" "'${lanip}'"'
			fi
		fi
	fi
}

_set_lan_ip() {
	local settings
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	lanip_current="$(echo "$vps_config" | jsonfilter -q -e '@.lan.ips' | sed -e 's:\\::g' -e 's:\[::g' -e 's:\]::g' -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//')"
	if [ "$lanips" != "" ] && [ "$lanip_current" != "$lanips" ]; then
	#if [ "$lanips" != "" ]; then
		settings='{"lanips" : ['$lanips']}'
		result=$(_set_json "lan" "$settings")
	fi
}

_set_bypass_ips() {
	local settings
	[ -z "$servername" ] && servername=$1
	bypassipv4s=$(ipset -q -o save list omr_dst_bypass_srv_vpn1_4 2>/dev/null | awk '/add/ NF {print "\""$3"\""}' | tr '\n' ',' | sed 's/,$//')
	[ -z "$bypassipv4s" ] && {
		bypassipv4slst=$(nft -j list set inet fw4 "omr_dst_bypass_srv_vpn1_4"  2>/dev/null)
		[ -n "$bypassipv4slst" ] && bypassipv4s=$(echo "$bypassipv4slst" | jsonfilter -q -e @.nftables[1].set.elem[*].prefix | awk '{gsub(/"/,"",$3);gsub(/,/,"/",$3); print $3 $5}')
	}
	bypassipv6s=$(ipset -q -o save list omr6_dst_bypass_srv_vpn1_6  2>/dev/null | awk '/add/ NF {print "\""$3"\""}' | tr '\n' ',' | sed 's/,$//')
	[ -z "$bypassipv6s" ] && {
		bypassipv6slst=$(nft -j list set inet fw4 "omr_dst_bypass_srv_vpn1_6"  2>/dev/null)
		[ -n "$bypassipv6slst" ] && bypassipv6s=$(echo "$bypassipv6slst" | jsonfilter -q -e @.nftables[1].set.elem[*].prefix | awk '{gsub(/"/,"",$3);gsub(/,/,"/",$3); print $3 $5}')
	}
	# "
	if [ "$bypassipv4s" != "" ] || [ "$bypassipv6s" != "" ]; then
		settings='{"ipv4s" : ['$bypassipv4s'],"ipv6s" : ['$bypassipv6s'],"intf" : "vpn1"}'
		result=$(_set_json "bypass" "$settings")
	fi
}

set_bypass_ips() {
	config_load openmptcprouter
	config_foreach _set_bypass_ips server
}

_set_vpn_ip() {
	local settings
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	[ "$(uci -q get openmptcprouter.settings.vpn)" = "none" ] && return
	vpnifname="$(uci -q get network.omrvpn.device)"
	vpnip_local_current="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.remoteip')"
	vpnip_local=$(ip -4 -br addr ls dev ${vpnifname} 2>/dev/null | head -n 1 | awk -F'[ /]+' '{print $3}')
	[ -z "$vpnip_local" ]  && vpnip_local=$(ip -4 addr show dev "$OMR_TRACKER_DEVICE" | grep -m 1 inet | awk '{print $2}' | cut -d'/' -s -f1)
	vpnip_remote_current="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.localip')"
	vpnip_remote=$(ip -4 r show default dev ${vpnifname} | head -n 1 | awk '{print $3}' | tr -d "\n")
	[ -z "$vpnip_remote" ] && [ -n "$vpnifname" ] && vpnip_remote=$(ip -4 r list dev ${vpnifname} 2>/dev/null | grep -m 1 kernel | awk '{print $1}' | tr -d "\n")
	[ -z "$vpnip_remote" ] && [ -n "$vpnifname" ] && vpnip_remote=$(ip -4 r list dev ${vpnifname} 2>/dev/null | grep -m 1 "proto static src" | awk '{print $3}' | tr -d "\n")
	[ -z "$vpnip_remote" ] && vpnip_remote=$(ifstatus omrvpn | jsonfilter -e '@.route[0].nexthop')
	[ -z "$vpnip_remote" ] && [ "$vpnifname" = "bonding-omrvpn" ] && vpnip_remote="10.255.248.1"
	ula="$(uci -q get network.globals.ula_prefix)"
	ula_current="$(echo "$vps_config" | jsonfilter -q -e '@.ip6in4.ula')"
	if [ "$vpnip_remote" != "" ] && [ "$vpnip_local" != "" ] && ([ "$vpnip_remote" != "$vpnip_remote_current" ] || [ "$vpnip_local" != "$vpnip_local_current" ] || [ "$ula" != "$ula_current" ]); then
		settings='{"remoteip" : "'$vpnip_local'","localip" : "'$vpnip_remote'","ula" : "'$ula'"}'
		result=$(_set_json "vpnips" "$settings")
	fi
}

set_vpn_ip() {
	_set_vpn_ip
}


_set_sipalg() {
	local settings
	sipalg="$(uci -q get openmptcprouter.settings.sipalg)"
	if [ "$sipalg" = "1" ]; then
		sipalg="true"
	else
		sipalg="false"
	fi
	settings='{"enable" : "'$sipalg'"}'
	result=$(_set_json "sipalg" "$settings")
}


_delete_client2client() {
	if [ -n "$(echo $1 | grep omr_client2client)" ]; then
		uci -q delete network.$1
	fi
}

_set_client2client() {
	local c2cid=0
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	c2cips="$(echo "$vps_config" | jsonfilter -q -e '@.client2client.lanips[0]')"
	vpnifname="$(uci -q get network.omrvpn.device)"
	vpnip_local=$(ip -4 -br addr ls dev ${vpnifname} | awk -F'[ /]+' '{print $3}' | tr -d "\n")
	vpnip_remote=$(ip -4 r list dev ${vpnifname} | grep via | grep -v default | grep -v / | grep -v metric | awk '{print $1}' | tr -d "\n")
	for lanip in $c2cips; do
		c2cid=$((c2cid+1))
		targetip=$(echo $lanip | awk -F '/' '{print $1}' | tr -d "\n")
		netmask=$(echo $lanip | awk -F '/' '{print $2}' | tr -d "\n")
		target=$(ipcalc.sh $targetip/$netmask | grep NETWORK | awk -F '=' '{print $2}' | tr -d "\n")
		uci -q batch <<-EOF >/dev/null
			set network.omr_client2client_${c2cid}=route
			set network.omr_client2client_${c2cid}.interface=omrvpn
			set network.omr_client2client_${c2cid}.target="${target}"
			set network.omr_client2client_${c2cid}.netmask="${netmask}"
			set network.omr_client2client_${c2cid}.gateway="${vpnip_remote}"
		EOF
	done
	[ -n "$(uci -q changes network)" ] && uci -q commit network
}

_vps_firewall_redirect_port() {
	local src proto src_dport section
	section=$1
	config_get src $1 src
	config_get proto $1 proto "tcp udp"
	config_get src_dport $1 src_dport
	config_get family $1 family "ipv4"
	config_get enabled $1 enabled "1"
	config_get src_dip $1 src_dip
	config_get dest_ip $1 dest_ip
	config_get dest_port $1 dest_port
	config_get src_ip $1 src_ip
	config_get v2ray $1 v2ray "0"
	config_get name $1 name
	config_get dmz $1 dmz "0"
	config_get target $1 target "REDIRECT"
	[ "$target" = "MARK" ] && return
	[ "$target" = "DSCP" ] && return
	if [ -z "$src_dport" ] && [ -n "$dest_port" ]; then
		src_dport=$dest_port
	fi
	if [ "$dmz" = "1" ] && [ "$src_dport" != "2-64999" ]; then
		uci -q batch <<-EOF >/dev/null
			set firewall.${section}.src_dport='2-64999'
			commit firewall
		EOF
		src_dport='2-64999'
	fi
	[ -n "$src_dport" ] && src_dport=$(echo $src_dport | sed 's/:/-/')
	if [ -n "$src_dport" ] && [ "$(echo $src_dport | cut -d'-' -f2)" -ge "65000" ]; then
		logger -t "OMR-VPS" "You can't redirect ports >= 65000, they are needed by OpenMPTCProuter Server part"
		enabled="0"
		#uci -q delete firewall.$1
		#return
	fi
	xray="$v2ray"
	([ "$(uci -q get v2ray.main.enabled)" != "1" ] || [ "$(uci -q get openmptcprouter.omr.v2ray)" = "down" ]) && v2ray="0"
	([ "$(uci -q get xray.main.enabled)" != "1" ] || [ "$(uci -q get openmptcprouter.omr.xray)" = "down" ]) && xray="0"
	[ "$proto" = "all" ] && proto="tcp udp"
	[ "$proto" = "" ] && proto="tcp udp"
	[ "$src" = "vpn" ] && [ -n "$proto" ] && [ -n "$src_dport" ] && [ "$enabled" != "0" ] && [ "$name" != "Allow-DHCP-Request-VPN" ] && {
		for protoi in $proto; do
			if [ "$v2ray" = "0" ] && [ "$xray" = "0" ]; then
				checkfw=""
				if [ "$family" = "ipv4" ]; then
					if [ "$src_dip" = "" ] && [ "$src_ip" = "" ]; then
						checkfw=$(echo "$vpsfwlist" | grep "$src_dport	# OMR $username redirect router $src_dport port $protoi")
					else
						comment=""
						[ -n "$src_dip" ] && {
							comment=" to $src_dip"
							if [ ! -f /usr/sbin/nft ]; then
								$IPTABLESSAVE --counters | sed "s:-d $src_dip/32::g" | $IPTABLESRESTORE -w
							else
								rule=$(nft -a list table inet fw4 | grep -m1 "ip daddr $src_dip")
								[ -n "$rule" ] && {
									handle=$(echo $rule | awk '{ print $NF }')
									nft replace rule inet fw4 dstnat_lan handle ${handle} $(echo $rule | awk '{ sub(" comment.*$",""); print }' | sed "s/ip daddr ${src_dip}//") 2>&1 >/dev/null
								}
								rule=$(nft -a list table inet fw4 | grep -m1 "ip daddr $src_dip")
								[ -n "$rule" ] && {
									handle=$(echo $rule | awk '{ print $NF }')
									nft replace rule inet fw4 dstnat_vpn handle $handle $(echo $rule | awk '{ sub(" comment.*$",""); print }' | sed "s/ip daddr ${src_dip}/meta nfproto ipv4/") 2>&1 >/dev/null
								}
							fi
						}
						[ -n "$src_ip" ] && comment=" from $src_ip"
						if [ "$target" = "ACCEPT" ]; then
							checkfw=$(echo "$vpsfwlist" | grep "# OMR $username open router $src_dport port ${protoi}${comment}")
						else
							checkfw=$(echo "$vpsfwlist" | grep "# OMR $username redirect router $src_dport port ${protoi}${comment}")
						fi
					fi
				else
					if [ "$src_dip" = "" ] && [ "$src_ip" = "" ]; then
						if [ "$target" = "ACCEPT" ]; then
							checkfw=$(echo "$vpsfw6list" | grep "$src_dport	# OMR $username open router $src_dport port $protoi")
						else
							checkfw=$(echo "$vpsfw6list" | grep "$src_dport	# OMR $username redirect router $src_dport port $protoi")
						fi
					else
						comment=""
						[ -n "$src_dip" ] && comment=" to $src_dip"
						[ -n "$src_ip" ] && comment=" from $src_ip"
						if [ "$target" = "ACCEPT" ]; then
							checkfw=$(echo "$vpsfw6list" | grep "# OMR $username open router $src_dport port ${protoi}${comment}")
						else
							checkfw=$(echo "$vpsfw6list" | grep "# OMR $username redirect router $src_dport port ${protoi}${comment}")
						fi
					fi
				fi
				if [ "$checkfw" = "" ]; then
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","source_dip" : "'$src_dip'","source_ip" : "'$src_ip'","proto" : "'$protoi'","fwtype" : "'$target'","ipproto" : "'$family'"}'
					result=$(_set_json "shorewallopen" "$settings")
				fi
				if [ "$family" = "ipv4" ]; then
					if [ "$src_dip" = "" ] && [ "$src_ip" = "" ]; then
						if [ "$target" = "ACCEPT" ]; then
							vpsfwlist=$(echo "$vpsfwlist" | grep -v "$src_dport	# OMR $username open router $src_dport port $protoi$")
							[ "$username" = "openmptcprouter" ] && vpsfwlist=$(echo "$vpsfwlist" | grep -v "$src_dport	# OMR open router $src_dport port $protoi$")
						else
							vpsfwlist=$(echo "$vpsfwlist" | grep -v "$src_dport	# OMR $username redirect router $src_dport port $protoi$")
							[ "$username" = "openmptcprouter" ] && vpsfwlist=$(echo "$vpsfwlist" | grep -v "$src_dport	# OMR redirect router $src_dport port $protoi$")
						fi
					else
						comment=""
						[ -n "$src_dip" ] && comment=" to $src_dip"
						[ -n "$src_ip" ] && comment=" from $src_ip"
						if [ "$target" = "ACCEPT" ]; then
							vpsfwlist=$(echo "$vpsfwlist" | grep -v "# OMR $username open router $src_dport port ${protoi}${comment}$")
							[ "$username" = "openmptcprouter" ] && vpsfwlist=$(echo "$vpsfwlist" | grep -v "# OMR open router $src_dport port ${protoi}${comment}$")
						else
							vpsfwlist=$(echo "$vpsfwlist" | grep -v "# OMR $username redirect router $src_dport port ${protoi}${comment}")
							[ "$username" = "openmptcprouter" ] && vpsfwlist=$(echo "$vpsfwlist" | grep -v "# OMR redirect router $src_dport port ${protoi}${comment}$")
						fi
					fi
				else
					if [ "$src_dip" = "" ] && [ "$src_ip" = "" ]; then
						if [ "$target" = "ACCEPT" ]; then
							vpsfw6list=$(echo "$vpsfw6list" | grep -v "$src_dport	# OMR $username open router $src_dport port $protoi$")
							[ "$username" = "openmptcprouter" ] && vpsfw6list=$(echo "$vpsfw6list" | grep -v "$src_dport	# OMR open router $src_dport port $protoi$")
						else
							vpsfw6list=$(echo "$vpsfw6list" | grep -v "$src_dport	# OMR $username redirect router $src_dport port $protoi$")
							[ "$username" = "openmptcprouter" ] && vpsfw6list=$(echo "$vpsfw6list" | grep -v "$src_dport	# OMR redirect router $src_dport port $protoi$")
						fi
					else
						[ -n "$src_dip" ] && comment=" to $src_dip"
						[ -n "$src_ip" ] && comment=" from $src_ip"
						if [ "$target" = "ACCEPT" ]; then
							vpsfw6list=$(echo "$vpsfw6list" | grep -v "# OMR $username open router $src_dport port ${protoi}${comment}$")
							[ "$username" = "openmptcprouter" ] && vpsfw6list=$(echo "$vpsfw6list" | grep -v "# OMR open router $src_dport port ${protoi}${comment}$")
						else
							vpsfw6list=$(echo "$vpsfw6list" | grep -v "# OMR $username redirect router $src_dport port ${protoi}${comment}$")
							[ "$username" = "openmptcprouter" ] && vpsfw6list=$(echo "$vpsfw6list" | grep -v "# OMR redirect router $src_dport port ${protoi}${comment}$")
						fi
					fi
				fi
			else
				# Always (re)issue shorewallopen + v2rayredirect/xrayredirect for
				# the active family instead of gating on a "does a shorewall
				# line already mention this family" text match: v2ray_add_port/
				# xray_add_port on the VPS are already idempotent (they key off
				# the inbound tag and no-op if it exists), and the previous
				# text-match gate went stale as soon as a proxy-family switch
				# left old comment text behind (see the resulting nft errors
				# below this loop and the cross-family unredirect a few lines
				# down) -- a stale match then wrongly skipped re-adding the
				# redirect for the now-active family, silently breaking the
				# forward instead of healing it.
				if [ "$family" = "ipv4" ] && [ -n "$src_dip" ]; then
					if [ ! -f /usr/sbin/nft ]; then
						$IPTABLESSAVE --counters | sed "s:-d $src_dip/32::g" | $IPTABLESRESTORE -w
					else
						rule=$(nft -a list table inet fw4 | grep -m1 "ip daddr $src_dip")
						[ -n "$rule" ] && {
							handle=$(echo $rule | awk '{ print $NF }')
							nft replace rule inet fw4 dstnat_lan handle ${handle} $(echo $rule | awk '{ sub(" comment.*$",""); print }' | sed "s/ip daddr ${src_dip}//") 2>&1 >/dev/null
						}
						rule=$(nft -a list table inet fw4 | grep -m1 "ip daddr $src_dip")
						[ -n "$rule" ] && {
							handle=$(echo $rule | awk '{ print $NF }')
							nft replace rule inet fw4 dstnat_vpn handle $handle $(echo $rule | awk '{ sub(" comment.*$",""); print }' | sed "s/ip daddr ${src_dip}/meta nfproto ipv4/") 2>&1 >/dev/null
						}
					fi
				fi
				[ "$v2ray" = "1" ] && settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","source_dip" : "'$src_dip'","source_ip" : "'$src_ip'","proto" : "'${protoi}'","fwtype" : "ACCEPT","ipproto" : "'$family'","comment" : "V2Ray to '${dest_ip}':'${dest_port}'"}'
				[ "$xray" = "1" ] && settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","source_dip" : "'$src_dip'","source_ip" : "'$src_ip'","proto" : "'${protoi}'","fwtype" : "ACCEPT","ipproto" : "'$family'","comment" : "XRay to '${dest_ip}':'${dest_port}'"}'
				result=$(_set_json "shorewallopen" "$settings")
				[ -z "$dest_port" ] && dest_port="$src_dport"
				settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","destip" : "'$dest_ip'","destport" : "'$dest_port'","proto" : "'${protoi}'"}'
				[ "$v2ray" = "1" ] && result=$(_set_json "v2rayredirect" "$settings")
				[ "$xray" = "1" ] && result=$(_set_json "xrayredirect" "$settings")
				# A forwarded port can only be safely bound to one proxy
				# family's dokodemo-door inbound at a time: the VPS runs
				# v2ray and xray simultaneously, so a stale redirect left on
				# the other family after a proxy switch competes for the same
				# port (SO_REUSEPORT) and randomly blackholes connections
				# that land on the family with no live reverse bridge.
				# Explicitly tear down the other family's redirect for this
				# exact port+destination every pass, not just when (re)adding
				# our own -- otherwise a stale entry left from before this
				# script gained cross-family cleanup (or from a run that hit
				# this same race) never gets closed.
				settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","destip" : "'$dest_ip'","destport" : "'$dest_port'","proto" : "'${protoi}'"}'
				[ "$v2ray" = "1" ] && result=$(_set_json "xrayunredirect" "$settings")
				[ "$xray" = "1" ] && result=$(_set_json "v2rayunredirect" "$settings")
				# Strip both the V2Ray- and XRay-tagged variant of this rule from
				# the stale-entry list unconditionally (not just the currently
				# active family's). A proxy-family switch rewrites the shorewall
				# comment to the new family without going through this cleanup
				# for the old one, so the old family's line otherwise survives
				# into _vps_firewall_close_port below. Since v2ray_del_port/
				# xray_del_port on the VPS key their delete purely off
				# proto+port+destip:destport (not family), that stale close call
				# then deletes the redirect this same pass just (re)created —
				# the port-forward would appear to "randomly" stop working after
				# switching proxy family on the same forwarded port (issue #4315
				# follow-up).
				if [ "$family" = "ipv4" ]; then
					if [ "$src_dip" = "" ] && [ "$src_ip" = "" ]; then
						vpsfwlist=$(echo "$vpsfwlist" | grep -v "$src_dport	# OMR $username open router $src_dport port ${protoi} --- V2Ray to ${dest_ip}:${dest_port}$")
						vpsfwlist=$(echo "$vpsfwlist" | grep -v "$src_dport	# OMR $username open router $src_dport port ${protoi} --- XRay to ${dest_ip}:${dest_port}$")
						[ "$username" = "openmptcprouter" ] && vpsfwlist=$(echo "$vpsfwlist" | grep -v "$src_dport	# OMR open router $src_dport port ${protoi} --- V2Ray to ${dest_ip}:${dest_port}$")
						[ "$username" = "openmptcprouter" ] && vpsfwlist=$(echo "$vpsfwlist" | grep -v "$src_dport	# OMR open router $src_dport port ${protoi} --- XRay to ${dest_ip}:${dest_port}$")
					else
						comment=""
						[ -n "$src_dip" ] && comment=" to $src_dip"
						[ -n "$src_ip" ] && comment=" from $src_ip"
						vpsfwlist=$(echo "$vpsfwlist" | grep -v "# OMR $username open router $src_dport port ${protoi}${comment} --- V2Ray to ${dest_ip}:${dest_port}$")
						vpsfwlist=$(echo "$vpsfwlist" | grep -v "# OMR $username open router $src_dport port ${protoi}${comment} --- XRay to ${dest_ip}:${dest_port}$")
						[ "$username" = "openmptcprouter" ] && vpsfwlist=$(echo "$vpsfwlist" | grep -v "# OMR open router $src_dport port ${protoi}${comment} --- V2Ray to ${dest_ip}:${dest_port}$")
						[ "$username" = "openmptcprouter" ] && vpsfwlist=$(echo "$vpsfwlist" | grep -v "# OMR open router $src_dport port ${protoi}${comment} --- XRay to ${dest_ip}:${dest_port}$")
					fi
				else
					if [ "$src_dip" = "" ] && [ "$src_ip" = "" ]; then
						vpsfw6list=$(echo "$vpsfw6list" | grep -v "$src_dport	# OMR $username open router $src_dport port ${protoi} --- V2Ray to ${dest_ip}:${dest_port}$")
						vpsfw6list=$(echo "$vpsfw6list" | grep -v "$src_dport	# OMR $username open router $src_dport port ${protoi} --- XRay to ${dest_ip}:${dest_port}$")
						[ "$username" = "openmptcprouter" ] && vpsfw6list=$(echo "$vpsfw6list" | grep -v "$src_dport	# OMR open router $src_dport port ${protoi} --- V2Ray to ${dest_ip}:${dest_port}$")
						[ "$username" = "openmptcprouter" ] && vpsfw6list=$(echo "$vpsfw6list" | grep -v "$src_dport	# OMR open router $src_dport port ${protoi} --- XRay to ${dest_ip}:${dest_port}$")
					else
						[ -n "$src_dip" ] && comment=" to $src_dip"
						[ -n "$src_ip" ] && comment=" from $src_ip"
						vpsfw6list=$(echo "$vpsfw6list" | grep -v "# OMR $username open router $src_dport port ${protoi}${comment} --- V2Ray to ${dest_ip}:${dest_port}$")
						vpsfw6list=$(echo "$vpsfw6list" | grep -v "# OMR $username open router $src_dport port ${protoi}${comment} --- XRay to ${dest_ip}:${dest_port}$")
						[ "$username" = "openmptcprouter" ] && vpsfw6list=$(echo "$vpsfw6list" | grep -v "# OMR open router $src_dport port ${protoi}${comment} --- V2Ray to ${dest_ip}:${dest_port}$")
						[ "$username" = "openmptcprouter" ] && vpsfw6list=$(echo "$vpsfw6list" | grep -v "# OMR open router $src_dport port ${protoi}${comment} --- XRay to ${dest_ip}:${dest_port}$")
					fi
				fi
			fi
		done
	}
}

_vps_firewall_close_port() {
	[ -n "$vpsfwlist" ] && {
		echo "$vpsfwlist" | while read -r line; do
			[ -n "$line" ] && {
				type=$(echo $line | awk '{print $1}' | tr -d "\n")
				proto=$(echo $line | awk '{print $4}' | tr -d "\n")
				src_dport=$(echo $line | awk '{print $5}' | tr -d "\n")
				source_port=$(echo $line | awk '{print $6}' | tr -d "\n")
				source_dip=$(echo $line | awk '{print $7}' | tr -d "\n")
				comment=$(echo $line | awk -F' --- ' '{print $2}' | tr -d "\n")
				[ "$source_dip" = "#" ] && source_dip=""
				source_ip=$(echo $line | awk '{print $2}' | awk -F ":" '{print $2}' | tr -d "\n")
				if [ "$source_port" = "-" ]; then
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","proto" : "'$proto'","fwtype" : "DNAT","source_dip": "'$source_dip'","source_ip": "'$source_ip'","comment" : "'$comment'"}'
				elif [ "$source_ip" != "" ]; then
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","proto" : "'$proto'","fwtype" : "DNAT","source_ip": "'$source_ip'","comment" : "'$comment'"}'
				else
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","proto" : "'$proto'","fwtype" : "DNAT","comment" : "'$comment'"}'
				fi
				result=$(_set_json "shorewallclose" "$settings")
				[ "$type" = "ACCEPT" ] && {
					destip=$(echo $comment | awk '{print $3}' | awk -F: '{print $1}')
					destport=$(echo $comment | awk '{print $3}' | awk -F: '{print $2}')
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","proto" : "'$proto'","destip" : "'$destip'","destport": "'$destport'"}'
					result=$(_set_json "v2rayunredirect" "$settings")
					result=$(_set_json "xrayunredirect" "$settings")
				}
			}
		done
	}
	[ -n "$vpsfw6list" ] && {
		echo "$vpsfw6list" | while read -r line; do
			[ -n "$line" ] && {
				type=$(echo $line | awk '{print $1}' | tr -d "\n")
				proto=$(echo $line | awk '{print $4}' | tr -d "\n")
				src_dport=$(echo $line | awk '{print $5}' | tr -d "\n")
				source_port=$(echo $line | awk '{print $6}' | tr -d "\n")
				source_dip=$(echo $line | awk '{print $7}' | tr -d "\n")
				comment=$(echo $line | awk -F' --- ' '{print $2}' | tr -d "\n")
				[ "$source_dip" = "#" ] && source_dip=""
				source_ip=$(echo $line | awk '{print $2}' | awk -F ":" '{print $2}' | tr -d "\n")
				if [ "$source_port" = "-" ]; then
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","proto" : "'$proto'","fwtype" : "DNAT","ipproto" : "ipv6","source_dip": "'$source_dip'","source_ip": "'$source_ip'","comment" : "'$comment'"}'
				elif [ "$source_ip" != "" ]; then
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","proto" : "'$proto'","fwtype" : "DNAT","ipproto" : "ipv6","source_dip": "'$source_dip'","source_ip": "'$source_ip'","comment" : "'$comment'"}'
				else
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","proto" : "'$proto'","fwtype" : "DNAT","ipproto" : "ipv6","comment" : "'$comment'"}'
				fi
				result=$(_set_json "shorewallclose" "$settings")
				[ "$type" = "ACCEPT" ] && {
					settings='{"name" : "router '$src_dport'","port" : "'$src_dport'","proto" : "'$proto'","comment" : "'$comment'"}'
					result=$(_set_json "v2rayunredirect" "$settings")
					result=$(_set_json "xrayunredirect" "$settings")
				}
			}
		done
	}
}

_set_vps_firewall() {
	fw3 -q print | grep 'vpn.* -d' |
	while IFS=$"\n" read -r c; do
		eval $(echo $c | sed 's/iptables/iptables -w/' | sed 's/-A/-D/') 2>&1 >/dev/null
		publicip=$(echo $c | awk -F'-d' '{print $2}' | cut -d '/' -f1 | sed 's/ //g')
		[ -n "$publicip" ] && {
			greintf=$(uci show network | grep "Tunnel for $publicip" | cut -d '.' -f2 | tr -d "\n")
			[ -n "$greintf" ] && greip=$(uci -q get network.${greintf}.ipaddr)
		}
		if [ -z "$greip" ]; then
			newrule=$(echo $c | sed 's/iptables/iptables -w/' | sed -E -e 's/ -d ([^ ])*//' -e 's/ -s ([^ ])*//')
		else
			newrule=$(echo $c | sed 's/iptables/iptables -w/' | sed -E -e "s/ -d ([^ ])*/ -d ${greip}\/255.255.255.252/" -e 's/ -s ([^ ])*//')
		fi
		eval $(echo $newrule | sed 's/-A/-D/') || true
		eval $newrule
	done
	#'

	fwservername=$1
	[ -n "$fwservername" ] && servername=$fwservername
	[ -z "$fwservername" ] && fwservername=$servername
	[ "$(uci -q get openmptcprouter.${fwservername}.nofwredirect)" = "1" ] && return
	[ -z "$(uci -q get openmptcprouter.${fwservername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${fwservername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${fwservername}.port)" ] && return
	token=""
	vps_config=""
	_login
	[ -z "$token" ] && {
		[ "$(logread | tail -n 3 | grep -c 'vps token')" -lt 3 ] && logger -t "OMR-VPS" "Can't get ${fwservername} token, try later"
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${fwservername}.admin_error=1
		EOF
		return
	}
	username="$(uci -q get openmptcprouter.${fwservername}.username)"
	settings='{"name" : "redirect router"}'
	fw_list=$(_set_json "shorewalllist" "$settings")
	[ -z "$fw_list" ] && return
	vpsfwlist=$(echo $fw_list | jsonfilter -q -e '@.list[*]' | sed '/^[[:space:]]*$/d')
	settings='{"name" : "open router"}'
	fw_list=$(_set_json "shorewalllist" "$settings")
	[ -z "$fw_list" ] && return
	if [ -n "$vpsfwlist" ]; then
		vpsfwlist="${vpsfwlist}"$'\n'"$(echo $fw_list | jsonfilter -q -e '@.list[*]' | sed '/^[[:space:]]*$/d')"
	else
		vpsfwlist="$(echo $fw_list | jsonfilter -q -e '@.list[*]' | sed '/^[[:space:]]*$/d')"
	fi
	settings='{"name" : "redirect router","ipproto" : "ipv6"}'
	fw6_list=$(_set_json "shorewalllist" "$settings")
	[ -z "$fw6_list" ] && return
	vpsfw6list=$(echo $fw6_list | jsonfilter -q -e '@.list[*]' | sed '/^[[:space:]]*$/d')
	settings='{"name" : "open router","ipproto" : "ipv6"}'
	fw6_list=$(_set_json "shorewalllist" "$settings")
	[ -z "$fw6_list" ] && return
	if [ -n "$vpsfw6list" ]; then
		vpsfw6list="$vpsfw6list\n$(echo $fw6_list | jsonfilter -q -e '@.list[*]' | sed '/^[[:space:]]*$/d')"
	else
		vpsfw6list="$(echo $fw6_list | jsonfilter -q -e '@.list[*]' | sed '/^[[:space:]]*$/d')"
	fi
	config_load firewall
	config_foreach _vps_firewall_redirect_port redirect
	config_foreach _vps_firewall_redirect_port rule
	[ -n "$vpsfwlist" ] || [ -n "$vpsfw6list" ] && {
		logger -t "OMR-VPS" "Remove old firewall rules"
		_vps_firewall_close_port
	}
	uci -q batch <<-EOF >/dev/null
		set openmptcprouter.${fwservername}.set_firewall=0
		commit openmptcprouter
	EOF
}

set_vps_firewall() {
	local server_opt=$2
	if [ -n "$server_opt" ]; then
		_set_vps_firewall $server_opt
	else
		config_load openmptcprouter
		config_foreach _set_vps_firewall server
	fi
}

_set_ss_redir() {
	local option=$2
	local value=$3
	if [ "$value" = "true" ]; then
		value=1
	elif [ "$value" = "false" ]; then
		value=0
	fi
	uci -q set shadowsocks-libev.$1.$option=$value
}

_set_ss_server() {
	local option=$2
	local value=$3
	if [ "$value" = "true" ]; then
		value=1
	elif [ "$value" = "false" ]; then
		value=0
	fi
	if [ "$(echo $1 | grep omr)" != "" ] || [ "$(echo $1 | grep sss)" ]; then 
		uci -q set shadowsocks-libev.$1.$option=$value
	fi
}

_set_ssrust_redir() {
	local option=$2
	local value=$3
	if [ "$value" = "true" ]; then
		value=1
	elif [ "$value" = "false" ]; then
		value=0
	fi
	uci -q set shadowsocks-rust.$1.$option=$value
}

_set_ssrust_server() {
	local option=$2
	local value=$3
	if [ "$value" = "true" ]; then
		value=1
	elif [ "$value" = "false" ]; then
		value=0
	fi
	# sss1 is the dedicated IPv6 proxy slot (see the wizard's dual-stack
	# setup): its "server" address comes from the wizard's IPv6 entry, not
	# from this v4-preferred $vpsip sync, so leave that one option alone
	# here -- otherwise every VPS-config refresh would clobber it back to
	# the IPv4 address. Other options (password, method, ...) still apply
	# to it uniformly like every other server section.
	if [ "$1" = "sss1" ] && [ "$option" = "server" ]; then
		return
	fi
	if [ "$(echo $1 | grep omr)" != "" ] || [ "$(echo $1 | grep sss)" ]; then
		uci -q set shadowsocks-rust.$1.$option=$value
	fi
}

_set_openvpn_server() {
	local option=$2
	local value=$3
	if [ "$value" = "true" ]; then
		value=1
	elif [ "$value" = "false" ]; then
		value=0
	fi
	if [ "$(echo $1 | grep omr)" != "" ]; then 
		if [ "$option" = "remote" ]; then
			uci -q delete openvpn.$1.$option
			uci -q add_list openvpn.$1.$option=$value
		else
			uci -q set openvpn.$1.$option=$value
		fi
	fi
}


_set_config_from_vps() {
	local shadowsocks_disabled vpn glorytun_state redirect shorewall_redirect mlvpn_key openvpn_key dsvpn_key mqvpn_key mqvpn_port mqvpn_user mqvpn_cc mqvpn_scheduler mqvpn_fec_enable_raw mqvpn_fec_enable mqvpn_fec_scheme mqvpn_reinjection_control_raw mqvpn_reinjection_control mqvpn_reinjection_mode mqvpn_reorder_enabled_raw mqvpn_reorder_enabled mqvpn_reorder_max_wait_ms mqvpn_reorder_cap_packets mqvpn_reorder_rules_json mqvpn_rule_idx mqvpn_rule_proto mqvpn_rule_port mqvpn_rule_profile proxy_before
	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return

	logger -t "OMR-VPS" "Get config from server ${servername}..."
	noerror=1
	# get VPS ip
	vpsip="$(uci -q get openmptcprouter.${servername}.ip | awk '{print $1}')"
	vps_lastchange="$(echo "$vps_config" | jsonfilter -q -e '@.vps.lastchange')"
	lastchange="$(uci -q get openmptcprouter.${servername}.lastchange)"
	[ -z "$lastchange" ] && lastchange=0
	#[ -n "$lastchange" ] && [ -n "$vps_lastchange" ] && [ "$lastchange" -gt "$vps_lastchange" ] && return
	user_permission="$(echo "$vps_config" | jsonfilter -q -e '@.user.permission')"
	[ -z "$user_permission" ] && user_permission="rw"

	vpsinternet="$(echo "$vps_config" | jsonfilter -q -e '@.network.internet')"
	if [ "$vpsinternet" = "false" ] && [ "$(uci -q get openmptcprouter.settings.external_check)" != "0" ]; then
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.settings.external_check='0'
		EOF
		logger -t "OMR-VPS" "OMR-Tracker restart..."
		/etc/init.d/omr-tracker restart
	fi

	ula="$(echo "$vps_config" | jsonfilter -q -e '@.ip6in4.ula')"
	if [ -n "$ula" ]; then
		case "$ula" in
			*" "*) ;;
			*)
				uci -q batch <<-EOF >/dev/null
				set network.globals.ula_prefix=$ula
				EOF
			;;
		esac
	fi

	# Set current VPN
	current_vpn="$(uci -q get openmptcprouter.settings.vpn)"
	if [ -z "$current_vpn" ] || ([ -n "$vps_lastchange" ] && [ "$vps_lastchange" -gt "$lastchange" ]) || [ "$user_permission" = "ro" ]; then
		current_vpn="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.current')"
		if [ -n "$current_vpn" ]; then
			uci -q batch <<-EOF >/dev/null
			set openmptcprouter.settings.vpn=${current_vpn}
			commit openmptcprouter
			EOF
		fi
	fi

	# Set current Proxy
	current_proxy="$(uci -q get openmptcprouter.settings.proxy)"
	if [ -z "$current_proxy" ] || ([ -n "$vps_lastchange" ] && [ "$vps_lastchange" -gt "$lastchange" ]) || [ "$user_permission" = "ro" ]; then
		proxy_before="$current_proxy"
		current_proxy="$(echo "$vps_config" | jsonfilter -q -e '@.proxy.current')"
		if [ -n "$current_proxy" ]; then
			uci -q batch <<-EOF >/dev/null
			set openmptcprouter.settings.proxy=${current_proxy}
			commit openmptcprouter
			EOF
			if [ "$current_proxy" = "shadowsocks" ]; then
				uci -q batch <<-EOF >/dev/null
					set shadowsocks-rust.sss0.disabled=1
					commit shadowsocks-rust
					set shadowsocks-libev.sss0.disabled=0
					commit shadowsocks-libev
					set v2ray.main.enabled=0
					commit v2ray
					set xray.main.enabled=0
					commit xray
					set openmptcprouter.settings.shadowsocks_disable=0
					commit openmptcprouter
				EOF
			elif [ "$current_proxy" = "shadowsocks-rust" ]; then
				uci -q batch <<-EOF >/dev/null
					set shadowsocks-rust.sss0.disabled=0
					commit shadowsocks-rust
					set shadowsocks-libev.sss0.disabled=1
					commit shadowsocks-libev
					set v2ray.main.enabled=0
					commit v2ray
					set xray.main.enabled=0
					commit xray
					set openmptcprouter.settings.shadowsocks_disable=0
					commit openmptcprouter
				EOF
			elif [ "$current_proxy" = "v2ray" ]; then
				uci -q batch <<-EOF >/dev/null
					set shadowsocks-rust.sss0.disabled=1
					commit shadowsocks-rust
					set shadowsocks-libev.sss0.disabled=1
					commit shadowsocks-libev
					set v2ray.main.enabled=1
					commit v2ray
					set xray.main.enabled=0
					commit xray
					set openmptcprouter.settings.shadowsocks_disable=1
					commit openmptcprouter
				EOF
			elif [ "$current_proxy" = "xray" ]; then
				uci -q batch <<-EOF >/dev/null
					set shadowsocks-rust.sss0.disabled=1
					commit shadowsocks-rust
					set shadowsocks-libev.sss0.disabled=1
					commit shadowsocks-libev
					set v2ray.main.enabled=0
					commit v2ray
					set xray.main.enabled=1
					commit xray
					set openmptcprouter.settings.shadowsocks_disable=1
					commit openmptcprouter
				EOF
			elif [ "$current_proxy" = "none" ]; then
				uci -q batch <<-EOF >/dev/null
					set shadowsocks-rust.sss0.disabled=1
					commit shadowsocks-rust
					set shadowsocks-libev.sss0.disabled=1
					commit shadowsocks-libev
					set v2ray.main.enabled=0
					commit v2ray
					set xray.main.enabled=0
					commit xray
					set openmptcprouter.settings.shadowsocks_disable=1
					commit openmptcprouter
				EOF
			fi
			# omr-tracker only reconciles which per-proxy watchdog instances
			# are running when it (re)starts. Without this, switching the
			# active proxy here (the VPS-poll path, as opposed to the local
			# wizard Save & Apply path which already restarts omr-tracker)
			# leaves the old proxy's tracker process orphaned, endlessly
			# trying to restart a proxy that was intentionally disabled.
			[ "$proxy_before" != "$current_proxy" ] && /etc/init.d/omr-tracker restart >/dev/null 2>&1
		fi
	fi

	# Shadowsocks settings
	#shadowsocks_disabled="$(uci -q get openmptcprouter.settings.shadowsocks_disable)"
	#[ -z "$shadowsocks_disabled" ] && shadowsocks_disabled=0
	ss_key="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.key')"
	ss_key="$(echo $ss_key | sed 's/-/+/g; s/_/\//g;')"
	if [ -n "$ss_key" ] && [ "$ss_key" != "$(uci -q get shadowsocks-libev.sss0.key)" ] && [ -n "$(uci -q get shadowsocks-libev.sss0)" ]; then
		ss_method="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.method')"
		ss_port="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.port')"
		ss_ebpf="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.ebpf')"
		if [ "$ss_ebpf" = "true" ] && [ "$ss_method" = "none" ]; then
			ss_ebpf=1
		else
			ss_ebpf=0
		fi
		[ -z "$ss_port" ] && ss_port=65101
		ss_no_delay="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.no_delay')"
		ss_fast_open="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.fast_open')"
		ss_obfs="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.obfs')"
		if [ "$ss_obfs" = "true" ]; then
			ss_obfs=1
		else
			ss_obfs=0
		fi
		ss_obfs_plugin="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.obfs_plugin')"
		ss_obfs_type="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.obfs_type')"
		ss_obfs_host="$(echo "$vps_config" | jsonfilter -q -e '@.network.domain')"
		#ss_reuse_port="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks.reuse_port')"
		config_load shadowsocks-libev
		config_foreach _set_ss_redir ss_redir "no_delay" $ss_no_delay
		config_foreach _set_ss_redir ss_redir "fast_open" $ss_fast_open
		config_foreach _set_ss_redir ss_redir "ebpf" $ss_ebpf
		config_foreach _set_ss_redir ss_local "no_delay" $ss_no_delay
		config_foreach _set_ss_redir ss_local "fast_open" $ss_fast_open
		config_foreach _set_ss_redir ss_local "ebpf" $ss_ebpf
		uci -q batch <<-EOF >/dev/null
			set shadowsocks-libev.sss0.key=$ss_key
			set shadowsocks-libev.sss0.server_port=$ss_port
			set shadowsocks-libev.sss0.method=$ss_method
			set shadowsocks-libev.sss0.obfs=$ss_obfs
			set shadowsocks-libev.sss0.obfs_plugin=$ss_obfs_plugin
			set shadowsocks-libev.sss0.obfs_type=$ss_obfs_type
			set shadowsocks-libev.sss0.obfs_host=$ss_obfs_host
		EOF
		config_foreach _set_ss_server server "key" $ss_key
		config_foreach _set_ss_server server "method" $ss_method
		config_foreach _set_ss_server server "obfs" $ss_obfs
		config_foreach _set_ss_server server "obfs_plugin" $ss_obfs_plugin
		config_foreach _set_ss_server server "obfs_type" $ss_obfs_type
		config_foreach _set_ss_server server "obfs_host" $ss_obfs_host
		if [ "$(uci -q get shadowsocks-libev.sss0.server)" != "127.0.0.1" ]; then
			config_foreach _set_ss_server server "server" $vpsip
		fi
		if [ -n "$(uci -q changes shadowsocks-libev)" ]; then
			uci -q commit shadowsocks-libev
			if [ "$(uci -q get shadowsocks-libev.sss0.disabled)" != "1" ]; then
				logger -t "OMR-VPS" "Shadowsocks restart..."
				/etc/init.d/shadowsocks-libev restart >/dev/null 2>&1
			else
				logger -t "OMR-VPS" "Shadowsocks stop..."
				/etc/init.d/shadowsocks-libev stop >/dev/null 2>&1
			fi
		fi
	fi

	# Shadowsocks Rust settings
	#shadowsocks_disabled="$(uci -q get openmptcprouter.settings.shadowsocks_disable)"
	#[ -z "$shadowsocks_disabled" ] && shadowsocks_disabled=0
	ssgo_sskey="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks_go.config.password')"
	if [ -n "$ssgo_sskey" ] && [ "$ssgo_sskey" != "$(uci -q get shadowsocks-rust.sss0.password)" ] && [ -n "$(uci -q get shadowsocks-rust.sss0)" ]; then
		ss_method="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks_go.config.protocol')"
		ss_port="65280"
		#ss_fast_open="$(echo "$vps_config" | jsonfilter -q -e '@.shadowsocks-rust.fast_open')"
		config_load shadowsocks-rust
		#config_foreach _set_ssrust_redir ss_redir "fast_open" $ss_fast_open
		#config_foreach _set_ssrust_redir ss_local "fast_open" $ss_fast_open
		uci -q batch <<-EOF >/dev/null
			set shadowsocks-rust.sss0.password=$ssgo_sskey
			set shadowsocks-rust.sss0.server_port=$ss_port
			set shadowsocks-rust.sss0.method=$ss_method
		EOF
		# sss1 (the IPv6 slot) is the same VPS on the same port; keep it in
		# sync too, but only once it actually has an address of its own --
		# otherwise this would be the only place a bare "server_port" ever
		# gets set on a still-unconfigured sss1.
		if [ -n "$(uci -q get shadowsocks-rust.sss1.server)" ]; then
			uci -q set shadowsocks-rust.sss1.server_port=$ss_port
		fi
		config_foreach _set_ssrust_server server "password" $ssgo_sskey
		config_foreach _set_ssrust_server server "method" $ss_method
		if [ "$(uci -q get shadowsocks-rust.sss0.server)" != "127.0.0.1" ]; then
			config_foreach _set_ssrust_server server "server" $vpsip
		fi
		if [ -n "$(uci -q changes shadowsocks-rust)" ]; then
			uci -q commit shadowsocks-rust
			if [ "$(uci -q get shadowsocks-rust.sss0.disabled)" != "1" ]; then
				logger -t "OMR-VPS" "Shadowsocks Rust restart..."
				/etc/init.d/shadowsocks-rust restart >/dev/null 2>&1
			else
				logger -t "OMR-VPS" "Shadowsocks Rust stop..."
				/etc/init.d/shadowsocks-rust stop >/dev/null 2>&1
			fi
		fi
	fi

	# V2Ray settings
	v2ray_key="$(echo "$vps_config" | jsonfilter -q -e '@.v2ray.config.key')"
	#v2ray_port="$(echo "$vps_config" | jsonfilter -q -e '@.v2ray.config.port')"
	v2ray_port="65228"
	if [ -n "$(uci -q get v2ray.omr)" ] && ( ([ -n "$v2ray_key" ] && [ "$v2ray_key" != "$(uci -q get v2ray.omrout.s_vmess_user_id)" ]) || ([ -n "$v2ray_port" ] && [ "$v2ray_port" != "$(uci -q get v2ray.omrout.s_vmess.port)" ]) ); then
		uci -q batch <<-EOF >/dev/null
			set v2ray.omrout.s_socks_user_id="$v2ray_key"
			set v2ray.omrout.s_socks_port="$((v2ray_port+3))"
			set v2ray.omrout.s_trojan_user_id="$v2ray_key"
			set v2ray.omrout.s_trojan_port="$((v2ray_port+1))"
			set v2ray.omrout.s_vmess_user_id="$v2ray_key"
			set v2ray.omrout.s_vmess_port="$((v2ray_port+2))"
			set v2ray.omrout.s_vless_user_id="$v2ray_key"
			set v2ray.omrout.s_vless_port="$v2ray_port"
		EOF
		if [ "$(uci -q get v2ray.omrout.s_vmess_address)" != "127.0.0.1" ]; then
			uci -q set v2ray.omrout.s_vmess_address="$vpsip"
			uci -q set v2ray.omrout.s_vless_address="$vpsip"
			uci -q set v2ray.omrout.s_trojan_address="$vpsip"
			uci -q set v2ray.omrout.s_socks_address="$vpsip"
		fi
		if [ -n "$(uci -q changes v2ray)" ]; then
			uci -q commit v2ray
			if [ "$(uci -q get v2ray.main.enabled)" = "1" ]; then
				logger -t "OMR-VPS" "V2ray restart..."
				/etc/init.d/v2ray restart >/dev/null 2>&1
			else
				logger -t "OMR-VPS" "Stop restart..."
				/etc/init.d/v2ray stop >/dev/null 2>&1
			fi
		fi
	fi

	# XRay settings
	xray_key="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.key')"
	xray_sskey="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.sskey')"
	xray_vless_reality_key="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.vless_reality_key')"
	xray_reverse_key="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.reverse_key')"
	xray_transport="$(echo "$vps_config" | jsonfilter -q -e '@.xray.config.transport')"
	#v2ray_port="$(echo "$vps_config" | jsonfilter -q -e '@.v2ray.config.port')"
	xray_port="65248"
	if [ -n "$(uci -q get xray.omr)" ] && ( ([ -n "$xray_key" ] && [ "$xray_key" != "$(uci -q get xray.omrout.s_vmess_user_id)" ]) || ([ -n "$xray_port" ] && [ "$xray_port" != "$(uci -q get xray.omrout.s_vmess.port)" ]) || ([ -n "$xray_transport" ] && [ "$xray_transport" != "$(uci -q get xray.omrout.ss_network)" ]) || ([ -n "$xray_vless_reality_key" ] && [ "$xray_vless_reality_key" != "$(uci -q get xray.omrout.s_vless_reality_public_key)" ])); then
		uci -q batch <<-EOF >/dev/null
			set xray.omrout.s_shadowsocks_port="$((xray_port+4))"
			set xray.omrout.s_shadowsocks_password="$xray_sskey"
			set xray.omrout.s_socks_user_id="$xray_key"
			set xray.omrout.s_socks_port="$((xray_port+3))"
			set xray.omrout.s_trojan_user_id="$xray_key"
			set xray.omrout.s_trojan_port="$((xray_port+1))"
			set xray.omrout.s_vmess_user_id="$xray_key"
			set xray.omrout.s_vmess_port="$((xray_port+2))"
			set xray.omrout.s_vless_user_id="$xray_key"
			set xray.omrout.s_vless_port="$xray_port"
			set xray.omrout.s_vless_reality_user_id="$xray_key"
			set xray.omrout.s_vless_reality_public_key="$xray_vless_reality_key"
			set xray.omrout.ss_network="$xray_transport"
		EOF
		[ -n "$xray_reverse_key" ] && uci -q set xray.omrout.s_vless_reverse_user_id="$xray_reverse_key"
		#uci -q set xray.omrout.s_shadowsocks_password=$xray_sskey
		if [ "$(uci -q get xray.omrout.s_vmess_address)" != "127.0.0.1" ]; then
			uci -q set xray.omrout.s_vmess_address="$vpsip"
			uci -q set xray.omrout.s_vless_address="$vpsip"
			uci -q set xray.omrout.s_vless_reality_address="$vpsip"
			uci -q set xray.omrout.s_trojan_address="$vpsip"
			uci -q set xray.omrout.s_socks_address="$vpsip"
			uci -q set xray.omrout.s_shadowsocks_address="$vpsip"
		fi
		if [ -n "$(uci -q changes xray)" ]; then
			uci -q commit xray
			if [ "$(uci -q get xray.main.enabled)" = "1" ]; then
				logger -t "OMR-VPS" "Xray restart..."
				/etc/init.d/xray restart >/dev/null 2>&1
			else
				logger -t "OMR-VPS" "Xray stop..."
				/etc/init.d/xray stop >/dev/null 2>&1
			fi
		fi
	fi

	# Glorytun settings
	glorytun_key="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.key')"
	glorytun_port="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.port')"
	if [ -n "$(uci -q get glorytun.vpn)" ] && ( ([ -n "$glorytun_key" ] && [ "$glorytun_key" != "$(uci -q get glorytun.vpn.key)" ]) || ([ -n "$glorytun_port" ] && [ "$glorytun_port" != "$(uci -q get glorytun.vpn.port)" ]) ); then
		vpn="$(uci -q get openmptcprouter.settings.vpn)"
		glorytun_state=0
		if [ "$vpn" = "glorytun_tcp" ]; then
			glorytun_state=1
		fi
		[ -z "$glorytun_port" ] && glorytun_port="65001"
		glorytun_chacha="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.chacha')"
		{ [ -z "$glorytun_chacha" ] || [ "$glorytun_chacha" = "true" ]; } && glorytun_chacha=1
		[ "$glorytun_chacha" = "false" ] && glorytun_chacha=0
		uci -q batch <<-EOF >/dev/null
			set glorytun.vpn.port=$glorytun_port
			set glorytun.vpn.key=$glorytun_key
			set glorytun.vpn.enable=$glorytun_state
			set glorytun.vpn.chacha20=$glorytun_chacha
		EOF
		if [ -z "$(uci -q get openmptcprouter.settings.mptcpovervpn)" ]; then
			uci -q set glorytun.vpn.host="$vpsip"
		fi
		if [ -n "$(uci -q changes glorytun)" ]; then
			uci -q commit glorytun
			logger -t "OMR-VPS" "Glorytun restart..."
			/etc/init.d/glorytun restart >/dev/null 2>&1
		fi
	fi
	if [ -n "$(uci -q get glorytun-udp.vpn)" ] && ( ([ -n "$glorytun_key" ] && [ "$glorytun_key" != "$(uci -q get glorytun-udp.vpn.key)" ]) || ([ -n "$glorytun_port" ] && [ "$glorytun_port" != "$(uci -q get glorytun-udp.vpn.port)" ]) ); then
		vpn="$(uci -q get openmptcprouter.settings.vpn)"
		glorytun_state=0
		if [ "$vpn" = "glorytun_udp" ]; then
			glorytun_state=1
		fi
		[ -z "$glorytun_port" ] && glorytun_port="65001"
		glorytun_chacha="$(echo "$vps_config" | jsonfilter -q -e '@.glorytun.chacha')"
		{ [ -z "$glorytun_chacha" ] || [ "$glorytun_chacha" = "true" ]; } && glorytun_chacha=1
		[ "$glorytun_chacha" = "false" ] && glorytun_chacha=0
		uci -q batch <<-EOF >/dev/null
			set glorytun-udp.vpn.port=$glorytun_port
			set glorytun-udp.vpn.key=$glorytun_key
			set glorytun-udp.vpn.enable=$glorytun_state
			set glorytun-udp.vpn.chacha=$glorytun_chacha
		EOF
		if [ -z "$(uci -q get openmptcprouter.settings.mptcpovervpn)" ]; then
			uci -q set glorytun-udp.vpn.host="$vpsip"
		fi
		if [ -n "$(uci -q changes glorytun-udp)" ]; then
			uci -q commit glorytun-udp
			logger -t "OMR-VPS" "Glorytun UDP restart..."
			/etc/init.d/glorytun-udp restart >/dev/null 2>&1
		fi
	fi

	# OpenVPN settings
	openvpn_key="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.key')"
	[ -n "$openvpn_key" ] && {
		echo $openvpn_key | base64 -d > /etc/luci-uploads/openvpn.key
		openvpn_port="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.port')"
		[ -z "$openvpn_port" ] && openvpn_port="65001"
		vpn="$(uci -q get openmptcprouter.settings.vpn)"
		openvpn_state=0
		if [ "$vpn" = "openvpn" ]; then
			openvpn_state=1
		fi
		config_load openvpn
		config_foreach _set_openvpn_server openvpn "port" "$openvpn_port"
		config_foreach _set_openvpn_server openvpn "secret" "/etc/luci-uploads/openvpn.key"

		#uci -q batch <<-EOF >/dev/null
		#	set openvpn.omr.port=$openvpn_port
		#	set openvpn.omr.secret="/etc/luci-uploads/openvpn.key"
		#EOF
		if [ "$(uci -q get openmptcprouter.settings.openvpn_lb)" = "0" ]; then
			config_foreach _set_openvpn_server openvpn "enabled" "0"
			uci -q set openvpn.omr.enabled=$openvpn_state
		else
			config_foreach _set_openvpn_server openvpn "enabled" "$openvpn_state"
		fi
		#if [ "$openvpn_state" = "1" ]; then
		#	uci -q set openvpn.omr.enabled=$openvpn_state
		#else
		#	uci -q del openvpn.omr.enabled
		#fi
		if [ "$(uci -q get openvpn.omr.remote)" != "127.0.0.1" ]; then
			config_foreach _set_openvpn_server openvpn "remote" "$vpsip"
			#uci -q set openvpn.omr.remote="$vpsip"
		fi
		if [ -n "$(uci -q changes openvpn)" ]; then
			uci -q commit openvpn
			if [ "$(uci -q get openvpn.omr.enabled)" = "1" ]; then
				logger -t "OMR-VPS" "OpenVPN restart..."
				/etc/init.d/openvpn restart
			fi
		fi
	}
	openvpn_client_key="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.client_key')"
	[ -n "$openvpn_client_key" ] && {
		echo $openvpn_client_key | base64 -d > /etc/luci-uploads/client.key
	}
	openvpn_client_crt="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.client_crt')"
	[ -n "$openvpn_client_crt" ] && {
		echo $openvpn_client_crt | base64 -d > /etc/luci-uploads/client.crt
	}
	openvpn_client_ca="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.client_ca')"
	[ -n "$openvpn_client_ca" ] && {
		echo $openvpn_client_ca | base64 -d > /etc/luci-uploads/ca.crt
	}
	[ -n "$openvpn_client_key" ] && [ -n "$openvpn_client_ca" ] && {
		openvpn_port="$(echo "$vps_config" | jsonfilter -q -e '@.openvpn.port')"
		[ -z "$openvpn_port" ] && openvpn_port="65001"
		vpn="$(uci -q get openmptcprouter.settings.vpn)"
		openvpn_state=0
		if [ "$vpn" = "openvpn" ]; then
			openvpn_state=1
		fi

		NBCPU=$(grep -c '^processor' /proc/cpuinfo | tr -d "\n")
		if [ "$NBCPU" -gt 1 ] && [ -z "$(uci -q get openvpn.omr2)" ]; then
			[ -f /rom/etc/uci-defaults/2020-omr-vpn ] && ./rom/etc/uci-defaults/2020-omr-vpn
			[ -f /usr/share/omr-update/2020-omr-vpn ] && ./usr/share/omr-update/2020-omr-vpn
		fi

		config_load openvpn
		if [ "$(uci -q get openmptcprouter.settings.openvpn_lb)" = "0" ]; then
			config_foreach _set_openvpn_server openvpn "enabled" "0"
			uci -q set openvpn.omr.enabled=$openvpn_state
		else
			config_foreach _set_openvpn_server openvpn "enabled" "$openvpn_state"
		fi
		config_foreach _set_openvpn_server openvpn "key" "/etc/luci-uploads/client.key"
		[ -n "$openvpn_client_crt" ] && config_foreach _set_openvpn_server openvpn "cert" "/etc/luci-uploads/client.crt"
		config_foreach _set_openvpn_server openvpn "ca" "/etc/luci-uploads/ca.crt"
		config_foreach _set_openvpn_server openvpn "tls_client" "1"
		config_foreach _set_openvpn_server openvpn "client" "1"
		config_foreach _set_openvpn_server openvpn "allow_recursive_routing" "1"

		#uci -q batch <<-EOF >/dev/null
		#	set openvpn.omr.port=$openvpn_port
		#	set openvpn.omr.key="/etc/luci-uploads/client.key"
		#	set openvpn.omr.cert="/etc/luci-uploads/client.crt"
		#	set openvpn.omr.ca="/etc/luci-uploads/ca.crt"
		#	set openvpn.omr.enabled=$openvpn_state
		#	set openvpn.omr.tls_client=1
		#	set openvpn.omr.client=1
		#	set openvpn.omr.allow_recursive_routing=1
		#EOF
		if [ "$(uci -q get openvpn.omr.remote)" != "127.0.0.1" ]; then
			config_foreach _set_openvpn_server openvpn "remote" "$vpsip"
			#uci -q set openvpn.omr.remote="$vpsip"
		fi
		if [ -n "$(uci -q changes openvpn)" ]; then
			uci -q commit openvpn
			if [ "$(uci -q get openvpn.omr.enabled)" = "1" ]; then
				logger -t "OMR-VPS" "OpenVPN restart..."
				/etc/init.d/openvpn restart
			fi
		fi
	}

	# MLVPN settings
	mlvpn_key="$(echo "$vps_config" | jsonfilter -q -e '@.mlvpn.key')"
	if [ -n "$(uci -q get mlvpn.general)" ] && [ -n "$mlvpn_key" ] && [ "$mlvpn_key" != "$(uci -q get mlvpn.general.password)" ] && [ -f /etc/init.d/mlvpn ]; then
		vpn="$(uci -q get openmptcprouter.settings.vpn)"
		mlvpn_state=0
		if [ "$vpn" = "mlvpn" ]; then
			mlvpn_state=1
		fi
		uci -q batch <<-EOF >/dev/null
			set mlvpn.general.password=$mlvpn_key
			set mlvpn.general.enable=$mlvpn_state
			commit mlvpn
		EOF
		if [ "$(uci -q get mlvpn.general.host)" != "127.0.0.1" ]; then
			uci -q set mlvpn.general.host="$vpsip"
		fi
		if [ -n "$(uci -q changes mlvpn)" ]; then
			uci -q commit mlvpn
			logger -t "OMR-VPS" "MLVPN restart..."
			/etc/init.d/mlvpn restart
		fi
	fi

	# DSVPN settings
	dsvpn_key="$(echo "$vps_config" | jsonfilter -q -e '@.dsvpn.key')"
	dsvpn_port="$(echo "$vps_config" | jsonfilter -q -e '@.dsvpn.port')"
	if [ -f /etc/init.d/dsvpn ] && [ -n "$(uci -q get dsvpn.vpn)" ] && ( ([ -n "$dsvpn_key" ] && [ "$dsvpn_key" != "$(uci -q get dsvpn.vpn.key)" ]) || ([ -n "$dsvpn_port" ] && [ "$dsvpn_port" != "$(uci -q get dsvpn.vpn.port)" ]) ); then
		dsvpn_state=0
		vpn="$(uci -q get openmptcprouter.settings.vpn)"
		if [ "$vpn" = "dsvpn" ]; then
			dsvpn_state=1
		fi
		uci -q batch <<-EOF >/dev/null
			set dsvpn.vpn.key=$dsvpn_key
			set dsvpn.vpn.enable=$dsvpn_state
			set dsvpn.vpn.port=$dsvpn_port
			commit dsvpn
		EOF
		if [ "$(uci -q get dsvpn.vpn.host)" != "127.0.0.1" ]; then
			uci -q set dsvpn.vpn.host="$vpsip"
		fi
		if [ -n "$(uci -q changes dsvpn)" ]; then
			uci -q commit dsvpn
			logger -t "OMR-VPS" "DSVPN restart..."
			/etc/init.d/dsvpn restart
		fi
	fi

	# MQVPN settings
	mqvpn_key="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.key')"
	mqvpn_port="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.port')"
	[ -z "$mqvpn_port" ] && mqvpn_port="65443"
	# mqvpn.server.port is user intent (pushed to the VPS by _set_mqvpn_vps):
	# only default it from the VPS when not set locally
	[ -n "$(uci -q get mqvpn.server.port)" ] && mqvpn_port="$(uci -q get mqvpn.server.port)"
	mqvpn_cc="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.cc')"
	mqvpn_scheduler="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.scheduler')"
	mqvpn_fec_enable_raw="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.fec_enable')"
	mqvpn_fec_scheme="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.fec_scheme')"
	mqvpn_reinjection_control_raw="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.reinjection_control')"
	mqvpn_reinjection_mode="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.reinjection_mode')"
	mqvpn_reorder_enabled_raw="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.reorder.enabled')"
	mqvpn_reorder_max_wait_ms="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.reorder.max_wait_ms')"
	mqvpn_reorder_cap_packets="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.reorder.cap_packets')"
	mqvpn_reorder_rules_json="$(echo "$vps_config" | jsonfilter -q -e '@.mqvpn.reorder_rules')"
	[ "$mqvpn_fec_enable_raw" = "true" ] && mqvpn_fec_enable="1" || mqvpn_fec_enable="0"
	[ "$mqvpn_reinjection_control_raw" = "true" ] && mqvpn_reinjection_control="1" || mqvpn_reinjection_control="0"
	case "$mqvpn_reorder_enabled_raw" in
		on|auto) mqvpn_reorder_enabled="1" ;;
		off) mqvpn_reorder_enabled="0" ;;
		*) mqvpn_reorder_enabled="" ;;
	esac
	mqvpn_user="$(uci -q get openmptcprouter.${servername}.username)"
	if [ -f /etc/init.d/mqvpn ] && [ -n "$(uci -q get mqvpn.auth)" ] && [ -n "$mqvpn_key" ] && \
	   ( [ "$mqvpn_key" != "$(uci -q get mqvpn.auth.key)" ] || \
	     [ "$mqvpn_user" != "$(uci -q get mqvpn.auth.user)" ] || \
	     [ "$(uci -q get mqvpn.server.ip)" != "$vpsip" ] || \
	     [ "$(uci -q get mqvpn.server.port)" != "$mqvpn_port" ] || \
	     [ "$(uci -q get mqvpn.server.insecure)" != "1" ] || \
	     ( [ -n "$mqvpn_cc" ] && [ "$mqvpn_cc" != "$(uci -q get mqvpn.multipath.cc)" ] ) || \
	     ( [ -n "$mqvpn_scheduler" ] && [ "$mqvpn_scheduler" != "$(uci -q get mqvpn.multipath.scheduler)" ] ) || \
	     ( [ -n "$mqvpn_fec_enable_raw" ] && [ "$mqvpn_fec_enable" != "$(uci -q get mqvpn.multipath.fec_enable)" ] ) || \
	     ( [ -n "$mqvpn_fec_scheme" ] && [ "$mqvpn_fec_scheme" != "$(uci -q get mqvpn.multipath.fec_scheme)" ] ) || \
	     ( [ -n "$mqvpn_reinjection_control_raw" ] && [ "$mqvpn_reinjection_control" != "$(uci -q get mqvpn.multipath.reinjection_control)" ] ) || \
	     ( [ -n "$mqvpn_reinjection_mode" ] && [ "$mqvpn_reinjection_mode" != "$(uci -q get mqvpn.multipath.reinjection_mode)" ] ) || \
	     ( [ -n "$mqvpn_reorder_enabled" ] && [ "$mqvpn_reorder_enabled" != "$(uci -q get mqvpn.reorder.enabled)" ] ) || \
	     ( [ "$(uci -q get openmptcprouter.settings.vpn)" = "mqvpn" ] && [ "$(uci -q get mqvpn.settings.enable)" != "1" ] ) ); then
		mqvpn_state=0
		vpn="$(uci -q get openmptcprouter.settings.vpn)"
		if [ "$vpn" = "mqvpn" ]; then
			mqvpn_state=1
		fi
		uci -q batch <<-EOF >/dev/null
			set mqvpn.auth.key=$mqvpn_key
			set mqvpn.auth.user=$mqvpn_user
			set mqvpn.settings.enable=$mqvpn_state
			set mqvpn.server.ip="$vpsip"
			set mqvpn.server.port="$mqvpn_port"
			set mqvpn.server.insecure=1
			$([ -n "$mqvpn_cc" ] && echo "set mqvpn.multipath.cc=$mqvpn_cc")
			$([ -n "$mqvpn_scheduler" ] && echo "set mqvpn.multipath.scheduler=$mqvpn_scheduler")
			$([ -n "$mqvpn_fec_enable_raw" ] && echo "set mqvpn.multipath.fec_enable=$mqvpn_fec_enable")
			$([ -n "$mqvpn_fec_scheme" ] && echo "set mqvpn.multipath.fec_scheme=$mqvpn_fec_scheme")
			$([ -n "$mqvpn_reinjection_control_raw" ] && echo "set mqvpn.multipath.reinjection_control=$mqvpn_reinjection_control")
			$([ -n "$mqvpn_reinjection_mode" ] && echo "set mqvpn.multipath.reinjection_mode=$mqvpn_reinjection_mode")
			$([ -n "$mqvpn_reorder_enabled" ] && echo "set mqvpn.reorder.enabled=$mqvpn_reorder_enabled")
			$([ -n "$mqvpn_reorder_max_wait_ms" ] && echo "set mqvpn.reorder.max_wait_ms=$mqvpn_reorder_max_wait_ms")
			$([ -n "$mqvpn_reorder_cap_packets" ] && echo "set mqvpn.reorder.cap_packets=$mqvpn_reorder_cap_packets")
		EOF
		if [ -n "$mqvpn_reorder_rules_json" ]; then
			while uci -q delete mqvpn.@reorder_rule[0] 2>/dev/null; do :; done
			mqvpn_rule_idx=0
			while true; do
				mqvpn_rule_proto="$(echo "$vps_config" | jsonfilter -q -e "@.mqvpn.reorder_rules[$mqvpn_rule_idx].proto")"
				[ -z "$mqvpn_rule_proto" ] && break
				mqvpn_rule_port="$(echo "$vps_config" | jsonfilter -q -e "@.mqvpn.reorder_rules[$mqvpn_rule_idx].port")"
				mqvpn_rule_profile="$(echo "$vps_config" | jsonfilter -q -e "@.mqvpn.reorder_rules[$mqvpn_rule_idx].profile")"
				uci -q add mqvpn reorder_rule >/dev/null
				uci -q set "mqvpn.@reorder_rule[-1].proto=$mqvpn_rule_proto"
				[ -n "$mqvpn_rule_port" ] && uci -q set "mqvpn.@reorder_rule[-1].port=$mqvpn_rule_port"
				[ -n "$mqvpn_rule_profile" ] && uci -q set "mqvpn.@reorder_rule[-1].profile=$mqvpn_rule_profile"
				mqvpn_rule_idx=$((mqvpn_rule_idx + 1))
			done
		fi
		if [ -n "$(uci -q changes mqvpn)" ]; then
			uci -q commit mqvpn
			logger -t "OMR-VPS" "MQVPN restart..."
			/etc/init.d/mqvpn restart
		fi
	fi

	# SoftEtherVPN settings
	softethervpn_password="$(echo "$vps_config" | jsonfilter -q -e '@.softethervpn.password')"
	softethervpn_port="$(echo "$vps_config" | jsonfilter -q -e '@.softethervpn.port')"
	if [ -f /etc/init.d/softethervpnclient ] && [ -n "$(uci -q get softethervpn.openmptcprouter)" ] && ( ([ -n "$softethervpn_password" ] && [ "$softethervpn_password" != "$(uci -q get softethervpn.openmptcprouter.password)" ]) || ([ -n "$softethervpn_port" ] && [ "$softethervpn_port" != "$(uci -q get softethervpn.openmptcprouter.port)" ]) ); then
		softethervpn_state=0
		vpn="$(uci -q get openmptcprouter.settings.vpn)"
		if [ "$vpn" = "softethervpn" ]; then
			softethervpn_state=1
		fi
		uci -q batch <<-EOF >/dev/null
			set softethervpn.openmptcprouter.password=$softethervpn_password
			set softethervpn.openmptcprouter.enable=$softethervpn_state
			set softethervpn.openmptcprouter.port=$softethervpn_port
			commit softethervpn
		EOF
		if [ "$(uci -q get softethervpn.openmptcprouter.host)" != "127.0.0.1" ]; then
			uci -q set softethervpn.openmptcprouter.host="$vpsip"
		fi
		if [ -n "$(uci -q changes softethervpn)" ]; then
			uci -q commit softethervpn
			logger -t "OMR-VPS" "SoftEther VPN restart..."
			/etc/init.d/softethervpnclient restart
		fi
	fi

	# Shorewall settings
	shorewall_redirect="$(echo "$vps_config" | jsonfilter -q -e '@.shorewall.redirect_ports')"
	[ "$shorewall_redirect" = "enable" ] && redirect="1"
	[ "$shorewall_redirect" = "disable" ] && redirect="0"
	uci -q batch <<-EOF >/dev/null
		set openmptcprouter.${servername}.redirect_ports=$redirect
	EOF

	# Wireguard settings
	wireguard_key="$(echo "$vps_config" | jsonfilter -q -e '@.wireguard.key')"
	uci -q batch <<-EOF >/dev/null
		set openmptcprouter.${servername}.wgkey=$wireguard_key
	EOF

	# MPTCP settings
	mptcp_path_manager="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.path_manager')"
	mptcp_scheduler="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.scheduler')"
	mptcp_checksum="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.checksum')"
	mptcp_syn_retries="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.syn_retries')"
	mptcp_enabled="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.enabled')"
	if [ "$mptcp_enabled" = "0" ]; then
		mptcp_enabled="disable"
	else
		mptcp_enabled="enable"
	fi
	congestion="$(echo "$vps_config" | jsonfilter -q -e '@.network.congestion_control')"
	uci -q batch <<-EOF >/dev/null
		set network.globals.multipath=$mptcp_enabled
		set network.globals.mptcp_checksum=$mptcp_checksum
		set network.globals.congestion=$congestion
		commit network
	EOF
	if [ "$mptcp_scheduler" != "" ] && [ "$mptcp_syn_retries" != "" ]; then
		uci -q batch <<-EOF >/dev/null
			set network.globals.mptcp_scheduler=$mptcp_scheduler
			set network.globals.mptcp_syn_retries=$mptcp_syn_retries
			commit network
		EOF
	fi
	if [ "$mptcp_path_manager" != "" ] && [ "$mptcp_path_manager" != "kernel" ]; then
		uci -q set network.globals.mptcp_path_manager="$mptcp_path_manager"
	fi
	mptcp_close_timeout="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.close_timeout')"
	mptcp_pm_type="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.pm_type')"
	mptcp_stale_loss_cnt="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.stale_loss_cnt')"
	mptcp_syn_retrans_before_tcp_fallback="$(echo "$vps_config" | jsonfilter -q -e '@.mptcp.syn_retrans_before_tcp_fallback')"
	[ -n "$mptcp_close_timeout" ] && [ "$mptcp_close_timeout" != "0" ] && uci -q set network.globals.mptcp_close_timeout="$mptcp_close_timeout"
	[ -n "$mptcp_pm_type" ] && [ "$mptcp_pm_type" != "0" ] && uci -q set network.globals.mptcp_pm_type="$mptcp_pm_type"
	[ -n "$mptcp_stale_loss_cnt" ] && [ "$mptcp_stale_loss_cnt" != "0" ] && uci -q set network.globals.mptcp_stale_loss_cnt="$mptcp_stale_loss_cnt"
	[ -n "$mptcp_syn_retrans_before_tcp_fallback" ] && [ "$mptcp_syn_retrans_before_tcp_fallback" != "0" ] && uci -q set network.globals.mptcp_syn_retrans_before_tcp_fallback="$mptcp_syn_retrans_before_tcp_fallback"
	if [ -n "$(uci -q changes network)" ]; then
		uci -q commit network
	fi

	# Check if server get an IPv6, if not disable IPv6 on OMR
	vps_ipv6_addr="$(echo "$vps_config" | jsonfilter -q -e '@.network.ipv6')"
	if [ -z "$vps_ipv6_addr" ]; then
		if [ "$(uci -q get openmptcprouter.settings.disable_ipv6)" = "0" ]; then
			logger -t "OMR-VPS" "No IPv6 support on VPS side ?"
		fi
		#uci -q batch <<-EOF >/dev/null
		#	set openmptcprouter.settings.disable_ipv6=1
		#EOF
		#sysctl -qw net.ipv6.conf.all.disable_ipv6=1
	else
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.ipv6=$vps_ipv6_addr
		EOF
	fi

	omr6in4_vps_localip="$(echo "$vps_config" | jsonfilter -q -e '@.ip6in4.localip')"
	omr6in4_vps_remoteip="$(echo "$vps_config" | jsonfilter -q -e '@.ip6in4.remoteip')"
	if ([ -n "$omr6in4_vps_localip" ] && [ "$omr6in4_vps_localip" != "$(uci -q get network.omr6in4.gateway)" ]) || ([ -n "$omr6in4_vps_remoteip" ] && [ "$omr6in4_vps_remoteip" != "$(uci -q get network.omr6in4.ip6addr)" ]); then
		logger -t "OMR-VPS" "Set omr6in4 ip address and gateway"
		uci -q batch <<-EOF >/dev/null
			set network.omr6in4.ip6addr="$omr6in4_vps_remoteip"
			set network.omr6in4.gateway="$omr6in4_vps_localip"
		EOF
		if [ -n "$(uci -q changes network)" ]; then
			uci -q commit network
			/etc/init.d/network reload
			sleep 6
		fi
	fi

	# Get VPS iperf config
	iperf_user="$(echo "$vps_config" | jsonfilter -q -e '@.iperf.user')"
	iperf_pass="$(echo "$vps_config" | jsonfilter -q -e '@.iperf.password')"
	iperf_key="$(echo "$vps_config" | jsonfilter -q -e '@.iperf.key')"
	uci -q batch <<-EOF >/dev/null
		del iperf.${servername}
		set iperf.${servername}=server
		set iperf.${servername}.host=$vpsip
		set iperf.${servername}.ports=65400
		set iperf.${servername}.ipv4=1
		set iperf.${servername}.ipv6=0
		set iperf.${servername}.speed=1000
		set iperf.${servername}.tcp=1
		set iperf.${servername}.udp=1
		set iperf.${servername}.user=$iperf_user
		set iperf.${servername}.password=$iperf_pass
		set iperf.${servername}.key="$iperf_key"
		commit iperf
	EOF

	# Get available server
	available_vpn="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.available' | sed -e 's/\[ //' -e 's/ \]//' -e 's/,//g')"
	available_proxy="$(echo "$vps_config" | jsonfilter -q -e '@.proxy.available' | sed -e 's/\[ //' -e 's/ \]//' -e 's/,//g')"
	uci -q batch <<-EOF >/dev/null
		del openmptcprouter.${servername}.available_vpn
		del openmptcprouter.${servername}.available_proxy
	EOF

	if [ "$user_permission" = "ro" ]; then
		current_vpn="$(echo "$vps_config" | jsonfilter -q -e '@.vpn.current')"
		if [ -n "$current_vpn" ]; then
			uci -q batch <<-EOF >/dev/null
				set openmptcprouter.${servername}.available_vpn=${current_vpn}
			EOF
		fi
		current_proxy="$(echo "$vps_config" | jsonfilter -q -e '@.proxy.current')"
		if [ -n "$current_proxy" ]; then
			uci -q batch <<-EOF >/dev/null
				set openmptcprouter.${servername}.available_proxy=${current_proxy}
			EOF
		fi
	
	else
		if [ -n "$available_vpn" ]; then
			for vpn in $available_vpn; do
				uci -q batch <<-EOF >/dev/null
					add_list openmptcprouter.${servername}.available_vpn=$vpn
				EOF
			done
		fi
		if [ -n "$available_proxy" ]; then
			for proxy in $available_proxy; do
				uci -q batch <<-EOF >/dev/null
					add_list openmptcprouter.${servername}.available_proxy=$proxy
				EOF
			done
		fi
	fi
	if [ "$noerror" = "1" ]; then
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.get_config=0
			commit openmptcprouter
		EOF
	fi
}

_backup_send() {
	servername=$1
	[ -z "$(uci -q get openmptcprouter.${servername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.port)" ] && return
	token=""
	vps_config=""
	_login
	[ -z "$token" ] && {
		[ "$(logread | tail -n 3 | grep -c 'vps token')" -lt 3 ] && logger -t "OMR-VPS" "Can't get ${servername} token, try later"
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.admin_error=1
		EOF
		return
	}
	sysupgrade -k -b /tmp/backup.tar.gz
	backup_data="$(cat /tmp/backup.tar.gz | base64 | tr -d '\n')"
	backup_sha256sum="$(sha256sum /tmp/backup.tar.gz | awk '{print $1}')"
	[ -n "$backup_data" ] && {
		logger -t "OMR-VPS" "Send backup file to server $servername"
		local backupjson
		backupjson='{"data": "'$backup_data'","sha256sum": "'$backup_sha256sum'"}'
		echo "$backupjson" > /tmp/backupjson
		result=$(_set_json "backuppost" "@/tmp/backupjson")
		rm -f /tmp/backupjson
		uci -q set openmptcprouter.$servername.lastbackup=$(date +%s)
	}
}

backup_send() {
	config_load openmptcprouter
	config_foreach _backup_send server
}


_backup_get() {
	servername=$1
	[ -z "$(uci -q get openmptcprouter.${servername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.port)" ] && return
	[ -z "$lastbackup" ] || [ "$lastbackup" -lt "$(uci -q get openmptcprouter.$servername.lastbackup)" ] && {
		lastbackup="$(uci -q get openmptcprouter.$servername.lastbackup)"
		serverbackup="$servername"
	}
}

_backup_get_and_apply() {
	servername=$1
	backupfile=$2
	token=""
	vps_config=""
	_login
	[ -z "$token" ] && {
		[ "$(logread | tail -n 3 | grep -c 'vps token')" -lt 3 ] && logger -t "OMR-VPS" "Can't get ${servername} token, try later"
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.admin_error=1
		EOF
		return
	}
	if [ -n "$backupfile" ]; then
		echo "get $backupfile"
		vps_backup=$(_get_json "backupget?filename=${backupfile}")
	else
		vps_backup=$(_get_json "backupget")
	fi
	[ -z "$vps_backup" ] && return
	backup_data="$(echo "$vps_backup" | jsonfilter -q -e '@.data')"
	backup_sha256sum="$(echo "$vps_backup" | jsonfilter -q -e '@.sha256sum')"
	[ -n "$backup_data" ] && {
		echo $backup_data | base64 -d > /tmp/backup.tar.gz
		sysupgrade -r /tmp/backup.tar.gz
	}
}

backup_get() {
	backuptoapply=$2
	if [ -n "$backuptoapply" ]; then
		_backup_get_and_apply $1 $2
	else
		lastbackup=""
		serverbackup=""
		config_load openmptcprouter
		config_foreach _backup_get server
		[ -n "$serverbackup" ] && _backup_get_and_apply $serverbackup
	fi
}

_backup_list() {
	bservername=$1
	[ -z "$servername" ] && servername=$bservername
	[ -z "$(uci -q get openmptcprouter.${servername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.port)" ] && return
	token=""
	vps_config=""
	_login
	[ -z "$token" ] && {
		[ "$(logread | tail -n 3 | grep -c 'vps token')" -lt 3 ] && logger -t "OMR-VPS" "Can't get ${servername} token, try later"
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.admin_error=1
		EOF
		return
	}
	vps_backup=$(_get_json "backuplist")
	[ -z "$vps_backup" ] && return
	backup_lastmodif="$(echo "$vps_backup" | jsonfilter -q -e '@.modif')"
	[ -n "$backup_lastmodif" ] && {
		uci -q set openmptcprouter.$servername.lastbackup=$backup_lastmodif
		backup_all="$(echo "$vps_backup" | jsonfilter -q -e '@.sorted[*]')"
		uci -q del openmptcprouter.$servername.allbackup
		echo "$vps_backup" | jsonfilter -q -e '@.sorted[*]' | while read backup; do
			name=$(echo "$backup" | jsonfilter -q -e '@[0]' | tr -d '\n')
			modiftime=$(echo "$backup" | jsonfilter -q -e '@[1]' | tr -d '\n')
			uci -q add_list openmptcprouter.$servername.allbackup="${name}|${modiftime}"
		done
	}
}



backup_list() {
	config_load openmptcprouter
	config_foreach _backup_list server
	[ -n "$(uci -q changes openmptcprouter)" ] && uci -q commit openmptcprouter
}


_count_server() {
	local servername=$1
	[ -z "$(uci -q get openmptcprouter.${servername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.port)" ] && return
	serversnb=$((serversnb+1))
}

_get_token() {
	servername=$1
	[ -z "$(uci -q get openmptcprouter.${servername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.port)" ] && return
	[ -z "$(uci -q get openmptcprouter.${servername}.ip)" ] && return
	[ "$(uci -q get openmptcprouter.${servername}.disabled)" = "1" ] && return
	token=""
	_login
	if [ -z "$token" ]; then
		reason=""
		test_ping() {
			_ping_server $1
			status=$?
			if $(exit $status); then
				reason="can ping server ${servername} on $1"
			else
				reason="can't ping server ${servername} on $1"
			fi
		}
		config_list_foreach "${servername}" ip test_ping
		port="$(uci -q get openmptcprouter.${servername}.port)"
		api_test() {
			server="$1"
			resolve="$(resolveip -t 5 $server)"
			valid_ip6=$(valid_subnet6 $server)
			if [ "$resolve" != "$server" ] || [ "$valid_ip6" != "ok" ]; then
				if [ "$(curl -s -k -m 3 https://${server}:${port}/)" = "" ]; then
					reason="$reason, no server API answer on $1"
				else
					reason="$reason, server API answer on $1"
				fi
			else
				if [ "$(curl -s -k -m 3 https://[${server}]:${port}/)" = "" ]; then
					reason="$reason, no server API answer on $1"
				else
					reason="$reason, server API answer on $1"
				fi
			fi
		}
		config_list_foreach "${servername}" ip api_test
		if [ "$(uci -q get openmptcprouter.${servername}.username)" != "openmptcprouter" ]; then
			reason="$reason, custom username"
		fi
		[ "$(logread | tail -n 3 | grep -c 'vps token')" -lt 3 ] && logger -t "OMR-VPS" "Can't get ${servername} token, try later ($reason)"
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.admin_error=1
		EOF
	else
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${servername}.token_error=0
			commit openmptcprouter
		EOF
	fi
	echo "$server:$serverport:$token"
}

_config_service() {
	servername=$1
	[ "$(uci -q get openmptcprouter.${servername}.disabled)" = "1" ] && return
	vps_config=""
	tokenserver=$(_get_token $servername)
	server="$(echo $tokenserver | cut -f1 -d:)"
	serverport="$(echo $tokenserver | cut -f2 -d:)"
        token="$(echo $tokenserver | cut -f3 -d:)"
	[ -z "$token" ] && return
	error=0
	if [ -n "$serial" ]; then
		[ -z "$vps_config" ] && vps_config=$(_get_json "config?serial=${serial}")
		if [ -n "$vps_config" ] && [ "$( echo "$vps_config" | jsonfilter -q -e '@.error')" = "False serial number" ]; then
			logger -t "OMR-VPS" "Invalid serial number"
			sed -i "s:${server}::g" /etc/config/*
			return
		fi
	fi
	[ "$(uci -q get openmptcprouter.${servername}.get_config)" = "1" ] && ([ "$(uci -q get openmptcprouter.${servername}.master)" = "1" ] || [ "$(uci -q get openmptcprouter.${servername}.current)" = "1" ]) && {
		_set_config_from_vps
		_get_gre_tunnel
		_get_vps_config
	}

#	([ "$(uci -q get openmptcprouter.${servername}.master)" = "1" ] || [ "$(uci -q get openmptcprouter.${servername}.current)" = "1" ]) && {
#		_get_vps_config
#	}

	if [ "$(uci -q get openmptcprouter.settings.firstboot)" != "0" ]; then
		[ -n "$(cat /proc/cpuinfo | grep aes)" ] && {
			vps_aes="$(echo "$vps_config" | jsonfilter -q -e '@.vps.aes')"
			method="$(uci -q get openmptcprouter.settings.encryption)"
			if [ "$vps_aes" != "false" ] && [ "$user_permission" != "ro" ] && [ "$method" != "aes-256-gcm" ]; then
				logger -t "OMR-VPS" "CPU support AES, set it by default"
				uci -q batch <<-EOF >/dev/null
					set openmptcprouter.settings.encryption="aes-256-gcm"
					commit openmptcprouter
					set glorytun.vpn.chacha20="0"
					commit glorytun
					set glorytun-udp.vpn.chacha="0"
					commit glorytun-udp
				EOF
				config_foreach _set_ss_server server "method" "aes-256-gcm"
				[ -n "$(uci -q changes shadowsocks-libev)" ] && uci -q commit shadowsocks-libev
				config_foreach _set_ssrust_server server "method" "2022-blake3-aes-256-gcm"
				[ -n "$(uci -q changes shadowsocks-rust)" ] && uci -q commit shadowsocks-rust
				[ "$method" != "aes-256-gcm" ] && {
					logger -t "OMR-VPS" "Shadowsocks restart"
					/etc/init.d/shadowsocks-libev restart
					/etc/init.d/shadowsocks-rust restart
				}
			fi
		}
	fi

	[ -z "$vps_config" ] && vps_config=$(_get_json "config")
	[ -z "$vps_config" ] && return
	kernel="$(echo "$vps_config" | jsonfilter -q -e '@.vps.kernel')"
	[ -z "$kernel" ] && return
	user_permission="$(echo "$vps_config" | jsonfilter -q -e '@.user.permission')"

	if [ "$user_permission" != "ro" ]; then
		#config_load shadowsocks-libev
		#config_foreach _set_ss_server_vps server
		[ -n "$(uci -q get shadowsocks-libev.sss0)" ] && _set_ss_server_vps
		[ -n "$(uci -q get shadowsocks-rust.sss0)" ] && _set_ssgo_server_vps
		[ -n "$(uci -q get v2ray.omr)" ] && _set_v2ray_server_vps
		[ -n "$(uci -q get xray.omr)" ] && _set_xray_server_vps
		([ -n "$(uci -q get glorytun.vpn)" ] || [ -n "$(uci -q get glorytun-udp.vpn)" ]) && [ -z "$(_set_glorytun_vps)" ] && error=1
		[ -n "$(uci -q get openvpn.omr)" ] && [ -z "$(_set_openvpn_vps)" ] && error=1
		[ -n "$(uci -q get mlvpn.general)" ] && [ -z "$(_set_mlvpn_vps)" ] && error=1
		[ -n "$(uci -q get mqvpn.server)" ] && [ -z "$(_set_mqvpn_vps)" ] && error=1
		[ -n "$(uci -q get softethervpn.openmptcprouter)" ] && [ -z "$(_set_softethervpn_vps)" ] && error=1
		_set_vps_firewall
		[ -n "$(uci -q show network | grep wireguard)" ] && [ -z "$(_set_wireguard_vps)" ] && error=1
	fi
	_backup_list
	redirect_port="0"
	if [ "$(uci -q get openmptcprouter.${servername}.redirect_ports)" = "1" ] || [ "$(uci -q get upnpd.config.enabled)" = "1" ]; then
		redirect_port="1"
	fi
	if [ "$user_permission" != "ro" ]; then
		[ -z "$(_set_redirect_ports_from_vps $redirect_port)" ] && error=1
		[ -z "$(_set_mptcp_vps)" ] && error=1
		[ -z "$(_set_vpn_vps)" ] && error=1
		[ -z "$(_set_proxy_vps)" ] && error=1
	fi
	([ -n "$(uci -q get shadowsocks-libev.sss0)" ] || [ -n "$(uci -q get shadowsocks-rust.sss0)" ]) && [ "$(uci -q get shadowsocks-libev.sss0.key)" = "" ] && [ "$(uci -q get shadowsocks-rust.sss0.password)" = "" ] && uci -q set openmptcprouter.${servername}.get_config=1
	
	#_set_pihole
	[ -n "$wanips" ] && _set_wan_ip
	_set_vpn_ip
	([ "$(uci -q get openmptcprouter.${servername}.master)" = "1" ] || [ "$(uci -q get openmptcprouter.${servername}.current)" = "1" ]) && {
		[ "$user_permission" != "ro" ] && _set_vxlan_vps
		_get_vxlan
	}
	config_load network
	lanips=""
	config_foreach _get_lan_ip interface
	_set_lan_ip
	_set_sipalg
	_set_bypass_ips
	config_foreach _delete_client2client route
	if [ "$(uci -q get openmptcprouter.settings.vpn)" != "openvpn" ] && [ "$(echo "$vps_config" | jsonfilter -q -e '@.client2client.enabled')" = "true" ]; then
		_set_client2client
	fi
	if [ "$error" = 0 ]; then
		#logger -t "OMR-VPS" "No errors"
		uci -q set openmptcprouter.${servername}.lastchange=$(date "+%s")
		[ -n "$vps_config" ] && uci -q set openmptcprouter.settings.firstboot=0
	else
		logger -t "OMR-VPS" "Setting server config error, try again"
	fi
	#[ "$error" != 0 ] && logger -t "OMR-VPS" "Error !"
	uci -q batch <<-EOF >/dev/null
		set openmptcprouter.${servername}.admin_error=$error
		commit openmptcprouter
	EOF
}

_set_pihole_server() {
	pservername=$1
	[ -z "$servername" ] && servername=$pservername
	[ -z "$(uci -q get openmptcprouter.${pservername}.username)" ] && return
	[ -z "$(uci -q get openmptcprouter.${pservername}.password)" ] && return
	[ -z "$(uci -q get openmptcprouter.${pservername}.port)" ] && return
	token=""
	vps_config=""
	_login
	[ -z "$token" ] && {
		[ "$(logread | tail -n 3 | grep -c 'vps token')" -lt 3 ] && logger -t "OMR-VPS" "Can't get token, try later"
		uci -q batch <<-EOF >/dev/null
			set openmptcprouter.${pservername}.admin_error=1
		EOF
		return
	}
	_get_pihole $pservername
}

set_pihole() {
	config_load openmptcprouter
	config_foreach _set_pihole_server server
}

token() {
	config_load openmptcprouter
	config_foreach _get_token server
}

_fix_server() {
	uci -q set openmptcprouter.$1=server
}

start_service() {
	serversnb=0
	wanips=""
	serial=$(cat /proc/cpuinfo | grep Serial | awk '{print $3}')
	[ -z "$serial" ] && [ -f "/usr/sbin/dmidecode" ] && serial=$(/usr/sbin/dmidecode -t 1 | egrep 'Serial' | awk '{print $3}')
	uci -q set openmptcprouter.settings.serial=${serial}
	config_load openmptcprouter
	config_foreach _fix_server openmptcprouter
	config_foreach _count_server server
	config_foreach _get_local_wan_ip interface
	config_foreach _config_service server

	uci -q batch <<-EOF >/dev/null
		commit openmptcprouter
	EOF
	if [ "$(ps w | grep sysupgrade)" = "" ]; then
		/etc/init.d/sysupgrade restart
	fi
}

service_triggers() {
	#procd_add_reload_trigger openmptcprouter network shadowsocks-libev v2ray glorytun glorytun-udp mlvpn openvpn dsvpn
	procd_add_reload_trigger shadowsocks-libev shadowsocks-rust v2ray xray glorytun glorytun-udp mlvpn dsvpn mqvpn
	procd_add_config_trigger "config.change" "firewall" /etc/init.d/openmptcprouter-vps set_vps_firewall
	#procd_add_config_trigger "config.change" "network" /etc/init.d/openmptcprouter-vps set_gre_tunnel
	#procd_add_reload_trigger openmptcprouter shadowsocks-libev network upnpd
}
