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
|
||||
|
||||
_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)**
|
||||
- **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
|
||||
- Backwards-compatible fallback to legacy per-app SMTP settings
|
||||
- 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)
|
||||
|
||||
_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
|
||||
|
||||
@ -8,6 +8,17 @@ _Last updated: 2026-03-17 (VM Firmware Build + CI Fixes)_
|
||||
|
||||
## 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
|
||||
|
||||
- **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