secubox-openwrt/DOCS/LUCI_DEVELOPMENT_REFERENCE.md
CyberMind-FR 403283419c docs: Reorganize documentation structure and add architecture diagrams
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>
2025-12-28 09:52:15 +01:00

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

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
  2. RPCD Backend Patterns
  3. LuCI API Module Patterns
  4. LuCI View Import Patterns
  5. ACL Permission Structure
  6. Data Structure Conventions
  7. Common Errors and Solutions
  8. Validation Checklist
  9. 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 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

#!/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 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:

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

  1. Cache expensive operations: Don't re-read /proc files multiple times
  2. Use command substitution efficiently:
    # Good
    uptime=$(cat /proc/uptime | cut -d' ' -f1)
    
    # Better
    read uptime _ < /proc/uptime
    uptime=${uptime%.*}
    
  3. 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 constructor
  • baseclass.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 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

'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 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:

# 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:

  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:

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:

  1. Check JavaScript for object name:

    grep -r "object:" luci-app-system-hub/htdocs --include="*.js"
    

    Output: object: 'luci.system-hub'

  2. Rename RPCD script to match exactly:

    mv root/usr/libexec/rpcd/system-hub root/usr/libexec/rpcd/luci.system-hub
    
  3. Ensure script is executable:

    chmod +x root/usr/libexec/rpcd/luci.system-hub
    
  4. 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:

  1. Open ACL file: root/usr/share/rpcd/acl.d/luci-app-system-hub.json

  2. Add method to appropriate section:

    "read": {
        "ubus": {
            "luci.system-hub": [
                "get_settings"
            ]
        }
    }
    
  3. 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:

  1. Check backend output:

    ubus call luci.system-hub get_health
    
  2. 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;
    
  3. 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:

  1. Check menu JSON:

    cat root/usr/share/luci/menu.d/luci-app-netifyd-dashboard.json
    

    Look for: "path": "netifyd/overview"

  2. Check actual file location:

    ls htdocs/luci-static/resources/view/
    

    File is at: view/netifyd-dashboard/overview.js

  3. 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:

  1. Verify script is deployed:

    ssh router "ls -la /usr/libexec/rpcd/"
    
  2. Check script is executable:

    ssh router "chmod +x /usr/libexec/rpcd/luci.system-hub"
    
  3. Restart RPCD:

    ssh router "/etc/init.d/rpcd restart"
    
  4. Clear browser cache (Ctrl+Shift+R)

  5. 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

  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:

# 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


Contact

For questions or contributions to this reference guide:


END OF REFERENCE GUIDE