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:
@@ -0,0 +1,10 @@
|
||||
package="kit-connect"
|
||||
version="0.1-0001"
|
||||
description="Unified management connectivity for Pioneer bus fleet routers. Single SPK — deploys Tailscale + reverse SSH tunnel. Hub auto-assigns ports on install. One package, all routers."
|
||||
maintainer="Keylink IT"
|
||||
arch="ipq806x"
|
||||
firmware="1.3.1-9346"
|
||||
checkport="no"
|
||||
startable="yes"
|
||||
displayname="KIT Bus Router Connect"
|
||||
thirdparty="yes"
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/bin/sh
|
||||
# kit-connect daemon — manages Tailscale + reverse SSH tunnel.
|
||||
# Config: /etc/kit-connect/connect.conf (fetched from hub on install)
|
||||
# One SPK, all routers. Hub assigns ports and keys.
|
||||
|
||||
PKG="kit-connect"
|
||||
PKG_DIR="/var/packages/${PKG}/target"
|
||||
CONF="/etc/${PKG}/connect.conf"
|
||||
KEY="$PKG_DIR/bin/connect_id_ed25519"
|
||||
DAEMON_PID_FILE="$PKG_DIR/var/daemon.pid"
|
||||
LOG_FILE="$PKG_DIR/var/connect.log"
|
||||
LOG_TAG="kit-connect"
|
||||
|
||||
log() {
|
||||
logger -t "$LOG_TAG" -p local0.warn "$*"
|
||||
printf '%s %s: %s\n' "$(date '+%b %d %H:%M:%S')" "$LOG_TAG" "$*" >> "$LOG_FILE"
|
||||
_lc=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
[ "$_lc" -gt 100 ] && tail -n 60 "$LOG_FILE" > "${LOG_FILE}.tmp" && mv "${LOG_FILE}.tmp" "$LOG_FILE"
|
||||
}
|
||||
|
||||
mkdir -p "$PKG_DIR/var"
|
||||
chmod 600 "$KEY" 2>/dev/null
|
||||
|
||||
# ── Load config ────────────────────────────────────────────────────
|
||||
load_config() {
|
||||
[ -f "$CONF" ] && . "$CONF" 2>/dev/null
|
||||
|
||||
: "${DEVICE_ID:=unknown}"
|
||||
: "${HUB_HOST:=162.243.83.36}"
|
||||
: "${HUB_PORT:=8080}"
|
||||
: "${TUNNEL_ENABLE:=1}"
|
||||
: "${TAILSCALE_ENABLE:=1}"
|
||||
|
||||
# Tunnel settings (hub-assigned or defaults)
|
||||
: "${TUNNEL_REMOTE_HOST:=162.243.83.36}"
|
||||
: "${TUNNEL_REMOTE_USER:=node}"
|
||||
: "${TUNNEL_REMOTE_SSH_PORT:=22}"
|
||||
: "${TUNNEL_REMOTE_PORT:=0}" # 0 = auto-assign by hub
|
||||
: "${TUNNEL_LOCAL_SSH_PORT:=2223}"
|
||||
: "${TUNNEL_RETRY_DELAY:=30}"
|
||||
|
||||
# Tailscale settings
|
||||
: "${TAILSCALE_STATEDIR:=/var/packages/Tailscale/var/state}"
|
||||
: "${TAILSCALE_SOCKET:=/var/packages/Tailscale/var/run/tailscaled.sock}"
|
||||
: "${TAILSCALE_AUTH_KEY:=}"
|
||||
: "${TAILSCALE_HOSTNAME:=${DEVICE_ID}}"
|
||||
|
||||
# Tailscale binary location (varies: SRM has /usr/local/bin, GL has /usr/bin)
|
||||
: "${TAILSCALE_BIN:=$(command -v tailscale 2>/dev/null || echo /usr/local/bin/tailscale)}"
|
||||
: "${TAILSCALED_BIN:=$(command -v tailscaled 2>/dev/null || echo /usr/local/bin/tailscaled)}"
|
||||
|
||||
# watchdog interval (seconds between forward health checks)
|
||||
: "${WATCHDOG_INTERVAL:=60}"
|
||||
: "${FORWARD_FAIL_LIMIT:=2}"
|
||||
}
|
||||
|
||||
log "EVENT=DAEMON_START device=${DEVICE_ID}"
|
||||
|
||||
# ── Start Tailscale ────────────────────────────────────────────────
|
||||
start_tailscale() {
|
||||
[ "$TAILSCALE_ENABLE" != "1" ] && { log "tailscale: disabled in config"; return 0; }
|
||||
[ -z "$TAILSCALE_AUTH_KEY" ] && { log "tailscale: no auth key — skipping"; return 0; }
|
||||
|
||||
if ! command -v "$TAILSCALED_BIN" >/dev/null 2>&1; then
|
||||
log "tailscale: binary not found at $TAILSCALED_BIN"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$TAILSCALE_STATEDIR")" "$(dirname "$TAILSCALE_SOCKET")"
|
||||
|
||||
# Already running?
|
||||
if "$TAILSCALE_BIN" --socket="$TAILSCALE_SOCKET" status >/dev/null 2>&1; then
|
||||
log "tailscale: already connected — $( "$TAILSCALE_BIN" --socket="$TAILSCALE_SOCKET" ip -4 2>/dev/null || echo no-ip)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Start tailscaled (userspace mode for SRM compat)
|
||||
log "tailscale: starting tailscaled..."
|
||||
/usr/bin/setsid "$TAILSCALED_BIN" \
|
||||
--statedir="$TAILSCALE_STATEDIR" \
|
||||
--tun=userspace-networking \
|
||||
--socket="$TAILSCALE_SOCKET" \
|
||||
>/dev/null 2>&1 &
|
||||
sleep 4
|
||||
|
||||
# Authenticate
|
||||
log "tailscale: authenticating with auth key..."
|
||||
"$TAILSCALE_BIN" --socket="$TAILSCALE_SOCKET" up \
|
||||
--auth-key "$TAILSCALE_AUTH_KEY" \
|
||||
--hostname "${TAILSCALE_HOSTNAME}" \
|
||||
--accept-routes=false \
|
||||
--accept-dns=false 2>&1 | while read line; do log "tailscale: $line"; done
|
||||
|
||||
local ts_ip=$("$TAILSCALE_BIN" --socket="$TAILSCALE_SOCKET" ip -4 2>/dev/null || echo "unknown")
|
||||
log "tailscale: connected — IP=${ts_ip}"
|
||||
}
|
||||
|
||||
# ── Start reverse SSH tunnel ───────────────────────────────────────
|
||||
start_tunnel() {
|
||||
[ "$TUNNEL_ENABLE" != "1" ] && { log "tunnel: disabled in config"; return 0; }
|
||||
[ "$TUNNEL_REMOTE_PORT" = "0" ] && { log "tunnel: no port assigned — run hub registration first"; return 0; }
|
||||
|
||||
log "tunnel: opening R:0.0.0.0:${TUNNEL_REMOTE_PORT} -> 127.0.0.1:${TUNNEL_LOCAL_SSH_PORT}"
|
||||
|
||||
ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o ServerAliveInterval=15 \
|
||||
-o ServerAliveCountMax=3 \
|
||||
-o ExitOnForwardFailure=yes \
|
||||
-o BatchMode=yes \
|
||||
-p "${TUNNEL_REMOTE_SSH_PORT}" \
|
||||
-N \
|
||||
-R "0.0.0.0:${TUNNEL_REMOTE_PORT}:127.0.0.1:${TUNNEL_LOCAL_SSH_PORT}" \
|
||||
-i "$KEY" \
|
||||
"${TUNNEL_REMOTE_USER}@${TUNNEL_REMOTE_HOST}" \
|
||||
2>"$PKG_DIR/var/ssh_err.tmp" &
|
||||
|
||||
SSH_PID=$!
|
||||
echo "$SSH_PID" > "$PKG_DIR/var/tunnel.pid"
|
||||
log "tunnel: SSH PID=${SSH_PID} port=${TUNNEL_REMOTE_PORT}"
|
||||
|
||||
# Watchdog loop
|
||||
_fail_count=0
|
||||
while kill -0 "$SSH_PID" 2>/dev/null; do
|
||||
sleep "$WATCHDOG_INTERVAL"
|
||||
if ! kill -0 "$SSH_PID" 2>/dev/null; then break; fi
|
||||
|
||||
# Probe forward health
|
||||
if timeout 10 ssh \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o ConnectTimeout=5 -o BatchMode=yes \
|
||||
-p "${TUNNEL_REMOTE_SSH_PORT}" -i "$KEY" \
|
||||
"${TUNNEL_REMOTE_USER}@${TUNNEL_REMOTE_HOST}" \
|
||||
"timeout 3 bash -c 'echo >/dev/tcp/127.0.0.1/${TUNNEL_REMOTE_PORT}' 2>/dev/null" \
|
||||
2>/dev/null; then
|
||||
[ "$_fail_count" -gt 0 ] && log "tunnel: forward recovered after ${_fail_count} checks"
|
||||
_fail_count=0
|
||||
else
|
||||
_fail_count=$((_fail_count + 1))
|
||||
log "tunnel: forward check failed (${_fail_count}/${FORWARD_FAIL_LIMIT})"
|
||||
[ "$_fail_count" -ge "$FORWARD_FAIL_LIMIT" ] && break
|
||||
fi
|
||||
done
|
||||
|
||||
[ -f "$PKG_DIR/var/tunnel.pid" ] && kill "$(cat "$PKG_DIR/var/tunnel.pid")" 2>/dev/null
|
||||
rm -f "$PKG_DIR/var/tunnel.pid"
|
||||
log "tunnel: exited — will retry in ${TUNNEL_RETRY_DELAY}s"
|
||||
sleep "$TUNNEL_RETRY_DELAY"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── Hub registration — get assigned port ───────────────────────────
|
||||
register_with_hub() {
|
||||
[ "$TUNNEL_REMOTE_PORT" != "0" ] && { log "hub: already registered (port=${TUNNEL_REMOTE_PORT})"; return 0; }
|
||||
|
||||
log "hub: registering device ${DEVICE_ID}..."
|
||||
|
||||
local resp=""
|
||||
resp=$(curl -s --connect-timeout 10 \
|
||||
"http://${HUB_HOST}:${HUB_PORT}/api/register/${DEVICE_ID}" 2>/dev/null) || true
|
||||
|
||||
if [ -z "$resp" ]; then
|
||||
log "hub: unreachable — using last-known config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Parse JSON response (minimal — avoids jq dependency)
|
||||
local port=""
|
||||
port=$(echo "$resp" | grep -o '"tunnel_port"[[:space:]]*:[[:space:]]*[0-9]*' | grep -o '[0-9]*')
|
||||
local tskey=""
|
||||
tskey=$(echo "$resp" | grep -o '"tailscale_auth_key"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -n "$port" ] && [ "$port" != "0" ]; then
|
||||
# Update config with assigned port
|
||||
sed -i "s/^TUNNEL_REMOTE_PORT=.*/TUNNEL_REMOTE_PORT=${port}/" "$CONF" 2>/dev/null
|
||||
log "hub: assigned port ${port}"
|
||||
fi
|
||||
|
||||
if [ -n "$tskey" ] && [ "$tskey" != "$TAILSCALE_AUTH_KEY" ]; then
|
||||
sed -i "s|^TAILSCALE_AUTH_KEY=.*|TAILSCALE_AUTH_KEY=${tskey}|" "$CONF" 2>/dev/null
|
||||
log "hub: updated Tailscale key"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Main ────────────────────────────────────────────────────────────
|
||||
load_config
|
||||
echo "$$" > "$DAEMON_PID_FILE"
|
||||
|
||||
while true; do
|
||||
load_config
|
||||
|
||||
# Register with hub if needed
|
||||
register_with_hub 2>/dev/null || true
|
||||
|
||||
# Start Tailscale in background (stays running)
|
||||
start_tailscale 2>/dev/null || true
|
||||
|
||||
# Start tunnel (blocking — restarts on failure)
|
||||
start_tunnel 2>/dev/null || true
|
||||
|
||||
log "daemon: both services running"
|
||||
wait # Wait for any child to die, then restart
|
||||
done
|
||||
@@ -0,0 +1,31 @@
|
||||
# kit-connect — Unified router connectivity config
|
||||
# One SPK, all routers. Hub assigns ports and keys.
|
||||
# Edit this file or re-run postinst to re-register with hub.
|
||||
|
||||
# ── Device Identity ─────────────────────────────────────────────────
|
||||
DEVICE_ID="REPLACE_WITH_DEVICE_ID"
|
||||
|
||||
# ── Hub Registration ────────────────────────────────────────────────
|
||||
HUB_HOST="162.243.83.36"
|
||||
HUB_PORT="8080"
|
||||
|
||||
# ── Reverse SSH Tunnel ──────────────────────────────────────────────
|
||||
# Port 0 = auto-assign via hub registration
|
||||
TUNNEL_ENABLE=1
|
||||
TUNNEL_REMOTE_HOST="162.243.83.36"
|
||||
TUNNEL_REMOTE_USER="node"
|
||||
TUNNEL_REMOTE_SSH_PORT="22"
|
||||
TUNNEL_REMOTE_PORT="0"
|
||||
TUNNEL_LOCAL_SSH_PORT="2223"
|
||||
TUNNEL_RETRY_DELAY="30"
|
||||
|
||||
# ── Tailscale ──────────────────────────────────────────────────────
|
||||
TAILSCALE_ENABLE=1
|
||||
TAILSCALE_STATEDIR="/var/packages/Tailscale/var/state"
|
||||
TAILSCALE_SOCKET="/var/packages/Tailscale/var/run/tailscaled.sock"
|
||||
TAILSCALE_AUTH_KEY="tskey-auth-kJz8wqNVo211CNTRL-GNL5EFjp5aWQcaWPSVn2aW9TNworKUNBV"
|
||||
TAILSCALE_HOSTNAME=""
|
||||
|
||||
# ── Behaviour ──────────────────────────────────────────────────────
|
||||
WATCHDOG_INTERVAL=60
|
||||
FORWARD_FAIL_LIMIT=2
|
||||
Reference in New Issue
Block a user