Files
kit-busrouter/scripts/provision.sh
T
allen f15ac69925 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>
2026-07-22 00:07:14 +00:00

344 lines
16 KiB
Bash
Executable File

#!/bin/sh
# provision.sh — Bootstrap a GL-XE3000 Pioneer Bus Router
#
# Run once from router LAN (192.168.8.1) or console on a fresh/factory unit.
#
# Usage:
# provision.sh <DEVICE_ID> <WG_PRIVKEY> <WG_TUNNEL_IP>
#
# DEVICE_ID : bus identifier, e.g. B0042
# Becomes the WiFi SSID on both bands (plan spec "X0000" means
# SSID = the device ID, not a literal string "X0000").
# WG_PRIVKEY : base64 WireGuard private key for this unit (wg genkey)
# WG_TUNNEL_IP : assigned tunnel IP, e.g. 10.88.0.42 (no DHCP; hub admin assigns)
#
# Prerequisites:
# - Cellular SIM installed and rmnet_mhi0 online (for opkg)
# - kit-busrouter .ipk at /tmp/kit-busrouter.ipk (or set BUSROUTER_IPK env)
# - WG keypair generated; public key registered at hub before running this script
# - Edit HUB_PUBKEY constant below before deploying
#
# Idempotent: safe to re-run after partial failure.
set -e
DEVICE_ID="${1:?Usage: provision.sh <DEVICE_ID> <WG_PRIVKEY> <WG_TUNNEL_IP>}"
WG_PRIVKEY="${2:?Missing WG_PRIVKEY}"
WG_TUNNEL_IP="${3:?Missing WG_TUNNEL_IP}"
BUSROUTER_IPK="${BUSROUTER_IPK:-/tmp/kit-busrouter.ipk}"
# ── Fleet-wide constants (edit before deploying) ──────────────────────────────
HUB_PUBKEY="4QAauZ9gS/wZ/u0Wf66OcJFGo4erng3V5mwpYpARlmc="
HUB_HOST="167.172.237.162"
HUB_PORT="51820"
WG_ALLOWED="10.88.0.0/24"
WIFI_PASS="Pioneer123"
ADMIN_PASS="Pioneer321!"
TIMEZONE="UTC"
TIMEZONE_NAME="UTC"
# Guard: catch forgotten placeholder before any changes are made
case "$HUB_PUBKEY" in REPLACE_*)
echo "[provision] ERROR: edit HUB_PUBKEY in provision.sh before running" >&2
exit 1
esac
echo "[provision] === Pioneer Bus Router Provisioning ==="
echo "[provision] DEVICE_ID : $DEVICE_ID"
echo "[provision] WG_TUNNEL_IP: $WG_TUNNEL_IP"
# ── 1. Identity files ─────────────────────────────────────────────────────────
echo "[provision] 1/12 Device identity"
mkdir -p /etc/busrouter
printf '%s\n' "$DEVICE_ID" > /etc/busrouter/device-id
[ -f /etc/busrouter/version ] || printf '0.0\n' > /etc/busrouter/version
# ── 2. Hostname + timezone ────────────────────────────────────────────────────
echo "[provision] 2/12 Hostname + timezone"
uci set system.@system[0].hostname="$DEVICE_ID"
uci set system.@system[0].timezone="$TIMEZONE"
uci set system.@system[0].zonename="$TIMEZONE_NAME"
uci commit system
# ── 3. Admin accounts ─────────────────────────────────────────────────────────
# GL-XE3000 SSH/WebUI authenticates as root. pioadmin is a uid=0 alias so that
# "ssh pioadmin@..." works alongside "ssh root@..." with the same password.
echo "[provision] 3/12 Admin accounts (root + pioadmin uid=0 alias)"
printf '%s\n%s\n' "$ADMIN_PASS" "$ADMIN_PASS" | passwd root 2>/dev/null || \
echo "root:${ADMIN_PASS}" | chpasswd 2>/dev/null || \
echo "[provision] WARNING: failed to set root password"
if ! grep -q '^pioadmin:' /etc/passwd 2>/dev/null; then
echo "pioadmin:x:0:0:Fleet Admin:/root:/bin/ash" >> /etc/passwd
fi
# BusyBox shadow needs 9 colon-separated fields
if ! grep -q '^pioadmin:' /etc/shadow 2>/dev/null; then
echo "pioadmin:!:0:0:99999:7:::" >> /etc/shadow
fi
printf '%s\n%s\n' "$ADMIN_PASS" "$ADMIN_PASS" | passwd pioadmin 2>/dev/null || \
echo "pioadmin:${ADMIN_PASS}" | chpasswd 2>/dev/null || \
echo "[provision] WARNING: failed to set pioadmin password"
# ── 4. WiFi SSID (2.4 GHz + 5 GHz) ──────────────────────────────────────────
echo "[provision] 4/12 SSID=${DEVICE_ID} / Pioneer123"
ifaces=$(uci show wireless 2>/dev/null | grep '=wifi-iface' | cut -d. -f2 | cut -d= -f1)
if [ -z "$ifaces" ]; then
echo "[provision] WARNING: no wifi-iface sections found — check 'uci show wireless'"
else
for iface in $ifaces; do
uci set "wireless.${iface}.ssid=${DEVICE_ID}"
uci set "wireless.${iface}.key=${WIFI_PASS}"
uci set "wireless.${iface}.encryption=psk2"
done
uci commit wireless
wifi reload 2>/dev/null || true
fi
# ── 5. Stand down GL's kmwan ─────────────────────────────────────────────────
echo "[provision] 5/13 Disabling GL kmwan"
/etc/init.d/kmwan disable 2>/dev/null || true
/etc/init.d/kmwan stop 2>/dev/null || true
# ── 5b. Disable GL route_policy VPN kill switch ───────────────────────────────
# GL firmware treats the WireGuard client as a "Primary Tunnel" and enables a
# kill switch that marks ALL LAN traffic for WG routing. Since our WG only
# carries management traffic (AllowedIPs=10.88.0.0/24), this kills internet
# for LAN clients. Disable the rule so GL does not interfere with mwan3.
echo "[provision] 5b/13 Disabling GL route_policy kill switch"
# The anonymous @rule[0] is the "Primary Tunnel" rule (wgclient1, killswitch=1)
uci set route_policy.@rule[0].killswitch='0' 2>/dev/null || true
uci set route_policy.@rule[0].enabled='0' 2>/dev/null || true
uci commit route_policy 2>/dev/null || true
# ── 6. Install packages ───────────────────────────────────────────────────────
echo "[provision] 6/12 opkg update + mwan3"
opkg update
opkg install mwan3
if [ -f "$BUSROUTER_IPK" ]; then
echo "[provision] Installing kit-busrouter"
opkg install --force-reinstall "$BUSROUTER_IPK"
else
echo "[provision] WARNING: $BUSROUTER_IPK not found — install kit-busrouter manually"
fi
# ── 7. wwan interface: enable DHCP default route for repeater ─────────────────
# GL sets classlessroute=0 by default, which prevents the DHCP-obtained gateway
# from being installed in the main routing table. Without a default via apcli0,
# the repeater cannot forward internet traffic even when connected.
echo "[provision] 7/13 wwan classlessroute fix (enable repeater gateway)"
uci set network.wwan.classlessroute=1
uci commit network
# ── 8. mwan3 base config ──────────────────────────────────────────────────────
# Priority: wwan (repeater, metric 1) > modem_0001 (5G cellular, metric 2) > wan (Starlink, metric 3)
# last_resort='default' — if all mwan3 members are offline, fall back to whatever
# the main routing table has rather than returning unreachable to all clients.
echo "[provision] 8/13 mwan3 (priority: wwan(1) > modem_0001(2) > wan(3))"
uci batch << 'MWAN3_EOF'
delete mwan3
set mwan3.globals=globals
set mwan3.globals.mmx_mask='0x3F00'
set mwan3.globals.logging='0'
set mwan3.wwan=interface
set mwan3.wwan.enabled='1'
set mwan3.wwan.initial_state='online'
set mwan3.wwan.family='ipv4'
set mwan3.wwan.track_ip='1.1.1.1'
add_list mwan3.wwan.track_ip='8.8.8.8'
set mwan3.wwan.track_method='ping'
set mwan3.wwan.reliability='1'
set mwan3.wwan.count='1'
set mwan3.wwan.down='3'
set mwan3.wwan.up='3'
set mwan3.wwan.interval='5'
set mwan3.modem_0001=interface
set mwan3.modem_0001.enabled='1'
set mwan3.modem_0001.initial_state='online'
set mwan3.modem_0001.family='ipv4'
set mwan3.modem_0001.track_ip='1.1.1.1'
add_list mwan3.modem_0001.track_ip='8.8.8.8'
set mwan3.modem_0001.track_method='ping'
set mwan3.modem_0001.reliability='1'
set mwan3.modem_0001.count='1'
set mwan3.modem_0001.down='3'
set mwan3.modem_0001.up='3'
set mwan3.modem_0001.interval='5'
set mwan3.wan=interface
set mwan3.wan.enabled='1'
set mwan3.wan.initial_state='online'
set mwan3.wan.family='ipv4'
set mwan3.wan.track_ip='1.1.1.1'
add_list mwan3.wan.track_ip='8.8.8.8'
set mwan3.wan.track_method='ping'
set mwan3.wan.reliability='1'
set mwan3.wan.count='1'
set mwan3.wan.down='3'
set mwan3.wan.up='3'
set mwan3.wan.interval='5'
set mwan3.wwan_mbr=member
set mwan3.wwan_mbr.interface='wwan'
set mwan3.wwan_mbr.metric='1'
set mwan3.wwan_mbr.weight='100'
set mwan3.cell_mbr=member
set mwan3.cell_mbr.interface='modem_0001'
set mwan3.cell_mbr.metric='2'
set mwan3.cell_mbr.weight='100'
set mwan3.wan_mbr=member
set mwan3.wan_mbr.interface='wan'
set mwan3.wan_mbr.metric='3'
set mwan3.wan_mbr.weight='100'
set mwan3.balanced=policy
set mwan3.balanced.last_resort='default'
add_list mwan3.balanced.use_member='wwan_mbr'
add_list mwan3.balanced.use_member='cell_mbr'
add_list mwan3.balanced.use_member='wan_mbr'
set mwan3.default_rule=rule
set mwan3.default_rule.dest='0.0.0.0/0'
set mwan3.default_rule.proto='all'
set mwan3.default_rule.use_policy='balanced'
MWAN3_EOF
uci commit mwan3
/etc/init.d/mwan3 enable
/etc/init.d/mwan3 restart 2>/dev/null || true
# ── 8. WireGuard mgmt tunnel ──────────────────────────────────────────────────
# GL-XE3000 uses proto 'wgclient' (GL proprietary). The interface wgclient1 exists
# in the factory image and references a peer config section (typically peer_2001).
# Private key goes on the INTERFACE; public key + endpoint go on the PEER section.
echo "[provision] 9/13 WireGuard (wgclient1)"
# Ensure interface section exists with correct type
uci get network.wgclient1 >/dev/null 2>&1 || uci set network.wgclient1=wgclient
uci set network.wgclient1.ip4table='1001'
uci set network.wgclient1.mtu='1280'
# Private key belongs on the interface (GL wgclient proto reads it here)
uci set network.wgclient1.private_key="${WG_PRIVKEY}"
uci set network.wgclient1.ip_address="${WG_TUNNEL_IP}/32"
# Update the peer section (hub endpoint + pubkey)
peer_sec=""
if uci get network.peer_2001 >/dev/null 2>&1; then
peer_sec="peer_2001"
else
peer_sec=$(uci show network 2>/dev/null | grep '=wgclient_peer' | head -n1 \
| cut -d. -f2 | cut -d= -f1)
fi
if [ -n "$peer_sec" ]; then
uci set "network.${peer_sec}.public_key=${HUB_PUBKEY}"
uci set "network.${peer_sec}.endpoint_host=${HUB_HOST}"
uci set "network.${peer_sec}.endpoint_port=${HUB_PORT}"
uci set "network.${peer_sec}.allowed_ips=${WG_ALLOWED}"
uci set "network.${peer_sec}.persistent_keepalive=25"
else
echo "[provision] WARNING: no WG peer section found — configure hub peer manually"
fi
uci commit network
# ── 9b. Inbound mgmt SSH over the tunnel ──────────────────────────────────────
# Bring up the path the hub/lab uses to reach this unit. Without BOTH of these
# the tunnel comes up but SSH silently hits the wgclient1 zone DROP.
# See docs/mgmt-tunnel-ssh.md for the full explanation.
echo "[provision] 9b/13 Inbound mgmt SSH (firewall rule + hub jump key)"
# The WG client interface lives in its own firewall zone (defaults input=DROP).
# Find the zone bound to wgclient1; fall back to the interface name.
WG_ZONE=$(uci show firewall 2>/dev/null | awk -F"[.=]" '
/\.name=/ {z=$2; name=$0}
/\.network=/ && /wgclient1/ {print z; exit}')
[ -n "$WG_ZONE" ] || WG_ZONE="wgclient1"
if ! uci show firewall 2>/dev/null | grep -q "name='Allow-WG-mgmt-SSH'"; then
uci add firewall rule >/dev/null
uci set firewall.@rule[-1].name='Allow-WG-mgmt-SSH'
uci set firewall.@rule[-1].src="$WG_ZONE"
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].dest_port='22'
uci set firewall.@rule[-1].family='ipv4'
uci set firewall.@rule[-1].src_ip="$WG_ALLOWED"
uci set firewall.@rule[-1].target='ACCEPT'
uci commit firewall
/etc/init.d/firewall restart 2>/dev/null || true
fi
# Install the busfleet-hub jump key so the hub can SSH inbound over the tunnel.
mkdir -p /etc/dropbear
JUMP_KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMS842ZbXPXHbVd9IjyVGubq7M/yQA/cn/vHgB5R0/ZE busfleet-hub-jump'
if ! grep -qF 'busfleet-hub-jump' /etc/dropbear/authorized_keys 2>/dev/null; then
printf '%s\n' "$JUMP_KEY" >> /etc/dropbear/authorized_keys
fi
chmod 600 /etc/dropbear/authorized_keys
# ── 9. ip rule: WG mgmt tunnel survives mwan3 ─────────────────────────────────
# See device-identity.md §4 for root-cause explanation.
echo "[provision] 10/13 WG mgmt ip rule (priority 99 -> table 1001)"
ip rule del to "$WG_ALLOWED" priority 99 lookup 1001 2>/dev/null || true
ip rule add to "$WG_ALLOWED" priority 99 lookup 1001 2>/dev/null || true
# Persist via UCI (uci show uses key='value' format, so this grep is correct)
if ! uci show network 2>/dev/null | grep -q "\.dest='${WG_ALLOWED}'"; then
uci add network rule
uci set network.@rule[-1].lookup='1001'
uci set network.@rule[-1].dest="${WG_ALLOWED}"
uci set network.@rule[-1].priority='99'
uci commit network
fi
# ── 10. rp_filter=2 for wgclient1 ────────────────────────────────────────────
echo "[provision] 11/13 rp_filter + hotplug"
# GL-XE3000 may use /etc/sysctl.d/ drop-ins; write there for reliability
mkdir -p /etc/sysctl.d
printf 'net.ipv4.conf.wgclient1.rp_filter=2\n' > /etc/sysctl.d/10-busrouter.conf
sysctl -w net.ipv4.conf.wgclient1.rp_filter=2 2>/dev/null || true
# Also add to /etc/sysctl.conf if it exists and doesn't already have our setting
if [ -f /etc/sysctl.conf ]; then
grep -q 'wgclient1.rp_filter' /etc/sysctl.conf || \
printf 'net.ipv4.conf.wgclient1.rp_filter=2\n' >> /etc/sysctl.conf
fi
# Inline hotplug: re-assert rp_filter after any WAN state change
cat > /etc/hotplug.d/iface/99-busrouter-rpfilter << 'HOTPLUG_EOF'
#!/bin/sh
[ "$ACTION" = "ifup" ] || exit 0
sysctl -w net.ipv4.conf.wgclient1.rp_filter=2 2>/dev/null || true
HOTPLUG_EOF
chmod +x /etc/hotplug.d/iface/99-busrouter-rpfilter
# ── 11. GNSS enable on boot ───────────────────────────────────────────────────
echo "[provision] 12/13 GNSS boot enable"
if ! grep -q 'AT+QGPS=1' /etc/rc.local 2>/dev/null; then
printf '# Enable Quectel GNSS engine on boot (OFF by default)\n' >> /etc/rc.local
printf 'sleep 10; gl_modem -B 1-1.2 AT AT+QGPS=1 2>/dev/null\n' >> /etc/rc.local
fi
# ── 12b. Apply GL route_policy change (restart gl-tertf daemon) ──────────────
# gl-tertf reads /etc/config/route_policy and installs iptables tunnel marks.
# Restart it now so the kill switch disable takes effect before the reboot.
/etc/init.d/gl-tertf restart 2>/dev/null || true
# ── 13. Enable + start busrouter daemon ──────────────────────────────────────
echo "[provision] 13/13 busrouter daemon"
if [ -f /etc/init.d/busrouter ]; then
/etc/init.d/busrouter enable
/etc/init.d/busrouter start 2>/dev/null || true
else
echo "[provision] WARNING: busrouter init not found — install kit-busrouter first"
fi
# ── Done ──────────────────────────────────────────────────────────────────────
echo ""
echo "[provision] ============================================"
echo "[provision] DONE: $DEVICE_ID provisioned."
echo " WiFi SSID : $DEVICE_ID (2.4G + 5G) pw: Pioneer123"
echo " Admin SSH : ssh root@192.168.8.1 pw: Pioneer321!"
echo " ssh pioadmin@192.168.8.1 pw: Pioneer321!"
echo " WG tunnel : $WG_TUNNEL_IP"
echo " Verify : mwan3 status"
echo " cat /etc/busrouter/device-id"
echo " ps | grep daemon.sh"
echo "[provision] ============================================"
echo ""
echo "[provision] Rebooting in 5 s ..."
sleep 5
reboot