#!/bin/sh # System Hub RPCD Backend # Central system control and monitoring # Version: 0.1.1 . /lib/functions.sh . /usr/share/libubox/jshn.sh get_pkg_version() { local ctrl="/usr/lib/opkg/status" local pkg="luci-app-system-hub" if [ -f "$ctrl" ]; then awk -F': ' '/Package: '"$pkg"'/,/^Package/ { if ($1 == "Version") { print $2; exit } }' "$ctrl" else echo "unknown" fi } PKG_VERSION="$(get_pkg_version)" DIAG_DIR="/tmp/system-hub/diagnostics" mkdir -p "$DIAG_DIR" safe_filename() { local name="$1" name="${name##*/}" name="${name//../}" echo "$name" } # Get comprehensive system status status() { json_init # Basic info local hostname=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "unknown") local uptime=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0) local model=$(cat /tmp/sysinfo/model 2>/dev/null || cat /proc/device-tree/model 2>/dev/null | tr -d '\0' || echo "Unknown") json_add_string "version" "$PKG_VERSION" json_add_string "hostname" "$hostname" json_add_string "model" "$model" json_add_int "uptime" "$uptime" # Health metrics local cpu_load=$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo "0") # Memory local mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_free=$(awk '/MemFree/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_available=$(awk '/MemAvailable/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_used=$((mem_total - mem_available)) local mem_percent=0 if [ "$mem_total" -gt 0 ]; then mem_percent=$(( (mem_used * 100) / mem_total )) fi json_add_object "health" json_add_string "cpu_load" "$cpu_load" json_add_int "mem_total_kb" "$mem_total" json_add_int "mem_used_kb" "$mem_used" json_add_int "mem_percent" "$mem_percent" json_close_object # Storage local disk_root=$(df / | awk 'NR==2 {gsub("%","",$5); print $5}' 2>/dev/null || echo 0) json_add_int "disk_percent" "$disk_root" # Service count local service_count=$(ls /etc/init.d/ 2>/dev/null | wc -l) json_add_int "service_count" "$service_count" json_dump } # Get detailed system information get_system_info() { json_init # Hostname local hostname=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "unknown") json_add_string "version" "$PKG_VERSION" json_add_string "hostname" "$hostname" # Model local model=$(cat /tmp/sysinfo/model 2>/dev/null || cat /proc/device-tree/model 2>/dev/null | tr -d '\0' || echo "Unknown") json_add_string "model" "$model" # Board name local board=$(cat /tmp/sysinfo/board_name 2>/dev/null || echo "unknown") json_add_string "board" "$board" # OpenWrt version local openwrt_version=$(cat /etc/openwrt_release 2>/dev/null | grep DISTRIB_DESCRIPTION | cut -d"'" -f2 || echo "Unknown") json_add_string "openwrt_version" "$openwrt_version" # Kernel version local kernel=$(uname -r 2>/dev/null || echo "unknown") json_add_string "kernel" "$kernel" # Architecture local arch=$(uname -m 2>/dev/null || echo "unknown") json_add_string "architecture" "$arch" # Uptime local uptime_sec=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0) json_add_int "uptime_seconds" "$uptime_sec" # Uptime formatted local days=$((uptime_sec / 86400)) local hours=$(((uptime_sec % 86400) / 3600)) local mins=$(((uptime_sec % 3600) / 60)) json_add_string "uptime_formatted" "${days}d ${hours}h ${mins}m" # Local time local localtime=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown") json_add_string "local_time" "$localtime" # Load averages local load=$(cat /proc/loadavg 2>/dev/null || echo "0 0 0") local load1=$(echo $load | awk '{print $1}') local load5=$(echo $load | awk '{print $2}') local load15=$(echo $load | awk '{print $3}') json_add_array "load" json_add_string "" "$load1" json_add_string "" "$load5" json_add_string "" "$load15" json_close_array # Quick Status Indicators json_add_object "status" # Internet connectivity (ping 8.8.8.8) local internet_ok=0 if ping -c 1 -W 2 8.8.8.8 >/dev/null 2>&1; then internet_ok=1 fi json_add_boolean "internet" "$internet_ok" # DNS resolution (resolve cloudflare.com) local dns_ok=0 if nslookup cloudflare.com >/dev/null 2>&1 || host cloudflare.com >/dev/null 2>&1; then dns_ok=1 fi json_add_boolean "dns" "$dns_ok" # NTP sync status local ntp_ok=0 if [ -f /var/state/ntpd ] || pidof ntpd >/dev/null 2>&1 || pidof chronyd >/dev/null 2>&1; then # Check if time seems reasonable (after year 2020) local year=$(date +%Y) [ "$year" -ge 2020 ] && ntp_ok=1 fi json_add_boolean "ntp" "$ntp_ok" # Firewall status (check if nftables or iptables rules exist) local firewall_ok=0 local firewall_rules=0 # Count nftables rules (chains with rules) firewall_rules=$(nft list ruleset 2>/dev/null | grep -cE "^\s+(type|counter|accept|drop|reject|jump|goto)" || echo 0) if [ "$firewall_rules" -gt 0 ]; then firewall_ok=1 else # Fallback to iptables firewall_rules=$(iptables -L -n 2>/dev/null | grep -cE "^(ACCEPT|DROP|REJECT)" || echo 0) [ "$firewall_rules" -gt 0 ] && firewall_ok=1 fi json_add_boolean "firewall" "$firewall_ok" json_add_int "firewall_rules" "$firewall_rules" json_close_object json_dump } # Get system health metrics get_health() { json_init # CPU usage calculation local cpu_cores=$(grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo 1) local load=$(cat /proc/loadavg 2>/dev/null || echo "0 0 0") local load1=$(echo $load | awk '{print $1}') local load5=$(echo $load | awk '{print $2}') local load15=$(echo $load | awk '{print $3}') # Process count (v0.3.2) local processes_running=$(echo $load | awk '{split($4,a,"/"); print a[1]}') local processes_total=$(echo $load | awk '{split($4,a,"/"); print a[2]}') # Calculate CPU usage percentage (load / cores * 100) local cpu_usage=$(awk -v load="$load1" -v cores="$cpu_cores" 'BEGIN { printf "%.0f", (load / cores) * 100 }') [ "$cpu_usage" -gt 100 ] && cpu_usage=100 # CPU status local cpu_status="ok" [ "$cpu_usage" -ge 80 ] && cpu_status="warning" [ "$cpu_usage" -ge 95 ] && cpu_status="critical" json_add_object "cpu" json_add_int "usage" "$cpu_usage" json_add_string "status" "$cpu_status" json_add_string "load_1m" "$load1" json_add_string "load_5m" "$load5" json_add_string "load_15m" "$load15" json_add_int "cores" "$cpu_cores" json_add_int "processes_running" "${processes_running:-0}" json_add_int "processes_total" "${processes_total:-0}" json_close_object # Memory (v0.3.2: added swap support) local mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_free=$(awk '/MemFree/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_available=$(awk '/MemAvailable/ {print $2}' /proc/meminfo 2>/dev/null || echo $mem_free) local mem_buffers=$(awk '/Buffers/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_cached=$(awk '/^Cached/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_used=$((mem_total - mem_available)) # Swap info (v0.3.2) local swap_total=$(awk '/SwapTotal/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local swap_free=$(awk '/SwapFree/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local swap_used=$((swap_total - swap_free)) local swap_usage=0 if [ "$swap_total" -gt 0 ]; then swap_usage=$(( (swap_used * 100) / swap_total )) fi local mem_usage=0 if [ "$mem_total" -gt 0 ]; then mem_usage=$(( (mem_used * 100) / mem_total )) fi # Memory status local mem_status="ok" [ "$mem_usage" -ge 80 ] && mem_status="warning" [ "$mem_usage" -ge 95 ] && mem_status="critical" json_add_object "memory" json_add_int "total_kb" "$mem_total" json_add_int "free_kb" "$mem_free" json_add_int "available_kb" "$mem_available" json_add_int "used_kb" "$mem_used" json_add_int "buffers_kb" "$mem_buffers" json_add_int "cached_kb" "$mem_cached" json_add_int "usage" "$mem_usage" json_add_string "status" "$mem_status" json_add_int "swap_total_kb" "$swap_total" json_add_int "swap_used_kb" "$swap_used" json_add_int "swap_usage" "$swap_usage" json_close_object # Disk (root filesystem) local disk_total=$(df / | awk 'NR==2 {print $2}') local disk_used=$(df / | awk 'NR==2 {print $3}') local disk_usage=$(df / | awk 'NR==2 {gsub("%","",$5); print $5}' 2>/dev/null || echo 0) # Disk status local disk_status="ok" [ "$disk_usage" -ge 80 ] && disk_status="warning" [ "$disk_usage" -ge 95 ] && disk_status="critical" json_add_object "disk" json_add_int "total_kb" "$disk_total" json_add_int "used_kb" "$disk_used" json_add_int "usage" "$disk_usage" json_add_string "status" "$disk_status" json_close_object # Temperature local temp_value=0 local temp_status="ok" for zone in /sys/class/thermal/thermal_zone*/temp; do if [ -f "$zone" ]; then local temp=$(cat "$zone" 2>/dev/null || echo 0) local temp_c=$((temp / 1000)) # Use the highest temperature [ "$temp_c" -gt "$temp_value" ] && temp_value="$temp_c" fi done [ "$temp_value" -ge 70 ] && temp_status="warning" [ "$temp_value" -ge 85 ] && temp_status="critical" json_add_object "temperature" json_add_int "value" "$temp_value" json_add_string "status" "$temp_status" json_close_object # Network (WAN connectivity + throughput v0.3.2) local wan_up=0 local wan_status="error" if ping -c 1 -W 2 8.8.8.8 >/dev/null 2>&1; then wan_up=1 wan_status="ok" fi # Network throughput (v0.3.2) - get total RX/TX from all interfaces local rx_bytes=0 local tx_bytes=0 for iface in /sys/class/net/*; do [ -d "$iface" ] || continue local ifname=$(basename "$iface") # Skip loopback and virtual interfaces case "$ifname" in lo|br-*|wlan*-*) continue ;; esac local rx=$(cat "$iface/statistics/rx_bytes" 2>/dev/null || echo 0) local tx=$(cat "$iface/statistics/tx_bytes" 2>/dev/null || echo 0) rx_bytes=$((rx_bytes + rx)) tx_bytes=$((tx_bytes + tx)) done json_add_object "network" json_add_boolean "wan_up" "$wan_up" json_add_string "status" "$wan_status" json_add_int "rx_bytes" "$rx_bytes" json_add_int "tx_bytes" "$tx_bytes" json_close_object # Services local running_count=0 local failed_count=0 for service in /etc/init.d/*; do [ -x "$service" ] || continue local name=$(basename "$service") case "$name" in boot|done|functions|rc.*|sysctl|umount) continue ;; esac if [ -f "/etc/rc.d/S"*"$name" ]; then if "$service" running >/dev/null 2>&1; then running_count=$((running_count + 1)) else failed_count=$((failed_count + 1)) fi fi done json_add_object "services" json_add_int "running" "$running_count" json_add_int "failed" "$failed_count" json_close_object # Calculate overall health score local score=100 # CPU impact (max -30) if [ "$cpu_usage" -ge 95 ]; then score=$((score - 30)) elif [ "$cpu_usage" -ge 80 ]; then score=$((score - 15)) elif [ "$cpu_usage" -ge 60 ]; then score=$((score - 5)) fi # Memory impact (max -25) if [ "$mem_usage" -ge 95 ]; then score=$((score - 25)) elif [ "$mem_usage" -ge 80 ]; then score=$((score - 12)) elif [ "$mem_usage" -ge 60 ]; then score=$((score - 5)) fi # Disk impact (max -20) if [ "$disk_usage" -ge 95 ]; then score=$((score - 20)) elif [ "$disk_usage" -ge 80 ]; then score=$((score - 10)) fi # Temperature impact (max -15) if [ "$temp_value" -ge 85 ]; then score=$((score - 15)) elif [ "$temp_value" -ge 70 ]; then score=$((score - 7)) fi # Network impact (max -10) [ "$wan_up" -eq 0 ] && score=$((score - 10)) # Services impact (max -10) [ "$failed_count" -gt 0 ] && score=$((score - 10)) json_add_int "score" "$score" json_add_string "timestamp" "$(date '+%Y-%m-%d %H:%M:%S')" # Recommendations json_add_array "recommendations" [ "$cpu_usage" -ge 80 ] && json_add_string "" "CPU usage is high ($cpu_usage%). Consider closing unnecessary services." [ "$mem_usage" -ge 80 ] && json_add_string "" "Memory usage is high ($mem_usage%). Check for memory leaks." [ "$disk_usage" -ge 80 ] && json_add_string "" "Disk usage is high ($disk_usage%). Clean up old files or logs." [ "$temp_value" -ge 70 ] && json_add_string "" "Temperature is elevated (${temp_value}°C). Ensure proper ventilation." [ "$wan_up" -eq 0 ] && json_add_string "" "WAN connection is down. Check network connectivity." [ "$failed_count" -gt 0 ] && json_add_string "" "$failed_count service(s) enabled but not running. Check service status." json_close_array json_dump } # List all services with status list_services() { json_init json_add_array "services" for service in /etc/init.d/*; do [ -x "$service" ] || continue local name=$(basename "$service") # Skip special scripts case "$name" in boot|done|functions|rc.*|sysctl|umount) continue ;; esac # Check if enabled local enabled=0 [ -f "/etc/rc.d/S"*"$name" ] && enabled=1 # Check if running local running=0 "$service" running >/dev/null 2>&1 && running=1 json_add_object "" json_add_string "name" "$name" json_add_boolean "enabled" "$enabled" json_add_boolean "running" "$running" json_close_object done json_close_array json_dump } # Perform service action service_action() { read -r input json_load "$input" local service action json_get_var service service json_get_var action action json_cleanup if [ -z "$service" ] || [ -z "$action" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Service and action are required" json_dump return 1 fi local service_script="/etc/init.d/$service" if [ ! -x "$service_script" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Service not found: $service" json_dump return 1 fi local result=0 case "$action" in start|stop|restart) "$service_script" "$action" >/dev/null 2>&1 result=$? ;; enable) "$service_script" enable >/dev/null 2>&1 result=$? ;; disable) "$service_script" disable >/dev/null 2>&1 result=$? ;; *) json_init json_add_boolean "success" 0 json_add_string "message" "Invalid action: $action" json_dump return 1 ;; esac json_init if [ "$result" -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Service $service $action successful" else json_add_boolean "success" 0 json_add_string "message" "Service $service $action failed" fi json_dump } # Get system logs get_logs() { read -r input json_load "$input" local lines filter json_get_var lines lines "100" json_get_var filter filter "" json_cleanup # Get logs into a temporary file to avoid subshell issues local tmpfile="/tmp/syslog-$$" if [ -n "$filter" ]; then logread | tail -n "$lines" | grep -i "$filter" > "$tmpfile" else logread | tail -n "$lines" > "$tmpfile" fi json_init json_add_array "logs" # Read from file line by line while IFS= read -r line; do json_add_string "" "$line" done < "$tmpfile" json_close_array # Cleanup rm -f "$tmpfile" json_dump } # Create backup backup_config() { local backup_file="/tmp/backup-$(date +%Y%m%d-%H%M%S).tar.gz" # Create backup sysupgrade -b "$backup_file" >/dev/null 2>&1 if [ ! -f "$backup_file" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Backup creation failed" json_dump return 1 fi # Encode to base64 local backup_data=$(base64 < "$backup_file" 2>/dev/null) local backup_size=$(stat -c%s "$backup_file" 2>/dev/null || echo 0) # Cleanup rm -f "$backup_file" json_init json_add_boolean "success" 1 json_add_string "data" "$backup_data" json_add_int "size" "$backup_size" json_add_string "filename" "backup-$(date +%Y%m%d-%H%M%S).tar.gz" json_dump } # Restore configuration restore_config() { read -r input json_load "$input" local backup_data file_name json_get_var backup_data data json_get_var file_name file_name json_cleanup if [ -z "$backup_data" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "No backup data provided" json_dump return 1 fi local clean_name="$(basename "${file_name:-backup.tar.gz}")" clean_name="$(echo "$clean_name" | tr -c 'A-Za-z0-9._-' '_')" [ -z "$clean_name" ] && clean_name="backup.tar.gz" local timestamp="$(date +%s)" local backup_file="/tmp/${timestamp}-${clean_name}" # Decode base64 if ! echo "$backup_data" | base64 -d > "$backup_file" 2>/dev/null; then rm -f "$backup_file" json_init json_add_boolean "success" 0 json_add_string "message" "Failed to decode backup data" json_dump return 1 fi if [ ! -s "$backup_file" ]; then rm -f "$backup_file" json_init json_add_boolean "success" 0 json_add_string "message" "Decoded backup archive is empty" json_dump return 1 fi # Restore sysupgrade -r "$backup_file" >/dev/null 2>&1 local result=$? # Cleanup rm -f "$backup_file" json_init if [ "$result" -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Configuration restored successfully. Reboot required." else json_add_boolean "success" 0 json_add_string "message" "Configuration restore failed" fi json_dump } # Reboot system reboot_system() { json_init json_add_boolean "success" 1 json_add_string "message" "System reboot initiated" json_dump # Reboot after 3 seconds ( sleep 3 && reboot ) & } # Get backup schedule from crontab get_backup_schedule() { json_init local cron_line="" local enabled=0 local frequency="weekly" local hour="03" local minute="00" local day_of_week="0" local day_of_month="1" local last_backup="" local next_backup="" # Check for existing backup cron job if [ -f /etc/crontabs/root ]; then cron_line=$(grep "sysupgrade -b" /etc/crontabs/root 2>/dev/null | head -n 1) fi if [ -n "$cron_line" ]; then enabled=1 # Parse cron schedule: minute hour day_of_month month day_of_week command minute=$(echo "$cron_line" | awk '{print $1}') hour=$(echo "$cron_line" | awk '{print $2}') local dom=$(echo "$cron_line" | awk '{print $3}') local dow=$(echo "$cron_line" | awk '{print $5}') # Determine frequency from cron pattern if [ "$dom" != "*" ] && [ "$dow" = "*" ]; then frequency="monthly" day_of_month="$dom" elif [ "$dow" != "*" ]; then frequency="weekly" day_of_week="$dow" else frequency="daily" fi fi # Find last backup file local last_file=$(ls -t /tmp/backup-*.tar.gz 2>/dev/null | head -n 1) if [ -n "$last_file" ] && [ -f "$last_file" ]; then last_backup=$(date -r "$last_file" '+%Y-%m-%d %H:%M' 2>/dev/null || echo "") fi # Calculate next backup time (simplified) if [ "$enabled" = "1" ]; then local now_hour=$(date +%H) local now_min=$(date +%M) local target_time="${hour}:${minute}" case "$frequency" in daily) if [ "$now_hour$now_min" -lt "${hour}${minute}" ]; then next_backup="Today at $target_time" else next_backup="Tomorrow at $target_time" fi ;; weekly) local dow_names="Sun Mon Tue Wed Thu Fri Sat" local dow_name=$(echo "$dow_names" | cut -d' ' -f$((day_of_week + 1))) next_backup="$dow_name at $target_time" ;; monthly) next_backup="Day $day_of_month at $target_time" ;; esac fi json_add_boolean "enabled" "$enabled" json_add_string "frequency" "$frequency" json_add_string "hour" "$hour" json_add_string "minute" "$minute" json_add_string "day_of_week" "$day_of_week" json_add_string "day_of_month" "$day_of_month" json_add_string "last_backup" "$last_backup" json_add_string "next_backup" "$next_backup" json_dump } # Set backup schedule in crontab set_backup_schedule() { read -r input json_load "$input" local enabled frequency hour minute day_of_week day_of_month json_get_var enabled enabled "0" json_get_var frequency frequency "weekly" json_get_var hour hour "03" json_get_var minute minute "00" json_get_var day_of_week day_of_week "0" json_get_var day_of_month day_of_month "1" json_cleanup # Validate inputs hour=$(printf "%02d" "$((${hour:-3} % 24))") minute=$(printf "%02d" "$((${minute:-0} % 60))") day_of_week=$(printf "%d" "$((${day_of_week:-0} % 7))") day_of_month=$(printf "%d" "$((${day_of_month:-1}))") [ "$day_of_month" -lt 1 ] && day_of_month=1 [ "$day_of_month" -gt 28 ] && day_of_month=28 # Backup destination local backup_dir="/root/backups" local backup_cmd="mkdir -p $backup_dir && sysupgrade -b $backup_dir/backup-\$(date +%Y%m%d-%H%M%S).tar.gz && find $backup_dir -name 'backup-*.tar.gz' -mtime +30 -delete" # Remove existing backup cron entries if [ -f /etc/crontabs/root ]; then grep -v "sysupgrade -b" /etc/crontabs/root > /tmp/crontab.tmp 2>/dev/null || touch /tmp/crontab.tmp mv /tmp/crontab.tmp /etc/crontabs/root else touch /etc/crontabs/root fi # Add new cron entry if enabled if [ "$enabled" = "1" ]; then local cron_schedule="" case "$frequency" in daily) cron_schedule="$minute $hour * * *" ;; weekly) cron_schedule="$minute $hour * * $day_of_week" ;; monthly) cron_schedule="$minute $hour $day_of_month * *" ;; esac echo "$cron_schedule $backup_cmd" >> /etc/crontabs/root fi # Reload cron /etc/init.d/cron restart >/dev/null 2>&1 || true json_init json_add_boolean "success" 1 json_add_string "message" "Backup schedule updated" json_dump } # Get storage details get_storage() { json_init json_add_array "storage" df -h | tail -n +2 | while read filesystem size used avail percent mountpoint; do local percent_num=$(echo $percent | tr -d '%') json_add_object "" json_add_string "filesystem" "$filesystem" json_add_string "size" "$size" json_add_string "used" "$used" json_add_string "available" "$avail" json_add_int "percent" "$percent_num" json_add_string "mountpoint" "$mountpoint" json_close_object done json_close_array json_dump } # Collect diagnostic data into archive collect_diagnostics() { read input [ -n "$input" ] && json_load "$input" local include_logs include_config include_network anonymize profile json_get_var profile profile 2>/dev/null json_get_var include_logs include_logs 2>/dev/null json_get_var include_config include_config 2>/dev/null json_get_var include_network include_network 2>/dev/null json_get_var anonymize anonymize 2>/dev/null # If profile specified, load its config and override flags if [ -n "$profile" ] && [ "$profile" != "manual" ]; then case "$profile" in network-issues) include_logs=1 include_config=1 include_network=1 anonymize=0 ;; performance-problems) include_logs=1 include_config=0 include_network=0 anonymize=0 ;; security-audit) include_logs=1 include_config=1 include_network=1 anonymize=1 ;; wifi-problems) include_logs=1 include_config=1 include_network=1 anonymize=0 ;; full-diagnostic) include_logs=1 include_config=1 include_network=1 anonymize=1 ;; esac fi [ -z "$include_logs" ] && include_logs=1 [ -z "$include_config" ] && include_config=1 [ -z "$include_network" ] && include_network=1 [ -z "$anonymize" ] && anonymize=0 [ -z "$profile" ] && profile="manual" local timestamp="$(date +%Y%m%d-%H%M%S)" local workdir="$DIAG_DIR/work-$timestamp" mkdir -p "$workdir" # System info { echo "=== System Information ===" echo "Profile: $profile" echo "Generated: $(date)" echo uname -a echo echo "--- CPU ---" cat /proc/cpuinfo echo echo "--- Memory ---" cat /proc/meminfo echo echo "--- Disk ---" df -h echo echo "--- Uptime ---" uptime } >"$workdir/sysinfo.txt" # Logs if [ "$include_logs" = "1" ]; then logread 2>/dev/null >"$workdir/system.log" || true dmesg 2>/dev/null >"$workdir/kernel.log" || true fi # Configs if [ "$include_config" = "1" ]; then mkdir -p "$workdir/config" sysupgrade -b "$workdir/config/backup.tar.gz" >/dev/null 2>&1 || true for conf in /etc/config/*; do [ -f "$conf" ] || continue local dest="$workdir/config/$(basename "$conf")" if [ "$anonymize" = "1" ]; then grep -viE "(password|passwd|secret|key|token|psk|ipaddr|macaddr)" "$conf" >"$dest" 2>/dev/null || cp "$conf" "$dest" else cp "$conf" "$dest" fi done fi # Network info if [ "$include_network" = "1" ]; then { echo "=== Interfaces ===" ip addr echo echo "=== Routes ===" ip route echo echo "=== Firewall ===" iptables -L -n -v 2>/dev/null || true echo echo "=== DNS ===" cat /etc/resolv.conf 2>/dev/null echo echo "=== Connectivity (8.8.8.8) ===" ping -c 3 -W 2 8.8.8.8 2>&1 echo echo "=== Connectivity (1.1.1.1) ===" ping -c 3 -W 2 1.1.1.1 2>&1 } >"$workdir/network.txt" fi local archive_name="diagnostics-$(hostname)-${profile}-$timestamp.tar.gz" local archive_path="$DIAG_DIR/$archive_name" tar -czf "$archive_path" -C "$workdir" . >/dev/null 2>&1 || { rm -rf "$workdir" json_init json_add_boolean "success" 0 json_add_string "error" "archive_failed" json_dump return } rm -rf "$workdir" local size=$(stat -c%s "$archive_path" 2>/dev/null || stat -f%z "$archive_path") local created=$(date -r "$archive_path" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S') json_init json_add_boolean "success" 1 json_add_string "file" "$archive_name" json_add_int "size" "$size" json_add_string "created_at" "$created" json_dump } list_diagnostics() { json_init json_add_array "archives" for file in $(ls -t "$DIAG_DIR"/diagnostics-*.tar.gz 2>/dev/null | head -n 50); do [ -f "$file" ] || continue local size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file") local created=$(date -r "$file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "") json_add_object json_add_string "name" "$(basename "$file")" json_add_int "size" "$size" json_add_string "created_at" "$created" json_close_object done json_close_array json_add_boolean "success" 1 json_dump } download_diagnostic() { read input json_load "$input" json_get_var name name local safe="$(safe_filename "$name")" local file="$DIAG_DIR/$safe" json_init if [ -z "$safe" ] || [ ! -f "$file" ]; then json_add_boolean "success" 0 json_add_string "error" "not_found" json_dump return fi local data=$(base64 "$file" 2>/dev/null) json_add_boolean "success" 1 json_add_string "name" "$safe" json_add_string "data" "$data" json_dump } delete_diagnostic() { read input json_load "$input" json_get_var name name local safe="$(safe_filename "$name")" local file="$DIAG_DIR/$safe" json_init if [ -n "$safe" ] && [ -f "$file" ]; then rm -f "$file" json_add_boolean "success" 1 else json_add_boolean "success" 0 json_add_string "error" "not_found" fi json_dump } run_diagnostic_test() { read input json_load "$input" json_get_var test test local output="" local success=1 case "$test" in connectivity) output="$(ping -c 3 -W 2 8.8.8.8 2>&1; echo; ping -c 3 -W 2 1.1.1.1 2>&1)" ;; dns) if command -v nslookup >/dev/null 2>&1; then output="$(nslookup openwrt.org 2>&1)" else output="nslookup unavailable" success=0 fi ;; latency) output="$(ping -c 5 -W 2 google.com 2>&1)" ;; disk) local testfile="/tmp/system-hub-disk-test" if dd if=/dev/zero of="$testfile" bs=1M count=4 conv=fsync 2>&1; then sync rm -f "$testfile" output="Disk write test completed (4MB)." else output="Disk test failed." success=0 fi ;; firewall) output="$(iptables -L -n -v 2>&1)" ;; wifi) if command -v iwinfo >/dev/null 2>&1; then output="$(iwinfo 2>&1)" else output="iwinfo not available" success=0 fi ;; *) output="unknown test" success=0 ;; esac json_init json_add_boolean "success" "$success" json_add_string "test" "${test:-unknown}" json_add_string "output" "$output" json_dump } # List all available diagnostic profiles list_diagnostic_profiles() { json_init json_add_array "profiles" # Profile 1: Network Issues json_add_object "" json_add_string "name" "network-issues" json_add_string "label" "Problèmes Réseau" json_add_string "icon" "🌐" json_add_string "description" "Diagnostique les pannes de routage, DNS, perte de paquets et blocages firewall" json_add_string "tests" "connectivity,dns,latency,firewall" json_add_int "include_logs" 1 json_add_int "include_config" 1 json_add_int "include_network" 1 json_add_int "anonymize" 0 json_close_object # Profile 2: Performance Problems json_add_object "" json_add_string "name" "performance-problems" json_add_string "label" "Problèmes Performance" json_add_string "icon" "⚡" json_add_string "description" "Identifie les goulots d'étranglement CPU/mémoire, problèmes d'E/S disque" json_add_string "tests" "disk,latency" json_add_int "include_logs" 1 json_add_int "include_config" 0 json_add_int "include_network" 0 json_add_int "anonymize" 0 json_close_object # Profile 3: Security Audit json_add_object "" json_add_string "name" "security-audit" json_add_string "label" "Audit Sécurité" json_add_string "icon" "🔐" json_add_string "description" "Revue des règles firewall, logs d'authentification, exposition réseau" json_add_string "tests" "firewall" json_add_int "include_logs" 1 json_add_int "include_config" 1 json_add_int "include_network" 1 json_add_int "anonymize" 1 json_close_object # Profile 4: WiFi Problems json_add_object "" json_add_string "name" "wifi-problems" json_add_string "label" "Problèmes WiFi" json_add_string "icon" "📶" json_add_string "description" "Analyse la force du signal, interférences canaux, associations clients" json_add_string "tests" "wifi,connectivity,latency" json_add_int "include_logs" 1 json_add_int "include_config" 1 json_add_int "include_network" 1 json_add_int "anonymize" 0 json_close_object # Profile 5: Full Diagnostic json_add_object "" json_add_string "name" "full-diagnostic" json_add_string "label" "Diagnostic Complet" json_add_string "icon" "📋" json_add_string "description" "Diagnostic complet pour escalade support - collecte tout" json_add_string "tests" "connectivity,dns,latency,disk,firewall,wifi" json_add_int "include_logs" 1 json_add_int "include_config" 1 json_add_int "include_network" 1 json_add_int "anonymize" 1 json_close_object # TODO: Add UCI custom profiles here # config_load system-hub # list_custom_profiles() { ... } json_close_array json_dump } # Get specific diagnostic profile get_diagnostic_profile() { read input [ -n "$input" ] && json_load "$input" local profile_name json_get_var profile_name name # Hardcoded profiles case "$profile_name" in network-issues) json_init json_add_string "name" "network-issues" json_add_string "label" "Problèmes Réseau" json_add_string "icon" "🌐" json_add_string "description" "Diagnostique les pannes de routage, DNS, perte de paquets et blocages firewall" json_add_string "tests" "connectivity,dns,latency,firewall" json_add_int "include_logs" 1 json_add_int "include_config" 1 json_add_int "include_network" 1 json_add_int "anonymize" 0 json_dump ;; performance-problems) json_init json_add_string "name" "performance-problems" json_add_string "label" "Problèmes Performance" json_add_string "icon" "⚡" json_add_string "description" "Identifie les goulots d'étranglement CPU/mémoire, problèmes d'E/S disque" json_add_string "tests" "disk,latency" json_add_int "include_logs" 1 json_add_int "include_config" 0 json_add_int "include_network" 0 json_add_int "anonymize" 0 json_dump ;; security-audit) json_init json_add_string "name" "security-audit" json_add_string "label" "Audit Sécurité" json_add_string "icon" "🔐" json_add_string "description" "Revue des règles firewall, logs d'authentification, exposition réseau" json_add_string "tests" "firewall" json_add_int "include_logs" 1 json_add_int "include_config" 1 json_add_int "include_network" 1 json_add_int "anonymize" 1 json_dump ;; wifi-problems) json_init json_add_string "name" "wifi-problems" json_add_string "label" "Problèmes WiFi" json_add_string "icon" "📶" json_add_string "description" "Analyse la force du signal, interférences canaux, associations clients" json_add_string "tests" "wifi,connectivity,latency" json_add_int "include_logs" 1 json_add_int "include_config" 1 json_add_int "include_network" 1 json_add_int "anonymize" 0 json_dump ;; full-diagnostic) json_init json_add_string "name" "full-diagnostic" json_add_string "label" "Diagnostic Complet" json_add_string "icon" "📋" json_add_string "description" "Diagnostic complet pour escalade support - collecte tout" json_add_string "tests" "connectivity,dns,latency,disk,firewall,wifi" json_add_int "include_logs" 1 json_add_int "include_config" 1 json_add_int "include_network" 1 json_add_int "anonymize" 1 json_dump ;; *) # TODO: Check UCI for custom profile json_init json_add_boolean "success" 0 json_add_string "error" "Profile not found: $profile_name" json_dump ;; esac } remote_status() { local installed=0 local running=0 local enabled=0 # Check if rtty is installed command -v rtty >/dev/null 2>&1 && installed=1 # Check if running if pidof rtty >/dev/null 2>&1; then running=1 fi # Check if enabled if [ -x /etc/init.d/rtty ]; then [ -f /etc/rc.d/S*rtty ] && enabled=1 fi # Get config from UCI local host=$(uci -q get rtty.@rtty[0].host || echo "") local port=$(uci -q get rtty.@rtty[0].port || echo "5912") local device_id=$(uci -q get rtty.@rtty[0].id || echo "") local description=$(uci -q get rtty.@rtty[0].description || echo "") local ssl=$(uci -q get rtty.@rtty[0].ssl || echo "0") local token=$(uci -q get rtty.@rtty[0].token || echo "") # Auto-generate ID from MAC if not set if [ -z "$device_id" ]; then local iface=$(uci -q get rtty.@rtty[0].interface || echo "lan") device_id=$(cat /sys/class/net/br-$iface/address 2>/dev/null | tr -d ':' || cat /sys/class/net/eth0/address 2>/dev/null | tr -d ':' || echo "") fi json_init json_add_boolean "installed" "$installed" json_add_boolean "running" "$running" json_add_boolean "enabled" "$enabled" json_add_string "host" "$host" json_add_int "port" "$port" json_add_string "id" "$device_id" json_add_string "description" "$description" json_add_boolean "ssl" "$ssl" json_add_string "token" "$token" json_dump } remote_install() { json_init if command -v rtty >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "already_installed" json_dump return fi if ! command -v opkg >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "opkg_missing" json_dump return fi opkg update >/tmp/rtty-install.log 2>&1 if opkg install rtty-openssl >>/tmp/rtty-install.log 2>&1; then json_add_boolean "success" 1 json_add_string "message" "rtty installed successfully" else local err="$(tail -n 20 /tmp/rtty-install.log 2>/dev/null)" json_add_boolean "success" 0 json_add_string "error" "${err:-install_failed}" fi json_dump } remote_configure() { read input json_load "$input" local host port device_id description ssl token json_get_var host host json_get_var port port json_get_var device_id id json_get_var description description json_get_var ssl ssl json_get_var token token # Ensure rtty config section exists if ! uci -q get rtty.@rtty[0] >/dev/null 2>&1; then uci add rtty rtty fi [ -n "$host" ] && uci set rtty.@rtty[0].host="$host" [ -n "$port" ] && uci set rtty.@rtty[0].port="$port" [ -n "$device_id" ] && uci set rtty.@rtty[0].id="$device_id" [ -n "$description" ] && uci set rtty.@rtty[0].description="$description" [ -n "$ssl" ] && uci set rtty.@rtty[0].ssl="$ssl" [ -n "$token" ] && uci set rtty.@rtty[0].token="$token" uci commit rtty json_init json_add_boolean "success" 1 json_dump } remote_get_credentials() { local device_id=$(uci -q get rtty.@rtty[0].id || echo "") local token=$(uci -q get rtty.@rtty[0].token || echo "") # Auto-generate ID from MAC if not set if [ -z "$device_id" ]; then local iface=$(uci -q get rtty.@rtty[0].interface || echo "lan") device_id=$(cat /sys/class/net/br-$iface/address 2>/dev/null | tr -d ':' || cat /sys/class/net/eth0/address 2>/dev/null | tr -d ':' || echo "") fi json_init json_add_boolean "success" 1 json_add_string "id" "$device_id" json_add_string "token" "$token" json_dump } remote_service_action() { read input json_load "$input" json_get_var action action json_init if ! command -v rtty >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "not_installed" json_dump return fi case "$action" in start) # Stop any existing instance killall rtty 2>/dev/null || true sleep 1 # Get config local host=$(uci -q get rtty.@rtty[0].host) local port=$(uci -q get rtty.@rtty[0].port || echo "5912") local device_id=$(uci -q get rtty.@rtty[0].id) local token=$(uci -q get rtty.@rtty[0].token) local ssl=$(uci -q get rtty.@rtty[0].ssl || echo "0") local description=$(uci -q get rtty.@rtty[0].description) if [ -z "$host" ]; then json_add_boolean "success" 0 json_add_string "error" "no_server_configured" json_dump return fi # Build command local cmd="rtty -h $host -p $port -a -D" [ -n "$device_id" ] && cmd="$cmd -I $device_id" [ -n "$token" ] && cmd="$cmd -t $token" [ -n "$description" ] && cmd="$cmd -d \"$description\"" [ "$ssl" = "1" ] && cmd="$cmd -s -x" eval $cmd sleep 2 if pidof rtty >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "rtty started" else json_add_boolean "success" 0 json_add_string "error" "failed_to_start" fi ;; stop) if killall rtty 2>/dev/null; then json_add_boolean "success" 1 json_add_string "message" "rtty stopped" else json_add_boolean "success" 0 json_add_string "error" "not_running" fi ;; restart) killall rtty 2>/dev/null || true sleep 1 # Recursive call to start echo '{"action":"start"}' | remote_service_action return ;; enable) if [ -x /etc/init.d/rtty ]; then /etc/init.d/rtty enable >/dev/null 2>&1 json_add_boolean "success" 1 json_add_string "message" "rtty enabled" else json_add_boolean "success" 0 json_add_string "error" "init_script_missing" fi ;; disable) if [ -x /etc/init.d/rtty ]; then /etc/init.d/rtty disable >/dev/null 2>&1 json_add_boolean "success" 1 json_add_string "message" "rtty disabled" else json_add_boolean "success" 0 json_add_string "error" "init_script_missing" fi ;; *) json_add_boolean "success" 0 json_add_string "error" "invalid_action" ;; esac json_dump } remote_save_settings() { read input json_load "$input" local host port device_id description ssl token json_get_var host host json_get_var port port json_get_var device_id id json_get_var description description json_get_var ssl ssl json_get_var token token # Ensure rtty config section exists if ! uci -q get rtty.@rtty[0] >/dev/null 2>&1; then uci add rtty rtty fi [ -n "$host" ] && uci set rtty.@rtty[0].host="$host" [ -n "$port" ] && uci set rtty.@rtty[0].port="$port" [ -n "$device_id" ] && uci set rtty.@rtty[0].id="$device_id" [ -n "$description" ] && uci set rtty.@rtty[0].description="$description" [ -n "$ssl" ] && uci set rtty.@rtty[0].ssl="$ssl" [ -n "$token" ] && uci set rtty.@rtty[0].token="$token" uci commit rtty json_init json_add_boolean "success" 1 json_dump } # ============================================ # TTYD Web Console Functions # ============================================ ttyd_status() { local installed=0 local running=0 local enabled=0 local port=7681 local interface="lan" # Check if installed command -v ttyd >/dev/null 2>&1 && installed=1 # Check if running if pidof ttyd >/dev/null 2>&1; then running=1 fi # Check if enabled in init if [ -x /etc/init.d/ttyd ] && [ -f /etc/rc.d/S*ttyd 2>/dev/null ]; then enabled=1 fi # Get port from UCI config local uci_port=$(uci -q get ttyd.@ttyd[0].port) [ -n "$uci_port" ] && port="$uci_port" # Get interface binding local uci_interface=$(uci -q get ttyd.@ttyd[0].interface) [ -n "$uci_interface" ] && interface="$uci_interface" json_init json_add_boolean "installed" "$installed" json_add_boolean "running" "$running" json_add_boolean "enabled" "$enabled" json_add_int "port" "$port" json_add_string "interface" "$interface" json_dump } ttyd_install() { json_init if command -v ttyd >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "already_installed" json_dump return fi if ! command -v opkg >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "opkg_missing" json_dump return fi opkg update >/tmp/ttyd-install.log 2>&1 if opkg install ttyd >>/tmp/ttyd-install.log 2>&1; then # Configure ttyd for LAN only by default if [ ! -f /etc/config/ttyd ]; then cat > /etc/config/ttyd <<'TTYDCONF' config ttyd option port '7681' option interface 'lan' option command '/bin/login' option ipv6 '0' TTYDCONF fi json_add_boolean "success" 1 json_add_string "message" "ttyd installed successfully" else local err="$(tail -n 20 /tmp/ttyd-install.log 2>/dev/null)" json_add_boolean "success" 0 json_add_string "error" "${err:-install_failed}" fi json_dump } ttyd_start() { json_init if ! command -v ttyd >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "not_installed" json_dump return fi # Stop any existing instance killall ttyd 2>/dev/null || true sleep 1 # Get config local port=$(uci -q get ttyd.@ttyd[0].port || echo 7681) local interface=$(uci -q get ttyd.@ttyd[0].interface || echo "lan") # Get interface IP local bind_ip="" if [ "$interface" != "" ] && [ "$interface" != "0.0.0.0" ]; then bind_ip=$(ubus call network.interface.$interface status 2>/dev/null | jsonfilter -e '@["ipv4-address"][0].address' 2>/dev/null) fi # Start ttyd if [ -n "$bind_ip" ]; then ttyd -p "$port" -i "$bind_ip" -W /bin/login & else ttyd -p "$port" -W /bin/login & fi sleep 2 if pidof ttyd >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "ttyd started on port $port" json_add_int "port" "$port" else json_add_boolean "success" 0 json_add_string "error" "failed_to_start" fi json_dump } ttyd_stop() { json_init if killall ttyd 2>/dev/null; then json_add_boolean "success" 1 json_add_string "message" "ttyd stopped" else json_add_boolean "success" 0 json_add_string "error" "not_running" fi json_dump } ttyd_configure() { read input json_load "$input" local port interface json_get_var port port json_get_var interface interface # Ensure config exists if [ ! -f /etc/config/ttyd ]; then touch /etc/config/ttyd uci set ttyd.main=ttyd fi [ -n "$port" ] && uci set ttyd.@ttyd[0].port="$port" [ -n "$interface" ] && uci set ttyd.@ttyd[0].interface="$interface" uci commit ttyd # Restart if running if pidof ttyd >/dev/null 2>&1; then killall ttyd 2>/dev/null sleep 1 ttyd_start >/dev/null 2>&1 fi json_init json_add_boolean "success" 1 json_dump } upload_diagnostics() { read input json_load "$input" json_get_var name name local upload_url=$(uci -q get system-hub.diagnostics.upload_url) local upload_token=$(uci -q get system-hub.diagnostics.upload_token) json_init if [ -z "$upload_url" ]; then json_add_boolean "success" 0 json_add_string "error" "upload_url_missing" json_dump return fi if ! command -v curl >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "curl_missing" json_dump return fi local safe="$(safe_filename "$name")" if [ -z "$safe" ]; then # Use latest archive if not specified safe="$(ls -t "$DIAG_DIR"/diagnostics-*.tar.gz 2>/dev/null | head -n1)" [ -n "$safe" ] && safe="$(basename "$safe")" fi local file="$DIAG_DIR/$safe" if [ -z "$safe" ] || [ ! -f "$file" ]; then json_add_boolean "success" 0 json_add_string "error" "archive_not_found" json_dump return fi local response if [ -n "$upload_token" ]; then response=$(curl -s -S -o /tmp/system-hub-upload.log -w "%{http_code}" \ -H "Authorization: Bearer $upload_token" \ -F "file=@$file" \ -F "hostname=$(hostname)" \ -F "timestamp=$(date +%s)" \ "$upload_url" 2>&1) else response=$(curl -s -S -o /tmp/system-hub-upload.log -w "%{http_code}" \ -F "file=@$file" \ -F "hostname=$(hostname)" \ -F "timestamp=$(date +%s)" \ "$upload_url" 2>&1) fi local curl_exit=$? local body="$(cat /tmp/system-hub-upload.log 2>/dev/null || true)" rm -f /tmp/system-hub-upload.log if [ $curl_exit -eq 0 ]; then json_add_boolean "success" 1 json_add_string "status" "$response" json_add_string "body" "$body" else json_add_boolean "success" 0 json_add_string "error" "upload_failed" json_add_string "details" "$body" fi json_dump } # Get settings get_settings() { json_init # Load UCI config if it exists local config_loaded=0 if [ -f "/etc/config/system-hub" ]; then config_load system-hub config_loaded=1 fi # General settings json_add_object "general" config_get auto_refresh general auto_refresh "1" config_get health_check general health_check "1" config_get debug_mode general debug_mode "0" config_get refresh_interval general refresh_interval "30" config_get log_retention general log_retention "30" json_add_boolean "auto_refresh" "${auto_refresh:-1}" json_add_boolean "health_check" "${health_check:-1}" json_add_boolean "debug_mode" "${debug_mode:-0}" json_add_int "refresh_interval" "${refresh_interval:-30}" json_add_int "log_retention" "${log_retention:-30}" json_close_object # Alert thresholds json_add_object "thresholds" config_get cpu_warning thresholds cpu_warning "80" config_get cpu_critical thresholds cpu_critical "95" config_get mem_warning thresholds mem_warning "80" config_get mem_critical thresholds mem_critical "95" config_get disk_warning thresholds disk_warning "80" config_get disk_critical thresholds disk_critical "95" config_get temp_warning thresholds temp_warning "70" config_get temp_critical thresholds temp_critical "85" json_add_int "cpu_warning" "${cpu_warning:-80}" json_add_int "cpu_critical" "${cpu_critical:-95}" json_add_int "mem_warning" "${mem_warning:-80}" json_add_int "mem_critical" "${mem_critical:-95}" json_add_int "disk_warning" "${disk_warning:-80}" json_add_int "disk_critical" "${disk_critical:-95}" json_add_int "temp_warning" "${temp_warning:-70}" json_add_int "temp_critical" "${temp_critical:-85}" json_close_object # Scheduled tasks json_add_object "schedules" config_get health_report schedules health_report "1" config_get backup_weekly schedules backup_weekly "1" config_get log_cleanup schedules log_cleanup "1" json_add_boolean "health_report" "${health_report:-1}" json_add_boolean "backup_weekly" "${backup_weekly:-1}" json_add_boolean "log_cleanup" "${log_cleanup:-1}" json_close_object # Upload settings json_add_object "upload" config_get auto_upload upload auto_upload "0" config_get upload_url upload url "" config_get upload_token upload token "" json_add_boolean "auto_upload" "${auto_upload:-0}" json_add_string "url" "${upload_url:-}" json_add_string "token" "${upload_token:-}" json_close_object # Support info json_add_object "support" config_get support_provider support provider "CyberMind.fr" config_get support_email support email "support@cybermind.fr" config_get support_docs support docs "https://docs.cybermind.fr" json_add_string "provider" "${support_provider:-CyberMind.fr}" json_add_string "email" "${support_email:-support@cybermind.fr}" json_add_string "docs" "${support_docs:-https://docs.cybermind.fr}" json_close_object json_dump } # Save settings save_settings() { read -r input json_load "$input" # Parse settings from input local section key value # Create UCI config if it doesn't exist if [ ! -f "/etc/config/system-hub" ]; then touch /etc/config/system-hub uci set system-hub.general=settings uci set system-hub.thresholds=thresholds uci set system-hub.schedules=schedules uci set system-hub.upload=upload uci set system-hub.support=support fi # This is a simplified version - in production you'd parse the JSON properly # For now, we'll extract specific values json_get_var auto_refresh auto_refresh json_get_var health_check health_check json_get_var debug_mode debug_mode json_get_var refresh_interval refresh_interval json_get_var log_retention log_retention json_get_var cpu_warning cpu_warning json_get_var cpu_critical cpu_critical json_get_var mem_warning mem_warning json_get_var mem_critical mem_critical json_get_var disk_warning disk_warning json_get_var disk_critical disk_critical json_get_var temp_warning temp_warning json_get_var temp_critical temp_critical json_cleanup # Save to UCI [ -n "$auto_refresh" ] && uci set system-hub.general.auto_refresh="$auto_refresh" [ -n "$health_check" ] && uci set system-hub.general.health_check="$health_check" [ -n "$debug_mode" ] && uci set system-hub.general.debug_mode="$debug_mode" [ -n "$refresh_interval" ] && uci set system-hub.general.refresh_interval="$refresh_interval" [ -n "$log_retention" ] && uci set system-hub.general.log_retention="$log_retention" [ -n "$cpu_warning" ] && uci set system-hub.thresholds.cpu_warning="$cpu_warning" [ -n "$cpu_critical" ] && uci set system-hub.thresholds.cpu_critical="$cpu_critical" [ -n "$mem_warning" ] && uci set system-hub.thresholds.mem_warning="$mem_warning" [ -n "$mem_critical" ] && uci set system-hub.thresholds.mem_critical="$mem_critical" [ -n "$disk_warning" ] && uci set system-hub.thresholds.disk_warning="$disk_warning" [ -n "$disk_critical" ] && uci set system-hub.thresholds.disk_critical="$disk_critical" [ -n "$temp_warning" ] && uci set system-hub.thresholds.temp_warning="$temp_warning" [ -n "$temp_critical" ] && uci set system-hub.thresholds.temp_critical="$temp_critical" uci commit system-hub json_init json_add_boolean "success" 1 json_add_string "message" "Settings saved successfully" json_dump } # Check if a package is installed (supports wildcards) is_package_installed() { local pkg="$1" [ -z "$pkg" ] && return 1 # Check exact match first opkg list-installed 2>/dev/null | grep -q "^${pkg} " && return 0 # Check if it's a LuCI app package opkg list-installed 2>/dev/null | grep -q "^luci-app-${pkg} " && return 0 return 1 } # Get installed package info (name and version) for a component get_installed_package_info() { local pkg="$1" [ -z "$pkg" ] && return # Try exact match local info=$(opkg list-installed "$pkg" 2>/dev/null | head -n1) if [ -z "$info" ]; then # Try luci-app- prefix info=$(opkg list-installed "luci-app-$pkg" 2>/dev/null | head -n1) fi echo "$info" } # Check if a service is running is_service_running() { local svc="$1" [ -z "$svc" ] && return 1 # Check init script if [ -x "/etc/init.d/$svc" ]; then /etc/init.d/"$svc" running >/dev/null 2>&1 && return 0 fi # Fallback: check process pidof "$svc" >/dev/null 2>&1 && return 0 return 1 } # Get package version get_package_version() { local pkg="$1" [ -z "$pkg" ] && echo "" && return local ver=$(opkg list-installed "$pkg" 2>/dev/null | awk '{print $3}' | head -n1) echo "${ver:-}" } # Add a single component to the JSON output add_component() { local id="$1" name="$2" category="$3" icon="$4" package="$5" service="$6" description="$7" color="$8" local installed=0 local running=0 local version="" if is_package_installed "$package"; then installed=1 version=$(get_package_version "$package") fi if [ "$installed" = "1" ] && is_service_running "$service"; then running=1 fi json_add_object "" json_add_string "id" "$id" json_add_string "name" "$name" json_add_string "category" "$category" json_add_string "icon" "$icon" json_add_string "package" "$package" json_add_string "service" "$service" json_add_string "description" "$description" json_add_string "color" "$color" json_add_string "version" "$version" json_add_boolean "installed" "$installed" json_add_boolean "running" "$running" json_close_object } # Get components with real installation/running status get_components() { # First try to get apps from secubox backend local apps_json=$(ubus call luci.secubox list_apps 2>/dev/null) if [ -n "$apps_json" ]; then json_init json_add_array "modules" # Parse using jsonfilter - get all app IDs first local app_ids=$(echo "$apps_json" | jsonfilter -e '@.apps[*].id' 2>/dev/null) local i=0 for id in $app_ids; do [ -z "$id" ] && continue local name=$(echo "$apps_json" | jsonfilter -e "@.apps[$i].name" 2>/dev/null) local category=$(echo "$apps_json" | jsonfilter -e "@.apps[$i].category" 2>/dev/null) local state=$(echo "$apps_json" | jsonfilter -e "@.apps[$i].state" 2>/dev/null) local version=$(echo "$apps_json" | jsonfilter -e "@.apps[$i].version" 2>/dev/null) local description=$(echo "$apps_json" | jsonfilter -e "@.apps[$i].description" 2>/dev/null) [ -z "$name" ] && name="$id" [ -z "$category" ] && category="system" [ -z "$state" ] && state="missing" [ -z "$description" ] && description="No description" local installed=0 local running=0 local color="#64748b" local icon="📦" local service="$id" # Determine if installed based on state from secubox case "$state" in installed|partial) installed=1 ;; esac # Determine color and icon based on category case "$category" in security) color="#ef4444"; icon="🛡️" ;; monitoring) color="#10b981"; icon="📊" ;; network) color="#3b82f6"; icon="🌐" ;; system) color="#64748b"; icon="⚙️" ;; esac # Override icon based on app ID case "$id" in crowdsec*) icon="🛡️" ;; auth-guardian) icon="🔐" ;; client-guardian) icon="👥" ;; key-storage*|ksm*) icon="🔑" ;; vaultwarden) icon="🔒" ;; wireguard*) icon="🔒" ;; bandwidth*|nlbwmon) icon="📊" ;; netdata*) icon="📉" ;; esac # Check if service is running if [ "$installed" = "1" ]; then # Try direct service name if [ -x "/etc/init.d/$id" ]; then /etc/init.d/"$id" running >/dev/null 2>&1 && running=1 fi # Try base service name (e.g., crowdsec-dashboard -> crowdsec) if [ "$running" = "0" ]; then local base_svc=$(echo "$id" | sed 's/-dashboard$//' | sed 's/-guardian$//' | sed 's/-manager$//') if [ -x "/etc/init.d/$base_svc" ]; then /etc/init.d/"$base_svc" running >/dev/null 2>&1 && running=1 service="$base_svc" fi fi # Fallback: check process if [ "$running" = "0" ]; then pgrep -f "$id" >/dev/null 2>&1 && running=1 fi fi # Get version from opkg if not provided if [ -z "$version" ] && [ "$installed" = "1" ]; then local luci_pkg="luci-app-${id}" version=$(opkg list-installed "$luci_pkg" 2>/dev/null | awk '{print $3}' | head -n1) fi json_add_object "" json_add_string "id" "$id" json_add_string "name" "$name" json_add_string "category" "$category" json_add_string "icon" "$icon" json_add_string "package" "luci-app-$id" json_add_string "service" "$service" json_add_string "description" "$description" json_add_string "color" "$color" json_add_string "version" "$version" json_add_string "state" "$state" json_add_boolean "installed" "$installed" json_add_boolean "running" "$running" json_close_object i=$((i + 1)) done # Also add core system components add_component "firewall4" "Firewall" "system" "🧱" "firewall4" "firewall" "nftables-based firewall" "#64748b" add_component "dnsmasq" "DNSMasq" "system" "🔍" "dnsmasq-full" "dnsmasq" "DNS and DHCP server" "#64748b" add_component "dropbear" "SSH Server" "system" "🔑" "dropbear" "dropbear" "Lightweight SSH server" "#64748b" add_component "uhttpd" "uHTTPd" "system" "🌐" "uhttpd" "uhttpd" "Lightweight HTTP server" "#64748b" json_close_array json_dump else # Fallback: use hardcoded list with real status checks json_init json_add_array "modules" add_component "crowdsec" "CrowdSec" "security" "🛡️" "crowdsec" "crowdsec" "Collaborative security engine" "#ef4444" add_component "crowdsec-firewall-bouncer" "CrowdSec Bouncer" "security" "🔥" "crowdsec-firewall-bouncer-nftables" "crowdsec-firewall-bouncer" "Firewall bouncer" "#ef4444" add_component "adguardhome" "AdGuard Home" "security" "🚫" "adguardhome" "AdGuardHome" "Ad and tracker blocking" "#22c55e" add_component "banip" "BanIP" "security" "🚷" "banip" "banip" "IP blocking service" "#ef4444" add_component "wireguard" "WireGuard" "network" "🔒" "wireguard-tools" "wg-quick@wg0" "Modern VPN tunnel" "#3b82f6" add_component "sqm" "SQM QoS" "network" "⚡" "sqm-scripts" "sqm" "Smart Queue Management" "#3b82f6" add_component "mwan3" "Multi-WAN" "network" "🔀" "mwan3" "mwan3" "Multi-WAN failover" "#3b82f6" add_component "nlbwmon" "Bandwidth Monitor" "monitoring" "📊" "nlbwmon" "nlbwmon" "Bandwidth monitoring" "#10b981" add_component "vnstat2" "Traffic Stats" "monitoring" "📈" "vnstat2" "vnstatd" "Traffic statistics" "#10b981" add_component "firewall4" "Firewall" "system" "🧱" "firewall4" "firewall" "nftables firewall" "#64748b" add_component "dnsmasq" "DNSMasq" "system" "🔍" "dnsmasq-full" "dnsmasq" "DNS/DHCP server" "#64748b" add_component "dropbear" "SSH Server" "system" "🔑" "dropbear" "dropbear" "SSH server" "#64748b" add_component "uhttpd" "uHTTPd" "system" "🌐" "uhttpd" "uhttpd" "HTTP server" "#64748b" json_close_array json_dump fi } # Get components by category get_components_by_category() { local input read -r input json_load "$input" local category json_get_var category category json_cleanup # Call secubox backend with category filter local result=$(ubus call luci.secubox modules_by_category "{\"category\":\"$category\"}" 2>/dev/null) if [ -n "$result" ]; then echo "$result" else # Fallback json_init json_add_array "modules" json_close_array json_dump fi } # Main dispatcher case "$1" in list) cat << 'EOF' { "status": {}, "get_system_info": {}, "get_health": {}, "get_components": {}, "get_components_by_category": { "category": "string" }, "list_services": {}, "service_action": { "service": "string", "action": "string" }, "get_logs": { "lines": 100, "filter": "" }, "backup_config": {}, "restore_config": { "file_name": "string", "data": "string" }, "get_backup_schedule": {}, "set_backup_schedule": { "enabled": 1, "frequency": "weekly", "hour": "03", "minute": "00", "day_of_week": "0", "day_of_month": "1" }, "reboot": {}, "get_storage": {}, "get_settings": {}, "save_settings": { "auto_refresh": 1, "health_check": 1, "debug_mode": 0, "refresh_interval": 30, "log_retention": 30, "cpu_warning": 80, "cpu_critical": 95, "mem_warning": 80, "mem_critical": 95, "disk_warning": 80, "disk_critical": 95, "temp_warning": 70, "temp_critical": 85 }, "collect_diagnostics": { "include_logs": 1, "include_config": 1, "include_network": 1, "anonymize": 1, "profile": "string" }, "list_diagnostics": {}, "list_diagnostic_profiles": {}, "get_diagnostic_profile": { "name": "string" }, "download_diagnostic": { "name": "string" }, "delete_diagnostic": { "name": "string" }, "run_diagnostic_test": { "test": "string" }, "upload_diagnostics": { "name": "string" }, "remote_status": {}, "remote_install": {}, "remote_configure": { "relay_server": "string", "relay_key": "string", "rustdesk_enabled": 1 }, "remote_get_credentials": {}, "remote_service_action": { "action": "start|stop|restart|enable|disable" }, "remote_save_settings": { "allow_unattended": 0, "require_approval": 1, "notify_on_connect": 1 }, "ttyd_status": {}, "ttyd_install": {}, "ttyd_start": {}, "ttyd_stop": {}, "ttyd_configure": { "port": 7681, "interface": "lan" } } EOF ;; call) case "$2" in status) status ;; get_system_info) get_system_info ;; get_health) get_health ;; get_components) get_components ;; get_components_by_category) get_components_by_category ;; list_services) list_services ;; service_action) service_action ;; get_logs) get_logs ;; backup_config) backup_config ;; restore_config) restore_config ;; get_backup_schedule) get_backup_schedule ;; set_backup_schedule) set_backup_schedule ;; reboot) reboot_system ;; get_storage) get_storage ;; get_settings) get_settings ;; save_settings) save_settings ;; collect_diagnostics) collect_diagnostics ;; list_diagnostics) list_diagnostics ;; list_diagnostic_profiles) list_diagnostic_profiles ;; get_diagnostic_profile) get_diagnostic_profile ;; download_diagnostic) download_diagnostic ;; delete_diagnostic) delete_diagnostic ;; run_diagnostic_test) run_diagnostic_test ;; upload_diagnostics) upload_diagnostics ;; remote_status) remote_status ;; remote_install) remote_install ;; remote_configure) remote_configure ;; remote_get_credentials) remote_get_credentials ;; remote_service_action) remote_service_action ;; remote_save_settings) remote_save_settings ;; ttyd_status) ttyd_status ;; ttyd_install) ttyd_install ;; ttyd_start) ttyd_start ;; ttyd_stop) ttyd_stop ;; ttyd_configure) ttyd_configure ;; *) json_init json_add_boolean "success" 0 json_add_string "error" "Unknown method: $2" json_dump ;; esac ;; esac