Major structural reorganization and feature additions: ## Folder Reorganization - Move 17 luci-app-* packages to package/secubox/ (except luci-app-secubox core hub) - Update all tooling to support new structure: - secubox-tools/quick-deploy.sh: search both locations - secubox-tools/validate-modules.sh: validate both directories - secubox-tools/fix-permissions.sh: fix permissions in both locations - .github/workflows/test-validate.yml: build from both paths - Update README.md links to new package/secubox/ paths ## AppStore Migration (Complete) - Add catalog entries for all remaining luci-app packages: - network-tweaks.json: Network optimization tools - secubox-bonus.json: Documentation & demos hub - Total: 24 apps in AppStore catalog (22 existing + 2 new) - New category: 'documentation' for docs/demos/tutorials ## VHost Manager v2.0 Enhancements - Add profile activation system for Internal Services and Redirects - Implement createVHost() API wrapper for template-based deployment - Fix Virtual Hosts view rendering with proper LuCI patterns - Fix RPCD backend shell script errors (remove invalid local declarations) - Extend backend validation for nginx return directives (redirect support) - Add section_id parameter for named VHost profiles - Add Remove button to Redirects page for feature parity - Update README to v2.0 with comprehensive feature documentation ## Network Tweaks Dashboard - Close button added to component details modal Files changed: 340+ (336 renames with preserved git history) Packages affected: 19 luci-app, 2 secubox-app, 1 theme, 4 tools 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
276 lines
6.4 KiB
Bash
Executable File
276 lines
6.4 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# SecuBox MQTT Bridge daemon
|
|
# Handles USB adapter discovery, stats tracking, and automation hooks.
|
|
|
|
. /lib/functions.sh
|
|
|
|
UCI_NAMESPACE="mqtt-bridge"
|
|
LOGTAG="mqtt-bridge"
|
|
SCAN_INTERVAL=10
|
|
COMMIT_NEEDED=0
|
|
START_TIME="$(date +%s)"
|
|
PREV_PAYLOAD_COUNT=0
|
|
PREV_TIMESTAMP="$START_TIME"
|
|
LAST_EVENT=""
|
|
|
|
log_msg() {
|
|
logger -t "$LOGTAG" "$*"
|
|
}
|
|
|
|
format_duration() {
|
|
local seconds="$1"
|
|
local h=$((seconds / 3600))
|
|
local m=$(( (seconds % 3600) / 60 ))
|
|
local s=$((seconds % 60))
|
|
printf '%02dh %02dm %02ds' "$h" "$m" "$s"
|
|
}
|
|
|
|
load_interval() {
|
|
config_load "$UCI_NAMESPACE"
|
|
config_get interval monitor interval
|
|
[ -n "$interval" ] && SCAN_INTERVAL="$interval"
|
|
}
|
|
|
|
find_usb_device() {
|
|
local vendor="$1"
|
|
local product="$2"
|
|
local dev
|
|
|
|
for dev in /sys/bus/usb/devices/*; do
|
|
[ -f "$dev/idVendor" ] || continue
|
|
[ -f "$dev/idProduct" ] || continue
|
|
local idVendor idProduct
|
|
idVendor="$(cat "$dev/idVendor" 2>/dev/null)"
|
|
idProduct="$(cat "$dev/idProduct" 2>/dev/null)"
|
|
[ "$idVendor" = "$vendor" ] || continue
|
|
[ "$idProduct" = "$product" ] || continue
|
|
echo "$dev"
|
|
return 0
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
find_usb_tty() {
|
|
local base="$1"
|
|
local path node
|
|
for path in "$base" "$base"/* "$base"/*/*; do
|
|
[ -d "$path/tty" ] || continue
|
|
for node in "$path"/tty/*; do
|
|
[ -e "$node" ] || continue
|
|
local tty
|
|
tty="$(basename "$node")"
|
|
[ -e "/dev/$tty" ] && { echo "/dev/$tty"; return 0; }
|
|
done
|
|
done
|
|
return 1
|
|
}
|
|
|
|
set_option_if_changed() {
|
|
local section="$1"
|
|
local key="$2"
|
|
local value="$3"
|
|
local current
|
|
current="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.${key} 2>/dev/null)"
|
|
[ "$current" = "$value" ] && return
|
|
uci set ${UCI_NAMESPACE}.adapter.${section}.${key}="$value"
|
|
COMMIT_NEEDED=1
|
|
}
|
|
|
|
clear_option_if_needed() {
|
|
local section="$1"
|
|
local key="$2"
|
|
local current
|
|
current="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.${key} 2>/dev/null)"
|
|
[ -z "$current" ] && return
|
|
uci delete ${UCI_NAMESPACE}.adapter.${section}.${key}
|
|
COMMIT_NEEDED=1
|
|
}
|
|
|
|
publish_alert() {
|
|
local message="$1"
|
|
local topic="$2"
|
|
[ -n "$message" ] || return
|
|
log_msg "ALERT: $message"
|
|
if [ -n "$topic" ]; then
|
|
printf '%s %s\n' "$(date -Iseconds)" "$message" >> /tmp/mqtt-bridge-alerts.log
|
|
fi
|
|
}
|
|
|
|
RULE_MATCH_ADAPTER=""
|
|
RULE_MATCH_STATE=""
|
|
|
|
apply_rule() {
|
|
local section="$1"
|
|
local target when action message topic
|
|
config_get target "$section" adapter
|
|
config_get when "$section" when
|
|
config_get action "$section" action
|
|
config_get message "$section" message
|
|
config_get topic "$section" topic
|
|
[ "$RULE_MATCH_ADAPTER" = "$target" ] || return
|
|
[ "$RULE_MATCH_STATE" = "$when" ] || return
|
|
|
|
case "$action" in
|
|
alert)
|
|
publish_alert "$message" "$topic"
|
|
;;
|
|
rescan)
|
|
log_msg "Rule $section triggered rescan for $target"
|
|
run_detection_once
|
|
;;
|
|
esac
|
|
}
|
|
|
|
run_rules() {
|
|
RULE_MATCH_ADAPTER="$1"
|
|
RULE_MATCH_STATE="$2"
|
|
config_foreach apply_rule rule
|
|
}
|
|
|
|
update_adapter_section() {
|
|
local section="$1"
|
|
local enabled vendor product title preset
|
|
|
|
config_get enabled "$section" enabled "1"
|
|
config_get vendor "$section" vendor
|
|
config_get product "$section" product
|
|
config_get preset "$section" preset
|
|
config_get title "$section" title
|
|
|
|
if [ "$enabled" != "1" ]; then
|
|
set_option_if_changed "$section" detected "0"
|
|
set_option_if_changed "$section" health "disabled"
|
|
return
|
|
fi
|
|
|
|
if [ -z "$vendor" ] || [ -z "$product" ]; then
|
|
set_option_if_changed "$section" detected "0"
|
|
set_option_if_changed "$section" health "unknown"
|
|
return
|
|
fi
|
|
|
|
local dev_path
|
|
dev_path="$(find_usb_device "$vendor" "$product")" || dev_path=""
|
|
|
|
local prev_detected
|
|
prev_detected="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.detected 2>/dev/null)"
|
|
|
|
if [ -n "$dev_path" ]; then
|
|
local bus devnum port ts
|
|
bus="$(cat "$dev_path/busnum" 2>/dev/null)"
|
|
devnum="$(cat "$dev_path/devnum" 2>/dev/null)"
|
|
port="$(find_usb_tty "$dev_path")"
|
|
ts="$(date -Iseconds)"
|
|
|
|
set_option_if_changed "$section" detected "1"
|
|
set_option_if_changed "$section" health "online"
|
|
[ -n "$bus" ] && set_option_if_changed "$section" bus "$bus"
|
|
[ -n "$devnum" ] && set_option_if_changed "$section" device "$devnum"
|
|
if [ -n "$port" ]; then
|
|
set_option_if_changed "$section" port "$port"
|
|
else
|
|
clear_option_if_needed "$section" port
|
|
fi
|
|
set_option_if_changed "$section" last_seen "$ts"
|
|
|
|
if [ "$prev_detected" != "1" ]; then
|
|
LAST_EVENT="$ts"
|
|
log_msg "Adapter $section ($title) detected on bus $bus dev $devnum $port"
|
|
run_rules "$section" "online"
|
|
fi
|
|
else
|
|
set_option_if_changed "$section" detected "0"
|
|
set_option_if_changed "$section" health "missing"
|
|
clear_option_if_needed "$section" port
|
|
clear_option_if_needed "$section" bus
|
|
clear_option_if_needed "$section" device
|
|
if [ "$prev_detected" = "1" ]; then
|
|
LAST_EVENT="$(date -Iseconds)"
|
|
log_msg "Adapter $section ($title) disconnected"
|
|
run_rules "$section" "missing"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
count_enabled_clients() {
|
|
local count=0
|
|
config_foreach _count_client adapter "$1"
|
|
echo "$count"
|
|
}
|
|
|
|
_count_client() {
|
|
local section="$1"
|
|
local enabled detected
|
|
config_get enabled "$section" enabled 1
|
|
config_get detected "$section" detected 0
|
|
if [ "$enabled" = "1" ] && [ "$detected" = "1" ]; then
|
|
count=$((count + 1))
|
|
fi
|
|
}
|
|
|
|
payload_count() {
|
|
uci -q show mqtt-bridge.payloads 2>/dev/null | grep -c '=payload'
|
|
}
|
|
|
|
update_stats() {
|
|
local now payloads clients delta count elapsed
|
|
now="$(date +%s)"
|
|
|
|
config_load "$UCI_NAMESPACE"
|
|
count=0
|
|
config_foreach _count_client adapter
|
|
clients="$count"
|
|
|
|
payloads="$(payload_count)"
|
|
elapsed=$((now - PREV_TIMESTAMP))
|
|
if [ "$elapsed" -gt 0 ]; then
|
|
delta=$((payloads - PREV_PAYLOAD_COUNT))
|
|
if [ "$delta" -lt 0 ]; then
|
|
delta=0
|
|
fi
|
|
local mps=$((delta / elapsed))
|
|
uci set ${UCI_NAMESPACE}.stats.mps="$mps"
|
|
fi
|
|
PREV_PAYLOAD_COUNT="$payloads"
|
|
PREV_TIMESTAMP="$now"
|
|
|
|
uci set ${UCI_NAMESPACE}.stats.clients="$clients"
|
|
uci set ${UCI_NAMESPACE}.stats.retained="${payloads:-0}"
|
|
uci set ${UCI_NAMESPACE}.stats.uptime="$(format_duration $((now - START_TIME)))"
|
|
[ -n "$LAST_EVENT" ] && uci set ${UCI_NAMESPACE}.stats.last_event="$LAST_EVENT"
|
|
|
|
uci commit ${UCI_NAMESPACE}
|
|
}
|
|
|
|
run_detection_once() {
|
|
COMMIT_NEEDED=0
|
|
config_load "$UCI_NAMESPACE"
|
|
config_foreach update_adapter_section adapter
|
|
if [ "$COMMIT_NEEDED" -eq 1 ]; then
|
|
uci commit "$UCI_NAMESPACE"
|
|
fi
|
|
update_stats
|
|
}
|
|
|
|
daemon_loop() {
|
|
while true; do
|
|
load_interval
|
|
run_detection_once
|
|
sleep "$SCAN_INTERVAL"
|
|
done
|
|
}
|
|
|
|
case "$1" in
|
|
--rescan)
|
|
run_detection_once
|
|
;;
|
|
--oneshot)
|
|
run_detection_once
|
|
;;
|
|
*)
|
|
daemon_loop
|
|
;;
|
|
esac
|