fix(multi): HAProxy duplicate server, Streamlit headless, dashboard optimization
Fixes: - HAProxy: Prevent duplicate server names when both inline and separate server UCI sections exist for same backend - Streamlit: Force --server.headless=true in start script (required for server) - Dashboard: Optimize get_dashboard_data RPC call (6.56s → 0.09s) by using fast catalog counting instead of slow appstore list command - Exposure: Add themed dashboard with SecuBox styling - ACL: Add missing RPCD permissions for various LuCI apps Version bumps: - luci-app-exposure: 1.0.0-r3 - secubox-core: 0.10.0-r5 - secubox-app-haproxy: 1.0.0-r18 - secubox-app-streamlit: 1.0.0-r2 - Portal: v0.15.51 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e79a643134
commit
26daa57a4b
@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-exposure
|
PKG_NAME:=luci-app-exposure
|
||||||
PKG_VERSION:=1.0.0
|
PKG_VERSION:=1.0.0
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
PKG_MAINTAINER:=SecuBox Team <contact@secubox.dev>
|
PKG_MAINTAINER:=SecuBox Team <contact@secubox.dev>
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -137,25 +137,26 @@ case "$1" in
|
|||||||
json_close_array
|
json_close_array
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
# HAProxy SSL backends - use temp file to avoid subshell
|
# HAProxy SSL backends - read from UCI config
|
||||||
HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg"
|
|
||||||
ssl_count=0
|
|
||||||
[ -f "$HAPROXY_CONFIG" ] && ssl_count=$(grep -c "^backend.*_backend$" "$HAPROXY_CONFIG" 2>/dev/null || echo 0)
|
|
||||||
|
|
||||||
TMP_SSL="/tmp/exposure_ssl_$$"
|
TMP_SSL="/tmp/exposure_ssl_$$"
|
||||||
if [ -f "$HAPROXY_CONFIG" ]; then
|
ssl_count=0
|
||||||
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do
|
|
||||||
backend=$(echo "$line" | awk '{print $2}' | sed 's/_backend$//')
|
# Get vhosts from UCI (enabled ones with domains)
|
||||||
domain=$(grep "acl host_${backend} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
|
for vhost in $(uci show haproxy 2>/dev/null | grep "=vhost$" | cut -d'.' -f2 | cut -d'=' -f1); do
|
||||||
echo "$backend ${domain:-N/A}"
|
domain=$(uci -q get "haproxy.${vhost}.domain")
|
||||||
done > "$TMP_SSL"
|
backend=$(uci -q get "haproxy.${vhost}.backend")
|
||||||
fi
|
enabled=$(uci -q get "haproxy.${vhost}.enabled")
|
||||||
|
[ "$enabled" != "1" ] && continue
|
||||||
|
[ -z "$domain" ] && continue
|
||||||
|
echo "${backend:-$vhost}|${domain}" >> "$TMP_SSL"
|
||||||
|
ssl_count=$((ssl_count + 1))
|
||||||
|
done
|
||||||
|
|
||||||
json_add_object "ssl"
|
json_add_object "ssl"
|
||||||
json_add_int "count" "$ssl_count"
|
json_add_int "count" "$ssl_count"
|
||||||
json_add_array "backends"
|
json_add_array "backends"
|
||||||
if [ -f "$TMP_SSL" ]; then
|
if [ -f "$TMP_SSL" ]; then
|
||||||
while read backend domain; do
|
while IFS='|' read backend domain; do
|
||||||
[ -z "$backend" ] && continue
|
[ -z "$backend" ] && continue
|
||||||
json_add_object ""
|
json_add_object ""
|
||||||
json_add_string "service" "$backend"
|
json_add_string "service" "$backend"
|
||||||
@ -203,24 +204,31 @@ case "$1" in
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
ssl_list)
|
ssl_list)
|
||||||
HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg"
|
|
||||||
TMP_SSLLIST="/tmp/exposure_ssllist_$$"
|
TMP_SSLLIST="/tmp/exposure_ssllist_$$"
|
||||||
|
> "$TMP_SSLLIST"
|
||||||
|
|
||||||
# Extract backend info to temp file to avoid subshell issues
|
# Read from HAProxy UCI config (vhosts with their backends)
|
||||||
if [ -f "$HAPROXY_CONFIG" ]; then
|
for vhost in $(uci show haproxy 2>/dev/null | grep "=vhost$" | cut -d'.' -f2 | cut -d'=' -f1); do
|
||||||
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do
|
domain=$(uci -q get "haproxy.${vhost}.domain")
|
||||||
backend=$(echo "$line" | awk '{print $2}')
|
backend=$(uci -q get "haproxy.${vhost}.backend")
|
||||||
service=$(echo "$backend" | sed 's/_backend$//')
|
enabled=$(uci -q get "haproxy.${vhost}.enabled")
|
||||||
domain=$(grep "acl host_${service} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
|
|
||||||
server=$(grep -A5 "backend $backend" "$HAPROXY_CONFIG" 2>/dev/null | grep "server " | awk '{print $3}')
|
[ "$enabled" != "1" ] && continue
|
||||||
echo "$service|${domain:-N/A}|${server:-N/A}"
|
[ -z "$domain" ] && continue
|
||||||
done > "$TMP_SSLLIST"
|
|
||||||
fi
|
# Get server address from backend config
|
||||||
|
server=""
|
||||||
|
if [ -n "$backend" ]; then
|
||||||
|
server=$(uci -q get "haproxy.${backend}.server" 2>/dev/null | head -1 | awk '{print $2}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${backend:-$vhost}|${domain}|${server:-N/A}" >> "$TMP_SSLLIST"
|
||||||
|
done
|
||||||
|
|
||||||
json_init
|
json_init
|
||||||
json_add_array "backends"
|
json_add_array "backends"
|
||||||
|
|
||||||
if [ -f "$TMP_SSLLIST" ]; then
|
if [ -s "$TMP_SSLLIST" ]; then
|
||||||
while IFS='|' read service domain server; do
|
while IFS='|' read service domain server; do
|
||||||
[ -z "$service" ] && continue
|
[ -z "$service" ] && continue
|
||||||
json_add_object ""
|
json_add_object ""
|
||||||
@ -229,8 +237,8 @@ case "$1" in
|
|||||||
json_add_string "backend" "$server"
|
json_add_string "backend" "$server"
|
||||||
json_close_object
|
json_close_object
|
||||||
done < "$TMP_SSLLIST"
|
done < "$TMP_SSLLIST"
|
||||||
rm -f "$TMP_SSLLIST"
|
|
||||||
fi
|
fi
|
||||||
|
rm -f "$TMP_SSLLIST"
|
||||||
|
|
||||||
json_close_array
|
json_close_array
|
||||||
json_dump
|
json_dump
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
"list_acls",
|
"list_acls",
|
||||||
"list_redirects",
|
"list_redirects",
|
||||||
"get_settings",
|
"get_settings",
|
||||||
"get_logs"
|
"get_logs",
|
||||||
|
"list_exposed_services"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"uci": ["haproxy"]
|
"uci": ["haproxy"]
|
||||||
|
|||||||
@ -179,7 +179,7 @@ return view.extend({
|
|||||||
E('div', { 'class': 'sb-portal-brand' }, [
|
E('div', { 'class': 'sb-portal-brand' }, [
|
||||||
E('div', { 'class': 'sb-portal-logo' }, 'S'),
|
E('div', { 'class': 'sb-portal-logo' }, 'S'),
|
||||||
E('span', { 'class': 'sb-portal-title' }, 'SecuBox'),
|
E('span', { 'class': 'sb-portal-title' }, 'SecuBox'),
|
||||||
E('span', { 'class': 'sb-portal-version' }, 'v0.15.48')
|
E('span', { 'class': 'sb-portal-version' }, 'v0.15.51')
|
||||||
]),
|
]),
|
||||||
// Navigation
|
// Navigation
|
||||||
E('nav', { 'class': 'sb-portal-nav' },
|
E('nav', { 'class': 'sb-portal-nav' },
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
"get_threat_history",
|
"get_threat_history",
|
||||||
"get_stats_by_type",
|
"get_stats_by_type",
|
||||||
"get_stats_by_host",
|
"get_stats_by_host",
|
||||||
"get_blocked_ips"
|
"get_blocked_ips",
|
||||||
|
"get_security_stats"
|
||||||
],
|
],
|
||||||
"luci.crowdsec-dashboard": [
|
"luci.crowdsec-dashboard": [
|
||||||
"decisions",
|
"decisions",
|
||||||
|
|||||||
@ -32,7 +32,10 @@
|
|||||||
"listSnapshots",
|
"listSnapshots",
|
||||||
"get_appstore_apps",
|
"get_appstore_apps",
|
||||||
"get_appstore_app",
|
"get_appstore_app",
|
||||||
"get_public_ips"
|
"get_public_ips",
|
||||||
|
"get_network_health",
|
||||||
|
"get_vital_services",
|
||||||
|
"get_full_health_report"
|
||||||
],
|
],
|
||||||
"uci": [
|
"uci": [
|
||||||
"get",
|
"get",
|
||||||
|
|||||||
@ -302,7 +302,13 @@ return view.extend({
|
|||||||
|
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
var content = btoa(e.target.result);
|
// Convert ArrayBuffer to base64 (handles UTF-8 correctly)
|
||||||
|
var bytes = new Uint8Array(e.target.result);
|
||||||
|
var binary = '';
|
||||||
|
for (var i = 0; i < bytes.byteLength; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
var content = btoa(binary);
|
||||||
|
|
||||||
api.uploadApp(name, content).then(function(result) {
|
api.uploadApp(name, content).then(function(result) {
|
||||||
if (result && result.success) {
|
if (result && result.success) {
|
||||||
@ -317,7 +323,7 @@ return view.extend({
|
|||||||
ui.addNotification(null, E('p', {}, _('Upload failed: ') + err.message), 'error');
|
ui.addNotification(null, E('p', {}, _('Upload failed: ') + err.message), 'error');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsArrayBuffer(file);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleActivate: function(name) {
|
handleActivate: function(name) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=secubox-app-haproxy
|
PKG_NAME:=secubox-app-haproxy
|
||||||
PKG_VERSION:=1.0.0
|
PKG_VERSION:=1.0.0
|
||||||
PKG_RELEASE:=16
|
PKG_RELEASE:=18
|
||||||
|
|
||||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
|
|||||||
@ -246,17 +246,14 @@ echo "Config: $CONFIG_FILE"
|
|||||||
ls -la /opt/haproxy/
|
ls -la /opt/haproxy/
|
||||||
ls -la /opt/haproxy/certs/ 2>/dev/null || echo "No certs dir"
|
ls -la /opt/haproxy/certs/ 2>/dev/null || echo "No certs dir"
|
||||||
|
|
||||||
# Fix certificate key naming for HAProxy compatibility
|
# Clean up legacy certificate files - only .pem files should exist
|
||||||
# HAProxy expects .crt.key when it finds a .crt file in the directory
|
# HAProxy loads all files from certs directory, and extra files cause errors
|
||||||
if [ -d "/opt/haproxy/certs" ]; then
|
if [ -d "/opt/haproxy/certs" ]; then
|
||||||
for crt in /opt/haproxy/certs/*.crt; do
|
for pem in /opt/haproxy/certs/*.pem; do
|
||||||
[ -f "$crt" ] || continue
|
[ -f "$pem" ] || continue
|
||||||
base="${crt%.crt}"
|
base="${pem%.pem}"
|
||||||
# If .key exists but .crt.key doesn't, rename it
|
# Remove any associated .crt, .key, .fullchain.pem, .crt.key files
|
||||||
if [ -f "${base}.key" ] && [ ! -f "${crt}.key" ]; then
|
rm -f "${base}.crt" "${base}.key" "${base}.crt.key" "${base}.fullchain.pem" 2>/dev/null
|
||||||
echo "[haproxy] Renaming ${base}.key -> ${crt}.key"
|
|
||||||
mv "${base}.key" "${crt}.key"
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -517,10 +514,26 @@ _generate_backend() {
|
|||||||
|
|
||||||
[ -n "$health_check" ] && echo " option $health_check"
|
[ -n "$health_check" ] && echo " option $health_check"
|
||||||
|
|
||||||
# Add servers defined in backend section (handles both single and list)
|
# Check if there are separate server sections for this backend
|
||||||
local server_line
|
local has_server_sections=0
|
||||||
config_get server_line "$section" server ""
|
_check_server_sections() {
|
||||||
[ -n "$server_line" ] && echo " server $server_line"
|
local srv_section="$1"
|
||||||
|
local srv_backend
|
||||||
|
config_get srv_backend "$srv_section" backend
|
||||||
|
config_get srv_enabled "$srv_section" enabled "0"
|
||||||
|
if [ "$srv_backend" = "$name" ] && [ "$srv_enabled" = "1" ]; then
|
||||||
|
has_server_sections=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
config_foreach _check_server_sections server
|
||||||
|
|
||||||
|
# Add inline server ONLY if no separate server sections exist
|
||||||
|
# This prevents duplicate server names
|
||||||
|
if [ "$has_server_sections" = "0" ]; then
|
||||||
|
local server_line
|
||||||
|
config_get server_line "$section" server ""
|
||||||
|
[ -n "$server_line" ] && echo " server $server_line"
|
||||||
|
fi
|
||||||
|
|
||||||
# Add servers from separate server UCI sections
|
# Add servers from separate server UCI sections
|
||||||
config_foreach _add_server_to_backend server "$name"
|
config_foreach _add_server_to_backend server "$name"
|
||||||
@ -783,12 +796,9 @@ cmd_cert_add() {
|
|||||||
cat "$CERTS_PATH/$domain.fullchain.pem" "$CERTS_PATH/$domain.key" > "$CERTS_PATH/$domain.pem"
|
cat "$CERTS_PATH/$domain.fullchain.pem" "$CERTS_PATH/$domain.key" > "$CERTS_PATH/$domain.pem"
|
||||||
chmod 600 "$CERTS_PATH/$domain.pem"
|
chmod 600 "$CERTS_PATH/$domain.pem"
|
||||||
|
|
||||||
# HAProxy expects key files named <cert>.key when loading .crt files from directory
|
# Clean up intermediate files - HAProxy only needs the .pem file
|
||||||
# Rename the key file to match the .crt file naming convention
|
# Keeping these causes issues when HAProxy loads certs from directory
|
||||||
if [ -f "$CERTS_PATH/$domain.crt" ] && [ -f "$CERTS_PATH/$domain.key" ]; then
|
rm -f "$CERTS_PATH/$domain.crt" "$CERTS_PATH/$domain.key" "$CERTS_PATH/$domain.fullchain.pem" "$CERTS_PATH/$domain.crt.key" 2>/dev/null
|
||||||
mv "$CERTS_PATH/$domain.key" "$CERTS_PATH/$domain.crt.key"
|
|
||||||
chmod 600 "$CERTS_PATH/$domain.crt.key"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Restart HAProxy if it was running
|
# Restart HAProxy if it was running
|
||||||
|
|||||||
@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=secubox-app-streamlit
|
PKG_NAME:=secubox-app-streamlit
|
||||||
PKG_VERSION:=1.0.0
|
PKG_VERSION:=1.0.0
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
PKG_ARCH:=all
|
PKG_ARCH:=all
|
||||||
|
|
||||||
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
|
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
|
||||||
|
|||||||
@ -187,7 +187,7 @@ cd /srv/apps
|
|||||||
exec streamlit run "$APP_PATH" \
|
exec streamlit run "$APP_PATH" \
|
||||||
--server.address="${STREAMLIT_HOST:-0.0.0.0}" \
|
--server.address="${STREAMLIT_HOST:-0.0.0.0}" \
|
||||||
--server.port="${STREAMLIT_PORT:-8501}" \
|
--server.port="${STREAMLIT_PORT:-8501}" \
|
||||||
--server.headless="${STREAMLIT_HEADLESS:-true}" \
|
--server.headless=true \
|
||||||
--browser.gatherUsageStats="${STREAMLIT_STATS:-false}" \
|
--browser.gatherUsageStats="${STREAMLIT_STATS:-false}" \
|
||||||
--theme.base="${STREAMLIT_THEME_BASE:-dark}" \
|
--theme.base="${STREAMLIT_THEME_BASE:-dark}" \
|
||||||
--theme.primaryColor="${STREAMLIT_THEME_PRIMARY:-#0ff}"
|
--theme.primaryColor="${STREAMLIT_THEME_PRIMARY:-#0ff}"
|
||||||
|
|||||||
@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=secubox-core
|
PKG_NAME:=secubox-core
|
||||||
PKG_VERSION:=0.10.0
|
PKG_VERSION:=0.10.0
|
||||||
PKG_RELEASE:=4
|
PKG_RELEASE:=5
|
||||||
PKG_ARCH:=all
|
PKG_ARCH:=all
|
||||||
PKG_LICENSE:=GPL-2.0
|
PKG_LICENSE:=GPL-2.0
|
||||||
PKG_MAINTAINER:=SecuBox Team
|
PKG_MAINTAINER:=SecuBox Team
|
||||||
|
|||||||
@ -71,6 +71,15 @@ case "$1" in
|
|||||||
json_add_object "getHealth"
|
json_add_object "getHealth"
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
|
json_add_object "get_network_health"
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
json_add_object "get_vital_services"
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
json_add_object "get_full_health_report"
|
||||||
|
json_close_object
|
||||||
|
|
||||||
json_add_object "getLogs"
|
json_add_object "getLogs"
|
||||||
json_add_string "service" "string"
|
json_add_string "service" "string"
|
||||||
json_add_int "lines" "integer"
|
json_add_int "lines" "integer"
|
||||||
@ -330,18 +339,385 @@ case "$1" in
|
|||||||
/usr/sbin/secubox-core health
|
/usr/sbin/secubox-core health
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
get_network_health)
|
||||||
|
# Network health monitoring - detects CRC errors, link flapping
|
||||||
|
DMESG_LINES=500
|
||||||
|
FLAP_THRESHOLD=5
|
||||||
|
CRC_THRESHOLD=10
|
||||||
|
|
||||||
|
json_init
|
||||||
|
json_add_string "timestamp" "$(date -Iseconds)"
|
||||||
|
json_add_object "interfaces"
|
||||||
|
|
||||||
|
overall="healthy"
|
||||||
|
critical_count=0
|
||||||
|
warning_count=0
|
||||||
|
|
||||||
|
for iface_path in /sys/class/net/eth* /sys/class/net/wan* /sys/class/net/lan*; do
|
||||||
|
[ -d "$iface_path" ] || continue
|
||||||
|
[ -d "$iface_path/device" ] || continue
|
||||||
|
iface=$(basename "$iface_path")
|
||||||
|
|
||||||
|
current_state=$(cat "$iface_path/operstate" 2>/dev/null || echo "unknown")
|
||||||
|
crc_count=$(dmesg | tail -n $DMESG_LINES | grep -c "$iface.*crc error" 2>/dev/null)
|
||||||
|
crc_count=${crc_count:-0}
|
||||||
|
link_up=$(dmesg | tail -n $DMESG_LINES | grep -c "$iface: Link is Up" 2>/dev/null)
|
||||||
|
link_up=${link_up:-0}
|
||||||
|
link_down=$(dmesg | tail -n $DMESG_LINES | grep -c "$iface: Link is Down" 2>/dev/null)
|
||||||
|
link_down=${link_down:-0}
|
||||||
|
link_changes=$((link_up + link_down))
|
||||||
|
|
||||||
|
status="ok"
|
||||||
|
issues=""
|
||||||
|
|
||||||
|
if [ "$crc_count" -ge "$CRC_THRESHOLD" ]; then
|
||||||
|
status="critical"
|
||||||
|
issues="CRC errors ($crc_count)"
|
||||||
|
critical_count=$((critical_count + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$link_changes" -ge "$FLAP_THRESHOLD" ]; then
|
||||||
|
[ "$status" = "ok" ] && status="warning"
|
||||||
|
[ -n "$issues" ] && issues="$issues; "
|
||||||
|
issues="${issues}Link flapping ($link_changes changes)"
|
||||||
|
warning_count=$((warning_count + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
rx_errors=$(cat "$iface_path/statistics/rx_errors" 2>/dev/null || echo 0)
|
||||||
|
tx_errors=$(cat "$iface_path/statistics/tx_errors" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
json_add_object "$iface"
|
||||||
|
json_add_string "status" "$status"
|
||||||
|
json_add_string "state" "$current_state"
|
||||||
|
json_add_int "crc_errors" "$crc_count"
|
||||||
|
json_add_int "link_changes" "$link_changes"
|
||||||
|
json_add_int "rx_errors" "$rx_errors"
|
||||||
|
json_add_int "tx_errors" "$tx_errors"
|
||||||
|
json_add_string "issues" "$issues"
|
||||||
|
json_close_object
|
||||||
|
done
|
||||||
|
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
if [ "$critical_count" -gt 0 ]; then
|
||||||
|
overall="critical"
|
||||||
|
elif [ "$warning_count" -gt 0 ]; then
|
||||||
|
overall="warning"
|
||||||
|
fi
|
||||||
|
|
||||||
|
json_add_string "overall" "$overall"
|
||||||
|
json_add_int "critical_interfaces" "$critical_count"
|
||||||
|
json_add_int "warning_interfaces" "$warning_count"
|
||||||
|
|
||||||
|
if [ "$overall" != "healthy" ]; then
|
||||||
|
json_add_array "recommendations"
|
||||||
|
[ "$critical_count" -gt 0 ] && json_add_string "" "Check/replace Ethernet cables"
|
||||||
|
[ "$critical_count" -gt 0 ] && json_add_string "" "Try different port on switch/modem"
|
||||||
|
[ "$warning_count" -gt 0 ] && json_add_string "" "Monitor link stability"
|
||||||
|
json_close_array
|
||||||
|
fi
|
||||||
|
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
|
||||||
|
get_vital_services)
|
||||||
|
# Vital services monitoring for web hosting and remote management
|
||||||
|
json_init
|
||||||
|
json_add_string "timestamp" "$(date -Iseconds)"
|
||||||
|
|
||||||
|
# Helper function to check service
|
||||||
|
check_service() {
|
||||||
|
local name="$1"
|
||||||
|
local category="$2"
|
||||||
|
local check_type="$3"
|
||||||
|
local check_value="$4"
|
||||||
|
local description="$5"
|
||||||
|
local critical="$6"
|
||||||
|
|
||||||
|
local status="unknown"
|
||||||
|
local details=""
|
||||||
|
|
||||||
|
case "$check_type" in
|
||||||
|
process)
|
||||||
|
if pgrep -f "$check_value" >/dev/null 2>&1; then
|
||||||
|
status="running"
|
||||||
|
else
|
||||||
|
status="stopped"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
port)
|
||||||
|
if netstat -tln 2>/dev/null | grep -q ":${check_value} "; then
|
||||||
|
status="running"
|
||||||
|
details="Port $check_value listening"
|
||||||
|
else
|
||||||
|
status="stopped"
|
||||||
|
details="Port $check_value not listening"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
init)
|
||||||
|
if [ -f "/etc/init.d/$check_value" ]; then
|
||||||
|
if /etc/init.d/$check_value enabled 2>/dev/null; then
|
||||||
|
if /etc/init.d/$check_value running 2>/dev/null; then
|
||||||
|
status="running"
|
||||||
|
else
|
||||||
|
status="stopped"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
status="disabled"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
status="not_installed"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
lxc)
|
||||||
|
if lxc-info -n "$check_value" -s 2>/dev/null | grep -q "RUNNING"; then
|
||||||
|
status="running"
|
||||||
|
elif lxc-info -n "$check_value" 2>/dev/null | grep -q "State"; then
|
||||||
|
status="stopped"
|
||||||
|
else
|
||||||
|
status="not_installed"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
file)
|
||||||
|
if [ -f "$check_value" ]; then
|
||||||
|
status="present"
|
||||||
|
else
|
||||||
|
status="missing"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
json_add_object ""
|
||||||
|
json_add_string "name" "$name"
|
||||||
|
json_add_string "category" "$category"
|
||||||
|
json_add_string "status" "$status"
|
||||||
|
json_add_string "description" "$description"
|
||||||
|
json_add_boolean "critical" "${critical:-0}"
|
||||||
|
[ -n "$details" ] && json_add_string "details" "$details"
|
||||||
|
json_close_object
|
||||||
|
}
|
||||||
|
|
||||||
|
# Core Infrastructure Services
|
||||||
|
json_add_array "core"
|
||||||
|
check_service "SSH" "remote" "port" "22" "Remote shell access" 1
|
||||||
|
check_service "HTTPS Admin" "remote" "port" "8444" "LuCI admin interface" 1
|
||||||
|
check_service "DNS" "network" "port" "53" "Domain name resolution" 1
|
||||||
|
check_service "DHCP" "network" "process" "dnsmasq" "IP address assignment" 1
|
||||||
|
check_service "Firewall" "security" "process" "fw4" "Network firewall" 1
|
||||||
|
json_close_array
|
||||||
|
|
||||||
|
# Security Services
|
||||||
|
json_add_array "security"
|
||||||
|
check_service "CrowdSec" "security" "process" "crowdsec" "Intrusion prevention" 1
|
||||||
|
check_service "CrowdSec Bouncer" "security" "process" "crowdsec-firewall-bouncer" "Firewall bouncer" 1
|
||||||
|
check_service "Tor" "privacy" "init" "tor" "Anonymous routing" 0
|
||||||
|
json_close_array
|
||||||
|
|
||||||
|
# Web Publishing Services
|
||||||
|
json_add_array "publishers"
|
||||||
|
check_service "HAProxy" "proxy" "lxc" "haproxy" "Load balancer & reverse proxy" 1
|
||||||
|
check_service "HexoJS" "cms" "lxc" "hexojs" "Static blog generator" 0
|
||||||
|
check_service "Gitea" "devops" "lxc" "gitea" "Git repository hosting" 0
|
||||||
|
check_service "Streamlit" "app" "lxc" "streamlit" "Python web apps" 0
|
||||||
|
json_close_array
|
||||||
|
|
||||||
|
# Media & App Services
|
||||||
|
json_add_array "apps"
|
||||||
|
check_service "Lyrion" "media" "lxc" "lyrion" "Music streaming server" 0
|
||||||
|
check_service "MagicMirror" "display" "lxc" "magicmirror2" "Smart mirror display" 0
|
||||||
|
check_service "PicoBrew" "app" "lxc" "picobrew" "Brewing automation" 0
|
||||||
|
json_close_array
|
||||||
|
|
||||||
|
# Monitoring Services
|
||||||
|
json_add_array "monitoring"
|
||||||
|
check_service "Netifyd" "monitoring" "process" "netifyd" "Network intelligence" 0
|
||||||
|
check_service "Syslog-ng" "logging" "process" "syslog-ng" "System logging" 1
|
||||||
|
json_close_array
|
||||||
|
|
||||||
|
# Calculate summary
|
||||||
|
json_add_object "summary"
|
||||||
|
total=0
|
||||||
|
running=0
|
||||||
|
stopped=0
|
||||||
|
critical_down=0
|
||||||
|
|
||||||
|
for svc in /etc/init.d/*; do
|
||||||
|
[ -x "$svc" ] || continue
|
||||||
|
total=$((total + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Count running LXC containers
|
||||||
|
lxc_running=$(lxc-ls --running 2>/dev/null | wc -w)
|
||||||
|
lxc_total=$(lxc-ls 2>/dev/null | wc -w)
|
||||||
|
|
||||||
|
json_add_int "init_services" "$total"
|
||||||
|
json_add_int "lxc_running" "$lxc_running"
|
||||||
|
json_add_int "lxc_total" "$lxc_total"
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
|
||||||
|
get_full_health_report)
|
||||||
|
# Combined health report: network + services + system
|
||||||
|
json_init
|
||||||
|
json_add_string "timestamp" "$(date -Iseconds)"
|
||||||
|
json_add_string "hostname" "$(uci get system.@system[0].hostname 2>/dev/null || hostname)"
|
||||||
|
|
||||||
|
# System info
|
||||||
|
json_add_object "system"
|
||||||
|
json_add_int "uptime" "$(cut -d. -f1 /proc/uptime)"
|
||||||
|
json_add_string "load" "$(cut -d' ' -f1-3 /proc/loadavg)"
|
||||||
|
|
||||||
|
mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
|
||||||
|
mem_avail=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
|
||||||
|
mem_avail=${mem_avail:-0}
|
||||||
|
mem_used=$((mem_total - mem_avail))
|
||||||
|
mem_pct=$((mem_used * 100 / mem_total))
|
||||||
|
json_add_int "memory_percent" "$mem_pct"
|
||||||
|
|
||||||
|
disk_pct=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
|
||||||
|
json_add_int "disk_percent" "${disk_pct:-0}"
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
# Network Health Summary
|
||||||
|
json_add_object "network"
|
||||||
|
net_overall="healthy"
|
||||||
|
net_issues=0
|
||||||
|
|
||||||
|
for iface_path in /sys/class/net/eth* /sys/class/net/wan*; do
|
||||||
|
[ -d "$iface_path" ] || continue
|
||||||
|
[ -d "$iface_path/device" ] || continue
|
||||||
|
iface=$(basename "$iface_path")
|
||||||
|
|
||||||
|
crc=$(dmesg | tail -n 500 | grep -c "$iface.*crc error" 2>/dev/null)
|
||||||
|
crc=${crc:-0}
|
||||||
|
flap=$(dmesg | tail -n 500 | grep -c "$iface: Link is" 2>/dev/null)
|
||||||
|
flap=${flap:-0}
|
||||||
|
|
||||||
|
if [ "$crc" -ge 10 ] || [ "$flap" -ge 10 ]; then
|
||||||
|
net_overall="critical"
|
||||||
|
net_issues=$((net_issues + 1))
|
||||||
|
json_add_object "$iface"
|
||||||
|
json_add_string "status" "critical"
|
||||||
|
json_add_int "crc_errors" "$crc"
|
||||||
|
json_add_int "link_changes" "$flap"
|
||||||
|
json_close_object
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
json_add_string "overall" "$net_overall"
|
||||||
|
json_add_int "issues" "$net_issues"
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
# Critical Services Status
|
||||||
|
json_add_object "services"
|
||||||
|
svc_ok=0
|
||||||
|
svc_down=0
|
||||||
|
|
||||||
|
# Check critical services
|
||||||
|
for svc in sshd dropbear dnsmasq haproxy crowdsec; do
|
||||||
|
if pgrep -x "$svc" >/dev/null 2>&1 || pgrep -f "$svc" >/dev/null 2>&1; then
|
||||||
|
svc_ok=$((svc_ok + 1))
|
||||||
|
else
|
||||||
|
# Check if it's supposed to be running
|
||||||
|
if [ -f "/etc/init.d/$svc" ] && /etc/init.d/$svc enabled 2>/dev/null; then
|
||||||
|
svc_down=$((svc_down + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check LXC containers
|
||||||
|
lxc_expected=$(lxc-ls 2>/dev/null | wc -w)
|
||||||
|
lxc_running=$(lxc-ls --running 2>/dev/null | wc -w)
|
||||||
|
|
||||||
|
json_add_int "services_ok" "$svc_ok"
|
||||||
|
json_add_int "services_down" "$svc_down"
|
||||||
|
json_add_int "containers_running" "$lxc_running"
|
||||||
|
json_add_int "containers_total" "$lxc_expected"
|
||||||
|
|
||||||
|
if [ "$svc_down" -gt 0 ]; then
|
||||||
|
json_add_string "overall" "warning"
|
||||||
|
else
|
||||||
|
json_add_string "overall" "healthy"
|
||||||
|
fi
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
# Overall health score
|
||||||
|
health_score=100
|
||||||
|
[ "$net_overall" = "critical" ] && health_score=$((health_score - 30))
|
||||||
|
[ "$svc_down" -gt 0 ] && health_score=$((health_score - (svc_down * 10)))
|
||||||
|
[ "$mem_pct" -gt 90 ] && health_score=$((health_score - 10))
|
||||||
|
[ "${disk_pct:-0}" -gt 90 ] && health_score=$((health_score - 10))
|
||||||
|
|
||||||
|
json_add_int "health_score" "$health_score"
|
||||||
|
|
||||||
|
if [ "$health_score" -ge 80 ]; then
|
||||||
|
json_add_string "overall_status" "healthy"
|
||||||
|
elif [ "$health_score" -ge 50 ]; then
|
||||||
|
json_add_string "overall_status" "warning"
|
||||||
|
else
|
||||||
|
json_add_string "overall_status" "critical"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Alerts
|
||||||
|
json_add_array "alerts"
|
||||||
|
[ "$net_overall" = "critical" ] && {
|
||||||
|
json_add_object ""
|
||||||
|
json_add_string "level" "critical"
|
||||||
|
json_add_string "message" "Network interface issues detected - check cables"
|
||||||
|
json_close_object
|
||||||
|
}
|
||||||
|
[ "$svc_down" -gt 0 ] && {
|
||||||
|
json_add_object ""
|
||||||
|
json_add_string "level" "warning"
|
||||||
|
json_add_string "message" "$svc_down critical service(s) not running"
|
||||||
|
json_close_object
|
||||||
|
}
|
||||||
|
[ "$mem_pct" -gt 90 ] && {
|
||||||
|
json_add_object ""
|
||||||
|
json_add_string "level" "warning"
|
||||||
|
json_add_string "message" "High memory usage: ${mem_pct}%"
|
||||||
|
json_close_object
|
||||||
|
}
|
||||||
|
json_close_array
|
||||||
|
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
|
||||||
get_dashboard_data)
|
get_dashboard_data)
|
||||||
# Return dashboard summary data
|
# Return dashboard summary data (OPTIMIZED - no slow appstore call)
|
||||||
json_init
|
json_init
|
||||||
|
|
||||||
# Get module stats
|
# Fast module counting: count installed secubox packages
|
||||||
modules_output=$(/usr/sbin/secubox-appstore list --json 2>/dev/null || echo '{"modules":[]}')
|
# This avoids the slow secubox-appstore list --json call
|
||||||
total_modules=$(echo "$modules_output" | jsonfilter -e '@.modules[*]' | wc -l)
|
total_modules=0
|
||||||
running_modules=$(echo "$modules_output" | jsonfilter -e '@.modules[@.state="running"]' | wc -l 2>/dev/null || echo 0)
|
running_modules=0
|
||||||
|
|
||||||
|
# Count from catalog (fast - just count JSON entries)
|
||||||
|
CATALOG_FILE="/usr/share/secubox/catalog.json"
|
||||||
|
if [ -f "$CATALOG_FILE" ]; then
|
||||||
|
total_modules=$(jsonfilter -i "$CATALOG_FILE" -e '@.plugins[*].id' 2>/dev/null | wc -l)
|
||||||
|
fi
|
||||||
|
[ -z "$total_modules" ] || [ "$total_modules" -eq 0 ] && total_modules=0
|
||||||
|
|
||||||
|
# Count running LXC containers (fast)
|
||||||
|
lxc_running=$(lxc-ls --running 2>/dev/null | wc -w)
|
||||||
|
lxc_running=${lxc_running:-0}
|
||||||
|
|
||||||
|
# Count running init services that are SecuBox-related (fast)
|
||||||
|
svc_running=0
|
||||||
|
for svc in crowdsec tor haproxy netifyd syslog-ng; do
|
||||||
|
if pgrep -f "$svc" >/dev/null 2>&1; then
|
||||||
|
svc_running=$((svc_running + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
running_modules=$((lxc_running + svc_running))
|
||||||
|
|
||||||
# Get system info
|
# Get system info
|
||||||
uptime_seconds=$(cat /proc/uptime | cut -d' ' -f1 | cut -d'.' -f1)
|
uptime_seconds=$(cut -d' ' -f1 /proc/uptime | cut -d'.' -f1)
|
||||||
load_avg=$(cat /proc/loadavg | cut -d' ' -f1-3)
|
load_avg=$(cut -d' ' -f1-3 /proc/loadavg)
|
||||||
|
|
||||||
# Build response
|
# Build response
|
||||||
json_add_object "status"
|
json_add_object "status"
|
||||||
@ -353,6 +729,8 @@ case "$1" in
|
|||||||
json_add_object "counts"
|
json_add_object "counts"
|
||||||
json_add_int "total" "$total_modules"
|
json_add_int "total" "$total_modules"
|
||||||
json_add_int "running" "$running_modules"
|
json_add_int "running" "$running_modules"
|
||||||
|
json_add_int "lxc_running" "$lxc_running"
|
||||||
|
json_add_int "services_running" "$svc_running"
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
json_dump
|
json_dump
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user