- Gateway ping latency is LAN RTT (<1ms), not WAN latency; set to null so
dashboards don't show misleading sub-ms values for Eyeride-NAT'd interfaces
- aiwanbal reports modem_type=generic for wired ISP ports; treat same as none
so carrier detection falls through to gateway IP pattern matching (AT&T etc.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
curl --interface fails for root on Synology due to policy routing. Pinging the
local gateway (e.g. Eyeride at 192.168.10.1) works reliably and confirms the
WAN path is up even when internet ICMP is NAT-blocked.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eyeride 5G device passes HTTP traffic but drops ICMP, causing false 100% loss.
When ping gives 100% loss, try curl --interface to confirm connectivity; if
HTTP probe succeeds, correct loss to 0% and derive latency from connect time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
stat output can include non-numeric chars on some RT2600ac firmware versions,
causing 0 to fail. Strip with tr -cd '0-9' before arithmetic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- hub/server.py: replace psycopg2 Postgres connection with urllib HTTP call to
ingest /api/devices endpoint (fleet Postgres is Docker-internal, not reachable
from the VPS directly)
- telemetry-synology.sh: run_speedtest() now writes per-interface cache files
(speedtest-result-<iface>); _cached_speedtest() reads cache if < 35 min old;
_collect_wan() uses cached result so throughput shows in telemetry without
blocking every 1-min cycle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-detect cellular vs fiber/Starlink regardless of physical port:
aiwanbal modem_type → Eyeride subnet (192.168.10.x) → gateway patterns
- Integrate aiwanbal/SmartAiBalancer state as primary data source when
available (fresh <120s); fall back to self-collected ping metrics
- Add carrier detection: AT&T for fiber gateways, Eyeride for cellular
- Add run_speedtest() with speedtest-cli/iperf3/curl download fallback
- Extract jitter (mdev) from ping output for richer metrics
- Consistent canonical GL format: modem_0001=cellular, wan=ethernet/fiber
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- sudo ping returns exit 1 on any packet loss, which triggered the
|| fallback to direct ping, causing duplicate output concatenation
- Now check exit code explicitly: only fall back on exit >= 2 (real error)
- Fixes loss_pct showing 100100 instead of 100 on failed WANs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ipgeo_gps: pipe curl output through tr -d to strip newlines
- _ping_iface: pipe printf output through tr -d for newline safety
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rewrite telemetry-synology.sh: canonical GL format (modem_0001/wan keys),
self-contained metrics (no aiwanbal), Eyeride GPS with IP geo fallback
- Add /api/fleet-telemetry to hub: joins tunnel status with fleet Postgres
- Update dashboard.html: per-device WAN health bars, GPS, signal strength
- Fix hub/ingest.sh normalisation: remap old wan1/wan2 → modem_0001/wan
- Bump SPK version: 0.1-0001 → 0.5.0-0001 for GL parity
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uses sed instead of grep -P for BusyBox ash compatibility.
Writes JSON to temp file to avoid shell escaping issues.
Posts to fleet hub ingest at 167.172.237.162:8080.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- docs/incident-log: mark x4078 recovered remotely via Tailscale (no
physical access needed); document the recovery steps and root cause
- connect-daemon.sh: remove bare `wait` that wedged the retry loop
forever on the setsid'd tailscaled child — once the tunnel failed the
daemon could not self-heal until the package was restarted; now the
main loop falls through start_tunnel (already blocking+retrying) and
restarts the whole cycle cleanly
- SPK rebuilt (32839680 bytes) with the daemon fix included
- Also documents the two latent lessons: (1) a field router is only
inaccessible when ALL three paths fail; (2) always check Tailscale +
QuickConnect before assuming a truck roll is required
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- wizard.sh + connect.conf: TUNNEL_LOCAL_SSH_PORT back to 2223 (Synology SSH port)
- port-registry: renamed x6340 → x4662 (matches QuickConnect ID)
- port-registry: added MR2200ac mesh satellite note for x4662
- deployment checklist: added kit-connect section, post-deployment rules,
incident log, and complete fleet port registry table
- memory: added fleet-router-inventory.md with full fleet state
- hub README: updated with current status and locations
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Includes fixes: which-vs-command-v for SRM busybox compat,
TUNNEL_LOCAL_SSH_PORT default 22, merged authorized_keys.
32MB SPK for Synology RT2600ac/RT6600ax (ipq806x).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
server.py:
- GET / serves dashboard.html (dark-themed fleet console)
- GET /api/fleet returns all registered devices with tunnel status,
SSH commands, and web dashboard access instructions
- Static file serving via serve_file()
dashboard.html:
- Shows all registered devices from port-registry.json
- Live tunnel status (port-open check on VPS)
- Platform badges (Synology vs GL-XE3000)
- Copy-to-clipboard SSH commands per device
- Web dashboard access via SSH port-forwarding instructions
- Auto-refreshes every 15s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SRM default PATH is /usr/bin:/bin:/usr/sbin:/sbin:/usr/syno/bin — /usr/local/bin
is NOT included. 'command -v /usr/local/bin/tailscaled' fails on busybox ash
even when the file exists and is executable. Replaced with '[ ! -x "$BIN" ]'
which is always reliable for absolute paths.
Found during x5925 deployment testing. Daemon logged 'binary not found' despite
the files being present at /usr/local/bin/tailscale[d].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GL connect-wizard.sh: fixed key authorization block indentation (was at 3 tabs instead of 2)
- GL kit-busrouter Makefile: added +kit-connect to DEPENDS so installing the balancer auto-pulls connectivity
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GL daemon: removed remaining 'local ts_ip' (busybox ash compat)
Synology wizard: auto-authorizes tunnel key via POST /api/authorize-key
GL wizard: same key authorization flow added
Synology: added connect-register.sh for parity with GL
syno-balance build.sh: removed deprecated checksum file (same lesson as kit-connect)
All four curl hubs (wizards + register scripts) now have --max-time 15.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SPK daemon fixes (x5925 testing feedback):
- register_with_hub: added --max-time 15 to prevent indefinite hang
- Removed bash 'local' keyword for busybox ash compatibility
- postinst: mkdir -p /usr/local/bin before copying Tailscale binaries
- postinst: chmod +x all bin/*.sh (fixes 644 execute bit bug)
- Added x5925-boot.sh for reboot persistence (stopgap until daemon fixed)
Hub security hardening:
- Added POST /api/authorize-key endpoint with device_id + pubkey
- Keys auto-authorized with restrict,port-forwarding,permitlisten="<port>"
- No shell access allowed — only tunnel forwarding to assigned port
- Server.py updated with input validation on key format
- register.sh --authorize-key subcommand for secure key management
GL daemon: same --max-time fix applied for curl timeout
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bundled tailscale 1.98.9 + tailscaled ARMv7 (ELF 32-bit, statically linked)
- Fixed connect-daemon.sh 644 → 755 (was preventing daemon from starting)
- Postinst now chmod +x all bin/*.sh and bundled binaries
- Postinst copies tailscale/tailscaled to /usr/local/bin if not present
- Hub firewall: opened ports 8080 (API) and 2230-2299 (SSH tunnels)
- Hub systemd service restarted after firewall fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds sections 2b (Safe Access) and 2c (Traffic Control):
- Disable WPS, login page title = DEVICE_ID
- Synology Account sign-in for QuickConnect relay
- Google Safe Browsing via API key, Threat Intelligence DB
- DoS protection enabled, NO DoH servers
- Traffic Control: per-client 80% cap, auto-speedtest on new WAN
- Update preflight memory with all security requirements
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/backup-config.sh: weekly router config backup to hub (SRM + OpenWrt)
Runs via cron, exports platform-specific config, POSTs to /api/backup
Keeps last 10 backups per device on hub
- hub/ingest.sh: fleet hub telemetry normalizer + backup receiver
Normalises GL (openwrt) and Synology telemetry into single schema
Adds platform field to GL telemetry for unified dashboard
- hub/dashboard.html: single-pane fleet console
Shows all routers (GL + Synology) with scores, WAN state, GPS, uptime
Auto-refreshes every 10s from /api/status endpoint
- 62-point checklist updated with weekly backup section 9b
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>