#!/bin/sh # SecuBox Mesh Join CLI Tool # Usage: sbx-mesh-join # or: sbx-mesh-join set -e NODE_ID_FILE="/etc/secubox/node.id" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # No Color log_info() { printf "${GREEN}[+]${NC} %s\n" "$1"; } log_warn() { printf "${YELLOW}[!]${NC} %s\n" "$1"; } log_error() { printf "${RED}[-]${NC} %s\n" "$1"; } log_step() { printf "${CYAN}[*]${NC} %s\n" "$1"; } # Get or generate node fingerprint get_fingerprint() { if [ -f "$NODE_ID_FILE" ]; then cat "$NODE_ID_FILE" else mkdir -p /etc/secubox local mac="" if [ -f /sys/class/net/br-lan/address ]; then mac=$(cat /sys/class/net/br-lan/address | tr -d ':') elif [ -f /sys/class/net/eth0/address ]; then mac=$(cat /sys/class/net/eth0/address | tr -d ':') else mac=$(cat /sys/class/net/*/address 2>/dev/null | head -1 | tr -d ':') fi local fp="owrt-${mac}" echo "$fp" > "$NODE_ID_FILE" echo "$fp" fi } # Get local IP get_local_ip() { ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1 || \ ip -4 addr show eth0 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1 || \ echo "unknown" } # Parse URL to extract IP and token parse_url() { local url="$1" # Match: http(s)://IP(:PORT)/...?token=XXX echo "$url" | sed -n 's|.*://\([^:/]*\).*[?&]token=\([^&]*\).*|\1 \2|p' } # Usage usage() { cat << 'EOF' SecuBox Mesh Join Tool Usage: sbx-mesh-join sbx-mesh-join Examples: sbx-mesh-join 192.168.1.1 abc123def456 sbx-mesh-join 'https://192.168.1.1/master-link/?token=abc123' sbx-mesh-join 'https://master.local/master-link/?token=abc123' The tool will: 1. Generate a unique node fingerprint (if not exists) 2. Collect local device info (hostname, IP, model) 3. Send join request to the master 4. Save mesh configuration on success EOF exit 1 } # Main main() { local master_ip="" local token="" # Parse arguments case "$#" in 1) # Single argument - could be URL local parsed=$(parse_url "$1") if [ -n "$parsed" ]; then master_ip=$(echo "$parsed" | awk '{print $1}') token=$(echo "$parsed" | awk '{print $2}') else log_error "Invalid URL format" usage fi ;; 2) # Two arguments: IP and token master_ip="$1" token="$2" ;; *) usage ;; esac if [ -z "$master_ip" ] || [ -z "$token" ]; then log_error "Missing master IP or token" usage fi log_info "SecuBox Mesh Join" echo "" # Gather local info log_step "Collecting node information..." local fingerprint=$(get_fingerprint) local hostname=$(uci -q get system.@system[0].hostname 2>/dev/null || hostname || echo "openwrt") local address=$(get_local_ip) local model="" if [ -f /tmp/sysinfo/model ]; then model=$(cat /tmp/sysinfo/model) elif command -v jsonfilter >/dev/null && [ -f /etc/board.json ]; then model=$(jsonfilter -i /etc/board.json -e '@.model.name' 2>/dev/null || echo "Unknown") else model="Unknown" fi log_info "Fingerprint: $fingerprint" log_info "Hostname: $hostname" log_info "Address: $address" log_info "Model: $model" echo "" # Prepare JSON payload local payload="{\"token\":\"${token}\",\"fingerprint\":\"${fingerprint}\",\"hostname\":\"${hostname}\",\"address\":\"${address}\",\"model\":\"${model}\"}" log_step "Connecting to master at ${master_ip}..." # Send join request (HTTPS with self-signed cert support) local response="" local api_url="https://${master_ip}/api/v1/p2p/master-link/join" if command -v curl >/dev/null; then response=$(curl -sf -k --connect-timeout 30 -X POST \ -H "Content-Type: application/json" \ -d "$payload" \ "$api_url" 2>/dev/null) else response=$(wget -qO- --no-check-certificate --timeout=30 \ --header="Content-Type: application/json" \ --post-data="$payload" \ "$api_url" 2>/dev/null) fi if [ -z "$response" ]; then log_error "Failed to connect to master" exit 1 fi # Parse response local status="" local message="" local master_fp="" local depth="" if command -v jsonfilter >/dev/null; then status=$(echo "$response" | jsonfilter -e '@.status' 2>/dev/null || true) message=$(echo "$response" | jsonfilter -e '@.message' 2>/dev/null || true) master_fp=$(echo "$response" | jsonfilter -e '@.master_fingerprint' 2>/dev/null || true) depth=$(echo "$response" | jsonfilter -e '@.depth' 2>/dev/null || true) elif command -v jq >/dev/null; then status=$(echo "$response" | jq -r '.status // empty') message=$(echo "$response" | jq -r '.message // empty') master_fp=$(echo "$response" | jq -r '.master_fingerprint // empty') depth=$(echo "$response" | jq -r '.depth // empty') else # Fallback: grep for status status=$(echo "$response" | grep -oE '"status"\s*:\s*"[^"]*"' | cut -d'"' -f4) message=$(echo "$response" | grep -oE '"message"\s*:\s*"[^"]*"' | cut -d'"' -f4) fi echo "" case "$status" in approved) log_info "Successfully joined mesh network!" [ -n "$master_fp" ] && log_info "Master fingerprint: $master_fp" [ -n "$depth" ] && log_info "Network depth: $depth" # Save to UCI if available if command -v uci >/dev/null; then log_step "Saving configuration..." # Ensure config file and section exist [ -f /etc/config/masterlink ] || touch /etc/config/masterlink uci -q get masterlink.settings >/dev/null || uci set masterlink.settings='mesh' uci set masterlink.settings.enabled='1' uci set masterlink.settings.role='peer' uci set masterlink.settings.status='approved' uci set masterlink.settings.master_ip="$master_ip" [ -n "$master_fp" ] && uci set masterlink.settings.master_fingerprint="$master_fp" [ -n "$depth" ] && uci set masterlink.settings.depth="$depth" uci set masterlink.settings.joined_at="$(date -Iseconds 2>/dev/null || date)" uci commit masterlink log_info "Configuration saved" fi ;; pending) log_warn "Join request submitted - waiting for master approval" log_info "Check back later or ask the master admin to approve your node" # Save pending state if command -v uci >/dev/null; then [ -f /etc/config/masterlink ] || touch /etc/config/masterlink uci -q get masterlink.settings >/dev/null || uci set masterlink.settings='mesh' uci set masterlink.settings.enabled='1' uci set masterlink.settings.role='peer' uci set masterlink.settings.status='pending' uci set masterlink.settings.master_ip="$master_ip" uci commit masterlink fi ;; error|rejected) log_error "Join failed: ${message:-Unknown error}" exit 1 ;; *) log_error "Unexpected response: $response" exit 1 ;; esac echo "" log_info "Done" } main "$@"