From cd6af3edff2ce03eec5fd419b04d11b945906bc3 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 26 Mar 2026 06:27:45 +0100 Subject: [PATCH] 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 --- .claude/HISTORY.md | 62 ++- .claude/WIP.md | 13 +- .../secubox-mesh_1.0.0-r1_all.ipk | Bin 0 -> 14374 bytes package/secubox/secubox-mesh/Makefile | 60 +++ .../secubox-mesh/files/etc/config/secubox | 33 ++ .../secubox-mesh/files/etc/init.d/secuboxd | 77 ++++ .../files/usr/lib/secubox-mesh/control.sh | 138 ++++++ .../files/usr/lib/secubox-mesh/discovery.sh | 215 +++++++++ .../files/usr/lib/secubox-mesh/election.sh | 184 ++++++++ .../files/usr/lib/secubox-mesh/telemetry.sh | 197 +++++++++ .../files/usr/lib/secubox-mesh/topology.sh | 208 +++++++++ .../files/usr/libexec/rpcd/luci.secubox-mesh | 150 +++++++ .../secubox-mesh/files/usr/sbin/secuboxctl | 285 ++++++++++++ .../secubox-mesh/files/usr/sbin/secuboxd | 412 ++++++++++++++++++ .../rpcd/acl.d/luci-app-secubox-mesh.json | 30 ++ 15 files changed, 2062 insertions(+), 2 deletions(-) create mode 100644 package/secubox/secubox-app-bonus/root/www/secubox-feed/secubox-mesh_1.0.0-r1_all.ipk create mode 100644 package/secubox/secubox-mesh/Makefile create mode 100644 package/secubox/secubox-mesh/files/etc/config/secubox create mode 100755 package/secubox/secubox-mesh/files/etc/init.d/secuboxd create mode 100755 package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/control.sh create mode 100755 package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/discovery.sh create mode 100755 package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/election.sh create mode 100755 package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/telemetry.sh create mode 100755 package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/topology.sh create mode 100755 package/secubox/secubox-mesh/files/usr/libexec/rpcd/luci.secubox-mesh create mode 100755 package/secubox/secubox-mesh/files/usr/sbin/secuboxctl create mode 100755 package/secubox/secubox-mesh/files/usr/sbin/secuboxd create mode 100644 package/secubox/secubox-mesh/root/usr/share/rpcd/acl.d/luci-app-secubox-mesh.json diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 98798060..00f559d9 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -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 diff --git a/.claude/WIP.md b/.claude/WIP.md index 6e26bfd5..259fab71 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -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)** diff --git a/package/secubox/secubox-app-bonus/root/www/secubox-feed/secubox-mesh_1.0.0-r1_all.ipk b/package/secubox/secubox-app-bonus/root/www/secubox-feed/secubox-mesh_1.0.0-r1_all.ipk new file mode 100644 index 0000000000000000000000000000000000000000..0c1bedcdd3f7a620eaa39b4687a6498a45864119 GIT binary patch literal 14374 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 literal 0 HcmV?d00001 diff --git a/package/secubox/secubox-mesh/Makefile b/package/secubox/secubox-mesh/Makefile new file mode 100644 index 00000000..8b19a966 --- /dev/null +++ b/package/secubox/secubox-mesh/Makefile @@ -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 +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)) diff --git a/package/secubox/secubox-mesh/files/etc/config/secubox b/package/secubox/secubox-mesh/files/etc/config/secubox new file mode 100644 index 00000000..08a46dd7 --- /dev/null +++ b/package/secubox/secubox-mesh/files/etc/config/secubox @@ -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' diff --git a/package/secubox/secubox-mesh/files/etc/init.d/secuboxd b/package/secubox/secubox-mesh/files/etc/init.d/secuboxd new file mode 100755 index 00000000..cf1b1a16 --- /dev/null +++ b/package/secubox/secubox-mesh/files/etc/init.d/secuboxd @@ -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 +} diff --git a/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/control.sh b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/control.sh new file mode 100755 index 00000000..a2a14b05 --- /dev/null +++ b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/control.sh @@ -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 < "$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 </dev/null) + + if echo "$response" | grep -q '"pong":true'; then + return 0 + fi + + return 1 +} diff --git a/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/discovery.sh b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/discovery.sh new file mode 100755 index 00000000..9bf90215 --- /dev/null +++ b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/discovery.sh @@ -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 diff --git a/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/election.sh b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/election.sh new file mode 100755 index 00000000..5b2045a7 --- /dev/null +++ b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/election.sh @@ -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" </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 diff --git a/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/telemetry.sh b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/telemetry.sh new file mode 100755 index 00000000..64850f1c --- /dev/null +++ b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/telemetry.sh @@ -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 < "$TELEMETRY_FILE" +} + +# Get latest telemetry +telemetry_get() { + if [ -f "$TELEMETRY_FILE" ]; then + cat "$TELEMETRY_FILE" + else + telemetry_collect + fi +} + +# Initialize on source +telemetry_init diff --git a/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/topology.sh b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/topology.sh new file mode 100755 index 00000000..cdba68c6 --- /dev/null +++ b/package/secubox/secubox-mesh/files/usr/lib/secubox-mesh/topology.sh @@ -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 </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 diff --git a/package/secubox/secubox-mesh/files/usr/libexec/rpcd/luci.secubox-mesh b/package/secubox/secubox-mesh/files/usr/libexec/rpcd/luci.secubox-mesh new file mode 100755 index 00000000..aa5131cb --- /dev/null +++ b/package/secubox/secubox-mesh/files/usr/libexec/rpcd/luci.secubox-mesh @@ -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 diff --git a/package/secubox/secubox-mesh/files/usr/sbin/secuboxctl b/package/secubox/secubox-mesh/files/usr/sbin/secuboxctl new file mode 100755 index 00000000..1034ab17 --- /dev/null +++ b/package/secubox/secubox-mesh/files/usr/sbin/secuboxctl @@ -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 < [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 "$@" diff --git a/package/secubox/secubox-mesh/files/usr/sbin/secuboxd b/package/secubox/secubox-mesh/files/usr/sbin/secuboxd new file mode 100755 index 00000000..49191225 --- /dev/null +++ b/package/secubox/secubox-mesh/files/usr/sbin/secuboxd @@ -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" < "$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 </dev/null | tr -d '\n') + fi + + cat </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 </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 < "$PIDFILE" + start_socket_server + start_mdns + daemon_loop + ;; + esac +} + +main "$@" diff --git a/package/secubox/secubox-mesh/root/usr/share/rpcd/acl.d/luci-app-secubox-mesh.json b/package/secubox/secubox-mesh/root/usr/share/rpcd/acl.d/luci-app-secubox-mesh.json new file mode 100644 index 00000000..eb911e23 --- /dev/null +++ b/package/secubox/secubox-mesh/root/usr/share/rpcd/acl.d/luci-app-secubox-mesh.json @@ -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"] + } + } +}