feat(secubox-mesh): Add network device and VM/container discovery

Enhanced mesh discovery with multi-method network device detection:

- discovery_scan_subnet(): Active /24 subnet scanning for SecuBox peers
- discovery_scan_docker(): Docker container detection via Unix socket
- discovery_scan_lxc(): LXC and Proxmox container detection
- discovery_scan_libvirt(): KVM/libvirt VM detection via virsh
- discovery_scan_all_devices(): Full ARP neighbor discovery with fingerprinting
- discovery_fingerprint_device(): Port scanning for service detection

New RPCD API methods:
- devices: List all discovered network devices
- scan_full: Trigger full network scan (includes subnet scan)
- scan_containers: Scan specifically for containers/VMs

LuCI mesh dashboard updates:
- "Discovered Devices" table with IP, MAC, type, hostname, services
- "Scan Network" button to trigger full discovery
- Device classification: secubox, server, container, vm, unknown
- Peer table now shows source field (docker:name, lxc:name, etc.)

Also includes CRT P31 theme CSS comprehensive fix for UI consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-26 11:38:37 +01:00
parent b772c6da11
commit e7a9062140
7 changed files with 1341 additions and 581 deletions

View File

@ -1,6 +1,34 @@
# SecuBox UI & Theme History
_Last updated: 2026-03-26 (Theme Deployment & Documentation)_
_Last updated: 2026-03-26 (Mesh Device Discovery)_
0. **Mesh Network Device/VM Discovery (2026-03-26)**
- **Enhanced discovery.sh** with multi-method network device detection:
- `discovery_scan_subnet()`: Active /24 subnet scanning for SecuBox peers
- `discovery_scan_docker()`: Docker container discovery via Unix socket API
- `discovery_scan_lxc()`: LXC and Proxmox (pct) container detection
- `discovery_scan_libvirt()`: KVM/libvirt VM detection via virsh
- `discovery_scan_all_devices()`: Full ARP neighbor discovery with fingerprinting
- **Device fingerprinting** by open ports (ssh, http, https, mitmproxy)
- **New RPCD methods**:
- `devices`: List all discovered network devices
- `scan_full`: Trigger full network scan (includes slow subnet scan)
- `scan_containers`: Scan specifically for Docker/LXC/libvirt containers
- **LuCI mesh dashboard** updated:
- "Discovered Devices" table with IP, MAC, type, hostname, services, state
- "Scan Network" button to trigger full discovery
- Device types: secubox, server, container, vm, unknown
- Peer table now shows source field (docker:name, lxc:name, etc.)
- **Files updated**:
- `secubox-mesh/files/usr/lib/secubox-mesh/discovery.sh`
- `secubox-mesh/files/usr/libexec/rpcd/luci.secubox-mesh`
- `secubox-mesh/root/usr/share/rpcd/acl.d/luci-app-secubox-mesh.json`
- `luci-app-secubox-mesh/htdocs/luci-static/resources/view/secubox/mesh.js`
0. **CRT P31 Theme UI Consistency Fix (2026-03-26)**
- Complete `cascade.css` rewrite (1100+ lines) for consistent styling
- All LuCI views now have uniform CRT P31 appearance
- Fixed: navigation, forms, tables, alerts, badges, modals, dropdowns
0. **Theme Deployment & Documentation (2026-03-26)**
- **LuCI 24.10 Compatibility Fix**:

View File

@ -1,6 +1,6 @@
# Work In Progress (Claude)
_Last updated: 2026-03-26 (Theme Deployment & Documentation)_
_Last updated: 2026-03-26 (Mesh Device Discovery)_
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
@ -10,6 +10,24 @@ _Last updated: 2026-03-26 (Theme Deployment & Documentation)_
### 2026-03-26
- **Mesh Network Device/VM Discovery (Complete)**
- Enhanced `discovery.sh` with multi-method device detection
- New discovery methods:
- `discovery_scan_subnet()` - Active subnet scanning
- `discovery_scan_docker()` - Docker container detection via Unix socket
- `discovery_scan_lxc()` - LXC/Proxmox container detection
- `discovery_scan_libvirt()` - KVM/libvirt VM detection
- `discovery_scan_all_devices()` - Full network neighbor discovery with fingerprinting
- Device fingerprinting by services (ssh, http, https, mitmproxy)
- New RPCD methods: `devices`, `scan_full`, `scan_containers`
- Updated LuCI mesh.js with discovered devices table and scan button
- Devices classified by type: secubox, server, container, vm, unknown
- **CRT P31 Theme UI Fix (Complete)**
- Comprehensive cascade.css rewrite (1100+ lines)
- Fixed UI inconsistencies across all LuCI views
- Consistent CRT P31 styling for nav, forms, tables, alerts, modals
- **CRT P31 Theme Deployment & LuCI 24.10 Fix (Complete)**
- Fixed LuCI 24.10 compatibility with ucode templates (.ut files)
- Fixed navbar layout: sidebar → horizontal top navigation

View File

@ -47,6 +47,24 @@ var callNodeRotate = rpc.declare({
expect: {}
});
var callDevices = rpc.declare({
object: 'luci.secubox-mesh',
method: 'devices',
expect: {}
});
var callScanFull = rpc.declare({
object: 'luci.secubox-mesh',
method: 'scan_full',
expect: {}
});
var callScanContainers = rpc.declare({
object: 'luci.secubox-mesh',
method: 'scan_containers',
expect: {}
});
function formatUptime(seconds) {
if (!seconds || seconds < 0) return '0s';
var days = Math.floor(seconds / 86400);
@ -105,7 +123,8 @@ return view.extend({
callNodeInfo().catch(function() { return {}; }),
callTelemetry().catch(function() { return {}; }),
callMeshPeers().catch(function() { return []; }),
callGetConfig().catch(function() { return {}; })
callGetConfig().catch(function() { return {}; }),
callDevices().catch(function() { return []; })
]);
},
@ -115,11 +134,13 @@ return view.extend({
var telemetry = data[2] || {};
var peers = data[3] || [];
var config = data[4] || {};
var devices = data[5] || [];
// Ensure peers is an array
if (!Array.isArray(peers)) {
peers = [];
}
// Ensure arrays
if (!Array.isArray(peers)) peers = [];
if (!Array.isArray(devices)) devices = [];
var self = this;
var view = E('div', { 'class': 'mesh-dashboard' }, [
E('style', {}, [
@ -279,6 +300,7 @@ return view.extend({
E('th', {}, 'DID'),
E('th', {}, 'Address'),
E('th', {}, 'Role'),
E('th', {}, 'Source'),
E('th', {}, 'Last Seen')
])
]),
@ -287,6 +309,7 @@ return view.extend({
E('td', {}, peer.did || '-'),
E('td', {}, peer.address || '-'),
E('td', {}, (peer.role || 'edge').toUpperCase()),
E('td', {}, peer.source || 'discovery'),
E('td', {}, peer.last_seen || '-')
]);
}))
@ -295,6 +318,64 @@ return view.extend({
E('div', { 'class': 'mesh-empty' }, 'No peers connected')
]
)
]),
// Network Devices Table
E('div', { 'class': 'mesh-card' }, [
E('div', { 'class': 'mesh-card-header', 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
E('span', {}, 'Discovered Devices (' + devices.length + ')'),
E('button', {
'class': 'mesh-btn',
'style': 'padding: 4px 12px; font-size: 12px;',
'click': ui.createHandlerFn(self, function() {
ui.addNotification(null, E('p', 'Full network scan initiated...'), 'info');
return callScanFull().then(function(res) {
if (res && res.started) {
ui.addNotification(null, E('p', 'Scan in progress. Results will appear shortly.'), 'success');
// Refresh after delay
setTimeout(function() { window.location.reload(); }, 5000);
} else {
ui.addNotification(null, E('p', 'Failed to start scan: ' + (res.error || 'unknown')), 'error');
}
}).catch(function(e) {
ui.addNotification(null, E('p', 'Scan failed: ' + e.message), 'error');
});
})
}, 'Scan Network')
]),
E('div', { 'class': 'mesh-card-body', 'id': 'mesh-devices' },
devices.length > 0 ? [
E('table', { 'class': 'mesh-peers-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, 'IP Address'),
E('th', {}, 'MAC'),
E('th', {}, 'Type'),
E('th', {}, 'Hostname'),
E('th', {}, 'Services'),
E('th', {}, 'State')
])
]),
E('tbody', {}, devices.map(function(dev) {
var typeColor = dev.type === 'secubox' ? '#33ff66' :
(dev.type === 'server' ? '#00aaff' : '#888888');
return E('tr', {}, [
E('td', {}, dev.ip || '-'),
E('td', { 'style': 'font-size: 11px;' }, dev.mac || '-'),
E('td', { 'style': 'color: ' + typeColor }, (dev.type || 'unknown').toUpperCase()),
E('td', {}, dev.hostname || '-'),
E('td', { 'style': 'font-size: 11px;' }, dev.services || '-'),
E('td', {}, dev.state || '-')
]);
}))
])
] : [
E('div', { 'class': 'mesh-empty' }, [
E('p', {}, 'No devices discovered yet.'),
E('p', { 'style': 'font-size: 12px; margin-top: 10px;' }, 'Click "Scan Network" to discover devices on your local network.')
])
]
)
])
]);
@ -303,13 +384,16 @@ return view.extend({
return Promise.all([
callMeshStatus().catch(function() { return {}; }),
callTelemetry().catch(function() { return {}; }),
callMeshPeers().catch(function() { return []; })
callMeshPeers().catch(function() { return []; }),
callDevices().catch(function() { return []; })
]).then(L.bind(function(data) {
var status = data[0] || {};
var telemetry = data[1] || {};
var peers = data[2] || [];
var devices = data[3] || [];
if (!Array.isArray(peers)) peers = [];
if (!Array.isArray(devices)) devices = [];
// Update status badge
var badge = document.querySelector('.mesh-status-badge');
@ -318,6 +402,18 @@ return view.extend({
badge.className = 'mesh-status-badge ' + (status.state === 'running' ? 'mesh-status-running' : 'mesh-status-stopped');
}
// Update peer count in header
var peersHeader = document.querySelector('#mesh-peers')?.parentNode?.querySelector('.mesh-card-header');
if (peersHeader && !peersHeader.querySelector('button')) {
peersHeader.textContent = 'Connected Peers (' + peers.length + ')';
}
// Update devices count in header
var devicesHeader = document.querySelector('#mesh-devices')?.parentNode?.querySelector('.mesh-card-header span');
if (devicesHeader) {
devicesHeader.textContent = 'Discovered Devices (' + devices.length + ')';
}
}, this));
}, this), 10);

View File

@ -1,16 +1,31 @@
#!/bin/sh
# SecuBox Mesh Peer Discovery
# mDNS-based service discovery for mesh peers
# Multi-method service discovery for mesh peers and network devices
# CyberMind — SecuBox — 2026
PEERS_FILE="/var/lib/secubox-mesh/peers.json"
DEVICES_FILE="/var/lib/secubox-mesh/devices.json"
DISCOVERY_CACHE="/tmp/secubox_discovery_cache"
MDNS_SERVICE="_secubox._udp"
DISCOVERY_TIMEOUT=2
# Well-known SecuBox/service ports for fingerprinting
PORT_SECUBOX_API=7331
PORT_SECUBOX_P2P=7332
PORT_WIREGUARD=51820
PORT_HTTP=80
PORT_HTTPS=443
PORT_SSH=22
PORT_NETDATA=19999
PORT_MITMPROXY=8080
PORT_CROWDSEC=6060
# Initialize discovery
discovery_init() {
mkdir -p "$(dirname "$PEERS_FILE")"
mkdir -p "$(dirname "$DEVICES_FILE")"
[ -f "$PEERS_FILE" ] || echo '[]' > "$PEERS_FILE"
[ -f "$DEVICES_FILE" ] || echo '[]' > "$DEVICES_FILE"
}
# Scan for peers using mDNS
@ -111,17 +126,346 @@ _scan_config_peer() {
[ -n "$did" ] && [ -n "$addr" ] && echo "$did|$addr|$role|$port"
}
# Combined peer discovery
# Check if port is open (fast, non-blocking)
_check_port() {
local host="$1"
local port="$2"
# Use netcat if available (most reliable)
if command -v nc >/dev/null 2>&1; then
nc -z -w 1 "$host" "$port" 2>/dev/null
return $?
fi
# Fallback: try wget
wget -q -T 1 -O /dev/null "http://$host:$port/" 2>/dev/null
[ $? -eq 0 ] || [ $? -eq 8 ] # 8 = server error (port open)
}
# Scan subnet range for SecuBox peers (active discovery)
discovery_scan_subnet() {
local subnet="${1:-}"
# Auto-detect subnet from br-lan if not provided
if [ -z "$subnet" ]; then
local lan_ip
lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -o 'inet [0-9.]*' | cut -d' ' -f2)
[ -z "$lan_ip" ] && lan_ip=$(ip -4 addr show eth0 2>/dev/null | grep -o 'inet [0-9.]*' | cut -d' ' -f2)
[ -z "$lan_ip" ] && return
# Extract network base (assume /24)
subnet=$(echo "$lan_ip" | cut -d. -f1-3)
fi
# Extract base if full CIDR given
local base
base=$(echo "$subnet" | cut -d/ -f1 | cut -d. -f1-3)
local my_ips
my_ips=$(ip -4 addr show 2>/dev/null | grep -o 'inet [0-9.]*' | cut -d' ' -f2 | tr '\n' ' ')
# Scan common SecuBox port across subnet
local i=1
while [ $i -lt 255 ]; do
local target="${base}.${i}"
# Skip self
echo "$my_ips" | grep -q "$target " && { i=$((i+1)); continue; }
# Quick port check
if _check_port "$target" "$PORT_SECUBOX_API" 2>/dev/null; then
local response
response=$(wget -q -T 1 -O- "http://$target:$PORT_SECUBOX_API/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)
[ -n "$did" ] && echo "$did|$target|${role:-edge}|$PORT_SECUBOX_API"
fi
fi
i=$((i+1))
done
}
# Scan for Docker containers
discovery_scan_docker() {
local docker_sock="/var/run/docker.sock"
[ -S "$docker_sock" ] || return
# List running containers via Unix socket
local containers
if command -v curl >/dev/null 2>&1; then
containers=$(curl -s --unix-socket "$docker_sock" "http://localhost/containers/json" 2>/dev/null)
else
return
fi
[ -z "$containers" ] && return
# Parse each container
echo "$containers" | jsonfilter -e '@[*].Id' 2>/dev/null | while read -r full_id; do
[ -z "$full_id" ] && continue
local id name state ip
id=$(echo "$full_id" | cut -c1-12)
# Get container details
local info
info=$(curl -s --unix-socket "$docker_sock" "http://localhost/containers/$id/json" 2>/dev/null)
name=$(echo "$info" | jsonfilter -e '@.Name' 2>/dev/null | tr -d '/')
state=$(echo "$info" | jsonfilter -e '@.State.Running' 2>/dev/null)
ip=$(echo "$info" | jsonfilter -e '@.NetworkSettings.IPAddress' 2>/dev/null)
# Try bridge network if no IP
[ -z "$ip" ] && ip=$(echo "$info" | jsonfilter -e '@.NetworkSettings.Networks.bridge.IPAddress' 2>/dev/null)
[ -z "$ip" ] && continue
[ "$state" != "true" ] && continue
# Generate DID from container ID
local did="did:container:docker:$id"
# Check if it's a SecuBox container
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
local response
response=$(wget -q -T 1 -O- "http://$ip:$PORT_SECUBOX_API/api/status" 2>/dev/null)
if [ -n "$response" ]; then
local real_did role
real_did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
echo "${real_did:-$did}|$ip|${role:-container}|$PORT_SECUBOX_API|docker:$name"
else
echo "$did|$ip|container|0|docker:$name"
fi
else
echo "$did|$ip|container|0|docker:$name"
fi
done
}
# Scan for LXC containers
discovery_scan_lxc() {
# Check for lxc-ls command
if command -v lxc-ls >/dev/null 2>&1; then
lxc-ls --running 2>/dev/null | while read -r container; do
[ -z "$container" ] && continue
# Get container IP
local ip
ip=$(lxc-info -n "$container" -iH 2>/dev/null | head -1)
[ -z "$ip" ] && continue
local did="did:container:lxc:$container"
# Check if it's a SecuBox container
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
local response
response=$(wget -q -T 1 -O- "http://$ip:$PORT_SECUBOX_API/api/status" 2>/dev/null)
if [ -n "$response" ]; then
local real_did role
real_did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
echo "${real_did:-$did}|$ip|${role:-container}|$PORT_SECUBOX_API|lxc:$container"
else
echo "$did|$ip|container|0|lxc:$container"
fi
else
echo "$did|$ip|container|0|lxc:$container"
fi
done
fi
# Check for Proxmox pct command
if command -v pct >/dev/null 2>&1; then
pct list 2>/dev/null | tail -n +2 | while read -r vmid status _ name _; do
[ "$status" != "running" ] && continue
# Get container IP via config
local ip
ip=$(pct config "$vmid" 2>/dev/null | grep -o 'ip=[0-9.]*' | head -1 | cut -d= -f2)
[ -z "$ip" ] && continue
local did="did:container:pve:$vmid"
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
local response
response=$(wget -q -T 1 -O- "http://$ip:$PORT_SECUBOX_API/api/status" 2>/dev/null)
if [ -n "$response" ]; then
local real_did role
real_did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
echo "${real_did:-$did}|$ip|${role:-container}|$PORT_SECUBOX_API|pve:$name"
else
echo "$did|$ip|container|0|pve:$name"
fi
else
echo "$did|$ip|container|0|pve:$name"
fi
done
fi
}
# Scan for libvirt/KVM virtual machines
discovery_scan_libvirt() {
command -v virsh >/dev/null 2>&1 || return
virsh list --name 2>/dev/null | while read -r vm; do
[ -z "$vm" ] && continue
# Get VM IP from guest agent or ARP
local ip=""
# Try qemu-guest-agent
ip=$(virsh domifaddr "$vm" --source agent 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1)
# Fallback to ARP inspection via MAC
if [ -z "$ip" ]; then
local mac
mac=$(virsh domiflist "$vm" 2>/dev/null | awk 'NR>2 && $5 != "" {print $5}' | head -1)
[ -n "$mac" ] && ip=$(ip neigh show 2>/dev/null | grep -i "$mac" | awk '{print $1}' | head -1)
fi
[ -z "$ip" ] && continue
local did="did:vm:libvirt:$vm"
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
local response
response=$(wget -q -T 1 -O- "http://$ip:$PORT_SECUBOX_API/api/status" 2>/dev/null)
if [ -n "$response" ]; then
local real_did role
real_did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
echo "${real_did:-$did}|$ip|${role:-vm}|$PORT_SECUBOX_API|libvirt:$vm"
else
echo "$did|$ip|vm|0|libvirt:$vm"
fi
else
echo "$did|$ip|vm|0|libvirt:$vm"
fi
done
}
# Fingerprint network device by open ports (quick check)
discovery_fingerprint_device() {
local ip="$1"
local services=""
# Quick port checks - prioritize common ports
_check_port "$ip" "$PORT_SSH" && services="${services}ssh,"
_check_port "$ip" "$PORT_HTTP" && services="${services}http,"
_check_port "$ip" "$PORT_HTTPS" && services="${services}https,"
_check_port "$ip" "$PORT_MITMPROXY" && services="${services}mitmproxy,"
echo "${services%,}"
}
# Scan all network neighbors for any device (not just SecuBox)
discovery_scan_all_devices() {
local tmp_devices="/tmp/secubox_devices_$$.txt"
# Get all neighbors from ARP
# Format: IP dev INTERFACE lladdr MAC STATE
ip neigh show 2>/dev/null | grep -v FAILED | grep lladdr | while read -r line; do
local ip mac state
# Parse: "192.168.1.1 dev br-lan lladdr aa:bb:cc:dd:ee:ff REACHABLE"
ip=$(echo "$line" | awk '{print $1}')
mac=$(echo "$line" | awk '{print $5}')
state=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf $i" "; print ""}' | xargs)
[ -z "$ip" ] && continue
[ -z "$mac" ] && continue
# Skip multicast/broadcast MACs
echo "$mac" | grep -qi "^ff:" && continue
echo "$mac" | grep -qi "^01:" && continue
local device_type="unknown"
local hostname=""
local services=""
# Try reverse DNS (quick timeout)
hostname=$(nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $NF}' | sed 's/\.$//' | head -1)
# Check if it's a SecuBox peer
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
device_type="secubox"
services="secubox-api"
else
# Fingerprint by services (skip fingerprinting for IPv6 to be faster)
if ! echo "$ip" | grep -q ":"; then
services=$(discovery_fingerprint_device "$ip")
[ -n "$services" ] && device_type="server"
fi
fi
# Generate device ID from MAC
local device_id
device_id=$(echo -n "$mac" | md5sum | cut -c1-12)
echo "$device_id|$ip|$mac|$device_type|$hostname|$services|$state"
done > "$tmp_devices"
# Build devices JSON
local devices_json='['
local first=1
while IFS='|' read -r id ip mac dtype hostname services state; do
[ -z "$id" ] && continue
[ "$first" = "1" ] || devices_json="$devices_json,"
local last_seen
last_seen=$(date -Iseconds)
devices_json="$devices_json{\"id\":\"$id\",\"ip\":\"$ip\",\"mac\":\"$mac\",\"type\":\"$dtype\",\"hostname\":\"${hostname:-}\",\"services\":\"${services:-}\",\"state\":\"$state\",\"last_seen\":\"$last_seen\"}"
first=0
done < "$tmp_devices"
devices_json="$devices_json]"
echo "$devices_json" > "$DEVICES_FILE"
rm -f "$tmp_devices"
}
# Get all discovered devices
discovery_get_devices() {
cat "$DEVICES_FILE" 2>/dev/null || echo '[]'
}
# Get device count
discovery_get_device_count() {
jsonfilter -i "$DEVICES_FILE" -e '@[*]' 2>/dev/null | wc -l
}
# Combined peer discovery (SecuBox nodes + containers + VMs)
discovery_scan_peers() {
local tmp_peers="/tmp/secubox_peers_$$.txt"
local seen_dids=""
# Combine all discovery methods
{
# Standard discovery methods
discovery_scan_mdns
discovery_scan_wireguard
discovery_scan_arp
discovery_scan_config
# Container/VM discovery (run if commands exist)
discovery_scan_docker 2>/dev/null
discovery_scan_lxc 2>/dev/null
discovery_scan_libvirt 2>/dev/null
} | sort -u > "$tmp_peers"
# Build peers JSON
@ -129,7 +473,7 @@ discovery_scan_peers() {
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
while IFS='|' read -r did addr role port source; do
[ -z "$did" ] && continue
# Skip self
@ -144,7 +488,11 @@ discovery_scan_peers() {
local last_seen
last_seen=$(date -Iseconds)
peers_json="$peers_json{\"did\":\"$did\",\"address\":\"$addr\",\"role\":\"$role\",\"port\":$port,\"last_seen\":\"$last_seen\"}"
# Include source if available (docker:name, lxc:name, etc.)
local source_field=""
[ -n "$source" ] && source_field=",\"source\":\"$source\""
peers_json="$peers_json{\"did\":\"$did\",\"address\":\"$addr\",\"role\":\"$role\",\"port\":${port:-0}$source_field,\"last_seen\":\"$last_seen\"}"
first=0
done < "$tmp_peers"
@ -154,6 +502,54 @@ discovery_scan_peers() {
echo "$peers_json" > "$PEERS_FILE"
rm -f "$tmp_peers"
# Also run full device discovery in background
discovery_scan_all_devices &
}
# Full discovery scan (includes subnet scan - slower)
discovery_scan_full() {
local tmp_peers="/tmp/secubox_peers_full_$$.txt"
# Run all methods including slow subnet scan
{
discovery_scan_mdns
discovery_scan_wireguard
discovery_scan_arp
discovery_scan_config
discovery_scan_docker 2>/dev/null
discovery_scan_lxc 2>/dev/null
discovery_scan_libvirt 2>/dev/null
discovery_scan_subnet 2>/dev/null
} | sort -u > "$tmp_peers"
# Use same parsing logic as discovery_scan_peers
local peers_json='['
local first=1
local my_did="${NODE_DID:-$(cat /var/lib/mirrornet/identity/did.txt 2>/dev/null)}"
local seen_dids=""
while IFS='|' read -r did addr role port source; do
[ -z "$did" ] && continue
[ "$did" = "$my_did" ] && continue
echo "$seen_dids" | grep -q "$did" && continue
seen_dids="$seen_dids $did"
[ "$first" = "1" ] || peers_json="$peers_json,"
local last_seen source_field=""
last_seen=$(date -Iseconds)
[ -n "$source" ] && source_field=",\"source\":\"$source\""
peers_json="$peers_json{\"did\":\"$did\",\"address\":\"$addr\",\"role\":\"$role\",\"port\":${port:-0}$source_field,\"last_seen\":\"$last_seen\"}"
first=0
done < "$tmp_peers"
peers_json="$peers_json]"
echo "$peers_json" > "$PEERS_FILE"
rm -f "$tmp_peers"
discovery_scan_all_devices
}
# Get peer count

View File

@ -7,6 +7,10 @@
. /usr/share/libubox/jshn.sh
SOCKET="/var/run/secuboxd/topo.sock"
STATEDIR="/var/lib/secubox-mesh"
# Source discovery library for direct calls
[ -f /usr/lib/secubox-mesh/discovery.sh ] && . /usr/lib/secubox-mesh/discovery.sh
# Send command to daemon
_send_cmd() {
@ -39,7 +43,10 @@ case_list() {
"ping": {},
"get_config": {},
"set_config": { "role": "string", "beacon_interval": "number" },
"restart": {}
"restart": {},
"devices": {},
"scan_full": {},
"scan_containers": {}
}
EOF
}
@ -126,6 +133,72 @@ case_call() {
json_add_boolean success 1
json_dump
;;
devices)
# Return discovered network devices
if [ -f "$STATEDIR/devices.json" ]; then
cat "$STATEDIR/devices.json"
else
echo '[]'
fi
;;
scan_full)
# Trigger full network scan (including subnet scan)
if type discovery_scan_full >/dev/null 2>&1; then
# Run in background to avoid timeout
discovery_scan_full &
local scan_pid=$!
json_init
json_add_boolean started 1
json_add_int pid "$scan_pid"
json_add_string message "Full scan initiated"
json_dump
else
json_init
json_add_boolean started 0
json_add_string error "Discovery library not available"
json_dump
fi
;;
scan_containers)
# Scan for containers and VMs specifically
local containers='[]'
if type discovery_scan_docker >/dev/null 2>&1; then
local docker_out lxc_out libvirt_out
docker_out=$(discovery_scan_docker 2>/dev/null)
lxc_out=$(discovery_scan_lxc 2>/dev/null)
libvirt_out=$(discovery_scan_libvirt 2>/dev/null)
# Build JSON from pipe-separated output
json_init
json_add_array containers
for line in $docker_out $lxc_out $libvirt_out; do
[ -z "$line" ] && continue
local did addr role port source
did=$(echo "$line" | cut -d'|' -f1)
addr=$(echo "$line" | cut -d'|' -f2)
role=$(echo "$line" | cut -d'|' -f3)
port=$(echo "$line" | cut -d'|' -f4)
source=$(echo "$line" | cut -d'|' -f5)
json_add_object ""
json_add_string did "$did"
json_add_string address "$addr"
json_add_string role "$role"
json_add_int port "${port:-0}"
json_add_string source "$source"
json_close_object
done
json_close_array
json_dump
else
echo '{"containers":[],"error":"Discovery library not available"}'
fi
;;
*)
json_init
json_add_string error "Unknown method: $method"

View File

@ -11,7 +11,9 @@
"node_info",
"telemetry",
"ping",
"get_config"
"get_config",
"devices",
"scan_containers"
]
},
"uci": ["secubox"]
@ -21,7 +23,8 @@
"luci.secubox-mesh": [
"node_rotate",
"set_config",
"restart"
"restart",
"scan_full"
]
},
"uci": ["secubox"]