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 <noreply@anthropic.com>
This commit is contained in:
parent
8207c05376
commit
413ebb45a0
@ -2,237 +2,176 @@
|
|||||||
'require baseclass';
|
'require baseclass';
|
||||||
'require rpc';
|
'require rpc';
|
||||||
|
|
||||||
var callMitmproxy = rpc.declare({
|
// Status and settings
|
||||||
|
var callStatus = rpc.declare({
|
||||||
object: 'luci.mitmproxy',
|
object: 'luci.mitmproxy',
|
||||||
method: 'get_status'
|
method: 'status',
|
||||||
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
var callGetConfig = rpc.declare({
|
var callSettings = rpc.declare({
|
||||||
object: 'luci.mitmproxy',
|
object: 'luci.mitmproxy',
|
||||||
method: 'get_config'
|
method: 'settings',
|
||||||
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
var callGetTransparentConfig = rpc.declare({
|
var callSaveSettings = rpc.declare({
|
||||||
object: 'luci.mitmproxy',
|
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',
|
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',
|
object: 'luci.mitmproxy',
|
||||||
method: 'get_filtering_config'
|
method: 'install',
|
||||||
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
var callGetAllConfig = rpc.declare({
|
var callStart = rpc.declare({
|
||||||
object: 'luci.mitmproxy',
|
object: 'luci.mitmproxy',
|
||||||
method: 'get_all_config'
|
method: 'start',
|
||||||
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
var callGetStats = rpc.declare({
|
var callStop = rpc.declare({
|
||||||
object: 'luci.mitmproxy',
|
object: 'luci.mitmproxy',
|
||||||
method: 'get_stats'
|
method: 'stop',
|
||||||
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
var callGetRequests = rpc.declare({
|
var callRestart = rpc.declare({
|
||||||
object: 'luci.mitmproxy',
|
object: 'luci.mitmproxy',
|
||||||
method: 'get_requests',
|
method: 'restart',
|
||||||
params: ['limit', 'category']
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
var callGetTopHosts = rpc.declare({
|
// Firewall control
|
||||||
|
var callSetupFirewall = rpc.declare({
|
||||||
object: 'luci.mitmproxy',
|
object: 'luci.mitmproxy',
|
||||||
method: 'get_top_hosts',
|
method: 'setup_firewall',
|
||||||
params: ['limit']
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
var callGetCaInfo = rpc.declare({
|
var callClearFirewall = rpc.declare({
|
||||||
object: 'luci.mitmproxy',
|
object: 'luci.mitmproxy',
|
||||||
method: 'get_ca_info'
|
method: 'clear_firewall',
|
||||||
});
|
expect: {}
|
||||||
|
|
||||||
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'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return baseclass.extend({
|
return baseclass.extend({
|
||||||
getStatus: function() {
|
getStatus: function() {
|
||||||
return callMitmproxy().catch(function() {
|
return callStatus().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 {
|
return {
|
||||||
total_requests: 0,
|
running: false,
|
||||||
unique_hosts: 0,
|
enabled: false,
|
||||||
flow_file_size: 0,
|
installed: false,
|
||||||
cdn_requests: 0,
|
lxc_available: false,
|
||||||
media_requests: 0,
|
mode: 'regular',
|
||||||
blocked_ads: 0
|
nft_active: false
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getRequests: function(limit, category) {
|
getSettings: function() {
|
||||||
return callGetRequests(limit || 50, category || 'all').catch(function() {
|
return callSettings().catch(function() {
|
||||||
return { requests: [] };
|
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) {
|
saveSettings: function(settings) {
|
||||||
return callGetTopHosts(limit || 20).catch(function() {
|
return callSaveSettings(
|
||||||
return { hosts: [] };
|
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() {
|
setMode: function(mode, applyNow) {
|
||||||
return callGetCaInfo().catch(function() {
|
return callSetMode(mode, applyNow !== false);
|
||||||
return { installed: false };
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getWebToken: function() {
|
install: function() {
|
||||||
return callGetWebToken().catch(function() {
|
return callInstall();
|
||||||
return { token: '', web_url: '', web_url_with_token: '' };
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
serviceStart: function() {
|
start: function() {
|
||||||
return callServiceStart();
|
return callStart();
|
||||||
},
|
},
|
||||||
|
|
||||||
serviceStop: function() {
|
stop: function() {
|
||||||
return callServiceStop();
|
return callStop();
|
||||||
},
|
},
|
||||||
|
|
||||||
serviceRestart: function() {
|
restart: function() {
|
||||||
return callServiceRestart();
|
return callRestart();
|
||||||
},
|
},
|
||||||
|
|
||||||
firewallSetup: function() {
|
setupFirewall: function() {
|
||||||
return callFirewallSetup();
|
return callSetupFirewall();
|
||||||
},
|
},
|
||||||
|
|
||||||
firewallClear: function() {
|
clearFirewall: function() {
|
||||||
return callFirewallClear();
|
return callClearFirewall();
|
||||||
},
|
|
||||||
|
|
||||||
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();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getAllData: function() {
|
getAllData: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
self.getStatus(),
|
self.getStatus(),
|
||||||
self.getAllConfig(),
|
self.getSettings()
|
||||||
self.getStats(),
|
|
||||||
self.getTopHosts(10),
|
|
||||||
self.getCaInfo()
|
|
||||||
]).then(function(results) {
|
]).then(function(results) {
|
||||||
return {
|
return {
|
||||||
status: results[0],
|
status: results[0],
|
||||||
config: results[1].main || results[1],
|
settings: results[1]
|
||||||
allConfig: results[1],
|
|
||||||
stats: results[2],
|
|
||||||
topHosts: results[3],
|
|
||||||
caInfo: results[4]
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,41 +1,325 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# RPCD backend for mitmproxy LuCI app
|
# RPCD backend for mitmproxy LuCI app
|
||||||
|
|
||||||
CONFIG="mitmproxy"
|
. /usr/share/libubox/jshn.sh
|
||||||
CONTAINER="secbx-mitmproxy"
|
|
||||||
|
|
||||||
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() {
|
get_status() {
|
||||||
local enabled=$(uci_get enabled)
|
local enabled=$(uci_get main.enabled)
|
||||||
local web_port=$(uci_get web_port)
|
local web_port=$(uci_get main.web_port)
|
||||||
local proxy_port=$(uci_get proxy_port)
|
local proxy_port=$(uci_get main.proxy_port)
|
||||||
local data_path=$(uci_get data_path)
|
local data_path=$(uci_get main.data_path)
|
||||||
|
local mode=$(uci_get main.mode)
|
||||||
|
|
||||||
local docker_available=0
|
# Check for LXC availability
|
||||||
command -v docker >/dev/null 2>&1 && docker_available=1
|
local lxc_available=0
|
||||||
|
command -v lxc-start >/dev/null 2>&1 && lxc_available=1
|
||||||
|
|
||||||
|
# Check if container is running
|
||||||
local running=0
|
local running=0
|
||||||
if [ "$docker_available" = "1" ]; then
|
if [ "$lxc_available" = "1" ]; then
|
||||||
docker ps --filter "name=$CONTAINER" --format "{{.Names}}" 2>/dev/null | grep -q "$CONTAINER" && running=1
|
lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING" && running=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check if installed (rootfs exists)
|
||||||
local installed=0
|
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 <<EOFJ
|
cat <<EOFJ
|
||||||
{
|
{
|
||||||
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
||||||
"running": $([ "$running" = "1" ] && echo "true" || echo "false"),
|
"running": $([ "$running" = "1" ] && echo "true" || echo "false"),
|
||||||
"installed": $([ "$installed" = "1" ] && echo "true" || echo "false"),
|
"installed": $([ "$installed" = "1" ] && echo "true" || echo "false"),
|
||||||
"docker_available": $([ "$docker_available" = "1" ] && echo "true" || echo "false"),
|
"lxc_available": $([ "$lxc_available" = "1" ] && echo "true" || echo "false"),
|
||||||
|
"docker_available": $([ "$lxc_available" = "1" ] && echo "true" || echo "false"),
|
||||||
"web_port": ${web_port:-8081},
|
"web_port": ${web_port:-8081},
|
||||||
"proxy_port": ${proxy_port:-8080},
|
"proxy_port": ${proxy_port:-8888},
|
||||||
"data_path": "${data_path:-/srv/mitmproxy}"
|
"data_path": "${data_path:-/srv/mitmproxy}",
|
||||||
|
"mode": "${mode:-regular}",
|
||||||
|
"nft_active": $([ "$nft_active" = "1" ] && echo "true" || echo "false")
|
||||||
}
|
}
|
||||||
EOFJ
|
EOFJ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_settings() {
|
||||||
|
json_init
|
||||||
|
|
||||||
|
# Main settings
|
||||||
|
local enabled=$(uci_get main.enabled)
|
||||||
|
local mode=$(uci_get main.mode)
|
||||||
|
local proxy_port=$(uci_get main.proxy_port)
|
||||||
|
local web_port=$(uci_get main.web_port)
|
||||||
|
local web_host=$(uci_get main.web_host)
|
||||||
|
local data_path=$(uci_get main.data_path)
|
||||||
|
local memory_limit=$(uci_get main.memory_limit)
|
||||||
|
local upstream_proxy=$(uci_get main.upstream_proxy)
|
||||||
|
local reverse_target=$(uci_get main.reverse_target)
|
||||||
|
local ssl_insecure=$(uci_get main.ssl_insecure)
|
||||||
|
local anticache=$(uci_get main.anticache)
|
||||||
|
local anticomp=$(uci_get main.anticomp)
|
||||||
|
|
||||||
|
json_add_boolean "enabled" "${enabled:-0}"
|
||||||
|
json_add_string "mode" "${mode:-regular}"
|
||||||
|
json_add_int "proxy_port" "${proxy_port:-8888}"
|
||||||
|
json_add_int "web_port" "${web_port:-8081}"
|
||||||
|
json_add_string "web_host" "${web_host:-0.0.0.0}"
|
||||||
|
json_add_string "data_path" "${data_path:-/srv/mitmproxy}"
|
||||||
|
json_add_string "memory_limit" "${memory_limit:-256M}"
|
||||||
|
json_add_string "upstream_proxy" "${upstream_proxy:-}"
|
||||||
|
json_add_string "reverse_target" "${reverse_target:-}"
|
||||||
|
json_add_boolean "ssl_insecure" "${ssl_insecure:-0}"
|
||||||
|
json_add_boolean "anticache" "${anticache:-0}"
|
||||||
|
json_add_boolean "anticomp" "${anticomp:-0}"
|
||||||
|
|
||||||
|
# Transparent settings
|
||||||
|
local transparent_enabled=$(uci_get transparent.enabled)
|
||||||
|
local transparent_iface=$(uci_get transparent.interface)
|
||||||
|
local redirect_http=$(uci_get transparent.redirect_http)
|
||||||
|
local redirect_https=$(uci_get transparent.redirect_https)
|
||||||
|
|
||||||
|
json_add_boolean "transparent_enabled" "${transparent_enabled:-0}"
|
||||||
|
json_add_string "transparent_interface" "${transparent_iface:-br-lan}"
|
||||||
|
json_add_boolean "redirect_http" "${redirect_http:-1}"
|
||||||
|
json_add_boolean "redirect_https" "${redirect_https:-1}"
|
||||||
|
|
||||||
|
# Filtering settings
|
||||||
|
local filtering_enabled=$(uci_get filtering.enabled)
|
||||||
|
local log_requests=$(uci_get filtering.log_requests)
|
||||||
|
local filter_cdn=$(uci_get filtering.filter_cdn)
|
||||||
|
local filter_media=$(uci_get filtering.filter_media)
|
||||||
|
local block_ads=$(uci_get filtering.block_ads)
|
||||||
|
|
||||||
|
json_add_boolean "filtering_enabled" "${filtering_enabled:-0}"
|
||||||
|
json_add_boolean "log_requests" "${log_requests:-1}"
|
||||||
|
json_add_boolean "filter_cdn" "${filter_cdn:-0}"
|
||||||
|
json_add_boolean "filter_media" "${filter_media:-0}"
|
||||||
|
json_add_boolean "block_ads" "${block_ads:-0}"
|
||||||
|
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
|
save_settings() {
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
|
||||||
|
# Get values from input
|
||||||
|
local mode enabled proxy_port web_port web_host data_path memory_limit
|
||||||
|
local upstream_proxy reverse_target ssl_insecure anticache anticomp
|
||||||
|
local transparent_enabled transparent_interface redirect_http redirect_https
|
||||||
|
local filtering_enabled log_requests filter_cdn filter_media block_ads
|
||||||
|
local apply_now
|
||||||
|
|
||||||
|
json_get_var mode mode
|
||||||
|
json_get_var enabled enabled
|
||||||
|
json_get_var proxy_port proxy_port
|
||||||
|
json_get_var web_port web_port
|
||||||
|
json_get_var web_host web_host
|
||||||
|
json_get_var data_path data_path
|
||||||
|
json_get_var memory_limit memory_limit
|
||||||
|
json_get_var upstream_proxy upstream_proxy
|
||||||
|
json_get_var reverse_target reverse_target
|
||||||
|
json_get_var ssl_insecure ssl_insecure
|
||||||
|
json_get_var anticache anticache
|
||||||
|
json_get_var anticomp anticomp
|
||||||
|
json_get_var transparent_enabled transparent_enabled
|
||||||
|
json_get_var transparent_interface transparent_interface
|
||||||
|
json_get_var redirect_http redirect_http
|
||||||
|
json_get_var redirect_https redirect_https
|
||||||
|
json_get_var filtering_enabled filtering_enabled
|
||||||
|
json_get_var log_requests log_requests
|
||||||
|
json_get_var filter_cdn filter_cdn
|
||||||
|
json_get_var filter_media filter_media
|
||||||
|
json_get_var block_ads block_ads
|
||||||
|
json_get_var apply_now apply_now
|
||||||
|
|
||||||
|
# Now initialize output JSON
|
||||||
|
json_init
|
||||||
|
|
||||||
|
# Ensure UCI sections exist
|
||||||
|
uci -q get mitmproxy.main >/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() {
|
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"}'
|
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}'; }
|
do_restart() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy restart >/dev/null 2>&1; echo '{"success":true}'; }
|
||||||
|
|
||||||
list_methods() { cat <<'EOFM'
|
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
|
EOFM
|
||||||
}
|
}
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
list) list_methods ;;
|
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"}' ;;
|
*) echo '{"error":"Unknown command"}' ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -1,7 +1,26 @@
|
|||||||
{
|
{
|
||||||
"luci-app-mitmproxy": {
|
"luci-app-mitmproxy": {
|
||||||
"description": "Grant access to mitmproxy",
|
"description": "Grant access to mitmproxy",
|
||||||
"read": { "ubus": { "luci.mitmproxy": ["status"] }, "uci": ["mitmproxy"] },
|
"read": {
|
||||||
"write": { "ubus": { "luci.mitmproxy": ["install", "start", "stop", "restart"] }, "uci": ["mitmproxy"] }
|
"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"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -336,6 +336,15 @@ case "$1" in
|
|||||||
json_add_object "setAdGuardEnabled"
|
json_add_object "setAdGuardEnabled"
|
||||||
json_add_string "enabled" "boolean"
|
json_add_string "enabled" "boolean"
|
||||||
json_close_object
|
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
|
json_dump
|
||||||
;;
|
;;
|
||||||
|
|
||||||
@ -742,6 +751,98 @@ EOF
|
|||||||
json_dump
|
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_init
|
||||||
json_add_boolean "success" 0
|
json_add_boolean "success" 0
|
||||||
|
|||||||
@ -1089,6 +1089,7 @@ check_port_firewall_open() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Get network connectivity info (public IPs, port accessibility)
|
# Get network connectivity info (public IPs, port accessibility)
|
||||||
|
# NOTE: External port checks disabled - too slow (HTTP requests to external services)
|
||||||
method_get_network_info() {
|
method_get_network_info() {
|
||||||
json_init
|
json_init
|
||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
@ -1097,89 +1098,69 @@ method_get_network_info() {
|
|||||||
lan_ip=$(get_lan_ip)
|
lan_ip=$(get_lan_ip)
|
||||||
json_add_string "lan_ip" "$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"
|
json_add_object "ipv4"
|
||||||
local public_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
|
if [ -n "$public_ipv4" ]; then
|
||||||
json_add_string "address" "$public_ipv4"
|
json_add_string "address" "$public_ipv4"
|
||||||
json_add_string "status" "ok"
|
json_add_string "status" "ok"
|
||||||
|
|
||||||
# Reverse DNS
|
|
||||||
local rdns
|
|
||||||
rdns=$(get_reverse_dns "$public_ipv4")
|
|
||||||
[ -n "$rdns" ] && json_add_string "hostname" "$rdns"
|
|
||||||
else
|
else
|
||||||
json_add_string "status" "unavailable"
|
json_add_string "status" "unavailable"
|
||||||
fi
|
fi
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
# Get public IPv6
|
# IPv6 - skip network check (often broken/slow), just report firewall status
|
||||||
json_add_object "ipv6"
|
json_add_object "ipv6"
|
||||||
local public_ipv6
|
# Check if IPv6 is enabled in network config
|
||||||
public_ipv6=$(get_public_ipv6)
|
local ipv6_enabled=0
|
||||||
if [ -n "$public_ipv6" ]; then
|
if uci -q get network.wan6 >/dev/null 2>&1 || \
|
||||||
json_add_string "address" "$public_ipv6"
|
uci -q get network.wan.ipv6 >/dev/null 2>&1; then
|
||||||
json_add_string "status" "ok"
|
ipv6_enabled=1
|
||||||
|
fi
|
||||||
# Reverse DNS
|
if [ "$ipv6_enabled" = "1" ]; then
|
||||||
local rdns6
|
json_add_string "status" "configured"
|
||||||
rdns6=$(get_reverse_dns "$public_ipv6")
|
|
||||||
[ -n "$rdns6" ] && json_add_string "hostname" "$rdns6"
|
|
||||||
else
|
else
|
||||||
json_add_string "status" "unavailable"
|
json_add_string "status" "disabled"
|
||||||
fi
|
fi
|
||||||
json_close_object
|
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"
|
json_add_object "external_ports"
|
||||||
if [ -n "$public_ipv4" ]; then
|
local http_fw=0
|
||||||
# Check port 80
|
local https_fw=0
|
||||||
json_add_object "http"
|
check_port_firewall_open 80 && http_fw=1
|
||||||
if check_external_port "$public_ipv4" 80; then
|
check_port_firewall_open 443 && https_fw=1
|
||||||
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
|
|
||||||
|
|
||||||
# Check port 443
|
json_add_object "http"
|
||||||
json_add_object "https"
|
if [ "$http_fw" = "1" ]; then
|
||||||
if check_external_port "$public_ipv4" 443; then
|
json_add_string "status" "firewall_open"
|
||||||
json_add_boolean "accessible" 1
|
json_add_string "hint" "Firewall allows port 80"
|
||||||
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
|
|
||||||
else
|
else
|
||||||
json_add_object "http"
|
json_add_string "status" "firewall_closed"
|
||||||
json_add_string "status" "unknown"
|
json_add_string "hint" "Add firewall rule for port 80"
|
||||||
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
|
|
||||||
fi
|
fi
|
||||||
json_close_object
|
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
|
# Local firewall status
|
||||||
json_add_object "firewall"
|
json_add_object "firewall"
|
||||||
local http_open=0
|
json_add_boolean "http_open" "$http_fw"
|
||||||
local https_open=0
|
json_add_boolean "https_open" "$https_fw"
|
||||||
check_port_firewall_open 80 && http_open=1
|
if [ "$http_fw" = "1" ] && [ "$https_fw" = "1" ]; then
|
||||||
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_string "status" "ok"
|
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"
|
json_add_string "status" "partial"
|
||||||
else
|
else
|
||||||
json_add_string "status" "closed"
|
json_add_string "status" "closed"
|
||||||
@ -1226,20 +1207,13 @@ method_check_service_health() {
|
|||||||
json_add_string "service_id" "$service_id"
|
json_add_string "service_id" "$service_id"
|
||||||
json_add_string "domain" "$domain"
|
json_add_string "domain" "$domain"
|
||||||
|
|
||||||
# Get public IPs for comparison
|
# Get public IPv4 (short timeout for responsiveness)
|
||||||
local public_ipv4 public_ipv6
|
local public_ipv4
|
||||||
public_ipv4=$(get_public_ipv4)
|
public_ipv4=$(wget -qO- -T 3 "http://ipv4.icanhazip.com" 2>/dev/null | tr -d '\n')
|
||||||
public_ipv6=$(get_public_ipv6)
|
|
||||||
|
|
||||||
# Public IP info
|
# Public IP info
|
||||||
json_add_object "public_ip"
|
json_add_object "public_ip"
|
||||||
json_add_string "ipv4" "$public_ipv4"
|
json_add_string "ipv4" "${public_ipv4:-unknown}"
|
||||||
[ -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_close_object
|
json_close_object
|
||||||
|
|
||||||
# DNS check with public IP comparison
|
# DNS check with public IP comparison
|
||||||
@ -1275,27 +1249,21 @@ method_check_service_health() {
|
|||||||
fi
|
fi
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
# External port accessibility check
|
# External port accessibility check (firewall-based, fast)
|
||||||
json_add_object "external_access"
|
json_add_object "external_access"
|
||||||
if [ -n "$public_ipv4" ]; then
|
local http_fw=0
|
||||||
local http_ext=0
|
local https_fw=0
|
||||||
local https_ext=0
|
check_port_firewall_open 80 && http_fw=1
|
||||||
check_external_port "$public_ipv4" 80 && http_ext=1
|
check_port_firewall_open 443 && https_fw=1
|
||||||
check_external_port "$public_ipv4" 443 && https_ext=1
|
json_add_boolean "http_accessible" "$http_fw"
|
||||||
json_add_boolean "http_accessible" "$http_ext"
|
json_add_boolean "https_accessible" "$https_fw"
|
||||||
json_add_boolean "https_accessible" "$https_ext"
|
if [ "$http_fw" = "1" ] && [ "$https_fw" = "1" ]; then
|
||||||
if [ "$http_ext" = "1" ] && [ "$https_ext" = "1" ]; then
|
json_add_string "status" "firewall_ok"
|
||||||
json_add_string "status" "ok"
|
elif [ "$http_fw" = "1" ] || [ "$https_fw" = "1" ]; then
|
||||||
elif [ "$http_ext" = "1" ] || [ "$https_ext" = "1" ]; then
|
json_add_string "status" "partial"
|
||||||
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
|
|
||||||
else
|
else
|
||||||
json_add_string "status" "unknown"
|
json_add_string "status" "closed"
|
||||||
json_add_string "error" "Could not determine public IP"
|
json_add_string "hint" "Open firewall ports 80/443 for external access"
|
||||||
fi
|
fi
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
|
|||||||
@ -105,7 +105,7 @@ var callSettings = rpc.declare({
|
|||||||
var callSaveSettings = rpc.declare({
|
var callSaveSettings = rpc.declare({
|
||||||
object: 'luci.tor-shield',
|
object: 'luci.tor-shield',
|
||||||
method: 'save_settings',
|
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: { }
|
expect: { }
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -173,8 +173,8 @@ return baseclass.extend({
|
|||||||
getBridges: function() { return callBridges(); },
|
getBridges: function() { return callBridges(); },
|
||||||
setBridges: function(enabled, type) { return callSetBridges(enabled, type); },
|
setBridges: function(enabled, type) { return callSetBridges(enabled, type); },
|
||||||
getSettings: function() { return callSettings(); },
|
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) {
|
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);
|
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,
|
formatBytes: formatBytes,
|
||||||
|
|||||||
@ -10,7 +10,7 @@ return view.extend({
|
|||||||
return api.getSettings();
|
return api.getSettings();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSave: function(form) {
|
handleSave: function(form, applyNow) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Gather form values
|
// Gather form values
|
||||||
@ -27,7 +27,7 @@ return view.extend({
|
|||||||
};
|
};
|
||||||
|
|
||||||
ui.showModal(_('Saving Settings'), [
|
ui.showModal(_('Saving Settings'), [
|
||||||
E('p', { 'class': 'spinning' }, _('Saving configuration...'))
|
E('p', { 'class': 'spinning' }, applyNow ? _('Saving and applying configuration...') : _('Saving configuration...'))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.saveSettings(
|
api.saveSettings(
|
||||||
@ -39,11 +39,16 @@ return view.extend({
|
|||||||
settings.dns_port,
|
settings.dns_port,
|
||||||
settings.exit_nodes,
|
settings.exit_nodes,
|
||||||
settings.exclude_exit_nodes,
|
settings.exclude_exit_nodes,
|
||||||
settings.strict_nodes
|
settings.strict_nodes,
|
||||||
|
applyNow
|
||||||
).then(function(result) {
|
).then(function(result) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
if (result.success) {
|
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 {
|
} else {
|
||||||
ui.addNotification(null, E('p', result.error || _('Failed to save settings')), 'error');
|
ui.addNotification(null, E('p', result.error || _('Failed to save settings')), 'error');
|
||||||
}
|
}
|
||||||
@ -231,14 +236,22 @@ return view.extend({
|
|||||||
]),
|
]),
|
||||||
|
|
||||||
// Actions
|
// 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', {
|
E('button', {
|
||||||
'type': 'button',
|
'type': 'button',
|
||||||
'class': 'tor-btn tor-btn-primary',
|
'class': 'tor-btn tor-btn-primary',
|
||||||
|
'style': 'background: linear-gradient(135deg, #059669 0%, #10b981 100%);',
|
||||||
'click': function() {
|
'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', {
|
E('a', {
|
||||||
'href': L.url('admin', 'services', 'tor-shield'),
|
'href': L.url('admin', 'services', 'tor-shield'),
|
||||||
'class': 'tor-btn'
|
'class': 'tor-btn'
|
||||||
|
|||||||
@ -842,7 +842,7 @@ save_settings() {
|
|||||||
|
|
||||||
# Get values from input BEFORE json_init (which wipes loaded JSON)
|
# 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 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 mode mode
|
||||||
json_get_var dns_over_tor dns_over_tor
|
json_get_var dns_over_tor dns_over_tor
|
||||||
json_get_var kill_switch kill_switch
|
json_get_var kill_switch kill_switch
|
||||||
@ -852,6 +852,7 @@ save_settings() {
|
|||||||
json_get_var exit_nodes exit_nodes
|
json_get_var exit_nodes exit_nodes
|
||||||
json_get_var exclude_exit exclude_exit_nodes
|
json_get_var exclude_exit exclude_exit_nodes
|
||||||
json_get_var strict_nodes strict_nodes
|
json_get_var strict_nodes strict_nodes
|
||||||
|
json_get_var apply_now apply_now
|
||||||
|
|
||||||
# Now initialize output JSON
|
# Now initialize output JSON
|
||||||
json_init
|
json_init
|
||||||
@ -891,8 +892,25 @@ save_settings() {
|
|||||||
|
|
||||||
uci commit tor-shield
|
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_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
|
json_dump
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,222 +1,170 @@
|
|||||||
# LuCI WireGuard Dashboard
|
# LuCI WireGuard Dashboard
|
||||||
|
|
||||||
**Version:** 0.4.0
|
Modern WireGuard VPN management interface for OpenWrt with setup wizard, peer management, and real-time monitoring.
|
||||||
**Last Updated:** 2025-12-28
|
|
||||||
**Status:** Active
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Modern and intuitive dashboard for WireGuard VPN monitoring on OpenWrt. Visualize tunnels, peers, and traffic in real-time.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### 🔐 Tunnel Status
|
- **Setup Wizard**: Create tunnels and peers in minutes with presets for common use cases
|
||||||
- Real-time interface monitoring
|
- **Dashboard Overview**: Real-time status of all tunnels and peers
|
||||||
- Public key display
|
- **Peer Management**: Add, remove, and configure peers with QR code generation
|
||||||
- Listen port and MTU info
|
- **Traffic Monitoring**: Live bandwidth statistics per interface and peer
|
||||||
- Interface state (up/down)
|
- **Client Config Export**: Generate configuration files and QR codes for mobile apps
|
||||||
|
|
||||||
### 👥 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
|
|
||||||

|
|
||||||
|
|
||||||
### Peers List
|
|
||||||

|
|
||||||
|
|
||||||
### Traffic Statistics
|
|
||||||

|
|
||||||
|
|
||||||
### Configuration
|
|
||||||

|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- OpenWrt 21.02 or later
|
|
||||||
- WireGuard installed (`kmod-wireguard`, `wireguard-tools`)
|
|
||||||
- LuCI web interface
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install WireGuard
|
|
||||||
opkg update
|
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
|
```bash
|
||||||
# Clone into OpenWrt build environment
|
# Generate keys
|
||||||
cd ~/openwrt/feeds/luci/applications/
|
keys=$(ubus call luci.wireguard-dashboard generate_keys '{}')
|
||||||
git clone https://github.com/gkerma/luci-app-wireguard-dashboard.git
|
privkey=$(echo "$keys" | jsonfilter -e '@.private_key')
|
||||||
|
|
||||||
# Update feeds and install
|
# Create interface
|
||||||
cd ~/openwrt
|
ubus call luci.wireguard-dashboard create_interface "{
|
||||||
./scripts/feeds update -a
|
\"name\": \"wg0\",
|
||||||
./scripts/feeds install -a
|
\"private_key\": \"$privkey\",
|
||||||
|
\"listen_port\": \"51820\",
|
||||||
# Enable in menuconfig
|
\"addresses\": \"10.10.0.1/24\",
|
||||||
make menuconfig
|
\"mtu\": \"1420\"
|
||||||
# Navigate to: LuCI > Applications > luci-app-wireguard-dashboard
|
}"
|
||||||
|
|
||||||
# Build package
|
|
||||||
make package/luci-app-wireguard-dashboard/compile V=s
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual Installation
|
### Example: Add Peer via CLI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Transfer package to router
|
# Generate peer keys
|
||||||
scp luci-app-wireguard-dashboard_1.0.0-1_all.ipk root@192.168.1.1:/tmp/
|
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
|
# Add peer
|
||||||
ssh root@192.168.1.1
|
ubus call luci.wireguard-dashboard add_peer "{
|
||||||
opkg install /tmp/luci-app-wireguard-dashboard_1.0.0-1_all.ipk
|
\"interface\": \"wg0\",
|
||||||
|
\"name\": \"Phone\",
|
||||||
# Restart services
|
\"allowed_ips\": \"10.10.0.2/32\",
|
||||||
/etc/init.d/rpcd restart
|
\"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_<interface>`): 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:
|
## File Locations
|
||||||
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
|
|
||||||
|
|
||||||
## 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 |
|
||||||
|
|
||||||
```
|
## Troubleshooting
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ LuCI JavaScript │
|
### Interface not coming up
|
||||||
│ (status.js, peers.js, traffic.js) │
|
|
||||||
└───────────────────────────┬─────────────────────────────┘
|
```bash
|
||||||
│ ubus RPC
|
# Check interface status
|
||||||
▼
|
wg show wg0
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ RPCD Backend │
|
# Check UCI configuration
|
||||||
│ /usr/libexec/rpcd/wireguard-dashboard │
|
uci show network.wg0
|
||||||
└───────────────────────────┬─────────────────────────────┘
|
|
||||||
│ executes
|
# Manually bring up
|
||||||
▼
|
ifup wg0
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ wg show │
|
# Check logs
|
||||||
│ WireGuard CLI Tool │
|
logread | grep -i wireguard
|
||||||
└───────────────────────────┬─────────────────────────────┘
|
|
||||||
│ manages
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ WireGuard Kernel Module │
|
|
||||||
│ Encrypted Tunnels │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Endpoints
|
### Peers not connecting
|
||||||
|
|
||||||
| Method | Description |
|
1. Verify firewall port is open: `iptables -L -n | grep 51820`
|
||||||
|--------|-------------|
|
2. Check endpoint is reachable from client
|
||||||
| `status` | Overall VPN status, interface/peer counts, total traffic |
|
3. Verify allowed_ips match on both ends
|
||||||
| `interfaces` | Detailed interface info (pubkey, port, IPs, state) |
|
4. Check for NAT issues - enable PersistentKeepalive
|
||||||
| `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) |
|
|
||||||
|
|
||||||
## Peer Status Indicators
|
### QR codes not generating
|
||||||
|
|
||||||
| Status | Meaning | Handshake Age |
|
Install qrencode for server-side QR generation:
|
||||||
|--------|---------|---------------|
|
```bash
|
||||||
| 🟢 Active | Recent communication | < 3 minutes |
|
opkg install qrencode
|
||||||
| 🟡 Idle | No recent traffic | 3-10 minutes |
|
```
|
||||||
| ⚪ Inactive | No handshake | > 10 minutes or never |
|
|
||||||
|
|
||||||
## Requirements
|
The dashboard also supports client-side QR generation via JavaScript (no server dependency).
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
## License
|
## 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/)
|
CyberMind.fr - SecuBox Project
|
||||||
- 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.*
|
|
||||||
|
|||||||
@ -43,8 +43,11 @@ return view.extend({
|
|||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var peers = (data[0] || {}).peers || [];
|
// Handle RPC expect unwrapping - results may be array or object with .peers/.interfaces
|
||||||
var interfaces = (data[1] || {}).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 activePeers = peers.filter(function(p) { return p.status === 'active'; }).length;
|
||||||
|
|
||||||
var view = E('div', { 'class': 'cbi-map' }, [
|
var view = E('div', { 'class': 'cbi-map' }, [
|
||||||
|
|||||||
@ -14,7 +14,9 @@ return view.extend({
|
|||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var status = data[0] || {};
|
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' }, [
|
var view = E('div', { 'class': 'cbi-map' }, [
|
||||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
||||||
|
|||||||
@ -137,7 +137,9 @@ return view.extend({
|
|||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var self = this;
|
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 status = data[1] || {};
|
||||||
var publicIP = data[2] || '';
|
var publicIP = data[2] || '';
|
||||||
|
|
||||||
@ -569,20 +571,33 @@ return view.extend({
|
|||||||
createInterface: function() {
|
createInterface: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var data = this.wizardData;
|
var data = this.wizardData;
|
||||||
|
var netmask = data.vpnNetwork.split('/')[1] || '24';
|
||||||
|
var addresses = data.serverIP + '/' + netmask;
|
||||||
|
|
||||||
// Call backend to create interface
|
// Call backend to create interface using proper RPCD method
|
||||||
return rpc.call('uci', 'add', {
|
return api.createInterface(
|
||||||
config: 'network',
|
data.ifaceName,
|
||||||
type: 'interface',
|
data.privateKey,
|
||||||
name: data.ifaceName,
|
data.listenPort,
|
||||||
values: {
|
addresses,
|
||||||
proto: 'wireguard',
|
data.mtu || '1420'
|
||||||
private_key: data.privateKey,
|
).then(function(result) {
|
||||||
listen_port: data.listenPort,
|
// Handle various response formats from RPC
|
||||||
addresses: [data.serverIP + '/' + data.vpnNetwork.split('/')[1]]
|
// Result could be: boolean, object with success field, or object with error
|
||||||
|
if (result === true) {
|
||||||
|
return { success: true };
|
||||||
}
|
}
|
||||||
}).then(function() {
|
if (result === false) {
|
||||||
return rpc.call('uci', 'commit', { config: 'network' });
|
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 };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -34,18 +34,25 @@ var callGenerateKeys = rpc.declare({
|
|||||||
expect: { }
|
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({
|
var callAddPeer = rpc.declare({
|
||||||
object: 'luci.wireguard-dashboard',
|
object: 'luci.wireguard-dashboard',
|
||||||
method: 'add_peer',
|
method: 'add_peer',
|
||||||
params: ['interface', 'name', 'allowed_ips', 'public_key', 'preshared_key', 'endpoint', 'persistent_keepalive'],
|
params: ['interface', 'name', 'allowed_ips', 'public_key', 'preshared_key', 'endpoint', 'persistent_keepalive'],
|
||||||
expect: { success: false }
|
expect: { }
|
||||||
});
|
});
|
||||||
|
|
||||||
var callRemovePeer = rpc.declare({
|
var callRemovePeer = rpc.declare({
|
||||||
object: 'luci.wireguard-dashboard',
|
object: 'luci.wireguard-dashboard',
|
||||||
method: 'remove_peer',
|
method: 'remove_peer',
|
||||||
params: ['interface', 'public_key'],
|
params: ['interface', 'public_key'],
|
||||||
expect: { success: false }
|
expect: { }
|
||||||
});
|
});
|
||||||
|
|
||||||
var callGetConfig = rpc.declare({
|
var callGetConfig = rpc.declare({
|
||||||
@ -78,7 +85,7 @@ var callInterfaceControl = rpc.declare({
|
|||||||
object: 'luci.wireguard-dashboard',
|
object: 'luci.wireguard-dashboard',
|
||||||
method: 'interface_control',
|
method: 'interface_control',
|
||||||
params: ['interface', 'action'],
|
params: ['interface', 'action'],
|
||||||
expect: { success: false }
|
expect: { }
|
||||||
});
|
});
|
||||||
|
|
||||||
var callPeerDescriptions = rpc.declare({
|
var callPeerDescriptions = rpc.declare({
|
||||||
@ -154,6 +161,7 @@ return baseclass.extend({
|
|||||||
getConfig: callGetConfig,
|
getConfig: callGetConfig,
|
||||||
getTraffic: callGetTraffic,
|
getTraffic: callGetTraffic,
|
||||||
generateKeys: callGenerateKeys,
|
generateKeys: callGenerateKeys,
|
||||||
|
createInterface: callCreateInterface,
|
||||||
addPeer: callAddPeer,
|
addPeer: callAddPeer,
|
||||||
removePeer: callRemovePeer,
|
removePeer: callRemovePeer,
|
||||||
generateConfig: callGenerateConfig,
|
generateConfig: callGenerateConfig,
|
||||||
|
|||||||
@ -387,6 +387,114 @@ generate_keys() {
|
|||||||
json_dump
|
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 a new peer to interface
|
||||||
add_peer() {
|
add_peer() {
|
||||||
read input
|
read input
|
||||||
@ -893,7 +1001,7 @@ get_bandwidth_rates() {
|
|||||||
# Main dispatcher
|
# Main dispatcher
|
||||||
case "$1" in
|
case "$1" in
|
||||||
list)
|
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)
|
call)
|
||||||
case "$2" in
|
case "$2" in
|
||||||
@ -915,6 +1023,9 @@ case "$1" in
|
|||||||
generate_keys)
|
generate_keys)
|
||||||
generate_keys
|
generate_keys
|
||||||
;;
|
;;
|
||||||
|
create_interface)
|
||||||
|
create_interface
|
||||||
|
;;
|
||||||
add_peer)
|
add_peer)
|
||||||
add_peer
|
add_peer
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
"ubus": {
|
"ubus": {
|
||||||
"luci.wireguard-dashboard": [
|
"luci.wireguard-dashboard": [
|
||||||
"generate_keys",
|
"generate_keys",
|
||||||
|
"create_interface",
|
||||||
"add_peer",
|
"add_peer",
|
||||||
"remove_peer",
|
"remove_peer",
|
||||||
"generate_config",
|
"generate_config",
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
# Main orchestration service for SecuBox framework
|
# 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
|
. /lib/functions.sh
|
||||||
. /usr/share/libubox/jshn.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)
|
# Services to monitor (init.d name:check_method:restart_delay)
|
||||||
# check_method: pid, docker, lxc, port:PORT
|
# 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
|
# Logging function
|
||||||
log() {
|
log() {
|
||||||
@ -172,10 +222,14 @@ run_watchdog() {
|
|||||||
local watchdog_enabled=$(uci -q get secubox.main.watchdog_enabled || echo "1")
|
local watchdog_enabled=$(uci -q get secubox.main.watchdog_enabled || echo "1")
|
||||||
[ "$watchdog_enabled" != "1" ] && return 0
|
[ "$watchdog_enabled" != "1" ] && return 0
|
||||||
|
|
||||||
# Get monitored services from UCI or use defaults
|
# Auto-discover services if none configured
|
||||||
local services=$(uci -q get secubox.main.watchdog_services || echo "$MONITORED_SERVICES")
|
local services=$(uci -q get secubox.main.watchdog_services)
|
||||||
|
[ -z "$services" ] && services=$(discover_secubox_services)
|
||||||
|
|
||||||
local restart_count=0
|
local restart_count=0
|
||||||
local status_json=""
|
local checked_count=0
|
||||||
|
local running_count=0
|
||||||
|
local services_status=""
|
||||||
|
|
||||||
log debug "Watchdog: Checking services..."
|
log debug "Watchdog: Checking services..."
|
||||||
|
|
||||||
@ -184,66 +238,105 @@ run_watchdog() {
|
|||||||
local check_method=$(echo "$service_entry" | cut -d: -f2)
|
local check_method=$(echo "$service_entry" | cut -d: -f2)
|
||||||
local restart_delay=$(echo "$service_entry" | cut -d: -f3)
|
local restart_delay=$(echo "$service_entry" | cut -d: -f3)
|
||||||
[ -z "$restart_delay" ] && restart_delay=5
|
[ -z "$restart_delay" ] && restart_delay=5
|
||||||
|
[ -z "$check_method" ] && check_method="pid"
|
||||||
|
|
||||||
# Check if service init script exists
|
# Determine if service is enabled (check ctl script or init.d)
|
||||||
[ ! -x "/etc/init.d/$service_name" ] && continue
|
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
|
if [ -x "$ctl_script" ]; then
|
||||||
/etc/init.d/$service_name enabled >/dev/null 2>&1 || continue
|
# 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 is_running=false
|
||||||
|
local status_detail=""
|
||||||
|
|
||||||
case "$check_method" in
|
case "$check_method" in
|
||||||
pid)
|
pid)
|
||||||
# Check via pidof or pgrep
|
# Check via pgrep
|
||||||
if pgrep "$service_name" >/dev/null 2>&1; then
|
if pgrep "$service_name" >/dev/null 2>&1; then
|
||||||
is_running=true
|
is_running=true
|
||||||
|
status_detail="pid=$(pgrep -o "$service_name")"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
docker)
|
docker)
|
||||||
# Check Docker container
|
# Check Docker container (secbx- prefix)
|
||||||
if docker ps --filter "name=$service_name" --format "{{.Names}}" 2>/dev/null | grep -q "$service_name"; then
|
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
|
is_running=true
|
||||||
|
status_detail="container=$container_name"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
lxc)
|
lxc)
|
||||||
# Check LXC container
|
# Check LXC container
|
||||||
if lxc-info -n "$service_name" -s 2>/dev/null | grep -q "RUNNING"; then
|
if lxc-info -n "$service_name" -s 2>/dev/null | grep -q "RUNNING"; then
|
||||||
is_running=true
|
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
|
fi
|
||||||
;;
|
;;
|
||||||
port:*)
|
port:*)
|
||||||
# Check if port is listening
|
# Check if port is listening
|
||||||
local port=$(echo "$check_method" | cut -d: -f2)
|
local port=$(echo "$check_method" | cut -d: -f2)
|
||||||
# Use /proc/net/tcp (ports in hex)
|
|
||||||
local port_hex=$(printf '%04X' "$port")
|
local port_hex=$(printf '%04X' "$port")
|
||||||
if grep -q ":$port_hex " /proc/net/tcp /proc/net/tcp6 2>/dev/null; then
|
if grep -q ":$port_hex " /proc/net/tcp /proc/net/tcp6 2>/dev/null; then
|
||||||
is_running=true
|
is_running=true
|
||||||
|
status_detail="port=$port"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
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..."
|
log warn "Watchdog: $service_name is down, restarting..."
|
||||||
sleep "$restart_delay"
|
sleep "$restart_delay"
|
||||||
|
|
||||||
# Double-check before restart (service might have recovered)
|
# Double-check before restart (service might have recovered)
|
||||||
case "$check_method" in
|
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
|
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))
|
restart_count=$((restart_count + 1))
|
||||||
|
|
||||||
# Log restart event
|
|
||||||
log info "Watchdog: Restarted $service_name"
|
log info "Watchdog: Restarted $service_name"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Save watchdog state
|
# Save detailed watchdog state
|
||||||
json_init
|
json_init
|
||||||
json_add_string "last_check" "$(date -Iseconds)"
|
json_add_string "last_check" "$(date -Iseconds)"
|
||||||
json_add_int "restarts" "$restart_count"
|
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
|
json_dump > "$WATCHDOG_STATE" 2>/dev/null
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user