Add || true to jsonfilter commands to prevent script exit when optional fields (master_fingerprint, depth) are missing from API response. The set -e directive was causing premature exit. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
232 lines
7.2 KiB
Bash
232 lines
7.2 KiB
Bash
#!/bin/sh
|
|
# SecuBox Mesh Join CLI Tool
|
|
# Usage: sbx-mesh-join <master-ip> <token>
|
|
# or: sbx-mesh-join <invite-url>
|
|
|
|
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 <master-ip> <token>
|
|
sbx-mesh-join <invite-url>
|
|
|
|
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..."
|
|
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
|
|
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 "$@"
|