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:
CyberMind-FR 2026-02-06 09:38:13 +01:00
parent e3784537c3
commit 5205b3b2bd
4 changed files with 151 additions and 4 deletions

View File

@ -50,12 +50,19 @@ var callMeshSync = rpc.declare({
expect: {}
});
var callGetPublished = rpc.declare({
object: 'luci.vortex-dns',
method: 'get_published',
expect: { services: [] }
});
return view.extend({
load: function() {
return Promise.all([
callStatus(),
callGetSlaves(),
callGetPeers()
callGetPeers(),
callGetPublished()
]);
},
@ -63,6 +70,7 @@ return view.extend({
var status = data[0] || {};
var slaves = data[1] || [];
var peers = data[2] || [];
var published = data[3] || [];
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, 'Vortex DNS'),
@ -103,6 +111,9 @@ return view.extend({
// Mesh Peers Section
this.renderPeersSection(status.mesh, peers),
// Published Services Section
this.renderServicesSection(published, status.master),
// Actions Section
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) {
var self = this;

View File

@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=secubox-core
PKG_VERSION:=0.10.0
PKG_RELEASE:=11
PKG_RELEASE:=12
PKG_ARCH:=all
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=SecuBox Team

View File

@ -5,6 +5,8 @@ config core 'main'
option appstore_url 'https://repo.secubox.org/catalog'
option appstore_fallback_local '1'
option health_check_interval '300'
option watchdog_interval '60'
option led_heartbeat '1'
option ai_enabled '0'
option ai_mode 'copilot'
option device_id ''

View File

@ -10,16 +10,82 @@
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
SECUBOX_VERSION="0.8.1"
SECUBOX_VERSION="0.8.2"
LOG_FILE="/var/log/secubox/core.log"
PID_FILE="/var/run/secubox/core.pid"
STATE_DIR="/var/run/secubox"
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)
# check_method: pid, docker, lxc, port:PORT
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
discover_secubox_services() {
local services=""
@ -429,16 +495,24 @@ daemon_mode() {
# Write PID
echo $$ > "$PID_FILE"
# Initialize LED heartbeat
led_init
led_heartbeat boot
# Get health check interval
local health_interval=$(uci -q get secubox.main.health_check_interval || echo "300")
# Get watchdog interval (faster than health check)
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
local health_counter=0
local health_cycles=$((health_interval / watchdog_interval))
[ "$health_cycles" -lt 1 ] && health_cycles=1
local last_health_status="healthy"
while true; do
# Run watchdog every cycle
@ -447,10 +521,18 @@ daemon_mode() {
# Run health check every N cycles
health_counter=$((health_counter + 1))
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
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 "$watchdog_interval"
done