224 lines
10 KiB
Python
224 lines
10 KiB
Python
|
|
#!/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('')
|