Has anyone had any success in modifying the gl_tailscale script to use a defined exit node when the toggle switch on the side of the router is turned on?
Hello,
Sorry, the router firmware does not support the toggle on/off the exit node of Tailscale.
Thanks for your suggestion! I will collect this request to the PM team to evaluate.
Hi Bruce,
I was able to get this working. The script below will specify the exit_node_ip to be 100.70.85.109 (my exit node’s IP address) whenever the side toggle switch is turned on. It will revert to the UCI setting (tailscale.settings.exit_node_ip) when the switch is turned off. Anyone wanting to run the command below will need to change the IP of my exit node 100.70.85.109 to the IP of the exit node they’re wanting to use when the side switch is toggled on.
This solution updates the Tailscale configuration on a GL.iNet Slate 7 router to toggle the exit node based on the physical switch position. It:
• Modifies /usr/bin/gl_tailscale to set exit_node_ip to 100.70.85.109 when the switch is “on” or uses the UCI setting when “off”.
• Creates /usr/bin/gl_tailscale_switch_monitor to check the switch status every 5 seconds and restart Tailscale if it changes.
• Ensures guest network routing uses the same switch-determined exit_node_ip.
• Adds the monitor script to startup and runs it in the background.
Router: GL.iNet Slate 7
Caution: This overwrites /usr/bin/gl_tailscale. Back up before proceeding.
Command to Install
Copy and paste this into your router’s CLI via SSH. Replace 100.70.85.109 with your desired exit node IP
cp /usr/bin/gl_tailscale /usr/bin/gl_tailscale.bak && \
cat << 'EOF' > /usr/bin/gl_tailscale
#!/bin/sh
. /lib/functions/gl_util.sh
action="$1"
TS_POLICY_ROUTE=/tmp/ts_policy_route
TS_FIREWALL_SECTION=gltailscale
TAILSCALE_ROUTE_TABLE=52
TAILSCALE_DNS_SERVER="100.100.100.100"
if [ "$action" = "set_route" ];then
sleep 5
ip route add 100.64.0.0/10 dev tailscale0
fi
add_policy_route()
{
target=$1
if [ -n "$target" ]; then
route_param="to $target table main"
/sbin/ip rule add $route_param
echo $route_param >> $TS_POLICY_ROUTE
fi
}
del_policy_route()
{
/sbin/ip rule del to $TAILSCALE_DNS_SERVER lookup $TAILSCALE_ROUTE_TABLE pri 50
if [ -f $TS_POLICY_ROUTE ]; then
while read line; do
if [ -n "$line" ]; then
/sbin/ip rule del $line
fi
done < $TS_POLICY_ROUTE
rm $TS_POLICY_ROUTE
fi
}
add_guest_policy_route()
{
guest_disable=$(uci -q get network.guest.disabled)
exit_node_ip=$1
if [ -n "$exit_node_ip" ] && [ "$guest_disable" = "0" ]; then
guestip=$(ubus call network.interface.guest status|jsonfilter -e '@["ipv4-address"][0].address')
guestmask=$(ubus call network.interface.guest status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$guestip" ] && [ -n "$guestmask" ] ;then
guest_network=$(ipcalc_network $guestip $guestmask | awk -F= '/NETWORK/{print $2}')
guest_mask=$(ipcalc_network $guestip $guestmask | awk -F= '/PREFIX/{print $2}')
fi
if [ -n "$guest_network" ] && [ -n "$guest_mask" ]; then
guest_ip="$guest_network/$guest_mask"
policy_route_param="from $guest_ip table main"
/sbin/ip rule add $policy_route_param
echo $policy_route_param >> $TS_POLICY_ROUTE
fi
fi
}
add_exit_node_rule()
{
count=0
while [ $count -le 5 ]
do
rule_exist=$(ip rule | grep "from all fwmark 0x80000/0x80000 lookup")
ts_prio=$(ip rule | grep "from all lookup 52" | awk -F':' '{print $1}' | head -n 1)
if [ "$rule_exist" = "" ]; then
if [ -n "$ts_prio" ]; then
pre_prio=$(($ts_prio-1))
policy_route_param="from all fwmark 0x80000/0x80000 lookup main prio $pre_prio"
/sbin/ip rule add $policy_route_param
echo $policy_route_param >> $TS_POLICY_ROUTE
fi
else
break
fi
count=$(($count+1))
sleep 1
done
}
add_ts_fw_rule()
{
if [ -f /etc/firewall.tailscale.sh ]; then
/etc/firewall.tailscale.sh &
fi
}
modify_dns_resolv()
{
dns_suffix=$(timeout 2 tailscale status -json | jsonfilter -e '@.MagicDNSSuffix')
rule_exist=$(grep "ts.net/100.100.100.100" /etc/dnsmasq.conf)
domain_orig=$(uci get dhcp.@dnsmasq[0].domain)
domain_orig_clean=$(echo "$domain_orig" | sed "s/.*ts.net //g")
if [ "$1" = "1" ]; then
if [ "$rule_exist" = "" ]; then
[ "$dns_suffix" = "" ] && dns_suffix="ts.net"
echo "server=/$dns_suffix/100.100.100.100" >> /etc/dnsmasq.conf
uci set dhcp.@dnsmasq[0].domain="$dns_suffix $domain_orig_clean"
uci commit dhcp
/etc/init.d/dnsmasq restart
fi
else
uci set dhcp.@dnsmasq[0].domain="$domain_orig_clean"
uci commit dhcp
if [ -n "$rule_exist" ]; then
sed -i '/ts\.net\/100\.100\.100\.100/d' /etc/dnsmasq.conf
/etc/init.d/dnsmasq restart
fi
fi
}
get_switch_status()
{
status=$(get_switch_button_status)
if [ "$status" = "no support" ]; then
echo "no_support"
elif [ "$status" = "on" ]; then
echo "on"
else
echo "off"
fi
}
if [ "$action" = "restart" ];then
for count in `seq 1 10`
do
[ $(pgrep -f "tailscaled") ] && break
sleep 1
done
/etc/init.d/tailscale restart
del_policy_route
add_ts_fw_rule
sys_mode=$(uci -q get glconfig.general.mode)
if [ "$sys_mode" != "router" ]; then
/etc/init.d/tailscale stop
modify_dns_resolv 0
exit 0
fi
enabled=$(uci -q get tailscale.settings.enabled)
if [ "$enabled" = "1" ]; then
/sbin/ip rule add to $TAILSCALE_DNS_SERVER lookup $TAILSCALE_ROUTE_TABLE pri 50
wanip=$(ubus call network.interface.wan status|jsonfilter -e '@["ipv4-address"][0].address')
wanmask=$(ubus call network.interface.wan status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$wanip" ] && [ -n "$wanmask" ] ;then
wan_network=$(ipcalc_network $wanip $wanmask | awk -F= '/NETWORK/{print $2}')
wan_mask=$(ipcalc_network $wanip $wanmask | awk -F= '/PREFIX/{print $2}')
fi
[ -n "$wan_network" -a -n "$wan_mask" ] && wan_ip="$wan_network/$wan_mask"
secondwanip=$(ubus call network.interface.secondwan status|jsonfilter -e '@["ipv4-address"][0].address')
secondwanmask=$(ubus call network.interface.secondwan status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$secondwanip" ] && [ -n "$secondwanmask" ] ;then
secondwan_network=$(ipcalc_network $secondwanip $secondwanmask | awk -F= '/NETWORK/{print $2}')
secondwan_mask=$(ipcalc_network $secondwanip $secondwanmask | awk -F= '/PREFIX/{print $2}')
fi
[ -n "$secondwan_network" -a -n "$secondwan_mask" ] && secondwan_ip="$secondwan_network/$secondwan_mask"
wwanip=$(ubus call network.interface.wwan status|jsonfilter -e '@["ipv4-address"][0].address')
wwanmask=$(ubus call network.interface.wwan status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$wwanip" ] && [ -n "$wwanmask" ] ;then
wwan_network=$(ipcalc_network $wwanip $wwanmask | awk -F= '/NETWORK/{print $2}')
wwan_mask=$(ipcalc_network $wwanip $wwanmask | awk -F= '/PREFIX/{print $2}')
fi
[ -n "$wwan_network" -a -n "$wwan_mask" ] && wwan_ip="$wwan_network/$wan_mask"
tetheringip=$(ubus call network.interface.tethering status|jsonfilter -e '@["ipv4-address"][0].address')
tetheringmask=$(ubus call network.interface.tethering status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$tetheringip" ] && [ -n "$tetheringmask" ] ;then
tethering_network=$(ipcalc_network $tetheringip $tetheringmask | awk -F= '/NETWORK/{print $2}')
tethering_mask=$(ipcalc_network $tetheringip $tetheringmask | awk -F= '/PREFIX/{print $2}')
fi
[ -n "$tethering_network" -a -n "$tethering_mask" ] && tethering_ip="$tethering_network/$tethering_mask"
lanip=$(uci -q get network.lan.ipaddr)
lanmask="$(uci -q get network.lan.netmask)"
lan_network=$(ipcalc_network $lanip $lanmask | awk -F= '/NETWORK/{print $2}')
lan_mask=$(ipcalc_network $lanip $lanmask | awk -F= '/PREFIX/{print $2}')
[ -n "$lan_network" -a -n "$lan_mask" ] && lan_ip="$lan_network/$lan_mask"
switch_status=$(get_switch_status)
if [ "$switch_status" = "on" ]; then
exit_node_ip="100.70.85.109"
else
exit_node_ip=$(uci -q get tailscale.settings.exit_node_ip)
fi
[ -n "$exit_node_ip" ] && lan_enabled="1" || lan_enabled=$(uci -q get tailscale.settings.lan_enabled)
[ -n "$exit_node_ip" ] && wan_enabled="1" || wan_enabled=$(uci -q get tailscale.settings.wan_enabled)
if [ "$lan_enabled" = "1" ]; then
if [ -n "$lan_ip" ]; then
[ -n "$routes" ] && routes="$routes,$lan_ip" || routes="$lan_ip"
fi
fi
if [ "$wan_enabled" = "1" ]; then
if [ -n "$wan_ip" ]; then
[ -n "$routes" ] && routes="$routes,$wan_ip" || routes="$wan_ip"
fi
if [ -n "$wwan_ip" ]; then
[ -n "$routes" ] && routes="$routes,$wwan_ip" || routes="$wwan_ip"
fi
if [ -n "$secondwan_ip" ]; then
[ -n "$routes" ] && routes="$routes,$secondwan_ip" || routes="$secondwan_ip"
fi
if [ -n "$tethering_ip" ]; then
[ -n "$routes" ] && routes="$routes,$tethering_ip" || routes="$tethering_ip"
fi
fi
if [ -n "$routes" ]; then
param="--advertise-routes=$routes"
else
param=""
fi
if [ -n "$exit_node_ip" ];then
add_exit_node_rule
param="$param --exit-node-allow-lan-access --exit-node=$exit_node_ip"
fi
[ -n "$lan_ip" ] && add_policy_route $lan_ip
[ -n "$wan_ip" ] && add_policy_route $wan_ip
[ -n "$secondwan_ip" ] && add_policy_route $secondwan_ip
[ -n "$wwan_ip" ] && add_policy_route $wwan_ip
[ -n "$tethering_ip" ] && add_policy_route $tethering_ip
add_guest_policy_route "$exit_node_ip"
timeout 10 /usr/sbin/tailscale up --reset --accept-routes $param --timeout 3s --accept-dns=false > /dev/null
else
/etc/init.d/tailscale stop
fi
modify_dns_resolv $enabled
fi
EOF
cat << 'EOF' > /usr/bin/gl_tailscale_switch_monitor
#!/bin/sh
. /lib/functions/gl_util.sh
STATE_FILE="/tmp/gl_tailscale_switch_status"
POLL_INTERVAL=5
LOG_FILE="/tmp/gl_tailscale_switch_monitor.log"
get_switch_status() {
status=$(get_switch_button_status)
if [ "$status" = "no support" ]; then
echo "no_support"
elif [ "$status" = "on" ]; then
echo "on"
else
echo "off"
fi
}
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}
if [ ! -f "$STATE_FILE" ]; then
echo "unknown" > "$STATE_FILE"
log_message "Initialized state file with 'unknown' status"
fi
while true; do
current_status=$(get_switch_status)
last_status=$(cat "$STATE_FILE")
if [ "$current_status" != "$last_status" ]; then
log_message "Switch status changed from '$last_status' to '$current_status'"
echo "$current_status" > "$STATE_FILE"
log_message "Executing /usr/bin/gl_tailscale restart"
/usr/bin/gl_tailscale restart
if [ $? -eq 0 ]; then
log_message "Successfully executed gl_tailscale restart"
else
log_message "Failed to execute gl_tailscale restart"
fi
fi
sleep "$POLL_INTERVAL"
done
EOF
chmod +x /usr/bin/gl_tailscale /usr/bin/gl_tailscale_switch_monitor && \
grep -q "/usr/bin/gl_tailscale_switch_monitor" /etc/rc.local || sed -i '/exit 0/i \/usr/bin\/gl_tailscale_switch_monitor &' /etc/rc.local && \
/usr/bin/gl_tailscale_switch_monitor &
Great!
Thank you for sharing and testing!
Update:
The command below will use the last exit node that was chosen in the html based console for when the side toggle switch is turned on. Instead of having to edit the script with the specific ip address of the desired exit node, the user instead will just need to pick that ip address once in the html console interface after running the command below. After that the external toggle switch will turn on the exit node to be whatever the most recent exit node chosen in the tailscale settings from the html console screen was.
cp /usr/bin/gl_tailscale /usr/bin/gl_tailscale.bak && \
mkdir -p /etc/tailscale && \
cat << 'EOF' > /usr/bin/gl_tailscale
#!/bin/sh
. /lib/functions/gl_util.sh
action="$1"
TS_POLICY_ROUTE=/tmp/ts_policy_route
TS_FIREWALL_SECTION=gltailscale
TAILSCALE_ROUTE_TABLE=52
TAILSCALE_DNS_SERVER="100.100.100.100"
LAST_EXIT_NODE_FILE="/etc/tailscale/last_exit_node_ip"
LOG_FILE="/tmp/gl_tailscale_switch_monitor.log"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}
# Check for required dependencies
for cmd in ubus jsonfilter ipcalc_network tailscale; do
if ! command -v "$cmd" >/dev/null; then
log_message "Error: Required command '$cmd' not found"
exit 1
fi
done
if [ "$action" = "set_route" ];then
sleep 5
ip route add 100.64.0.0/10 dev tailscale0
fi
add_policy_route()
{
target=$1
if [ -n "$target" ]; then
route_param="to $target table main"
/sbin/ip rule add $route_param
echo $route_param >> $TS_POLICY_ROUTE
fi
}
del_policy_route()
{
/sbin/ip rule del to $TAILSCALE_DNS_SERVER lookup $TAILSCALE_ROUTE_TABLE pri 50 2>/dev/null
if [ -f $TS_POLICY_ROUTE ]; then
while read line; do
if [ -n "$line" ]; then
/sbin/ip rule del $line 2>/dev/null
fi
done < $TS_POLICY_ROUTE
rm $TS_POLICY_ROUTE
fi
}
add_guest_policy_route()
{
guest_disable=$(uci -q get network.guest.disabled)
exit_node_ip=$1
if [ -n "$exit_node_ip" ] && [ "$guest_disable" = "0" ]; then
guestip=$(ubus call network.interface.guest status|jsonfilter -e '@["ipv4-address"][0].address')
guestmask=$(ubus call network.interface.guest status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$guestip" ] && [ -n "$guestmask" ] ;then
guest_network=$(ipcalc_network $guestip $guestmask | awk -F= '/NETWORK/{print $2}')
guest_mask=$(ipcalc_network $guestip $guestmask | awk -F= '/PREFIX/{print $2}')
fi
if [ -n "$guest_network" ] && [ -n "$guest_mask" ]; then
guest_ip="$guest_network/$guest_mask"
policy_route_param="from $guest_ip table main"
/sbin/ip rule add $policy_route_param
echo $policy_route_param >> $TS_POLICY_ROUTE
fi
fi
}
add_exit_node_rule()
{
count=0
while [ $count -le 5 ]
do
rule_exist=$(ip rule | grep "from all fwmark 0x80000/0x80000 lookup")
ts_prio=$(ip rule | grep "from all lookup 52" | awk -F':' '{print $1}' | head -n 1)
if [ "$rule_exist" = "" ]; then
if [ -n "$ts_prio" ]; then
pre_prio=$(($ts_prio-1))
policy_route_param="from all fwmark 0x80000/0x80000 lookup main prio $pre_prio"
/sbin/ip rule add $policy_route_param
echo $policy_route_param >> $TS_POLICY_ROUTE
fi
else
break
fi
count=$(($count+1))
sleep 1
done
}
add_ts_fw_rule()
{
if [ -f /etc/firewall.tailscale.sh ]; then
/etc/firewall.tailscale.sh &
fi
}
modify_dns_resolv()
{
dns_suffix=$(timeout 2 tailscale status -json | jsonfilter -e '@.MagicDNSSuffix' 2>/dev/null)
if [ $? -ne 0 ]; then
log_message "Warning: Failed to retrieve MagicDNSSuffix from tailscale status"
return 1
fi
rule_exist=$(grep "ts.net/100.100.100.100" /etc/dnsmasq.conf)
domain_orig=$(uci get dhcp.@dnsmasq[0].domain)
domain_orig_clean=$(echo "$domain_orig" | sed "s/.*ts.net //g")
if [ "$1" = "1" ]; then
if [ "$rule_exist" = "" ]; then
[ "$dns_suffix" = "" ] && dns_suffix="ts.net"
echo "server=/$dns_suffix/100.100.100.100" >> /etc/dnsmasq.conf
uci set dhcp.@dnsmasq[0].domain="$dns_suffix $domain_orig_clean"
uci commit dhcp
/etc/init.d/dnsmasq restart
fi
else
uci set dhcp.@dnsmasq[0].domain="$domain_orig_clean"
uci commit dhcp
if [ -n "$rule_exist" ]; then
sed -i '/ts\.net\/100\.100\.100\.100/d' /etc/dnsmasq.conf
/etc/init.d/dnsmasq restart
fi
fi
}
get_switch_status()
{
if ! command -v get_switch_button_status >/dev/null; then
log_message "Error: get_switch_button_status not found in /lib/functions/gl_util.sh"
echo "no_support"
return 1
fi
status=$(get_switch_button_status)
if [ "$status" = "no support" ]; then
echo "no_support"
elif [ "$status" = "on" ]; then
echo "on"
else
echo "off"
fi
}
if [ "$action" = "restart" ];then
for count in `seq 1 20`
do
[ $(pgrep -f "tailscaled") ] && break
sleep 1
done
if [ $count -eq 21 ]; then
log_message "Error: tailscaled not running after 20 seconds"
exit 1
fi
/etc/init.d/tailscale restart
del_policy_route
add_ts_fw_rule
sys_mode=$(uci -q get glconfig.general.mode)
if [ "$sys_mode" != "router" ]; then
/etc/init.d/tailscale stop
modify_dns_resolv 0
exit 0
fi
enabled=$(uci -q get tailscale.settings.enabled)
if [ "$enabled" = "1" ]; then
/sbin/ip rule add to $TAILSCALE_DNS_SERVER lookup $TAILSCALE_ROUTE_TABLE pri 50
# Save UCI exit_node_ip if defined
uci_exit_node_ip=$(uci -q get tailscale.settings.exit_node_ip)
if [ -n "$uci_exit_node_ip" ]; then
echo "$uci_exit_node_ip" > "$LAST_EXIT_NODE_FILE"
log_message "Saved UCI exit_node_ip: $uci_exit_node_ip to $LAST_EXIT_NODE_FILE"
fi
wanip=$(ubus call network.interface.wan status|jsonfilter -e '@["ipv4-address"][0].address')
wanmask=$(ubus call network.interface.wan status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$wanip" ] && [ -n "$wanmask" ] ;then
wan_network=$(ipcalc_network $wanip $wanmask | awk -F= '/NETWORK/{print $2}')
wan_mask=$(ipcalc_network $wanip $wanmask | awk -F= '/PREFIX/{print $2}')
fi
[ -n "$wan_network" -a -n "$wan_mask" ] && wan_ip="$wan_network/$wan_mask"
secondwanip=$(ubus call network.interface.secondwan status|jsonfilter -e '@["ipv4-address"][0].address')
secondwanmask=$(ubus call network.interface.secondwan status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$secondwanip" ] && [ -n "$secondwanmask" ] ;then
secondwan_network=$(ipcalc_network $secondwanip $secondwanmask | awk -F= '/NETWORK/{print $2}')
secondwan_mask=$(ipcalc_network $secondwanip $secondwanmask | awk -F= '/PREFIX/{print $2}')
fi
[ -n "$secondwan_network" -a -n "$secondwan_mask" ] && secondwan_ip="$secondwan_network/$secondwan_mask"
wwanip=$(ubus call network.interface.wwan status|jsonfilter -e '@["ipv4-address"][0].address')
wwanmask=$(ubus call network.interface.wwan status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$wwanip" ] && [ -n "$wwanmask" ] ;then
wwan_network=$(ipcalc_network $wwanip $wwanmask | awk -F= '/NETWORK/{print $2}')
wwan_mask=$(ipcalc_network $wwanip $wwanmask | awk -F= '/PREFIX/{print $2}')
fi
[ -n "$wwan_network" -a -n "$wwan_mask" ] && wwan_ip="$wwan_network/$wan_mask"
tetheringip=$(ubus call network.interface.tethering status|jsonfilter -e '@["ipv4-address"][0].address')
tetheringmask=$(ubus call network.interface.tethering status|jsonfilter -e '@["ipv4-address"][0].mask')
if [ -n "$tetheringip" ] && [ -n "$tetheringmask" ] ;then
tethering_network=$(ipcalc_network $tetheringip $tetheringmask | awk -F= '/NETWORK/{print $2}')
tethering_mask=$(ipcalc_network $tetheringip $tetheringmask | awk -F= '/PREFIX/{print $2}')
fi
[ -n "$tethering_network" -a -n "$tethering_mask" ] && tethering_ip="$tethering_network/$tethering_mask"
lanip=$(uci -q get network.lan.ipaddr)
lanmask="$(uci -q get network.lan.netmask)"
lan_network=$(ipcalc_network $lanip $lanmask | awk -F= '/NETWORK/{print $2}')
lan_mask=$(ipcalc_network $lanip $lanmask | awk -F= '/PREFIX/{print $2}')
[ -n "$lan_network" -a -n "$lan_mask" ] && lan_ip="$lan_network/$lan_mask"
switch_status=$(get_switch_status)
if [ "$switch_status" = "on" ]; then
if [ -f "$LAST_EXIT_NODE_FILE" ]; then
exit_node_ip=$(cat "$LAST_EXIT_NODE_FILE")
log_message "Using exit_node_ip: $exit_node_ip from $LAST_EXIT_NODE_FILE"
else
exit_node_ip=""
log_message "No exit_node_ip found in $LAST_EXIT_NODE_FILE"
fi
else
exit_node_ip=""
log_message "Switch off, no exit node set"
fi
[ -n "$exit_node_ip" ] && lan_enabled="1" || lan_enabled=$(uci -q get tailscale.settings.lan_enabled)
[ -n "$exit_node_ip" ] && wan_enabled="1" || wan_enabled=$(uci -q get tailscale.settings.wan_enabled)
if [ "$lan_enabled" = "1" ]; then
if [ -n "$lan_ip" ]; then
[ -n "$routes" ] && routes="$routes,$lan_ip" || routes="$lan_ip"
fi
fi
if [ "$wan_enabled" = "1" ]; then
if [ -n "$wan_ip" ]; then
[ -n "$routes" ] && routes="$routes,$wan_ip" || routes="$wan_ip"
fi
if [ -n "$wwan_ip" ]; then
[ -n "$routes" ] && routes="$routes,$wwan_ip" || routes="$wwan_ip"
fi
if [ -n "$secondwan_ip" ]; then
[ -n "$routes" ] && routes="$routes,$secondwan_ip" || routes="$secondwan_ip"
fi
if [ -n "$tethering_ip" ]; then
[ -n "$routes" ] && routes="$routes,$tethering_ip" || routes="$tethering_ip"
fi
fi
if [ -n "$routes" ]; then
param="--advertise-routes=$routes"
else
param=""
fi
if [ -n "$exit_node_ip" ];then
add_exit_node_rule
param="$param --exit-node-allow-lan-access --exit-node=$exit_node_ip"
fi
[ -n "$lan_ip" ] && add_policy_route $lan_ip
[ -n "$wan_ip" ] && add_policy_route $wan_ip
[ -n "$secondwan_ip" ] && add_policy_route $secondwan_ip
[ -n "$wwan_ip" ] && add_policy_route $wwan_ip
[ -n "$tethering_ip" ] && add_policy_route $tethering_ip
add_guest_policy_route "$exit_node_ip"
timeout 10 /usr/sbin/tailscale up --reset --accept-routes $param --timeout 3s --accept-dns=false > /dev/null
if [ $? -ne 0 ]; then
log_message "Error: tailscale up command failed"
exit 1
fi
else
/etc/init.d/tailscale stop
fi
modify_dns_resolv $enabled
fi
EOF
cat << 'EOF' > /usr/bin/gl_tailscale_switch_monitor
#!/bin/sh
. /lib/functions/gl_util.sh
STATE_FILE="/tmp/gl_tailscale_switch_status"
POLL_INTERVAL=5
LOG_FILE="/tmp/gl_tailscale_switch_monitor.log"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}
get_switch_status() {
if ! command -v get_switch_button_status >/dev/null; then
log_message "Error: get_switch_button_status not found in /lib/functions/gl_util.sh"
echo "no_support"
return 1
fi
status=$(get_switch_button_status)
if [ "$status" = "no support" ]; then
echo "no_support"
elif [ "$status" = "on" ]; then
echo "on"
else
echo "off"
fi
}
if [ ! -f "$STATE_FILE" ]; then
echo "unknown" > "$STATE_FILE"
log_message "Initialized state file with 'unknown' status"
fi
while true; do
current_status=$(get_switch_status)
last_status=$(cat "$STATE_FILE")
if [ "$current_status" != "$last_status" ]; then
log_message "Switch status changed from '$last_status' to '$current_status'"
echo "$current_status" > "$STATE_FILE"
log_message "Executing /usr/bin/gl_tailscale restart"
/usr/bin/gl_tailscale restart
if [ $? -eq 0 ]; then
log_message "Successfully executed gl_tailscale restart"
else
log_message "Failed to execute gl_tailscale restart"
fi
fi
sleep "$POLL_INTERVAL"
done
EOF
chmod +x /usr/bin/gl_tailscale /usr/bin/gl_tailscale_switch_monitor && \
grep -q "/usr/bin/gl_tailscale_switch_monitor" /etc/rc.local || sed -i '/exit 0/i \/usr/bin\/gl_tailscale_switch_monitor &' /etc/rc.local && \
sleep 2 && /usr/bin/gl_tailscale_switch_monitor &
I’ve made an additional update. The newest command will find the location of places needing changed in the /usr/bin/gl_tailscale script and make the appropriate changes without overwriting the entire /usr/bin/gl_tailscale file. The purpose of this change is to make this command more likely to work on other gl-inet devices and continue to work in future firmware updates.
cp /usr/bin/gl_tailscale /usr/bin/gl_tailscale.bak && \
mkdir -p /etc/tailscale && \
sed -i '/^[[:space:]]*exit_node_ip=$(uci -q get tailscale\.settings\.exit_node_ip)/ {
s/.*/exit_node_ip=$(uci -q get tailscale.settings.exit_node_ip)\n if [ -n "$exit_node_ip" ]; then\n echo "$exit_node_ip" > "\/etc\/tailscale\/last_exit_node_ip"\n fi/
}' /usr/bin/gl_tailscale && \
sed -i '/^[[:space:]]*switch_status=$(get_switch_status)/,/^[[:space:]]*exit_node_ip=$(uci -q get tailscale\.settings\.exit_node_ip)/ {
/switch_status=$(get_switch_status)/!d
/exit_node_ip=$(uci -q get tailscale\.settings\.exit_node_ip)/!d
s/.*/switch_status=$(get_switch_status)\n if [ "$switch_status" = "on" ]; then\n if [ -f "\/etc\/tailscale\/last_exit_node_ip" ]; then\n exit_node_ip=$(cat "\/etc\/tailscale\/last_exit_node_ip")\n else\n exit_node_ip=""\n fi\n else\n exit_node_ip=""\n fi/
}' /usr/bin/gl_tailscale && \
if ! grep -q "get_switch_status()" /usr/bin/gl_tailscale; then
sed -i '/action="$1"/a \
get_switch_status() \{\
status=$(get_switch_button_status 2>/dev/null || echo "no support")\
if [ "$status" = "no support" ] || [ "$status" = "" ]; then\
echo "no_support"\
elif [ "$status" = "on" ]; then\
echo "on"\
else\
echo "off"\
fi\
\}' /usr/bin/gl_tailscale
fi && \
cat << 'EOF' > /usr/bin/gl_tailscale_switch_monitor
#!/bin/sh
. /lib/functions/gl_util.sh
STATE_FILE="/tmp/gl_tailscale_switch_status"
POLL_INTERVAL=5
LOG_FILE="/tmp/gl_tailscale_switch_monitor.log"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}
get_switch_status() {
status=$(get_switch_button_status 2>/dev/null || echo "no support")
if [ "$status" = "no support" ] || [ "$status" = "" ]; then
echo "no_support"
elif [ "$status" = "on" ]; then
echo "on"
else
echo "off"
fi
}
if [ ! -f "$STATE_FILE" ]; then
echo "unknown" > "$STATE_FILE"
log_message "Initialized state file with 'unknown' status"
fi
while true; do
current_status=$(get_switch_status)
last_status=$(cat "$STATE_FILE")
if [ "$current_status" != "$last_status" ]; then
log_message "Switch status changed from '$last_status' to '$current_status'"
echo "$current_status" > "$STATE_FILE"
log_message "Executing /usr/bin/gl_tailscale restart"
/usr/bin/gl_tailscale restart
if [ $? -eq 0 ]; then
log_message "Successfully executed gl_tailscale restart"
else
log_message "Failed to execute gl_tailscale restart"
fi
fi
sleep "$POLL_INTERVAL"
done
EOF
chmod +x /usr/bin/gl_tailscale_switch_monitor && \
grep -q "/usr/bin/gl_tailscale_switch_monitor" /etc/rc.local || \
sed -i '/exit 0/i \/usr/bin\/gl_tailscale_switch_monitor \&' /etc/rc.local && \
sleep 2 && /usr/bin/gl_tailscale_switch_monitor &
I’ve tested with no issues on Slate 7 running firmware 4.8.1. I’m not able to test on another device. The use case for this for me is to be able to toggle on the exit node when I’m on a hotel’s network without having to log in to the web console. Changing the position of the toggle switch on the side of the router is much simpler.