#!/bin/sh # SecuBox P2P Hub Manager # Handles peer discovery, mesh networking, and service federation # Supports WAN IP, LAN IP, and WireGuard tunnel redundancy VERSION="0.6.0" CONFIG_FILE="/etc/config/secubox-p2p" PEERS_FILE="/tmp/secubox-p2p-peers.json" SERVICES_FILE="/tmp/secubox-p2p-services.json" STATE_DIR="/var/run/secubox-p2p" MDNS_PID_FILE="$STATE_DIR/mdns.pid" API_PORT=7331 # Initialize init() { mkdir -p "$STATE_DIR" [ -f "$PEERS_FILE" ] || echo '{"peers":[]}' > "$PEERS_FILE" [ -f "$SERVICES_FILE" ] || echo '{"services":[]}' > "$SERVICES_FILE" # Initialize node info _init_node_info } # Get LAN IP address get_lan_ip() { local lan_ip lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1) [ -z "$lan_ip" ] && lan_ip=$(uci -q get network.lan.ipaddr) echo "${lan_ip:-127.0.0.1}" } # Get WAN IP address (real external IP) get_wan_ip() { local wan_ip wan_iface # Get WAN interface wan_iface=$(uci -q get network.wan.device || uci -q get network.wan.ifname || echo "eth0") # Try to get IP from WAN interface wan_ip=$(ip -4 addr show "$wan_iface" 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1) # If no direct WAN IP, try to get public IP via external service if [ -z "$wan_ip" ] || echo "$wan_ip" | grep -qE '^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)'; then # Behind NAT - try to get public IP wan_ip=$(curl -s --connect-timeout 3 https://api.ipify.org 2>/dev/null) [ -z "$wan_ip" ] && wan_ip=$(curl -s --connect-timeout 3 https://ifconfig.me 2>/dev/null) fi echo "${wan_ip:-}" } # Get WireGuard tunnel IP(s) get_wg_ips() { local wg_ips="" # Check all WireGuard interfaces for wg_iface in $(ip link show type wireguard 2>/dev/null | grep -oE 'wg[0-9]+' | head -5); do local wg_ip wg_ip=$(ip -4 addr show "$wg_iface" 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}') if [ -n "$wg_ip" ]; then [ -n "$wg_ips" ] && wg_ips="$wg_ips," wg_ips="${wg_ips}$wg_ip" fi done echo "$wg_ips" } # Get all node addresses as JSON array get_node_addresses() { local lan_ip wan_ip wg_ips addresses lan_ip=$(get_lan_ip) wan_ip=$(get_wan_ip) wg_ips=$(get_wg_ips) # Build addresses JSON array addresses="[{\"type\":\"lan\",\"address\":\"$lan_ip\",\"port\":$API_PORT}" if [ -n "$wan_ip" ]; then addresses="$addresses,{\"type\":\"wan\",\"address\":\"$wan_ip\",\"port\":$API_PORT}" fi # Add WireGuard IPs if [ -n "$wg_ips" ]; then for wg_ip in $(echo "$wg_ips" | tr ',' ' '); do addresses="$addresses,{\"type\":\"wireguard\",\"address\":\"$wg_ip\",\"port\":$API_PORT}" done fi addresses="$addresses]" echo "$addresses" } # Initialize node identity _init_node_info() { local node_name node_name=$(get_config main node_name "") # Generate node name from hostname if not set if [ -z "$node_name" ]; then node_name=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "secubox") set_config main node_name "$node_name" fi # Generate node ID if not exists local node_id_file="$STATE_DIR/node.id" if [ ! -f "$node_id_file" ]; then # Generate unique node ID from MAC address local mac mac=$(ip link show br-lan 2>/dev/null | grep ether | awk '{print $2}' | tr -d ':') [ -z "$mac" ] && mac=$(cat /proc/sys/kernel/random/uuid | tr -d '-' | head -c 12) echo "sb-${mac}" > "$node_id_file" fi } # Get config value get_config() { local section="$1" local option="$2" local default="$3" uci -q get "secubox-p2p.${section}.${option}" || echo "$default" } # Set config value set_config() { local section="$1" local option="$2" local value="$3" uci set "secubox-p2p.${section}.${option}=$value" uci commit secubox-p2p } # Discover peers via mDNS discover_mdns() { local timeout="${1:-5}" local peers="[]" # Check if avahi-browse is available if command -v avahi-browse >/dev/null 2>&1; then # Discover _secubox._tcp services local discovered=$(avahi-browse -t -r _secubox._tcp 2>/dev/null | grep -E "^=|hostname|address" || true) if [ -n "$discovered" ]; then # Parse discovered services into JSON echo "$discovered" | awk ' /^=/ { name=$4 } /hostname/ { host=$3 } /address/ { addr=$3 if (name && addr) { printf "{\"id\":\"%s\",\"name\":\"%s\",\"address\":\"%s\",\"status\":\"online\"},", name, name, addr } } ' | sed 's/,$//' | awk '{print "["$0"]"}' else echo "[]" fi else # Fallback: scan local network local gateway=$(ip route | grep default | awk '{print $3}') local subnet=$(echo "$gateway" | sed 's/\.[0-9]*$/./') # Quick ping scan for i in $(seq 1 254); do ping -c1 -W1 "${subnet}${i}" >/dev/null 2>&1 & done wait # Check for SecuBox peers via HTTP arp -n | grep -v incomplete | awk '{print $1}' | while read ip; do if curl -s --connect-timeout 1 "http://${ip}/cgi-bin/luci/admin/secubox" >/dev/null 2>&1; then echo "{\"id\":\"peer-${ip}\",\"name\":\"SecuBox@${ip}\",\"address\":\"${ip}\",\"status\":\"online\"}" fi done | jq -s '.' 2>/dev/null || echo "[]" fi } # Get peers list get_peers() { # Ensure state dir and node info exist mkdir -p "$STATE_DIR" _init_node_info # If no peers file or empty, ensure local node is registered if [ ! -f "$PEERS_FILE" ] || grep -q '"peers":\[\]' "$PEERS_FILE" 2>/dev/null; then register_self fi if [ -f "$PEERS_FILE" ]; then cat "$PEERS_FILE" else # Still no file? Create with local node local node_name node_id lan_ip node_name=$(get_config main node_name "secubox") node_id=$(cat "$STATE_DIR/node.id" 2>/dev/null || echo "unknown") lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1) [ -z "$lan_ip" ] && lan_ip=$(uci -q get network.lan.ipaddr || echo "127.0.0.1") echo "{\"peers\":[{\"id\":\"$node_id\",\"name\":\"$node_name (local)\",\"address\":\"$lan_ip\",\"status\":\"online\",\"is_local\":true}]}" fi } # Add peer with optional WAN and WireGuard addresses add_peer() { local address="$1" local name="${2:-Peer}" local wan_address="${3:-}" local wg_address="${4:-}" local id="peer-$(echo "$address" | md5sum | cut -c1-8)" local peers=$(get_peers) # Build addresses array local addresses="[{\"type\":\"lan\",\"address\":\"$address\",\"port\":$API_PORT}" [ -n "$wan_address" ] && addresses="$addresses,{\"type\":\"wan\",\"address\":\"$wan_address\",\"port\":$API_PORT}" [ -n "$wg_address" ] && addresses="$addresses,{\"type\":\"wireguard\",\"address\":\"$wg_address\",\"port\":$API_PORT}" addresses="$addresses]" local new_peer="{\"id\":\"$id\",\"name\":\"$name\",\"address\":\"$address\",\"wan_address\":\"${wan_address:-}\",\"wg_addresses\":\"${wg_address:-}\",\"addresses\":$addresses,\"status\":\"unknown\",\"added\":\"$(date -Iseconds)\"}" echo "$peers" | jq ".peers += [$new_peer]" > "$PEERS_FILE" echo "{\"success\":true,\"peer_id\":\"$id\"}" } # Remove peer remove_peer() { local peer_id="$1" local peers=$(get_peers) echo "$peers" | jq ".peers = [.peers[] | select(.id != \"$peer_id\")]" > "$PEERS_FILE" echo "{\"success\":true}" } # Get settings get_settings() { cat </dev/null 2>&1; then if "$init_script" status 2>&1 | grep -qiE "running|active"; then status="running" fi fi # Method 2: Check via pgrep (fallback) if [ "$status" = "stopped" ]; then if pgrep -f "$svc_name" >/dev/null 2>&1; then status="running" pid=$(pgrep -f "$svc_name" | head -1) fi fi # Method 3: Check if enabled in UCI/procd local enabled="0" if "$init_script" enabled 2>/dev/null; then enabled="1" fi local port=$(get_service_port "$svc_name") # Build JSON entry if [ $count -gt 0 ]; then services="$services," fi services="$services{\"name\":\"$svc_name\",\"status\":\"$status\",\"enabled\":$enabled,\"port\":\"$port\",\"pid\":\"$pid\"}" count=$((count + 1)) done echo "{\"services\":[$services],\"total\":$count}" } # Get shared services (from peers) get_shared_services() { local all_services="[]" # Use active_address if available, skip IPv6 and local nodes local peers=$(get_peers | jq -r '.peers[] | select(.status=="online") | select(.is_local==false or .is_local==null) | .active_address // .address' | grep -v '^\[' | sed 's/ .*//') for peer_addr in $peers; do [ -z "$peer_addr" ] && continue [ "$peer_addr" = "null" ] && continue # Fetch services with reasonable timeout local response=$(curl -s --connect-timeout 2 --max-time 10 "http://${peer_addr}/cgi-bin/p2p-services" 2>/dev/null) if [ -n "$response" ]; then local peer_services=$(echo "$response" | jq -c '.services // []' 2>/dev/null) if [ -n "$peer_services" ] && [ "$peer_services" != "null" ] && [ "$peer_services" != "[]" ]; then # Add peer info to each service peer_services=$(echo "$peer_services" | jq --arg addr "$peer_addr" '[.[] | . + {peer: $addr}]') all_services=$(echo "$all_services" "$peer_services" | jq -s '.[0] + .[1]' 2>/dev/null) fi fi done echo "{\"shared_services\":$all_services}" } # Sync with peers sync_catalog() { local peers=$(get_peers | jq -r '.peers[].address') local synced=0 for peer_addr in $peers; do if curl -s --connect-timeout 2 "http://${peer_addr}/cgi-bin/luci" >/dev/null 2>&1; then synced=$((synced + 1)) fi done echo "{\"success\":true,\"synced_peers\":$synced}" } # Broadcast command to all peers broadcast_command() { local cmd="$1" local peers=$(get_peers | jq -r '.peers[] | select(.status=="online") | .address') local success=0 local failed=0 for peer_addr in $peers; do if curl -s --connect-timeout 5 -X POST "http://${peer_addr}:8080/p2p/command" -d "{\"command\":\"$cmd\"}" >/dev/null 2>&1; then success=$((success + 1)) else failed=$((failed + 1)) fi done echo "{\"success\":true,\"broadcast_success\":$success,\"broadcast_failed\":$failed}" } # Publish mDNS service announcement publish_mdns() { local node_name node_name=$(get_config main node_name "secubox") # Kill any existing avahi-publish process [ -f "$MDNS_PID_FILE" ] && kill $(cat "$MDNS_PID_FILE") 2>/dev/null rm -f "$MDNS_PID_FILE" # Check if avahi-publish is available if command -v avahi-publish >/dev/null 2>&1; then # Get LAN IP for service registration local lan_ip lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1) [ -z "$lan_ip" ] && lan_ip=$(uci -q get network.lan.ipaddr) # Publish _secubox._tcp service avahi-publish -s "$node_name" "_secubox._tcp" "$API_PORT" \ "version=$VERSION" \ "type=mesh-node" \ "ip=$lan_ip" & echo $! > "$MDNS_PID_FILE" logger -t secubox-p2p "mDNS service published: $node_name._secubox._tcp on port $API_PORT" return 0 else logger -t secubox-p2p "WARNING: avahi-publish not available, mDNS disabled" return 1 fi } # Stop mDNS service announcement stop_mdns() { if [ -f "$MDNS_PID_FILE" ]; then kill $(cat "$MDNS_PID_FILE") 2>/dev/null rm -f "$MDNS_PID_FILE" logger -t secubox-p2p "mDNS service stopped" fi } # =========================================== # DNS Federation - Dynamic mesh DNS entries # =========================================== DNS_HOSTS_FILE="/tmp/hosts/secubox-mesh" DNS_DOMAIN_BASE="mesh.local" # Initialize DNS federation dns_init() { local dns_enabled=$(get_config dns enabled 0) [ "$dns_enabled" = "1" ] || return 0 DNS_DOMAIN_BASE=$(get_config dns base_domain "mesh.local") # Ensure hosts directory exists mkdir -p /tmp/hosts # Create or update mesh hosts file touch "$DNS_HOSTS_FILE" # Ensure dnsmasq reads from /tmp/hosts # Check if addn-hosts is configured if ! uci -q get dhcp.@dnsmasq[0].addnhosts | grep -q "/tmp/hosts"; then uci add_list dhcp.@dnsmasq[0].addnhosts="/tmp/hosts" uci commit dhcp logger -t secubox-p2p "Added /tmp/hosts to dnsmasq additional hosts" fi } # Update DNS entries for all mesh peers dns_update_peers() { local dns_enabled=$(get_config dns enabled 0) [ "$dns_enabled" = "1" ] || return 0 dns_init local base_domain=$(get_config dns base_domain "mesh.local") local hosts_content="" local count=0 # Get all peers (including local) local peers_json=$(cat "$PEERS_FILE" 2>/dev/null || echo '{"peers":[]}') # Parse peers and build hosts entries # Format: IP hostname hostname.mesh.local if command -v jq >/dev/null 2>&1; then hosts_content=$(echo "$peers_json" | jq -r '.peers[] | select(.status == "online" or .is_local == true) | # Build hostname entries ( (.address // empty) as $lan | (.wan_address // empty) as $wan | (.wg_addresses // empty) as $wg | (.name // "peer") | gsub(" \\(local\\)"; "") | gsub("[^a-zA-Z0-9-]"; "-") | ascii_downcase as $hostname | # LAN entry (primary for local network) (if $lan != "" and $lan != null then "\($lan)\t\($hostname)\t\($hostname).'$base_domain'" else empty end), # WireGuard entry (for mesh access) (if $wg != "" and $wg != null then ($wg | split(",")[0]) as $wg_ip | if $wg_ip != "" then "\($wg_ip)\t\($hostname)-wg\t\($hostname)-wg.'$base_domain'" else empty end else empty end) ) ' 2>/dev/null) else # Fallback without jq - use jsonfilter local peer_count=$(echo "$peers_json" | jsonfilter -e '@.peers[*]' 2>/dev/null | wc -l) local i=0 while [ $i -lt $peer_count ]; do local addr=$(echo "$peers_json" | jsonfilter -e "@.peers[$i].address" 2>/dev/null) local name=$(echo "$peers_json" | jsonfilter -e "@.peers[$i].name" 2>/dev/null) local status=$(echo "$peers_json" | jsonfilter -e "@.peers[$i].status" 2>/dev/null) local is_local=$(echo "$peers_json" | jsonfilter -e "@.peers[$i].is_local" 2>/dev/null) local wg_addr=$(echo "$peers_json" | jsonfilter -e "@.peers[$i].wg_addresses" 2>/dev/null | cut -d',' -f1) # Only add online peers or local node if [ "$status" = "online" ] || [ "$is_local" = "true" ]; then # Clean hostname (remove special chars) local hostname=$(echo "$name" | sed -e 's/ (local)//' -e 's/[^a-zA-Z0-9-]/-/g' | tr 'A-Z' 'a-z') # Add LAN entry if [ -n "$addr" ]; then hosts_content="$hosts_content$addr $hostname $hostname.$base_domain " count=$((count + 1)) fi # Add WireGuard entry if [ -n "$wg_addr" ]; then hosts_content="$hosts_content$wg_addr ${hostname}-wg ${hostname}-wg.$base_domain " count=$((count + 1)) fi fi i=$((i + 1)) done fi # Write hosts file echo "# SecuBox Mesh DNS Federation - Auto-generated" > "$DNS_HOSTS_FILE" echo "# Domain: $base_domain" >> "$DNS_HOSTS_FILE" echo "# Updated: $(date -Iseconds 2>/dev/null || date)" >> "$DNS_HOSTS_FILE" echo "" >> "$DNS_HOSTS_FILE" echo "$hosts_content" >> "$DNS_HOSTS_FILE" # Count actual entries (non-comment, non-empty lines) count=$(grep -c "^[0-9]" "$DNS_HOSTS_FILE" 2>/dev/null || echo "0") # Signal dnsmasq to reload hosts killall -HUP dnsmasq 2>/dev/null || /etc/init.d/dnsmasq reload 2>/dev/null logger -t secubox-p2p "DNS federation updated: $count mesh host entries in $base_domain" echo "{\"success\":true,\"entries\":$count,\"domain\":\"$base_domain\"}" } # Add a single peer to DNS dns_add_peer() { local hostname="$1" local ip="$2" local dns_enabled=$(get_config dns enabled 0) [ "$dns_enabled" = "1" ] || return 0 local base_domain=$(get_config dns base_domain "mesh.local") # Clean hostname hostname=$(echo "$hostname" | sed -e 's/[^a-zA-Z0-9-]/-/g' | tr 'A-Z' 'a-z') # Append to hosts file (avoid duplicates) if ! grep -q " $hostname " "$DNS_HOSTS_FILE" 2>/dev/null; then echo "$ip $hostname $hostname.$base_domain" >> "$DNS_HOSTS_FILE" killall -HUP dnsmasq 2>/dev/null logger -t secubox-p2p "DNS: Added $hostname.$base_domain -> $ip" fi } # Remove a peer from DNS dns_remove_peer() { local hostname="$1" local dns_enabled=$(get_config dns enabled 0) [ "$dns_enabled" = "1" ] || return 0 # Clean hostname hostname=$(echo "$hostname" | sed -e 's/[^a-zA-Z0-9-]/-/g' | tr 'A-Z' 'a-z') # Remove from hosts file sed -i "/ $hostname /d" "$DNS_HOSTS_FILE" 2>/dev/null killall -HUP dnsmasq 2>/dev/null logger -t secubox-p2p "DNS: Removed $hostname" } # Get DNS federation status dns_status() { local dns_enabled=$(get_config dns enabled 0) local base_domain=$(get_config dns base_domain "mesh.local") local entry_count=0 if [ -f "$DNS_HOSTS_FILE" ]; then entry_count=$(grep -c "^[0-9]" "$DNS_HOSTS_FILE" 2>/dev/null || echo "0") fi cat << EOF { "enabled": $dns_enabled, "domain": "$base_domain", "hosts_file": "$DNS_HOSTS_FILE", "entries": $entry_count, "dnsmasq_running": $(pgrep dnsmasq >/dev/null && echo "true" || echo "false") } EOF } # Enable DNS federation dns_enable() { set_config dns enabled 1 local base_domain="${1:-mesh.local}" set_config dns base_domain "$base_domain" dns_init dns_update_peers logger -t secubox-p2p "DNS federation enabled with domain: $base_domain" echo "{\"success\":true,\"domain\":\"$base_domain\"}" } # Disable DNS federation dns_disable() { set_config dns enabled 0 # Clear hosts file echo "# SecuBox Mesh DNS Federation - Disabled" > "$DNS_HOSTS_FILE" killall -HUP dnsmasq 2>/dev/null logger -t secubox-p2p "DNS federation disabled" echo "{\"success\":true}" } # Get node status JSON (for REST API) # Now includes WAN IP and WireGuard tunnel addresses get_node_status() { local node_name node_id lan_ip wan_ip wg_ips addresses uptime node_name=$(get_config main node_name "secubox") node_id=$(cat "$STATE_DIR/node.id" 2>/dev/null || echo "unknown") lan_ip=$(get_lan_ip) wan_ip=$(get_wan_ip) wg_ips=$(get_wg_ips) addresses=$(get_node_addresses) uptime=$(cat /proc/uptime | cut -d' ' -f1) cat </dev/null | wc -l) } EOF } # Register self in peer list (ensure node is visible in its own mesh view) # Now includes WAN IP and WireGuard tunnel addresses for redundancy register_self() { local node_name node_id lan_ip wan_ip wg_ips addresses node_name=$(get_config main node_name "secubox") node_id=$(cat "$STATE_DIR/node.id" 2>/dev/null || echo "unknown") # Get all available addresses lan_ip=$(get_lan_ip) wan_ip=$(get_wan_ip) wg_ips=$(get_wg_ips) addresses=$(get_node_addresses) # Build peer entry with all addresses # Primary address is LAN for local network, but WAN and WG are included for external/redundancy local self_peer="{\"id\":\"$node_id\",\"name\":\"$node_name (local)\",\"address\":\"$lan_ip\",\"wan_address\":\"${wan_ip:-}\",\"wg_addresses\":\"${wg_ips:-}\",\"addresses\":$addresses,\"status\":\"online\",\"is_local\":true,\"added\":\"$(date -Iseconds)\"}" # Check if self is already registered using grep (jsonfilter syntax workaround) local current=$(cat "$PEERS_FILE" 2>/dev/null || echo '{"peers":[]}') local exists="" if echo "$current" | grep -q "\"$node_id\""; then exists="yes" fi if [ -z "$exists" ]; then # Check if peers array is empty (handle whitespace variations) if echo "$current" | grep -qE '"peers"[[:space:]]*:[[:space:]]*\[[[:space:]]*\]'; then # Empty array - just set self as first peer echo "{\"peers\":[$self_peer]}" > "$PEERS_FILE" else # Array has entries - prepend self using awk for reliable JSON manipulation echo "$current" | awk -v self="$self_peer" '{ gsub(/"peers"[[:space:]]*:[[:space:]]*\[/, "\"peers\": [" self ", ") print }' > "$PEERS_FILE.tmp" if [ -s "$PEERS_FILE.tmp" ]; then mv "$PEERS_FILE.tmp" "$PEERS_FILE" else # Fallback: fresh peers file echo "{\"peers\":[$self_peer]}" > "$PEERS_FILE" fi fi logger -t secubox-p2p "Registered local node: $node_name ($node_id) LAN=$lan_ip WAN=${wan_ip:-none} WG=${wg_ips:-none}" fi } # Daemon mode daemon_loop() { init # Publish mDNS service publish_mdns # Register self in peer list register_self # Initialize DNS federation dns_init # Setup signal handlers trap 'stop_mdns; exit 0' INT TERM while true; do # Auto-discovery if enabled if [ "$(get_config main discovery_enabled 1)" = "1" ]; then local discovered=$(discover_mdns 3) if [ "$discovered" != "[]" ] && [ -n "$discovered" ]; then # Update peers file with discovered peers local current=$(get_peers) # Parse discovered peers (handle jq or jsonfilter) if command -v jq >/dev/null 2>&1; then for peer in $(echo "$discovered" | jq -c '.[]' 2>/dev/null); do local peer_id=$(echo "$peer" | jq -r '.id') local exists=$(echo "$current" | jq ".peers[] | select(.id==\"$peer_id\")") if [ -z "$exists" ]; then current=$(echo "$current" | jq ".peers += [$peer]") fi done echo "$current" > "$PEERS_FILE" fi fi fi # Update peer status (skip local node) # Try all available addresses: WireGuard first (secure tunnel), then WAN, then LAN local peers=$(get_peers) if command -v jq >/dev/null 2>&1; then local updated_peers=$(echo "$peers" | jq -c '.peers[] | select(.is_local != true)' 2>/dev/null) for peer in $updated_peers; do local addr=$(echo "$peer" | jq -r '.address') local wan_addr=$(echo "$peer" | jq -r '.wan_address // empty') local wg_addrs=$(echo "$peer" | jq -r '.wg_addresses // empty') local id=$(echo "$peer" | jq -r '.id') local reachable="" local best_addr="" # Try WireGuard addresses first (secure redundancy) if [ -n "$wg_addrs" ]; then for wg_addr in $(echo "$wg_addrs" | tr ',' ' '); do if ping -c1 -W1 "$wg_addr" >/dev/null 2>&1; then reachable="yes" best_addr="$wg_addr (wg)" break fi done fi # Try WAN address if WG failed if [ -z "$reachable" ] && [ -n "$wan_addr" ] && [ "$wan_addr" != "null" ]; then if ping -c1 -W1 "$wan_addr" >/dev/null 2>&1; then reachable="yes" best_addr="$wan_addr (wan)" fi fi # Try LAN address as fallback if [ -z "$reachable" ]; then if ping -c1 -W1 "$addr" >/dev/null 2>&1; then reachable="yes" best_addr="$addr (lan)" fi fi if [ -n "$reachable" ]; then peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).status = \"online\"") peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).active_address = \"$best_addr\"") else peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).status = \"offline\"") peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).active_address = null") fi done echo "$peers" > "$PEERS_FILE" fi # Update DNS federation with current peer list dns_update_peers >/dev/null 2>&1 # Sleep interval local interval=$(get_config main sync_interval 60) sleep "$interval" done } # Main case "$1" in daemon) daemon_loop ;; discover) discover_mdns "${2:-5}" ;; peers) get_peers ;; add-peer) add_peer "$2" "$3" ;; remove-peer) remove_peer "$2" ;; settings) get_settings ;; set-settings) set_settings "$2" ;; services) get_services ;; shared-services) get_shared_services ;; sync) sync_catalog ;; broadcast) broadcast_command "$2" ;; version) echo "$VERSION" ;; status) get_node_status ;; publish-mdns) publish_mdns ;; stop-mdns) stop_mdns ;; register-self) init register_self ;; # DNS Federation commands dns-status) dns_status ;; dns-enable) dns_enable "$2" ;; dns-disable) dns_disable ;; dns-update) init dns_update_peers ;; dns-add) dns_add_peer "$2" "$3" ;; dns-remove) dns_remove_peer "$2" ;; *) echo "Usage: $0 {daemon|discover|peers|add-peer|remove-peer|settings|set-settings|services|shared-services|sync|broadcast|version|status|publish-mdns|stop-mdns|register-self|dns-status|dns-enable|dns-disable|dns-update|dns-add|dns-remove}" exit 1 ;; esac