feat(secubox-core): Add LED heartbeat for MochaBin and Vortex services dashboard
- Add LED heartbeat to secubox-core daemon using MochaBin's RGB LEDs (led1) - Green flash: system healthy - Double red flash: warning state - Long red flash: error state - Blue flash: boot/startup - LED pulses once per watchdog cycle (default 60s) - New UCI options: led_heartbeat (default 1), watchdog_interval (default 60) - Add "Node Services" section to Vortex DNS LuCI dashboard showing published sites - Bump secubox-core version to 0.10.0-r12 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e3784537c3
commit
5205b3b2bd
@ -50,12 +50,19 @@ var callMeshSync = rpc.declare({
|
|||||||
expect: {}
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callGetPublished = rpc.declare({
|
||||||
|
object: 'luci.vortex-dns',
|
||||||
|
method: 'get_published',
|
||||||
|
expect: { services: [] }
|
||||||
|
});
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
callStatus(),
|
callStatus(),
|
||||||
callGetSlaves(),
|
callGetSlaves(),
|
||||||
callGetPeers()
|
callGetPeers(),
|
||||||
|
callGetPublished()
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -63,6 +70,7 @@ return view.extend({
|
|||||||
var status = data[0] || {};
|
var status = data[0] || {};
|
||||||
var slaves = data[1] || [];
|
var slaves = data[1] || [];
|
||||||
var peers = data[2] || [];
|
var peers = data[2] || [];
|
||||||
|
var published = data[3] || [];
|
||||||
|
|
||||||
var view = E('div', { 'class': 'cbi-map' }, [
|
var view = E('div', { 'class': 'cbi-map' }, [
|
||||||
E('h2', {}, 'Vortex DNS'),
|
E('h2', {}, 'Vortex DNS'),
|
||||||
@ -103,6 +111,9 @@ return view.extend({
|
|||||||
// Mesh Peers Section
|
// Mesh Peers Section
|
||||||
this.renderPeersSection(status.mesh, peers),
|
this.renderPeersSection(status.mesh, peers),
|
||||||
|
|
||||||
|
// Published Services Section
|
||||||
|
this.renderServicesSection(published, status.master),
|
||||||
|
|
||||||
// Actions Section
|
// Actions Section
|
||||||
this.renderActionsSection(status)
|
this.renderActionsSection(status)
|
||||||
]);
|
]);
|
||||||
@ -225,6 +236,58 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderServicesSection: function(services, master) {
|
||||||
|
var wildcard = master ? master.wildcard_domain : null;
|
||||||
|
var nodePrefix = wildcard ? wildcard.split('.')[0] : null;
|
||||||
|
|
||||||
|
// Deduplicate services by name
|
||||||
|
var seen = {};
|
||||||
|
var uniqueServices = [];
|
||||||
|
(services || []).forEach(function(s) {
|
||||||
|
if (!seen[s.name]) {
|
||||||
|
seen[s.name] = true;
|
||||||
|
uniqueServices.push(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('h3', {}, 'Node Services'),
|
||||||
|
E('div', { 'class': 'cbi-section-descr' },
|
||||||
|
wildcard ? 'Services published on *.' + wildcard : 'Published services on this node'),
|
||||||
|
|
||||||
|
uniqueServices.length > 0 ? E('table', { 'class': 'table' }, [
|
||||||
|
E('tr', { 'class': 'tr table-titles' }, [
|
||||||
|
E('th', { 'class': 'th' }, 'Service'),
|
||||||
|
E('th', { 'class': 'th' }, 'Domain'),
|
||||||
|
E('th', { 'class': 'th' }, 'Node URL'),
|
||||||
|
E('th', { 'class': 'th' }, 'Actions')
|
||||||
|
])
|
||||||
|
].concat(uniqueServices.map(function(s) {
|
||||||
|
var nodeUrl = nodePrefix ? 'https://' + s.name + '.' + wildcard : null;
|
||||||
|
return E('tr', { 'class': 'tr' }, [
|
||||||
|
E('td', { 'class': 'td' }, E('strong', {}, s.name)),
|
||||||
|
E('td', { 'class': 'td' }, E('a', {
|
||||||
|
'href': 'https://' + s.domain,
|
||||||
|
'target': '_blank',
|
||||||
|
'style': 'color: #00d4aa;'
|
||||||
|
}, s.domain)),
|
||||||
|
E('td', { 'class': 'td' }, nodeUrl ? E('a', {
|
||||||
|
'href': nodeUrl,
|
||||||
|
'target': '_blank',
|
||||||
|
'style': 'color: #888; font-size: 0.9em;'
|
||||||
|
}, s.name + '.' + wildcard) : '-'),
|
||||||
|
E('td', { 'class': 'td' }, E('a', {
|
||||||
|
'href': 'https://' + s.domain,
|
||||||
|
'target': '_blank',
|
||||||
|
'class': 'btn cbi-button-action',
|
||||||
|
'style': 'padding: 2px 8px; font-size: 0.85em;'
|
||||||
|
}, 'Open'))
|
||||||
|
]);
|
||||||
|
}))) : E('p', { 'style': 'color: #888; margin-top: 8px;' },
|
||||||
|
'No services published. Use "metablogizerctl emancipate" to publish sites.')
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
renderActionsSection: function(status) {
|
renderActionsSection: function(status) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|||||||
@ -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:=11
|
PKG_RELEASE:=12
|
||||||
PKG_ARCH:=all
|
PKG_ARCH:=all
|
||||||
PKG_LICENSE:=GPL-2.0
|
PKG_LICENSE:=GPL-2.0
|
||||||
PKG_MAINTAINER:=SecuBox Team
|
PKG_MAINTAINER:=SecuBox Team
|
||||||
|
|||||||
@ -5,6 +5,8 @@ config core 'main'
|
|||||||
option appstore_url 'https://repo.secubox.org/catalog'
|
option appstore_url 'https://repo.secubox.org/catalog'
|
||||||
option appstore_fallback_local '1'
|
option appstore_fallback_local '1'
|
||||||
option health_check_interval '300'
|
option health_check_interval '300'
|
||||||
|
option watchdog_interval '60'
|
||||||
|
option led_heartbeat '1'
|
||||||
option ai_enabled '0'
|
option ai_enabled '0'
|
||||||
option ai_mode 'copilot'
|
option ai_mode 'copilot'
|
||||||
option device_id ''
|
option device_id ''
|
||||||
|
|||||||
@ -10,16 +10,82 @@
|
|||||||
. /lib/functions.sh
|
. /lib/functions.sh
|
||||||
. /usr/share/libubox/jshn.sh
|
. /usr/share/libubox/jshn.sh
|
||||||
|
|
||||||
SECUBOX_VERSION="0.8.1"
|
SECUBOX_VERSION="0.8.2"
|
||||||
LOG_FILE="/var/log/secubox/core.log"
|
LOG_FILE="/var/log/secubox/core.log"
|
||||||
PID_FILE="/var/run/secubox/core.pid"
|
PID_FILE="/var/run/secubox/core.pid"
|
||||||
STATE_DIR="/var/run/secubox"
|
STATE_DIR="/var/run/secubox"
|
||||||
WATCHDOG_STATE="/var/run/secubox/watchdog.json"
|
WATCHDOG_STATE="/var/run/secubox/watchdog.json"
|
||||||
|
|
||||||
|
# LED paths for MochaBin (RGB LEDs: led1, led2, led3)
|
||||||
|
LED_GREEN="/sys/class/leds/green:led1"
|
||||||
|
LED_RED="/sys/class/leds/red:led1"
|
||||||
|
LED_BLUE="/sys/class/leds/blue:led1"
|
||||||
|
LED_ENABLED=0
|
||||||
|
|
||||||
# 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=""
|
MONITORED_SERVICES=""
|
||||||
|
|
||||||
|
# LED helper functions
|
||||||
|
led_init() {
|
||||||
|
# Check if LEDs are available (MochaBin or compatible)
|
||||||
|
if [ -d "$LED_GREEN" ] && [ -d "$LED_RED" ]; then
|
||||||
|
LED_ENABLED=1
|
||||||
|
# Set trigger to none for manual control
|
||||||
|
echo none > "$LED_GREEN/trigger" 2>/dev/null
|
||||||
|
echo none > "$LED_RED/trigger" 2>/dev/null
|
||||||
|
echo none > "$LED_BLUE/trigger" 2>/dev/null
|
||||||
|
log debug "LED heartbeat enabled (MochaBin detected)"
|
||||||
|
else
|
||||||
|
log debug "LED heartbeat disabled (no compatible LEDs)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
led_set() {
|
||||||
|
local led="$1"
|
||||||
|
local state="$2" # 0 = off, 1 = on
|
||||||
|
[ "$LED_ENABLED" = "1" ] || return 0
|
||||||
|
[ -f "${led}/brightness" ] && echo "$state" > "${led}/brightness" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
led_pulse() {
|
||||||
|
local led="$1"
|
||||||
|
[ "$LED_ENABLED" = "1" ] || return 0
|
||||||
|
led_set "$led" 255
|
||||||
|
# Brief flash - actual timing handled by background subshell
|
||||||
|
( sleep 1 && led_set "$led" 0 ) &
|
||||||
|
}
|
||||||
|
|
||||||
|
# Heartbeat function - shows system status via LED
|
||||||
|
# Green pulse = healthy, Red pulse = warning/error
|
||||||
|
led_heartbeat() {
|
||||||
|
[ "$LED_ENABLED" = "1" ] || return 0
|
||||||
|
local status="$1" # healthy, warning, error
|
||||||
|
|
||||||
|
case "$status" in
|
||||||
|
healthy)
|
||||||
|
# Single green flash
|
||||||
|
led_set "$LED_GREEN" 255
|
||||||
|
( sleep 1 && echo 0 > "${LED_GREEN}/brightness" 2>/dev/null ) &
|
||||||
|
;;
|
||||||
|
warning)
|
||||||
|
# Double red flash
|
||||||
|
led_set "$LED_RED" 255
|
||||||
|
( sleep 1 && echo 0 > "${LED_RED}/brightness" && sleep 1 && echo 255 > "${LED_RED}/brightness" && sleep 1 && echo 0 > "${LED_RED}/brightness" ) &
|
||||||
|
;;
|
||||||
|
error)
|
||||||
|
# Long red flash
|
||||||
|
led_set "$LED_RED" 255
|
||||||
|
( sleep 2 && echo 0 > "${LED_RED}/brightness" 2>/dev/null ) &
|
||||||
|
;;
|
||||||
|
boot)
|
||||||
|
# Blue pulse during startup
|
||||||
|
led_set "$LED_BLUE" 255
|
||||||
|
( sleep 2 && echo 0 > "${LED_BLUE}/brightness" 2>/dev/null ) &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# Auto-discover SecuBox services from ctl scripts
|
# Auto-discover SecuBox services from ctl scripts
|
||||||
discover_secubox_services() {
|
discover_secubox_services() {
|
||||||
local services=""
|
local services=""
|
||||||
@ -429,16 +495,24 @@ daemon_mode() {
|
|||||||
# Write PID
|
# Write PID
|
||||||
echo $$ > "$PID_FILE"
|
echo $$ > "$PID_FILE"
|
||||||
|
|
||||||
|
# Initialize LED heartbeat
|
||||||
|
led_init
|
||||||
|
led_heartbeat boot
|
||||||
|
|
||||||
# Get health check interval
|
# Get health check interval
|
||||||
local health_interval=$(uci -q get secubox.main.health_check_interval || echo "300")
|
local health_interval=$(uci -q get secubox.main.health_check_interval || echo "300")
|
||||||
|
|
||||||
# Get watchdog interval (faster than health check)
|
# Get watchdog interval (faster than health check)
|
||||||
local watchdog_interval=$(uci -q get secubox.main.watchdog_interval || echo "60")
|
local watchdog_interval=$(uci -q get secubox.main.watchdog_interval || echo "60")
|
||||||
|
|
||||||
|
# LED heartbeat setting (enabled by default)
|
||||||
|
local led_heartbeat_enabled=$(uci -q get secubox.main.led_heartbeat || echo "1")
|
||||||
|
|
||||||
# Main daemon loop
|
# Main daemon loop
|
||||||
local health_counter=0
|
local health_counter=0
|
||||||
local health_cycles=$((health_interval / watchdog_interval))
|
local health_cycles=$((health_interval / watchdog_interval))
|
||||||
[ "$health_cycles" -lt 1 ] && health_cycles=1
|
[ "$health_cycles" -lt 1 ] && health_cycles=1
|
||||||
|
local last_health_status="healthy"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
# Run watchdog every cycle
|
# Run watchdog every cycle
|
||||||
@ -447,10 +521,18 @@ daemon_mode() {
|
|||||||
# Run health check every N cycles
|
# Run health check every N cycles
|
||||||
health_counter=$((health_counter + 1))
|
health_counter=$((health_counter + 1))
|
||||||
if [ "$health_counter" -ge "$health_cycles" ]; then
|
if [ "$health_counter" -ge "$health_cycles" ]; then
|
||||||
run_health_check > /tmp/secubox/health-status.json
|
local health_output=$(run_health_check)
|
||||||
|
echo "$health_output" > /tmp/secubox/health-status.json
|
||||||
|
# Extract status for LED
|
||||||
|
last_health_status=$(echo "$health_output" | jsonfilter -e '@.status' 2>/dev/null || echo "healthy")
|
||||||
health_counter=0
|
health_counter=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# LED heartbeat pulse (once per watchdog cycle)
|
||||||
|
if [ "$led_heartbeat_enabled" = "1" ]; then
|
||||||
|
led_heartbeat "$last_health_status"
|
||||||
|
fi
|
||||||
|
|
||||||
# Sleep until next check
|
# Sleep until next check
|
||||||
sleep "$watchdog_interval"
|
sleep "$watchdog_interval"
|
||||||
done
|
done
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user