|
|
@@ -24,13 +24,15 @@ flowchart TB
|
|
|
subgraph Router["OpenWrt Router"]
|
|
|
direction TB
|
|
|
Lua[("client2server-unified.lua")]
|
|
|
- Hotplug[("/etc/hotplug.d/* scripts")]
|
|
|
+ Hotplug[("/etc/hotplug.d/{wireless,dhcp}/* scripts")]
|
|
|
Wifi[("wifi_connected / disconnected")]
|
|
|
DHCP[("dhcp_lease_new / expire")]
|
|
|
WAN[("wan_link / dhcp events")]
|
|
|
CMD[("Command Executor")]
|
|
|
- Buf[("Event buffer
|
|
|
-/tmp/event_buffer")]
|
|
|
+ Shared[("Shared buffer
|
|
|
+/var/run/client2server/buffer")]
|
|
|
+ Wake[("wake file
|
|
|
+/var/run/client2server/wake")]
|
|
|
end
|
|
|
|
|
|
subgraph Edge["Reverse Proxy / LB"]
|
|
|
@@ -63,9 +65,12 @@ Events / Commands / Alerts")]
|
|
|
Router -->|WebSocket :3843| Caddy
|
|
|
Caddy -->|lb| S1
|
|
|
Caddy -->|lb| S2
|
|
|
- Lua -->|offline| Buf
|
|
|
- Buf -->|reconnect| Lua
|
|
|
- Hotplug -->|HTTP POST /api/events| S1
|
|
|
+ Lua -->|offline| Shared
|
|
|
+ Hotplug -->|spool| Shared
|
|
|
+ Hotplug -->|touch| Wake
|
|
|
+ Wake -->|polled by| Lua
|
|
|
+ Shared -->|flush on reconnect| Lua
|
|
|
+ Lua -->|WS frame| Caddy
|
|
|
S1 -->|publish| REvents
|
|
|
S1 -->|publish| RCommands
|
|
|
S2 -->|publish| REvents
|
|
|
@@ -82,6 +87,12 @@ Events / Commands / Alerts")]
|
|
|
Router --> CMD
|
|
|
```
|
|
|
|
|
|
+> **Key change (v2.x):** Hotplug scripts no longer POST to the server
|
|
|
+> directly. Both hotplug events and Lua-generated events go through a
|
|
|
+> **single shared on-disk buffer** (`/var/run/client2server/buffer`) that
|
|
|
+> survives internet outages. See [Offline Buffering](#offline-buffering)
|
|
|
+> for the full flow.
|
|
|
+
|
|
|
**Read it as three planes:**
|
|
|
|
|
|
| Plane | What flows | Direction |
|
|
|
@@ -100,27 +111,33 @@ Events / Commands / Alerts")]
|
|
|
sequenceDiagram
|
|
|
participant R as Router
|
|
|
participant H as Hotplug
|
|
|
+ participant Buf as Shared buffer<br/>(/var/run/client2server/buffer)
|
|
|
+ participant L as Lua agent
|
|
|
participant S as Go Server
|
|
|
participant DB as SQLite
|
|
|
participant RP as Redpanda
|
|
|
participant D as Dashboard (SSE)
|
|
|
participant C as Consumer
|
|
|
|
|
|
- Note over R,C: Hybrid delivery: instant hotplug + ≤1s luv state-diff
|
|
|
+ Note over R,C: Hybrid delivery: instant hotplug + ≤1s luv state-diff<br/>Both paths share one on-disk buffer for offline resilience
|
|
|
|
|
|
par Hotplug path (instant)
|
|
|
- H->>S: POST /api/events (curl)
|
|
|
+ H->>Buf: append NDJSON event
|
|
|
+ H->>L: touch wake file
|
|
|
and Lua state-diff (≤1s)
|
|
|
- R->>S: WS frame: {type: event, ...}
|
|
|
+ L->>L: detect (WAN/DHCP/state change)
|
|
|
end
|
|
|
|
|
|
+ L->>L: main loop polls wake file
|
|
|
+ L->>Buf: read queued events
|
|
|
+ L->>S: WS frame: {type: event, ...}
|
|
|
+
|
|
|
S->>S: authenticate (legacy token or JWT)
|
|
|
S->>DB: INSERT INTO events
|
|
|
S->>RP: publish (fire-and-forget)
|
|
|
S->>D: SSE push to all subscribers
|
|
|
RP-->>C: consume (any Kafka client)
|
|
|
- S-->>R: ACK
|
|
|
- S-->>H: 200 OK
|
|
|
+ S-->>L: ACK
|
|
|
```
|
|
|
|
|
|
### 2. Command Flow (Dashboard → Router)
|
|
|
@@ -194,6 +211,64 @@ sequenceDiagram
|
|
|
Note over S,D: On reconnect: alert auto-cleared
|
|
|
```
|
|
|
|
|
|
+### 5. Offline Buffering (router → server, with outages)
|
|
|
+
|
|
|
+```mermaid
|
|
|
+sequenceDiagram
|
|
|
+ participant Ev as Event source
|
|
|
+ participant H as Hotplug script
|
|
|
+ participant L as Lua agent
|
|
|
+ participant Buf as Shared buffer<br/>NDJSON file
|
|
|
+ participant W as Wake file
|
|
|
+ participant S as Go server
|
|
|
+ participant D as SQLite
|
|
|
+
|
|
|
+ Note over Ev,D: Single buffer handles BOTH hotplug and Lua events.
|
|
|
+ Note over Ev,D: Survives: internet outage, server restart, agent restart.
|
|
|
+ Note over Ev,D: Does NOT survive: router reboot (tmpfs).
|
|
|
+
|
|
|
+ Ev->>H: WiFi/DHCP event
|
|
|
+ H->>Buf: append NDJSON line
|
|
|
+ H->>W: touch wake file
|
|
|
+ H->>L: send SIGHUP (best-effort)
|
|
|
+
|
|
|
+ L->>W: poll wake file (each loop)
|
|
|
+ L->>Buf: read queued events
|
|
|
+ loop per event (in order)
|
|
|
+ L->>S: WS frame {type: event, ...}
|
|
|
+ S->>D: INSERT events
|
|
|
+ S-->>L: ACK
|
|
|
+ end
|
|
|
+ L->>Buf: remove sent lines
|
|
|
+
|
|
|
+ alt Internet down
|
|
|
+ L->>L: WS send fails
|
|
|
+ L->>Buf: append own event
|
|
|
+ Note over L: exponential backoff
|
|
|
+ L->>L: 30s → 60s → 120s → ... → 5min
|
|
|
+ Note over L: 10s retry when buffer >80% full
|
|
|
+ end
|
|
|
+
|
|
|
+ alt Buffer overflow (>1000 events)
|
|
|
+ L->>L: drop oldest (FIFO), log warning
|
|
|
+ end
|
|
|
+```
|
|
|
+
|
|
|
+**File locations:**
|
|
|
+
|
|
|
+| File | Purpose | Owner |
|
|
|
+|---|---|---|
|
|
|
+| `/var/run/client2server/buffer` | NDJSON event queue | Both hotplug + Lua (shared) |
|
|
|
+| `/var/run/client2server/pid` | Lua agent's PID | Lua (written at startup) |
|
|
|
+| `/var/run/client2server/wake` | Hotplug → agent signal | Hotplug (touch), Lua (consume) |
|
|
|
+| `/var/run/client2server/env` | UCI exports for hotplug | init.d (write), hotplug (read) |
|
|
|
+| `/usr/share/client2server/hotplug-lib.sh` | Shared hotplug helpers | Makefile (install) |
|
|
|
+
|
|
|
+**Wake latency:** bounded by the Lua agent's main loop (1s sleep between
|
|
|
+iterations), so a hotplug event is typically picked up within ~1s. SIGHUP
|
|
|
+is sent as a fast path but is not relied on (Lua 5.1 has no portable
|
|
|
+signal API).
|
|
|
+
|
|
|
---
|
|
|
|
|
|
## Component Architecture
|
|
|
@@ -305,24 +380,32 @@ fetch + JWT header")]
|
|
|
flowchart LR
|
|
|
subgraph Router["OpenWrt"]
|
|
|
Init[("/etc/init.d/client2server")]
|
|
|
+ State[("State dir
|
|
|
+/var/run/client2server/
|
|
|
+{buffer,pid,env,wake}")]
|
|
|
Lua[("client2server-unified.lua")]
|
|
|
+ Lib[("hotplug-lib.sh
|
|
|
+/usr/share/client2server/")]
|
|
|
HP1[("hotplug.d/wireless/01-wifi")]
|
|
|
HP2[("hotplug.d/dhcp/02-dhcp")]
|
|
|
UCI[("/etc/config/client2server")]
|
|
|
UB[("UCI store")]
|
|
|
- Buf[("/tmp/event_buffer")]
|
|
|
WScli[("WebSocket client
|
|
|
-(mosquitto/lua-websockets)")]
|
|
|
+(luasocket)")]
|
|
|
CMDex[("Command executor")]
|
|
|
end
|
|
|
|
|
|
- Init --> Lua
|
|
|
+ Init -->|create state dir| State
|
|
|
+ Init -->|export UCI env| State
|
|
|
+ Init -->|launch| Lua
|
|
|
UCI --> Lua
|
|
|
- HP1 -->|curl POST /api/events| Server[("Go server :3843")]
|
|
|
- HP2 -->|curl POST /api/events| Server
|
|
|
- Lua -->|WS| Server
|
|
|
- Lua --> Buf
|
|
|
- Buf --> Lua
|
|
|
+ Lua -->|write pid| State
|
|
|
+ HP1 --> Lib
|
|
|
+ HP2 --> Lib
|
|
|
+ Lib -->|spool NDJSON| State
|
|
|
+ Lib -->|touch wake| State
|
|
|
+ Lua -->|poll wake file| State
|
|
|
+ Lua -->|WS| Server[("Go server :3843")]
|
|
|
Server -->|WS frame| WScli
|
|
|
WScli --> CMDex
|
|
|
CMDex --> UB
|
|
|
@@ -574,7 +657,7 @@ client2server/
|
|
|
├── package/ # OpenWrt IPK build
|
|
|
│ ├── src/client2server-unified.lua
|
|
|
│ ├── files/etc/{init.d,config}/client2server
|
|
|
-│ └── hotplug/{01-wifi,02-dhcp}
|
|
|
+│ └── hotplug/{01-wifi,02-dhcp,_lib.sh}
|
|
|
├── server/ # Go server (single binary, 6 files)
|
|
|
│ ├── main.go # HTTP/WS routes + ingest
|
|
|
│ ├── auth.go # JWT + scrypt
|