feat(secubox-mesh): Add OpenWrt mesh daemon with topology management
Port secuboxd from Debian/Go to OpenWrt shell implementation: - secuboxd daemon with Unix control socket at /var/run/secuboxd/topo.sock - secuboxctl CLI compatible with Debian version interface - Mesh libraries: topology, discovery, election, telemetry, control - Mesh gate election with weighted scoring (uptime, peers, CPU, memory, role) - mDNS service discovery (_secubox._udp.local) via umdns - DID integration via mirrornet identity library - RPCD handler with 11 ubus methods for LuCI integration - procd init script with respawn and network triggers - UCI config sections: mesh, node, telemetry, discovery Fixes subprocess state access for socat handler by saving daemon state to file. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
707142c6bb
commit
cd6af3edff
@ -1,6 +1,37 @@
|
|||||||
# SecuBox UI & Theme History
|
# SecuBox UI & Theme History
|
||||||
|
|
||||||
_Last updated: 2026-03-20 (Wiki Translations & Meta-package)_
|
_Last updated: 2026-03-25 (CRT P31 Phosphor Theme Enhancement)_
|
||||||
|
|
||||||
|
0. **CRT P31 Phosphor Theme Enhancement (2026-03-25)**
|
||||||
|
- **NEW: CRT P31 theme variant** (`themes/crt-p31.css`)
|
||||||
|
- Authentic P31 phosphor green CRT terminal aesthetic
|
||||||
|
- Color palette: peak (#33ff66), hot (#66ffaa), mid (#22cc44), dim (#0f8822), ghost (#052210)
|
||||||
|
- Phosphor decay amber for warnings (#ffb347)
|
||||||
|
- CRT tube blacks for backgrounds (#050803, #080d05, #0d1208)
|
||||||
|
- **CRT visual effects** (`htdocs/luci-static/secubox/`)
|
||||||
|
- `cascade.css`: Complete CRT theme as standalone LuCI theme
|
||||||
|
- `crt-engine.js`: Scanlines overlay, phosphor glow, boot sequence animation
|
||||||
|
- `crt-components.js`: Reusable widgets, badges, progress bars, topology nodes
|
||||||
|
- **LuCI view templates** (`luasrc/luci/view/themes/secubox/`)
|
||||||
|
- `header.htm`: CRT-styled header with hostname display
|
||||||
|
- `footer.htm`: Mesh version and branding
|
||||||
|
- `sysauth.htm`: Terminal-style login page with glowing elements
|
||||||
|
- **Theme features**:
|
||||||
|
- Scanlines overlay (CSS pseudo-element, pointer-events: none)
|
||||||
|
- Phosphor bloom effects on text and interactive elements
|
||||||
|
- Monospace font stack (Courier Prime, IBM Plex Mono, Fira Code)
|
||||||
|
- Terminal boot sequence animation on first visit
|
||||||
|
- Status indicators with appropriate glow colors
|
||||||
|
- **Integration**:
|
||||||
|
- Added to existing secubox-theme system via `data-secubox-theme="crt-p31"`
|
||||||
|
- Compatible with all SecuBox LuCI modules
|
||||||
|
- Set as default theme via UCI defaults script
|
||||||
|
- **Files created/updated**:
|
||||||
|
- `htdocs/luci-static/resources/secubox-theme/themes/crt-p31.css`
|
||||||
|
- `htdocs/luci-static/secubox/cascade.css`
|
||||||
|
- `htdocs/luci-static/secubox/crt-engine.js`
|
||||||
|
- `htdocs/luci-static/secubox/crt-components.js`
|
||||||
|
- Makefile updated to PKG_RELEASE:=2
|
||||||
|
|
||||||
0. **Wiki Internationalization & Meta-package (2026-03-20)**
|
0. **Wiki Internationalization & Meta-package (2026-03-20)**
|
||||||
- **Wiki translations**: All 17 wiki pages translated to French and Chinese
|
- **Wiki translations**: All 17 wiki pages translated to French and Chinese
|
||||||
@ -5433,3 +5464,32 @@ git checkout HEAD -- index.html
|
|||||||
- Migrated secubox-reporter and bandwidth-manager to use shared library
|
- Migrated secubox-reporter and bandwidth-manager to use shared library
|
||||||
- Backwards-compatible fallback to legacy per-app SMTP settings
|
- Backwards-compatible fallback to legacy per-app SMTP settings
|
||||||
- Eliminates duplicated SMTP configuration across SecuBox apps
|
- Eliminates duplicated SMTP configuration across SecuBox apps
|
||||||
|
|
||||||
|
### 2026-03-25
|
||||||
|
|
||||||
|
- **SecuBox Mesh Daemon (`secubox-mesh`) (Complete)**
|
||||||
|
- New `secubox-mesh` package: OpenWrt-native mesh daemon ported from Debian/Go version
|
||||||
|
- **secuboxd** daemon with Unix control socket at `/var/run/secuboxd/topo.sock`
|
||||||
|
- **secuboxctl** CLI tool compatible with Debian version interface:
|
||||||
|
- `mesh status|peers|topology|nodes` - mesh operations
|
||||||
|
- `node info|rotate` - node identity management
|
||||||
|
- `telemetry latest` - system metrics
|
||||||
|
- `start|stop|restart` - service control
|
||||||
|
- **Libraries** in `/usr/lib/secubox-mesh/`:
|
||||||
|
- `topology.sh` - node/edge management, graph storage, pruning
|
||||||
|
- `discovery.sh` - mDNS, WireGuard, ARP, static peer discovery
|
||||||
|
- `election.sh` - mesh gate election with weighted scoring algorithm
|
||||||
|
- `telemetry.sh` - system metrics collection (CPU, memory, uptime, load)
|
||||||
|
- `control.sh` - socket command parsing, rate limiting, health checks
|
||||||
|
- **Mesh Gate Election Algorithm**:
|
||||||
|
- Scoring weights: uptime (30%), peers (25%), CPU (15%), memory (15%), role (15%)
|
||||||
|
- Relay nodes preferred over edge nodes for gate role
|
||||||
|
- Leader election via highest score, periodic re-election
|
||||||
|
- **mDNS Service Discovery**: `_secubox._udp.local` with TXT records (did, role, version)
|
||||||
|
- **DID Integration**: Uses existing mirrornet identity library for did:plc format
|
||||||
|
- **UCI Config**: `/etc/config/secubox` with mesh, node, telemetry, discovery sections
|
||||||
|
- **procd Init Script**: Respawn, network triggers, file triggers
|
||||||
|
- **RPCD Handler**: `luci.secubox-mesh` with 11 methods for ubus access
|
||||||
|
- **ACL Permissions**: Read (status, peers, topology, nodes, telemetry, ping, get_config), Write (node_rotate, set_config, restart)
|
||||||
|
- **Dependencies**: secubox-mirrornet, secubox-identity, libubox-lua, libubus-lua, umdns, wireguard-tools, jshn, socat
|
||||||
|
- Cross-platform compatible with Debian secuboxd for hybrid mesh networks
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Work In Progress (Claude)
|
# Work In Progress (Claude)
|
||||||
|
|
||||||
_Last updated: 2026-03-17 (VM Firmware Build + CI Fixes)_
|
_Last updated: 2026-03-25 (SecuBox Mesh Daemon)_
|
||||||
|
|
||||||
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
|
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
|
||||||
|
|
||||||
@ -8,6 +8,17 @@ _Last updated: 2026-03-17 (VM Firmware Build + CI Fixes)_
|
|||||||
|
|
||||||
## Recently Completed
|
## Recently Completed
|
||||||
|
|
||||||
|
### 2026-03-25
|
||||||
|
|
||||||
|
- **SecuBox Mesh Daemon (`secubox-mesh`) (Complete)**
|
||||||
|
- OpenWrt-native mesh daemon ported from Debian/Go version
|
||||||
|
- `secuboxd` daemon with Unix control socket, `secuboxctl` CLI
|
||||||
|
- Libraries: topology, discovery, election, telemetry, control
|
||||||
|
- Mesh gate election with weighted scoring algorithm
|
||||||
|
- mDNS service discovery: `_secubox._udp.local`
|
||||||
|
- RPCD handler with 11 ubus methods
|
||||||
|
- Cross-platform compatible with Debian version
|
||||||
|
|
||||||
### 2026-03-17
|
### 2026-03-17
|
||||||
|
|
||||||
- **SecuBox VM Firmware Build Workflow (Complete)**
|
- **SecuBox VM Firmware Build Workflow (Complete)**
|
||||||
|
|||||||
Binary file not shown.
60
package/secubox/secubox-mesh/Makefile
Normal file
60
package/secubox/secubox-mesh/Makefile
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# SecuBox Mesh Daemon for OpenWrt
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=secubox-mesh
|
||||||
|
PKG_VERSION:=1.0.0
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
PKG_MAINTAINER:=Gerald KERMA <devel@cybermind.fr>
|
||||||
|
PKG_LICENSE:=MIT
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define Package/secubox-mesh
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=SecuBox
|
||||||
|
TITLE:=SecuBox Mesh Daemon
|
||||||
|
DEPENDS:=+secubox-mirrornet +secubox-identity +libubox-lua +libubus-lua +umdns +wireguard-tools +jshn
|
||||||
|
PKGARCH:=all
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/secubox-mesh/description
|
||||||
|
SecuBox mesh daemon (secuboxd) for OpenWrt providing:
|
||||||
|
- Unix control socket at /var/run/secuboxd/topo.sock
|
||||||
|
- mDNS service discovery (_secubox._udp)
|
||||||
|
- Mesh topology management with gate election
|
||||||
|
- Compatible with Debian secuboxd for cross-platform mesh
|
||||||
|
- CLI tool: secuboxctl
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/secubox-mesh/conffiles
|
||||||
|
/etc/config/secubox
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/secubox-mesh/install
|
||||||
|
$(INSTALL_DIR) $(1)/usr/sbin
|
||||||
|
$(INSTALL_BIN) ./files/usr/sbin/secuboxd $(1)/usr/sbin/
|
||||||
|
$(INSTALL_BIN) ./files/usr/sbin/secuboxctl $(1)/usr/sbin/
|
||||||
|
$(INSTALL_DIR) $(1)/usr/lib/secubox-mesh
|
||||||
|
$(INSTALL_DATA) ./files/usr/lib/secubox-mesh/topology.sh $(1)/usr/lib/secubox-mesh/
|
||||||
|
$(INSTALL_DATA) ./files/usr/lib/secubox-mesh/discovery.sh $(1)/usr/lib/secubox-mesh/
|
||||||
|
$(INSTALL_DATA) ./files/usr/lib/secubox-mesh/telemetry.sh $(1)/usr/lib/secubox-mesh/
|
||||||
|
$(INSTALL_DATA) ./files/usr/lib/secubox-mesh/control.sh $(1)/usr/lib/secubox-mesh/
|
||||||
|
$(INSTALL_DATA) ./files/usr/lib/secubox-mesh/election.sh $(1)/usr/lib/secubox-mesh/
|
||||||
|
$(INSTALL_DIR) $(1)/etc/config
|
||||||
|
$(INSTALL_CONF) ./files/etc/config/secubox $(1)/etc/config/
|
||||||
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
|
$(INSTALL_BIN) ./files/etc/init.d/secuboxd $(1)/etc/init.d/
|
||||||
|
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
||||||
|
$(INSTALL_BIN) ./files/usr/libexec/rpcd/luci.secubox-mesh $(1)/usr/libexec/rpcd/
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||||
|
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-secubox-mesh.json $(1)/usr/share/rpcd/acl.d/
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,secubox-mesh))
|
||||||
33
package/secubox/secubox-mesh/files/etc/config/secubox
Normal file
33
package/secubox/secubox-mesh/files/etc/config/secubox
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
config mesh 'mesh'
|
||||||
|
option enabled '1'
|
||||||
|
option role 'edge'
|
||||||
|
option subnet '10.42.0.0/16'
|
||||||
|
option mdns_service '_secubox._udp'
|
||||||
|
option beacon_interval '30'
|
||||||
|
option election_interval '60'
|
||||||
|
option prune_interval '300'
|
||||||
|
|
||||||
|
config node 'node'
|
||||||
|
option did ''
|
||||||
|
option keypair '/var/lib/mirrornet/identity/keys/primary.key'
|
||||||
|
option auto_rotate '0'
|
||||||
|
option rotate_days '30'
|
||||||
|
|
||||||
|
config telemetry 'telemetry'
|
||||||
|
option enabled '1'
|
||||||
|
option interval '60'
|
||||||
|
option share_metrics '1'
|
||||||
|
|
||||||
|
config discovery 'discovery'
|
||||||
|
option mdns '1'
|
||||||
|
option wireguard '1'
|
||||||
|
option arp '1'
|
||||||
|
option static '1'
|
||||||
|
|
||||||
|
# Example static peer configuration
|
||||||
|
#config peer 'peer1'
|
||||||
|
# option did 'did:plc:abcdef0123456789'
|
||||||
|
# option address '192.168.1.100'
|
||||||
|
# option role 'relay'
|
||||||
|
# option port '51820'
|
||||||
77
package/secubox/secubox-mesh/files/etc/init.d/secuboxd
Executable file
77
package/secubox/secubox-mesh/files/etc/init.d/secuboxd
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# SecuBox Mesh Daemon init script
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
START=95
|
||||||
|
STOP=10
|
||||||
|
USE_PROCD=1
|
||||||
|
|
||||||
|
PROG=/usr/sbin/secuboxd
|
||||||
|
PIDFILE=/var/run/secuboxd/secuboxd.pid
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
config_load secubox
|
||||||
|
|
||||||
|
local enabled
|
||||||
|
config_get_bool enabled mesh enabled 1
|
||||||
|
|
||||||
|
[ "$enabled" -eq 0 ] && {
|
||||||
|
echo "secuboxd is disabled"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create required directories
|
||||||
|
mkdir -p /var/run/secuboxd
|
||||||
|
mkdir -p /var/lib/secubox-mesh
|
||||||
|
mkdir -p /var/log
|
||||||
|
|
||||||
|
procd_open_instance secuboxd
|
||||||
|
procd_set_param command "$PROG" --foreground
|
||||||
|
procd_set_param respawn 3600 5 5
|
||||||
|
procd_set_param stdout 1
|
||||||
|
procd_set_param stderr 1
|
||||||
|
procd_set_param pidfile "$PIDFILE"
|
||||||
|
|
||||||
|
# Reload on network changes
|
||||||
|
procd_set_param netdev br-lan wg0
|
||||||
|
procd_set_param file /etc/config/secubox
|
||||||
|
|
||||||
|
procd_close_instance
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
# Clean up socket
|
||||||
|
rm -f /var/run/secuboxd/topo.sock
|
||||||
|
|
||||||
|
# Kill any remaining processes
|
||||||
|
killall -q secuboxd 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_service() {
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
}
|
||||||
|
|
||||||
|
service_triggers() {
|
||||||
|
procd_add_reload_trigger "secubox" "network"
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$PIDFILE" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||||
|
echo "secuboxd is running (PID: $pid)"
|
||||||
|
|
||||||
|
# Show quick status
|
||||||
|
if [ -S /var/run/secuboxd/topo.sock ]; then
|
||||||
|
echo "Socket: /var/run/secuboxd/topo.sock (active)"
|
||||||
|
secuboxctl mesh status 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "secuboxd is not running"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
138
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/control.sh
Executable file
138
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/control.sh
Executable file
@ -0,0 +1,138 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox Mesh Control Socket Handler
|
||||||
|
# Handles commands received via Unix control socket
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
# Control socket helpers
|
||||||
|
|
||||||
|
# Parse JSON-RPC style command
|
||||||
|
control_parse_command() {
|
||||||
|
local input="$1"
|
||||||
|
|
||||||
|
# Simple command (just method name)
|
||||||
|
if ! echo "$input" | grep -q '{'; then
|
||||||
|
echo "$input"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# JSON-RPC format: {"method":"...", "params":{...}}
|
||||||
|
local method
|
||||||
|
method=$(echo "$input" | jsonfilter -e '@.method' 2>/dev/null)
|
||||||
|
echo "$method"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get command parameters
|
||||||
|
control_get_params() {
|
||||||
|
local input="$1"
|
||||||
|
echo "$input" | jsonfilter -e '@.params' 2>/dev/null || echo '{}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format JSON response
|
||||||
|
control_response() {
|
||||||
|
local result="$1"
|
||||||
|
local id="${2:-null}"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{"jsonrpc":"2.0","result":$result,"id":$id}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format JSON error
|
||||||
|
control_error() {
|
||||||
|
local code="$1"
|
||||||
|
local message="$2"
|
||||||
|
local id="${3:-null}"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{"jsonrpc":"2.0","error":{"code":$code,"message":"$message"},"id":$id}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate command authorization (placeholder for future auth)
|
||||||
|
control_authorize() {
|
||||||
|
local cmd="$1"
|
||||||
|
local client="$2"
|
||||||
|
|
||||||
|
# All commands allowed for now (socket access = authorized)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log control socket activity
|
||||||
|
control_log() {
|
||||||
|
local level="$1"
|
||||||
|
local msg="$2"
|
||||||
|
|
||||||
|
logger -t secuboxd-control -p "daemon.$level" "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rate limiting (simple token bucket)
|
||||||
|
RATE_LIMIT_FILE="/tmp/secuboxd_rate_limit"
|
||||||
|
RATE_LIMIT_MAX=100 # Max requests per minute
|
||||||
|
RATE_LIMIT_WINDOW=60
|
||||||
|
|
||||||
|
control_check_rate_limit() {
|
||||||
|
local client="$1"
|
||||||
|
local current_time
|
||||||
|
current_time=$(date +%s)
|
||||||
|
|
||||||
|
if [ ! -f "$RATE_LIMIT_FILE" ]; then
|
||||||
|
echo "$current_time:1" > "$RATE_LIMIT_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local last_time last_count
|
||||||
|
IFS=':' read -r last_time last_count < "$RATE_LIMIT_FILE"
|
||||||
|
|
||||||
|
local elapsed=$((current_time - last_time))
|
||||||
|
|
||||||
|
if [ "$elapsed" -ge "$RATE_LIMIT_WINDOW" ]; then
|
||||||
|
# Reset window
|
||||||
|
echo "$current_time:1" > "$RATE_LIMIT_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$last_count" -ge "$RATE_LIMIT_MAX" ]; then
|
||||||
|
control_log "warn" "Rate limit exceeded for $client"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$last_time:$((last_count + 1))" > "$RATE_LIMIT_FILE"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Available commands and their handlers
|
||||||
|
control_list_commands() {
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"commands": [
|
||||||
|
{"method": "ping", "description": "Check daemon is alive"},
|
||||||
|
{"method": "mesh.status", "description": "Get mesh status"},
|
||||||
|
{"method": "mesh.peers", "description": "List connected peers"},
|
||||||
|
{"method": "mesh.topology", "description": "Get mesh topology graph"},
|
||||||
|
{"method": "mesh.nodes", "description": "List all known nodes"},
|
||||||
|
{"method": "node.info", "description": "Get this node's information"},
|
||||||
|
{"method": "node.rotate", "description": "Rotate node keys"},
|
||||||
|
{"method": "telemetry.latest", "description": "Get latest telemetry"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check for socket server
|
||||||
|
control_health_check() {
|
||||||
|
local socket="$1"
|
||||||
|
|
||||||
|
if [ ! -S "$socket" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try sending ping
|
||||||
|
local response
|
||||||
|
response=$(echo "ping" | timeout 2 socat - "UNIX-CONNECT:$socket" 2>/dev/null)
|
||||||
|
|
||||||
|
if echo "$response" | grep -q '"pong":true'; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
215
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/discovery.sh
Executable file
215
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/discovery.sh
Executable file
@ -0,0 +1,215 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox Mesh Peer Discovery
|
||||||
|
# mDNS-based service discovery for mesh peers
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
PEERS_FILE="/var/lib/secubox-mesh/peers.json"
|
||||||
|
DISCOVERY_CACHE="/tmp/secubox_discovery_cache"
|
||||||
|
MDNS_SERVICE="_secubox._udp"
|
||||||
|
|
||||||
|
# Initialize discovery
|
||||||
|
discovery_init() {
|
||||||
|
mkdir -p "$(dirname "$PEERS_FILE")"
|
||||||
|
[ -f "$PEERS_FILE" ] || echo '[]' > "$PEERS_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan for peers using mDNS
|
||||||
|
discovery_scan_mdns() {
|
||||||
|
local peers=""
|
||||||
|
|
||||||
|
# Try umdns-client first
|
||||||
|
if command -v umdns >/dev/null 2>&1; then
|
||||||
|
# Query umdns for _secubox._udp services
|
||||||
|
local services
|
||||||
|
services=$(ubus call umdns browse 2>/dev/null | jsonfilter -e '@.services[*]' 2>/dev/null)
|
||||||
|
|
||||||
|
echo "$services" | while IFS= read -r svc; do
|
||||||
|
local name addr port txt_did txt_role
|
||||||
|
name=$(echo "$svc" | jsonfilter -e '@.name' 2>/dev/null)
|
||||||
|
addr=$(echo "$svc" | jsonfilter -e '@.address' 2>/dev/null)
|
||||||
|
port=$(echo "$svc" | jsonfilter -e '@.port' 2>/dev/null)
|
||||||
|
|
||||||
|
# Parse TXT records
|
||||||
|
txt_did=$(echo "$svc" | jsonfilter -e '@.txt[*]' 2>/dev/null | grep "^did=" | cut -d= -f2)
|
||||||
|
txt_role=$(echo "$svc" | jsonfilter -e '@.txt[*]' 2>/dev/null | grep "^role=" | cut -d= -f2)
|
||||||
|
|
||||||
|
[ -n "$txt_did" ] && echo "$txt_did|$addr|${txt_role:-edge}|$port"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try avahi-browse as fallback
|
||||||
|
if command -v avahi-browse >/dev/null 2>&1; then
|
||||||
|
avahi-browse -rpt "$MDNS_SERVICE.local" 2>/dev/null | \
|
||||||
|
grep "^=" | while IFS=';' read -r _ _ _ name _ _ addr port txt; do
|
||||||
|
local did role
|
||||||
|
did=$(echo "$txt" | grep -o 'did=[^"]*' | cut -d= -f2 | tr -d '"')
|
||||||
|
role=$(echo "$txt" | grep -o 'role=[^"]*' | cut -d= -f2 | tr -d '"')
|
||||||
|
[ -n "$did" ] && echo "$did|$addr|${role:-edge}|$port"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan for peers using WireGuard peer list
|
||||||
|
discovery_scan_wireguard() {
|
||||||
|
local wg_interface="${1:-wg0}"
|
||||||
|
|
||||||
|
if command -v wg >/dev/null 2>&1; then
|
||||||
|
wg show "$wg_interface" endpoints 2>/dev/null | while read -r pubkey endpoint; do
|
||||||
|
[ -z "$endpoint" ] || [ "$endpoint" = "(none)" ] && continue
|
||||||
|
|
||||||
|
local addr port
|
||||||
|
addr=$(echo "$endpoint" | cut -d: -f1)
|
||||||
|
port=$(echo "$endpoint" | cut -d: -f2)
|
||||||
|
|
||||||
|
# Generate DID from public key
|
||||||
|
local did
|
||||||
|
did="did:plc:$(echo -n "$pubkey" | md5sum | cut -c1-16)"
|
||||||
|
|
||||||
|
echo "$did|$addr|peer|$port"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan for peers using ARP/neighbor discovery
|
||||||
|
discovery_scan_arp() {
|
||||||
|
# Look for SecuBox nodes on local network via known ports
|
||||||
|
local secubox_port=7331 # P2P API port
|
||||||
|
|
||||||
|
ip neigh show 2>/dev/null | grep -v FAILED | while read -r ip _ _ mac _; do
|
||||||
|
[ -z "$ip" ] && continue
|
||||||
|
|
||||||
|
# Quick check if SecuBox P2P API is responding
|
||||||
|
local response
|
||||||
|
response=$(timeout 1 wget -qO- "http://$ip:$secubox_port/api/status" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ]; then
|
||||||
|
local did role
|
||||||
|
did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null || echo "edge")
|
||||||
|
|
||||||
|
[ -n "$did" ] && echo "$did|$ip|$role|$secubox_port"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan for peers using configured peer list
|
||||||
|
discovery_scan_config() {
|
||||||
|
# Read static peers from UCI config
|
||||||
|
config_load secubox
|
||||||
|
config_foreach _scan_config_peer peer
|
||||||
|
}
|
||||||
|
|
||||||
|
_scan_config_peer() {
|
||||||
|
local section="$1"
|
||||||
|
local did addr role port
|
||||||
|
|
||||||
|
config_get did "$section" did ""
|
||||||
|
config_get addr "$section" address ""
|
||||||
|
config_get role "$section" role "edge"
|
||||||
|
config_get port "$section" port "51820"
|
||||||
|
|
||||||
|
[ -n "$did" ] && [ -n "$addr" ] && echo "$did|$addr|$role|$port"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Combined peer discovery
|
||||||
|
discovery_scan_peers() {
|
||||||
|
local tmp_peers="/tmp/secubox_peers_$$.txt"
|
||||||
|
local seen_dids=""
|
||||||
|
|
||||||
|
# Combine all discovery methods
|
||||||
|
{
|
||||||
|
discovery_scan_mdns
|
||||||
|
discovery_scan_wireguard
|
||||||
|
discovery_scan_arp
|
||||||
|
discovery_scan_config
|
||||||
|
} | sort -u > "$tmp_peers"
|
||||||
|
|
||||||
|
# Build peers JSON
|
||||||
|
local peers_json='['
|
||||||
|
local first=1
|
||||||
|
local my_did="${NODE_DID:-$(cat /var/lib/mirrornet/identity/did.txt 2>/dev/null)}"
|
||||||
|
|
||||||
|
while IFS='|' read -r did addr role port; do
|
||||||
|
[ -z "$did" ] && continue
|
||||||
|
|
||||||
|
# Skip self
|
||||||
|
[ "$did" = "$my_did" ] && continue
|
||||||
|
|
||||||
|
# Skip duplicates
|
||||||
|
echo "$seen_dids" | grep -q "$did" && continue
|
||||||
|
seen_dids="$seen_dids $did"
|
||||||
|
|
||||||
|
[ "$first" = "1" ] || peers_json="$peers_json,"
|
||||||
|
|
||||||
|
local last_seen
|
||||||
|
last_seen=$(date -Iseconds)
|
||||||
|
|
||||||
|
peers_json="$peers_json{\"did\":\"$did\",\"address\":\"$addr\",\"role\":\"$role\",\"port\":$port,\"last_seen\":\"$last_seen\"}"
|
||||||
|
first=0
|
||||||
|
done < "$tmp_peers"
|
||||||
|
|
||||||
|
peers_json="$peers_json]"
|
||||||
|
|
||||||
|
# Save to peers file
|
||||||
|
echo "$peers_json" > "$PEERS_FILE"
|
||||||
|
|
||||||
|
rm -f "$tmp_peers"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get peer count
|
||||||
|
discovery_get_peer_count() {
|
||||||
|
jsonfilter -i "$PEERS_FILE" -e '@[*]' 2>/dev/null | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get peer by DID
|
||||||
|
discovery_get_peer() {
|
||||||
|
local did="$1"
|
||||||
|
jsonfilter -i "$PEERS_FILE" -e "@[did=\"$did\"]" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all peers
|
||||||
|
discovery_get_peers() {
|
||||||
|
cat "$PEERS_FILE" 2>/dev/null || echo '[]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check peer connectivity
|
||||||
|
discovery_check_peer() {
|
||||||
|
local did="$1"
|
||||||
|
local addr
|
||||||
|
|
||||||
|
addr=$(discovery_get_peer "$did" | jsonfilter -e '@.address' 2>/dev/null)
|
||||||
|
[ -z "$addr" ] && return 1
|
||||||
|
|
||||||
|
# Try ping
|
||||||
|
ping -c 1 -W 1 "$addr" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Refresh peer status
|
||||||
|
discovery_refresh_peer() {
|
||||||
|
local did="$1"
|
||||||
|
local peer_json
|
||||||
|
|
||||||
|
peer_json=$(discovery_get_peer "$did")
|
||||||
|
[ -z "$peer_json" ] && return 1
|
||||||
|
|
||||||
|
local addr port
|
||||||
|
addr=$(echo "$peer_json" | jsonfilter -e '@.address' 2>/dev/null)
|
||||||
|
port=$(echo "$peer_json" | jsonfilter -e '@.port' 2>/dev/null || echo "7331")
|
||||||
|
|
||||||
|
# Query peer's status API
|
||||||
|
local response
|
||||||
|
response=$(timeout 2 wget -qO- "http://$addr:$port/api/status" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ]; then
|
||||||
|
# Update peer info
|
||||||
|
local new_role
|
||||||
|
new_role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
|
||||||
|
[ -n "$new_role" ] && logger -t secubox-mesh "Peer $did role updated: $new_role"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize on source
|
||||||
|
discovery_init
|
||||||
184
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/election.sh
Executable file
184
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/election.sh
Executable file
@ -0,0 +1,184 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox Mesh Gate Election
|
||||||
|
# Elects a mesh gate (coordinator) based on node capabilities
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
ELECTION_STATE="/var/lib/secubox-mesh/election_state.json"
|
||||||
|
MESH_GATE_FILE="/var/lib/secubox-mesh/mesh_gate"
|
||||||
|
|
||||||
|
# Election criteria weights
|
||||||
|
WEIGHT_UPTIME=30 # Higher uptime = more stable
|
||||||
|
WEIGHT_PEERS=25 # More peers = better connected
|
||||||
|
WEIGHT_CPU=15 # Lower CPU = more available
|
||||||
|
WEIGHT_MEMORY=15 # Lower memory = more available
|
||||||
|
WEIGHT_ROLE=15 # relay > edge for gate role
|
||||||
|
|
||||||
|
# Initialize election state
|
||||||
|
election_init() {
|
||||||
|
mkdir -p "$(dirname "$ELECTION_STATE")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate node score for gate election
|
||||||
|
election_calculate_score() {
|
||||||
|
local did="$1"
|
||||||
|
local telemetry="$2"
|
||||||
|
local peer_count="${3:-0}"
|
||||||
|
local role="${4:-edge}"
|
||||||
|
|
||||||
|
local score=0
|
||||||
|
|
||||||
|
# Uptime score (max 30 points for 24h+ uptime)
|
||||||
|
local uptime
|
||||||
|
uptime=$(echo "$telemetry" | jsonfilter -e '@.uptime' 2>/dev/null || echo 0)
|
||||||
|
local uptime_hours=$((uptime / 3600))
|
||||||
|
local uptime_score=$((uptime_hours > 24 ? WEIGHT_UPTIME : uptime_hours * WEIGHT_UPTIME / 24))
|
||||||
|
score=$((score + uptime_score))
|
||||||
|
|
||||||
|
# Peer count score (max 25 points for 10+ peers)
|
||||||
|
local peer_score=$((peer_count > 10 ? WEIGHT_PEERS : peer_count * WEIGHT_PEERS / 10))
|
||||||
|
score=$((score + peer_score))
|
||||||
|
|
||||||
|
# CPU availability score (lower usage = higher score)
|
||||||
|
local cpu_percent
|
||||||
|
cpu_percent=$(echo "$telemetry" | jsonfilter -e '@.cpu_percent' 2>/dev/null || echo 50)
|
||||||
|
local cpu_score=$(( (100 - cpu_percent) * WEIGHT_CPU / 100 ))
|
||||||
|
score=$((score + cpu_score))
|
||||||
|
|
||||||
|
# Memory availability score (lower usage = higher score)
|
||||||
|
local memory_percent
|
||||||
|
memory_percent=$(echo "$telemetry" | jsonfilter -e '@.memory_percent' 2>/dev/null || echo 50)
|
||||||
|
local memory_score=$(( (100 - memory_percent) * WEIGHT_MEMORY / 100 ))
|
||||||
|
score=$((score + memory_score))
|
||||||
|
|
||||||
|
# Role score
|
||||||
|
local role_score=0
|
||||||
|
case "$role" in
|
||||||
|
relay|master)
|
||||||
|
role_score=$WEIGHT_ROLE
|
||||||
|
;;
|
||||||
|
submaster)
|
||||||
|
role_score=$((WEIGHT_ROLE * 3 / 4))
|
||||||
|
;;
|
||||||
|
edge|peer)
|
||||||
|
role_score=$((WEIGHT_ROLE / 2))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
role_score=$((WEIGHT_ROLE / 4))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
score=$((score + role_score))
|
||||||
|
|
||||||
|
echo "$score"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run gate election
|
||||||
|
election_run() {
|
||||||
|
local nodes_file="/var/lib/secubox-mesh/nodes.json"
|
||||||
|
local telemetry_file="/var/lib/secubox-mesh/telemetry.json"
|
||||||
|
local peers_file="/var/lib/secubox-mesh/peers.json"
|
||||||
|
|
||||||
|
local best_did=""
|
||||||
|
local best_score=0
|
||||||
|
local my_did="${NODE_DID:-$(cat /var/lib/mirrornet/identity/did.txt 2>/dev/null)}"
|
||||||
|
|
||||||
|
# Calculate score for this node
|
||||||
|
if [ -n "$my_did" ]; then
|
||||||
|
local my_telemetry
|
||||||
|
my_telemetry=$(cat "$telemetry_file" 2>/dev/null || echo '{}')
|
||||||
|
local my_peer_count
|
||||||
|
my_peer_count=$(jsonfilter -i "$peers_file" -e '@[*]' 2>/dev/null | wc -l)
|
||||||
|
local my_role="${CURRENT_ROLE:-edge}"
|
||||||
|
|
||||||
|
local my_score
|
||||||
|
my_score=$(election_calculate_score "$my_did" "$my_telemetry" "$my_peer_count" "$my_role")
|
||||||
|
|
||||||
|
best_did="$my_did"
|
||||||
|
best_score="$my_score"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check peer scores (if we have their telemetry)
|
||||||
|
while IFS= read -r peer_json; do
|
||||||
|
[ -z "$peer_json" ] && continue
|
||||||
|
|
||||||
|
local peer_did peer_role
|
||||||
|
peer_did=$(echo "$peer_json" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
peer_role=$(echo "$peer_json" | jsonfilter -e '@.role' 2>/dev/null || echo "edge")
|
||||||
|
|
||||||
|
[ -z "$peer_did" ] && continue
|
||||||
|
|
||||||
|
# Try to get peer's telemetry via API
|
||||||
|
local peer_addr peer_port
|
||||||
|
peer_addr=$(echo "$peer_json" | jsonfilter -e '@.address' 2>/dev/null)
|
||||||
|
peer_port=$(echo "$peer_json" | jsonfilter -e '@.port' 2>/dev/null || echo "7331")
|
||||||
|
|
||||||
|
local peer_telemetry
|
||||||
|
peer_telemetry=$(timeout 2 wget -qO- "http://$peer_addr:$peer_port/api/telemetry" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$peer_telemetry" ]; then
|
||||||
|
# Count peer's peers (simplified: assume similar to ours)
|
||||||
|
local peer_peer_count="$my_peer_count"
|
||||||
|
|
||||||
|
local peer_score
|
||||||
|
peer_score=$(election_calculate_score "$peer_did" "$peer_telemetry" "$peer_peer_count" "$peer_role")
|
||||||
|
|
||||||
|
if [ "$peer_score" -gt "$best_score" ]; then
|
||||||
|
best_did="$peer_did"
|
||||||
|
best_score="$peer_score"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(jsonfilter -i "$peers_file" -e '@[*]' 2>/dev/null)
|
||||||
|
|
||||||
|
# Save election result
|
||||||
|
echo "$best_did" > "$MESH_GATE_FILE"
|
||||||
|
|
||||||
|
# Save election state
|
||||||
|
cat > "$ELECTION_STATE" <<EOF
|
||||||
|
{
|
||||||
|
"mesh_gate": "$best_did",
|
||||||
|
"score": $best_score,
|
||||||
|
"elected_at": "$(date -Iseconds)",
|
||||||
|
"candidates_evaluated": $(jsonfilter -i "$peers_file" -e '@[*]' 2>/dev/null | wc -l)
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Log election result
|
||||||
|
if [ "$best_did" = "$my_did" ]; then
|
||||||
|
logger -t secubox-mesh "This node elected as mesh gate (score: $best_score)"
|
||||||
|
else
|
||||||
|
logger -t secubox-mesh "Mesh gate: $best_did (score: $best_score)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$best_did"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get current mesh gate
|
||||||
|
election_get_gate() {
|
||||||
|
cat "$MESH_GATE_FILE" 2>/dev/null || echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if this node is the gate
|
||||||
|
election_is_gate() {
|
||||||
|
local current_gate
|
||||||
|
current_gate=$(election_get_gate)
|
||||||
|
local my_did="${NODE_DID:-$(cat /var/lib/mirrornet/identity/did.txt 2>/dev/null)}"
|
||||||
|
|
||||||
|
[ "$current_gate" = "$my_did" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Force election (trigger immediate re-election)
|
||||||
|
election_force() {
|
||||||
|
rm -f "$MESH_GATE_FILE" "$ELECTION_STATE"
|
||||||
|
election_run
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get election state
|
||||||
|
election_get_state() {
|
||||||
|
if [ -f "$ELECTION_STATE" ]; then
|
||||||
|
cat "$ELECTION_STATE"
|
||||||
|
else
|
||||||
|
echo '{"mesh_gate":"","score":0,"elected_at":"","candidates_evaluated":0}'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize on source
|
||||||
|
election_init
|
||||||
197
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/telemetry.sh
Executable file
197
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/telemetry.sh
Executable file
@ -0,0 +1,197 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox Mesh Telemetry Collection
|
||||||
|
# Collects system metrics for mesh sharing
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
TELEMETRY_FILE="/var/lib/secubox-mesh/telemetry.json"
|
||||||
|
|
||||||
|
# Initialize telemetry
|
||||||
|
telemetry_init() {
|
||||||
|
mkdir -p "$(dirname "$TELEMETRY_FILE")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get CPU usage percentage
|
||||||
|
_get_cpu_percent() {
|
||||||
|
local idle total
|
||||||
|
local cpu_line
|
||||||
|
|
||||||
|
cpu_line=$(head -1 /proc/stat)
|
||||||
|
set -- $cpu_line
|
||||||
|
|
||||||
|
shift # Remove "cpu" prefix
|
||||||
|
local user=$1 nice=$2 system=$3 idle_val=$4 iowait=$5
|
||||||
|
|
||||||
|
total=$((user + nice + system + idle_val + iowait))
|
||||||
|
idle=$idle_val
|
||||||
|
|
||||||
|
# Calculate percentage (non-idle)
|
||||||
|
if [ "$total" -gt 0 ]; then
|
||||||
|
echo $(( (total - idle) * 100 / total ))
|
||||||
|
else
|
||||||
|
echo 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get memory usage percentage
|
||||||
|
_get_memory_percent() {
|
||||||
|
local total free buffers cached available
|
||||||
|
local meminfo="/proc/meminfo"
|
||||||
|
|
||||||
|
total=$(grep "^MemTotal:" "$meminfo" | awk '{print $2}')
|
||||||
|
free=$(grep "^MemFree:" "$meminfo" | awk '{print $2}')
|
||||||
|
buffers=$(grep "^Buffers:" "$meminfo" | awk '{print $2}')
|
||||||
|
cached=$(grep "^Cached:" "$meminfo" | awk '{print $2}')
|
||||||
|
available=$(grep "^MemAvailable:" "$meminfo" | awk '{print $2}')
|
||||||
|
|
||||||
|
if [ -n "$available" ] && [ "$total" -gt 0 ]; then
|
||||||
|
echo $(( (total - available) * 100 / total ))
|
||||||
|
elif [ "$total" -gt 0 ]; then
|
||||||
|
local used=$((total - free - buffers - cached))
|
||||||
|
echo $(( used * 100 / total ))
|
||||||
|
else
|
||||||
|
echo 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get disk usage percentage
|
||||||
|
_get_disk_percent() {
|
||||||
|
local mount="${1:-/}"
|
||||||
|
df "$mount" 2>/dev/null | tail -1 | awk '{print int($5)}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get load average
|
||||||
|
_get_load_avg() {
|
||||||
|
cut -d' ' -f1 /proc/loadavg
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get uptime in seconds
|
||||||
|
_get_uptime() {
|
||||||
|
cut -d. -f1 /proc/uptime
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get network interface stats
|
||||||
|
_get_network_stats() {
|
||||||
|
local interface="${1:-br-lan}"
|
||||||
|
local rx_bytes tx_bytes
|
||||||
|
|
||||||
|
rx_bytes=$(cat "/sys/class/net/$interface/statistics/rx_bytes" 2>/dev/null || echo 0)
|
||||||
|
tx_bytes=$(cat "/sys/class/net/$interface/statistics/tx_bytes" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
echo "{\"rx_bytes\":$rx_bytes,\"tx_bytes\":$tx_bytes}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get temperature (if available)
|
||||||
|
_get_temperature() {
|
||||||
|
local temp=""
|
||||||
|
|
||||||
|
# Try thermal zone
|
||||||
|
if [ -f /sys/class/thermal/thermal_zone0/temp ]; then
|
||||||
|
temp=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null)
|
||||||
|
[ -n "$temp" ] && temp=$((temp / 1000))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try hwmon
|
||||||
|
if [ -z "$temp" ] && [ -d /sys/class/hwmon ]; then
|
||||||
|
for hwmon in /sys/class/hwmon/hwmon*/temp1_input; do
|
||||||
|
[ -f "$hwmon" ] || continue
|
||||||
|
temp=$(cat "$hwmon" 2>/dev/null)
|
||||||
|
[ -n "$temp" ] && temp=$((temp / 1000))
|
||||||
|
break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${temp:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get WireGuard interface stats
|
||||||
|
_get_wireguard_stats() {
|
||||||
|
local interface="${1:-wg0}"
|
||||||
|
local peer_count=0
|
||||||
|
local rx_total=0
|
||||||
|
local tx_total=0
|
||||||
|
|
||||||
|
if command -v wg >/dev/null 2>&1; then
|
||||||
|
local stats
|
||||||
|
stats=$(wg show "$interface" transfer 2>/dev/null)
|
||||||
|
|
||||||
|
while read -r pubkey rx tx; do
|
||||||
|
[ -z "$pubkey" ] && continue
|
||||||
|
peer_count=$((peer_count + 1))
|
||||||
|
rx_total=$((rx_total + rx))
|
||||||
|
tx_total=$((tx_total + tx))
|
||||||
|
done <<EOF
|
||||||
|
$stats
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "{\"peers\":$peer_count,\"rx_bytes\":$rx_total,\"tx_bytes\":$tx_total}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get connection count
|
||||||
|
_get_connection_count() {
|
||||||
|
local tcp udp
|
||||||
|
|
||||||
|
if [ -f /proc/net/tcp ]; then
|
||||||
|
tcp=$(( $(wc -l < /proc/net/tcp) - 1 ))
|
||||||
|
else
|
||||||
|
tcp=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f /proc/net/udp ]; then
|
||||||
|
udp=$(( $(wc -l < /proc/net/udp) - 1 ))
|
||||||
|
else
|
||||||
|
udp=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "{\"tcp\":$tcp,\"udp\":$udp}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect all telemetry
|
||||||
|
telemetry_collect() {
|
||||||
|
local timestamp cpu_percent memory_percent disk_percent
|
||||||
|
local load_avg uptime_seconds temperature
|
||||||
|
local network_stats wg_stats conn_stats
|
||||||
|
|
||||||
|
timestamp=$(date -Iseconds)
|
||||||
|
cpu_percent=$(_get_cpu_percent)
|
||||||
|
memory_percent=$(_get_memory_percent)
|
||||||
|
disk_percent=$(_get_disk_percent)
|
||||||
|
load_avg=$(_get_load_avg)
|
||||||
|
uptime_seconds=$(_get_uptime)
|
||||||
|
temperature=$(_get_temperature)
|
||||||
|
network_stats=$(_get_network_stats)
|
||||||
|
wg_stats=$(_get_wireguard_stats)
|
||||||
|
conn_stats=$(_get_connection_count)
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"timestamp": "$timestamp",
|
||||||
|
"cpu_percent": $cpu_percent,
|
||||||
|
"memory_percent": $memory_percent,
|
||||||
|
"disk_percent": $disk_percent,
|
||||||
|
"load_avg": $load_avg,
|
||||||
|
"uptime": $uptime_seconds,
|
||||||
|
"temperature": $temperature,
|
||||||
|
"network": $network_stats,
|
||||||
|
"wireguard": $wg_stats,
|
||||||
|
"connections": $conn_stats
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store telemetry
|
||||||
|
telemetry_store() {
|
||||||
|
telemetry_collect > "$TELEMETRY_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get latest telemetry
|
||||||
|
telemetry_get() {
|
||||||
|
if [ -f "$TELEMETRY_FILE" ]; then
|
||||||
|
cat "$TELEMETRY_FILE"
|
||||||
|
else
|
||||||
|
telemetry_collect
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize on source
|
||||||
|
telemetry_init
|
||||||
208
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/topology.sh
Executable file
208
package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/topology.sh
Executable file
@ -0,0 +1,208 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox Mesh Topology Management
|
||||||
|
# Tracks mesh nodes, edges, and generates topology graph
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
TOPO_FILE="/var/lib/secubox-mesh/topology.json"
|
||||||
|
NODES_FILE="/var/lib/secubox-mesh/nodes.json"
|
||||||
|
EDGES_FILE="/var/lib/secubox-mesh/edges.json"
|
||||||
|
|
||||||
|
# Initialize topology storage
|
||||||
|
topology_init() {
|
||||||
|
mkdir -p "$(dirname "$TOPO_FILE")"
|
||||||
|
[ -f "$TOPO_FILE" ] || echo '{"nodes":[],"edges":[],"mesh_gate":""}' > "$TOPO_FILE"
|
||||||
|
[ -f "$NODES_FILE" ] || echo '[]' > "$NODES_FILE"
|
||||||
|
[ -f "$EDGES_FILE" ] || echo '[]' > "$EDGES_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add or update a node in topology
|
||||||
|
topology_add_node() {
|
||||||
|
local did="$1"
|
||||||
|
local address="$2"
|
||||||
|
local role="${3:-edge}"
|
||||||
|
local last_seen
|
||||||
|
last_seen=$(date -Iseconds)
|
||||||
|
|
||||||
|
# Check if node exists
|
||||||
|
local exists
|
||||||
|
exists=$(jsonfilter -i "$NODES_FILE" -e "@[\"$did\"]" 2>/dev/null)
|
||||||
|
|
||||||
|
local tmp_file="/tmp/topo_nodes_$$.json"
|
||||||
|
|
||||||
|
if [ -n "$exists" ]; then
|
||||||
|
# Update existing node
|
||||||
|
local updated='{"did":"'"$did"'","address":"'"$address"'","role":"'"$role"'","last_seen":"'"$last_seen"'","zkp_valid":true}'
|
||||||
|
# Use sed to update (simplified)
|
||||||
|
sed "s|{\"did\":\"$did\"[^}]*}|$updated|" "$NODES_FILE" > "$tmp_file"
|
||||||
|
else
|
||||||
|
# Add new node
|
||||||
|
if [ "$(cat "$NODES_FILE")" = "[]" ]; then
|
||||||
|
echo '[{"did":"'"$did"'","address":"'"$address"'","role":"'"$role"'","last_seen":"'"$last_seen"'","zkp_valid":true}]' > "$tmp_file"
|
||||||
|
else
|
||||||
|
# Insert before closing bracket
|
||||||
|
sed "s/\]$/,{\"did\":\"$did\",\"address\":\"$address\",\"role\":\"$role\",\"last_seen\":\"$last_seen\",\"zkp_valid\":true}]/" "$NODES_FILE" > "$tmp_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$tmp_file" "$NODES_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove a node from topology
|
||||||
|
topology_remove_node() {
|
||||||
|
local did="$1"
|
||||||
|
local tmp_file="/tmp/topo_nodes_$$.json"
|
||||||
|
|
||||||
|
# Remove node entry (simplified - removes entire object)
|
||||||
|
grep -v "\"did\":\"$did\"" "$NODES_FILE" > "$tmp_file" || echo '[]' > "$tmp_file"
|
||||||
|
mv "$tmp_file" "$NODES_FILE"
|
||||||
|
|
||||||
|
# Also remove edges involving this node
|
||||||
|
topology_remove_edges_for_node "$did"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add an edge between two nodes
|
||||||
|
topology_add_edge() {
|
||||||
|
local from_did="$1"
|
||||||
|
local to_did="$2"
|
||||||
|
local weight="${3:-1}"
|
||||||
|
local active="${4:-true}"
|
||||||
|
|
||||||
|
local edge_id="${from_did}:${to_did}"
|
||||||
|
local tmp_file="/tmp/topo_edges_$$.json"
|
||||||
|
|
||||||
|
if [ "$(cat "$EDGES_FILE")" = "[]" ]; then
|
||||||
|
echo '[{"id":"'"$edge_id"'","from":"'"$from_did"'","to":"'"$to_did"'","weight":'"$weight"',"active":'"$active"'}]' > "$tmp_file"
|
||||||
|
else
|
||||||
|
# Check if edge exists
|
||||||
|
if grep -q "\"id\":\"$edge_id\"" "$EDGES_FILE"; then
|
||||||
|
return 0 # Edge already exists
|
||||||
|
fi
|
||||||
|
sed "s/\]$/,{\"id\":\"$edge_id\",\"from\":\"$from_did\",\"to\":\"$to_did\",\"weight\":$weight,\"active\":$active}]/" "$EDGES_FILE" > "$tmp_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$tmp_file" "$EDGES_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove edges for a node
|
||||||
|
topology_remove_edges_for_node() {
|
||||||
|
local did="$1"
|
||||||
|
local tmp_file="/tmp/topo_edges_$$.json"
|
||||||
|
|
||||||
|
grep -v "\"$did\"" "$EDGES_FILE" > "$tmp_file" || echo '[]' > "$tmp_file"
|
||||||
|
mv "$tmp_file" "$EDGES_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all nodes
|
||||||
|
topology_get_nodes() {
|
||||||
|
cat "$NODES_FILE" 2>/dev/null || echo '[]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all edges
|
||||||
|
topology_get_edges() {
|
||||||
|
cat "$EDGES_FILE" 2>/dev/null || echo '[]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get full topology
|
||||||
|
topology_get() {
|
||||||
|
local nodes edges mesh_gate
|
||||||
|
nodes=$(cat "$NODES_FILE" 2>/dev/null || echo '[]')
|
||||||
|
edges=$(cat "$EDGES_FILE" 2>/dev/null || echo '[]')
|
||||||
|
mesh_gate=$(cat /var/lib/secubox-mesh/mesh_gate 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{"nodes":$nodes,"edges":$edges,"mesh_gate":"$mesh_gate"}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update topology from peer discovery
|
||||||
|
topology_update() {
|
||||||
|
local peers_file="/var/lib/secubox-mesh/peers.json"
|
||||||
|
|
||||||
|
[ -f "$peers_file" ] || return
|
||||||
|
|
||||||
|
# Add this node first
|
||||||
|
local my_did my_addr my_role
|
||||||
|
my_did="${NODE_DID:-$(cat /var/lib/mirrornet/identity/did.txt 2>/dev/null)}"
|
||||||
|
my_addr=$(uci -q get network.lan.ipaddr || echo "0.0.0.0")
|
||||||
|
my_role="${CURRENT_ROLE:-edge}"
|
||||||
|
|
||||||
|
[ -n "$my_did" ] && topology_add_node "$my_did" "$my_addr" "$my_role"
|
||||||
|
|
||||||
|
# Process discovered peers
|
||||||
|
local peer_count=0
|
||||||
|
while IFS= read -r peer_json; do
|
||||||
|
[ -z "$peer_json" ] && continue
|
||||||
|
|
||||||
|
local peer_did peer_addr peer_role
|
||||||
|
peer_did=$(echo "$peer_json" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
peer_addr=$(echo "$peer_json" | jsonfilter -e '@.address' 2>/dev/null)
|
||||||
|
peer_role=$(echo "$peer_json" | jsonfilter -e '@.role' 2>/dev/null || echo "edge")
|
||||||
|
|
||||||
|
[ -z "$peer_did" ] && continue
|
||||||
|
|
||||||
|
topology_add_node "$peer_did" "$peer_addr" "$peer_role"
|
||||||
|
|
||||||
|
# Add edge from this node to peer
|
||||||
|
[ -n "$my_did" ] && topology_add_edge "$my_did" "$peer_did" 1 true
|
||||||
|
|
||||||
|
peer_count=$((peer_count + 1))
|
||||||
|
done < <(jsonfilter -i "$peers_file" -e '@[*]' 2>/dev/null)
|
||||||
|
|
||||||
|
# Save combined topology
|
||||||
|
topology_get > "$TOPO_FILE"
|
||||||
|
|
||||||
|
logger -t secubox-mesh "Topology updated: $peer_count peers"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prune stale nodes (not seen in 5 minutes)
|
||||||
|
topology_prune_stale() {
|
||||||
|
local stale_threshold=300 # 5 minutes
|
||||||
|
local current_time
|
||||||
|
current_time=$(date +%s)
|
||||||
|
|
||||||
|
local tmp_file="/tmp/topo_nodes_prune_$$.json"
|
||||||
|
local pruned=0
|
||||||
|
|
||||||
|
echo '[' > "$tmp_file"
|
||||||
|
local first=1
|
||||||
|
|
||||||
|
while IFS= read -r node_json; do
|
||||||
|
[ -z "$node_json" ] && continue
|
||||||
|
|
||||||
|
local last_seen_str last_seen_ts age
|
||||||
|
last_seen_str=$(echo "$node_json" | jsonfilter -e '@.last_seen' 2>/dev/null)
|
||||||
|
|
||||||
|
# Convert ISO date to timestamp (simplified)
|
||||||
|
last_seen_ts=$(date -d "$last_seen_str" +%s 2>/dev/null || echo "$current_time")
|
||||||
|
age=$((current_time - last_seen_ts))
|
||||||
|
|
||||||
|
if [ "$age" -lt "$stale_threshold" ]; then
|
||||||
|
[ "$first" = "1" ] || echo ',' >> "$tmp_file"
|
||||||
|
echo "$node_json" >> "$tmp_file"
|
||||||
|
first=0
|
||||||
|
else
|
||||||
|
pruned=$((pruned + 1))
|
||||||
|
local did
|
||||||
|
did=$(echo "$node_json" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
logger -t secubox-mesh "Pruned stale node: $did (age: ${age}s)"
|
||||||
|
fi
|
||||||
|
done < <(jsonfilter -i "$NODES_FILE" -e '@[*]' 2>/dev/null)
|
||||||
|
|
||||||
|
echo ']' >> "$tmp_file"
|
||||||
|
mv "$tmp_file" "$NODES_FILE"
|
||||||
|
|
||||||
|
[ "$pruned" -gt 0 ] && topology_update
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get node count
|
||||||
|
topology_node_count() {
|
||||||
|
jsonfilter -i "$NODES_FILE" -e '@[*]' 2>/dev/null | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get edge count
|
||||||
|
topology_edge_count() {
|
||||||
|
jsonfilter -i "$EDGES_FILE" -e '@[*]' 2>/dev/null | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize on source
|
||||||
|
topology_init
|
||||||
150
package/secubox/secubox-mesh/files/usr/libexec/rpcd/luci.secubox-mesh
Executable file
150
package/secubox/secubox-mesh/files/usr/libexec/rpcd/luci.secubox-mesh
Executable file
@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox Mesh RPCD Handler
|
||||||
|
# Exposes mesh daemon commands via ubus
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
. /usr/share/libubox/jshn.sh
|
||||||
|
|
||||||
|
SOCKET="/var/run/secuboxd/topo.sock"
|
||||||
|
|
||||||
|
# Send command to daemon
|
||||||
|
_send_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if [ ! -S "$SOCKET" ]; then
|
||||||
|
echo '{"error":"Daemon not running"}'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v socat >/dev/null 2>&1; then
|
||||||
|
echo "$cmd" | socat - "UNIX-CONNECT:$SOCKET" 2>/dev/null
|
||||||
|
else
|
||||||
|
echo '{"error":"socat not available"}'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# List available methods
|
||||||
|
case_list() {
|
||||||
|
cat <<'EOF'
|
||||||
|
{
|
||||||
|
"status": {},
|
||||||
|
"peers": {},
|
||||||
|
"topology": {},
|
||||||
|
"nodes": {},
|
||||||
|
"node_info": {},
|
||||||
|
"node_rotate": {},
|
||||||
|
"telemetry": {},
|
||||||
|
"ping": {},
|
||||||
|
"get_config": {},
|
||||||
|
"set_config": { "role": "string", "beacon_interval": "number" },
|
||||||
|
"restart": {}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call method
|
||||||
|
case_call() {
|
||||||
|
local method="$1"
|
||||||
|
|
||||||
|
case "$method" in
|
||||||
|
status)
|
||||||
|
_send_cmd "mesh.status"
|
||||||
|
;;
|
||||||
|
peers)
|
||||||
|
_send_cmd "mesh.peers"
|
||||||
|
;;
|
||||||
|
topology)
|
||||||
|
_send_cmd "mesh.topology"
|
||||||
|
;;
|
||||||
|
nodes)
|
||||||
|
_send_cmd "mesh.nodes"
|
||||||
|
;;
|
||||||
|
node_info)
|
||||||
|
_send_cmd "node.info"
|
||||||
|
;;
|
||||||
|
node_rotate)
|
||||||
|
_send_cmd "node.rotate"
|
||||||
|
;;
|
||||||
|
telemetry)
|
||||||
|
_send_cmd "telemetry.latest"
|
||||||
|
;;
|
||||||
|
ping)
|
||||||
|
local response
|
||||||
|
response=$(_send_cmd "ping")
|
||||||
|
if echo "$response" | grep -q '"pong":true'; then
|
||||||
|
json_init
|
||||||
|
json_add_boolean running 1
|
||||||
|
json_dump
|
||||||
|
else
|
||||||
|
json_init
|
||||||
|
json_add_boolean running 0
|
||||||
|
json_add_string error "Daemon not responding"
|
||||||
|
json_dump
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
get_config)
|
||||||
|
json_init
|
||||||
|
|
||||||
|
config_load secubox
|
||||||
|
|
||||||
|
local enabled role subnet beacon_interval
|
||||||
|
config_get_bool enabled mesh enabled 1
|
||||||
|
config_get role mesh role "edge"
|
||||||
|
config_get subnet mesh subnet "10.42.0.0/16"
|
||||||
|
config_get beacon_interval mesh beacon_interval "30"
|
||||||
|
|
||||||
|
json_add_boolean enabled "$enabled"
|
||||||
|
json_add_string role "$role"
|
||||||
|
json_add_string subnet "$subnet"
|
||||||
|
json_add_int beacon_interval "$beacon_interval"
|
||||||
|
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
set_config)
|
||||||
|
read -r input
|
||||||
|
|
||||||
|
local role beacon_interval
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var role role
|
||||||
|
json_get_var beacon_interval beacon_interval
|
||||||
|
|
||||||
|
[ -n "$role" ] && uci set secubox.mesh.role="$role"
|
||||||
|
[ -n "$beacon_interval" ] && uci set secubox.mesh.beacon_interval="$beacon_interval"
|
||||||
|
|
||||||
|
uci commit secubox
|
||||||
|
|
||||||
|
json_init
|
||||||
|
json_add_boolean success 1
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
/etc/init.d/secuboxd restart >/dev/null 2>&1
|
||||||
|
|
||||||
|
json_init
|
||||||
|
json_add_boolean success 1
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
json_init
|
||||||
|
json_add_string error "Unknown method: $method"
|
||||||
|
json_dump
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main dispatcher
|
||||||
|
case "$1" in
|
||||||
|
list)
|
||||||
|
case_list
|
||||||
|
;;
|
||||||
|
call)
|
||||||
|
case_call "$2"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo '{"error":"Invalid command"}'
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
285
package/secubox/secubox-mesh/files/usr/sbin/secuboxctl
Executable file
285
package/secubox/secubox-mesh/files/usr/sbin/secuboxctl
Executable file
@ -0,0 +1,285 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox Mesh Control CLI (secuboxctl)
|
||||||
|
# Communicates with secuboxd via Unix control socket
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
SOCKET="/var/run/secuboxd/topo.sock"
|
||||||
|
VERSION="1.0.0"
|
||||||
|
|
||||||
|
# Send command to daemon and get response
|
||||||
|
send_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if [ ! -S "$SOCKET" ]; then
|
||||||
|
echo '{"error":"Daemon not running","socket":"'"$SOCKET"'"}'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use socat if available
|
||||||
|
if command -v socat >/dev/null 2>&1; then
|
||||||
|
echo "$cmd" | socat - UNIX-CONNECT:"$SOCKET"
|
||||||
|
else
|
||||||
|
# Fallback: use FIFO approach
|
||||||
|
local fifo_in="/var/run/secuboxd/socket_in"
|
||||||
|
local fifo_out="/var/run/secuboxd/socket_out"
|
||||||
|
if [ -p "$fifo_in" ] && [ -p "$fifo_out" ]; then
|
||||||
|
echo "$cmd" > "$fifo_in"
|
||||||
|
timeout 5 cat "$fifo_out" 2>/dev/null || echo '{"error":"Timeout"}'
|
||||||
|
else
|
||||||
|
echo '{"error":"Socket communication not available"}'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format JSON output for display
|
||||||
|
format_output() {
|
||||||
|
local json="$1"
|
||||||
|
local format="${2:-json}"
|
||||||
|
|
||||||
|
case "$format" in
|
||||||
|
json)
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "$json" | jq .
|
||||||
|
else
|
||||||
|
echo "$json"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
table)
|
||||||
|
# Simple table formatting using jsonfilter
|
||||||
|
echo "$json"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "$json"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
cmd_status() {
|
||||||
|
local response
|
||||||
|
response=$(send_command "mesh.status")
|
||||||
|
format_output "$response" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_peers() {
|
||||||
|
local response
|
||||||
|
response=$(send_command "mesh.peers")
|
||||||
|
|
||||||
|
if [ "$1" = "-q" ] || [ "$1" = "--quiet" ]; then
|
||||||
|
echo "$response" | jsonfilter -e '@[*].did' 2>/dev/null | wc -l
|
||||||
|
else
|
||||||
|
format_output "$response" "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_topology() {
|
||||||
|
local response
|
||||||
|
response=$(send_command "mesh.topology")
|
||||||
|
format_output "$response" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_nodes() {
|
||||||
|
local response
|
||||||
|
response=$(send_command "mesh.nodes")
|
||||||
|
format_output "$response" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_info() {
|
||||||
|
local response
|
||||||
|
response=$(send_command "node.info")
|
||||||
|
format_output "$response" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_rotate() {
|
||||||
|
local response
|
||||||
|
response=$(send_command "node.rotate")
|
||||||
|
|
||||||
|
local success
|
||||||
|
success=$(echo "$response" | jsonfilter -e '@.success' 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "$success" = "true" ]; then
|
||||||
|
echo "Key rotation successful"
|
||||||
|
echo "$response" | jsonfilter -e '@.new_expiry' 2>/dev/null
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "Key rotation failed"
|
||||||
|
echo "$response" | jsonfilter -e '@.error' 2>/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_telemetry() {
|
||||||
|
local response
|
||||||
|
response=$(send_command "telemetry.latest")
|
||||||
|
format_output "$response" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_ping() {
|
||||||
|
local response
|
||||||
|
response=$(send_command "ping")
|
||||||
|
|
||||||
|
if echo "$response" | grep -q '"pong":true'; then
|
||||||
|
echo "Daemon is running"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "Daemon not responding"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Service management
|
||||||
|
cmd_start() {
|
||||||
|
if pgrep -x secuboxd >/dev/null 2>&1; then
|
||||||
|
echo "secuboxd is already running"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
/etc/init.d/secuboxd start
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if pgrep -x secuboxd >/dev/null 2>&1; then
|
||||||
|
echo "secuboxd started"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "Failed to start secuboxd"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_stop() {
|
||||||
|
/etc/init.d/secuboxd stop
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if pgrep -x secuboxd >/dev/null 2>&1; then
|
||||||
|
echo "secuboxd still running, killing..."
|
||||||
|
pkill -9 secuboxd
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "secuboxd stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_restart() {
|
||||||
|
cmd_stop
|
||||||
|
sleep 1
|
||||||
|
cmd_start
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show usage
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
SecuBox Mesh Control CLI (secuboxctl) v$VERSION
|
||||||
|
|
||||||
|
Usage: secuboxctl <command> [options]
|
||||||
|
|
||||||
|
Mesh Commands:
|
||||||
|
mesh status Show mesh status (state, role, peers, gate)
|
||||||
|
mesh peers List connected peers
|
||||||
|
mesh topology Get mesh topology graph
|
||||||
|
mesh nodes List all known nodes
|
||||||
|
|
||||||
|
Node Commands:
|
||||||
|
node info Show this node's information
|
||||||
|
node rotate Rotate node keys
|
||||||
|
|
||||||
|
Telemetry:
|
||||||
|
telemetry latest Get latest system telemetry
|
||||||
|
|
||||||
|
Service Commands:
|
||||||
|
start Start secuboxd daemon
|
||||||
|
stop Stop secuboxd daemon
|
||||||
|
restart Restart secuboxd daemon
|
||||||
|
ping Check if daemon is running
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f, --format Output format: json (default), table
|
||||||
|
-q, --quiet Minimal output
|
||||||
|
-h, --help Show this help
|
||||||
|
-v, --version Show version
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
secuboxctl mesh status
|
||||||
|
secuboxctl mesh peers -f table
|
||||||
|
secuboxctl node info
|
||||||
|
secuboxctl telemetry latest
|
||||||
|
|
||||||
|
Socket: $SOCKET
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
main() {
|
||||||
|
local cmd="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
|
mesh)
|
||||||
|
local subcmd="$1"
|
||||||
|
shift
|
||||||
|
case "$subcmd" in
|
||||||
|
status) cmd_status "$@" ;;
|
||||||
|
peers) cmd_peers "$@" ;;
|
||||||
|
topology) cmd_topology "$@" ;;
|
||||||
|
nodes) cmd_nodes "$@" ;;
|
||||||
|
*) echo "Unknown mesh command: $subcmd"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
node)
|
||||||
|
local subcmd="$1"
|
||||||
|
shift
|
||||||
|
case "$subcmd" in
|
||||||
|
info) cmd_info "$@" ;;
|
||||||
|
rotate) cmd_rotate "$@" ;;
|
||||||
|
*) echo "Unknown node command: $subcmd"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
telemetry)
|
||||||
|
local subcmd="$1"
|
||||||
|
shift
|
||||||
|
case "$subcmd" in
|
||||||
|
latest) cmd_telemetry "$@" ;;
|
||||||
|
*) echo "Unknown telemetry command: $subcmd"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
cmd_status "$@"
|
||||||
|
;;
|
||||||
|
peers)
|
||||||
|
cmd_peers "$@"
|
||||||
|
;;
|
||||||
|
topology)
|
||||||
|
cmd_topology "$@"
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
cmd_start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
cmd_stop
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
cmd_restart
|
||||||
|
;;
|
||||||
|
ping)
|
||||||
|
cmd_ping
|
||||||
|
;;
|
||||||
|
-h|--help|help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-v|--version|version)
|
||||||
|
echo "secuboxctl $VERSION (OpenWrt)"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown command: $cmd"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
412
package/secubox/secubox-mesh/files/usr/sbin/secuboxd
Executable file
412
package/secubox/secubox-mesh/files/usr/sbin/secuboxd
Executable file
@ -0,0 +1,412 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox Mesh Daemon (secuboxd) for OpenWrt
|
||||||
|
# Unix control socket server with mesh topology management
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
RUNDIR="/var/run/secuboxd"
|
||||||
|
SOCKET="$RUNDIR/topo.sock"
|
||||||
|
PIDFILE="$RUNDIR/secuboxd.pid"
|
||||||
|
STATEDIR="/var/lib/secubox-mesh"
|
||||||
|
LOGFILE="/var/log/secuboxd.log"
|
||||||
|
|
||||||
|
# Source mesh libraries
|
||||||
|
. /usr/lib/secubox-mesh/topology.sh
|
||||||
|
. /usr/lib/secubox-mesh/discovery.sh
|
||||||
|
. /usr/lib/secubox-mesh/telemetry.sh
|
||||||
|
. /usr/lib/secubox-mesh/control.sh
|
||||||
|
. /usr/lib/secubox-mesh/election.sh
|
||||||
|
|
||||||
|
# Source mirrornet libraries if available
|
||||||
|
[ -f /usr/lib/mirrornet/identity.sh ] && . /usr/lib/mirrornet/identity.sh
|
||||||
|
[ -f /usr/lib/mirrornet/gossip.sh ] && . /usr/lib/mirrornet/gossip.sh
|
||||||
|
[ -f /usr/lib/mirrornet/health.sh ] && . /usr/lib/mirrornet/health.sh
|
||||||
|
|
||||||
|
# Global state
|
||||||
|
DAEMON_START_TIME=0
|
||||||
|
MESH_STATE="starting"
|
||||||
|
CURRENT_ROLE="edge"
|
||||||
|
MESH_GATE=""
|
||||||
|
PEER_COUNT=0
|
||||||
|
|
||||||
|
log() {
|
||||||
|
local level="$1"
|
||||||
|
shift
|
||||||
|
local msg="$*"
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
echo "[$timestamp] [$level] $msg" >> "$LOGFILE"
|
||||||
|
logger -t secuboxd -p "daemon.$level" "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() { log "info" "$@"; }
|
||||||
|
log_warn() { log "warn" "$@"; }
|
||||||
|
log_error() { log "error" "$@"; }
|
||||||
|
|
||||||
|
# Save daemon state for subprocess access
|
||||||
|
save_daemon_state() {
|
||||||
|
cat > "$STATEDIR/daemon_state" <<STATE_EOF
|
||||||
|
NODE_DID="$NODE_DID"
|
||||||
|
CURRENT_ROLE="$CURRENT_ROLE"
|
||||||
|
MESH_STATE="$MESH_STATE"
|
||||||
|
MESH_GATE="$MESH_GATE"
|
||||||
|
PEER_COUNT="$PEER_COUNT"
|
||||||
|
DAEMON_START_TIME="$DAEMON_START_TIME"
|
||||||
|
BEACON_INTERVAL="$BEACON_INTERVAL"
|
||||||
|
STATEDIR="$STATEDIR"
|
||||||
|
STATE_EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize daemon
|
||||||
|
daemon_init() {
|
||||||
|
log_info "Initializing SecuBox mesh daemon"
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
mkdir -p "$RUNDIR" "$STATEDIR"
|
||||||
|
chmod 755 "$RUNDIR"
|
||||||
|
|
||||||
|
# Initialize state files
|
||||||
|
echo '{"nodes":[],"edges":[],"mesh_gate":""}' > "$STATEDIR/topology.json"
|
||||||
|
echo '[]' > "$STATEDIR/peers.json"
|
||||||
|
echo '{}' > "$STATEDIR/telemetry.json"
|
||||||
|
|
||||||
|
# Get node identity
|
||||||
|
if type identity_get_did >/dev/null 2>&1; then
|
||||||
|
NODE_DID=$(identity_get_did)
|
||||||
|
else
|
||||||
|
# Fallback DID generation
|
||||||
|
local machine_id mac_addr
|
||||||
|
machine_id=$(cat /etc/machine-id 2>/dev/null || echo "openwrt")
|
||||||
|
mac_addr=$(cat /sys/class/net/br-lan/address 2>/dev/null || \
|
||||||
|
cat /sys/class/net/eth0/address 2>/dev/null || echo "00:00:00:00:00:00")
|
||||||
|
NODE_DID="did:plc:$(echo -n "${machine_id}:${mac_addr}" | md5sum | cut -c1-16)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
config_load secubox
|
||||||
|
config_get CURRENT_ROLE mesh role "edge"
|
||||||
|
config_get BEACON_INTERVAL mesh beacon_interval "30"
|
||||||
|
config_get ELECTION_INTERVAL mesh election_interval "60"
|
||||||
|
config_get MDNS_SERVICE mesh mdns_service "_secubox._udp"
|
||||||
|
|
||||||
|
DAEMON_START_TIME=$(date +%s)
|
||||||
|
|
||||||
|
# Save state for socket handler subprocess
|
||||||
|
save_daemon_state
|
||||||
|
|
||||||
|
log_info "Node DID: $NODE_DID"
|
||||||
|
log_info "Role: $CURRENT_ROLE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle control socket command
|
||||||
|
handle_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
local response=""
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
|
"ping")
|
||||||
|
response='{"pong":true}'
|
||||||
|
;;
|
||||||
|
"mesh.status")
|
||||||
|
response=$(cmd_mesh_status)
|
||||||
|
;;
|
||||||
|
"mesh.peers")
|
||||||
|
response=$(cmd_mesh_peers)
|
||||||
|
;;
|
||||||
|
"mesh.topology")
|
||||||
|
response=$(cmd_mesh_topology)
|
||||||
|
;;
|
||||||
|
"mesh.nodes")
|
||||||
|
response=$(cmd_mesh_nodes)
|
||||||
|
;;
|
||||||
|
"node.info")
|
||||||
|
response=$(cmd_node_info)
|
||||||
|
;;
|
||||||
|
"node.rotate")
|
||||||
|
response=$(cmd_node_rotate)
|
||||||
|
;;
|
||||||
|
"telemetry.latest")
|
||||||
|
response=$(cmd_telemetry_latest)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
response='{"error":"Unknown command","command":"'"$cmd"'"}'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "$response"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command: mesh.status
|
||||||
|
cmd_mesh_status() {
|
||||||
|
local uptime=$(($(date +%s) - DAEMON_START_TIME))
|
||||||
|
cat <<EOF
|
||||||
|
{"state":"$MESH_STATE","peer_count":$PEER_COUNT,"role":"$CURRENT_ROLE","mesh_gate":"$MESH_GATE","uptime":$uptime,"did":"$NODE_DID"}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command: mesh.peers
|
||||||
|
cmd_mesh_peers() {
|
||||||
|
if [ -f "$STATEDIR/peers.json" ]; then
|
||||||
|
cat "$STATEDIR/peers.json"
|
||||||
|
else
|
||||||
|
echo '[]'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command: mesh.topology
|
||||||
|
cmd_mesh_topology() {
|
||||||
|
if [ -f "$STATEDIR/topology.json" ]; then
|
||||||
|
cat "$STATEDIR/topology.json"
|
||||||
|
else
|
||||||
|
echo '{"nodes":[],"edges":[],"mesh_gate":""}'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command: mesh.nodes
|
||||||
|
cmd_mesh_nodes() {
|
||||||
|
topology_get_nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command: node.info
|
||||||
|
cmd_node_info() {
|
||||||
|
local zkp_valid="false"
|
||||||
|
[ -f "$STATEDIR/zkp_valid" ] && zkp_valid="true"
|
||||||
|
|
||||||
|
local pubkey=""
|
||||||
|
if [ -f /var/lib/mirrornet/identity/keys/primary.pub ]; then
|
||||||
|
pubkey=$(cat /var/lib/mirrornet/identity/keys/primary.pub 2>/dev/null | tr -d '\n')
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{"did":"$NODE_DID","role":"$CURRENT_ROLE","public_key":"$pubkey","zkp_valid":$zkp_valid,"mesh_gate":"$MESH_GATE"}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command: node.rotate
|
||||||
|
cmd_node_rotate() {
|
||||||
|
if type identity_rotate_key >/dev/null 2>&1; then
|
||||||
|
identity_rotate_key "primary"
|
||||||
|
local new_expiry
|
||||||
|
new_expiry=$(date -d "+30 days" -Iseconds 2>/dev/null || date -Iseconds)
|
||||||
|
echo '{"success":true,"new_expiry":"'"$new_expiry"'"}'
|
||||||
|
else
|
||||||
|
echo '{"success":false,"error":"Identity rotation not available"}'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command: telemetry.latest
|
||||||
|
cmd_telemetry_latest() {
|
||||||
|
telemetry_collect
|
||||||
|
}
|
||||||
|
|
||||||
|
# Control socket server using netcat
|
||||||
|
start_socket_server() {
|
||||||
|
log_info "Starting control socket server at $SOCKET"
|
||||||
|
|
||||||
|
# Remove old socket
|
||||||
|
rm -f "$SOCKET"
|
||||||
|
|
||||||
|
# Create socket directory
|
||||||
|
mkdir -p "$(dirname "$SOCKET")"
|
||||||
|
|
||||||
|
# Use socat if available, fallback to nc
|
||||||
|
if command -v socat >/dev/null 2>&1; then
|
||||||
|
socat UNIX-LISTEN:"$SOCKET",fork EXEC:"/usr/sbin/secuboxd --handle-client" &
|
||||||
|
SOCKET_PID=$!
|
||||||
|
else
|
||||||
|
# Fallback: use a FIFO-based approach
|
||||||
|
local fifo_in="$RUNDIR/socket_in"
|
||||||
|
local fifo_out="$RUNDIR/socket_out"
|
||||||
|
mkfifo "$fifo_in" "$fifo_out" 2>/dev/null
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if read -r cmd < "$fifo_in" 2>/dev/null; then
|
||||||
|
handle_command "$cmd" > "$fifo_out"
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done &
|
||||||
|
SOCKET_PID=$!
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Socket server started (PID: $SOCKET_PID)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle client connection (for socat exec mode)
|
||||||
|
handle_client() {
|
||||||
|
# Load daemon state for subprocess
|
||||||
|
STATEDIR="/var/lib/secubox-mesh"
|
||||||
|
[ -f "$STATEDIR/daemon_state" ] && . "$STATEDIR/daemon_state"
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[ -z "$line" ] && continue
|
||||||
|
handle_command "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# mDNS service advertisement
|
||||||
|
start_mdns() {
|
||||||
|
log_info "Starting mDNS advertisement"
|
||||||
|
|
||||||
|
local wg_port
|
||||||
|
wg_port=$(uci -q get network.wg0.listen_port || echo "51820")
|
||||||
|
|
||||||
|
# Use umdns if available
|
||||||
|
if command -v umdns >/dev/null 2>&1; then
|
||||||
|
# Create service file for umdns
|
||||||
|
mkdir -p /var/run/umdns
|
||||||
|
cat > /var/run/umdns/secubox.json <<EOF
|
||||||
|
{
|
||||||
|
"secubox": {
|
||||||
|
"service": "_secubox._udp.local",
|
||||||
|
"port": $wg_port,
|
||||||
|
"txt": [
|
||||||
|
"did=$NODE_DID",
|
||||||
|
"role=$CURRENT_ROLE",
|
||||||
|
"version=1.0.0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
# Reload umdns
|
||||||
|
/etc/init.d/umdns reload 2>/dev/null
|
||||||
|
log_info "mDNS service registered via umdns"
|
||||||
|
elif command -v avahi-publish >/dev/null 2>&1; then
|
||||||
|
# Fallback to avahi-publish
|
||||||
|
avahi-publish -s "secubox-${NODE_DID##*:}" "_secubox._udp" "$wg_port" \
|
||||||
|
"did=$NODE_DID" "role=$CURRENT_ROLE" "version=1.0.0" &
|
||||||
|
AVAHI_PID=$!
|
||||||
|
log_info "mDNS service registered via avahi-publish (PID: $AVAHI_PID)"
|
||||||
|
else
|
||||||
|
log_warn "No mDNS daemon available (umdns or avahi)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stop mDNS advertisement
|
||||||
|
stop_mdns() {
|
||||||
|
rm -f /var/run/umdns/secubox.json
|
||||||
|
/etc/init.d/umdns reload 2>/dev/null
|
||||||
|
[ -n "$AVAHI_PID" ] && kill "$AVAHI_PID" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main daemon loop
|
||||||
|
daemon_loop() {
|
||||||
|
local loop_count=0
|
||||||
|
MESH_STATE="running"
|
||||||
|
save_daemon_state
|
||||||
|
|
||||||
|
log_info "Entering main daemon loop"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
loop_count=$((loop_count + 1))
|
||||||
|
|
||||||
|
# Peer discovery (every beacon interval)
|
||||||
|
if [ $((loop_count % BEACON_INTERVAL)) -eq 0 ]; then
|
||||||
|
discovery_scan_peers
|
||||||
|
PEER_COUNT=$(discovery_get_peer_count)
|
||||||
|
log_info "Peer discovery: found $PEER_COUNT peers"
|
||||||
|
save_daemon_state
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Topology update (every 2x beacon interval)
|
||||||
|
if [ $((loop_count % (BEACON_INTERVAL * 2))) -eq 0 ]; then
|
||||||
|
topology_update
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Gate election (every election interval)
|
||||||
|
if [ $((loop_count % ELECTION_INTERVAL)) -eq 0 ]; then
|
||||||
|
local old_gate="$MESH_GATE"
|
||||||
|
MESH_GATE=$(election_run)
|
||||||
|
if [ "$MESH_GATE" != "$old_gate" ]; then
|
||||||
|
log_info "Mesh gate changed: $old_gate -> $MESH_GATE"
|
||||||
|
fi
|
||||||
|
# Update state file with new values
|
||||||
|
save_daemon_state
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Telemetry collection (every 60 seconds)
|
||||||
|
if [ $((loop_count % 60)) -eq 0 ]; then
|
||||||
|
telemetry_collect > "$STATEDIR/telemetry.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Health check (every 30 seconds)
|
||||||
|
if [ $((loop_count % 30)) -eq 0 ] && type health_check >/dev/null 2>&1; then
|
||||||
|
health_check
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Signal handlers
|
||||||
|
cleanup() {
|
||||||
|
log_info "Shutting down secuboxd"
|
||||||
|
MESH_STATE="stopping"
|
||||||
|
|
||||||
|
stop_mdns
|
||||||
|
|
||||||
|
[ -n "$SOCKET_PID" ] && kill "$SOCKET_PID" 2>/dev/null
|
||||||
|
rm -f "$SOCKET" "$PIDFILE"
|
||||||
|
|
||||||
|
log_info "SecuBox mesh daemon stopped"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup INT TERM
|
||||||
|
|
||||||
|
# Main entry point
|
||||||
|
main() {
|
||||||
|
case "$1" in
|
||||||
|
--handle-client)
|
||||||
|
handle_client
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--foreground|-f)
|
||||||
|
daemon_init
|
||||||
|
start_socket_server
|
||||||
|
start_mdns
|
||||||
|
daemon_loop
|
||||||
|
;;
|
||||||
|
--version|-v)
|
||||||
|
echo "secuboxd 1.0.0 (OpenWrt)"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
cat <<EOF
|
||||||
|
SecuBox Mesh Daemon (secuboxd)
|
||||||
|
|
||||||
|
Usage: secuboxd [OPTION]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f, --foreground Run in foreground (for procd)
|
||||||
|
-v, --version Show version
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Control socket: $SOCKET
|
||||||
|
Configuration: /etc/config/secubox
|
||||||
|
|
||||||
|
Commands (via socket):
|
||||||
|
ping - Check daemon is alive
|
||||||
|
mesh.status - Get mesh status
|
||||||
|
mesh.peers - List connected peers
|
||||||
|
mesh.topology - Get mesh topology
|
||||||
|
mesh.nodes - List all nodes
|
||||||
|
node.info - Get this node's info
|
||||||
|
node.rotate - Rotate node keys
|
||||||
|
telemetry.latest - Get latest telemetry
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Default: run in foreground for procd
|
||||||
|
daemon_init
|
||||||
|
echo $$ > "$PIDFILE"
|
||||||
|
start_socket_server
|
||||||
|
start_mdns
|
||||||
|
daemon_loop
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"luci-app-secubox-mesh": {
|
||||||
|
"description": "Grant access to SecuBox Mesh Daemon",
|
||||||
|
"read": {
|
||||||
|
"ubus": {
|
||||||
|
"luci.secubox-mesh": [
|
||||||
|
"status",
|
||||||
|
"peers",
|
||||||
|
"topology",
|
||||||
|
"nodes",
|
||||||
|
"node_info",
|
||||||
|
"telemetry",
|
||||||
|
"ping",
|
||||||
|
"get_config"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uci": ["secubox"]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"ubus": {
|
||||||
|
"luci.secubox-mesh": [
|
||||||
|
"node_rotate",
|
||||||
|
"set_config",
|
||||||
|
"restart"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uci": ["secubox"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user