secubox-openwrt/docs/luci-development-reference.md
CyberMind-FR ef936f1295 docs: Add GitHub Pages documentation site structure
Created comprehensive documentation site using MkDocs Material theme
for GitHub Pages deployment. Moved version sync scripts to secubox-tools.

## Documentation Site (18 new files)

Created docs/ directory with complete documentation:

**Main Pages:**
- index.md - Home page with navigation cards and module overview
- quick-start.md - Quick start guide
- documentation-index.md - Documentation index

**Development Guides:**
- development-guidelines.md - Complete development reference (1857 lines)
- code-templates.md - Working examples and patterns (1405 lines)
- module-implementation-guide.md - Step-by-step workflow (901 lines)

**Reference Documentation:**
- claude.md - Build system and RPCD architecture (553 lines)
- validation-guide.md - Validation workflows (518 lines)
- permissions-guide.md - Permission guidelines (248 lines)
- luci-development-reference.md - LuCI development (1196 lines)

**Module Information:**
- module-status.md - 15 module status (896 lines)
- feature-regeneration-prompts.md - AI prompts (2084 lines)
- todo-analyse.md - Roadmap and tasks (1080 lines)

**Archive (4 files):**
- archive/index.md - Archive index
- archive/build-issues.md - Build troubleshooting
- archive/completion-report.md - Project milestones
- archive/module-enable-disable-design.md - Feature design

**Styling:**
- stylesheets/extra.css - SecuBox custom CSS

## Scripts Reorganization (2 files moved)

Moved version sync utilities to secubox-tools:
- scripts/sync_module_versions.py → secubox-tools/sync_module_versions.py
- scripts/sync_module_versions.sh → secubox-tools/sync_module_versions.sh

## Site Features

- Material theme with dark/light mode
- Responsive design with navigation tabs
- Live search and syntax highlighting
- Custom SecuBox branding (indigo/violet gradients)
- 12,780+ lines of comprehensive documentation

Summary:
- 21 files changed (+12,780 lines)
- 18 new documentation pages
- 2 scripts relocated
- Ready for GitHub Pages deployment

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-28 21:57:29 +01:00

1197 lines
30 KiB
Markdown

# LuCI Development Reference Guide
**Version:** 1.0.0
**Last Updated:** 2025-12-28
**Status:** Active
**Version:** 1.0.0
**Last Updated:** 2025-12-28
**Status:** Active
**Based on:** luci-app-secubox and luci-app-system-hub implementations
**Target Audience:** Claude.ai and developers working on OpenWrt LuCI applications
---
## See Also
- **Design & Standards:** [DEVELOPMENT-GUIDELINES.md](development-guidelines.md)
- **Quick Commands:** [QUICK-START.md](quick-start.md)
- **Automation Briefing:** [CODEX.md](codex.md)
- **Code Templates:** [CODE-TEMPLATES.md](code-templates.md)
This document captures critical patterns, best practices, and common pitfalls discovered during the development of SecuBox LuCI applications. Use this as a validation reference for all future LuCI application development.
---
## Table of Contents
1. [ubus and RPC Fundamentals](#ubus-and-rpc-fundamentals)
2. [RPCD Backend Patterns](#rpcd-backend-patterns)
3. [LuCI API Module Patterns](#luci-api-module-patterns)
4. [LuCI View Import Patterns](#luci-view-import-patterns)
5. [ACL Permission Structure](#acl-permission-structure)
6. [Data Structure Conventions](#data-structure-conventions)
7. [Common Errors and Solutions](#common-errors-and-solutions)
8. [Validation Checklist](#validation-checklist)
9. [Testing and Deployment](#testing-and-deployment)
---
## ubus and RPC Fundamentals
### What is ubus?
**ubus** (OpenWrt micro bus architecture) is OpenWrt's inter-process communication (IPC) system. It enables:
- RPC (Remote Procedure Call) between processes
- Web interface (LuCI) to backend service communication
- Command-line interaction via `ubus call`
### ubus Object Naming Convention
**CRITICAL RULE**: All LuCI application ubus objects MUST use the `luci.` prefix.
```javascript
// ✅ CORRECT
object: 'luci.system-hub'
object: 'luci.cdn-cache'
object: 'luci.wireguard-dashboard'
// ❌ WRONG
object: 'system-hub'
object: 'systemhub'
object: 'cdn-cache'
```
**Why?** LuCI expects objects under the `luci.*` namespace for web applications. Without this prefix:
- ACL permissions won't match
- RPCD won't route calls correctly
- Browser console shows: `RPC call to system-hub/status failed with error -32000: Object not found`
### RPCD Script Naming MUST Match ubus Object
The RPCD script filename MUST exactly match the ubus object name:
```bash
# If JavaScript declares:
# object: 'luci.system-hub'
# Then RPCD script MUST be named:
/usr/libexec/rpcd/luci.system-hub
# NOT:
/usr/libexec/rpcd/system-hub
/usr/libexec/rpcd/luci-system-hub
```
**Validation Command**:
```bash
# Check JavaScript files for ubus object names
grep -r "object:" luci-app-*/htdocs --include="*.js"
# Verify RPCD script exists with matching name
ls luci-app-*/root/usr/libexec/rpcd/
```
### ubus Call Types
**Read Operations** (GET-like):
- `status` - Get current state
- `get_*` - Retrieve data (e.g., `get_health`, `get_settings`)
- `list_*` - Enumerate items (e.g., `list_services`)
**Write Operations** (POST-like):
- `save_*` - Persist configuration (e.g., `save_settings`)
- `*_action` - Perform actions (e.g., `service_action`)
- `backup`, `restore`, `reboot` - System modifications
**ACL Mapping**:
- Read operations → `"read"` section in ACL
- Write operations → `"write"` section in ACL
---
## RPCD Backend Patterns
### Shell Script Structure
RPCD backends are executable shell scripts that:
1. Parse `$1` for the action (`list` or `call`)
2. Parse `$2` for the method name (if `call`)
3. Read JSON input from stdin (for methods with parameters)
4. Output JSON to stdout
5. Exit with status 0 on success, non-zero on error
### Standard Template
```bash
#!/bin/sh
# RPCD Backend: luci.system-hub
# Version: 0.1.0
# Source JSON shell helper
. /usr/share/libubox/jshn.sh
case "$1" in
list)
# List all available methods and their parameters
echo '{
"status": {},
"get_health": {},
"service_action": { "service": "string", "action": "string" },
"save_settings": {
"auto_refresh": 0,
"health_check": 0,
"refresh_interval": 0
}
}'
;;
call)
case "$2" in
status)
status
;;
get_health)
get_health
;;
service_action)
# Read JSON input from stdin
read -r input
json_load "$input"
json_get_var service service
json_get_var action action
service_action "$service" "$action"
;;
save_settings)
read -r input
json_load "$input"
json_get_var auto_refresh auto_refresh
json_get_var health_check health_check
json_get_var refresh_interval refresh_interval
save_settings "$auto_refresh" "$health_check" "$refresh_interval"
;;
*)
echo '{"error": "Method not found"}'
exit 1
;;
esac
;;
esac
```
### JSON Output with jshn.sh
**jshn.sh** provides shell functions for JSON manipulation:
```bash
# Initialize JSON object
json_init
# Add simple values
json_add_string "hostname" "openwrt"
json_add_int "uptime" 86400
json_add_boolean "running" 1
# Add nested object
json_add_object "cpu"
json_add_int "usage" 25
json_add_string "status" "ok"
json_close_object
# Add array
json_add_array "services"
json_add_string "" "network"
json_add_string "" "firewall"
json_close_array
# Output JSON to stdout
json_dump
```
**Common Functions**:
- `json_init` - Start new JSON object
- `json_add_string "key" "value"` - Add string
- `json_add_int "key" 123` - Add integer
- `json_add_boolean "key" 1` - Add boolean (0 or 1)
- `json_add_object "key"` - Start nested object
- `json_close_object` - End nested object
- `json_add_array "key"` - Start array
- `json_close_array` - End array
- `json_dump` - Output JSON to stdout
### Error Handling
Always validate inputs and return meaningful errors:
```bash
service_action() {
local service="$1"
local action="$2"
# Validate service name
if [ -z "$service" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Service name is required"
json_dump
return 1
fi
# Validate action
case "$action" in
start|stop|restart|enable|disable)
;;
*)
json_init
json_add_boolean "success" 0
json_add_string "error" "Invalid action: $action"
json_dump
return 1
;;
esac
# Perform action
/etc/init.d/"$service" "$action" >/dev/null 2>&1
if [ $? -eq 0 ]; then
json_init
json_add_boolean "success" 1
json_add_string "message" "Service $service $action successful"
json_dump
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Service $service $action failed"
json_dump
return 1
fi
}
```
### UCI Integration
For persistent configuration, use UCI (Unified Configuration Interface):
```bash
save_settings() {
local auto_refresh="$1"
local health_check="$2"
local refresh_interval="$3"
# Create/update UCI config
uci set system-hub.general=general
uci set system-hub.general.auto_refresh="$auto_refresh"
uci set system-hub.general.health_check="$health_check"
uci set system-hub.general.refresh_interval="$refresh_interval"
uci commit system-hub
json_init
json_add_boolean "success" 1
json_add_string "message" "Settings saved successfully"
json_dump
}
get_settings() {
# Load UCI config
if [ -f "/etc/config/system-hub" ]; then
. /lib/functions.sh
config_load system-hub
fi
json_init
json_add_object "general"
# Get value or use default
config_get auto_refresh general auto_refresh "1"
json_add_boolean "auto_refresh" "${auto_refresh:-1}"
config_get refresh_interval general refresh_interval "30"
json_add_int "refresh_interval" "${refresh_interval:-30}"
json_close_object
json_dump
}
```
### Performance Tips
1. **Cache expensive operations**: Don't re-read `/proc` files multiple times
2. **Use command substitution efficiently**:
```bash
# Good
uptime=$(cat /proc/uptime | cut -d' ' -f1)
# Better
read uptime _ < /proc/uptime
uptime=${uptime%.*}
```
3. **Avoid external commands when possible**:
```bash
# Slow
count=$(ls /etc/init.d | wc -l)
# Fast
count=0
for file in /etc/init.d/*; do
[ -f "$file" ] && count=$((count + 1))
done
```
---
## LuCI API Module Patterns
### CRITICAL: Use baseclass.extend()
**RULE**: LuCI API modules MUST use `baseclass.extend()` pattern.
```javascript
'use strict';
'require baseclass';
'require rpc';
// Declare RPC methods
var callStatus = rpc.declare({
object: 'luci.system-hub',
method: 'status',
expect: {}
});
var callGetHealth = rpc.declare({
object: 'luci.system-hub',
method: 'get_health',
expect: {}
});
var callSaveSettings = rpc.declare({
object: 'luci.system-hub',
method: 'save_settings',
params: ['auto_refresh', 'health_check', 'refresh_interval'],
expect: {}
});
// ✅ CORRECT: Use baseclass.extend()
return baseclass.extend({
getStatus: callStatus,
getHealth: callGetHealth,
saveSettings: callSaveSettings
});
// ❌ WRONG: Do NOT use these patterns
return baseclass.singleton({...}); // Breaks everything!
return {...}; // Plain object doesn't work
```
**Why baseclass.extend()?**
- LuCI's module system expects class-based modules
- Views import with `'require module/api as API'` which auto-instantiates
- `baseclass.extend()` creates a proper class constructor
- `baseclass.singleton()` breaks the instantiation mechanism
- Plain objects don't support LuCI's module lifecycle
### rpc.declare() Parameters
```javascript
var callMethodName = rpc.declare({
object: 'luci.module-name', // ubus object name (MUST start with luci.)
method: 'method_name', // RPCD method name
params: ['param1', 'param2'], // Optional: parameter names (order matters!)
expect: {} // Expected return structure (or { key: [] } for arrays)
});
```
**Parameter Order Matters**:
```javascript
// RPCD expects parameters in this exact order
var callSaveSettings = rpc.declare({
object: 'luci.system-hub',
method: 'save_settings',
params: ['auto_refresh', 'health_check', 'debug_mode', 'refresh_interval'],
expect: {}
});
// JavaScript call MUST pass parameters in same order
API.saveSettings(1, 1, 0, 30); // auto_refresh=1, health_check=1, debug_mode=0, refresh_interval=30
```
### expect Parameter Patterns
```javascript
// Method returns single object
expect: {}
// Method returns array at top level
expect: { services: [] }
// Method returns specific structure
expect: {
services: [],
count: 0
}
```
### Error Handling in API Module
API methods return Promises. Handle errors in views:
```javascript
return API.getHealth().then(function(data) {
if (!data || typeof data !== 'object') {
console.error('Invalid health data:', data);
return null;
}
return data;
}).catch(function(err) {
console.error('Failed to load health data:', err);
ui.addNotification(null, E('p', {}, 'Failed to load health data'), 'error');
return null;
});
```
---
## LuCI View Import Patterns
### CRITICAL: Use 'require ... as VAR' for APIs
**RULE**: When importing API modules, use the `'require ... as VAR'` pattern at the top of the file.
```javascript
// ✅ CORRECT: Auto-instantiates the class
'require system-hub/api as API';
return L.view.extend({
load: function() {
return API.getHealth(); // API is already instantiated
}
});
// ❌ WRONG: Returns class constructor, not instance
var api = L.require('system-hub.api');
api.getHealth(); // ERROR: api.getHealth is not a function
```
**Why?**
- `'require module/path as VAR'` (with forward slashes) auto-instantiates classes
- `L.require('module.path')` (with dots) returns raw class constructor
- API modules extend `baseclass`, which needs instantiation
- LuCI's module loader handles instantiation when using the `as VAR` pattern
### Standard View Structure
```javascript
'use strict';
'require view';
'require form';
'require ui';
'require system-hub/api as API';
return L.view.extend({
load: function() {
// Load data needed for rendering
return Promise.all([
API.getHealth(),
API.getStatus()
]);
},
render: function(data) {
var health = data[0];
var status = data[1];
// Create UI elements
var container = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, 'Dashboard'),
// ... more elements
]);
return container;
},
handleSave: null, // Disable save button
handleSaveApply: null, // Disable save & apply button
handleReset: null // Disable reset button
});
```
### Import Patterns Summary
```javascript
// Core LuCI modules (always with quotes)
'require view';
'require form';
'require ui';
'require rpc';
'require baseclass';
// Custom API modules (use 'as VAR' for auto-instantiation)
'require system-hub/api as API';
'require cdn-cache/api as CdnAPI';
// Access global L object (no require)
L.resolveDefault(...)
L.Poll.add(...)
L.ui.addNotification(...)
```
---
## ACL Permission Structure
### File Location
ACL files are located in:
```
/usr/share/rpcd/acl.d/luci-app-<module-name>.json
```
In source tree:
```
luci-app-<module-name>/root/usr/share/rpcd/acl.d/luci-app-<module-name>.json
```
### Standard ACL Template
```json
{
"luci-app-module-name": {
"description": "Module Name - Description",
"read": {
"ubus": {
"luci.module-name": [
"status",
"get_system_info",
"get_health",
"list_services",
"get_logs",
"get_storage",
"get_settings"
]
}
},
"write": {
"ubus": {
"luci.module-name": [
"service_action",
"backup_config",
"restore_config",
"reboot",
"save_settings"
]
}
}
}
}
```
### Read vs Write Classification
**Read Operations** (no system modification):
- `status` - Get current state
- `get_*` - Retrieve data (system info, health, settings, logs, storage)
- `list_*` - Enumerate items (services, interfaces, etc.)
**Write Operations** (modify system state):
- `*_action` - Perform actions (start/stop services, etc.)
- `save_*` - Persist configuration changes
- `backup`, `restore` - System backup/restore
- `reboot`, `shutdown` - System control
### Common ACL Errors
**Error**: `Access denied` or RPC error `-32002`
**Cause**: Method not listed in ACL, or listed in wrong section (read vs write)
**Solution**:
1. Identify if method is read or write operation
2. Add method name to correct section in ACL
3. Restart RPCD: `/etc/init.d/rpcd restart`
**Validation**:
```bash
# Check if ACL file is valid JSON
jsonlint /usr/share/rpcd/acl.d/luci-app-system-hub.json
# List all ubus objects and methods
ubus list luci.system-hub
# Test method with ubus call
ubus call luci.system-hub get_health
```
---
## Data Structure Conventions
### Health Metrics Structure (system-hub v0.1.0)
Based on extensive iteration, this structure provides clarity and consistency:
```json
{
"cpu": {
"usage": 25,
"status": "ok",
"load_1m": "0.25",
"load_5m": "0.30",
"load_15m": "0.28",
"cores": 4
},
"memory": {
"total_kb": 4096000,
"free_kb": 2048000,
"available_kb": 3072000,
"used_kb": 1024000,
"buffers_kb": 512000,
"cached_kb": 1536000,
"usage": 25,
"status": "ok"
},
"disk": {
"total_kb": 30408704,
"used_kb": 5447680,
"free_kb": 24961024,
"usage": 19,
"status": "ok"
},
"temperature": {
"value": 45,
"status": "ok"
},
"network": {
"wan_up": true,
"status": "ok"
},
"services": {
"running": 35,
"failed": 2
},
"score": 92,
"timestamp": "2025-12-26 10:30:00",
"recommendations": [
"2 service(s) enabled but not running. Check service status."
]
}
```
**Key Principles**:
1. **Nested objects** for related metrics (cpu, memory, disk, etc.)
2. **Consistent structure**: Each metric has `usage` (percentage) and `status` (ok/warning/critical)
3. **Raw values + computed values**: Provide both (e.g., `used_kb` AND `usage` percentage)
4. **Status thresholds**: ok (< warning), warning (warning-critical), critical (≥ critical)
5. **Overall score**: Single 0-100 health score for dashboard
6. **Dynamic recommendations**: Array of actionable alerts based on thresholds
### Status Values
Use consistent status strings across all metrics:
- `"ok"` - Normal operation (green)
- `"warning"` - Approaching threshold (orange)
- `"critical"` - Exceeded threshold (red)
- `"error"` - Unable to retrieve metric
- `"unknown"` - Metric not available
### Timestamp Format
Use ISO 8601 or consistent local format:
```bash
timestamp="$(date '+%Y-%m-%d %H:%M:%S')" # 2025-12-26 10:30:00
```
### Boolean Values in JSON
In shell scripts using jshn.sh:
```bash
json_add_boolean "wan_up" 1 # true
json_add_boolean "wan_up" 0 # false
```
In JavaScript:
```javascript
if (health.network.wan_up) {
// WAN is up
}
```
### Array vs Single Value
**Use arrays for**:
- Multiple items of same type (services, interfaces, mount points)
- Variable-length data
**Use single values for**:
- System-wide metrics (CPU, memory, disk)
- Primary/aggregate values (overall temperature, total uptime)
**Example - Storage**:
```json
// Multiple mount points - use array
"storage": [
{
"mount": "/",
"total_kb": 30408704,
"used_kb": 5447680,
"usage": 19
},
{
"mount": "/mnt/usb",
"total_kb": 128000000,
"used_kb": 64000000,
"usage": 50
}
]
// Root filesystem only - use object
"disk": {
"total_kb": 30408704,
"used_kb": 5447680,
"usage": 19,
"status": "ok"
}
```
---
## Common Errors and Solutions
### 1. RPC Error: "Object not found" (-32000)
**Error Message**:
```
RPC call to system-hub/status failed with error -32000: Object not found
```
**Cause**: RPCD script name doesn't match ubus object name in JavaScript
**Solution**:
1. Check JavaScript for object name:
```bash
grep -r "object:" luci-app-system-hub/htdocs --include="*.js"
```
Output: `object: 'luci.system-hub'`
2. Rename RPCD script to match exactly:
```bash
mv root/usr/libexec/rpcd/system-hub root/usr/libexec/rpcd/luci.system-hub
```
3. Ensure script is executable:
```bash
chmod +x root/usr/libexec/rpcd/luci.system-hub
```
4. Restart RPCD:
```bash
/etc/init.d/rpcd restart
```
### 2. JavaScript Error: "api.methodName is not a function"
**Error Message**:
```
Uncaught TypeError: api.getHealth is not a function
at view.load (health.js:12)
```
**Cause**: Wrong import pattern - imported class constructor instead of instance
**Solution**:
Change from:
```javascript
var api = L.require('system-hub.api'); // ❌ Wrong
```
To:
```javascript
'require system-hub/api as API'; // ✅ Correct
```
**Why**: `L.require('module.path')` returns raw class, `'require module/path as VAR'` auto-instantiates.
### 3. RPC Error: "Access denied" (-32002)
**Error Message**:
```
RPC call to luci.system-hub/get_settings failed with error -32002: Access denied
```
**Cause**: Method not listed in ACL file, or in wrong section (read vs write)
**Solution**:
1. Open ACL file: `root/usr/share/rpcd/acl.d/luci-app-system-hub.json`
2. Add method to appropriate section:
```json
"read": {
"ubus": {
"luci.system-hub": [
"get_settings"
]
}
}
```
3. Deploy and restart RPCD:
```bash
scp luci-app-system-hub/root/usr/share/rpcd/acl.d/*.json router:/usr/share/rpcd/acl.d/
ssh router "/etc/init.d/rpcd restart"
```
### 4. Display Error: "NaN%" or Undefined Values
**Error**: Dashboard shows "NaN%", "undefined", or empty values
**Cause**: Frontend using wrong data structure keys (outdated after backend changes)
**Solution**:
1. Check backend output:
```bash
ubus call luci.system-hub get_health
```
2. Update frontend to match structure:
```javascript
// Old structure
var cpuPercent = health.load / health.cores * 100;
var memPercent = health.memory.percent;
// New structure
var cpuPercent = health.cpu ? health.cpu.usage : 0;
var memPercent = health.memory ? health.memory.usage : 0;
```
3. Add null/undefined checks:
```javascript
var temp = health.temperature?.value || 0;
var loadAvg = health.cpu?.load_1m || '0.00';
```
### 5. HTTP 404: View File Not Found
**Error Message**:
```
HTTP error 404 while loading class file '/luci-static/resources/view/netifyd/overview.js'
```
**Cause**: Menu path doesn't match actual view file location
**Solution**:
1. Check menu JSON:
```bash
cat root/usr/share/luci/menu.d/luci-app-netifyd-dashboard.json
```
Look for: `"path": "netifyd/overview"`
2. Check actual file location:
```bash
ls htdocs/luci-static/resources/view/
```
File is at: `view/netifyd-dashboard/overview.js`
3. Fix either menu path OR file location:
```json
// Option 1: Update menu path to match file
"path": "netifyd-dashboard/overview"
// Option 2: Move file to match menu
mv view/netifyd-dashboard/ view/netifyd/
```
### 6. Build Error: "factory yields invalid constructor"
**Error Message**:
```
/luci-static/resources/system-hub/api.js: factory yields invalid constructor
```
**Cause**: Used wrong pattern in API module (singleton, plain object, etc.)
**Solution**:
Always use `baseclass.extend()`:
```javascript
return baseclass.extend({
getStatus: callStatus,
getHealth: callGetHealth,
// ... more methods
});
```
Do NOT use:
- `baseclass.singleton({...})`
- Plain object: `return {...}`
- `baseclass.prototype`
### 7. RPCD Not Responding After Changes
**Symptom**: Changes to RPCD script don't take effect
**Solution**:
1. Verify script is deployed:
```bash
ssh router "ls -la /usr/libexec/rpcd/"
```
2. Check script is executable:
```bash
ssh router "chmod +x /usr/libexec/rpcd/luci.system-hub"
```
3. Restart RPCD:
```bash
ssh router "/etc/init.d/rpcd restart"
```
4. Clear browser cache (Ctrl+Shift+R)
5. Check RPCD logs:
```bash
ssh router "logread | grep rpcd"
```
---
## Validation Checklist
Use this checklist before deployment:
### File Structure
- [ ] RPCD script exists: `/usr/libexec/rpcd/luci.<module-name>`
- [ ] RPCD script is executable: `chmod +x`
- [ ] Menu JSON exists: `/usr/share/luci/menu.d/luci-app-<module>.json`
- [ ] ACL JSON exists: `/usr/share/rpcd/acl.d/luci-app-<module>.json`
- [ ] API module exists: `htdocs/luci-static/resources/<module>/api.js`
- [ ] Views exist: `htdocs/luci-static/resources/view/<module>/*.js`
### Naming Conventions
- [ ] RPCD script name matches ubus object in JavaScript (including `luci.` prefix)
- [ ] Menu paths match view file directory structure
- [ ] All ubus objects start with `luci.`
- [ ] ACL key matches package name: `"luci-app-<module>"`
### Code Validation
- [ ] API module uses `baseclass.extend()` pattern
- [ ] Views import API with `'require <module>/api as API'` pattern
- [ ] All rpc.declare() calls include correct `object`, `method`, `params`, `expect`
- [ ] RPCD script outputs valid JSON (test with `ubus call`)
- [ ] Menu JSON is valid (test with `jsonlint`)
- [ ] ACL JSON is valid (test with `jsonlint`)
### Permissions
- [ ] All read methods in ACL `"read"` section
- [ ] All write methods in ACL `"write"` section
- [ ] Methods in ACL match RPCD script method names exactly
### Testing
- [ ] Run validation script: `./secubox-tools/validate-modules.sh`
- [ ] Test each method via ubus: `ubus call luci.<module> <method>`
- [ ] Test frontend in browser (check console for errors)
- [ ] Clear browser cache after deployment
- [ ] Verify RPCD restart: `/etc/init.d/rpcd restart`
### Automated Validation Command
```bash
# Run comprehensive validation
./secubox-tools/validate-modules.sh
# Validate specific module
./secubox-tools/validate-module-generation.sh luci-app-system-hub
# Check JSON syntax
find luci-app-system-hub -name "*.json" -exec jsonlint {} \;
# Check shell scripts
shellcheck luci-app-system-hub/root/usr/libexec/rpcd/*
```
---
## Testing and Deployment
### Local Testing with ubus
Before deploying to router, test RPCD script locally:
```bash
# Copy RPCD script to local /tmp
cp luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub /tmp/
# Make executable
chmod +x /tmp/luci.system-hub
# Test 'list' action
/tmp/luci.system-hub list
# Test 'call' action with method
/tmp/luci.system-hub call status
# Test method with parameters
echo '{"service":"network","action":"restart"}' | /tmp/luci.system-hub call service_action
```
### Deployment Script
Use a deployment script for fast iteration:
```bash
#!/bin/bash
# deploy-system-hub.sh
ROUTER="root@192.168.8.191"
echo "🚀 Deploying system-hub to $ROUTER"
# Deploy API module
scp luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js \
"$ROUTER:/www/luci-static/resources/system-hub/"
# Deploy views
scp luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/*.js \
"$ROUTER:/www/luci-static/resources/view/system-hub/"
# Deploy RPCD backend
scp luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub \
"$ROUTER:/usr/libexec/rpcd/"
# Deploy ACL
scp luci-app-system-hub/root/usr/share/rpcd/acl.d/luci-app-system-hub.json \
"$ROUTER:/usr/share/rpcd/acl.d/"
# Set permissions and restart
ssh "$ROUTER" "chmod +x /usr/libexec/rpcd/luci.system-hub && /etc/init.d/rpcd restart"
echo "✅ Deployment complete! Clear browser cache (Ctrl+Shift+R)"
```
### Browser Testing
1. Open browser console (F12)
2. Navigate to module page
3. Check for errors:
- RPC errors (object not found, method not found, access denied)
- JavaScript errors (api.method is not a function)
- 404 errors (view files not found)
4. Test functionality:
- Load data displays correctly
- Actions work (start/stop services, save settings)
- No "NaN", "undefined", or empty values
### Remote ubus Testing
Test RPCD methods on router:
```bash
# List all methods
ssh router "ubus list luci.system-hub"
# Call method without parameters
ssh router "ubus call luci.system-hub status"
# Call method with parameters
ssh router "ubus call luci.system-hub service_action '{\"service\":\"network\",\"action\":\"restart\"}'"
# Pretty-print JSON output
ssh router "ubus call luci.system-hub get_health | jsonlint"
```
### Debugging Tips
**Enable RPCD debug logging**:
```bash
# Edit /etc/init.d/rpcd
# Add -v flag to procd_set_param command
procd_set_param command "$PROG" -v
# Restart RPCD
/etc/init.d/rpcd restart
# Watch logs
logread -f | grep rpcd
```
**Enable JavaScript console logging**:
```javascript
// Add to api.js
console.log('🔧 API v0.1.0 loaded at', new Date().toISOString());
// Add to views
console.log('Loading health data...');
API.getHealth().then(function(data) {
console.log('Health data:', data);
});
```
**Test JSON output**:
```bash
# On router
/usr/libexec/rpcd/luci.system-hub call get_health | jsonlint
# Check for common errors
# - Missing commas
# - Trailing commas
# - Unquoted keys
# - Invalid escape sequences
```
---
## Best Practices Summary
### DO:
Use `luci.` prefix for all ubus objects
Name RPCD scripts to match ubus object exactly
Use `baseclass.extend()` for API modules
Import APIs with `'require module/api as API'` pattern
Add null/undefined checks in frontend: `health.cpu?.usage || 0`
Validate JSON with `jsonlint` before deploying
Test with `ubus call` before browser testing
Restart RPCD after backend changes
Clear browser cache after frontend changes
Run `./secubox-tools/validate-modules.sh` before committing
### DON'T:
Use ubus object names without `luci.` prefix
Use `baseclass.singleton()` or plain objects for API modules
Import APIs with `L.require('module.path')` (returns class, not instance)
Forget to add methods to ACL file
Mix up read/write methods in ACL sections
Output non-JSON from RPCD scripts
Use inconsistent data structures between backend and frontend
Deploy without testing locally first
Assume data exists - always check for null/undefined
Forget to make RPCD scripts executable (`chmod +x`)
---
## Version History
**v1.0** (2025-12-26)
- Initial reference guide
- Based on luci-app-secubox v1.0.0 and luci-app-system-hub v0.1.0
- Documented all critical patterns and common errors
- Validated against real-world implementation challenges
---
## References
- **OpenWrt Documentation**: https://openwrt.org/docs/guide-developer/start
- **LuCI Documentation**: https://github.com/openwrt/luci/wiki
- **ubus Documentation**: https://openwrt.org/docs/techref/ubus
- **UCI Documentation**: https://openwrt.org/docs/guide-user/base-system/uci
- **jshn.sh Library**: `/usr/share/libubox/jshn.sh` on OpenWrt
---
## Contact
For questions or contributions to this reference guide:
- **Author**: CyberMind <contact@cybermind.fr>
- **Project**: SecuBox OpenWrt
- **Repository**: https://github.com/cybermind-fr/secubox-openwrt
---
**END OF REFERENCE GUIDE**