Initial commit of mico-framework refactor. Original monolithic script still exists in /src
This commit is contained in:
@@ -1,24 +1,255 @@
|
||||
**Project: RVLink MV2400 Custom Control API (skyweave.lua)**
|
||||
# Skyweave API Framework
|
||||
|
||||
This repository contains `skyweave.lua`, a custom CGI application written in Lua for the OpenWrt-based RVLink MV2402 roof-mounted internet unit. The primary goal is to achieve direct, programmatic control over the MV2402's networking functionalities, independent of the original indoor controller unit (RV2458).
|
||||
## 1. Overview
|
||||
|
||||
**Key Features & Objectives:**
|
||||
* Bypass indoor unit orchestration by disabling proprietary services (`wtpd`, `heart_beat`).
|
||||
* Implement a Lua CGI API (`skyweave.lua`) for:
|
||||
* Wi-Fi client (STA) connection management (SSID, BSSID, encryption, key).
|
||||
* Wi-Fi scanning using `iwinfo`.
|
||||
* Retrieval of system logs (raw and parsed with SHA256 hashes).
|
||||
* Fetching detailed network and system status (replicating `guide_config`, live IP/DNS/gateway, etc.).
|
||||
* Utilize standard OpenWrt tools (`uci`, `logread`, `iwinfo`, `ubus`, shell commands) for backend operations.
|
||||
* Based on reverse engineering insights from the original `webmgnt` C firmware (analyzed with Ghidra).
|
||||
* Facilitates advanced WAN management, failover, and integration with downstream routers like MikroTik.
|
||||
This document describes the refactored Skyweave API framework, a lightweight Lua micro-framework designed for the RVLink MV2400 Roof Unit (COMFAST CF-E5 derivative) running a legacy OpenWrt version with Lua 5.1. It replaces the original monolithic `skyweave.lua` script with a more modular, extensible, and maintainable architecture.
|
||||
|
||||
**Prerequisites & Setup on MV2400:**
|
||||
* **`opkg` Configuration:** The default `opkg.conf` on the MV2402 likely needs to be updated to point to valid OpenWrt Chaos Calmer 15.05 package repositories (e.g., `http://archive.openwrt.org/chaos_calmer/15.05/ar71xx/generic/packages/`) to enable package installation.
|
||||
* **Essential Packages:** After fixing `opkg`, install the following:
|
||||
* `lua`: The Lua interpreter.
|
||||
* `uhttpd`: OpenWrt's web server, to run Lua CGI scripts.
|
||||
* `sha256sum`: For generating SHA256 hashes (part of `coreutils-sha256sum` or `coreutils` depending on the feed) if not already present.
|
||||
* **JSON Library:** This project uses a pure Lua JSON library (`rxi/json.lua`). The `json.lua` file needs to be manually placed in a Lua library path on the device, typically `/usr/lib/lua/`.
|
||||
The primary goal of this refactoring is to decouple request routing and handling from the business logic of individual API endpoints, making it easier to add, remove, or modify endpoints.
|
||||
|
||||
This project empowers users with enhanced control and automation capabilities for the RVLink system.
|
||||
## 2. Architecture
|
||||
|
||||
The framework is structured across several key directories and files:
|
||||
|
||||
* **/app/cgi_scripts/skyweave.lua** (source): This is the main CGI script. For deployment, it should be placed at **`/www/cgi-bin/skyweave.lua`** and be made executable. It is run by the `uhttpd` web server (with Lua CGI support enabled via `/etc/config/uhttpd`).
|
||||
* **Note for Deployment**: Ensure `uhttpd` is configured to handle `.lua` scripts as CGI, and the `lua` interpreter is available at `#!/usr/bin/lua`.
|
||||
* **/app/skyweave/** (source): This directory contains all core framework modules. For deployment, this entire directory should be copied to **`/usr/lib/lua/skyweave/`** on the target system.
|
||||
* `router.lua`: The routing engine. It maps incoming requests to the appropriate handler functions.
|
||||
* `request.lua`: Handles parsing of incoming HTTP requests (method, query, headers, body) into a `request_context` table.
|
||||
* `response.lua`: Provides helper functions for sending standardized JSON responses (success and error).
|
||||
* `utils.lua`: A collection of common utility functions (e.g., command execution, UCI interaction, string parsing).
|
||||
* **/app/skyweave/handlers/**: This directory houses the business logic for individual API endpoints. Each `.lua` file typically groups related handlers.
|
||||
* Examples: `network.lua`, `system.lua`, `wifi.lua`.
|
||||
* **/app/lib/**: Contains external Lua libraries.
|
||||
* `json.lua`: The rxi/json.lua library. For deployment, this file (from `/app/lib/json.lua` in the repository) should be copied to **`/usr/lib/lua/json.lua`** on the target system.
|
||||
|
||||
### Lua Module Loading & Execution
|
||||
|
||||
The main CGI script (`/www/cgi-bin/skyweave.lua` after deployment) relies on Lua's standard `package.path` mechanism to find modules. It uses `require` calls like `require("skyweave.router")` and `require("json")`. This requires:
|
||||
1. The `skyweave` framework modules to be located at `/usr/lib/lua/skyweave/`.
|
||||
2. The `json.lua` library to be located at `/usr/lib/lua/json.lua`.
|
||||
3. The OpenWrt Lua interpreter's `package.path` to include `/usr/lib/lua/?.lua` and `/usr/lib/lua/?/init.lua` (which is typical).
|
||||
|
||||
The script does **not** perform any local `package.path` manipulation.
|
||||
|
||||
## 3. Key Framework Components
|
||||
|
||||
### 3.1. Router (`skyweave/router.lua`)
|
||||
|
||||
The router is responsible for directing incoming requests to the correct handler function. It maintains a routing table where each route is defined by an HTTP method (e.g., "GET", "POST") and a conceptual path key (e.g., "/network_config").
|
||||
|
||||
A key feature for backward compatibility is its ability to translate query parameters from the original API style (e.g., `?get_config=status` or `?set_config=wifi`) into these internal method/path keys for routing.
|
||||
The router now also supports dispatching based on the URL path (via the CGI `PATH_INFO` variable). If a request like `/cgi-bin/skyweave.lua/network/config` is made, and `PATH_INFO` is correctly passed by the uhttpd server as `/network/config`, the router will use this path directly to find a matching route. This `PATH_INFO`-based routing is prioritized over the query parameter method.
|
||||
|
||||
### 3.2. Request Context (`skyweave/request.lua`)
|
||||
|
||||
Instead of direct CGI variable manipulation, all handler functions receive a single Lua table called `request_context`. This table contains pre-parsed information about the request:
|
||||
|
||||
* `request.method`: The HTTP request method (e.g., "GET", "POST").
|
||||
* `request.path`: The PATH_INFO component of the URL (if provided by `uhttpd` from the web server configuration).
|
||||
* `request.query`: A table of parsed query string parameters.
|
||||
* `request.headers`: A table of HTTP headers.
|
||||
* `request.raw_body`: The raw request body as a string.
|
||||
* `request.body`: If the `Content-Type` is `application/json` and a JSON library is available, this field contains the parsed JSON body as a Lua table. Otherwise, it's `nil`.
|
||||
* `request.body_parse_error`: Contains an error message if JSON parsing of the body failed.
|
||||
|
||||
### 3.3. Response Helpers (`skyweave/response.lua`)
|
||||
|
||||
To ensure consistent API output, two main helper functions are provided:
|
||||
|
||||
* `response.json(data_table, http_status_code)`: Sends a successful JSON response. `http_status_code` defaults to 200.
|
||||
* `response.error(error_message, http_status_code, internal_error_code)`: Sends a JSON error response. `http_status_code` defaults to 500, and `internal_error_code` defaults to -1.
|
||||
|
||||
Both functions automatically set the `Content-Type: application/json` header and the CGI `Status:` header (e.g., "Status: 200 OK").
|
||||
|
||||
## 4. API Endpoints
|
||||
|
||||
This section describes how to add new API endpoints and how they can be accessed.
|
||||
|
||||
### 4.1. Create/Update Handler Function
|
||||
|
||||
1. **Location**: Create a new Lua module or open an existing one in the `/app/skyweave/handlers/` directory (e.g., `/app/skyweave/handlers/myfeature.lua`).
|
||||
2. **Structure**:
|
||||
```lua
|
||||
-- /app/skyweave/handlers/myfeature.lua
|
||||
local myfeature_handler = {}
|
||||
|
||||
-- Handler function
|
||||
function myfeature_handler.get_custom_data(request_context)
|
||||
-- Access request data via request_context
|
||||
local item_id = request_context.query.id
|
||||
|
||||
if not item_id then
|
||||
return { errCode = -100, errMsg = "Item ID is required" }
|
||||
end
|
||||
|
||||
-- Your business logic here...
|
||||
local data = { id = item_id, value = "Some data for " .. item_id }
|
||||
|
||||
return data -- This will be sent as a JSON response
|
||||
end
|
||||
|
||||
return myfeature_handler
|
||||
```
|
||||
3. **Function Signature**: The handler function must accept a single argument, `request_context`.
|
||||
4. **Return Value**:
|
||||
* For a successful synchronous response, return a Lua table. This table will be automatically encoded as JSON.
|
||||
* To indicate an error handled within the function, return a table with `errCode` and `errMsg` fields.
|
||||
* To trigger a 204 No Content response, return `nil`.
|
||||
* For asynchronous tasks, see Section 5.
|
||||
|
||||
### 4.2. Register Route
|
||||
|
||||
1. **Edit Main Dispatcher**: Open `/app/cgi_scripts/skyweave.lua`.
|
||||
2. **Require Handler Module**: Add a `require` statement for your new handler module:
|
||||
```lua
|
||||
local myfeature_handlers = require("skyweave.handlers.myfeature")
|
||||
```
|
||||
3. **Add Route**: In the "Register Routes" section, call `router:add_route()`:
|
||||
```lua
|
||||
-- Example: Map GET ?get_config=custom_data to myfeature_handler.get_custom_data
|
||||
router:add_route("GET", "/custom_data", myfeature_handlers.get_custom_data)
|
||||
```
|
||||
The `path_key` (e.g., `"/custom_data"`) will be matched if the router derives this key either from a `PATH_INFO` like `/custom_data` in the URL (e.g. `/www/cgi-bin/skyweave.lua/custom_data`), or from a query parameter like `?get_config=custom_data` (as per the logic in `determine_route_key_from_request`).
|
||||
|
||||
## 5. Asynchronous Task Pattern
|
||||
|
||||
For operations that may take a long time (e.g., restarting services, applying complex configurations), handlers can initiate an asynchronous task. This prevents the HTTP request from timing out.
|
||||
|
||||
To use this pattern, a handler function should return a special Lua table:
|
||||
|
||||
```lua
|
||||
function network_handler.set_connect_wifi(request_context)
|
||||
-- ... (validation and command setup) ...
|
||||
local uci_commands = "uci set wireless.wwan.ssid='MyNetwork'; uci commit wireless; wifi"
|
||||
|
||||
return {
|
||||
async = true, -- Required: signals an asynchronous task
|
||||
command = uci_commands, -- Required: the shell command to execute
|
||||
response = { -- Required: the immediate JSON response
|
||||
errCode = 0,
|
||||
errMsg = "WiFi connection command accepted. Will apply in background."
|
||||
}
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
The framework will:
|
||||
1. Immediately send the content of the `response` field as a JSON response to the client (typically with an HTTP 202 Accepted status).
|
||||
2. Execute the `command` string in the system shell in the background. The execution is wrapped like this: `(sleep 2; <your_command_here>) >/dev/null 2>&1 &`. The `sleep 2` provides a brief buffer before execution.
|
||||
|
||||
## 6. Dependencies
|
||||
|
||||
* **Lua 5.1**
|
||||
* **JSON Library**: The framework modules now use `require("json")` to load the JSON library. It is assumed that `lua-cjson` is not available or not preferred. For deployment, the file `/app/lib/json.lua` (which is rxi/json.lua) must be copied to a standard Lua path on the target OpenWrt system and named `json.lua` (e.g., copy `/app/lib/json.lua` to `/usr/lib/lua/json.lua`).
|
||||
|
||||
## 7. Original Script Analysis Summary
|
||||
|
||||
The original `skyweave.lua` was a monolithic script handling all API logic. This refactoring introduced:
|
||||
* **Decoupled Routing Engine**: Clear separation of routing from handler logic.
|
||||
* **Request Abstraction**: Unified `request_context` object.
|
||||
* **Standardized Response Helpers**: Consistent JSON output.
|
||||
* **Formalized Asynchronous Task Pattern**: Standard way for handlers to perform background tasks.
|
||||
* **Modular Code Structure**: Improved organization and maintainability.
|
||||
|
||||
## 8. Example Refactored Handler: `set_connect_wifi`
|
||||
|
||||
The `set_connect_wifi` handler in `/app/skyweave/handlers/network.lua` demonstrates using the `request_context.body` for POST data and the asynchronous task return pattern:
|
||||
|
||||
```lua
|
||||
-- In /app/skyweave/handlers/network.lua
|
||||
local utils = require("skyweave.utils")
|
||||
local network_handler = {}
|
||||
|
||||
-- ... (other handlers like get_network_config) ...
|
||||
|
||||
function network_handler.set_connect_wifi(request_context)
|
||||
local body = request_context.body
|
||||
if not body then
|
||||
return { errCode = -29, errMsg = "Request body is missing or not valid JSON.", configDone = false }
|
||||
end
|
||||
|
||||
local ssid = body.ssid
|
||||
-- ... (retrieve other params: bssid, encryption_type, key, device from body) ...
|
||||
|
||||
-- ... (validation logic for ssid, encryption_type, key) ...
|
||||
-- Example: if not ssid or ssid == "" then return { errCode = -30, errMsg = "SSID is required." } end
|
||||
|
||||
local commands = {}
|
||||
local function add_cmd(cmd) table.insert(commands, cmd) end
|
||||
|
||||
-- ... (Build UCI commands as strings using add_cmd) ...
|
||||
-- Example: add_cmd("uci set wireless.wwan.ssid='" .. ssid:gsub("'", "'\\''") .. "'")
|
||||
add_cmd("uci set wireless.wwan.disabled=0")
|
||||
add_cmd("uci set wireless.wwan.mode=sta")
|
||||
-- Add all other necessary UCI commands for setting up Wi-Fi connection
|
||||
add_cmd("uci commit wireless")
|
||||
add_cmd("uci commit network")
|
||||
add_cmd("/etc/init.d/network restartall")
|
||||
|
||||
local command_string = table.concat(commands, "; ")
|
||||
|
||||
local response_msg = "OK (Configuration accepted. Device will attempt to connect to SSID: " .. ssid .. ")"
|
||||
-- ... (append BSSID to response_msg if present) ...
|
||||
|
||||
return {
|
||||
async = true,
|
||||
command = command_string,
|
||||
response = {
|
||||
errCode = 0,
|
||||
errMsg = response_msg,
|
||||
configDone = true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
return network_handler
|
||||
```
|
||||
This example illustrates how to access POST data from `request_context.body` and how to structure the return for an asynchronous command execution.
|
||||
|
||||
### 4.3. Accessing API Endpoints
|
||||
|
||||
API endpoints can be accessed in two ways:
|
||||
|
||||
**A. Path-Based URLs (Recommended for clarity):**
|
||||
This method uses the path part of the URL after the script name.
|
||||
- Example GET: `curl http://<device_ip>/cgi-bin/skyweave.lua/network_config`
|
||||
- Example POST: `curl -X POST -d '{\"ssid\":\"MyNet\"}' http://<device_ip>/cgi-bin/skyweave.lua/connect_wifi`
|
||||
This relies on your uhttpd server correctly setting the `PATH_INFO` environment variable (see Section 9: uhttpd Server Configuration).
|
||||
|
||||
**B. Query Parameter-Based URLs (Legacy/Alternative):**
|
||||
This method uses query parameters like `get_config` or `set_config`.
|
||||
- Example GET: `curl http://<device_ip>/cgi-bin/skyweave.lua?get_config=network_config`
|
||||
- Example POST: `curl -X POST -d '{\"ssid\":\"MyNet\"}' http://<device_ip>/cgi-bin/skyweave.lua?set_config=connect_wifi`
|
||||
|
||||
## 9. uhttpd Server Configuration for PATH_INFO
|
||||
|
||||
The path-based URL feature (e.g., `/cgi-bin/skyweave.lua/network/config`) depends on the `uhttpd`
|
||||
web server correctly interpreting the part of the URL after the script name as `PATH_INFO`
|
||||
and passing it as an environment variable to the `skyweave.lua` script.
|
||||
|
||||
Standard OpenWrt `uhttpd` configurations for CGI scripts typically support this by default.
|
||||
Ensure your `/etc/config/uhttpd` includes:
|
||||
```
|
||||
config uhttpd main
|
||||
# ... other main settings ...
|
||||
option cgi_prefix '/cgi-bin'
|
||||
|
||||
config uhttpd cgi
|
||||
list interpreter '.lua=/usr/bin/lua'
|
||||
# For OpenWrt 21.02+ or uhttpd versions with rfc_cgi_pathinfo support,
|
||||
# explicitly enabling it can ensure compliance, though often not needed:
|
||||
# option rfc_cgi_pathinfo '1'
|
||||
```
|
||||
Usually, no special configuration for `PATH_INFO` itself is required beyond enabling CGI for Lua.
|
||||
|
||||
**Verifying `PATH_INFO`:**
|
||||
If you suspect `PATH_INFO` is not being set, you can temporarily add the following
|
||||
at the beginning of your `/www/cgi-bin/skyweave.lua` script (after deployment):
|
||||
```lua
|
||||
print("Content-Type: text/plain")
|
||||
print("")
|
||||
print("DEBUG PATH_INFO: " .. (os.getenv("PATH_INFO") or "PATH_INFO is nil"))
|
||||
os.exit()
|
||||
```
|
||||
Then, access a URL like `http://<device_ip>/cgi-bin/skyweave.lua/test/path`. The output
|
||||
should show `/test/path`. Remember to remove this debugging code afterwards.
|
||||
Reference in New Issue
Block a user