Major documentation improvements and restructuring for better maintainability and navigation. ## Structural Changes ### New Documentation Organization - Move all documentation to DOCS/ directory for better organization - Create DOCS/archive/ for historical documents - Move deployment scripts to secubox-tools/ directory ### Archived Documents - COMPLETION_REPORT.md → archive/ (project milestone) - MODULE-ENABLE-DISABLE-DESIGN.md → archive/ (feature implemented) - BUILD_ISSUES.md → archive/ (issues resolved) - Add archive/README.md with archiving policy and document index ## Documentation Enhancements ### Version Standardization - Add version headers to CLAUDE.md (v1.0.0) - Add version headers to BUILD_ISSUES.md (v1.0.0) - Standardize date format to YYYY-MM-DD across all docs ### Cross-References & Navigation - Add "See Also" sections to PERMISSIONS-GUIDE.md - Add "See Also" sections to VALIDATION-GUIDE.md - Link quick references to detailed guides - Improve documentation discoverability ### Architecture Diagrams (Mermaid) Add 3 professional diagrams to DEVELOPMENT-GUIDELINES.md: 1. **System Architecture Diagram** (§2) - Complete data flow: Browser → LuCI → RPCD → ubus → System - Color-coded components by layer - Shows JavaScript, RPC, RPCD daemon, UCI, system services 2. **Deployment Workflow Diagram** (§9) - Step-by-step deployment process with validation checkpoints - Error recovery paths for common issues (403, 404, -32000) - Local validation, file transfer, permission fixes, service restarts 3. **Component Hierarchy Diagram** (§1) - Standard page structure and CSS class relationships - Page → Header → Stats → Content → Cards → Buttons - Shows design system component organization ## New Files ### TODO-ANALYSE.md - Comprehensive documentation improvement roadmap - Tasks categorized: Immediate, Short-term, Long-term, Optional - Progress tracking with acceptance criteria - Covers testing, security, performance guides - Documentation automation plans ## Benefits ✅ Cleaner project structure (docs in DOCS/, tools in secubox-tools/) ✅ Better documentation navigation with cross-references ✅ Visual understanding through architecture diagrams ✅ Historical documents archived but accessible ✅ Standardized versioning across all documentation ✅ Clear roadmap for future documentation improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
30 KiB
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
- Quick Commands: QUICK-START.md
- Automation Briefing: CODEX.md
- Code Templates: 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
- ubus and RPC Fundamentals
- RPCD Backend Patterns
- LuCI API Module Patterns
- LuCI View Import Patterns
- ACL Permission Structure
- Data Structure Conventions
- Common Errors and Solutions
- Validation Checklist
- 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.
// ✅ 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:
# 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:
# 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 stateget_*- 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:
- Parse
$1for the action (listorcall) - Parse
$2for the method name (ifcall) - Read JSON input from stdin (for methods with parameters)
- Output JSON to stdout
- Exit with status 0 on success, non-zero on error
Standard Template
#!/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:
# 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 objectjson_add_string "key" "value"- Add stringjson_add_int "key" 123- Add integerjson_add_boolean "key" 1- Add boolean (0 or 1)json_add_object "key"- Start nested objectjson_close_object- End nested objectjson_add_array "key"- Start arrayjson_close_array- End arrayjson_dump- Output JSON to stdout
Error Handling
Always validate inputs and return meaningful errors:
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):
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
- Cache expensive operations: Don't re-read
/procfiles multiple times - Use command substitution efficiently:
# Good uptime=$(cat /proc/uptime | cut -d' ' -f1) # Better read uptime _ < /proc/uptime uptime=${uptime%.*} - Avoid external commands when possible:
# 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.
'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 constructorbaseclass.singleton()breaks the instantiation mechanism- Plain objects don't support LuCI's module lifecycle
rpc.declare() Parameters
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:
// 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
// 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:
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.
// ✅ 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 classesL.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 VARpattern
Standard View Structure
'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
// 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
{
"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 stateget_*- 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 changesbackup,restore- System backup/restorereboot,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:
- Identify if method is read or write operation
- Add method name to correct section in ACL
- Restart RPCD:
/etc/init.d/rpcd restart
Validation:
# 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:
{
"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:
- Nested objects for related metrics (cpu, memory, disk, etc.)
- Consistent structure: Each metric has
usage(percentage) andstatus(ok/warning/critical) - Raw values + computed values: Provide both (e.g.,
used_kbANDusagepercentage) - Status thresholds: ok (< warning), warning (warning-critical), critical (≥ critical)
- Overall score: Single 0-100 health score for dashboard
- 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:
timestamp="$(date '+%Y-%m-%d %H:%M:%S')" # 2025-12-26 10:30:00
Boolean Values in JSON
In shell scripts using jshn.sh:
json_add_boolean "wan_up" 1 # true
json_add_boolean "wan_up" 0 # false
In 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:
// 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:
-
Check JavaScript for object name:
grep -r "object:" luci-app-system-hub/htdocs --include="*.js"Output:
object: 'luci.system-hub' -
Rename RPCD script to match exactly:
mv root/usr/libexec/rpcd/system-hub root/usr/libexec/rpcd/luci.system-hub -
Ensure script is executable:
chmod +x root/usr/libexec/rpcd/luci.system-hub -
Restart RPCD:
/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:
var api = L.require('system-hub.api'); // ❌ Wrong
To:
'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:
-
Open ACL file:
root/usr/share/rpcd/acl.d/luci-app-system-hub.json -
Add method to appropriate section:
"read": { "ubus": { "luci.system-hub": [ "get_settings" ] } } -
Deploy and restart RPCD:
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:
-
Check backend output:
ubus call luci.system-hub get_health -
Update frontend to match structure:
// ❌ 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; -
Add null/undefined checks:
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:
-
Check menu JSON:
cat root/usr/share/luci/menu.d/luci-app-netifyd-dashboard.jsonLook for:
"path": "netifyd/overview" -
Check actual file location:
ls htdocs/luci-static/resources/view/File is at:
view/netifyd-dashboard/overview.js -
Fix either menu path OR file location:
// 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():
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:
-
Verify script is deployed:
ssh router "ls -la /usr/libexec/rpcd/" -
Check script is executable:
ssh router "chmod +x /usr/libexec/rpcd/luci.system-hub" -
Restart RPCD:
ssh router "/etc/init.d/rpcd restart" -
Clear browser cache (Ctrl+Shift+R)
-
Check RPCD logs:
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
# 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:
# 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:
#!/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
- Open browser console (F12)
- Navigate to module page
- 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)
- 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:
# 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:
# 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:
// 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:
# 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.shon 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