#!/usr/bin/env python3 """ Interactive CLI for configuring Besu network Configures genesis.json and all configuration files with necessary details """ import json import os import sys import shutil from pathlib import Path from typing import Dict, Any, List, Optional, Tuple import subprocess import re # Import validation module sys.path.insert(0, str(Path(__file__).parent)) try: from configure_network_validation import ConfigurationValidator, ValidationError except ImportError: # Fallback if validation module not available class ConfigurationValidator: def __init__(self, config, project_root): self.config = config self.project_root = project_root def validate_all(self): return True, [], [] class ValidationError(Exception): pass # Color codes for terminal output class Colors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def print_header(text: str): """Print a header""" print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}") print(f"{Colors.HEADER}{Colors.BOLD}{text:^60}{Colors.ENDC}") print(f"{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}\n") def print_success(text: str): """Print success message""" print(f"{Colors.OKGREEN}✓ {text}{Colors.ENDC}") def print_error(text: str): """Print error message""" print(f"{Colors.FAIL}✗ {text}{Colors.ENDC}") def print_warning(text: str): """Print warning message""" print(f"{Colors.WARNING}⚠ {text}{Colors.ENDC}") def print_info(text: str): """Print info message""" print(f"{Colors.OKCYAN}ℹ {text}{Colors.ENDC}") def input_with_default(prompt: str, default: str = "", validate_func=None) -> str: """Get input with default value""" if default: full_prompt = f"{prompt} [{default}]: " else: full_prompt = f"{prompt}: " while True: value = input(full_prompt).strip() if not value: value = default if not value: print_error("This field is required") continue if validate_func and not validate_func(value): continue return value def input_int(prompt: str, default: int = 0, min_val: int = None, max_val: int = None) -> int: """Get integer input with validation""" while True: try: value = input_with_default(prompt, str(default) if default else "") int_value = int(value) if min_val is not None and int_value < min_val: print_error(f"Value must be >= {min_val}") continue if max_val is not None and int_value > max_val: print_error(f"Value must be <= {max_val}") continue return int_value except ValueError: print_error("Please enter a valid integer") def input_hex(prompt: str, default: str = "") -> str: """Get hexadecimal input with validation""" while True: value = input_with_default(prompt, default) if value.startswith("0x"): value = value[2:] try: int(value, 16) return f"0x{value}" except ValueError: print_error("Please enter a valid hexadecimal value") def input_yes_no(prompt: str, default: bool = True) -> bool: """Get yes/no input""" default_str = "Y/n" if default else "y/N" while True: value = input(f"{prompt} [{default_str}]: ").strip().lower() if not value: return default if value in ['y', 'yes']: return True if value in ['n', 'no']: return False print_error("Please enter 'y' or 'n'") def validate_ip(ip: str) -> bool: """Validate IP address""" parts = ip.split('.') if len(parts) != 4: return False try: return all(0 <= int(part) <= 255 for part in parts) except ValueError: return False def validate_cidr(cidr: str) -> bool: """Validate CIDR notation""" try: ip, mask = cidr.split('/') if not validate_ip(ip): return False mask = int(mask) return 0 <= mask <= 32 except (ValueError, AttributeError): return False def validate_port(port: str) -> bool: """Validate port number""" try: port_num = int(port) return 1 <= port_num <= 65535 except ValueError: return False def validate_chain_id(chain_id: str) -> bool: """Validate chain ID""" try: cid = int(chain_id) return 1 <= cid <= 2147483647 except ValueError: return False class NetworkConfigurator: def __init__(self, project_root: Path): self.project_root = project_root self.config = {} self.backup_dir = project_root / ".config-backup" def backup_existing_files(self): """Backup existing configuration files""" if self.backup_dir.exists(): shutil.rmtree(self.backup_dir) self.backup_dir.mkdir(parents=True, exist_ok=True) files_to_backup = [ "config/genesis.json", "config/validators/besu-config.toml", "config/sentries/besu-config.toml", "config/rpc/besu-config.toml", "terraform/terraform.tfvars", "helm/besu-network/values.yaml", ] for file_path in files_to_backup: source = self.project_root / file_path if source.exists(): dest = self.backup_dir / file_path dest.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source, dest) print_success(f"Backed up {file_path}") def collect_genesis_config(self): """Collect genesis block configuration""" print_header("Genesis Block Configuration") self.config['genesis'] = { 'chainId': input_int("Chain ID", 138, 1, 2147483647), 'blockPeriodSeconds': input_int("Block period (seconds)", 2, 1, 60), 'epochLength': input_int("Epoch length (blocks)", 30000, 1000, 1000000), 'requestTimeoutSeconds': input_int("Request timeout (seconds)", 10, 1, 60), 'gasLimit': input_hex("Gas limit", "0x1c9c380"), 'difficulty': input_hex("Difficulty", "0x1"), 'timestamp': input_hex("Timestamp", "0x0"), } # IBFT2 validators print_info("\nIBFT2 Validator Configuration") validator_count = input_int("Number of validators", 4, 1, 100) self.config['validators'] = [] for i in range(validator_count): print_info(f"\nValidator {i+1}:") validator_address = input_hex(f" Validator address (hex)", "0x0") self.config['validators'].append(validator_address) # Pre-allocated accounts print_info("\nPre-allocated Accounts") alloc_count = input_int("Number of pre-allocated accounts", 4, 0, 100) self.config['alloc'] = {} for i in range(alloc_count): address = input_hex(f" Account {i+1} address", f"0x{'0'*39}{i+1}") # Decision tree: Validate address format if not re.match(r'^0x[a-fA-F0-9]{40}$', address): print_error(f"Invalid address format: {address}") address = input_hex(f" Account {i+1} address (must be 40 hex characters)", f"0x{'0'*39}{i+1}") balance = input_hex(f" Account {i+1} balance (wei)", "0x1") # Decision tree: Validate balance try: balance_int = int(balance, 16) if balance_int == 0: print_warning(f"Account {i+1} has zero balance. Consider adding balance or removing account.") except ValueError: print_error(f"Invalid balance format: {balance}") balance = input_hex(f" Account {i+1} balance (wei, valid hex)", "0x1") self.config['alloc'][address] = {'balance': balance} print_success("Genesis configuration collected") def collect_network_config(self): """Collect network configuration""" print_header("Network Configuration") self.config['network'] = { 'clusterName': input_with_default("Cluster name", "defi-oracle-aks"), 'resourceGroup': input_with_default("Azure resource group", "defi-oracle-mainnet-rg"), 'location': input_with_default("Azure region", "eastus"), 'vnetAddressSpace': input_with_default("VNet address space (CIDR)", "10.0.0.0/16", validate_cidr), } # Subnets print_info("\nSubnet Configuration") self.config['network']['subnets'] = { 'validators': input_with_default("Validators subnet (CIDR)", "10.0.1.0/24", validate_cidr), 'sentries': input_with_default("Sentries subnet (CIDR)", "10.0.2.0/24", validate_cidr), 'rpc': input_with_default("RPC subnet (CIDR)", "10.0.3.0/24", validate_cidr), 'aks': input_with_default("AKS subnet (CIDR)", "10.0.4.0/24", validate_cidr), } # Node counts print_info("\nNode Configuration") self.config['nodes'] = { 'validatorCount': input_int("Number of validators", 4, 1, 100), 'sentryCount': input_int("Number of sentries", 3, 1, 100), 'rpcCount': input_int("Number of RPC nodes", 3, 1, 100), } # VM sizes print_info("\nVM Size Configuration") self.config['vmSizes'] = { 'validator': input_with_default("Validator VM size", "Standard_D4s_v3"), 'sentry': input_with_default("Sentry VM size", "Standard_D4s_v3"), 'rpc': input_with_default("RPC VM size", "Standard_D8s_v3"), } # Ports print_info("\nPort Configuration") self.config['ports'] = { 'p2p': input_int("P2P port", 30303, 1, 65535), 'rpcHttp': input_int("RPC HTTP port", 8545, 1, 65535), 'rpcWs': input_int("RPC WebSocket port", 8546, 1, 65535), 'metrics': input_int("Metrics port", 9545, 1, 65535), } print_success("Network configuration collected") def collect_besu_config(self): """Collect Besu node configuration""" print_header("Besu Node Configuration") # Validator configuration print_info("Validator Node Configuration") # Decision tree: Validators should not have RPC enabled validator_rpc_enabled = input_yes_no("Enable RPC on validators? (NOT RECOMMENDED for security)", False) if validator_rpc_enabled: print_warning("Security: Validators with RPC enabled are exposed to attacks. Not recommended for production!") if not input_yes_no("Continue with RPC enabled on validators?", False): validator_rpc_enabled = False self.config['besu'] = { 'validators': { 'syncMode': input_with_default("Sync mode", "FULL", lambda x: x in ['FULL', 'FAST', 'SNAP']), 'rpcHttpEnabled': validator_rpc_enabled, 'rpcHttpHost': "127.0.0.1" if validator_rpc_enabled else "0.0.0.0", 'rpcHttpPort': 8545, 'p2pPort': self.config['ports']['p2p'], 'p2pEnabled': True, 'metricsEnabled': True, 'metricsPort': self.config['ports']['metrics'], }, 'sentries': { 'syncMode': input_with_default("Sentry sync mode", "FULL", lambda x: x in ['FULL', 'FAST', 'SNAP']), 'rpcHttpEnabled': True, 'rpcHttpHost': "127.0.0.1", 'rpcHttpPort': 8545, 'p2pPort': self.config['ports']['p2p'], 'p2pEnabled': True, 'metricsEnabled': True, 'metricsPort': self.config['ports']['metrics'], }, 'rpc': { 'syncMode': input_with_default("RPC sync mode", "SNAP", lambda x: x in ['FULL', 'FAST', 'SNAP']), 'rpcHttpEnabled': True, 'rpcHttpHost': "0.0.0.0", 'rpcHttpPort': self.config['ports']['rpcHttp'], 'rpcWsEnabled': input_yes_no("Enable WebSocket on RPC nodes?", True), 'rpcWsPort': self.config['ports']['rpcWs'], 'rpcWsHost': "0.0.0.0", 'p2pEnabled': False, # RPC nodes should not have P2P 'metricsEnabled': True, 'metricsPort': self.config['ports']['metrics'], } } # Decision tree: RPC nodes should not have P2P rpc_p2p_enabled = input_yes_no("Enable P2P on RPC nodes? (NOT RECOMMENDED for security)", False) if rpc_p2p_enabled: print_warning("Security: RPC nodes with P2P enabled are exposed to network attacks. Not recommended!") if not input_yes_no("Continue with P2P enabled on RPC nodes?", False): rpc_p2p_enabled = False self.config['besu']['rpc']['p2pEnabled'] = rpc_p2p_enabled # CORS and security print_info("\nSecurity Configuration") enable_cors = input_yes_no("Enable CORS", False) if enable_cors: cors_origins_input = input_with_default("CORS origins (comma-separated)", "https://yourdomain.com") cors_origins = [origin.strip() for origin in cors_origins_input.split(',')] # Decision tree: Warn about wildcard CORS if '*' in cors_origins: print_warning("CORS wildcard '*' allows all origins. Security risk in production!") if not input_yes_no("Continue with wildcard CORS?", False): cors_origins_input = input_with_default("CORS origins (comma-separated, no wildcards)", "https://yourdomain.com") cors_origins = [origin.strip() for origin in cors_origins_input.split(',')] cors_origins = [o for o in cors_origins if o != '*'] # Validate CORS origins format for origin in cors_origins: if origin and not (origin.startswith('http://') or origin.startswith('https://')): print_warning(f"CORS origin '{origin}' should include protocol (http:// or https://)") if input_yes_no(f"Add https:// to '{origin}'?", True): cors_origins[cors_origins.index(origin)] = f"https://{origin}" self.config['besu']['rpc']['corsOrigins'] = cors_origins else: self.config['besu']['rpc']['corsOrigins'] = [] # Host allowlist enable_host_allowlist = input_yes_no("Enable host allowlist", False) if enable_host_allowlist: host_allowlist_input = input_with_default("Host allowlist (comma-separated)", "localhost,127.0.0.1") host_allowlist = [host.strip() for host in host_allowlist_input.split(',')] # Decision tree: Warn about wildcard host allowlist if '0.0.0.0' in host_allowlist or '*' in host_allowlist: print_warning("Host allowlist '0.0.0.0' or '*' allows all hosts. Security risk in production!") if not input_yes_no("Continue with wildcard host allowlist?", False): host_allowlist_input = input_with_default("Host allowlist (comma-separated, no wildcards)", "localhost,127.0.0.1") host_allowlist = [host.strip() for host in host_allowlist_input.split(',')] host_allowlist = [h for h in host_allowlist if h not in ['0.0.0.0', '*']] self.config['besu']['rpc']['hostAllowlist'] = host_allowlist else: # Decision tree: Warn if RPC enabled without restrictions if self.config['besu']['rpc']['rpcHttpEnabled']: print_warning("RPC enabled without CORS or host restrictions. Security risk!") if input_yes_no("Add host allowlist for security?", True): host_allowlist_input = input_with_default("Host allowlist (comma-separated)", "localhost,127.0.0.1") host_allowlist = [host.strip() for host in host_allowlist_input.split(',')] self.config['besu']['rpc']['hostAllowlist'] = host_allowlist else: self.config['besu']['rpc']['hostAllowlist'] = [] else: self.config['besu']['rpc']['hostAllowlist'] = [] # JVM options print_info("\nJVM Configuration") self.config['besu']['jvmOptions'] = { 'validator': input_with_default("Validator JVM options", "-Xmx4g -Xms4g"), 'sentry': input_with_default("Sentry JVM options", "-Xmx4g -Xms4g"), 'rpc': input_with_default("RPC JVM options", "-Xmx8g -Xms8g"), } print_success("Besu configuration collected") def collect_deployment_config(self): """Collect deployment configuration""" print_header("Deployment Configuration") deployment_type = input_with_default("Deployment type (aks/vm/both)", "both", lambda x: x in ['aks', 'vm', 'both']) # Decision tree: Deployment type selection if deployment_type == 'vm': print_info("VM deployment: Simpler setup, lower cost, but manual scaling required.") elif deployment_type == 'aks': print_info("AKS deployment: Kubernetes orchestration, auto-scaling, but higher cost and complexity.") else: print_info("Both AKS and VM: Maximum flexibility, but higher cost and complexity.") self.config['deployment'] = { 'type': deployment_type, 'aksEnabled': deployment_type in ['aks', 'both'], 'vmEnabled': deployment_type in ['vm', 'both'], } if self.config['deployment']['vmEnabled']: print_info("\nVM Deployment Configuration") use_vmss = input_yes_no("Use VM Scale Sets (recommended for auto-scaling)?", False) if use_vmss: print_info("VM Scale Sets: Automatic scaling, load balancing, but less control over individual VMs.") else: print_info("Individual VMs: Full control, manual scaling, simpler management.") regions_input = input_with_default("Azure regions (comma-separated)", "eastus,westus") regions = [r.strip() for r in regions_input.split(',')] # Decision tree: Warn about too many regions if len(regions) > 5: print_warning(f"Deploying to {len(regions)} regions may be costly. Consider reducing number of regions.") if not input_yes_no("Continue with multiple regions?", True): regions_input = input_with_default("Azure regions (comma-separated, fewer regions)", "eastus") regions = [r.strip() for r in regions_input.split(',')] ssh_key_path = input_with_default("SSH public key path", "~/.ssh/id_rsa.pub") expanded_ssh_key = Path(ssh_key_path).expanduser() # Decision tree: Validate SSH key exists if not expanded_ssh_key.exists(): print_error(f"SSH public key not found: {ssh_key_path}") if input_yes_no("Generate SSH key?", True): # Generate SSH key key_dir = expanded_ssh_key.parent key_dir.mkdir(parents=True, exist_ok=True) key_name = expanded_ssh_key.stem subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '4096', '-f', str(key_dir / key_name), '-N', ''], check=False) print_success(f"Generated SSH key: {expanded_ssh_key}") else: ssh_key_path = input_with_default("SSH public key path", "~/.ssh/id_rsa.pub") self.config['deployment']['vm'] = { 'useVmss': use_vmss, 'regions': regions, 'sshPublicKey': ssh_key_path, } # Decision tree: Warn about large VM deployment total_nodes = self.config['nodes']['validatorCount'] + self.config['nodes']['sentryCount'] + self.config['nodes']['rpcCount'] if total_nodes > 50: print_warning(f"Large VM deployment: {total_nodes} nodes. Consider using VM Scale Sets for cost optimization.") if not use_vmss and input_yes_no("Switch to VM Scale Sets?", True): self.config['deployment']['vm']['useVmss'] = True # Key Vault print_info("\nKey Vault Configuration") self.config['keyVault'] = { 'name': input_with_default("Key Vault name", "defi-oracle-kv"), 'enableSoftDelete': input_yes_no("Enable soft delete", True), 'enablePurgeProtection': input_yes_no("Enable purge protection", True), } # Monitoring print_info("\nMonitoring Configuration") self.config['monitoring'] = { 'enabled': input_yes_no("Enable monitoring", True), 'prometheusEnabled': input_yes_no("Enable Prometheus", True), 'grafanaEnabled': input_yes_no("Enable Grafana", True), 'lokiEnabled': input_yes_no("Enable Loki", True), } # Blockscout print_info("\nBlockscout Configuration") blockscout_enabled = input_yes_no("Enable Blockscout", True) # Decision tree: Blockscout requires RPC if blockscout_enabled: rpc_enabled = self.config['besu']['rpc']['rpcHttpEnabled'] if not rpc_enabled: print_error("Blockscout requires RPC to be enabled. Enable RPC HTTP for RPC nodes first.") if input_yes_no("Enable RPC HTTP on RPC nodes?", True): self.config['besu']['rpc']['rpcHttpEnabled'] = True blockscout_enabled = True else: print_warning("Blockscout disabled because RPC is not enabled.") blockscout_enabled = False self.config['blockscout'] = { 'enabled': blockscout_enabled, 'image': input_with_default("Blockscout image", "blockscout/blockscout:v5.1.5") if blockscout_enabled else "blockscout/blockscout:v5.1.5", } print_success("Deployment configuration collected") def generate_genesis_json(self): """Generate genesis.json file""" genesis = { "config": { "chainId": self.config['genesis']['chainId'], "berlinBlock": 0, "londonBlock": 0, "istanbulBlock": 0, "clique": None, "ibft2": { "blockperiodseconds": self.config['genesis']['blockPeriodSeconds'], "epochlength": self.config['genesis']['epochLength'], "requesttimeoutseconds": self.config['genesis']['requestTimeoutSeconds'] }, "ethash": {} }, "nonce": "0x0", "timestamp": self.config['genesis']['timestamp'], "gasLimit": self.config['genesis']['gasLimit'], "difficulty": self.config['genesis']['difficulty'], "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": self.config['alloc'], "extraData": self._generate_extra_data(), "number": "0x0", "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" } genesis_file = self.project_root / "config" / "genesis.json" genesis_file.parent.mkdir(parents=True, exist_ok=True) with open(genesis_file, 'w') as f: json.dump(genesis, f, indent=2) print_success(f"Generated {genesis_file}") def _generate_extra_data(self) -> str: """Generate extraData for IBFT2""" # For IBFT2, extraData should be generated using Besu's operator generate-blockchain-config # This returns a placeholder - user should run generate-genesis-proper.sh after generating keys print_warning("extraData will be set to 0x - run generate-genesis-proper.sh after generating validator keys") return "0x" def generate_besu_config(self, node_type: str): """Generate Besu configuration file""" config = self.config['besu'][node_type] toml_content = f"""# Besu Configuration for {node_type} nodes # Generated by configure-network.py # Data directory data-path="/data" # Network configuration network-id={self.config['genesis']['chainId']} """ if node_type != 'rpc': toml_content += f"p2p-port={config.get('p2pPort', 30303)}\n" toml_content += f"p2p-enabled={str(config.get('p2pEnabled', True)).lower()}\n" else: toml_content += f"p2p-enabled={str(config.get('p2pEnabled', False)).lower()}\n" toml_content += f""" # Sync mode sync-mode="{config['syncMode']}" # RPC configuration rpc-http-enabled={str(config['rpcHttpEnabled']).lower()} """ if config['rpcHttpEnabled']: toml_content += f'rpc-http-host="{config.get("rpcHttpHost", "0.0.0.0")}"\n' toml_content += f"rpc-http-port={config.get('rpcHttpPort', 8545)}\n" if config.get('rpcWsEnabled'): toml_content += f"rpc-ws-enabled={str(config.get('rpcWsEnabled', False)).lower()}\n" toml_content += f'rpc-ws-host="{config.get("rpcWsHost", "0.0.0.0")}"\n' toml_content += f"rpc-ws-port={config.get('rpcWsPort', 8546)}\n" # CORS and host allowlist (only for RPC nodes) if node_type == 'rpc': if config.get('corsOrigins'): cors_origins = ', '.join([f'"{origin}"' for origin in config['corsOrigins']]) toml_content += f"rpc-http-cors-origins=[{cors_origins}]\n" if config.get('hostAllowlist'): host_allowlist = ', '.join([f'"{host}"' for host in config['hostAllowlist']]) toml_content += f"rpc-http-host-allowlist=[{host_allowlist}]\n" if config.get('rpcWsEnabled') and config.get('corsOrigins'): ws_origins = ', '.join([f'"{origin}"' for origin in config['corsOrigins']]) toml_content += f"rpc-ws-origins=[{ws_origins}]\n" # Permissions (for validators and sentries) if node_type in ['validators', 'sentries']: toml_content += """ # Permissions permissions-nodes-config-file-enabled=true permissions-nodes-config-file="/config/permissions-nodes.toml" """ toml_content += f""" # Metrics configuration metrics-enabled={str(config.get('metricsEnabled', True)).lower()} metrics-port={config.get('metricsPort', 9545)} metrics-host="0.0.0.0" # Genesis file genesis-file="/config/genesis.json" # Logging logging="INFO" log-destination="BOTH" """ config_file = self.project_root / "config" / f"{node_type}s" / "besu-config.toml" config_file.parent.mkdir(parents=True, exist_ok=True) with open(config_file, 'w') as f: f.write(toml_content) print_success(f"Generated {config_file}") def generate_terraform_vars(self): """Generate Terraform variables file""" tfvars = f"""# Terraform variables for Besu network # Generated by configure-network.py # Resource group resource_group_name = "{self.config['network']['resourceGroup']}" location = "{self.config['network']['location']}" # Cluster configuration cluster_name = "{self.config['network']['clusterName']}" kubernetes_version = "1.28" # Node counts node_count = {{ system = 3 validators = {self.config['nodes']['validatorCount']} sentries = {self.config['nodes']['sentryCount']} rpc = {self.config['nodes']['rpcCount']} }} # VM sizes vm_size = {{ system = "Standard_D2s_v3" validators = "{self.config['vmSizes']['validator']}" sentries = "{self.config['vmSizes']['sentry']}" rpc = "{self.config['vmSizes']['rpc']}" }} # VM deployment vm_deployment_enabled = {str(self.config['deployment']['vmEnabled']).lower()} """ if self.config['deployment']['vmEnabled']: tfvars += f""" # VM configuration vm_regions = {self.config['deployment']['vm']['regions']} validator_vm_count = {self.config['nodes']['validatorCount']} sentry_vm_count = {self.config['nodes']['sentryCount']} rpc_vm_count = {self.config['nodes']['rpcCount']} use_vmss = {str(self.config['deployment']['vm']['useVmss']).lower()} ssh_public_key = "$(cat {self.config['deployment']['vm']['sshPublicKey']})" """ tfvars_file = self.project_root / "terraform" / "terraform.tfvars" tfvars_file.parent.mkdir(parents=True, exist_ok=True) with open(tfvars_file, 'w') as f: f.write(tfvars) print_success(f"Generated {tfvars_file}") def generate_helm_values(self): """Generate Helm values file""" values = f"""# Helm values for Besu network # Generated by configure-network.py global: namespace: besu-network chainId: {self.config['genesis']['chainId']} image: repository: hyperledger/besu tag: "23.10.0" pullPolicy: IfNotPresent validators: replicaCount: {self.config['nodes']['validatorCount']} resources: requests: cpu: "2" memory: "4Gi" limits: cpu: "4" memory: "8Gi" jvmOptions: "{self.config['besu']['jvmOptions']['validator']}" sentries: replicaCount: {self.config['nodes']['sentryCount']} resources: requests: cpu: "2" memory: "4Gi" limits: cpu: "4" memory: "8Gi" jvmOptions: "{self.config['besu']['jvmOptions']['sentry']}" rpc: replicaCount: {self.config['nodes']['rpcCount']} resources: requests: cpu: "4" memory: "8Gi" limits: cpu: "8" memory: "16Gi" jvmOptions: "{self.config['besu']['jvmOptions']['rpc']}" """ values_file = self.project_root / "helm" / "besu-network" / "values.yaml" values_file.parent.mkdir(parents=True, exist_ok=True) with open(values_file, 'w') as f: f.write(values) print_success(f"Generated {values_file}") def generate_config_summary(self): """Generate configuration summary""" summary_file = self.project_root / "CONFIG_SUMMARY.md" with open(summary_file, 'w') as f: f.write("# Configuration Summary\n\n") f.write("This file was generated by `configure-network.py`\n\n") f.write("## Genesis Configuration\n\n") f.write(f"- Chain ID: {self.config['genesis']['chainId']}\n") f.write(f"- Block Period: {self.config['genesis']['blockPeriodSeconds']} seconds\n") f.write(f"- Epoch Length: {self.config['genesis']['epochLength']} blocks\n") f.write(f"- Gas Limit: {self.config['genesis']['gasLimit']}\n") f.write(f"- Validators: {len(self.config['validators'])}\n\n") f.write("## Network Configuration\n\n") f.write(f"- Cluster Name: {self.config['network']['clusterName']}\n") f.write(f"- Resource Group: {self.config['network']['resourceGroup']}\n") f.write(f"- Location: {self.config['network']['location']}\n") f.write(f"- VNet Address Space: {self.config['network']['vnetAddressSpace']}\n\n") f.write("## Node Configuration\n\n") f.write(f"- Validators: {self.config['nodes']['validatorCount']}\n") f.write(f"- Sentries: {self.config['nodes']['sentryCount']}\n") f.write(f"- RPC Nodes: {self.config['nodes']['rpcCount']}\n\n") f.write("## Deployment Configuration\n\n") f.write(f"- Deployment Type: {self.config['deployment']['type']}\n") f.write(f"- AKS Enabled: {self.config['deployment']['aksEnabled']}\n") f.write(f"- VM Enabled: {self.config['deployment']['vmEnabled']}\n\n") print_success(f"Generated {summary_file}") def run(self): """Run the configuration process""" try: print_header("Besu Network Configuration Tool") print_info("This tool will help you configure all necessary files for your Besu network.") print_warning("Existing configuration files will be backed up.") if not input_yes_no("Continue?", True): print_info("Configuration cancelled") return # Backup existing files self.backup_existing_files() # Collect configuration self.collect_genesis_config() self.collect_network_config() self.collect_besu_config() self.collect_deployment_config() # Generate files print_header("Generating Configuration Files") self.generate_genesis_json() self.generate_besu_config('validators') self.generate_besu_config('sentries') self.generate_besu_config('rpc') self.generate_terraform_vars() self.generate_helm_values() self.generate_config_summary() print_header("Configuration Complete") print_success("All configuration files have been generated successfully!") print_info(f"Configuration summary: {self.project_root / 'CONFIG_SUMMARY.md'}") print_info(f"Backup directory: {self.backup_dir}") print_warning("Please review the generated files before deploying.") except KeyboardInterrupt: print_error("\nConfiguration cancelled by user") sys.exit(1) except Exception as e: print_error(f"Error: {e}") import traceback traceback.print_exc() sys.exit(1) def main(): """Main entry point""" project_root = Path(__file__).parent.parent configurator = NetworkConfigurator(project_root) configurator.run() if __name__ == "__main__": main()