#!/usr/bin/env bash # Practical fix for enode URLs: Extract first 128 chars and map to correct IPs # Based on Besu requirements: enode IDs must be exactly 128 hex characters set -euo pipefail PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}" WORK_DIR="/tmp/fix-enodes-$$" mkdir -p "$WORK_DIR" cleanup() { rm -rf "$WORK_DIR"; } trap cleanup EXIT # Container IP mapping declare -A CONTAINER_IPS=( [106]="192.168.11.13" # besu-validator-1 [107]="192.168.11.14" # besu-validator-2 [108]="192.168.11.15" # besu-validator-3 [109]="192.168.11.16" # besu-validator-4 [110]="192.168.11.18" # besu-validator-5 [111]="192.168.11.19" # besu-sentry-2 [112]="192.168.11.20" # besu-sentry-3 [113]="192.168.11.21" # besu-sentry-4 [114]="192.168.11.22" # besu-sentry-5 [115]="192.168.11.23" # besu-rpc-1 [116]="192.168.11.24" # besu-rpc-2 [117]="192.168.11.25" # besu-rpc-3 ) # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[✓]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARNING]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } # Extract and fix enodes from a container's current static-nodes.json extract_and_fix_enodes() { local vmid="$1" local container_ip="${CONTAINER_IPS[$vmid]}" log_info "Processing container $vmid ($container_ip)..." # Get current static-nodes.json local current_json current_json=$(ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "pct exec $vmid -- cat /etc/besu/static-nodes.json 2>/dev/null" || echo '[]') if [[ "$current_json" == "[]" ]] || [[ -z "$current_json" ]]; then log_warn "Container $vmid: No static-nodes.json found" return 1 fi # Extract node IDs (first 128 chars) and create corrected enodes python3 << PYEOF import json import re try: static_nodes = json.loads('''$current_json''') except: static_nodes = [] fixed_enodes = [] node_ids_seen = set() for i, enode in enumerate(static_nodes): # Extract hex part match = re.search(r'enode://([a-fA-F0-9]+)@', enode) if not match: continue full_hex = match.group(1).lower() # Take first 128 chars (removes trailing zeros padding) node_id = full_hex[:128] if len(node_id) != 128: print(f"SKIP: Node ID {i+1} has length {len(node_id)}, not 128", file=sys.stderr) continue # Validate hex if not re.match(r'^[0-9a-f]{128}$', node_id): print(f"SKIP: Node ID {i+1} contains invalid hex", file=sys.stderr) continue # Map first 5 to validator IPs (106-110), others keep original IP for now if i < 5: vmid_map = 106 + i if vmid_map in ${!CONTAINER_IPS[@]}: ip = "${CONTAINER_IPS[$vmid_map]}" else: # Extract original IP ip_match = re.search(r'@([0-9.]+):', enode) ip = ip_match.group(1) if ip_match else "$container_ip" else: # Extract original IP ip_match = re.search(r'@([0-9.]+):', enode) ip = ip_match.group(1) if ip_match else "$container_ip" fixed_enode = f"enode://{node_id}@{ip}:30303" # Avoid duplicates if node_id not in node_ids_seen: fixed_enodes.append(fixed_enode) node_ids_seen.add(node_id) # Save to file with open('$WORK_DIR/static-nodes-${vmid}.json', 'w') as f: json.dump(fixed_enodes, f, indent=2) print(f"Fixed {len(fixed_enodes)} enode URLs for container $vmid") PYEOF return 0 } # Generate corrected permissions-nodes.toml from all containers generate_permissions_toml() { log_info "Generating corrected permissions-nodes.toml..." # Collect all unique enodes from all containers python3 << 'PYEOF' import json import re import glob all_enodes = set() # Read all fixed static-nodes.json files for json_file in glob.glob('$WORK_DIR/static-nodes-*.json'): try: with open(json_file, 'r') as f: enodes = json.load(f) for enode in enodes: all_enodes.add(enode) except: pass # Sort for consistency sorted_enodes = sorted(all_enodes) # Generate TOML toml_content = """# Node Permissioning Configuration # Lists nodes that are allowed to connect to this node # Generated from actual container enodes (first 128 chars of node IDs) # All validators, sentries, and RPC nodes are included nodes-allowlist=[ """ for enode in sorted_enodes: toml_content += f' "{enode}",\n' # Remove trailing comma toml_content = toml_content.rstrip(',\n') + '\n]' with open('$WORK_DIR/permissions-nodes.toml', 'w') as f: f.write(toml_content) print(f"Generated permissions-nodes.toml with {len(sorted_enodes)} unique nodes") PYEOF log_success "Generated permissions-nodes.toml" } # Deploy corrected files deploy_files() { log_info "Deploying corrected files to all containers..." # First, copy permissions-nodes.toml to host scp -o StrictHostKeyChecking=accept-new \ "$WORK_DIR/permissions-nodes.toml" \ "root@${PROXMOX_HOST}:/tmp/permissions-nodes-fixed.toml" for vmid in 106 107 108 109 110 111 112 113 114 115 116 117; do if ! ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "pct status $vmid 2>/dev/null | grep -q running"; then log_warn "Container $vmid: not running, skipping" continue fi log_info "Deploying to container $vmid..." # Copy static-nodes.json (container-specific) if [[ -f "$WORK_DIR/static-nodes-${vmid}.json" ]]; then scp -o StrictHostKeyChecking=accept-new \ "$WORK_DIR/static-nodes-${vmid}.json" \ "root@${PROXMOX_HOST}:/tmp/static-nodes-${vmid}.json" ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" << REMOTE_SCRIPT pct push $vmid /tmp/static-nodes-${vmid}.json /etc/besu/static-nodes.json pct exec $vmid -- chown besu:besu /etc/besu/static-nodes.json rm -f /tmp/static-nodes-${vmid}.json REMOTE_SCRIPT fi # Copy permissions-nodes.toml (same for all) ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" << REMOTE_SCRIPT pct push $vmid /tmp/permissions-nodes-fixed.toml /etc/besu/permissions-nodes.toml pct exec $vmid -- chown besu:besu /etc/besu/permissions-nodes.toml REMOTE_SCRIPT log_success "Container $vmid: files deployed" done # Cleanup ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "rm -f /tmp/permissions-nodes-fixed.toml" } # Main main() { echo "╔════════════════════════════════════════════════════════════════╗" echo "║ FIX ENODE CONFIGS (EXTRACT FIRST 128 CHARS) ║" echo "╚════════════════════════════════════════════════════════════════╝" echo "" # Process all containers for vmid in 106 107 108 109 110 111 112 113 114 115 116 117; do extract_and_fix_enodes "$vmid" || true done generate_permissions_toml echo "" log_info "Preview of generated files:" echo "" echo "=== static-nodes.json (container 106) ===" cat "$WORK_DIR/static-nodes-106.json" 2>/dev/null | head -10 || echo "Not generated" echo "" echo "=== permissions-nodes.toml (first 20 lines) ===" cat "$WORK_DIR/permissions-nodes.toml" | head -20 echo "" read -p "Deploy corrected files to all containers? [y/N]: " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then deploy_files log_success "All files deployed!" log_info "Next: Restart Besu services on all containers" else log_info "Files available in $WORK_DIR for review" fi } main "$@"