- Add get_service_health RPCD method to check all HAProxy routes - Integrate /usr/sbin/service-health-check for backend HTTP probing - Add health panel in services.js with up/down stats and health % - Display down services list with tooltips showing IP:port - Add refresh button for manual health check trigger - Update ACL with get_service_health read permission - 5-minute cache for health data with force-refresh option Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2418 lines
66 KiB
Bash
Executable File
2418 lines
66 KiB
Bash
Executable File
#!/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
|
|
}
|
|
|
|
# Get denoised logs with smart filtering
|
|
# Modes: RAW (all logs), SMART (filter known IPs), SIGNAL_ONLY (new threats only)
|
|
get_denoised_logs() {
|
|
read -r input
|
|
json_load "$input"
|
|
|
|
local lines filter mode
|
|
json_get_var lines lines "100"
|
|
json_get_var filter filter ""
|
|
json_get_var mode mode "RAW"
|
|
json_cleanup
|
|
|
|
local tmpfile="/tmp/syslog-denoised-$$"
|
|
local rawfile="/tmp/syslog-raw-$$"
|
|
local total_lines=0
|
|
local filtered_lines=0
|
|
|
|
# Get raw logs first
|
|
if [ -n "$filter" ]; then
|
|
logread | tail -n "$lines" | grep -i "$filter" > "$rawfile"
|
|
else
|
|
logread | tail -n "$lines" > "$rawfile"
|
|
fi
|
|
|
|
total_lines=$(wc -l < "$rawfile" 2>/dev/null || echo 0)
|
|
|
|
case "$mode" in
|
|
RAW)
|
|
# No filtering - return all logs
|
|
cp "$rawfile" "$tmpfile"
|
|
;;
|
|
SMART|SIGNAL_ONLY)
|
|
# Get known blocked IPs from ipblocklist ipset
|
|
local ipset_name
|
|
ipset_name=$(uci -q get ipblocklist.global.ipset_name 2>/dev/null || echo "secubox_blocklist")
|
|
|
|
# Create temp file for blocked IPs (extract IPs from ipset)
|
|
local blocked_ips="/tmp/blocked_ips_$$"
|
|
|
|
# Try nftables first, then iptables ipset
|
|
if command -v nft >/dev/null 2>&1 && nft list set inet fw4 "$ipset_name" >/dev/null 2>&1; then
|
|
nft list set inet fw4 "$ipset_name" 2>/dev/null | \
|
|
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]+)?' > "$blocked_ips"
|
|
elif command -v ipset >/dev/null 2>&1; then
|
|
ipset list "$ipset_name" 2>/dev/null | \
|
|
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]+)?' > "$blocked_ips"
|
|
else
|
|
touch "$blocked_ips"
|
|
fi
|
|
|
|
# Also get CrowdSec decisions if available
|
|
if command -v cscli >/dev/null 2>&1; then
|
|
cscli decisions list -o json 2>/dev/null | \
|
|
jsonfilter -e '@[*].value' 2>/dev/null | \
|
|
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' >> "$blocked_ips" 2>/dev/null
|
|
fi
|
|
|
|
# Sort and unique the blocked IPs (remove CIDR for comparison)
|
|
sort -u "$blocked_ips" | sed 's|/[0-9]*||g' > "${blocked_ips}.clean"
|
|
mv "${blocked_ips}.clean" "$blocked_ips"
|
|
|
|
# Filter logs
|
|
> "$tmpfile"
|
|
while IFS= read -r line; do
|
|
# Extract IP addresses from log line
|
|
local line_ips
|
|
line_ips=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -5)
|
|
|
|
local is_known=0
|
|
if [ -n "$line_ips" ] && [ -s "$blocked_ips" ]; then
|
|
for ip in $line_ips; do
|
|
# Skip private/local IPs
|
|
case "$ip" in
|
|
10.*|172.1[6-9].*|172.2[0-9].*|172.3[01].*|192.168.*|127.*)
|
|
continue
|
|
;;
|
|
esac
|
|
# Check if IP is in blocked list
|
|
if grep -qF "$ip" "$blocked_ips" 2>/dev/null; then
|
|
is_known=1
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ "$mode" = "SMART" ]; then
|
|
# SMART mode: Show all logs but mark known IPs
|
|
if [ "$is_known" -eq 1 ]; then
|
|
filtered_lines=$((filtered_lines + 1))
|
|
fi
|
|
echo "$line" >> "$tmpfile"
|
|
else
|
|
# SIGNAL_ONLY mode: Only show logs with unknown IPs
|
|
if [ "$is_known" -eq 0 ]; then
|
|
echo "$line" >> "$tmpfile"
|
|
else
|
|
filtered_lines=$((filtered_lines + 1))
|
|
fi
|
|
fi
|
|
done < "$rawfile"
|
|
|
|
rm -f "$blocked_ips"
|
|
;;
|
|
esac
|
|
|
|
# Calculate noise ratio
|
|
local noise_ratio=0
|
|
if [ "$total_lines" -gt 0 ]; then
|
|
noise_ratio=$((filtered_lines * 100 / total_lines))
|
|
fi
|
|
|
|
json_init
|
|
json_add_string "mode" "$mode"
|
|
json_add_int "total_lines" "$total_lines"
|
|
json_add_int "filtered_lines" "$filtered_lines"
|
|
json_add_int "noise_ratio" "$noise_ratio"
|
|
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" "$rawfile"
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Get log denoising stats
|
|
get_denoise_stats() {
|
|
local ipset_name
|
|
ipset_name=$(uci -q get ipblocklist.global.ipset_name 2>/dev/null || echo "secubox_blocklist")
|
|
|
|
# Count blocked IPs
|
|
local blocked_count=0
|
|
if command -v nft >/dev/null 2>&1 && nft list set inet fw4 "$ipset_name" >/dev/null 2>&1; then
|
|
blocked_count=$(nft list set inet fw4 "$ipset_name" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | wc -l 2>/dev/null || echo 0)
|
|
elif command -v ipset >/dev/null 2>&1 && ipset list "$ipset_name" >/dev/null 2>&1; then
|
|
blocked_count=$(ipset list "$ipset_name" 2>/dev/null | grep -c '^[0-9]' 2>/dev/null || echo 0)
|
|
fi
|
|
# Ensure it's a valid number
|
|
[ -z "$blocked_count" ] && blocked_count=0
|
|
|
|
# Count CrowdSec decisions (count unique decision IDs)
|
|
local crowdsec_count=0
|
|
if command -v cscli >/dev/null 2>&1; then
|
|
crowdsec_count=$(cscli decisions list -o json 2>/dev/null | grep -c '"id":' 2>/dev/null || echo 0)
|
|
fi
|
|
# Ensure it's a valid number
|
|
[ -z "$crowdsec_count" ] && crowdsec_count=0
|
|
|
|
# Check if ipblocklist is enabled
|
|
local ipblocklist_enabled
|
|
ipblocklist_enabled=$(uci -q get ipblocklist.global.enabled 2>/dev/null || echo "0")
|
|
|
|
json_init
|
|
json_add_int "blocked_ips" "$blocked_count"
|
|
json_add_int "crowdsec_decisions" "$crowdsec_count"
|
|
json_add_int "total_known_threats" "$((blocked_count + crowdsec_count))"
|
|
json_add_boolean "ipblocklist_enabled" "$ipblocklist_enabled"
|
|
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
|
|
}
|
|
|
|
# Service Health Check - checks all HAProxy routes status
|
|
get_service_health() {
|
|
local cache_file="/tmp/service-health.json"
|
|
|
|
# Read refresh param
|
|
local input
|
|
read -r input
|
|
json_load "$input" 2>/dev/null
|
|
local refresh
|
|
json_get_var refresh refresh
|
|
json_cleanup
|
|
[ -z "$refresh" ] && refresh="0"
|
|
|
|
# Refresh if requested or cache is old (>5min) or missing
|
|
if [ "$refresh" = "1" ] || [ ! -f "$cache_file" ]; then
|
|
/usr/sbin/service-health-check json > "$cache_file" 2>/dev/null
|
|
elif [ -f "$cache_file" ]; then
|
|
local age=$(( $(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0) ))
|
|
if [ "$age" -gt 300 ]; then
|
|
/usr/sbin/service-health-check json > "$cache_file" 2>/dev/null
|
|
fi
|
|
fi
|
|
|
|
if [ -f "$cache_file" ]; then
|
|
cat "$cache_file"
|
|
else
|
|
json_init
|
|
json_add_string "error" "No health data available"
|
|
json_dump
|
|
fi
|
|
}
|
|
|
|
# Main dispatcher
|
|
case "$1" in
|
|
list)
|
|
cat << 'EOF'
|
|
{
|
|
"status": {},
|
|
"get_system_info": {},
|
|
"get_health": {},
|
|
"get_service_health": { "refresh": 0 },
|
|
"get_components": {},
|
|
"get_components_by_category": { "category": "string" },
|
|
"list_services": {},
|
|
"service_action": { "service": "string", "action": "string" },
|
|
"get_logs": { "lines": 100, "filter": "" },
|
|
"get_denoised_logs": { "lines": 100, "filter": "", "mode": "RAW" },
|
|
"get_denoise_stats": {},
|
|
"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_service_health) get_service_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 ;;
|
|
get_denoised_logs) get_denoised_logs ;;
|
|
get_denoise_stats) get_denoise_stats ;;
|
|
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
|