#!/bin/sh # SPDX-License-Identifier: Apache-2.0 # SecuBox Master RPCD Backend # Module discovery and unified status # Copyright (C) 2025 CyberMind.fr . /lib/functions.sh . /usr/share/libubox/jshn.sh get_pkg_version() { local version="" # OpenWrt 25.12+ uses apk packages if command -v apk >/dev/null 2>&1; then local apk_line=$(apk info -v luci-app-secubox 2>/dev/null | grep -E '^luci-app-secubox-[0-9]' | head -n1) if [ -n "$apk_line" ]; then version=$(echo "$apk_line" | sed 's/^luci-app-secubox-//' | awk '{print $1}' | sed 's/-r[0-9]*$//') else version=$(apk info luci-app-secubox 2>/dev/null | awk -F': ' '/^Version/ {print $2; exit}') fi if [ -n "$version" ]; then echo "$version" return fi fi # Legacy opkg metadata local ctrl="/usr/lib/opkg/info/luci-app-secubox.control" if [ -f "$ctrl" ]; then version=$(awk -F': ' '/^Version/ { print $2; exit }' "$ctrl") if [ -n "$version" ]; then echo "$version" return fi fi if command -v opkg >/dev/null 2>&1; then version=$(opkg list-installed luci-app-secubox 2>/dev/null | awk '{print $3}' | sed 's/-r[0-9]*$//' | head -n1) if [ -n "$version" ]; then echo "$version" return fi fi local cfg_version="$(uci -q get secubox.main.version)" if [ -n "$cfg_version" ]; then echo "$cfg_version" elif [ -f "/usr/share/secubox/VERSION" ]; then head -n1 /usr/share/secubox/VERSION else echo "unknown" fi } PKG_VERSION="$(get_pkg_version)" PLUGIN_DIR="/usr/share/secubox/plugins" PROFILE_DIR="/usr/share/secubox/profiles" PROFILE_BACKUP_DIR="/etc/secubox-profiles/backups" DEFAULT_STORAGE_PATH="/srv/secubox" SECOBOX_APP="/usr/sbin/secubox-app" OPKG_UPDATED=0 manifest_files() { if [ -d "$PLUGIN_DIR/catalog" ]; then for file in "$PLUGIN_DIR"/catalog/*.json; do [ -f "$file" ] || continue echo "$file" done fi for file in "$PLUGIN_DIR"/*/manifest.json; do [ -f "$file" ] || continue echo "$file" done } manifest_file_for_id() { local id="$1" local catalog="$PLUGIN_DIR/catalog/$id.json" local legacy="$PLUGIN_DIR/$id/manifest.json" if [ -f "$catalog" ]; then printf '%s' "$catalog" return 0 fi if [ -f "$legacy" ]; then printf '%s' "$legacy" return 0 fi return 1 } validate_manifest_json() { local manifest="$1" local id name runtime packages category id=$(jsonfilter -s "$manifest" -e '@.id' 2>/dev/null) name=$(jsonfilter -s "$manifest" -e '@.name' 2>/dev/null) category=$(jsonfilter -s "$manifest" -e '@.category' 2>/dev/null) runtime=$(jsonfilter -s "$manifest" -e '@.runtime' 2>/dev/null) [ -n "$runtime" ] || runtime=$(jsonfilter -s "$manifest" -e '@.type' 2>/dev/null) packages=$(jsonfilter -s "$manifest" -e '@.packages[*]' 2>/dev/null) [ -n "$id" ] && [ -n "$name" ] && [ -n "$runtime" ] && [ -n "$category" ] && [ -n "$packages" ] } # Module registry - auto-detected from /usr/libexec/rpcd/ detect_modules() { local modules="" local scripts="luci.crowdsec-dashboard luci.netdata-dashboard luci.netifyd-dashboard luci.wireguard-dashboard luci.network-modes luci.client-guardian luci.system-hub luci.bandwidth-manager luci.auth-guardian luci.media-flow luci.vhost-manager luci.cdn-cache luci.traffic-shaper luci.ksm-manager" for script in $scripts; do if [ -x "/usr/libexec/rpcd/$script" ]; then # Remove luci. prefix, remove -dashboard suffix, convert dashes to underscores local module_id=$(echo "$script" | sed 's/^luci\.//' | sed 's/-dashboard$//' | sed 's/-/_/g') modules="$modules $module_id" fi done echo "$modules" } MODULES=$(detect_modules) is_root_password_set() { local hash hash=$(grep '^root:' /etc/shadow 2>/dev/null | cut -d: -f2) [ -n "$hash" ] && [ "$hash" != "!" ] && [ "$hash" != "*" ] } get_storage_path() { local path path=$(uci -q get secubox.main.storage_path) if [ -z "$path" ]; then path="$DEFAULT_STORAGE_PATH" fi echo "$path" } package_installed() { local pkg="$1" if command -v opkg >/dev/null 2>&1; then opkg status "$pkg" >/dev/null 2>&1 && return 0 elif command -v apk >/dev/null 2>&1; then apk info -e "$pkg" >/dev/null 2>&1 && return 0 fi return 1 } packages_state() { local manifest="$1" local total=0 installed=0 missing=0 pkg for pkg in $(jsonfilter -s "$manifest" -e '@.packages[*]' 2>/dev/null); do total=$((total + 1)) if package_installed "$pkg"; then installed=$((installed + 1)) else missing=$((missing + 1)) fi done if [ "$total" -eq 0 ]; then echo "n/a" elif [ "$missing" -eq 0 ]; then echo "installed" elif [ "$installed" -eq 0 ]; then echo "missing" else echo "partial" fi } ensure_directory() { local dir="$1" [ -d "$dir" ] || mkdir -p "$dir" } apply_network_mode() { local mode="$1" [ -n "$mode" ] || return if command -v ubus >/dev/null 2>&1; then if ubus call luci.network-modes set_mode "{\"mode\":\"$mode\"}" >/dev/null 2>&1; then ubus call luci.network-modes apply_mode >/dev/null 2>&1 fi fi } ensure_opkg_updated() { [ "$OPKG_UPDATED" -eq 1 ] && return if command -v opkg >/dev/null 2>&1; then opkg update >/dev/null 2>&1 && OPKG_UPDATED=1 fi } install_package_if_missing() { local pkg="$1" package_installed "$pkg" && return if command -v opkg >/dev/null 2>&1; then ensure_opkg_updated opkg install "$pkg" >/dev/null 2>&1 elif command -v apk >/dev/null 2>&1; then apk add "$pkg" >/dev/null 2>&1 fi } # Check if a module is installed (supports both opkg and apk) check_module_installed() { local module="$1" local package config config_get package "$module" package "" config_get config "$module" config "" # Check if package is installed via apk (OpenWrt 25.12+) or opkg (24.10 and earlier) if [ -x "/usr/bin/apk" ]; then # OpenWrt 25.12+ uses apk if apk list --installed 2>/dev/null | grep -q "^${package}-"; then echo "1" return fi elif [ -x "/bin/opkg" ] || [ -x "/usr/bin/opkg" ]; then # OpenWrt 24.10 and earlier uses opkg if opkg list-installed 2>/dev/null | grep -q "^${package} "; then echo "1" return fi fi # Fallback: check if config file exists if [ -f "/etc/config/${config}" ]; then echo "1" else echo "0" fi } # Check if a module is enabled (UCI config) check_module_enabled() { local module="$1" local enabled config_load secubox config_get enabled "$module" enabled "1" echo "$enabled" } # Check if a module service is running check_module_running() { local module="$1" local config config_load secubox config_get config "$module" config "" case "$module" in crowdsec) pgrep -f crowdsec > /dev/null 2>&1 && echo "1" || echo "0" ;; netdata) pgrep -f netdata > /dev/null 2>&1 && echo "1" || echo "0" ;; netifyd) pgrep -f netifyd > /dev/null 2>&1 && echo "1" || echo "0" ;; wireguard) [ -n "$(wg show 2>/dev/null)" ] && echo "1" || echo "0" ;; cdn_cache) pgrep -f "nginx.*cdn-cache" > /dev/null 2>&1 && echo "1" || echo "0" ;; *) # Generic check via init.d if [ -x "/etc/init.d/${config}" ]; then /etc/init.d/${config} running > /dev/null 2>&1 && echo "1" || echo "0" else echo "0" fi ;; esac } # Determine module status based on enabled + running get_module_status() { local enabled="$1" local running="$2" if [ "$enabled" = "1" ] && [ "$running" = "1" ]; then echo "active" elif [ "$enabled" = "1" ] && [ "$running" = "0" ]; then echo "error" elif [ "$enabled" = "0" ] && [ "$running" = "0" ]; then echo "disabled" else echo "unknown" fi } # Get overall system status get_status() { local total=0 local installed=0 local running=0 json_init json_add_string "version" "$PKG_VERSION" json_add_string "hostname" "$(uci -q get system.@system[0].hostname || echo 'SecuBox')" json_add_boolean "enabled" "$(uci -q get secubox.main.enabled || echo 1)" # System info local uptime=$(cat /proc/uptime | cut -d. -f1) local load=$(cat /proc/loadavg | cut -d' ' -f1-3) local mem_total=$(grep MemTotal /proc/meminfo | awk '{print $2}') local mem_free=$(grep MemAvailable /proc/meminfo | awk '{print $2}') local mem_used=$((mem_total - mem_free)) local mem_pct=$((mem_used * 100 / mem_total)) json_add_int "uptime" "$uptime" json_add_string "load" "$load" json_add_int "memory_total" "$mem_total" json_add_int "memory_used" "$mem_used" json_add_int "memory_percent" "$mem_pct" # Count modules from UCI config (same as get_modules) config_load secubox 2>/dev/null || true local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1) for module in $module_sections; do total=$((total + 1)) local is_installed=$(check_module_installed "$module") local is_running=$(check_module_running "$module") if [ "$is_installed" = "1" ]; then installed=$((installed + 1)) fi if [ "$is_running" = "1" ]; then running=$((running + 1)) fi done json_add_int "modules_total" "$total" json_add_int "modules_installed" "$installed" json_add_int "modules_running" "$running" json_dump } # Detect real installed luci-app packages (supports both opkg and apk) detect_real_modules() { if [ -x "/usr/bin/apk" ]; then # OpenWrt 25.12+ uses apk apk list --installed 2>/dev/null | grep "^luci-app-" | grep -v "^luci-app-secubox-" | while IFS=- read -r pkg rest; do # Parse apk output: luci-app-network-modes-0.3.1-r1 noarch {...} (license) [installed] local package=$(echo "$pkg-$rest" | awk '{print $1}') local version=$(echo "$package" | sed 's/^luci-app-[^-]*-//' | sed 's/-r[0-9]*$//') local pkg_name=$(echo "$package" | sed 's/-[0-9].*//') local module_id=$(echo "$pkg_name" | sed 's/^luci-app-//' | sed 's/-dashboard$//' | sed 's/-/_/g') echo "$module_id:$pkg_name:$version" done elif [ -x "/bin/opkg" ] || [ -x "/usr/bin/opkg" ]; then # OpenWrt 24.10 and earlier uses opkg opkg list-installed 2>/dev/null | grep "^luci-app-" | grep -v "^luci-app-secubox " | while read package version; do # Extract module ID from package name local module_id=$(echo "$package" | sed 's/^luci-app-//' | sed 's/-dashboard$//' | sed 's/-/_/g') echo "$module_id:$package:$version" done fi } # Get detailed modules list (UCI + auto-detected) get_modules() { json_init json_add_array "modules" config_load secubox 2>/dev/null || true # Create associative array to track processed modules local processed_modules="" # First, add modules from UCI config local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1) for module in $module_sections; do local name desc category icon color package config version config_get name "$module" name "$module" config_get desc "$module" description "" config_get category "$module" category "other" config_get icon "$module" icon "📦" config_get color "$module" color "#64748b" config_get package "$module" package "" config_get config "$module" config "" config_get version "$module" version "0.0.9" local is_installed=$(check_module_installed "$module") local is_enabled=$(check_module_enabled "$module") local is_running=$(check_module_running "$module") local status=$(get_module_status "$is_enabled" "$is_running") # Get real version from package manager if installed if [ "$is_installed" = "1" ] && [ -n "$package" ]; then if [ -x "/usr/bin/apk" ]; then # OpenWrt 25.12+ uses apk # apk output: luci-app-network-modes-0.3.1-r1 noarch {...} local real_version=$(apk list --installed "$package" 2>/dev/null | head -1 | awk '{print $1}' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') [ -n "$real_version" ] && version="$real_version" elif [ -x "/bin/opkg" ] || [ -x "/usr/bin/opkg" ]; then # OpenWrt 24.10 and earlier uses opkg local real_version=$(opkg list-installed "$package" 2>/dev/null | awk '{print $3}' | sed 's/-.*$//') [ -n "$real_version" ] && version="$real_version" fi fi json_add_object "" json_add_string "id" "$module" json_add_string "name" "$name" json_add_string "description" "$desc" json_add_string "category" "$category" json_add_string "icon" "$icon" json_add_string "color" "$color" json_add_string "package" "$package" json_add_string "config" "$config" json_add_string "version" "$version" json_add_boolean "installed" "$is_installed" json_add_boolean "enabled" "$is_enabled" json_add_boolean "running" "$is_running" json_add_string "status" "$status" json_add_boolean "in_uci" "1" json_close_object processed_modules="$processed_modules $module" done # Second, add auto-detected modules not in UCI detect_real_modules | while IFS=: read module_id package version; do # Skip if already processed echo "$processed_modules" | grep -q " $module_id " && continue # Auto-detect properties based on package name local name=$(echo "$package" | sed 's/^luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++){$i=toupper(substr($i,1,1)) substr($i,2)}}1') local category="other" local icon="📦" local color="#64748b" # Auto-categorize case "$package" in *crowdsec*|*guardian*|*auth*) category="security"; icon="🛡️"; color="#22c55e" ;; *netdata*|*monitoring*) category="monitoring"; icon="📊"; color="#00ab44" ;; *network*|*wireguard*|*bandwidth*|*traffic*) category="network"; icon="🌐"; color="#3b82f6" ;; *system*|*hub*) category="system"; icon="⚙️"; color="#6366f1" ;; esac local clean_module=$(echo "$module_id" | sed 's/_/-/g') local is_enabled=$(check_module_enabled "$module_id") local is_running=$(check_module_running "$module_id") local status=$(get_module_status "$is_enabled" "$is_running") json_add_object "" json_add_string "id" "$module_id" json_add_string "name" "$name" json_add_string "description" "Auto-detected module" json_add_string "category" "$category" json_add_string "icon" "$icon" json_add_string "color" "$color" json_add_string "package" "$package" json_add_string "config" "$clean_module" json_add_string "version" "$version" json_add_boolean "installed" "1" json_add_boolean "enabled" "$is_enabled" json_add_boolean "running" "$is_running" json_add_string "status" "$status" json_add_boolean "in_uci" "0" json_close_object done json_close_array json_dump } # Get modules by category get_modules_by_category() { local category="$1" json_init json_add_array "modules" config_load secubox 2>/dev/null || true local processed_modules="" # List all module sections from UCI config local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1) # First pass: UCI-defined modules in this category for module in $module_sections; do local mod_category config_get mod_category "$module" category "other" if [ "$mod_category" = "$category" ]; then local name desc icon color package config version config_get name "$module" name "$module" config_get desc "$module" description "" config_get icon "$module" icon "box" config_get color "$module" color "#64748b" config_get package "$module" package "" config_get config "$module" config "" config_get version "$module" version "0.0.9" local is_installed=$(check_module_installed "$module") local is_enabled=$(check_module_enabled "$module") local is_running=$(check_module_running "$module") local status=$(get_module_status "$is_enabled" "$is_running") # Get real version from opkg if installed if [ "$is_installed" = "1" ] && [ -n "$package" ]; then local real_version=$(opkg list-installed "$package" 2>/dev/null | awk '{print $3}' | sed 's/-.*$//') [ -n "$real_version" ] && version="$real_version" fi json_add_object "" json_add_string "id" "$module" json_add_string "name" "$name" json_add_string "description" "$desc" json_add_string "icon" "$icon" json_add_string "color" "$color" json_add_string "version" "$version" json_add_string "package" "$package" json_add_boolean "installed" "$is_installed" json_add_boolean "enabled" "$is_enabled" json_add_boolean "running" "$is_running" json_add_string "status" "$status" json_add_boolean "in_uci" "1" json_close_object processed_modules="$processed_modules $module" fi done # Second pass: Auto-detected modules not in UCI detect_real_modules | while IFS=: read module_id package version; do # Skip if already processed from UCI echo "$processed_modules" | grep -q " $module_id " && continue # Auto-categorize local mod_category icon color name case "$package" in *crowdsec*|*guardian*|*auth*) mod_category="security"; icon="🛡️"; color="#22c55e" ;; *netdata*|*monitoring*) mod_category="monitoring"; icon="📊"; color="#00ab44" ;; *network*|*wireguard*|*bandwidth*|*traffic*|*cdn*) mod_category="network"; icon="🌐"; color="#3b82f6" ;; *system*|*hub*|*vhost*) mod_category="system"; icon="⚙️"; color="#6366f1" ;; *) mod_category="other"; icon="📦"; color="#64748b" ;; esac # Only include if matches requested category if [ "$mod_category" = "$category" ]; then # Generate nice name from package name=$(echo "$package" | sed 's/^luci-app-//' | sed 's/-dashboard$//' | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g') local is_installed="1" # Must be installed if detected local is_enabled=$(check_module_enabled "$module_id") local is_running=$(check_module_running "$module_id") local status=$(get_module_status "$is_enabled" "$is_running") json_add_object "" json_add_string "id" "$module_id" json_add_string "name" "$name" json_add_string "description" "Auto-detected module" json_add_string "icon" "$icon" json_add_string "color" "$color" json_add_string "version" "$version" json_add_string "package" "$package" json_add_string "category" "$mod_category" json_add_boolean "installed" "$is_installed" json_add_boolean "enabled" "$is_enabled" json_add_boolean "running" "$is_running" json_add_string "status" "$status" json_add_boolean "in_uci" "0" json_close_object fi done json_close_array json_dump } # Get single module info get_module_info() { local module="$1" config_load secubox local name desc category icon color package config version config_get name "$module" name "$module" config_get desc "$module" description "" config_get category "$module" category "other" config_get icon "$module" icon "box" config_get color "$module" color "#64748b" config_get package "$module" package "" config_get config "$module" config "" config_get version "$module" version "0.0.9" local is_installed=$(check_module_installed "$module") local is_enabled=$(check_module_enabled "$module") local is_running=$(check_module_running "$module") local status=$(get_module_status "$is_enabled" "$is_running") json_init json_add_string "id" "$module" json_add_string "name" "$name" json_add_string "description" "$desc" json_add_string "category" "$category" json_add_string "icon" "$icon" json_add_string "color" "$color" json_add_string "package" "$package" json_add_string "config" "$config" json_add_string "version" "$version" json_add_boolean "installed" "$is_installed" json_add_boolean "enabled" "$is_enabled" json_add_boolean "running" "$is_running" json_add_string "status" "$status" json_dump } # Start a module start_module() { local module="$1" local config config_load secubox config_get config "$module" config "" if [ -x "/etc/init.d/${config}" ]; then /etc/init.d/${config} start json_init json_add_boolean "success" 1 json_add_string "message" "Module started" json_dump else json_init json_add_boolean "success" 0 json_add_string "message" "Init script not found" json_dump fi } # Stop a module stop_module() { local module="$1" local config config_load secubox config_get config "$module" config "" if [ -x "/etc/init.d/${config}" ]; then /etc/init.d/${config} stop json_init json_add_boolean "success" 1 json_add_string "message" "Module stopped" json_dump else json_init json_add_boolean "success" 0 json_add_string "message" "Init script not found" json_dump fi } # Restart a module (DEPRECATED - use disable/enable instead) restart_module() { local module="$1" local config config_load secubox config_get config "$module" config "" if [ -x "/etc/init.d/${config}" ]; then /etc/init.d/${config} restart json_init json_add_boolean "success" 1 json_add_string "message" "Module restarted" json_dump else json_init json_add_boolean "success" 0 json_add_string "message" "Init script not found" json_dump fi } # Enable a module (NEW v0.3.1) enable_module() { local module="$1" # Set enabled flag in UCI (v0.3.1) # This only activates the module in configuration # Use start_module() to actually start the service uci set secubox.${module}.enabled='1' uci commit secubox json_init json_add_boolean "success" 1 json_add_string "message" "Module activé dans la configuration" json_add_string "note" "Utilisez 'Démarrer' pour lancer le service" json_dump } # Disable a module (NEW v0.3.1) disable_module() { local module="$1" # Set disabled flag in UCI (v0.3.1) # This only deactivates the module in configuration # Use stop_module() to actually stop the service uci set secubox.${module}.enabled='0' uci commit secubox json_init json_add_boolean "success" 1 json_add_string "message" "Module désactivé dans la configuration" json_add_string "note" "Utilisez 'Arrêter' pour stopper le service" json_dump } # Get health report get_health() { json_init json_add_array "checks" config_load secubox 2>/dev/null || true # List all module sections from UCI config local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1) # Check each installed module for module in $module_sections; do local is_installed=$(check_module_installed "$module") if [ "$is_installed" = "1" ]; then local is_running=$(check_module_running "$module") local name config_get name "$module" name "$module" local status="ok" local message="Running normally" if [ "$is_running" != "1" ]; then status="warning" message="Service not running" fi json_add_object "" json_add_string "module" "$module" json_add_string "name" "$name" json_add_string "status" "$status" json_add_string "message" "$message" json_close_object fi done json_close_array # Overall health local overall="healthy" # Could add more sophisticated health checks here json_add_string "overall" "$overall" json_add_int "timestamp" "$(date +%s)" json_dump } # Generate diagnostics bundle get_diagnostics() { json_init # System info json_add_object "system" json_add_string "hostname" "$(uci -q get system.@system[0].hostname)" json_add_string "model" "$(cat /tmp/sysinfo/model 2>/dev/null || echo 'Unknown')" json_add_string "openwrt_version" "$(cat /etc/openwrt_release | grep DISTRIB_RELEASE | cut -d= -f2 | tr -d \"\')" json_add_string "kernel" "$(uname -r)" json_add_int "uptime" "$(cat /proc/uptime | cut -d. -f1)" json_close_object # Modules status json_add_array "modules" config_load secubox 2>/dev/null || true local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1) for module in $module_sections; do local is_installed=$(check_module_installed "$module") local is_running=$(check_module_running "$module") json_add_object "" json_add_string "id" "$module" json_add_boolean "installed" "$is_installed" json_add_boolean "running" "$is_running" json_close_object done json_close_array # Network interfaces json_add_array "interfaces" for iface in $(ls /sys/class/net/); do local mac=$(cat /sys/class/net/$iface/address 2>/dev/null) local state=$(cat /sys/class/net/$iface/operstate 2>/dev/null) json_add_object "" json_add_string "name" "$iface" json_add_string "mac" "$mac" json_add_string "state" "$state" json_close_object done json_close_array json_add_int "generated" "$(date +%s)" json_dump } # Get system health metrics get_system_health() { json_init # CPU usage (based on load average) local load=$(cat /proc/loadavg | cut -d' ' -f1) local cpu_cores=$(grep -c processor /proc/cpuinfo) local cpu_pct=$(awk "BEGIN {printf \"%.0f\", ($load / $cpu_cores) * 100}") # Memory local mem_total=$(grep MemTotal /proc/meminfo | awk '{print $2}') local mem_avail=$(grep MemAvailable /proc/meminfo | awk '{print $2}') local mem_used=$((mem_total - mem_avail)) local mem_pct=$((mem_used * 100 / mem_total)) # Disk usage (root filesystem) local disk_info=$(df / | tail -1) local disk_total=$(echo "$disk_info" | awk '{print $2}') local disk_used=$(echo "$disk_info" | awk '{print $3}') local disk_pct=$(echo "$disk_info" | awk '{print $5}' | tr -d '%') # Network stats (sum all interfaces) local net_rx=0 local net_tx=0 for iface in $(ls /sys/class/net/ | grep -v lo); do if [ -f "/sys/class/net/$iface/statistics/rx_bytes" ]; then local rx=$(cat /sys/class/net/$iface/statistics/rx_bytes 2>/dev/null || echo 0) local tx=$(cat /sys/class/net/$iface/statistics/tx_bytes 2>/dev/null || echo 0) net_rx=$((net_rx + rx)) net_tx=$((net_tx + tx)) fi done # Uptime local uptime=$(cat /proc/uptime | cut -d. -f1) # Load averages local load_1min=$(cat /proc/loadavg | cut -d' ' -f1) local load_5min=$(cat /proc/loadavg | cut -d' ' -f2) local load_15min=$(cat /proc/loadavg | cut -d' ' -f3) json_add_object "cpu" json_add_int "percent" "$cpu_pct" json_add_int "cores" "$cpu_cores" json_add_string "load_1min" "$load_1min" json_add_string "load_5min" "$load_5min" json_add_string "load_15min" "$load_15min" json_close_object json_add_object "memory" json_add_int "percent" "$mem_pct" json_add_int "total_kb" "$mem_total" json_add_int "used_kb" "$mem_used" json_add_int "available_kb" "$mem_avail" json_close_object json_add_object "disk" json_add_int "percent" "$disk_pct" json_add_int "total_kb" "$disk_total" json_add_int "used_kb" "$disk_used" json_close_object json_add_object "network" json_add_int "rx_bytes" "$net_rx" json_add_int "tx_bytes" "$net_tx" json_close_object json_add_int "uptime" "$uptime" json_add_int "timestamp" "$(date +%s)" json_dump } # Get aggregated alerts from all modules get_alerts() { json_init json_add_array "alerts" local alert_count=0 # Check each installed module for alerts config_load secubox 2>/dev/null || true # List all module sections from UCI config local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1) for module in $module_sections; do local is_installed=$(check_module_installed "$module") if [ "$is_installed" = "1" ]; then # Check if module service is not running local is_running=$(check_module_running "$module") if [ "$is_running" != "1" ]; then local name config_get name "$module" name "$module" json_add_object "" json_add_string "module" "$module" json_add_string "message" "$name service is not running" json_add_string "severity" "warning" json_add_int "timestamp" "$(date +%s)" json_close_object alert_count=$((alert_count + 1)) fi fi done json_close_array json_add_int "count" "$alert_count" json_add_int "timestamp" "$(date +%s)" json_dump } # Execute quick actions quick_action() { local action="$1" json_init case "$action" in restart_rpcd) /etc/init.d/rpcd restart >/dev/null 2>&1 json_add_boolean "success" 1 json_add_string "message" "RPCD service restarted" ;; restart_uhttpd) /etc/init.d/uhttpd restart >/dev/null 2>&1 json_add_boolean "success" 1 json_add_string "message" "uHTTPd service restarted" ;; clear_cache) sync echo 3 > /proc/sys/vm/drop_caches 2>/dev/null json_add_boolean "success" 1 json_add_string "message" "System cache cleared" ;; backup_config) local backup_file="/tmp/backup-$(date +%Y%m%d-%H%M%S).tar.gz" sysupgrade -b "$backup_file" >/dev/null 2>&1 if [ -f "$backup_file" ]; then json_add_boolean "success" 1 json_add_string "message" "Configuration backup created" json_add_string "file" "$backup_file" else json_add_boolean "success" 0 json_add_string "message" "Backup failed" fi ;; restart_network) /etc/init.d/network restart >/dev/null 2>&1 json_add_boolean "success" 1 json_add_string "message" "Network services restarted" ;; restart_firewall) /etc/init.d/firewall restart >/dev/null 2>&1 json_add_boolean "success" 1 json_add_string "message" "Firewall restarted" ;; *) json_add_boolean "success" 0 json_add_string "message" "Unknown action: $action" ;; esac json_add_int "timestamp" "$(date +%s)" json_dump } # Get all dashboard data in one call get_dashboard_data() { json_init # Get status info local uptime=$(cat /proc/uptime | cut -d. -f1) local load=$(cat /proc/loadavg | cut -d' ' -f1-3) local mem_total=$(grep MemTotal /proc/meminfo | awk '{print $2}') local mem_free=$(grep MemAvailable /proc/meminfo | awk '{print $2}') local mem_used=$((mem_total - mem_free)) local mem_pct=$((mem_used * 100 / mem_total)) json_add_object "status" json_add_string "version" "$PKG_VERSION" json_add_string "hostname" "$(uci -q get system.@system[0].hostname || echo 'SecuBox')" json_add_int "uptime" "$uptime" json_add_string "load" "$load" json_add_int "memory_total" "$mem_total" json_add_int "memory_used" "$mem_used" json_add_int "memory_percent" "$mem_pct" json_close_object # Load config once for all modules config_load secubox 2>/dev/null || true # Get modules list json_add_array "modules" local total=0 installed=0 running=0 # List all module sections from UCI config local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1) for module in $module_sections; do local name desc category icon color package version config_get name "$module" name "$module" config_get desc "$module" description "" config_get category "$module" category "other" config_get icon "$module" icon "box" config_get color "$module" color "#64748b" config_get package "$module" package "" config_get version "$module" version "0.0.9" local is_installed=$(check_module_installed "$module") local is_enabled="0" local is_running="0" local status="disabled" total=$((total + 1)) if [ "$is_installed" = "1" ]; then is_enabled=$(check_module_enabled "$module") is_running=$(check_module_running "$module") status=$(get_module_status "$is_enabled" "$is_running") installed=$((installed + 1)) [ "$is_running" = "1" ] && running=$((running + 1)) # Get real version from package manager if installed if [ -n "$package" ]; then if [ -x "/usr/bin/apk" ]; then # OpenWrt 25.12+ uses apk local real_version=$(apk list --installed "$package" 2>/dev/null | head -1 | awk '{print $1}' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') [ -n "$real_version" ] && version="$real_version" elif [ -x "/bin/opkg" ] || [ -x "/usr/bin/opkg" ]; then # OpenWrt 24.10 and earlier uses opkg local real_version=$(opkg list-installed "$package" 2>/dev/null | awk '{print $3}' | sed 's/-.*$//') [ -n "$real_version" ] && version="$real_version" fi fi fi json_add_object "" json_add_string "id" "$module" json_add_string "name" "$name" json_add_string "description" "$desc" json_add_string "category" "$category" json_add_string "icon" "$icon" json_add_string "color" "$color" json_add_string "version" "$version" json_add_boolean "installed" "$is_installed" json_add_boolean "enabled" "$is_enabled" json_add_boolean "running" "$is_running" json_add_string "status" "$status" json_close_object done json_close_array json_add_object "counts" json_add_int "total" "$total" json_add_int "installed" "$installed" json_add_int "running" "$running" json_close_object # Get system health local cpu_cores=$(grep -c processor /proc/cpuinfo) local load_val=$(echo "$load" | cut -d' ' -f1) local cpu_pct=$(awk "BEGIN {printf \"%.0f\", ($load_val / $cpu_cores) * 100}" 2>/dev/null || echo "0") local disk_pct=$(df / | tail -1 | awk '{print $5}' | tr -d '%') json_add_object "health" json_add_int "cpu_percent" "$cpu_pct" json_add_int "memory_percent" "$mem_pct" json_add_int "disk_percent" "$disk_pct" json_close_object # Empty alerts array json_add_array "alerts" json_close_array json_add_int "timestamp" "$(date +%s)" json_dump } # Get theme setting get_theme() { local theme="dark" # Load secubox config if [ -f "/etc/config/secubox" ]; then config_load secubox config_get theme main theme "dark" fi json_init json_add_string "theme" "$theme" json_dump } # Persist theme setting set_theme() { local input theme="dark" read -r input [ -n "$input" ] && json_load "$input" json_get_var theme theme "dark" case "$theme" in dark|light|system|cyberpunk) ;; *) json_init json_add_boolean "success" 0 json_add_string "message" "Invalid theme" json_dump return 1 ;; esac uci -q set secubox.main.theme="$theme" uci -q commit secubox json_init json_add_boolean "success" 1 json_add_string "theme" "$theme" json_add_string "message" "Theme updated" json_dump } # Dismiss a specific alert dismiss_alert() { local alert_id="$1" # Store dismissed alerts in tmp file local dismissed_file="/tmp/secubox_dismissed_alerts" # Append alert_id to dismissed list echo "$alert_id" >> "$dismissed_file" json_init json_add_boolean "success" 1 json_add_string "message" "Alert dismissed" json_dump } # Clear all alerts clear_alerts() { # Clear dismissed alerts file local dismissed_file="/tmp/secubox_dismissed_alerts" rm -f "$dismissed_file" json_init json_add_boolean "success" 1 json_add_string "message" "All alerts cleared" json_dump } # Fix permissions (v0.3.1) fix_permissions() { local fix_script="/usr/libexec/secubox/fix-permissions.sh" if [ ! -x "$fix_script" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Fix script not found or not executable" json_dump return 1 fi # Run the fix script and capture output local output=$($fix_script 2>&1) local exit_code=$? json_init if [ $exit_code -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Permissions fixed successfully" else json_add_boolean "success" 0 json_add_string "message" "Failed to fix permissions" fi json_add_string "output" "$output" json_dump } # Main dispatcher first_run_status() { local timezone storage network_json mode_name mode_id storage_path storage_path=$(get_storage_path) local storage_ready=0 [ -d "$storage_path" ] && storage_ready=1 local password_state=0 if is_root_password_set; then password_state=1 fi timezone=$(uci -q get system.@system[0].timezone || echo "UTC") mode_id="unknown" mode_name="N/A" if command -v ubus >/dev/null 2>&1; then network_json=$(ubus call luci.network-modes status 2>/dev/null || printf '') if [ -n "$network_json" ]; then mode_id=$(jsonfilter -s "$network_json" -e '@.current_mode' 2>/dev/null || echo "$mode_id") mode_name=$(jsonfilter -s "$network_json" -e '@.mode_name' 2>/dev/null || echo "$mode_name") fi fi json_init json_add_boolean "password_set" "$password_state" json_add_string "timezone" "$timezone" json_add_string "storage_path" "$storage_path" json_add_boolean "storage_ready" "$storage_ready" json_add_string "network_mode" "$mode_id" json_add_string "network_mode_name" "$mode_name" json_add_array "recommended_modes" json_add_string "" "router" json_add_string "" "dmz" json_close_array json_dump } apply_first_run() { local input read input json_load "$input" json_get_var timezone timezone json_get_var storage_path storage_path json_get_var network_mode network_mode json_init json_add_array "messages" if [ -n "$timezone" ]; then uci set system.@system[0].timezone="$timezone" uci set system.@system[0].zonename="$timezone" uci commit system if [ -f "/usr/share/zoneinfo/$timezone" ]; then ln -sf "/usr/share/zoneinfo/$timezone" /etc/localtime fi json_add_string "" "Timezone updated" fi if [ -n "$storage_path" ]; then mkdir -p "$storage_path" chmod 755 "$storage_path" uci set secubox.main.storage_path="$storage_path" uci commit secubox json_add_string "" "Storage prepared at $storage_path" fi if [ -n "$network_mode" ]; then if command -v ubus >/dev/null 2>&1; then if ubus call luci.network-modes set_mode "{\"mode\":\"$network_mode\"}" >/dev/null 2>&1; then if ubus call luci.network-modes apply_mode >/dev/null 2>&1; then json_add_string "" "Network mode $network_mode applied" else json_add_string "" "Failed to apply network mode" fi else json_add_string "" "Mode $network_mode unsupported" fi else json_add_string "" "ubus unavailable; cannot change network mode" fi fi json_close_array json_add_boolean "success" 1 json_dump } list_apps() { ensure_directory "$PLUGIN_DIR" json_init json_add_array "apps" local manifest_file manifest id name runtime type version description state wizard_field category maturity seen_ids="" while IFS= read -r manifest_file; do [ -f "$manifest_file" ] || continue manifest=$(cat "$manifest_file") id=$(jsonfilter -s "$manifest" -e '@.id' 2>/dev/null) [ -n "$id" ] || continue case " $seen_ids " in *" $id "*) continue ;; esac if ! validate_manifest_json "$manifest"; then continue fi seen_ids="$seen_ids $id" name=$(jsonfilter -s "$manifest" -e '@.name' 2>/dev/null) type=$(jsonfilter -s "$manifest" -e '@.type' 2>/dev/null) runtime=$(jsonfilter -s "$manifest" -e '@.runtime' 2>/dev/null) [ -n "$type" ] || type="$runtime" version=$(jsonfilter -s "$manifest" -e '@.version' 2>/dev/null) description=$(jsonfilter -s "$manifest" -e '@.description' 2>/dev/null) category=$(jsonfilter -s "$manifest" -e '@.category' 2>/dev/null) maturity=$(jsonfilter -s "$manifest" -e '@.maturity' 2>/dev/null) state=$(packages_state "$manifest") wizard_field=$(jsonfilter -s "$manifest" -e '@.wizard.fields[0].id' 2>/dev/null) json_add_object json_add_string "id" "$id" json_add_string "name" "$name" json_add_string "runtime" "$runtime" json_add_string "type" "$type" json_add_string "version" "$version" json_add_string "description" "$description" json_add_string "category" "$category" json_add_string "maturity" "$maturity" json_add_string "state" "$state" json_add_boolean "has_wizard" "$([ -n "$wizard_field" ] && echo 1 || echo 0)" json_close_object done </dev/null) if [ -z "$app_id" ]; then json_init json_add_boolean "success" 0 json_add_string "error" "app_id required" json_dump return fi file=$(manifest_file_for_id "$app_id") if [ -z "$file" ] || [ ! -f "$file" ]; then json_init json_add_boolean "success" 0 json_add_string "error" "Manifest not found" json_dump return fi manifest=$(cat "$file") state=$(packages_state "$manifest") json_load "$manifest" json_add_string "state" "$state" json_dump } apply_app_wizard() { local input app_id file manifest config section fields field option value read input app_id=$(jsonfilter -s "$input" -e '@.app_id' 2>/dev/null) if [ -z "$app_id" ]; then json_init json_add_boolean "success" 0 json_add_string "error" "app_id required" json_dump return fi file=$(manifest_file_for_id "$app_id") if [ -z "$file" ] || [ ! -f "$file" ]; then json_init json_add_boolean "success" 0 json_add_string "error" "Manifest not found" json_dump return fi manifest=$(cat "$file") config=$(jsonfilter -s "$manifest" -e '@.wizard.uci.config' 2>/dev/null) section=$(jsonfilter -s "$manifest" -e '@.wizard.uci.section' 2>/dev/null) [ -n "$section" ] || section="main" if [ -z "$config" ]; then json_init json_add_boolean "success" 0 json_add_string "error" "Wizard missing UCI config" json_dump return fi fields=$(jsonfilter -s "$manifest" -e '@.wizard.fields[*].id' 2>/dev/null) for field in $fields; do option=$(jsonfilter -s "$manifest" -e "@.wizard.fields[@.id='$field'].uci_option" 2>/dev/null) [ -n "$option" ] || continue value=$(jsonfilter -s "$input" -e "@.values.$field" 2>/dev/null) [ -n "$value" ] || continue uci set ${config}.${section}.${option}="$value" done uci commit "$config" json_init json_add_boolean "success" 1 json_dump } list_profiles() { ensure_directory "$PROFILE_DIR" json_init json_add_array "profiles" local profile_file for profile_file in "$PROFILE_DIR"/*.json; do [ -f "$profile_file" ] || continue local profile_json id name description network_mode apps state profile_json=$(cat "$profile_file") id=$(jsonfilter -s "$profile_json" -e '@.id' 2>/dev/null) name=$(jsonfilter -s "$profile_json" -e '@.name' 2>/dev/null) description=$(jsonfilter -s "$profile_json" -e '@.description' 2>/dev/null) network_mode=$(jsonfilter -s "$profile_json" -e '@.network_mode' 2>/dev/null) apps=$(jsonfilter -s "$profile_json" -e '@.apps[*]' 2>/dev/null) state=$(packages_state "$profile_json") [ -n "$id" ] || continue json_add_object json_add_string "id" "$id" json_add_string "name" "$name" json_add_string "description" "$description" json_add_string "network_mode" "$network_mode" json_add_string "state" "$state" json_add_array "apps" for app in $apps; do json_add_string "" "$app" done json_close_array json_close_object done json_close_array json_dump } apply_profile() { local input profile_id profile_file profile_json network_mode packages apps backup_file notes="" read input json_load "$input" json_get_var profile_id profile_id json_cleanup [ -n "$profile_id" ] || { json_init; json_add_boolean "success" 0; json_add_string "error" "profile_id required"; json_dump; return; } profile_file="$PROFILE_DIR/$profile_id.json" if [ ! -f "$profile_file" ]; then json_init; json_add_boolean "success" 0; json_add_string "error" "Profile not found"; json_dump; return; fi profile_json=$(cat "$profile_file") ensure_directory "$PROFILE_BACKUP_DIR" local timestamp=$(date +%Y%m%d_%H%M%S) backup_file="$PROFILE_BACKUP_DIR/profile_${profile_id}_${timestamp}.tar.gz" tar -czf "$backup_file" /etc/config 2>/dev/null notes="${notes}Backup saved to $backup_file\n" uci set secubox.profile.last_backup="$backup_file" uci set secubox.profile.last_profile="$profile_id" uci commit secubox network_mode=$(jsonfilter -s "$profile_json" -e '@.network_mode' 2>/dev/null) if [ -n "$network_mode" ]; then notes="${notes}Switched network mode to $network_mode\n" apply_network_mode "$network_mode" fi packages=$(jsonfilter -s "$profile_json" -e '@.packages[*]' 2>/dev/null) for pkg in $packages; do install_package_if_missing "$pkg" notes="${notes}Ensured package $pkg\n" done apps=$(jsonfilter -s "$profile_json" -e '@.apps[*]' 2>/dev/null) for app in $apps; do if [ -x "$SECOBOX_APP" ]; then $SECOBOX_APP install "$app" >/dev/null 2>&1 && notes="${notes}Installed app $app\n" fi done json_load "$profile_json" local uci_configs="" if json_select uci >/dev/null 2>&1; then local entries json_get_keys entries for entry in $entries; do json_select "$entry" local cfg section option value json_get_var cfg config json_get_var section section json_get_var option option json_get_var value value section=${section:-main} if [ -n "$cfg" ] && [ -n "$option" ]; then uci set ${cfg}.${section}.${option}="$value" uci_configs="${uci_configs} $cfg" notes="${notes}Set ${cfg}.${section}.${option}=${value}\n" fi json_select .. done json_select .. fi json_cleanup local committed="" for cfg in $uci_configs; do case " $committed " in *" $cfg "*) continue;; esac uci commit "$cfg" committed="$committed $cfg" done json_init json_add_boolean "success" 1 json_add_array "messages" printf '%b' "$notes" | while IFS= read -r line; do [ -n "$line" ] && json_add_string "" "$line" done json_close_array json_add_string "backup" "$backup_file" json_dump } rollback_profile() { local latest_backup latest_backup=$(ls -t "$PROFILE_BACKUP_DIR"/profile_*.tar.gz 2>/dev/null | head -1) if [ -z "$latest_backup" ]; then json_init; json_add_boolean "success" 0; json_add_string "error" "No backups available"; json_dump; return; fi cd / tar -xzf "$latest_backup" 2>/dev/null /etc/init.d/network reload >/dev/null 2>&1 /etc/init.d/firewall reload >/dev/null 2>&1 /etc/init.d/dnsmasq reload >/dev/null 2>&1 json_init json_add_boolean "success" 1 json_add_string "message" "Restored $latest_backup" json_dump } # ============================================================================ # App Store Functions # ============================================================================ APPSTORE_JSON="/usr/share/secubox/.appstore/apps.json" # Get all apps from app store catalog get_appstore_apps() { if [ ! -f "$APPSTORE_JSON" ]; then json_init json_add_array "apps" json_close_array json_add_object "categories" json_close_object json_dump return fi local appstore_data=$(cat "$APPSTORE_JSON") local app_count=$(jsonfilter -s "$appstore_data" -e '@.apps[*]' | wc -l) json_init json_add_array "apps" local i=0 while [ $i -lt $app_count ]; do local app_id=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].id") local app_name=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].name") local app_version=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].version") local app_category=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].category") local app_description=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].description") local app_icon=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].icon") local app_status=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].status") local app_luci=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].luci_app") local app_notes=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].notes") # Check if app is installed local is_installed=0 if command -v opkg >/dev/null 2>&1; then opkg list-installed "$app_id" 2>/dev/null | grep -q "^$app_id " && is_installed=1 elif command -v apk >/dev/null 2>&1; then apk info -e "$app_id" >/dev/null 2>&1 && is_installed=1 fi json_add_object json_add_string "id" "$app_id" json_add_string "name" "$app_name" json_add_string "version" "$app_version" json_add_string "category" "$app_category" json_add_string "description" "$app_description" json_add_string "icon" "$app_icon" json_add_string "status" "$app_status" json_add_boolean "installed" "$is_installed" [ -n "$app_luci" ] && json_add_string "luci_app" "$app_luci" [ -n "$app_notes" ] && json_add_string "notes" "$app_notes" json_close_object i=$((i + 1)) done json_close_array # Add categories json_add_object "categories" local cat_keys=$(jsonfilter -s "$appstore_data" -e '@.categories' | jsonfilter -e '@[@]') for cat in security network iot media; do local cat_name=$(jsonfilter -s "$appstore_data" -e "@.categories.$cat.name") local cat_icon=$(jsonfilter -s "$appstore_data" -e "@.categories.$cat.icon") local cat_desc=$(jsonfilter -s "$appstore_data" -e "@.categories.$cat.description") if [ -n "$cat_name" ]; then json_add_object "$cat" json_add_string "name" "$cat_name" json_add_string "icon" "$cat_icon" json_add_string "description" "$cat_desc" json_close_object fi done json_close_object json_dump } # Get single app from app store get_appstore_app() { local input app_id read input json_load "$input" json_get_var app_id app_id json_cleanup if [ ! -f "$APPSTORE_JSON" ]; then json_init json_add_boolean "success" 0 json_add_string "error" "App store catalog not found" json_dump return fi local appstore_data=$(cat "$APPSTORE_JSON") local app_count=$(jsonfilter -s "$appstore_data" -e '@.apps[*]' | wc -l) local i=0 while [ $i -lt $app_count ]; do local current_id=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].id") if [ "$current_id" = "$app_id" ]; then # Found the app json_init json_add_string "id" "$current_id" json_add_string "name" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].name")" json_add_string "version" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].version")" json_add_string "category" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].category")" json_add_string "description" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].description")" json_add_string "icon" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].icon")" json_add_string "author" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].author")" json_add_string "license" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].license")" json_add_string "url" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].url")" json_add_string "status" "$(jsonfilter -s "$appstore_data" -e "@.apps[$i].status")" local app_luci=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].luci_app") [ -n "$app_luci" ] && [ "$app_luci" != "null" ] && json_add_string "luci_app" "$app_luci" local app_notes=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].notes") [ -n "$app_notes" ] && [ "$app_notes" != "null" ] && json_add_string "notes" "$app_notes" # Add dependencies array json_add_array "dependencies" local dep_count=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].dependencies[*]" | wc -l) local j=0 while [ $j -lt $dep_count ]; do local dep=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].dependencies[$j]") json_add_string "" "$dep" j=$((j + 1)) done json_close_array # Add tags array json_add_array "tags" local tag_count=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].tags[*]" | wc -l) j=0 while [ $j -lt $tag_count ]; do local tag=$(jsonfilter -s "$appstore_data" -e "@.apps[$i].tags[$j]") json_add_string "" "$tag" j=$((j + 1)) done json_close_array # Check installation status local is_installed=0 if command -v opkg >/dev/null 2>&1; then opkg list-installed "$app_id" 2>/dev/null | grep -q "^$app_id " && is_installed=1 elif command -v apk >/dev/null 2>&1; then apk info -e "$app_id" >/dev/null 2>&1 && is_installed=1 fi json_add_boolean "installed" "$is_installed" json_dump return fi i=$((i + 1)) done # App not found json_init json_add_boolean "success" 0 json_add_string "error" "App not found" json_dump } # Install app from app store install_appstore_app() { local input app_id read input json_load "$input" json_get_var app_id app_id json_cleanup [ -z "$app_id" ] && { json_init json_add_boolean "success" 0 json_add_string "error" "app_id required" json_dump return } # Update package lists if not done in this session if [ "$OPKG_UPDATED" -eq 0 ]; then if command -v opkg >/dev/null 2>&1; then opkg update >/dev/null 2>&1 elif command -v apk >/dev/null 2>&1; then apk update >/dev/null 2>&1 fi OPKG_UPDATED=1 fi # Install the package local install_output if command -v opkg >/dev/null 2>&1; then install_output=$(opkg install "$app_id" 2>&1) local ret=$? if [ $ret -eq 0 ]; then json_init json_add_boolean "success" 1 json_add_string "message" "App installed successfully" json_add_string "app_id" "$app_id" json_dump else json_init json_add_boolean "success" 0 json_add_string "error" "Installation failed" json_add_string "details" "$install_output" json_dump fi elif command -v apk >/dev/null 2>&1; then install_output=$(apk add "$app_id" 2>&1) local ret=$? if [ $ret -eq 0 ]; then json_init json_add_boolean "success" 1 json_add_string "message" "App installed successfully" json_add_string "app_id" "$app_id" json_dump else json_init json_add_boolean "success" 0 json_add_string "error" "Installation failed" json_add_string "details" "$install_output" json_dump fi else json_init json_add_boolean "success" 0 json_add_string "error" "No package manager found" json_dump fi } # Remove app from app store remove_appstore_app() { local input app_id read input json_load "$input" json_get_var app_id app_id json_cleanup [ -z "$app_id" ] && { json_init json_add_boolean "success" 0 json_add_string "error" "app_id required" json_dump return } # Remove the package local remove_output if command -v opkg >/dev/null 2>&1; then remove_output=$(opkg remove "$app_id" 2>&1) local ret=$? if [ $ret -eq 0 ]; then json_init json_add_boolean "success" 1 json_add_string "message" "App removed successfully" json_add_string "app_id" "$app_id" json_dump else json_init json_add_boolean "success" 0 json_add_string "error" "Removal failed" json_add_string "details" "$remove_output" json_dump fi elif command -v apk >/dev/null 2>&1; then remove_output=$(apk del "$app_id" 2>&1) local ret=$? if [ $ret -eq 0 ]; then json_init json_add_boolean "success" 1 json_add_string "message" "App removed successfully" json_add_string "app_id" "$app_id" json_dump else json_init json_add_boolean "success" 0 json_add_string "error" "Removal failed" json_add_string "details" "$remove_output" json_dump fi else json_init json_add_boolean "success" 0 json_add_string "error" "No package manager found" json_dump fi } case "$1" in list) json_init json_add_object "status" json_close_object json_add_object "modules" json_close_object json_add_object "modules_by_category" json_add_string "category" "string" json_close_object json_add_object "module_info" json_add_string "module" "string" json_close_object json_add_object "start_module" json_add_string "module" "string" json_close_object json_add_object "stop_module" json_add_string "module" "string" json_close_object json_add_object "restart_module" json_add_string "module" "string" json_close_object json_add_object "enable_module" json_add_string "module" "string" json_close_object json_add_object "disable_module" json_add_string "module" "string" json_close_object json_add_object "check_module_enabled" json_add_string "module" "string" json_close_object json_add_object "health" json_close_object json_add_object "diagnostics" json_close_object json_add_object "get_system_health" json_close_object json_add_object "get_alerts" json_close_object json_add_object "quick_action" json_add_string "action" "string" json_close_object json_add_object "get_dashboard_data" json_close_object json_add_object "get_theme" json_close_object json_add_object "set_theme" json_add_string "theme" "string" json_close_object json_add_object "dismiss_alert" json_add_string "alert_id" "string" json_close_object json_add_object "clear_alerts" json_close_object json_add_object "fix_permissions" json_close_object json_add_object "first_run_status" json_close_object json_add_object "apply_first_run" json_close_object json_add_object "list_apps" json_close_object json_add_object "get_app_manifest" json_add_string "app_id" "string" json_close_object json_add_object "apply_app_wizard" json_add_string "app_id" "string" json_close_object json_add_object "list_profiles" json_close_object json_add_object "apply_profile" json_add_string "profile_id" "string" json_close_object json_add_object "rollback_profile" json_close_object json_add_object "get_appstore_apps" json_close_object json_add_object "get_appstore_app" json_add_string "app_id" "string" json_close_object json_add_object "install_appstore_app" json_add_string "app_id" "string" json_close_object json_add_object "remove_appstore_app" json_add_string "app_id" "string" json_close_object json_add_object "first_run_status" json_close_object json_add_object "apply_first_run" json_close_object json_add_object "list_apps" json_close_object json_add_object "get_app_manifest" json_add_string "app_id" "string" json_close_object json_add_object "apply_app_wizard" json_add_string "app_id" "string" json_close_object json_dump ;; call) case "$2" in status) get_status ;; modules) get_modules ;; modules_by_category) read -r input json_load "$input" json_get_var category category "security" get_modules_by_category "$category" ;; module_info) read -r input json_load "$input" json_get_var module module "" get_module_info "$module" ;; start_module) read -r input json_load "$input" json_get_var module module "" start_module "$module" ;; stop_module) read -r input json_load "$input" json_get_var module module "" stop_module "$module" ;; restart_module) read -r input json_load "$input" json_get_var module module "" restart_module "$module" ;; enable_module) read -r input json_load "$input" json_get_var module module "" enable_module "$module" ;; disable_module) read -r input json_load "$input" json_get_var module module "" disable_module "$module" ;; check_module_enabled) read -r input json_load "$input" json_get_var module module "" local enabled=$(check_module_enabled "$module") json_init json_add_boolean "enabled" "$enabled" json_dump ;; health) get_health ;; diagnostics) get_diagnostics ;; get_system_health) get_system_health ;; get_alerts) get_alerts ;; quick_action) read -r input json_load "$input" json_get_var action action "" quick_action "$action" ;; get_dashboard_data) get_dashboard_data ;; get_theme) get_theme ;; set_theme) set_theme ;; dismiss_alert) read -r input json_load "$input" json_get_var alert_id alert_id "" dismiss_alert "$alert_id" ;; clear_alerts) clear_alerts ;; fix_permissions) fix_permissions ;; first_run_status) first_run_status ;; apply_first_run) apply_first_run ;; list_apps) list_apps ;; get_app_manifest) get_app_manifest ;; apply_app_wizard) apply_app_wizard ;; list_profiles) list_profiles ;; apply_profile) apply_profile ;; rollback_profile) rollback_profile ;; get_appstore_apps) get_appstore_apps ;; get_appstore_app) get_appstore_app ;; install_appstore_app) install_appstore_app ;; remove_appstore_app) remove_appstore_app ;; *) echo '{"error":"Unknown method"}' ;; esac ;; *) echo '{"error":"Unknown command"}' ;; esac