#!/usr/bin/env python3 """ Compare endpoints from JSON export with NPMplus configuration Identifies missing domains, mismatches, and discrepancies """ import json import os from urllib.parse import urlparse from collections import defaultdict # Get script directory and project root SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) PROJECT_ROOT = os.path.dirname(SCRIPT_DIR) # Load endpoints JSON endpoints_file = os.path.join(PROJECT_ROOT, 'reports', 'endpoints-export.json') with open(endpoints_file, 'r') as f: endpoints_data = json.load(f) # NPMplus configuration from configure-npmplus-domains.js NPMPLUS_DOMAINS = [ # sankofa.nexus zone (placeholder - routes to Blockscout) {'domain': 'sankofa.nexus', 'target': 'http://192.168.11.140:80', 'websocket': False}, {'domain': 'phoenix.sankofa.nexus', 'target': 'http://192.168.11.140:80', 'websocket': False}, {'domain': 'the-order.sankofa.nexus', 'target': 'http://192.168.11.140:80', 'websocket': False}, # d-bis.org zone - RPC endpoints (UPDATED IPs) {'domain': 'explorer.d-bis.org', 'target': 'http://192.168.11.140:4000', 'websocket': False}, {'domain': 'rpc-http-pub.d-bis.org', 'target': 'http://192.168.11.221:8545', 'websocket': True}, {'domain': 'rpc-ws-pub.d-bis.org', 'target': 'http://192.168.11.221:8546', 'websocket': True}, {'domain': 'rpc-http-prv.d-bis.org', 'target': 'http://192.168.11.211:8545', 'websocket': True}, {'domain': 'rpc-ws-prv.d-bis.org', 'target': 'http://192.168.11.211:8546', 'websocket': True}, {'domain': 'dbis-admin.d-bis.org', 'target': 'http://192.168.11.130:80', 'websocket': False}, {'domain': 'dbis-api.d-bis.org', 'target': 'http://192.168.11.155:3000', 'websocket': False}, {'domain': 'dbis-api-2.d-bis.org', 'target': 'http://192.168.11.156:3000', 'websocket': False}, {'domain': 'secure.d-bis.org', 'target': 'http://192.168.11.130:80', 'websocket': False}, # mim4u.org zone {'domain': 'mim4u.org', 'target': 'http://192.168.11.36:80', 'websocket': False}, {'domain': 'secure.mim4u.org', 'target': 'http://192.168.11.36:80', 'websocket': False}, {'domain': 'training.mim4u.org', 'target': 'http://192.168.11.36:80', 'websocket': False}, # defi-oracle.io zone - ThirdWeb RPC {'domain': 'rpc.public-0138.defi-oracle.io', 'target': 'https://192.168.11.240:443', 'websocket': True}, ] # Extract domains from endpoints JSON # Note: domain field might contain "Running"/"Stopped" - we need to check the actual domain mappings endpoint_domains_map = defaultdict(list) for endpoint in endpoints_data: domain = endpoint.get('domain', '').strip() # Skip if domain is empty or contains "Running"/"Stopped" if domain and domain not in ['Running', 'Stopped']: domains = [d.strip() for d in domain.split(',') if d.strip() and d.strip() not in ['Running', 'Stopped']] for dom in domains: endpoint_domains_map[dom].append({ 'vmid': endpoint['vmid'], 'ip': endpoint['ip'], 'port': endpoint['port'], 'protocol': endpoint['protocol'], 'service': endpoint['service'], 'status': endpoint['status'], 'endpoint': endpoint['endpoint'] }) # Also map by IP:Port to find potential matches even without domain ip_port_to_endpoints = defaultdict(list) for endpoint in endpoints_data: key = f"{endpoint['ip']}:{endpoint['port']}" ip_port_to_endpoints[key].append(endpoint) # Parse NPMplus targets npmplus_map = {} for config in NPMPLUS_DOMAINS: url = urlparse(config['target']) npmplus_map[config['domain']] = { 'target': config['target'], 'ip': url.hostname, 'port': url.port or (443 if url.scheme == 'https' else 80), 'protocol': url.scheme, 'websocket': config['websocket'] } # Compare comparison = { 'matches': [], 'mismatches': [], 'missing_in_npmplus': [], 'missing_in_endpoints': [], 'notes': [] } # Check domains from NPMplus against endpoints for domain, npmplus_config in npmplus_map.items(): target_key = f"{npmplus_config['ip']}:{npmplus_config['port']}" matching_endpoints = ip_port_to_endpoints.get(target_key, []) # Find endpoint that matches this domain domain_endpoints = endpoint_domains_map.get(domain, []) if domain_endpoints: # Domain found in endpoints - check if it matches matched = False for ep in domain_endpoints: if ep['ip'] == npmplus_config['ip'] and ep['port'] == str(npmplus_config['port']): comparison['matches'].append({ 'domain': domain, 'npmplus': npmplus_config, 'endpoint': ep }) matched = True break if not matched: comparison['mismatches'].append({ 'domain': domain, 'npmplus': npmplus_config, 'endpoints': domain_endpoints, 'issue': f"Domain exists but IP/Port mismatch: NPMplus targets {npmplus_config['ip']}:{npmplus_config['port']}" }) else: # Domain not in endpoints JSON, but check if IP:Port exists if matching_endpoints: comparison['matches'].append({ 'domain': domain, 'npmplus': npmplus_config, 'endpoint': matching_endpoints[0], 'note': 'Domain not explicitly in endpoints JSON but IP:Port matches' }) else: comparison['missing_in_endpoints'].append({ 'domain': domain, 'npmplus': npmplus_config }) # Check endpoints that have domains but aren't in NPMplus for domain, endpoints in endpoint_domains_map.items(): if domain not in npmplus_map: comparison['missing_in_npmplus'].append({ 'domain': domain, 'endpoints': endpoints }) # Generate report print('') print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print('📊 Endpoints vs NPMplus Comparison Report') print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print('') print(f'📋 Summary:') print(f' ✅ Matches: {len(comparison["matches"])}') print(f' ⚠️ Mismatches: {len(comparison["mismatches"])}') print(f' ❌ Missing in NPMplus: {len(comparison["missing_in_npmplus"])}') print(f' ❌ Missing in Endpoints: {len(comparison["missing_in_endpoints"])}') print('') # Matches if comparison['matches']: print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print(f'✅ MATCHES ({len(comparison["matches"])})') print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print('') for match in comparison['matches']: note = f" ({match.get('note', '')})" if match.get('note') else '' print(f' {match["domain"]}{note}') print(f' NPMplus: {match["npmplus"]["target"]} {"(WebSocket ✓)" if match["npmplus"]["websocket"] else ""}') print(f' Endpoint: {match["endpoint"]["endpoint"]} (VMID {match["endpoint"]["vmid"]}, {match["endpoint"]["service"]})') print('') # Mismatches if comparison['mismatches']: print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print(f'⚠️ MISMATCHES ({len(comparison["mismatches"])})') print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print('') for mismatch in comparison['mismatches']: print(f' {mismatch["domain"]}') print(f' NPMplus: {mismatch["npmplus"]["target"]}') print(f' Issue: {mismatch["issue"]}') print(f' Available endpoints:') for ep in mismatch['endpoints']: print(f' - {ep["endpoint"]} (VMID {ep["vmid"]}, {ep["service"]}, {ep["status"]})') print('') # Missing in NPMplus if comparison['missing_in_npmplus']: print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print(f'❌ MISSING IN NPMPLUS ({len(comparison["missing_in_npmplus"])})') print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print('') print(' These domains exist in endpoints but are not configured in NPMplus:') print('') for missing in comparison['missing_in_npmplus']: print(f' {missing["domain"]}') for ep in missing['endpoints']: print(f' - {ep["endpoint"]} (VMID {ep["vmid"]}, {ep["service"]}, {ep["status"]})') print('') # Missing in Endpoints if comparison['missing_in_endpoints']: print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print(f'❌ MISSING IN ENDPOINTS ({len(comparison["missing_in_endpoints"])})') print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print('') print(' These domains are configured in NPMplus but not found in endpoints:') print('') for missing in comparison['missing_in_endpoints']: print(f' {missing["domain"]}') print(f' NPMplus target: {missing["npmplus"]["target"]}') print('') # Export JSON comparison_file = os.path.join(PROJECT_ROOT, 'reports', 'endpoints-npmplus-comparison.json') with open(comparison_file, 'w') as f: json.dump(comparison, f, indent=2) print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print('📄 Detailed comparison saved to: reports/endpoints-npmplus-comparison.json') print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') print('')