mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 05:13:56 +08:00
🐤 Sync 2026-09-29 08:28:44
This commit is contained in:
1 parent
5fe727a4a8
commit
a96317f317
99 files changed
+3036
-17584
No files matched your search
@@ -1,523 +1,79 @@
|
||||
#!/bin/sh
|
||||
# Tailscale update helper script
|
||||
|
||||
# Copyright (C) 2025 asvow
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
TAILSCALE_BIN="${TAILSCALE_BIN:-/usr/sbin/tailscale}"
|
||||
LOGGER_CMD="${LOGGER_CMD:-logger}"
|
||||
FLOCK_BIN="${FLOCK_BIN:-flock}"
|
||||
IFCONFIG_BIN="${IFCONFIG_BIN:-ifconfig}"
|
||||
FIREWALL_INIT="${FIREWALL_INIT:-/etc/init.d/firewall}"
|
||||
DNSMASQ_INIT="${DNSMASQ_INIT:-/etc/init.d/dnsmasq}"
|
||||
TAILSCALE_SECRETS_BIN="${TAILSCALE_SECRETS_BIN:-/usr/sbin/tailscale_secrets}"
|
||||
TAILSCALE_HELPER_STATE_DIR="${TAILSCALE_HELPER_STATE_DIR:-/var/lib/tailscale}"
|
||||
EXIT_NODE_FIREWALL_STATE_FILE="$TAILSCALE_HELPER_STATE_DIR/exit_node_firewall_state"
|
||||
FIREWALL_PENDING_STATE_FILE="${FIREWALL_PENDING_STATE_FILE:-/var/run/tailscale-firewall-pending}"
|
||||
LOCK_FILE="${LOCK_FILE:-/var/lock/tailscale.lock}"
|
||||
TAILSCALE_PORT="${TAILSCALE_PORT:-41641}"
|
||||
TAILSCALE_STATE_WAS_PRESENT="${TAILSCALE_STATE_WAS_PRESENT:-0}"
|
||||
ALLOW_WAN_DIRECT="${ALLOW_WAN_DIRECT:-0}"
|
||||
WAN_DIRECT_ZONES="${WAN_DIRECT_ZONES:-wan}"
|
||||
POLICY_ROUTING_HELPER="${POLICY_ROUTING_HELPER:-/usr/sbin/tailscale_policy_routing}"
|
||||
|
||||
log_error() {
|
||||
"$LOGGER_CMD" -p daemon.err -t tailscale_helper "$(date '+%Y/%m/%d %H:%M:%S') $1"
|
||||
}
|
||||
|
||||
# Error handling function
|
||||
revert_exit() {
|
||||
log_error "$1"
|
||||
uci revert dhcp && uci revert network && uci revert firewall
|
||||
exit 1
|
||||
}
|
||||
|
||||
find_lan_wan_forwarding() {
|
||||
uci show firewall | awk -F= '
|
||||
$1 ~ /^firewall\.@forwarding\[[0-9]+\]$/ && $2 == "forwarding" {
|
||||
section=$1
|
||||
next
|
||||
}
|
||||
$1 ~ /^firewall\.@forwarding\[[0-9]+\]\.src$/ {
|
||||
value=$2
|
||||
gsub(/\047/, "", value)
|
||||
if (section != "" && value == "lan") {
|
||||
src[section]=1
|
||||
}
|
||||
next
|
||||
}
|
||||
$1 ~ /^firewall\.@forwarding\[[0-9]+\]\.dest$/ {
|
||||
value=$2
|
||||
gsub(/\047/, "", value)
|
||||
if (section != "" && value == "wan") {
|
||||
dest[section]=1
|
||||
}
|
||||
}
|
||||
END {
|
||||
for (section in src) {
|
||||
if (dest[section]) {
|
||||
print section
|
||||
exit
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
valid_port() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*) return 1 ;;
|
||||
*) [ "$1" -ge 1 ] && [ "$1" -le 65535 ] ;;
|
||||
esac
|
||||
}
|
||||
|
||||
normalize_uci_id() {
|
||||
printf '%s' "$1" | sed 's/[^A-Za-z0-9_]/_/g'
|
||||
}
|
||||
|
||||
firewall_section_has_only_options() {
|
||||
local section="$1"
|
||||
local allowed="$2"
|
||||
local output option
|
||||
|
||||
output="$(uci -q show "firewall.$section" 2>/dev/null)" || return 1
|
||||
for option in $(printf '%s\n' "$output" | sed -n "s/^firewall\\.$section\\.\\([^.=]*\\)=.*/\\1/p"); do
|
||||
case " $allowed " in
|
||||
*" $option "*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
delete_named_firewall_sections() {
|
||||
local output section
|
||||
|
||||
output="$(uci -q show firewall 2>/dev/null)" || return 1
|
||||
for section in "$@"; do
|
||||
if printf '%s\n' "$output" | grep -q "^firewall\\.$section="; then
|
||||
uci -q delete "firewall.$section" || return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
delete_wan_direct_rules() {
|
||||
local output section
|
||||
|
||||
output="$(uci -q show firewall 2>/dev/null)" || return 1
|
||||
if printf '%s\n' "$output" | grep -q '^firewall\.ts_wan_direct='; then
|
||||
uci -q delete firewall.ts_wan_direct || return 1
|
||||
fi
|
||||
for section in $(printf '%s\n' "$output" | sed -n 's/^firewall\.\(ts_wan_direct_[^.=]*\)=[^=]*$/\1/p'); do
|
||||
uci -q delete "firewall.$section" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
wan_direct_rule_matches() {
|
||||
local section="$1"
|
||||
local port="$2"
|
||||
local zone="$3"
|
||||
|
||||
[ "$(uci -q get "firewall.$section" 2>/dev/null)" = "rule" ] &&
|
||||
[ "$(uci -q get "firewall.$section.name" 2>/dev/null)" = "Allow-Tailscale-WAN-$port-$zone" ] &&
|
||||
[ "$(uci -q get "firewall.$section.src" 2>/dev/null)" = "$zone" ] &&
|
||||
[ "$(uci -q get "firewall.$section.proto" 2>/dev/null)" = "udp" ] &&
|
||||
[ "$(uci -q get "firewall.$section.dest_port" 2>/dev/null)" = "$port" ] &&
|
||||
[ "$(uci -q get "firewall.$section.target" 2>/dev/null)" = "ACCEPT" ] &&
|
||||
firewall_section_has_only_options "$section" "name src proto dest_port target"
|
||||
}
|
||||
|
||||
wan_direct_rules_match() {
|
||||
local port="$1"
|
||||
local output zones zone zone_id section index seen current_count
|
||||
|
||||
output="$(uci -q show firewall 2>/dev/null)" || return 1
|
||||
printf '%s\n' "$output" | grep -q '^firewall\.ts_wan_direct=' && return 1
|
||||
|
||||
current_count=0
|
||||
for section in $(printf '%s\n' "$output" | sed -n 's/^firewall\.\(ts_wan_direct_[^.=]*\)=[^=]*$/\1/p'); do
|
||||
current_count=$((current_count + 1))
|
||||
done
|
||||
|
||||
if [ "$ALLOW_WAN_DIRECT" != "1" ]; then
|
||||
[ "$current_count" -eq 0 ]
|
||||
return $?
|
||||
fi
|
||||
|
||||
zones="${WAN_DIRECT_ZONES:-wan}"
|
||||
index=0
|
||||
seen=" "
|
||||
for zone in $zones; do
|
||||
[ -n "$zone" ] || continue
|
||||
case "$seen" in
|
||||
*" $zone "*) continue ;;
|
||||
esac
|
||||
seen="${seen}${zone} "
|
||||
index=$((index + 1))
|
||||
zone_id="$(normalize_uci_id "$zone")"
|
||||
[ -n "$zone_id" ] || zone_id="zone"
|
||||
section="ts_wan_direct_${index}_${zone_id}"
|
||||
wan_direct_rule_matches "$section" "$port" "$zone" || return 1
|
||||
done
|
||||
|
||||
[ "$index" -gt 0 ] && [ "$current_count" -eq "$index" ]
|
||||
}
|
||||
|
||||
configure_wan_direct_rule() {
|
||||
local port zones zone zone_id section index seen
|
||||
|
||||
port="$TAILSCALE_PORT"
|
||||
valid_port "$port" || port=41641
|
||||
wan_direct_rules_match "$port" && return 0
|
||||
|
||||
delete_wan_direct_rules || return 1
|
||||
|
||||
[ "$ALLOW_WAN_DIRECT" = "1" ] || return 0
|
||||
|
||||
zones="${WAN_DIRECT_ZONES:-wan}"
|
||||
index=0
|
||||
seen=" "
|
||||
for zone in $zones; do
|
||||
[ -n "$zone" ] || continue
|
||||
case "$seen" in
|
||||
*" $zone "*) continue ;;
|
||||
esac
|
||||
seen="${seen}${zone} "
|
||||
index=$((index + 1))
|
||||
zone_id="$(normalize_uci_id "$zone")"
|
||||
[ -n "$zone_id" ] || zone_id="zone"
|
||||
section="ts_wan_direct_${index}_${zone_id}"
|
||||
|
||||
uci set "firewall.$section=rule" || return 1
|
||||
uci set "firewall.$section.name=Allow-Tailscale-WAN-$port-$zone" || return 1
|
||||
uci set "firewall.$section.src=$zone" || return 1
|
||||
uci set "firewall.$section.proto=udp" || return 1
|
||||
uci set "firewall.$section.dest_port=$port" || return 1
|
||||
uci set "firewall.$section.target=ACCEPT" || return 1
|
||||
done
|
||||
|
||||
[ "$index" -gt 0 ] || return 1
|
||||
}
|
||||
|
||||
access_has() {
|
||||
case " $ACCESS " in
|
||||
*" $1 "*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
tailscale_zone_matches() {
|
||||
local masq="$1"
|
||||
|
||||
[ "$(uci -q get firewall.tszone 2>/dev/null)" = "zone" ] &&
|
||||
[ "$(uci -q get firewall.tszone.input 2>/dev/null)" = "ACCEPT" ] &&
|
||||
[ "$(uci -q get firewall.tszone.output 2>/dev/null)" = "ACCEPT" ] &&
|
||||
[ "$(uci -q get firewall.tszone.forward 2>/dev/null)" = "ACCEPT" ] &&
|
||||
[ "$(uci -q get firewall.tszone.masq 2>/dev/null)" = "$masq" ] &&
|
||||
[ "$(uci -q get firewall.tszone.mtu_fix 2>/dev/null)" = "1" ] &&
|
||||
[ "$(uci -q get firewall.tszone.name 2>/dev/null)" = "tailscale" ] &&
|
||||
[ "$(uci -q get firewall.tszone.device 2>/dev/null)" = "tailscale0" ] &&
|
||||
firewall_section_has_only_options tszone "input output forward masq mtu_fix name device"
|
||||
}
|
||||
|
||||
forwarding_rule_matches() {
|
||||
local section="$1"
|
||||
local src="$2"
|
||||
local dest="$3"
|
||||
|
||||
[ "$(uci -q get "firewall.$section" 2>/dev/null)" = "forwarding" ] &&
|
||||
[ "$(uci -q get "firewall.$section.src" 2>/dev/null)" = "$src" ] &&
|
||||
[ "$(uci -q get "firewall.$section.dest" 2>/dev/null)" = "$dest" ] &&
|
||||
firewall_section_has_only_options "$section" "src dest"
|
||||
}
|
||||
|
||||
tailscale_access_rules_match() {
|
||||
local masq="$1"
|
||||
local output section token src dest zone_required=0
|
||||
|
||||
output="$(uci -q show firewall 2>/dev/null)" || return 1
|
||||
[ -n "$ACCESS" ] && zone_required=1
|
||||
[ "$DISABLE_SNAT_SUBNET_ROUTES" = "1" ] && zone_required=1
|
||||
|
||||
if [ "$zone_required" = 0 ]; then
|
||||
for section in tszone ts_ac_lan ts_ac_wan lan_ac_ts wan_ac_ts; do
|
||||
printf '%s\n' "$output" | grep -q "^firewall\\.$section=" && return 1
|
||||
done
|
||||
return 0
|
||||
fi
|
||||
|
||||
tailscale_zone_matches "$masq" || return 1
|
||||
for token in ts_ac_lan ts_ac_wan lan_ac_ts wan_ac_ts; do
|
||||
case "$token" in
|
||||
ts_ac_lan) src=tailscale; dest=lan ;;
|
||||
ts_ac_wan) src=tailscale; dest=wan ;;
|
||||
lan_ac_ts) src=lan; dest=tailscale ;;
|
||||
wan_ac_ts) src=wan; dest=tailscale ;;
|
||||
esac
|
||||
if access_has "$token"; then
|
||||
forwarding_rule_matches "$token" "$src" "$dest" || return 1
|
||||
elif printf '%s\n' "$output" | grep -q "^firewall\\.$token="; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
configure_tailscale_access_rules() {
|
||||
local masq=1 token src dest
|
||||
|
||||
[ "$DISABLE_SNAT_SUBNET_ROUTES" = "1" ] && masq=0
|
||||
tailscale_access_rules_match "$masq" && return 0
|
||||
|
||||
delete_named_firewall_sections tszone ts_ac_lan ts_ac_wan lan_ac_ts wan_ac_ts || return 1
|
||||
[ -n "$ACCESS" ] || [ "$DISABLE_SNAT_SUBNET_ROUTES" = "1" ] || return 0
|
||||
|
||||
uci set firewall.tszone='zone' || return 1
|
||||
uci set firewall.tszone.input='ACCEPT' || return 1
|
||||
uci set firewall.tszone.output='ACCEPT' || return 1
|
||||
uci set firewall.tszone.forward='ACCEPT' || return 1
|
||||
uci set "firewall.tszone.masq=$masq" || return 1
|
||||
uci set firewall.tszone.mtu_fix='1' || return 1
|
||||
uci set firewall.tszone.name='tailscale' || return 1
|
||||
uci add_list firewall.tszone.device='tailscale0' || return 1
|
||||
|
||||
for token in ts_ac_lan ts_ac_wan lan_ac_ts wan_ac_ts; do
|
||||
access_has "$token" || continue
|
||||
case "$token" in
|
||||
ts_ac_lan) src=tailscale; dest=lan ;;
|
||||
ts_ac_wan) src=tailscale; dest=wan ;;
|
||||
lan_ac_ts) src=lan; dest=tailscale ;;
|
||||
wan_ac_ts) src=wan; dest=tailscale ;;
|
||||
esac
|
||||
uci set "firewall.$token=forwarding" || return 1
|
||||
uci set "firewall.$token.src=$src" || return 1
|
||||
uci set "firewall.$token.dest=$dest" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
apply_firewall_changes() {
|
||||
local changes pending_dir had_pending=0
|
||||
|
||||
changes="$(uci changes firewall 2>/dev/null)" || return 1
|
||||
[ -e "$FIREWALL_PENDING_STATE_FILE" ] && had_pending=1
|
||||
if [ -n "$changes" ]; then
|
||||
pending_dir="${FIREWALL_PENDING_STATE_FILE%/*}"
|
||||
[ "$pending_dir" = "$FIREWALL_PENDING_STATE_FILE" ] && pending_dir=.
|
||||
mkdir -p "$pending_dir" || return 1
|
||||
: >"$FIREWALL_PENDING_STATE_FILE" || return 1
|
||||
if ! uci commit firewall; then
|
||||
[ "$had_pending" -eq 1 ] || rm -f "$FIREWALL_PENDING_STATE_FILE"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
[ -e "$FIREWALL_PENDING_STATE_FILE" ] || return 0
|
||||
if "$FIREWALL_INIT" reload || "$FIREWALL_INIT" restart; then
|
||||
rm -f "$FIREWALL_PENDING_STATE_FILE" || return 1
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
save_exit_node_firewall_state() {
|
||||
local default_forward lan_wan_section lan_wan_enabled state_tmp
|
||||
|
||||
[ -f "$EXIT_NODE_FIREWALL_STATE_FILE" ] && return 0
|
||||
mkdir -p "$TAILSCALE_HELPER_STATE_DIR" || return 1
|
||||
state_tmp="${EXIT_NODE_FIREWALL_STATE_FILE}.tmp"
|
||||
: >"$state_tmp" || return 1
|
||||
|
||||
if default_forward="$(uci get firewall.@defaults[0].forward 2>/dev/null)"; then
|
||||
printf 'default_forward_present=1\n' >>"$state_tmp"
|
||||
printf 'default_forward_value=%s\n' "$default_forward" >>"$state_tmp"
|
||||
else
|
||||
printf 'default_forward_present=0\n' >>"$state_tmp"
|
||||
fi
|
||||
|
||||
lan_wan_section="$(find_lan_wan_forwarding)"
|
||||
if [ -n "$lan_wan_section" ]; then
|
||||
printf 'lan_wan_forwarding_present=1\n' >>"$state_tmp"
|
||||
if lan_wan_enabled="$(uci get "$lan_wan_section.enabled" 2>/dev/null)"; then
|
||||
printf 'lan_wan_enabled_present=1\n' >>"$state_tmp"
|
||||
printf 'lan_wan_enabled_value=%s\n' "$lan_wan_enabled" >>"$state_tmp"
|
||||
else
|
||||
printf 'lan_wan_enabled_present=0\n' >>"$state_tmp"
|
||||
fi
|
||||
else
|
||||
printf 'lan_wan_forwarding_present=0\n' >>"$state_tmp"
|
||||
fi
|
||||
|
||||
mv "$state_tmp" "$EXIT_NODE_FIREWALL_STATE_FILE"
|
||||
}
|
||||
|
||||
restore_exit_node_firewall_state() {
|
||||
local default_forward_value default_forward_present lan_wan_enabled_present lan_wan_enabled_value
|
||||
local lan_wan_section current_value
|
||||
|
||||
[ -r "$EXIT_NODE_FIREWALL_STATE_FILE" ] || return 0
|
||||
|
||||
default_forward_value=
|
||||
default_forward_present=0
|
||||
lan_wan_enabled_present=0
|
||||
lan_wan_enabled_value=
|
||||
lan_wan_forwarding_present=0
|
||||
. "$EXIT_NODE_FIREWALL_STATE_FILE"
|
||||
|
||||
if [ "${default_forward_present:-0}" = "1" ]; then
|
||||
current_value="$(uci get firewall.@defaults[0].forward 2>/dev/null || true)"
|
||||
if [ "$current_value" != "${default_forward_value:-}" ]; then
|
||||
uci set "firewall.@defaults[0].forward=${default_forward_value}" || return 1
|
||||
fi
|
||||
elif uci get firewall.@defaults[0].forward >/dev/null 2>&1; then
|
||||
uci -q delete firewall.@defaults[0].forward || return 1
|
||||
fi
|
||||
|
||||
lan_wan_section="$(find_lan_wan_forwarding)"
|
||||
if [ -n "$lan_wan_section" ] && [ "${lan_wan_forwarding_present:-0}" = "1" ]; then
|
||||
if [ "${lan_wan_enabled_present:-0}" = "1" ]; then
|
||||
current_value="$(uci get "$lan_wan_section.enabled" 2>/dev/null || true)"
|
||||
if [ "$current_value" != "${lan_wan_enabled_value:-}" ]; then
|
||||
uci set "$lan_wan_section.enabled=${lan_wan_enabled_value}" || return 1
|
||||
fi
|
||||
elif uci get "$lan_wan_section.enabled" >/dev/null 2>&1; then
|
||||
uci -q delete "$lan_wan_section.enabled" || return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always run the apply path so a previous commit with a pending reload is
|
||||
# retried even when the restored values already match UCI.
|
||||
apply_firewall_changes || return 1
|
||||
rm -f "$EXIT_NODE_FIREWALL_STATE_FILE" || return 1
|
||||
}
|
||||
|
||||
cleanup_managed_firewall() {
|
||||
restore_exit_node_firewall_state || return 1
|
||||
|
||||
ALLOW_WAN_DIRECT=0
|
||||
configure_wan_direct_rule || return 1
|
||||
|
||||
ACCESS=
|
||||
DISABLE_SNAT_SUBNET_ROUTES=0
|
||||
configure_tailscale_access_rules || return 1
|
||||
|
||||
apply_firewall_changes
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--run)
|
||||
shift
|
||||
;;
|
||||
--cleanup-exit-node-firewall)
|
||||
restore_exit_node_firewall_state || {
|
||||
log_error "Failed to restore exit-node firewall state."
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
;;
|
||||
--cleanup-wan-direct-firewall)
|
||||
ALLOW_WAN_DIRECT=0
|
||||
configure_wan_direct_rule || {
|
||||
log_error "Failed to remove Tailscale WAN direct firewall rules."
|
||||
exit 1
|
||||
}
|
||||
apply_firewall_changes || {
|
||||
log_error "Failed to apply Tailscale WAN direct firewall cleanup."
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
;;
|
||||
--cleanup-managed-firewall)
|
||||
cleanup_managed_firewall || {
|
||||
log_error "Failed to remove Tailscale managed firewall state."
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unsupported tailscale_helper mode: ${1:-<none>}"
|
||||
exit 2
|
||||
;;
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64|amd64) TS_ARCH="amd64" ;;
|
||||
aarch64|arm64) TS_ARCH="arm64" ;;
|
||||
armv7l) TS_ARCH="arm" ;;
|
||||
*) echo '{"error":"unsupported architecture: '$ARCH'"}'; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Use an auth key only for first enrollment or an explicit replacement request.
|
||||
# Legacy stored keys are ignored when an existing tailscaled identity is present.
|
||||
authkey=
|
||||
if [ "$TAILSCALE_STATE_WAS_PRESENT" != "1" ] || \
|
||||
"$TAILSCALE_SECRETS_BIN" authkey-pending >/dev/null 2>&1; then
|
||||
authkey="$("$TAILSCALE_SECRETS_BIN" get authkey 2>/dev/null || true)"
|
||||
fi
|
||||
if [ -n "$authkey" ]; then
|
||||
"$TAILSCALE_BIN" up --reset --authkey="$authkey" "$@" || revert_exit "tailscale up failed."
|
||||
else
|
||||
"$TAILSCALE_BIN" up --reset "$@" || revert_exit "tailscale up failed."
|
||||
fi
|
||||
unset authkey
|
||||
get_current_version() {
|
||||
if [ -x /usr/sbin/tailscale ]; then
|
||||
/usr/sbin/tailscale version 2>/dev/null | head -1 | tr -d '\n'
|
||||
else
|
||||
echo "not installed"
|
||||
fi
|
||||
}
|
||||
|
||||
# Use flock to acquire an exclusive lock
|
||||
exec 9> "$LOCK_FILE"
|
||||
"$FLOCK_BIN" -xn 9 || { revert_exit "Failed to acquire lock on $LOCK_FILE"; }
|
||||
trap 'rm -f "$LOCK_FILE"; exit' INT TERM EXIT
|
||||
check_update() {
|
||||
CURRENT=$( get_current_version )
|
||||
LATEST=$( wget -qO- "https://api.github.com/repos/tailscale/tailscale/releases/latest" 2>/dev/null | \
|
||||
grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/' )
|
||||
if [ -z "$LATEST" ]; then
|
||||
echo "{\"current\":\"$CURRENT\",\"latest\":\"unknown\",\"error\":\"failed to fetch latest version\"}"
|
||||
else
|
||||
echo "{\"current\":\"$CURRENT\",\"latest\":\"$LATEST\"}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Wait for Tailscale IPv4 address
|
||||
count=0
|
||||
while [ -z "$("$IFCONFIG_BIN" | grep 'tailscale' | awk '{print $1}')" ] || [ -z "$("$TAILSCALE_BIN" ip -4)" ]; do
|
||||
sleep 2
|
||||
count=$((count + 1))
|
||||
[ "${count}" -ge 5 ] && revert_exit "Failed to get Tailscale IPv4 address after 5 attempts."
|
||||
done
|
||||
do_update() {
|
||||
LATEST=$( wget -qO- "https://api.github.com/repos/tailscale/tailscale/releases/latest" 2>/dev/null | \
|
||||
grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/' )
|
||||
if [ -z "$LATEST" ]; then
|
||||
echo '{"error":"failed to fetch latest version"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Registration keys are one-time inputs. Purge every stored credential version
|
||||
# only after the connection has a Tailscale address, so failed enrollment keeps
|
||||
# the key available for correction and retry.
|
||||
if "$TAILSCALE_SECRETS_BIN" has authkey >/dev/null 2>&1; then
|
||||
"$TAILSCALE_SECRETS_BIN" clear-authkey || revert_exit "Failed to clear the used Tailscale auth key."
|
||||
fi
|
||||
URL="https://pkgs.tailscale.com/stable/tailscale_${LATEST}_${TS_ARCH}.tgz"
|
||||
TMP_DIR="/tmp/tailscale_update"
|
||||
rm -rf "$TMP_DIR"
|
||||
mkdir -p "$TMP_DIR"
|
||||
|
||||
# Configure Tailscale MagicDNS
|
||||
if [ "$ACCEPT_DNS" = "1" ]; then
|
||||
MagicDNSSuffix=$("$TAILSCALE_BIN" status --json | awk -F'"' '/"MagicDNSSuffix"/ {last=$(NF-1)} END {print last}')
|
||||
if [ -n "$MagicDNSSuffix" ]; then
|
||||
target_address="/$MagicDNSSuffix/100.100.100.100"
|
||||
index=$(uci show dhcp | sed -n 's/^dhcp\.@dnsmasq\[\([0-9][0-9]*\)\]=dnsmasq$/\1/p')
|
||||
for i in $index; do
|
||||
if ! uci -q get "dhcp.@dnsmasq[$i].address" 2>/dev/null | tr ' ' '\n' | grep -qxF "$target_address"; then
|
||||
uci add_list "dhcp.@dnsmasq[$i].address=$target_address" || revert_exit "Failed to add DNS address."
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
wget -qO "$TMP_DIR/tailscale.tgz" "$URL" 2>/dev/null
|
||||
if [ ! -s "$TMP_DIR/tailscale.tgz" ]; then
|
||||
echo "{\"error\":\"download failed: $URL\"}"
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Remove legacy LuCI network wrappers for tailscale0. tailscaled owns the TUN
|
||||
# device, policy rules, and table 52 routes; netifd must not manage it.
|
||||
uci -q delete network.tailscale
|
||||
uci -q delete network.ts_lan
|
||||
for route in $(uci show network | grep 'network.ts_subnet[0-9]\+=route' | grep -o 'network.ts_subnet[0-9]\+'); do
|
||||
uci -q delete "$route"
|
||||
done
|
||||
tar -xzf "$TMP_DIR/tailscale.tgz" -C "$TMP_DIR" 2>/dev/null
|
||||
EXTRACT_DIR="$TMP_DIR/tailscale_${LATEST}_${TS_ARCH}"
|
||||
|
||||
# Configure exit node firewall rules
|
||||
if [ -n "$EXIT_NODE" ]; then
|
||||
save_exit_node_firewall_state || revert_exit "Failed to save exit-node firewall state."
|
||||
uci set firewall.@defaults[0].forward='REJECT' || revert_exit "Failed to set default forward policy to REJECT."
|
||||
lan_wan_section="$(find_lan_wan_forwarding)"
|
||||
[ -n "$lan_wan_section" ] && uci set "$lan_wan_section.enabled=0" || true
|
||||
else
|
||||
restore_exit_node_firewall_state || revert_exit "Failed to restore exit-node firewall state."
|
||||
fi
|
||||
if [ ! -f "$EXTRACT_DIR/tailscale" ] || [ ! -f "$EXTRACT_DIR/tailscaled" ]; then
|
||||
echo '{"error":"extracted files not found"}'
|
||||
rm -rf "$TMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
configure_wan_direct_rule || revert_exit "Failed to configure Tailscale WAN direct firewall rule."
|
||||
configure_tailscale_access_rules || revert_exit "Failed to configure Tailscale firewall zone and forwarding rules."
|
||||
/etc/init.d/tailscale stop 2>/dev/null
|
||||
sleep 1
|
||||
|
||||
# Commit configuration changes and reload service
|
||||
if [ -n "$(uci changes dhcp)" ]; then
|
||||
uci commit dhcp || revert_exit "Failed to commit Tailscale DNS configuration."
|
||||
"$DNSMASQ_INIT" reload || revert_exit "Failed to reload dnsmasq."
|
||||
fi
|
||||
if [ -n "$(uci changes network)" ]; then
|
||||
uci commit network || revert_exit "Failed to commit Tailscale network cleanup."
|
||||
fi
|
||||
apply_firewall_changes || revert_exit "Failed to commit or apply Tailscale firewall configuration."
|
||||
cp "$EXTRACT_DIR/tailscale" /usr/sbin/tailscale
|
||||
cp "$EXTRACT_DIR/tailscaled" /usr/sbin/tailscaled
|
||||
chmod +x /usr/sbin/tailscale /usr/sbin/tailscaled
|
||||
|
||||
# The policy-routing helper owns only its dedicated network UCI rule and the
|
||||
# matching runtime rule. Its failure must not disrupt an otherwise healthy node.
|
||||
if [ -x "$POLICY_ROUTING_HELPER" ] && ! "$POLICY_ROUTING_HELPER" sync; then
|
||||
log_error "Failed to synchronize Tailscale/mwan3 policy routing precedence."
|
||||
fi
|
||||
exit 0
|
||||
/etc/init.d/tailscale start 2>/dev/null
|
||||
|
||||
rm -rf "$TMP_DIR"
|
||||
|
||||
NEW_VER=$( get_current_version )
|
||||
echo "{\"success\":true,\"version\":\"$NEW_VER\"}"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
check_update) check_update ;;
|
||||
do_update) do_update ;;
|
||||
*) echo '{"error":"usage: tailscale_helper check_update|do_update"}' ;;
|
||||
esac
|
||||
Reference in new issue
Block a user