Bi-directional event forwarder: OpenWrt routers → Go server → Redpanda → LuIS backend.
| Layer | Tech |
|---|---|
| Router client | Lua (OpenWrt), client2server-unified.lua |
| Hotplug paths | Shell + curl, /etc/hotplug.d/{wireless,dhcp}/* |
| Transport | WebSocket :3843 (Caddy LB → 2× Go servers) |
| API | HTTP :3844 (Caddy → server1) |
| Event bus | Redpanda (Kafka-compatible) :9092 |
| Server | Go (nhooyr.io/websocket, redpanda-data/redpanda-sdk-go) |
client2server/
├── ARCHITECTURE.md # Mermaid diagrams, full spec
├── README.md # User-facing docs
├── MEMORY.md # ← you are here
├── Caddyfile # LB + reverse proxy
├── docker-compose.yml # redpanda + 2× server + caddy
├── package/
│ ├── Makefile # IPK build (includes hotplug)
│ ├── src/
│ │ └── client2server-unified.lua # CANONICAL Lua client
│ ├── files/
│ │ ├── etc/config/client2server # UCI defaults
│ │ └── etc/init.d/client2server # Procd init script (exports UCI → env)
│ └── hotplug/
│ ├── 01-wifi # wireless hotplug → wifi_connected/disconnected
│ └── 02-dhcp # dhcp hotplug → dhcp_lease_new/expire
└── server/
├── main.go # WS handler + Redpanda producer/consumer
├── go.mod
└── Dockerfile
Removed in 2026-06-09 cleanup: package/src/{minimal,ws}.lua, legacy etc/ and usr/ (predecessor event-forwarder), server/{index,server-ws}.js (Node fallback). All in git history if needed.
| Event | Source | Payload |
|---|---|---|
dhcp_lease_new |
dnsmasq (luv timer + hotplug) | mac, ip, hostname |
dhcp_lease_expire |
dnsmasq (luv timer + hotplug) | mac, old_ip |
wan_link_up |
/sys/class/net/* | device |
wan_link_down |
/sys/class/net/* | device |
wan_dhcp_new |
ubus | new_ip |
wan_dhcp_changed |
ubus | old_ip, new_ip |
wifi_connected |
hostapd hotplug | mac, interface |
wifi_disconnected |
hostapd hotplug | mac, interface |
✅ All event names are now consistent (hotplug scripts renamed to match unified.lua vocabulary on 2026-06-09).
| Command | Args |
|---|---|
uci_set |
config, section, option, value |
shell |
command |
reboot |
— |
wifi_restart |
— |
status |
— |
| Service | Port | Notes |
|---|---|---|
| WebSocket LB | 3843 | Routers connect here |
| HTTP API | 3844 | REST + health |
| Redpanda Kafka | 9092 | Internal |
| Redpanda REST | 8082 | Schema/management |
| Redpanda Schema | 8081 | Schema registry |
# Full stack
cd /root/.openclaw/workspace/client2server
TOKEN=*** docker-compose up -d
# Server only
cd server && go build -o server . && \
REDPANDA_BROKERS=localhost:9092 TOKEN=*** ./server
# Install on router (manual)
scp package/src/client2server-unified.lua root@router:/usr/sbin/
scp package/files/etc/init.d/client2server root@router:/etc/init.d/
scp package/files/etc/config/client2server root@router:/etc/config/
scp package/hotplug/01-wifi root@router:/etc/hotplug.d/wireless/
scp package/hotplug/02-dhcp root@router:/etc/hotplug.d/dhcp/
ssh root@router "chmod +x /usr/sbin/client2server-unified.lua /etc/init.d/client2server /etc/hotplug.d/wireless/01-wifi /etc/hotplug.d/dhcp/02-dhcp"
ssh root@router "/etc/init.d/client2server enable && /etc/init.d/client2server start"
# Or build IPK
make package/client2server-unified/ipk
The router has two parallel event paths to the server:
Hotplug path (instant) — kernel fires, shell runs, curl POSTs
01-wifi for WiFi connect/disconnect02-dhcp for DHCP lease add/delLua state-diff path (≤1s) — luv async loop polls state, sends diffs
wan_link_up/downwan_dhcp_new/changeddhcp_lease_new/expire (fallback / redundancy with hotplug)Both paths post to the same POST /api/events endpoint, so server-side sees one event stream.
init.d/client2server reads UCI on start() and writes /var/run/client2server.envSERVER_URL, TOKEN, ROUTER_ID to Lua's environmentuci get directly as fallback (in case called outside init.d context)stop()The Lua client went through ~6 architectural rewrites in 2 days (Jun 7 2026) trying to kill polling. Final settled state = hybrid hotplug + luv state-diff. See git log 9e248cd..0b6d8ff for the iteration history.
auto_httpswss://your-server.com/ws is a placeholder — must be edited per-deployment*** — override via env or .env fileunified.lua is 759 lines — worth splitting into modules (DHCP/WiFi/WAN/WS/CMD) but functional as-iscommand_id idempotency + per-router command queue are server-side features (per commits 4134845, 7c85b33) — verify they work end-to-endLuis Rosales — MIT License 2026