feat: kit-busrouter v2.0 — complete Synology + GL fleet router platform
Includes: - package/ GL-XE3000 kit-busrouter (opkg) - scripts/provision.sh (GL) and provision-synology.sh (Synology) - syno-balance/ — new WAN balancer replacing aiwanbal (SmartWAN adapter) - kit-connect/ — unified connectivity SPK (Tailscale + reverse SSH) - docs/deployment/synology-rt2600ac-checklist.md — 62-point checklist - docs/provisioning/device-identity.md — fleet identity spec - docs/pilot/checklist.md — field pilot validation - x4078_20260721.dss — reference config backup Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/bin/sh
|
||||
# smartwan-adapter.sh — Read SmartWAN state and write dw_weight_ratio.
|
||||
# NEVER calls loadbalance lb-enable. NEVER calls loadbalance at all.
|
||||
# Rides entirely on top of SmartWAN — does not own routing.
|
||||
#
|
||||
# Public API:
|
||||
# sw_read_state read SmartWAN mode, interfaces, current weights
|
||||
# sw_read_wan_state <iface> read per-WAN state: operstate, gateway, weight
|
||||
# sw_write_weights <w1> <w2> write dw_weight_ratio (50=balanced, 20/80=biased)
|
||||
# sw_signal_reload touch SmartWAN to re-read config (light touch, no conntrack flush)
|
||||
#
|
||||
# Config file: /usr/syno/etc/smartwan/smartwan.conf
|
||||
# Weight field: dw_weight_ratio = <0-100> (WAN1 share %, WAN2 = 100-WAN1)
|
||||
# - dw_weight_ratio appears twice in the conf (once for each ifname section)
|
||||
# - We update BOTH occurrences
|
||||
# - 50/50 = balanced, 70 = 70% WAN1 / 30% WAN2
|
||||
|
||||
SMARTWAN_CONF="/usr/syno/etc/smartwan/smartwan.conf"
|
||||
STATE_DIR="${STATE_DIR:-/tmp/syno-balance}"
|
||||
LOG_TAG="${LOG_TAG:-syno-balance}"
|
||||
|
||||
log() { logger -t "$LOG_TAG" -p local0.warn "$*"; }
|
||||
warn() { logger -t "$LOG_TAG" -p local0.warn "WARNING: $*"; }
|
||||
|
||||
# ── Read SmartWAN global state ──────────────────────────────────────
|
||||
sw_read_state() {
|
||||
[ -f "$SMARTWAN_CONF" ] || { warn "smartwan.conf not found"; return 1; }
|
||||
. "$SMARTWAN_CONF" 2>/dev/null
|
||||
|
||||
echo "MODE=${smartwan_mode:-unknown}"
|
||||
echo "WAN1_IFNAME=${smartwan_ifname_1:-wan}"
|
||||
echo "WAN2_IFNAME=${smartwan_ifname_2:-lan1}"
|
||||
echo "WAN1_WEIGHT=${dw_weight_ratio:-50}"
|
||||
}
|
||||
|
||||
# ── Read a single WAN's runtime state ───────────────────────────────
|
||||
# Returns: STATE=up|down GW=x.x.x.x WEIGHT=N
|
||||
sw_read_wan_state() {
|
||||
local iface="$1"
|
||||
[ -n "$iface" ] || return 1
|
||||
|
||||
# Physical link state
|
||||
local operstate="down"
|
||||
operstate=$(cat "/sys/class/net/${iface}/operstate" 2>/dev/null || echo "unknown")
|
||||
|
||||
# Gateway from routing table
|
||||
local gw=""
|
||||
gw=$(ip route show dev "$iface" 2>/dev/null | grep default | awk '{print $3}' | head -1)
|
||||
[ -z "$gw" ] && gw=$(ip route show dev "$iface" 2>/dev/null | awk '/via/ {print $3}' | head -1)
|
||||
|
||||
# Current weight from smartwan.conf
|
||||
local weight=0
|
||||
[ -f "$SMARTWAN_CONF" ] && . "$SMARTWAN_CONF" 2>/dev/null
|
||||
weight="${dw_weight_ratio:-50}"
|
||||
|
||||
echo "IFACE=${iface} STATE=${operstate} GW=${gw:-none} WEIGHT=${weight}"
|
||||
}
|
||||
|
||||
# ── Write dw_weight_ratio for both WANs ─────────────────────────────
|
||||
# $1: WAN1 weight (0-100, WAN2 = 100-WAN1)
|
||||
# Sets dw_weight_ratio in smartwan.conf for both ifname sections.
|
||||
# Does NOT call loadbalance. SmartWAN re-reads on its own cycle.
|
||||
sw_write_weights() {
|
||||
local w1="$1"
|
||||
case "$w1" in
|
||||
''|*[!0-9]*) warn "invalid weight: $w1"; return 1 ;;
|
||||
esac
|
||||
[ "$w1" -lt 0 ] && w1=0
|
||||
[ "$w1" -gt 100 ] && w1=100
|
||||
|
||||
local current=50
|
||||
[ -f "$SMARTWAN_CONF" ] && . "$SMARTWAN_CONF" 2>/dev/null
|
||||
current="${dw_weight_ratio:-50}"
|
||||
|
||||
# Only write if changed by >= 10 (hysteresis via lib-decide.sh)
|
||||
local diff=$(( w1 > current ? w1 - current : current - w1 ))
|
||||
if [ "$diff" -lt 10 ]; then
|
||||
return 0 # within hysteresis band — no-op
|
||||
fi
|
||||
|
||||
# Write to smartwan.conf using sed — safe, atomic-ish
|
||||
# SmartWAN has dw_weight_ratio on both ifname sections
|
||||
if [ -f "$SMARTWAN_CONF" ]; then
|
||||
sed -i "s/^dw_weight_ratio=.*/dw_weight_ratio=${w1}/" "$SMARTWAN_CONF" 2>/dev/null
|
||||
fi
|
||||
|
||||
log "smartwan: dw_weight_ratio ${current} -> ${w1} (WAN1=${w1}% WAN2=$((100-w1))%)"
|
||||
mkdir -p "$STATE_DIR"
|
||||
echo "$w1" > "$STATE_DIR/sw_weight_written"
|
||||
echo "$(date +%s)" > "$STATE_DIR/sw_weight_time"
|
||||
}
|
||||
|
||||
# ── Light touch — signal SmartWAN to re-read config ────────────────
|
||||
# Uses synoservice if available. NEVER calls loadbalance lb-enable.
|
||||
sw_signal_reload() {
|
||||
# SRM 1.3+ has synoservice to restart individual services
|
||||
if command -v synoservice >/dev/null 2>&1; then
|
||||
synoservice --restart smartwan 2>/dev/null && \
|
||||
{ log "smartwan: light-reload via synoservice"; return 0; }
|
||||
fi
|
||||
|
||||
# Fallback: just touch the config — SmartWAN polls it
|
||||
touch "$SMARTWAN_CONF" 2>/dev/null
|
||||
log "smartwan: config touched for polling re-read"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── Check if SmartWAN is in load-balance mode ───────────────────────
|
||||
sw_is_lb_mode() {
|
||||
[ -f "$SMARTWAN_CONF" ] || return 1
|
||||
local mode=""
|
||||
mode=$(grep '^smartwan_mode=' "$SMARTWAN_CONF" 2>/dev/null | cut -d= -f2)
|
||||
case "$mode" in
|
||||
loadbalance|loadbalancing_failover) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
Reference in New Issue
Block a user