From 329d5febb940763195bda023392606edc179c1b2 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Wed, 28 Jan 2026 17:53:56 +0100 Subject: [PATCH] fix(mitmproxy,tor-shield): Add transparent mode firewall support - Add RPCD methods to mitmproxy: settings, save_settings, set_mode, setup_firewall, clear_firewall - Add apply_now parameter to tor-shield save_settings to restart service and apply iptables rules immediately - Update ACL files with new permissions - Add Save & Apply button to tor-shield settings page - Update api.js files to use correct RPCD method signatures Co-Authored-By: Claude Opus 4.5 --- .../luci-static/resources/mitmproxy/api.js | 275 ++++++--------- .../root/usr/libexec/rpcd/luci.mitmproxy | 332 +++++++++++++++++- .../share/rpcd/acl.d/luci-app-mitmproxy.json | 23 +- .../root/usr/libexec/rpcd/luci.network-tweaks | 101 ++++++ .../usr/libexec/rpcd/luci.service-registry | 146 +++----- .../luci-static/resources/tor-shield/api.js | 6 +- .../resources/view/tor-shield/settings.js | 27 +- .../root/usr/libexec/rpcd/luci.tor-shield | 22 +- .../luci-app-wireguard-dashboard/README.md | 316 +++++++---------- .../view/wireguard-dashboard/peers.js | 7 +- .../view/wireguard-dashboard/settings.js | 4 +- .../view/wireguard-dashboard/wizard.js | 41 ++- .../resources/wireguard-dashboard/api.js | 14 +- .../usr/libexec/rpcd/luci.wireguard-dashboard | 113 +++++- .../acl.d/luci-app-wireguard-dashboard.json | 1 + .../secubox-core/root/usr/sbin/secubox-core | 129 ++++++- 16 files changed, 1047 insertions(+), 510 deletions(-) diff --git a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/mitmproxy/api.js b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/mitmproxy/api.js index 6482edd6..e6c65fbf 100644 --- a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/mitmproxy/api.js +++ b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/mitmproxy/api.js @@ -2,237 +2,176 @@ 'require baseclass'; 'require rpc'; -var callMitmproxy = rpc.declare({ +// Status and settings +var callStatus = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_status' + method: 'status', + expect: {} }); -var callGetConfig = rpc.declare({ +var callSettings = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_config' + method: 'settings', + expect: {} }); -var callGetTransparentConfig = rpc.declare({ +var callSaveSettings = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_transparent_config' + method: 'save_settings', + params: ['mode', 'enabled', 'proxy_port', 'web_port', 'web_host', 'data_path', + 'memory_limit', 'upstream_proxy', 'reverse_target', 'ssl_insecure', + 'anticache', 'anticomp', 'transparent_enabled', 'transparent_interface', + 'redirect_http', 'redirect_https', 'filtering_enabled', 'log_requests', + 'filter_cdn', 'filter_media', 'block_ads', 'apply_now'], + expect: {} }); -var callGetWhitelistConfig = rpc.declare({ +var callSetMode = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_whitelist_config' + method: 'set_mode', + params: ['mode', 'apply_now'], + expect: {} }); -var callGetFilteringConfig = rpc.declare({ +// Service control +var callInstall = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_filtering_config' + method: 'install', + expect: {} }); -var callGetAllConfig = rpc.declare({ +var callStart = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_all_config' + method: 'start', + expect: {} }); -var callGetStats = rpc.declare({ +var callStop = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_stats' + method: 'stop', + expect: {} }); -var callGetRequests = rpc.declare({ +var callRestart = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_requests', - params: ['limit', 'category'] + method: 'restart', + expect: {} }); -var callGetTopHosts = rpc.declare({ +// Firewall control +var callSetupFirewall = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_top_hosts', - params: ['limit'] + method: 'setup_firewall', + expect: {} }); -var callGetCaInfo = rpc.declare({ +var callClearFirewall = rpc.declare({ object: 'luci.mitmproxy', - method: 'get_ca_info' -}); - -var callGetWebToken = rpc.declare({ - object: 'luci.mitmproxy', - method: 'get_web_token' -}); - -var callServiceStart = rpc.declare({ - object: 'luci.mitmproxy', - method: 'service_start' -}); - -var callServiceStop = rpc.declare({ - object: 'luci.mitmproxy', - method: 'service_stop' -}); - -var callServiceRestart = rpc.declare({ - object: 'luci.mitmproxy', - method: 'service_restart' -}); - -var callFirewallSetup = rpc.declare({ - object: 'luci.mitmproxy', - method: 'firewall_setup' -}); - -var callFirewallClear = rpc.declare({ - object: 'luci.mitmproxy', - method: 'firewall_clear' -}); - -var callSetConfig = rpc.declare({ - object: 'luci.mitmproxy', - method: 'set_config', - params: ['key', 'value'] -}); - -var callAddToList = rpc.declare({ - object: 'luci.mitmproxy', - method: 'add_to_list', - params: ['key', 'value'] -}); - -var callRemoveFromList = rpc.declare({ - object: 'luci.mitmproxy', - method: 'remove_from_list', - params: ['key', 'value'] -}); - -var callClearData = rpc.declare({ - object: 'luci.mitmproxy', - method: 'clear_data' + method: 'clear_firewall', + expect: {} }); return baseclass.extend({ getStatus: function() { - return callMitmproxy().catch(function() { - return { running: false, enabled: false }; - }); - }, - - getConfig: function() { - return callGetConfig().catch(function() { - return {}; - }); - }, - - getTransparentConfig: function() { - return callGetTransparentConfig().catch(function() { - return { enabled: false }; - }); - }, - - getWhitelistConfig: function() { - return callGetWhitelistConfig().catch(function() { - return { enabled: true, bypass_ip: [], bypass_domain: [] }; - }); - }, - - getFilteringConfig: function() { - return callGetFilteringConfig().catch(function() { - return { enabled: false }; - }); - }, - - getAllConfig: function() { - return callGetAllConfig().catch(function() { - return { main: {}, transparent: {}, whitelist: {}, filtering: {} }; - }); - }, - - getStats: function() { - return callGetStats().catch(function() { + return callStatus().catch(function() { return { - total_requests: 0, - unique_hosts: 0, - flow_file_size: 0, - cdn_requests: 0, - media_requests: 0, - blocked_ads: 0 + running: false, + enabled: false, + installed: false, + lxc_available: false, + mode: 'regular', + nft_active: false }; }); }, - getRequests: function(limit, category) { - return callGetRequests(limit || 50, category || 'all').catch(function() { - return { requests: [] }; + getSettings: function() { + return callSettings().catch(function() { + return { + enabled: false, + mode: 'regular', + proxy_port: 8888, + web_port: 8081, + web_host: '0.0.0.0', + data_path: '/srv/mitmproxy', + memory_limit: '256M', + transparent_enabled: false, + transparent_interface: 'br-lan', + redirect_http: true, + redirect_https: true, + filtering_enabled: false, + log_requests: true, + filter_cdn: false, + filter_media: false, + block_ads: false + }; }); }, - getTopHosts: function(limit) { - return callGetTopHosts(limit || 20).catch(function() { - return { hosts: [] }; - }); + saveSettings: function(settings) { + return callSaveSettings( + settings.mode, + settings.enabled, + settings.proxy_port, + settings.web_port, + settings.web_host, + settings.data_path, + settings.memory_limit, + settings.upstream_proxy, + settings.reverse_target, + settings.ssl_insecure, + settings.anticache, + settings.anticomp, + settings.transparent_enabled, + settings.transparent_interface, + settings.redirect_http, + settings.redirect_https, + settings.filtering_enabled, + settings.log_requests, + settings.filter_cdn, + settings.filter_media, + settings.block_ads, + settings.apply_now !== false + ); }, - getCaInfo: function() { - return callGetCaInfo().catch(function() { - return { installed: false }; - }); + setMode: function(mode, applyNow) { + return callSetMode(mode, applyNow !== false); }, - getWebToken: function() { - return callGetWebToken().catch(function() { - return { token: '', web_url: '', web_url_with_token: '' }; - }); + install: function() { + return callInstall(); }, - serviceStart: function() { - return callServiceStart(); + start: function() { + return callStart(); }, - serviceStop: function() { - return callServiceStop(); + stop: function() { + return callStop(); }, - serviceRestart: function() { - return callServiceRestart(); + restart: function() { + return callRestart(); }, - firewallSetup: function() { - return callFirewallSetup(); + setupFirewall: function() { + return callSetupFirewall(); }, - firewallClear: function() { - return callFirewallClear(); - }, - - setConfig: function(key, value) { - return callSetConfig(key, value); - }, - - addToList: function(key, value) { - return callAddToList(key, value); - }, - - removeFromList: function(key, value) { - return callRemoveFromList(key, value); - }, - - clearData: function() { - return callClearData(); + clearFirewall: function() { + return callClearFirewall(); }, getAllData: function() { var self = this; return Promise.all([ self.getStatus(), - self.getAllConfig(), - self.getStats(), - self.getTopHosts(10), - self.getCaInfo() + self.getSettings() ]).then(function(results) { return { status: results[0], - config: results[1].main || results[1], - allConfig: results[1], - stats: results[2], - topHosts: results[3], - caInfo: results[4] + settings: results[1] }; }); }, diff --git a/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy b/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy index 6a9e8005..8d55b719 100755 --- a/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy +++ b/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy @@ -1,41 +1,325 @@ #!/bin/sh # RPCD backend for mitmproxy LuCI app -CONFIG="mitmproxy" -CONTAINER="secbx-mitmproxy" +. /usr/share/libubox/jshn.sh -uci_get() { uci -q get ${CONFIG}.main.$1; } +CONFIG="mitmproxy" +LXC_NAME="mitmproxy" +LXC_PATH="/srv/lxc" +LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs" + +uci_get() { uci -q get ${CONFIG}.$1; } +uci_set() { uci set ${CONFIG}.$1="$2"; } get_status() { - local enabled=$(uci_get enabled) - local web_port=$(uci_get web_port) - local proxy_port=$(uci_get proxy_port) - local data_path=$(uci_get data_path) + local enabled=$(uci_get main.enabled) + local web_port=$(uci_get main.web_port) + local proxy_port=$(uci_get main.proxy_port) + local data_path=$(uci_get main.data_path) + local mode=$(uci_get main.mode) - local docker_available=0 - command -v docker >/dev/null 2>&1 && docker_available=1 + # Check for LXC availability + local lxc_available=0 + command -v lxc-start >/dev/null 2>&1 && lxc_available=1 + # Check if container is running local running=0 - if [ "$docker_available" = "1" ]; then - docker ps --filter "name=$CONTAINER" --format "{{.Names}}" 2>/dev/null | grep -q "$CONTAINER" && running=1 + if [ "$lxc_available" = "1" ]; then + lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING" && running=1 fi + # Check if installed (rootfs exists) local installed=0 - [ "$docker_available" = "1" ] && docker images --format "{{.Repository}}" 2>/dev/null | grep -q "mitmproxy" && installed=1 + [ -d "$LXC_ROOTFS" ] && [ -x "$LXC_ROOTFS/usr/bin/mitmproxy" ] && installed=1 + + # Check nftables status for transparent mode + local nft_active=0 + if [ "$mode" = "transparent" ] && command -v nft >/dev/null 2>&1; then + nft list table inet mitmproxy >/dev/null 2>&1 && nft_active=1 + fi cat </dev/null 2>&1 || { + uci set mitmproxy.main=mitmproxy + uci set mitmproxy.main.enabled='0' + uci set mitmproxy.main.mode='regular' + } + uci -q get mitmproxy.transparent >/dev/null 2>&1 || { + uci set mitmproxy.transparent=transparent + uci set mitmproxy.transparent.enabled='0' + } + uci -q get mitmproxy.filtering >/dev/null 2>&1 || { + uci set mitmproxy.filtering=filtering + uci set mitmproxy.filtering.enabled='0' + } + + # Apply main settings + [ -n "$mode" ] && uci_set main.mode "$mode" + [ -n "$enabled" ] && uci_set main.enabled "$enabled" + [ -n "$proxy_port" ] && uci_set main.proxy_port "$proxy_port" + [ -n "$web_port" ] && uci_set main.web_port "$web_port" + [ -n "$web_host" ] && uci_set main.web_host "$web_host" + [ -n "$data_path" ] && uci_set main.data_path "$data_path" + [ -n "$memory_limit" ] && uci_set main.memory_limit "$memory_limit" + [ -n "$upstream_proxy" ] && uci_set main.upstream_proxy "$upstream_proxy" + [ -n "$reverse_target" ] && uci_set main.reverse_target "$reverse_target" + [ -n "$ssl_insecure" ] && uci_set main.ssl_insecure "$ssl_insecure" + [ -n "$anticache" ] && uci_set main.anticache "$anticache" + [ -n "$anticomp" ] && uci_set main.anticomp "$anticomp" + + # Apply transparent settings + [ -n "$transparent_enabled" ] && uci_set transparent.enabled "$transparent_enabled" + [ -n "$transparent_interface" ] && uci_set transparent.interface "$transparent_interface" + [ -n "$redirect_http" ] && uci_set transparent.redirect_http "$redirect_http" + [ -n "$redirect_https" ] && uci_set transparent.redirect_https "$redirect_https" + + # Apply filtering settings + [ -n "$filtering_enabled" ] && uci_set filtering.enabled "$filtering_enabled" + [ -n "$log_requests" ] && uci_set filtering.log_requests "$log_requests" + [ -n "$filter_cdn" ] && uci_set filtering.filter_cdn "$filter_cdn" + [ -n "$filter_media" ] && uci_set filtering.filter_media "$filter_media" + [ -n "$block_ads" ] && uci_set filtering.block_ads "$block_ads" + + uci commit mitmproxy + + # Restart service to apply firewall rules if enabled and apply_now is set + local is_enabled=$(uci_get main.enabled) + local restarted=0 + if [ "$is_enabled" = "1" ] && [ "$apply_now" = "1" ]; then + /etc/init.d/mitmproxy restart >/dev/null 2>&1 & + restarted=1 + fi + + json_add_boolean "success" 1 + if [ "$restarted" = "1" ]; then + json_add_string "message" "Settings saved and applied" + json_add_boolean "restarted" 1 + else + json_add_string "message" "Settings saved" + json_add_boolean "restarted" 0 + fi + + json_dump +} + +set_mode() { + read input + json_load "$input" + + local mode apply_now + json_get_var mode mode + json_get_var apply_now apply_now + + json_init + + if [ -z "$mode" ]; then + json_add_boolean "success" 0 + json_add_string "error" "Mode is required" + json_dump + return + fi + + # Validate mode + case "$mode" in + regular|transparent|upstream|reverse) ;; + *) + json_add_boolean "success" 0 + json_add_string "error" "Invalid mode: $mode" + json_dump + return + ;; + esac + + # Ensure section exists + uci -q get mitmproxy.main >/dev/null 2>&1 || { + uci set mitmproxy.main=mitmproxy + uci set mitmproxy.main.enabled='0' + } + + uci_set main.mode "$mode" + uci commit mitmproxy + + # Restart to apply firewall rules if needed + local is_enabled=$(uci_get main.enabled) + local restarted=0 + if [ "$is_enabled" = "1" ] && [ "$apply_now" = "1" ]; then + /etc/init.d/mitmproxy restart >/dev/null 2>&1 & + restarted=1 + fi + + json_add_boolean "success" 1 + json_add_string "mode" "$mode" + if [ "$restarted" = "1" ]; then + json_add_string "message" "Mode set to $mode and applied" + json_add_boolean "restarted" 1 + else + json_add_string "message" "Mode set to $mode" + json_add_boolean "restarted" 0 + fi + + json_dump +} + +setup_firewall() { + json_init + + if ! command -v mitmproxyctl >/dev/null 2>&1; then + json_add_boolean "success" 0 + json_add_string "error" "mitmproxyctl not found" + json_dump + return + fi + + mitmproxyctl firewall-setup >/tmp/mitmproxy-fw.log 2>&1 + local result=$? + + if [ $result -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Firewall rules applied" + else + json_add_boolean "success" 0 + json_add_string "error" "Failed to setup firewall rules" + local log=$(cat /tmp/mitmproxy-fw.log 2>/dev/null) + [ -n "$log" ] && json_add_string "details" "$log" + fi + + json_dump +} + +clear_firewall() { + json_init + + if ! command -v mitmproxyctl >/dev/null 2>&1; then + json_add_boolean "success" 0 + json_add_string "error" "mitmproxyctl not found" + json_dump + return + fi + + mitmproxyctl firewall-clear >/tmp/mitmproxy-fw.log 2>&1 + local result=$? + + if [ $result -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Firewall rules cleared" + else + json_add_boolean "success" 0 + json_add_string "error" "Failed to clear firewall rules" + fi + + json_dump +} + do_install() { command -v mitmproxyctl >/dev/null 2>&1 && { mitmproxyctl install >/tmp/mitmproxy-install.log 2>&1 & echo '{"success":true,"message":"Installing"}'; } || echo '{"success":false,"error":"mitmproxyctl not found"}' } @@ -45,12 +329,26 @@ do_stop() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy stop >/dev/nul do_restart() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy restart >/dev/null 2>&1; echo '{"success":true}'; } list_methods() { cat <<'EOFM' -{"status":{},"install":{},"start":{},"stop":{},"restart":{}} +{"status":{},"settings":{},"save_settings":{"mode":"str","enabled":"bool","proxy_port":"int","web_port":"int","apply_now":"bool"},"set_mode":{"mode":"str","apply_now":"bool"},"setup_firewall":{},"clear_firewall":{},"install":{},"start":{},"stop":{},"restart":{}} EOFM } case "$1" in list) list_methods ;; - call) case "$2" in status) get_status ;; install) do_install ;; start) do_start ;; stop) do_stop ;; restart) do_restart ;; *) echo '{"error":"Unknown method"}' ;; esac ;; + call) + case "$2" in + status) get_status ;; + settings) get_settings ;; + save_settings) save_settings ;; + set_mode) set_mode ;; + setup_firewall) setup_firewall ;; + clear_firewall) clear_firewall ;; + install) do_install ;; + start) do_start ;; + stop) do_stop ;; + restart) do_restart ;; + *) echo '{"error":"Unknown method"}' ;; + esac + ;; *) echo '{"error":"Unknown command"}' ;; esac diff --git a/package/secubox/luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json b/package/secubox/luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json index 3bf19a5b..37ba6681 100644 --- a/package/secubox/luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json +++ b/package/secubox/luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json @@ -1,7 +1,26 @@ { "luci-app-mitmproxy": { "description": "Grant access to mitmproxy", - "read": { "ubus": { "luci.mitmproxy": ["status"] }, "uci": ["mitmproxy"] }, - "write": { "ubus": { "luci.mitmproxy": ["install", "start", "stop", "restart"] }, "uci": ["mitmproxy"] } + "read": { + "ubus": { + "luci.mitmproxy": ["status", "settings"] + }, + "uci": ["mitmproxy"] + }, + "write": { + "ubus": { + "luci.mitmproxy": [ + "install", + "start", + "stop", + "restart", + "save_settings", + "set_mode", + "setup_firewall", + "clear_firewall" + ] + }, + "uci": ["mitmproxy"] + } } } diff --git a/package/secubox/luci-app-network-tweaks/root/usr/libexec/rpcd/luci.network-tweaks b/package/secubox/luci-app-network-tweaks/root/usr/libexec/rpcd/luci.network-tweaks index b0cd404c..81033415 100755 --- a/package/secubox/luci-app-network-tweaks/root/usr/libexec/rpcd/luci.network-tweaks +++ b/package/secubox/luci-app-network-tweaks/root/usr/libexec/rpcd/luci.network-tweaks @@ -336,6 +336,15 @@ case "$1" in json_add_object "setAdGuardEnabled" json_add_string "enabled" "boolean" json_close_object + json_add_object "getOpkgSettings" + json_close_object + json_add_object "setOpkgIpv4" + json_add_string "enabled" "boolean" + json_close_object + json_add_object "testUrl" + json_add_string "url" "string" + json_add_string "timeout" "integer" + json_close_object json_dump ;; @@ -742,6 +751,98 @@ EOF json_dump ;; + getOpkgSettings) + # Get opkg network settings + json_init + json_add_boolean "success" 1 + + # Check if IPv4 is forced (via profile alias) + ipv4_forced=0 + if [ -f /etc/profile.d/opkg_ipv4.sh ]; then + grep -q "opkg.*-4\|force.*ipv4\|ipv4.*only" /etc/profile.d/opkg_ipv4.sh 2>/dev/null && ipv4_forced=1 + fi + + json_add_object "opkg" + json_add_boolean "ipv4_only" "$ipv4_forced" + json_close_object + + json_dump + ;; + + setOpkgIpv4) + # Enable/disable IPv4-only mode for opkg + read input + json_load "$input" + + json_get_var enabled enabled + + if [ "$enabled" = "1" ] || [ "$enabled" = "true" ]; then + # Create profile script to force IPv4 for opkg + cat > /etc/profile.d/opkg_ipv4.sh << 'OPKG_EOF' +# Force opkg to use IPv4 only (uclient-fetch -4) +# Useful when IPv6 connectivity is broken +alias opkg='/bin/opkg' +export UCLIENT_FETCH_OPTS="-4" +OPKG_EOF + chmod +x /etc/profile.d/opkg_ipv4.sh + + json_init + json_add_boolean "success" 1 + json_add_string "message" "opkg IPv4-only mode enabled" + else + # Remove IPv4 forcing + rm -f /etc/profile.d/opkg_ipv4.sh + + json_init + json_add_boolean "success" 1 + json_add_string "message" "opkg IPv4-only mode disabled" + fi + + json_dump + ;; + + testUrl) + # Test URL connectivity (for network diagnostics) + read input + json_load "$input" + + json_get_var url url + json_get_var timeout timeout + [ -z "$timeout" ] && timeout=5 + + json_init + + if [ -z "$url" ]; then + json_add_boolean "success" 0 + json_add_string "error" "URL is required" + json_dump + exit 0 + fi + + # Test URL with wget --spider (more reliable than uclient-fetch) + status_msg="" + start_time=$(date +%s) + + # Use wget with timeout and spider mode + if wget -4 --spider -q -T "$timeout" --no-check-certificate "$url" 2>/dev/null; then + status_msg="reachable" + else + status_msg="unreachable" + fi + + end_time=$(date +%s) + response_time=$((end_time - start_time)) + + json_add_boolean "success" 1 + json_add_object "result" + json_add_string "url" "$url" + json_add_string "status" "$status_msg" + json_add_int "response_time_s" "$response_time" + json_close_object + + json_dump + ;; + *) json_init json_add_boolean "success" 0 diff --git a/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry b/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry index 30bf6972..3e0ef564 100644 --- a/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry +++ b/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry @@ -1089,6 +1089,7 @@ check_port_firewall_open() { } # Get network connectivity info (public IPs, port accessibility) +# NOTE: External port checks disabled - too slow (HTTP requests to external services) method_get_network_info() { json_init json_add_boolean "success" 1 @@ -1097,89 +1098,69 @@ method_get_network_info() { lan_ip=$(get_lan_ip) json_add_string "lan_ip" "$lan_ip" - # Get public IPv4 + # Get public IPv4 (use uclient-fetch with IPv4 only for faster response) json_add_object "ipv4" local public_ipv4 - public_ipv4=$(get_public_ipv4) + public_ipv4=$(uclient-fetch -4 -q -T 2 -O - "http://ipv4.icanhazip.com" 2>/dev/null | tr -d '\n') if [ -n "$public_ipv4" ]; then json_add_string "address" "$public_ipv4" json_add_string "status" "ok" - - # Reverse DNS - local rdns - rdns=$(get_reverse_dns "$public_ipv4") - [ -n "$rdns" ] && json_add_string "hostname" "$rdns" else json_add_string "status" "unavailable" fi json_close_object - # Get public IPv6 + # IPv6 - skip network check (often broken/slow), just report firewall status json_add_object "ipv6" - local public_ipv6 - public_ipv6=$(get_public_ipv6) - if [ -n "$public_ipv6" ]; then - json_add_string "address" "$public_ipv6" - json_add_string "status" "ok" - - # Reverse DNS - local rdns6 - rdns6=$(get_reverse_dns "$public_ipv6") - [ -n "$rdns6" ] && json_add_string "hostname" "$rdns6" + # Check if IPv6 is enabled in network config + local ipv6_enabled=0 + if uci -q get network.wan6 >/dev/null 2>&1 || \ + uci -q get network.wan.ipv6 >/dev/null 2>&1; then + ipv6_enabled=1 + fi + if [ "$ipv6_enabled" = "1" ]; then + json_add_string "status" "configured" else - json_add_string "status" "unavailable" + json_add_string "status" "disabled" fi json_close_object - # External port accessibility (from internet perspective) + # External port accessibility - use firewall check only (fast) + # NOTE: Real external check would require slow HTTP requests to external services json_add_object "external_ports" - if [ -n "$public_ipv4" ]; then - # Check port 80 - json_add_object "http" - if check_external_port "$public_ipv4" 80; then - json_add_boolean "accessible" 1 - json_add_string "status" "open" - else - json_add_boolean "accessible" 0 - json_add_string "status" "blocked" - json_add_string "hint" "Check upstream router port forwarding" - fi - json_close_object + local http_fw=0 + local https_fw=0 + check_port_firewall_open 80 && http_fw=1 + check_port_firewall_open 443 && https_fw=1 - # Check port 443 - json_add_object "https" - if check_external_port "$public_ipv4" 443; then - json_add_boolean "accessible" 1 - json_add_string "status" "open" - else - json_add_boolean "accessible" 0 - json_add_string "status" "blocked" - json_add_string "hint" "Check upstream router port forwarding" - fi - json_close_object + json_add_object "http" + if [ "$http_fw" = "1" ]; then + json_add_string "status" "firewall_open" + json_add_string "hint" "Firewall allows port 80" else - json_add_object "http" - json_add_string "status" "unknown" - json_add_string "error" "No public IP" - json_close_object - json_add_object "https" - json_add_string "status" "unknown" - json_add_string "error" "No public IP" - json_close_object + json_add_string "status" "firewall_closed" + json_add_string "hint" "Add firewall rule for port 80" fi json_close_object + json_add_object "https" + if [ "$https_fw" = "1" ]; then + json_add_string "status" "firewall_open" + json_add_string "hint" "Firewall allows port 443" + else + json_add_string "status" "firewall_closed" + json_add_string "hint" "Add firewall rule for port 443" + fi + json_close_object + json_close_object + # Local firewall status json_add_object "firewall" - local http_open=0 - local https_open=0 - check_port_firewall_open 80 && http_open=1 - check_port_firewall_open 443 && https_open=1 - json_add_boolean "http_open" "$http_open" - json_add_boolean "https_open" "$https_open" - if [ "$http_open" = "1" ] && [ "$https_open" = "1" ]; then + json_add_boolean "http_open" "$http_fw" + json_add_boolean "https_open" "$https_fw" + if [ "$http_fw" = "1" ] && [ "$https_fw" = "1" ]; then json_add_string "status" "ok" - elif [ "$http_open" = "1" ] || [ "$https_open" = "1" ]; then + elif [ "$http_fw" = "1" ] || [ "$https_fw" = "1" ]; then json_add_string "status" "partial" else json_add_string "status" "closed" @@ -1226,20 +1207,13 @@ method_check_service_health() { json_add_string "service_id" "$service_id" json_add_string "domain" "$domain" - # Get public IPs for comparison - local public_ipv4 public_ipv6 - public_ipv4=$(get_public_ipv4) - public_ipv6=$(get_public_ipv6) + # Get public IPv4 (short timeout for responsiveness) + local public_ipv4 + public_ipv4=$(wget -qO- -T 3 "http://ipv4.icanhazip.com" 2>/dev/null | tr -d '\n') # Public IP info json_add_object "public_ip" - json_add_string "ipv4" "$public_ipv4" - [ -n "$public_ipv6" ] && json_add_string "ipv6" "$public_ipv6" - if [ -n "$public_ipv4" ]; then - local rdns - rdns=$(get_reverse_dns "$public_ipv4") - [ -n "$rdns" ] && json_add_string "hostname" "$rdns" - fi + json_add_string "ipv4" "${public_ipv4:-unknown}" json_close_object # DNS check with public IP comparison @@ -1275,27 +1249,21 @@ method_check_service_health() { fi json_close_object - # External port accessibility check + # External port accessibility check (firewall-based, fast) json_add_object "external_access" - if [ -n "$public_ipv4" ]; then - local http_ext=0 - local https_ext=0 - check_external_port "$public_ipv4" 80 && http_ext=1 - check_external_port "$public_ipv4" 443 && https_ext=1 - json_add_boolean "http_accessible" "$http_ext" - json_add_boolean "https_accessible" "$https_ext" - if [ "$http_ext" = "1" ] && [ "$https_ext" = "1" ]; then - json_add_string "status" "ok" - elif [ "$http_ext" = "1" ] || [ "$https_ext" = "1" ]; then - json_add_string "status" "partial" - json_add_string "hint" "Check upstream router/ISP port forwarding" - else - json_add_string "status" "blocked" - json_add_string "hint" "Ports not accessible from internet - check router/ISP" - fi + local http_fw=0 + local https_fw=0 + check_port_firewall_open 80 && http_fw=1 + check_port_firewall_open 443 && https_fw=1 + json_add_boolean "http_accessible" "$http_fw" + json_add_boolean "https_accessible" "$https_fw" + if [ "$http_fw" = "1" ] && [ "$https_fw" = "1" ]; then + json_add_string "status" "firewall_ok" + elif [ "$http_fw" = "1" ] || [ "$https_fw" = "1" ]; then + json_add_string "status" "partial" else - json_add_string "status" "unknown" - json_add_string "error" "Could not determine public IP" + json_add_string "status" "closed" + json_add_string "hint" "Open firewall ports 80/443 for external access" fi json_close_object diff --git a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/tor-shield/api.js b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/tor-shield/api.js index e659fd2b..1525f289 100644 --- a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/tor-shield/api.js +++ b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/tor-shield/api.js @@ -105,7 +105,7 @@ var callSettings = rpc.declare({ var callSaveSettings = rpc.declare({ object: 'luci.tor-shield', method: 'save_settings', - params: ['mode', 'dns_over_tor', 'kill_switch', 'socks_port', 'trans_port', 'dns_port', 'exit_nodes', 'exclude_exit_nodes', 'strict_nodes'], + params: ['mode', 'dns_over_tor', 'kill_switch', 'socks_port', 'trans_port', 'dns_port', 'exit_nodes', 'exclude_exit_nodes', 'strict_nodes', 'apply_now'], expect: { } }); @@ -173,8 +173,8 @@ return baseclass.extend({ getBridges: function() { return callBridges(); }, setBridges: function(enabled, type) { return callSetBridges(enabled, type); }, getSettings: function() { return callSettings(); }, - saveSettings: function(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes) { - return callSaveSettings(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes); + saveSettings: function(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes, apply_now) { + return callSaveSettings(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes, apply_now !== false ? '1' : '0'); }, formatBytes: formatBytes, diff --git a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/settings.js b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/settings.js index a0d1970e..cc96db84 100644 --- a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/settings.js +++ b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/settings.js @@ -10,7 +10,7 @@ return view.extend({ return api.getSettings(); }, - handleSave: function(form) { + handleSave: function(form, applyNow) { var self = this; // Gather form values @@ -27,7 +27,7 @@ return view.extend({ }; ui.showModal(_('Saving Settings'), [ - E('p', { 'class': 'spinning' }, _('Saving configuration...')) + E('p', { 'class': 'spinning' }, applyNow ? _('Saving and applying configuration...') : _('Saving configuration...')) ]); api.saveSettings( @@ -39,11 +39,16 @@ return view.extend({ settings.dns_port, settings.exit_nodes, settings.exclude_exit_nodes, - settings.strict_nodes + settings.strict_nodes, + applyNow ).then(function(result) { ui.hideModal(); if (result.success) { - ui.addNotification(null, E('p', _('Settings saved. Restart Tor Shield to apply changes.')), 'info'); + if (result.restarted) { + ui.addNotification(null, E('p', _('Settings saved and applied. Firewall rules updated.')), 'info'); + } else { + ui.addNotification(null, E('p', _('Settings saved. Restart Tor Shield to apply changes.')), 'info'); + } } else { ui.addNotification(null, E('p', result.error || _('Failed to save settings')), 'error'); } @@ -231,14 +236,22 @@ return view.extend({ ]), // Actions - E('div', { 'style': 'display: flex; gap: 12px; margin-top: 20px;' }, [ + E('div', { 'style': 'display: flex; gap: 12px; margin-top: 20px; flex-wrap: wrap;' }, [ E('button', { 'type': 'button', 'class': 'tor-btn tor-btn-primary', + 'style': 'background: linear-gradient(135deg, #059669 0%, #10b981 100%);', 'click': function() { - self.handleSave(document.getElementById('tor-settings-form')); + self.handleSave(document.getElementById('tor-settings-form'), true); } - }, ['\uD83D\uDCBE ', _('Save Settings')]), + }, ['\u26A1 ', _('Save & Apply')]), + E('button', { + 'type': 'button', + 'class': 'tor-btn', + 'click': function() { + self.handleSave(document.getElementById('tor-settings-form'), false); + } + }, ['\uD83D\uDCBE ', _('Save Only')]), E('a', { 'href': L.url('admin', 'services', 'tor-shield'), 'class': 'tor-btn' diff --git a/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield b/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield index a7d1e136..dcb85ba8 100755 --- a/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield +++ b/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield @@ -842,7 +842,7 @@ save_settings() { # Get values from input BEFORE json_init (which wipes loaded JSON) local mode dns_over_tor kill_switch socks_port trans_port dns_port - local exit_nodes exclude_exit strict_nodes + local exit_nodes exclude_exit strict_nodes apply_now json_get_var mode mode json_get_var dns_over_tor dns_over_tor json_get_var kill_switch kill_switch @@ -852,6 +852,7 @@ save_settings() { json_get_var exit_nodes exit_nodes json_get_var exclude_exit exclude_exit_nodes json_get_var strict_nodes strict_nodes + json_get_var apply_now apply_now # Now initialize output JSON json_init @@ -891,8 +892,25 @@ save_settings() { uci commit tor-shield + # Restart service to apply firewall rules if Tor is enabled and apply_now is set + local enabled + config_load "$CONFIG" + config_get enabled main enabled '0' + + local restarted=0 + if [ "$enabled" = "1" ] && [ "$apply_now" = "1" ]; then + /etc/init.d/tor-shield restart >/dev/null 2>&1 & + restarted=1 + fi + json_add_boolean "success" 1 - json_add_string "message" "Settings saved" + if [ "$restarted" = "1" ]; then + json_add_string "message" "Settings saved and applied" + json_add_boolean "restarted" 1 + else + json_add_string "message" "Settings saved" + json_add_boolean "restarted" 0 + fi json_dump } diff --git a/package/secubox/luci-app-wireguard-dashboard/README.md b/package/secubox/luci-app-wireguard-dashboard/README.md index f522a24e..b1902a9d 100644 --- a/package/secubox/luci-app-wireguard-dashboard/README.md +++ b/package/secubox/luci-app-wireguard-dashboard/README.md @@ -1,222 +1,170 @@ # LuCI WireGuard Dashboard -**Version:** 0.4.0 -**Last Updated:** 2025-12-28 -**Status:** Active - - -![Version](https://img.shields.io/badge/version-1.0.0-cyan) -![License](https://img.shields.io/badge/license-Apache--2.0-green) -![OpenWrt](https://img.shields.io/badge/OpenWrt-21.02+-orange) - -Modern and intuitive dashboard for WireGuard VPN monitoring on OpenWrt. Visualize tunnels, peers, and traffic in real-time. - -![Dashboard Preview](screenshots/dashboard-preview.png) +Modern WireGuard VPN management interface for OpenWrt with setup wizard, peer management, and real-time monitoring. ## Features -### ๐Ÿ” Tunnel Status -- Real-time interface monitoring -- Public key display -- Listen port and MTU info -- Interface state (up/down) - -### ๐Ÿ‘ฅ Peer Management -- Active/idle/inactive status -- Endpoint tracking -- Last handshake time -- Allowed IPs display -- Preshared key indicator - -### ๐Ÿ“Š Traffic Statistics -- Per-peer RX/TX bytes -- Per-interface totals -- Combined traffic view -- Visual progress bars - -### โš™๏ธ Configuration View -- WireGuard config syntax display -- Interface and peer sections -- Tunnel visualization -- UCI integration info - -### ๐ŸŽจ Modern Interface -- Cyan/blue VPN tunnel theme -- Animated status indicators -- Responsive grid layout -- Real-time updates - -## Screenshots - -### Status Overview -![Status](screenshots/status.png) - -### Peers List -![Peers](screenshots/peers.png) - -### Traffic Statistics -![Traffic](screenshots/traffic.png) - -### Configuration -![Config](screenshots/config.png) +- **Setup Wizard**: Create tunnels and peers in minutes with presets for common use cases +- **Dashboard Overview**: Real-time status of all tunnels and peers +- **Peer Management**: Add, remove, and configure peers with QR code generation +- **Traffic Monitoring**: Live bandwidth statistics per interface and peer +- **Client Config Export**: Generate configuration files and QR codes for mobile apps ## Installation -### Prerequisites - -- OpenWrt 21.02 or later -- WireGuard installed (`kmod-wireguard`, `wireguard-tools`) -- LuCI web interface - ```bash -# Install WireGuard opkg update -opkg install kmod-wireguard wireguard-tools luci-proto-wireguard +opkg install luci-app-wireguard-dashboard ``` -### From Source +### Dependencies + +- `wireguard-tools` - WireGuard userspace tools +- `luci-base` - LuCI web interface +- `qrencode` (optional) - For server-side QR code generation + +## Setup Wizard + +The wizard provides preset configurations for common VPN scenarios: + +### Tunnel Presets + +| Preset | Description | Default Port | Network | +|--------|-------------|--------------|---------| +| Road Warrior | Remote access for mobile users | 51820 | 10.10.0.0/24 | +| Site-to-Site | Connect two networks | 51821 | 10.20.0.0/24 | +| IoT Tunnel | Isolated tunnel for smart devices | 51822 | 10.30.0.0/24 | + +### Peer Zone Presets + +| Zone | Description | Tunnel Mode | +|------|-------------|-------------| +| Home User | Full network access | Full | +| Remote Worker | Office resources only | Split | +| Mobile Device | On-the-go access | Full | +| IoT Device | Limited VPN-only access | Split | +| Guest | Temporary visitor access | Full | +| Server/Site | Site-to-site connection | Split | + +### Wizard Flow + +1. **Select Tunnel Type** - Choose preset (Road Warrior, Site-to-Site, IoT) +2. **Configure Tunnel** - Set interface name, port, VPN network, public endpoint +3. **Select Peer Zones** - Choose which peer types to create +4. **Create** - Wizard generates keys, creates interface, adds peers, shows QR codes + +## RPCD API + +The dashboard communicates via `luci.wireguard-dashboard` RPCD object. + +### Methods + +| Method | Parameters | Description | +|--------|------------|-------------| +| `status` | - | Get overall WireGuard status | +| `interfaces` | - | List all WireGuard interfaces | +| `peers` | - | List all peers with status | +| `traffic` | - | Get traffic statistics | +| `generate_keys` | - | Generate new key pair + PSK | +| `create_interface` | name, private_key, listen_port, addresses, mtu | Create new WireGuard interface with firewall rules | +| `add_peer` | interface, name, allowed_ips, public_key, preshared_key, endpoint, persistent_keepalive | Add peer to interface | +| `remove_peer` | interface, public_key | Remove peer from interface | +| `interface_control` | interface, action (up/down/restart) | Control interface state | +| `generate_config` | interface, peer, private_key, endpoint | Generate client config file | +| `generate_qr` | interface, peer, private_key, endpoint | Generate QR code (requires qrencode) | + +### Example: Create Interface via CLI ```bash -# Clone into OpenWrt build environment -cd ~/openwrt/feeds/luci/applications/ -git clone https://github.com/gkerma/luci-app-wireguard-dashboard.git +# Generate keys +keys=$(ubus call luci.wireguard-dashboard generate_keys '{}') +privkey=$(echo "$keys" | jsonfilter -e '@.private_key') -# Update feeds and install -cd ~/openwrt -./scripts/feeds update -a -./scripts/feeds install -a - -# Enable in menuconfig -make menuconfig -# Navigate to: LuCI > Applications > luci-app-wireguard-dashboard - -# Build package -make package/luci-app-wireguard-dashboard/compile V=s +# Create interface +ubus call luci.wireguard-dashboard create_interface "{ + \"name\": \"wg0\", + \"private_key\": \"$privkey\", + \"listen_port\": \"51820\", + \"addresses\": \"10.10.0.1/24\", + \"mtu\": \"1420\" +}" ``` -### Manual Installation +### Example: Add Peer via CLI ```bash -# Transfer package to router -scp luci-app-wireguard-dashboard_1.0.0-1_all.ipk root@192.168.1.1:/tmp/ +# Generate peer keys +peer_keys=$(ubus call luci.wireguard-dashboard generate_keys '{}') +peer_pubkey=$(echo "$peer_keys" | jsonfilter -e '@.public_key') +peer_psk=$(echo "$peer_keys" | jsonfilter -e '@.preshared_key') -# Install on router -ssh root@192.168.1.1 -opkg install /tmp/luci-app-wireguard-dashboard_1.0.0-1_all.ipk - -# Restart services -/etc/init.d/rpcd restart +# Add peer +ubus call luci.wireguard-dashboard add_peer "{ + \"interface\": \"wg0\", + \"name\": \"Phone\", + \"allowed_ips\": \"10.10.0.2/32\", + \"public_key\": \"$peer_pubkey\", + \"preshared_key\": \"$peer_psk\", + \"persistent_keepalive\": \"25\" +}" ``` -## Usage +## Firewall Integration -After installation, access the dashboard at: +When creating an interface via the wizard or `create_interface` API, the following firewall rules are automatically created: -**VPN โ†’ WireGuard Dashboard** +1. **Zone** (`wg_`): INPUT/OUTPUT/FORWARD = ACCEPT +2. **Forwarding**: Bidirectional forwarding to/from `lan` zone +3. **WAN Rule**: Allow UDP traffic on listen port from WAN -The dashboard has four tabs: -1. **Status**: Overview with interfaces and active peers -2. **Peers**: Detailed peer information and status -3. **Traffic**: Bandwidth statistics per peer/interface -4. **Configuration**: Config file view and tunnel visualization +## File Locations -## Architecture +| File | Purpose | +|------|---------| +| `/usr/libexec/rpcd/luci.wireguard-dashboard` | RPCD backend | +| `/www/luci-static/resources/wireguard-dashboard/api.js` | JavaScript API wrapper | +| `/www/luci-static/resources/view/wireguard-dashboard/*.js` | LuCI views | +| `/usr/share/luci/menu.d/luci-app-wireguard-dashboard.json` | Menu configuration | +| `/usr/share/rpcd/acl.d/luci-app-wireguard-dashboard.json` | ACL permissions | -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ LuCI JavaScript โ”‚ -โ”‚ (status.js, peers.js, traffic.js) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ ubus RPC - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ RPCD Backend โ”‚ -โ”‚ /usr/libexec/rpcd/wireguard-dashboard โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ executes - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ wg show โ”‚ -โ”‚ WireGuard CLI Tool โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ manages - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ WireGuard Kernel Module โ”‚ -โ”‚ Encrypted Tunnels โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +## Troubleshooting + +### Interface not coming up + +```bash +# Check interface status +wg show wg0 + +# Check UCI configuration +uci show network.wg0 + +# Manually bring up +ifup wg0 + +# Check logs +logread | grep -i wireguard ``` -## API Endpoints +### Peers not connecting -| Method | Description | -|--------|-------------| -| `status` | Overall VPN status, interface/peer counts, total traffic | -| `interfaces` | Detailed interface info (pubkey, port, IPs, state) | -| `peers` | All peers with endpoint, handshake, traffic, allowed IPs | -| `traffic` | Per-peer and per-interface RX/TX statistics | -| `config` | Configuration display (no private keys exposed) | +1. Verify firewall port is open: `iptables -L -n | grep 51820` +2. Check endpoint is reachable from client +3. Verify allowed_ips match on both ends +4. Check for NAT issues - enable PersistentKeepalive -## Peer Status Indicators +### QR codes not generating -| Status | Meaning | Handshake Age | -|--------|---------|---------------| -| ๐ŸŸข Active | Recent communication | < 3 minutes | -| ๐ŸŸก Idle | No recent traffic | 3-10 minutes | -| โšช Inactive | No handshake | > 10 minutes or never | +Install qrencode for server-side QR generation: +```bash +opkg install qrencode +``` -## Requirements - -- OpenWrt 21.02+ -- `kmod-wireguard` (kernel module) -- `wireguard-tools` (wg command) -- `luci-proto-wireguard` (optional, for LuCI config) -- LuCI (luci-base) -- rpcd with luci module - -## Dependencies - -- `luci-base` -- `luci-lib-jsonc` -- `rpcd` -- `rpcd-mod-luci` -- `wireguard-tools` - -## Security Notes - -- Private keys are **never** exposed through the dashboard -- Only public keys and configuration are displayed -- All data is read-only (no config modifications) -- RPCD ACLs restrict access to authorized users - -## Contributing - -Contributions are welcome! Please feel free to submit issues and pull requests. - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request +The dashboard also supports client-side QR generation via JavaScript (no server dependency). ## License -This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. +Apache-2.0 -## Credits +## Author -- Powered by [WireGuardยฎ](https://www.wireguard.com/) -- Built for [OpenWrt](https://openwrt.org/) -- Developed by [Gandalf @ CyberMind.fr](https://cybermind.fr) - -## Related Projects - -- [luci-proto-wireguard](https://github.com/openwrt/luci) - WireGuard protocol support -- [wg-easy](https://github.com/WeeJeWel/wg-easy) - Web UI for WireGuard -- [wireguard-ui](https://github.com/ngoduykhanh/wireguard-ui) - Another WireGuard UI - ---- - -Made with ๐Ÿ” for secure networking - -*WireGuard is a registered trademark of Jason A. Donenfeld.* +CyberMind.fr - SecuBox Project diff --git a/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/peers.js b/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/peers.js index 7984f2d8..eddb6dc2 100644 --- a/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/peers.js +++ b/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/peers.js @@ -43,8 +43,11 @@ return view.extend({ render: function(data) { var self = this; - var peers = (data[0] || {}).peers || []; - var interfaces = (data[1] || {}).interfaces || []; + // Handle RPC expect unwrapping - results may be array or object with .peers/.interfaces + var peersData = data[0] || []; + var interfacesData = data[1] || []; + var peers = Array.isArray(peersData) ? peersData : (peersData.peers || []); + var interfaces = Array.isArray(interfacesData) ? interfacesData : (interfacesData.interfaces || []); var activePeers = peers.filter(function(p) { return p.status === 'active'; }).length; var view = E('div', { 'class': 'cbi-map' }, [ diff --git a/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/settings.js b/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/settings.js index d86d90f1..a9cfb461 100644 --- a/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/settings.js +++ b/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/settings.js @@ -14,7 +14,9 @@ return view.extend({ render: function(data) { var status = data[0] || {}; - var interfaces = (data[1] || {}).interfaces || []; + // Handle RPC expect unwrapping - results may be array or object + var interfacesData = data[1] || []; + var interfaces = Array.isArray(interfacesData) ? interfacesData : (interfacesData.interfaces || []); var view = E('div', { 'class': 'cbi-map' }, [ E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }), diff --git a/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/wizard.js b/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/wizard.js index 1df57baa..e12f97f9 100644 --- a/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/wizard.js +++ b/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/wizard.js @@ -137,7 +137,9 @@ return view.extend({ render: function(data) { var self = this; - var interfaces = (data[0] || {}).interfaces || []; + // Handle RPC expect unwrapping - results may be array or object + var interfacesData = data[0] || []; + var interfaces = Array.isArray(interfacesData) ? interfacesData : (interfacesData.interfaces || []); var status = data[1] || {}; var publicIP = data[2] || ''; @@ -569,20 +571,33 @@ return view.extend({ createInterface: function() { var self = this; var data = this.wizardData; + var netmask = data.vpnNetwork.split('/')[1] || '24'; + var addresses = data.serverIP + '/' + netmask; - // Call backend to create interface - return rpc.call('uci', 'add', { - config: 'network', - type: 'interface', - name: data.ifaceName, - values: { - proto: 'wireguard', - private_key: data.privateKey, - listen_port: data.listenPort, - addresses: [data.serverIP + '/' + data.vpnNetwork.split('/')[1]] + // Call backend to create interface using proper RPCD method + return api.createInterface( + data.ifaceName, + data.privateKey, + data.listenPort, + addresses, + data.mtu || '1420' + ).then(function(result) { + // Handle various response formats from RPC + // Result could be: boolean, object with success field, or object with error + if (result === true) { + return { success: true }; } - }).then(function() { - return rpc.call('uci', 'commit', { config: 'network' }); + if (result === false) { + throw new Error('Failed to create interface'); + } + if (typeof result === 'object' && result !== null) { + if (result.success === false || result.error) { + throw new Error(result.error || 'Failed to create interface'); + } + return result; + } + // If we got here with no error, assume success + return { success: true }; }); }, diff --git a/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/wireguard-dashboard/api.js b/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/wireguard-dashboard/api.js index 2fa4d950..20118b43 100644 --- a/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/wireguard-dashboard/api.js +++ b/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/wireguard-dashboard/api.js @@ -34,18 +34,25 @@ var callGenerateKeys = rpc.declare({ expect: { } }); +var callCreateInterface = rpc.declare({ + object: 'luci.wireguard-dashboard', + method: 'create_interface', + params: ['name', 'private_key', 'listen_port', 'addresses', 'mtu'], + expect: { } +}); + var callAddPeer = rpc.declare({ object: 'luci.wireguard-dashboard', method: 'add_peer', params: ['interface', 'name', 'allowed_ips', 'public_key', 'preshared_key', 'endpoint', 'persistent_keepalive'], - expect: { success: false } + expect: { } }); var callRemovePeer = rpc.declare({ object: 'luci.wireguard-dashboard', method: 'remove_peer', params: ['interface', 'public_key'], - expect: { success: false } + expect: { } }); var callGetConfig = rpc.declare({ @@ -78,7 +85,7 @@ var callInterfaceControl = rpc.declare({ object: 'luci.wireguard-dashboard', method: 'interface_control', params: ['interface', 'action'], - expect: { success: false } + expect: { } }); var callPeerDescriptions = rpc.declare({ @@ -154,6 +161,7 @@ return baseclass.extend({ getConfig: callGetConfig, getTraffic: callGetTraffic, generateKeys: callGenerateKeys, + createInterface: callCreateInterface, addPeer: callAddPeer, removePeer: callRemovePeer, generateConfig: callGenerateConfig, diff --git a/package/secubox/luci-app-wireguard-dashboard/root/usr/libexec/rpcd/luci.wireguard-dashboard b/package/secubox/luci-app-wireguard-dashboard/root/usr/libexec/rpcd/luci.wireguard-dashboard index 79dc5daa..43788981 100755 --- a/package/secubox/luci-app-wireguard-dashboard/root/usr/libexec/rpcd/luci.wireguard-dashboard +++ b/package/secubox/luci-app-wireguard-dashboard/root/usr/libexec/rpcd/luci.wireguard-dashboard @@ -387,6 +387,114 @@ generate_keys() { json_dump } +# Create a new WireGuard interface +create_interface() { + read input + json_load "$input" + json_get_var name name + json_get_var private_key private_key + json_get_var listen_port listen_port + json_get_var addresses addresses + json_get_var mtu mtu + + json_init + + # Validate required fields + if [ -z "$name" ] || [ -z "$private_key" ]; then + json_add_boolean "success" 0 + json_add_string "error" "Missing required fields: name and private_key" + json_dump + return + fi + + # Check if interface already exists + if uci -q get network.$name >/dev/null 2>&1; then + json_add_boolean "success" 0 + json_add_string "error" "Interface $name already exists" + json_dump + return + fi + + # Validate private key format (base64, 44 chars ending with =) + if ! echo "$private_key" | grep -qE '^[A-Za-z0-9+/]{43}=$'; then + json_add_boolean "success" 0 + json_add_string "error" "Invalid private key format" + json_dump + return + fi + + # Default values + [ -z "$listen_port" ] && listen_port="51820" + [ -z "$mtu" ] && mtu="1420" + + # Create the interface in UCI + uci set network.$name=interface + uci set network.$name.proto='wireguard' + uci set network.$name.private_key="$private_key" + uci set network.$name.listen_port="$listen_port" + uci set network.$name.mtu="$mtu" + + # Set addresses (can be multiple) + if [ -n "$addresses" ]; then + uci delete network.$name.addresses 2>/dev/null + for addr in $addresses; do + uci add_list network.$name.addresses="$addr" + done + fi + + # Commit changes + if uci commit network; then + # Create firewall zone for the interface + local zone_name="wg_${name}" + local zone_exists=$(uci show firewall 2>/dev/null | grep "\.name='$zone_name'" | head -1) + + if [ -z "$zone_exists" ]; then + # Add new firewall zone + local zone_section=$(uci add firewall zone) + uci set firewall.$zone_section.name="$zone_name" + uci set firewall.$zone_section.input='ACCEPT' + uci set firewall.$zone_section.output='ACCEPT' + uci set firewall.$zone_section.forward='ACCEPT' + uci add_list firewall.$zone_section.network="$name" + + # Add forwarding to lan + local fwd_section=$(uci add firewall forwarding) + uci set firewall.$fwd_section.src="$zone_name" + uci set firewall.$fwd_section.dest='lan' + + # Add forwarding from lan + local fwd2_section=$(uci add firewall forwarding) + uci set firewall.$fwd2_section.src='lan' + uci set firewall.$fwd2_section.dest="$zone_name" + + # Add rule to allow WireGuard port from wan + local rule_section=$(uci add firewall rule) + uci set firewall.$rule_section.name="Allow-WireGuard-$name" + uci set firewall.$rule_section.src='wan' + uci set firewall.$rule_section.dest_port="$listen_port" + uci set firewall.$rule_section.proto='udp' + uci set firewall.$rule_section.target='ACCEPT' + + uci commit firewall + fi + + # Bring up the interface + ifup "$name" 2>/dev/null & + + # Reload firewall + /etc/init.d/firewall reload 2>/dev/null & + + json_add_boolean "success" 1 + json_add_string "message" "Interface $name created successfully" + json_add_string "interface" "$name" + else + json_add_boolean "success" 0 + json_add_string "error" "Failed to commit network configuration" + fi + + json_dump +} + # Add a new peer to interface add_peer() { read input @@ -893,7 +1001,7 @@ get_bandwidth_rates() { # Main dispatcher case "$1" in list) - echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"},"interface_control":{"interface":"str","action":"str"},"peer_descriptions":{},"bandwidth_rates":{}}' + echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"create_interface":{"name":"str","private_key":"str","listen_port":"str","addresses":"str","mtu":"str"},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"},"interface_control":{"interface":"str","action":"str"},"peer_descriptions":{},"bandwidth_rates":{}}' ;; call) case "$2" in @@ -915,6 +1023,9 @@ case "$1" in generate_keys) generate_keys ;; + create_interface) + create_interface + ;; add_peer) add_peer ;; diff --git a/package/secubox/luci-app-wireguard-dashboard/root/usr/share/rpcd/acl.d/luci-app-wireguard-dashboard.json b/package/secubox/luci-app-wireguard-dashboard/root/usr/share/rpcd/acl.d/luci-app-wireguard-dashboard.json index 7128a566..f94018b2 100644 --- a/package/secubox/luci-app-wireguard-dashboard/root/usr/share/rpcd/acl.d/luci-app-wireguard-dashboard.json +++ b/package/secubox/luci-app-wireguard-dashboard/root/usr/share/rpcd/acl.d/luci-app-wireguard-dashboard.json @@ -27,6 +27,7 @@ "ubus": { "luci.wireguard-dashboard": [ "generate_keys", + "create_interface", "add_peer", "remove_peer", "generate_config", diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-core b/package/secubox/secubox-core/root/usr/sbin/secubox-core index 4b0ad77b..d17ece84 100755 --- a/package/secubox/secubox-core/root/usr/sbin/secubox-core +++ b/package/secubox/secubox-core/root/usr/sbin/secubox-core @@ -5,7 +5,7 @@ # Main orchestration service for SecuBox framework # -set -e +# Note: Not using set -e as many UCI/service checks legitimately return non-zero . /lib/functions.sh . /usr/share/libubox/jshn.sh @@ -18,7 +18,57 @@ WATCHDOG_STATE="/var/run/secubox/watchdog.json" # Services to monitor (init.d name:check_method:restart_delay) # check_method: pid, docker, lxc, port:PORT -MONITORED_SERVICES="haproxy:pid:5 crowdsec:pid:10 tor:pid:10" +MONITORED_SERVICES="" + +# Auto-discover SecuBox services from ctl scripts +discover_secubox_services() { + local services="" + + # Discover LXC-based services from *ctl scripts + for ctl in /usr/sbin/*ctl; do + [ -x "$ctl" ] || continue + local basename=$(basename "$ctl") + + # Extract service name from xxxctl pattern + local svc_name="" + case "$basename" in + haproxyctl) + svc_name="haproxy" + services="$services haproxy:lxc:5" + ;; + lyrionctl) + svc_name="lyrion" + services="$services lyrion:lxc:10" + ;; + mitmproxyctl) + svc_name="mitmproxy" + services="$services mitmproxy:lxc:10" + ;; + metablogizerctl) + svc_name="metablogizer" + services="$services metablogizer:lxc:10" + ;; + hexojsctl) + svc_name="hexojs" + services="$services hexojs:lxc:10" + ;; + adguardhomectl) + svc_name="adguardhome" + services="$services adguardhome:docker:10" + ;; + esac + done + + # Add native services (PID-based) + if [ -x "/etc/init.d/crowdsec" ]; then + services="$services crowdsec:pid:10" + fi + if [ -x "/etc/init.d/tor" ]; then + services="$services tor:pid:10" + fi + + echo "$services" +} # Logging function log() { @@ -172,10 +222,14 @@ run_watchdog() { local watchdog_enabled=$(uci -q get secubox.main.watchdog_enabled || echo "1") [ "$watchdog_enabled" != "1" ] && return 0 - # Get monitored services from UCI or use defaults - local services=$(uci -q get secubox.main.watchdog_services || echo "$MONITORED_SERVICES") + # Auto-discover services if none configured + local services=$(uci -q get secubox.main.watchdog_services) + [ -z "$services" ] && services=$(discover_secubox_services) + local restart_count=0 - local status_json="" + local checked_count=0 + local running_count=0 + local services_status="" log debug "Watchdog: Checking services..." @@ -184,66 +238,105 @@ run_watchdog() { local check_method=$(echo "$service_entry" | cut -d: -f2) local restart_delay=$(echo "$service_entry" | cut -d: -f3) [ -z "$restart_delay" ] && restart_delay=5 + [ -z "$check_method" ] && check_method="pid" - # Check if service init script exists - [ ! -x "/etc/init.d/$service_name" ] && continue + # Determine if service is enabled (check ctl script or init.d) + local ctl_script="/usr/sbin/${service_name}ctl" + local init_script="/etc/init.d/$service_name" + local is_enabled=false - # Check if service is supposed to be enabled - /etc/init.d/$service_name enabled >/dev/null 2>&1 || continue + if [ -x "$ctl_script" ]; then + # Check via UCI for LXC/Docker services + local uci_enabled=$(uci -q get "$service_name.main.enabled" 2>/dev/null || echo "0") + [ "$uci_enabled" = "1" ] && is_enabled=true + elif [ -x "$init_script" ]; then + $init_script enabled >/dev/null 2>&1 && is_enabled=true + fi + # Skip disabled services + [ "$is_enabled" = "false" ] && continue + + checked_count=$((checked_count + 1)) local is_running=false + local status_detail="" case "$check_method" in pid) - # Check via pidof or pgrep + # Check via pgrep if pgrep "$service_name" >/dev/null 2>&1; then is_running=true + status_detail="pid=$(pgrep -o "$service_name")" fi ;; docker) - # Check Docker container - if docker ps --filter "name=$service_name" --format "{{.Names}}" 2>/dev/null | grep -q "$service_name"; then + # Check Docker container (secbx- prefix) + local container_name="secbx-${service_name}" + if docker ps --filter "name=$container_name" --format "{{.Names}}" 2>/dev/null | grep -q "$container_name"; then is_running=true + status_detail="container=$container_name" fi ;; lxc) # Check LXC container if lxc-info -n "$service_name" -s 2>/dev/null | grep -q "RUNNING"; then is_running=true + # Get container IP if available + local lxc_ip=$(lxc-info -n "$service_name" -i 2>/dev/null | awk '{print $2}' | head -1) + [ -n "$lxc_ip" ] && status_detail="ip=$lxc_ip" fi ;; port:*) # Check if port is listening local port=$(echo "$check_method" | cut -d: -f2) - # Use /proc/net/tcp (ports in hex) local port_hex=$(printf '%04X' "$port") if grep -q ":$port_hex " /proc/net/tcp /proc/net/tcp6 2>/dev/null; then is_running=true + status_detail="port=$port" fi ;; esac - if [ "$is_running" = "false" ]; then + if [ "$is_running" = "true" ]; then + running_count=$((running_count + 1)) + services_status="$services_status ${service_name}:ok" + else + services_status="$services_status ${service_name}:down" log warn "Watchdog: $service_name is down, restarting..." sleep "$restart_delay" # Double-check before restart (service might have recovered) case "$check_method" in - pid) pgrep "$service_name" >/dev/null 2>&1 && continue ;; + pid) pgrep "$service_name" >/dev/null 2>&1 && { running_count=$((running_count + 1)); continue; } ;; + lxc) lxc-info -n "$service_name" -s 2>/dev/null | grep -q "RUNNING" && { running_count=$((running_count + 1)); continue; } ;; esac - /etc/init.d/$service_name restart >/dev/null 2>&1 + # Restart using ctl script if available, otherwise init.d + if [ -x "$ctl_script" ]; then + $ctl_script restart >/dev/null 2>&1 & + elif [ -x "$init_script" ]; then + $init_script restart >/dev/null 2>&1 & + fi restart_count=$((restart_count + 1)) - # Log restart event log info "Watchdog: Restarted $service_name" fi done - # Save watchdog state + # Save detailed watchdog state json_init json_add_string "last_check" "$(date -Iseconds)" json_add_int "restarts" "$restart_count" + json_add_int "checked" "$checked_count" + json_add_int "running" "$running_count" + + json_add_object "services" + for svc_status in $services_status; do + local svc=$(echo "$svc_status" | cut -d: -f1) + local status=$(echo "$svc_status" | cut -d: -f2) + json_add_string "$svc" "$status" + done + json_close_object + json_dump > "$WATCHDOG_STATE" 2>/dev/null return 0