def get_lighthouse_installed_version(): # Get the installed version for Lighthouse log.info('Getting Lighthouse installed version...') process_result = subprocess.run([LIGHTHOUSE_INSTALLED_PATH, '--version'], capture_output=True, text=True) if process_result.returncode != 0: log.error(f'Unexpected return code from Lighthouse. Return code: ' f'{process_result.returncode}') return UNKNOWN_VALUE process_output = process_result.stdout result = re.search(r'Lighthouse v?(?P<version>[^-]+)', process_output) if not result: log.error( f'Cannot parse {process_output} for Lighthouse installed version.') return UNKNOWN_VALUE installed_version = result.group('version') log.info(f'Lighthouse installed version is {installed_version}') return installed_version
def get_geth_available_version(): # Get the available version for Geth, potentially for update log.info('Getting Geth available version...') subprocess.run(['apt', '-y', 'update']) process_result = subprocess.run(['apt-cache', 'policy', 'geth'], capture_output=True, text=True) if process_result.returncode != 0: log.error(f'Unexpected return code from apt-cache. Return code: ' f'{process_result.returncode}') return UNKNOWN_VALUE process_output = process_result.stdout result = re.search(r'Candidate: (?P<version>[^\+]+)', process_output) if not result: log.error(f'Cannot parse {process_output} for Geth candidate version.') return UNKNOWN_VALUE available_version = result.group('version') log.info(f'Geth available version is {available_version}') return available_version
def get_geth_installed_version(): # Get the installed version for Geth log.info('Getting Geth installed version...') process_result = subprocess.run(['geth', 'version'], capture_output=True, text=True) if process_result.returncode != 0: log.error(f'Unexpected return code from geth. Return code: ' f'{process_result.returncode}') return UNKNOWN_VALUE process_output = process_result.stdout result = re.search(r'Version: (?P<version>[^-]+)', process_output) if not result: log.error(f'Cannot parse {process_output} for Geth installed version.') return UNKNOWN_VALUE installed_version = result.group('version') log.info(f'Geth installed version is {installed_version}') return installed_version
def upgrade_geth(): # Upgrade the Geth client log.info('Upgrading Geth client...') subprocess.run(['apt', '-y', 'update']) subprocess.run(['apt', '-y', 'install', 'geth']) log.info('Restarting Geth service...') subprocess.run(['systemctl', 'restart', GETH_SYSTEMD_SERVICE_NAME]) return True
def enter_maintenance(context): # Maintenance entry point for Ubuntu. # Maintenance is started after the wizard has completed. log.info(f'Entering maintenance mode. To be implemented.') if context is None: log.error('Missing context.') return False context = use_default_client(context) if context is None: log.error('Missing context.') return False return show_dashboard(context)
def get_geth_running_version(): # Get the running version for Geth log.info('Getting Geth running version...') local_geth_jsonrpc_url = 'http://127.0.0.1:8545' request_json = {'jsonrpc': '2.0', 'method': 'web3_clientVersion', 'id': 67} headers = {'Content-Type': 'application/json'} try: response = httpx.post(local_geth_jsonrpc_url, json=request_json, headers=headers) except httpx.RequestError as exception: log.error(f'Cannot connect to Geth. Exception: {exception}') return UNKNOWN_VALUE if response.status_code != 200: log.error( f'Unexpected status code from {local_geth_jsonrpc_url}. Status code: ' f'{response.status_code}') return UNKNOWN_VALUE response_json = response.json() if 'result' not in response_json: log.error( f'Unexpected JSON response from {local_geth_jsonrpc_url}. result not found.' ) return UNKNOWN_VALUE version_agent = response_json['result'] # Version agent should look like: Geth/v1.10.12-stable-6c4dc6c3/linux-amd64/go1.17.2 result = re.search( r'Geth/v(?P<version>[^-/]+)(-(?P<stable>[^-/]+))?(-(?P<commit>[^-/]+))?', version_agent) if not result: log.error(f'Cannot parse {version_agent} for Geth version.') return UNKNOWN_VALUE running_version = result.group('version') log.info(f'Geth running version is {running_version}') return running_version
def has_su_perm(platform): # Check to see if the script has super user (root, sudo or elevated) permissions if platform == PLATFORM_UBUNTU: has_su = os.geteuid() == 0 if not has_su: from ethwizard.platforms.ubuntu.common import log log.warning( 'Running without super user (root or sudo) permissions') return has_su elif platform == PLATFORM_WINDOWS10: perform_elevation = False try: if ctypes.windll.shell32.IsUserAnAdmin(): return True else: perform_elevation = True except: perform_elevation = True if perform_elevation: from ethwizard.platforms.windows.common import log log.info('Performing privilege elevation') pythonpath_env = rf'$env:PYTHONPATH = "{";".join(sys.path)}";' encoding_change = ( '$OutputEncoding = [console]::InputEncoding = ' '[console]::OutputEncoding = New-Object System.Text.UTF8Encoding;' ) target_command = (encoding_change + pythonpath_env + sys.executable + ' ' + " ".join(sys.argv)) encoded_command = base64.b64encode( codecs.encode(target_command, 'utf_16_le')) encoded_command = codecs.decode(encoded_command, 'ascii') args = f'-NoProfile -EncodedCommand {encoded_command}' ctypes.windll.shell32.ShellExecuteW(None, 'runas', 'powershell', args, None, 1) # End the unprivileged process sys.exit() return False
def get_lighthouse_latest_version(): # Get the latest version for Lighthouse log.info('Getting Lighthouse latest version...') lighthouse_gh_release_url = GITHUB_REST_API_URL + LIGHTHOUSE_LATEST_RELEASE headers = {'Accept': GITHUB_API_VERSION} try: response = httpx.get(lighthouse_gh_release_url, headers=headers, follow_redirects=True) except httpx.RequestError as exception: log.error( f'Exception while getting the latest stable version for Lighthouse. {exception}' ) return UNKNOWN_VALUE if response.status_code != 200: log.error( f'HTTP error while getting the latest stable version for Lighthouse. ' f'Status code {response.status_code}') return UNKNOWN_VALUE release_json = response.json() if 'tag_name' not in release_json or not isinstance( release_json['tag_name'], str): log.error( f'Unable to find tag name in Github response while getting the latest stable ' f'version for Lighthouse.') return UNKNOWN_VALUE tag_name = release_json['tag_name'] result = re.search(r'v?(?P<version>.+)', tag_name) if not result: log.error(f'Cannot parse tag name {tag_name} for Lighthouse version.') return UNKNOWN_VALUE latest_version = result.group('version') log.info(f'Lighthouse latest version is {latest_version}') return latest_version
def get_lighthouse_running_version(): # Get the running version for Lighthouse log.info('Getting Lighthouse running version...') local_lighthouse_bn_version_url = 'http://127.0.0.1:5052' + BN_VERSION_EP try: response = httpx.get(local_lighthouse_bn_version_url) except httpx.RequestError as exception: log.error(f'Cannot connect to Lighthouse. Exception: {exception}') return UNKNOWN_VALUE if response.status_code != 200: log.error( f'Unexpected status code from {local_lighthouse_bn_version_url}. Status code: ' f'{response.status_code}') return UNKNOWN_VALUE response_json = response.json() if 'data' not in response_json or 'version' not in response_json['data']: log.error( f'Unexpected JSON response from {local_lighthouse_bn_version_url}. result not found.' ) return UNKNOWN_VALUE version_agent = response_json['data']['version'] # Version agent should look like: Lighthouse/v2.0.1-aaa5344/x86_64-linux result = re.search( r'Lighthouse/v(?P<version>[^-/]+)(-(?P<commit>[^-/]+))?', version_agent) if not result: log.error(f'Cannot parse {version_agent} for Lighthouse version.') return UNKNOWN_VALUE running_version = result.group('version') log.info(f'Lighthouse running version is {running_version}') return running_version
def upgrade_lighthouse(): # Upgrade the Lighthouse client log.info('Upgrading Lighthouse client...') # Getting latest Lighthouse release files lighthouse_gh_release_url = GITHUB_REST_API_URL + LIGHTHOUSE_LATEST_RELEASE headers = {'Accept': GITHUB_API_VERSION} try: response = httpx.get(lighthouse_gh_release_url, headers=headers, follow_redirects=True) except httpx.RequestError as exception: log.error( f'Exception while downloading lighthouse binary. {exception}') return False if response.status_code != 200: log.error(f'HTTP error while downloading lighthouse binary. ' f'Status code {response.status_code}') return False release_json = response.json() if 'assets' not in release_json: log.error('No assets in Github release for lighthouse.') return False binary_asset = None signature_asset = None for asset in release_json['assets']: if 'name' not in asset: continue if 'browser_download_url' not in asset: continue file_name = asset['name'] file_url = asset['browser_download_url'] if file_name.endswith('x86_64-unknown-linux-gnu.tar.gz'): binary_asset = {'file_name': file_name, 'file_url': file_url} elif file_name.endswith('x86_64-unknown-linux-gnu.tar.gz.asc'): signature_asset = {'file_name': file_name, 'file_url': file_url} if binary_asset is None or signature_asset is None: log.error( 'Could not find binary or signature asset in Github release.') return False # Downloading latest Lighthouse release files download_path = Path(Path.home(), 'ethwizard', 'downloads') download_path.mkdir(parents=True, exist_ok=True) binary_path = Path(download_path, binary_asset['file_name']) try: with open(binary_path, 'wb') as binary_file: with httpx.stream('GET', binary_asset['file_url'], follow_redirects=True) as http_stream: if http_stream.status_code != 200: log.error( f'HTTP error while downloading Lighthouse binary from Github. ' f'Status code {http_stream.status_code}') return False for data in http_stream.iter_bytes(): binary_file.write(data) except httpx.RequestError as exception: log.error( f'Exception while downloading Lighthouse binary from Github. {exception}' ) return False signature_path = Path(download_path, signature_asset['file_name']) try: with open(signature_path, 'wb') as signature_file: with httpx.stream('GET', signature_asset['file_url'], follow_redirects=True) as http_stream: if http_stream.status_code != 200: log.error( f'HTTP error while downloading Lighthouse signature from Github. ' f'Status code {http_stream.status_code}') return False for data in http_stream.iter_bytes(): signature_file.write(data) except httpx.RequestError as exception: log.error( f'Exception while downloading Lighthouse signature from Github. {exception}' ) return False # Test if gpg is already installed gpg_is_installed = False try: gpg_is_installed = is_package_installed('gpg') except Exception: return False if not gpg_is_installed: # Install gpg using APT subprocess.run(['apt', '-y', 'update']) subprocess.run(['apt', '-y', 'install', 'gpg']) # Verify PGP signature command_line = [ 'gpg', '--list-keys', '--with-colons', LIGHTHOUSE_PRIME_PGP_KEY_ID ] process_result = subprocess.run(command_line) pgp_key_found = process_result.returncode == 0 if not pgp_key_found: retry_index = 0 retry_count = 15 key_server = PGP_KEY_SERVERS[retry_index % len(PGP_KEY_SERVERS)] log.info(f'Downloading Sigma Prime\'s PGP key from {key_server} ...') command_line = [ 'gpg', '--keyserver', key_server, '--recv-keys', LIGHTHOUSE_PRIME_PGP_KEY_ID ] process_result = subprocess.run(command_line) if process_result.returncode != 0: # GPG failed to download Sigma Prime's PGP key, let's wait and retry a few times while process_result.returncode != 0 and retry_index < retry_count: retry_index = retry_index + 1 delay = 5 log.warning( f'GPG failed to download the PGP key. We will wait {delay} seconds ' f'and try again from a different server.') time.sleep(delay) key_server = PGP_KEY_SERVERS[retry_index % len(PGP_KEY_SERVERS)] log.info( f'Downloading Sigma Prime\'s PGP key from {key_server} ...' ) command_line = [ 'gpg', '--keyserver', key_server, '--recv-keys', LIGHTHOUSE_PRIME_PGP_KEY_ID ] process_result = subprocess.run(command_line) if process_result.returncode != 0: log.error(f''' We failed to download the Sigma Prime's PGP key to verify the lighthouse binary after {retry_count} retries. ''') return False process_result = subprocess.run(['gpg', '--verify', signature_path]) if process_result.returncode != 0: log.error('The lighthouse binary signature is wrong. ' 'We will stop here to protect you.') return False # Stopping Lighthouse services before updating the binary log.info('Stopping Lighthouse services...') subprocess.run([ 'systemctl', 'stop', LIGHTHOUSE_BN_SYSTEMD_SERVICE_NAME, LIGHTHOUSE_VC_SYSTEMD_SERVICE_NAME ]) # Extracting the Lighthouse binary archive log.info('Updating Lighthouse binary...') subprocess.run([ 'tar', 'xvf', binary_path, '--directory', LIGHTHOUSE_INSTALLED_DIRECTORY ]) # Restarting Lighthouse services after updating the binary log.info('Starting Lighthouse services...') subprocess.run([ 'systemctl', 'start', LIGHTHOUSE_BN_SYSTEMD_SERVICE_NAME, LIGHTHOUSE_VC_SYSTEMD_SERVICE_NAME ]) # Remove download leftovers binary_path.unlink() signature_path.unlink() return True
def perform_maintenance(execution_client, execution_client_details, consensus_client, consensus_client_details): # Perform all the maintenance tasks if execution_client == EXECUTION_CLIENT_GETH: # Geth maintenance tasks if execution_client_details[ 'next_step'] == MAINTENANCE_RESTART_SERVICE: log.info('Restarting Geth service...') subprocess.run(['systemctl', 'restart', GETH_SYSTEMD_SERVICE_NAME]) elif execution_client_details[ 'next_step'] == MAINTENANCE_UPGRADE_CLIENT: if not upgrade_geth(): log.error('We could not upgrade the Geth client.') return False elif execution_client_details[ 'next_step'] == MAINTENANCE_START_SERVICE: log.info('Starting Geth service...') subprocess.run(['systemctl', 'start', GETH_SYSTEMD_SERVICE_NAME]) elif execution_client_details[ 'next_step'] == MAINTENANCE_REINSTALL_CLIENT: log.warn('TODO: Reinstalling client is to be implemented.') else: log.error(f'Unknown execution client {execution_client}.') return False if consensus_client == CONSENSUS_CLIENT_LIGHTHOUSE: # Lighthouse maintenance tasks if consensus_client_details[ 'next_step'] == MAINTENANCE_RESTART_SERVICE: log.info('Restarting Lighthouse services...') subprocess.run([ 'systemctl', 'restart', LIGHTHOUSE_BN_SYSTEMD_SERVICE_NAME, LIGHTHOUSE_VC_SYSTEMD_SERVICE_NAME ]) elif consensus_client_details[ 'next_step'] == MAINTENANCE_UPGRADE_CLIENT: if not upgrade_lighthouse(): log.error('We could not upgrade the Lighthouse client.') return False elif consensus_client_details[ 'next_step'] == MAINTENANCE_START_SERVICE: log.info('Starting Lighthouse services...') subprocess.run([ 'systemctl', 'start', LIGHTHOUSE_BN_SYSTEMD_SERVICE_NAME, LIGHTHOUSE_VC_SYSTEMD_SERVICE_NAME ]) elif consensus_client_details[ 'next_step'] == MAINTENANCE_REINSTALL_CLIENT: log.warn('TODO: Reinstalling client is to be implemented.') else: log.error(f'Unknown consensus client {consensus_client}.') return False return True