#!/usr/bin/env bash # Fix enode URLs using Besu's built-in commands # This script generates correct enode URLs from actual Besu node keys set -euo pipefail # 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"; } PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}" WORK_DIR="/tmp/besu-enode-fix-$$" mkdir -p "$WORK_DIR" cleanup() { rm -rf "$WORK_DIR" } trap cleanup EXIT # Extract enode from a running Besu node using admin_nodeInfo extract_enode_from_running_node() { local vmid="$1" local ip="${CONTAINER_IPS[$vmid]}" log_info "Extracting enode from container $vmid ($ip)..." # Try to get enode via RPC (works if RPC is enabled) local enode enode=$(ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "pct exec $vmid -- curl -s -X POST --data '{\"jsonrpc\":\"2.0\",\"method\":\"admin_nodeInfo\",\"params\":[],\"id\":1}' -H 'Content-Type: application/json' http://localhost:8545 2>/dev/null" | \ python3 -c "import sys, json; data = json.load(sys.stdin); print(data.get('result', {}).get('enode', ''))" 2>/dev/null || echo "") if [[ -n "$enode" && "$enode" != "null" && "$enode" != "" ]]; then echo "$enode" return 0 fi log_warn "Could not get enode via RPC for container $vmid, trying alternative method..." return 1 } # Extract node key path and generate enode using Besu CLI extract_enode_from_nodekey() { local vmid="$1" local ip="${CONTAINER_IPS[$vmid]}" log_info "Extracting enode from nodekey for container $vmid ($ip)..." # Find nodekey file (Besu stores it in data directory) local nodekey_path nodekey_path=$(ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "pct exec $vmid -- find /data/besu -name 'nodekey*' -o -name 'key.priv' 2>/dev/null | head -1" || echo "") if [[ -z "$nodekey_path" ]]; then # Try common locations for path in "/data/besu/key" "/data/besu/nodekey" "/keys/besu/nodekey"; do if ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" "pct exec $vmid -- test -f $path 2>/dev/null"; then nodekey_path="$path" break fi done fi if [[ -z "$nodekey_path" ]]; then log_error "Nodekey not found for container $vmid" return 1 fi log_info "Found nodekey at: $nodekey_path" # Use Besu to export public key in enode format local enode enode=$(ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "pct exec $vmid -- /opt/besu/bin/besu public-key export --node-private-key-file=\"$nodekey_path\" --format=enode 2>/dev/null" || echo "") if [[ -n "$enode" ]]; then # Replace IP in enode with actual container IP enode=$(echo "$enode" | sed "s/@[0-9.]*:/@${ip}:/") echo "$enode" return 0 fi log_error "Failed to generate enode from nodekey for container $vmid" return 1 } # Validate enode format validate_enode() { local enode="$1" # Extract node ID part local node_id node_id=$(echo "$enode" | sed 's|^enode://||' | cut -d'@' -f1 | tr '[:upper:]' '[:lower:]') # Check length if [[ ${#node_id} -ne 128 ]]; then log_error "Invalid enode: node ID length is ${#node_id}, expected 128" return 1 fi # Check it's valid hex if ! echo "$node_id" | grep -qE '^[0-9a-f]{128}$'; then log_error "Invalid enode: node ID contains non-hex characters" return 1 fi log_success "Enode validated: ${node_id:0:16}...${node_id: -16} (128 chars)" return 0 } # Collect all enodes from containers collect_enodes() { log_info "Collecting enodes from all containers..." echo "" > "$WORK_DIR/enodes.txt" local validators=() local sentries=() local rpcs=() for vmid in 106 107 108 109 110; do local hostname ip enode hostname=$(ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "pct config $vmid 2>/dev/null | grep '^hostname:' | cut -d' ' -f2" || echo "unknown") ip="${CONTAINER_IPS[$vmid]}" # Try running node first, then nodekey enode=$(extract_enode_from_running_node "$vmid" 2>/dev/null || \ extract_enode_from_nodekey "$vmid" 2>/dev/null || echo "") if [[ -n "$enode" ]]; then if validate_enode "$enode"; then echo "$vmid|$hostname|$ip|$enode|validator" >> "$WORK_DIR/enodes.txt" validators+=("$enode") log_success "Container $vmid ($hostname): enode extracted" else log_error "Container $vmid: invalid enode format" fi else log_warn "Container $vmid: could not extract enode" fi done for vmid in 111 112 113 114; do local hostname ip enode hostname=$(ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "pct config $vmid 2>/dev/null | grep '^hostname:' | cut -d' ' -f2" || echo "unknown") ip="${CONTAINER_IPS[$vmid]}" enode=$(extract_enode_from_running_node "$vmid" 2>/dev/null || \ extract_enode_from_nodekey "$vmid" 2>/dev/null || echo "") if [[ -n "$enode" ]]; then if validate_enode "$enode"; then echo "$vmid|$hostname|$ip|$enode|sentry" >> "$WORK_DIR/enodes.txt" sentries+=("$enode") log_success "Container $vmid ($hostname): enode extracted" fi fi done for vmid in 115 116 117; do local hostname ip enode hostname=$(ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" \ "pct config $vmid 2>/dev/null | grep '^hostname:' | cut -d' ' -f2" || echo "unknown") ip="${CONTAINER_IPS[$vmid]}" enode=$(extract_enode_from_running_node "$vmid" 2>/dev/null || \ extract_enode_from_nodekey "$vmid" 2>/dev/null || echo "") if [[ -n "$enode" ]]; then if validate_enode "$enode"; then echo "$vmid|$hostname|$ip|$enode|rpc" >> "$WORK_DIR/enodes.txt" rpcs+=("$enode") log_success "Container $vmid ($hostname): enode extracted" fi fi done log_info "Collected ${#validators[@]} validator enodes, ${#sentries[@]} sentry enodes, ${#rpcs[@]} RPC enodes" } # Generate corrected static-nodes.json (validators only) generate_static_nodes_json() { log_info "Generating corrected static-nodes.json..." python3 << PYEOF import json # Read collected enodes enodes = [] with open('$WORK_DIR/enodes.txt', 'r') as f: for line in f: line = line.strip() if not line: continue parts = line.split('|') if len(parts) >= 4 and parts[4] == 'validator': enodes.append(parts[3]) # enode URL # Generate JSON static_nodes = sorted(set(enodes)) # Remove duplicates, sort with open('$WORK_DIR/static-nodes.json', 'w') as f: json.dump(static_nodes, f, indent=2) print(f"Generated static-nodes.json with {len(static_nodes)} validators") PYEOF log_success "Generated static-nodes.json" } # Generate corrected permissions-nodes.toml (all nodes) generate_permissions_nodes_toml() { log_info "Generating corrected permissions-nodes.toml..." python3 << 'PYEOF' import re # Read collected enodes enodes = [] with open('$WORK_DIR/enodes.txt', 'r') as f: for line in f: line = line.strip() if not line: continue parts = line.split('|') if len(parts) >= 4: enodes.append(parts[3]) # enode URL # Remove duplicates and sort enodes = sorted(set(enodes)) # Generate TOML toml_content = """# Node Permissioning Configuration # Lists nodes that are allowed to connect to this node # Generated using Besu native commands # All validators, sentries, and RPC nodes are included nodes-allowlist=[ """ for enode in 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(enodes)} nodes") PYEOF log_success "Generated permissions-nodes.toml" } # Deploy corrected files to containers deploy_files() { log_info "Deploying corrected files to containers..." # Copy static-nodes.json to all containers 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_info "Deploying to container $vmid..." scp -o StrictHostKeyChecking=accept-new "$WORK_DIR/static-nodes.json" \ "root@${PROXMOX_HOST}:/tmp/static-nodes-${vmid}.json" scp -o StrictHostKeyChecking=accept-new "$WORK_DIR/permissions-nodes.toml" \ "root@${PROXMOX_HOST}:/tmp/permissions-nodes-${vmid}.toml" ssh -o StrictHostKeyChecking=accept-new "root@${PROXMOX_HOST}" << REMOTE_SCRIPT pct push $vmid /tmp/static-nodes-${vmid}.json /etc/besu/static-nodes.json pct push $vmid /tmp/permissions-nodes-${vmid}.toml /etc/besu/permissions-nodes.toml pct exec $vmid -- chown besu:besu /etc/besu/static-nodes.json /etc/besu/permissions-nodes.toml rm -f /tmp/static-nodes-${vmid}.json /tmp/permissions-nodes-${vmid}.toml REMOTE_SCRIPT log_success "Container $vmid: files deployed" else log_warn "Container $vmid: not running, skipping" fi done } # Main execution main() { echo "╔════════════════════════════════════════════════════════════════╗" echo "║ FIX ENODE URLs USING BESU NATIVE COMMANDS ║" echo "╚════════════════════════════════════════════════════════════════╝" echo "" collect_enodes generate_static_nodes_json generate_permissions_nodes_toml echo "" log_info "Preview of generated files:" echo "" echo "=== static-nodes.json ===" cat "$WORK_DIR/static-nodes.json" | head -10 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 successfully!" log_info "Next step: Restart Besu services on all containers" else log_info "Files are in $WORK_DIR (will be cleaned up on exit)" log_info "Review them and run deployment manually if needed" fi } main "$@"