🔥 Sync 2026-10-24 23:19:23

This commit is contained in:
github-actions[bot] committed 2026-10-24 23:19:23 +08:00
1 parent 9886197604
commit 37e1475e87
59 files changed
+5167 -1483

No files matched your search

@@ -6,6 +6,15 @@ STOP=10
NAME=tailscale-settings
# State files live on tmpfs so they are rebuilt after every boot.
STATE_DIR=/var/run/tailscale-settings
DAEMON_SIG_FILE="$STATE_DIR/daemon"
ENABLED_FILE="$STATE_DIR/enabled"
DISABLE_REQUEST_FILE="$STATE_DIR/disable-requested"
DNS_MODE_FILE="$STATE_DIR/dns_mode"
TAILSCALE_INIT=/etc/init.d/tailscale
# Compare version strings: return 0 (true) if $1 >= $2
version_gte() {
awk -v a="${1%%-*}" -v b="${2%%-*}" 'BEGIN {
@@ -21,6 +30,31 @@ version_gte() {
return $?
}
# Path of the tailscale daemon state file, as configured for the daemon init.
tailscale_state_file() {
local state_file
config_get state_file settings state_file '/etc/tailscale/tailscaled.state'
echo "$state_file"
}
# Fingerprint of the UCI options that are only read by the daemon init script
# (they end up as procd env/command arguments, so changing them needs a restart).
daemon_signature() {
local fw_mode port state_file log_stdout log_stderr
config_get fw_mode settings fw_mode 'nftables'
config_get port settings port '41641'
config_get state_file settings state_file '/etc/tailscale/tailscaled.state'
config_get_bool log_stdout settings log_stdout 1
config_get_bool log_stderr settings log_stderr 1
echo "$fw_mode|$port|$state_file|$log_stdout|$log_stderr"
}
# Returns 0 when the tailscale daemon is managed and currently running.
tailscale_running() {
[ -x "$TAILSCALE_INIT" ] || return 1
"$TAILSCALE_INIT" running >/dev/null 2>&1
}
# Add/remove DNS forward entry in dnsmasq for MagicDNS via OpenWrt
setup_tailscale_dns_forward() {
if [ ! -f /etc/config/dhcp ] || [ ! -x /etc/init.d/dnsmasq ]; then
@@ -49,27 +83,52 @@ remove_tailscale_dns_forward() {
fi
local entry="/.ts.net/100.100.100.100"
local idx=0
local removed=0
while true; do
local val
val=$(uci -q get dhcp.@dnsmasq[0].server."${idx}" 2>/dev/null)
[ -z "$val" ] && break
if [ "$val" = "$entry" ]; then
uci -q del_list dhcp.@dnsmasq[0].server="$entry"
removed=1
break
fi
idx=$((idx + 1))
local found=0 s
for s in $(uci -q get dhcp.@dnsmasq[0].server); do
[ "$s" = "$entry" ] && found=1
done
if [ "$removed" = "1" ]; then
if [ "$found" = "1" ]; then
uci -q del_list dhcp.@dnsmasq[0].server="$entry"
uci commit dhcp
/etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null
logger -t "$NAME" "Removed Tailscale DNS forward entry: $entry"
fi
}
# Keep the dnsmasq forward record in sync with dns_mode.
#
# Only magicdns takes resolution away from dnsmasq; both openwrt_forward and
# disabled leave dnsmasq authoritative, so the forward entry stays in place
# unless the user actually switches to magicdns. This mirrors what the old
# code did for openwrt_forward but stops deleting the entry when the mode goes
# back to disabled, which used to silently break *.ts.net resolution.
apply_dns_forward() {
local dns_mode prev=''
config_get dns_mode settings dns_mode 'disabled'
[ -f "$DNS_MODE_FILE" ] && prev=$(cat "$DNS_MODE_FILE")
if [ "$dns_mode" = "$prev" ]; then
return 0
fi
case "$dns_mode" in
magicdns)
remove_tailscale_dns_forward
;;
openwrt_forward)
setup_tailscale_dns_forward
;;
*)
# disabled: leave whatever the previous mode put in place,
# dnsmasq is authoritative either way
;;
esac
echo "$dns_mode" > "$DNS_MODE_FILE"
}
apply_settings() {
local ts_bin
if [ -x /usr/sbin/tailscale ]; then
@@ -143,16 +202,10 @@ apply_settings() {
case "$dns_mode" in
magicdns)
set -- "$@" --accept-dns=true
remove_tailscale_dns_forward
;;
openwrt_forward)
set -- "$@" --accept-dns=false
setup_tailscale_dns_forward
;;
*)
# disabled (default)
# openwrt_forward and disabled both let dnsmasq own resolution
set -- "$@" --accept-dns=false
remove_tailscale_dns_forward
;;
esac
@@ -186,7 +239,7 @@ apply_settings() {
# --relay-server-port (requires tailscale >= 1.90.5)
local ts_ver
ts_ver=$("$ts_bin" version 2>/dev/null | head -n1)
if version_gte "$ts_ver" "1.90.5"; then
if [ -n "$ts_ver" ] && version_gte "$ts_ver" "1.90.5"; then
if [ "$enable_relay" = "1" ]; then
set -- "$@" "--relay-server-port=${relay_server_port:-40000}"
else
@@ -208,26 +261,146 @@ apply_settings() {
return $ret
}
handle_service_state() {
config_load tailscale
local service_enabled
# Enable or disable autostart and the running daemon.
#
# The enable direction is applied here because starting a stopped daemon is
# harmless. The disable direction is only *armed* from the RPC (before the
# commit), because stopping is not reversible and the UCI rollback timer can
# still fire afterwards: rpcd commits first and arms the timer last, and a
# rollback only restores files. Keeping the actual stop in the reload handler
# means a rolled back config lands on service_enabled=1 while the daemon is
# still up, and the next apply simply drops the request.
sync_service_enabled() {
local service_enabled=1 prev=''
local requested=0
config_get service_enabled settings service_enabled '1'
if [ "$service_enabled" = "0" ]; then
logger -t "$NAME" "Service is disabled, stopping tailscale"
/etc/init.d/tailscale stop 2>/dev/null
/etc/init.d/tailscale disable 2>/dev/null
killall tailscaled 2>/dev/null
else
logger -t "$NAME" "Service is enabled, ensuring tailscale is running"
/etc/init.d/tailscale enable 2>/dev/null
/etc/init.d/tailscale start 2>/dev/null
apply_settings
prev=$(cat "$ENABLED_FILE" 2>/dev/null)
case "$prev" in
0|1) ;;
*)
# No usable record of the last applied value (first apply after
# an upgrade, or a truncated file). Adopt the configuration as
# the baseline instead of treating it as a transition: starting
# a daemon here would run start_service, which arms another UCI
# rollback window right after the one this apply is inside.
echo "$service_enabled" > "$ENABLED_FILE"
prev="$service_enabled"
;;
esac
if [ -f "$DISABLE_REQUEST_FILE" ]; then
requested=1
fi
if [ "$service_enabled" = "0" ]; then
if [ "$prev" = "0" ]; then
return 0
fi
# Only stop when the UI asked for it, a hand edited config is
# respected for autostart but never kills a running tunnel.
if [ "$requested" -eq 0 ]; then
logger -t "$NAME" "service_enabled=0 for the first time, disabling autostart only"
"$TAILSCALE_INIT" disable 2>/dev/null
echo "0" > "$ENABLED_FILE"
return 0
fi
logger -t "$NAME" "Service disabled, stopping tailscale"
"$TAILSCALE_INIT" stop 2>/dev/null
"$TAILSCALE_INIT" disable 2>/dev/null
rm -f "$DISABLE_REQUEST_FILE"
echo "0" > "$ENABLED_FILE"
return 0
fi
rm -f "$DISABLE_REQUEST_FILE"
# The service should run. A daemon that is already up must not be touched:
# that is the whole point of this handler. A daemon that is down is only
# brought back when the user explicitly turned the service on in this
# apply, not because some unrelated option happened to be saved, and not
# because it crashed or was stopped by hand.
if tailscale_running; then
[ "$prev" = "1" ] || echo "1" > "$ENABLED_FILE"
return 0
fi
if [ "$prev" = "1" ]; then
return 0
fi
logger -t "$NAME" "Service enabled, starting tailscale"
"$TAILSCALE_INIT" enable 2>/dev/null
tailscale_running || "$TAILSCALE_INIT" start 2>/dev/null
echo "1" > "$ENABLED_FILE"
}
# Wait briefly for procd to bring the daemon back after a restart. Called from
# reload_service, which rc.common runs outside of any function, so the loop
# counter has to live in a function of its own to stay out of the global scope.
wait_for_daemon() {
local waited=0
while [ "$waited" -lt 2 ]; do
tailscale_running && return 0
sleep 1
waited=$((waited + 1))
done
return 1
}
handle_service_state() {
if [ ! -x "$TAILSCALE_INIT" ]; then
logger -t "$NAME" "tailscale init script not found, keeping settings only"
apply_settings
apply_dns_forward
return 0
fi
sync_service_enabled
# Nothing may touch a running daemon here. The upstream init runs
# 'tailscaled --cleanup' before handing the instance to procd, which tears
# down tailscale0 addresses, table 52 routes and the netfilter rules while
# the old process is still alive. Running 'start' on a live service (or
# 'restart' without reason) therefore leaves a node that still answers
# 'tailscale status' and 'tailscale ping' but has no working kernel state.
if ! tailscale_running; then
return 0
fi
local desired
desired=$(daemon_signature)
if [ -f "$DAEMON_SIG_FILE" ] && [ "$(cat "$DAEMON_SIG_FILE")" != "$desired" ]; then
logger -t "$NAME" "Daemon options changed, restarting tailscale"
"$TAILSCALE_INIT" restart 2>/dev/null
wait_for_daemon || logger -t "$NAME" "tailscale did not come back within 2s"
fi
echo "$desired" > "$DAEMON_SIG_FILE"
apply_settings
apply_dns_forward
}
start_service() {
mkdir -p "$STATE_DIR"
config_load tailscale
daemon_signature > "$DAEMON_SIG_FILE"
# Record the desired flag without acting on it: at boot the service state
# is already correct and acting here would start or stop a daemon we do
# not own. Only seed the file when there is nothing to inherit, so a
# manual `disable` in the shell survives until the reload handler sees a
# real transition.
if [ ! -f "$ENABLED_FILE" ]; then
local service_enabled=1
config_get service_enabled settings service_enabled '1'
echo "$service_enabled" > "$ENABLED_FILE"
fi
apply_settings
apply_dns_forward
}
service_triggers() {
@@ -235,5 +408,6 @@ service_triggers() {
}
reload_service() {
mkdir -p "$STATE_DIR"
handle_service_state
}