def setup(args): release = latest_release() if args.nodetype == "archivenode": Helpers.archivenode_deprecate_message() composefileurl = os.getenv( COMPOSE_FILE_OVERIDE, f"https://raw.githubusercontent.com/radixdlt/node-runner/{cli_version()}/node-runner-cli/release_ymls/radix-{args.nodetype}-compose.yml" ) print( f"Going to setup node type {args.nodetype} from location {composefileurl}.\n" ) # TODO autoapprove continue_setup = input("Do you want to continue [Y/n]?:") if not Helpers.check_Yes(continue_setup): print(" Quitting ....") sys.exit() keystore_password, file_location = Base.generatekey( keyfile_path=Helpers.get_keyfile_path(), keygen_tag=release) Docker.setup_compose_file(composefileurl, file_location, args.enabletransactions) trustednode_ip = Helpers.parse_trustednode(args.trustednode) compose_file_name = composefileurl.rsplit('/', 1)[-1] action = "update" if args.update else "start" print( f"About to {action} the node using docker-compose file {compose_file_name}, which is as below" ) run_shell_command(f"cat {compose_file_name}", shell=True) # TODO AutoApprove should_start = input(f"\nOkay to start the node [Y/n]?:") if Helpers.check_Yes(should_start): if action == "update": print( f"For update, bringing down the node using compose file {compose_file_name}" ) Docker.run_docker_compose_down(compose_file_name) Docker.run_docker_compose_up(keystore_password, compose_file_name, args.trustednode) else: print(f""" --------------------------------------------------------------- Bring up node by updating the file {compose_file_name} You can do it through cli using below command radixnode docker stop -f {compose_file_name} radixnode docker start -f {compose_file_name} -t {args.trustednode} ---------------------------------------------------------------- """)
def validator_metadata(actions: List, validator_info: EntityResponse, health): print("\n--------Update validator meta info-----\n") validatorMetadata = [ x for x in validator_info.data_objects if x.type == 'ValidatorMetadata' ] Helpers.print_coloured_line( f"Current name: {validatorMetadata[0]['name']}", bcolors.OKBLUE) Helpers.print_coloured_line( f"Current url: {validatorMetadata[0]['url']}", bcolors.OKBLUE) ask_add_or_change_info = input( "\nDo you want add/change the validator name and info url [Y/n]?") if Helpers.check_Yes(ask_add_or_change_info): validator_name = input( Helpers.print_coloured_line( f"Enter the Name of your validator to be updated:", bcolors.OKBLUE, return_string=True)) validator_url = input( Helpers.print_coloured_line( f"Enter Info URL of your validator to be updated:", bcolors.OKBLUE, return_string=True)) actions.append( Action.set_validator_metadata(validator_name, validator_url)) return actions return actions
def retract_candidate_fork_readiness_signal(args): core_api_helper = CoreApiHelper(False) should_vote = input( f"This action will retract your candidate fork readiness signal (if there was one), continue? [Y/n]{bcolors.ENDC}" ) if Helpers.check_Yes(should_vote): core_api_helper.withdraw_vote(print_response=True)
def update_validator_config(args): health = DefaultApiHelper(verify_ssl=False).check_health() core_api_helper = CoreApiHelper(verify_ssl=False) key_list_response: KeyListResponse = core_api_helper.key_list() validator_info: EntityResponse = core_api_helper.entity( key_list_response.public_keys[0].identifiers. validator_entity_identifier) actions = [] actions = ValidatorConfig.registration(actions, validator_info, health) actions = ValidatorConfig.validator_metadata(actions, validator_info, health) actions = ValidatorConfig.add_validation_fee(actions, validator_info) actions = ValidatorConfig.setup_update_delegation(actions, validator_info) actions = ValidatorConfig.add_change_ownerid(actions, validator_info) build_response: ConstructionBuildResponse = core_api_helper.construction_build( actions, ask_user=True) if build_response: signed_transaction: KeySignResponse = core_api_helper.key_sign( build_response.unsigned_transaction) core_api_helper.construction_submit( signed_transaction.signed_transaction, print_response=True) if health['fork_vote_status'] == 'VOTE_REQUIRED': print("\n------Candidate fork detected------") engine_configuration = core_api_helper.engine_configuration() print_vote_and_fork_info(health, engine_configuration) should_vote = input( f"Do you want to signal the readiness for {core_api_helper.engine_configuration().forks[-1]['name']} now? [Y/n]{bcolors.ENDC}" ) if Helpers.check_Yes(should_vote): core_api_helper.vote(print_response=True)
def setup_node_optimisation_config(version): check_ansible = run_shell_command(f"pip list | grep ansible", shell=True, fail_on_error=False) import subprocess user = subprocess.check_output('whoami', shell=True).strip() if check_ansible.returncode != 0: print( f"Ansible not found for the user {user.decode('utf-8')}. Installing ansible now" ) check_pip = run_shell_command("pip -V ", shell=True, fail_on_error=False) if check_pip.returncode != 0: print(f"Pip is not installed. Installing pip now") run_shell_command('sudo apt install python3-pip', shell=True) run_shell_command(f"pip install --user ansible==2.10.0", shell=True) print(""" ---------------------------------------------------------------------------------------- Ansible installed successfully. You need exit shell and login back""" ) sys.exit() ansible_dir = f'https://raw.githubusercontent.com/radixdlt/node-runner/{version}/node-runner-cli' print(f"Downloading artifacts from {ansible_dir}\n") Base.download_ansible_file(ansible_dir, 'ansible/project/provision.yml') ask_setup_limits = input \ ("Do you want to setup ulimits [Y/n]?:") setup_limits = "true" if Helpers.check_Yes( ask_setup_limits) else "false" run_shell_command( f"ansible-playbook ansible/project/provision.yml -e setup_limits={setup_limits}", shell=True) ask_setup_swap = input \ ("Do you want to setup swap space [Y/n]?:") if Helpers.check_Yes(ask_setup_swap): setup_swap = "true" ask_swap_size = input \ ("Enter swap size in GB. Example - 1G or 3G or 8G ?:") run_shell_command( f"ansible-playbook ansible/project/provision.yml -e setup_swap={setup_swap} -e swap_size={ask_swap_size}", shell=True) else: setup_swap = "false"
def backup_file(filepath, filename, backup_time): if os.path.isfile(f"{filepath}/{filename}"): # TODO AutoApprove backup_yes = input( f"{filename} file exists. Do you want to back up [Y/n]:") if Helpers.check_Yes(backup_yes): Path(f"{backup_time}").mkdir(parents=True, exist_ok=True) run_shell_command( f"cp {filepath}/{filename} {backup_time}/{filename}", shell=True)
def setup_compose_file(composefileurl, file_location, enable_transactions=False): compose_file_name = composefileurl.rsplit('/', 1)[-1] if os.path.isfile(compose_file_name): backup_file_name = f"{Helpers.get_current_date_time()}_{compose_file_name}" print( f"Docker compose file {compose_file_name} exists. Backing it up as {backup_file_name}" ) run_shell_command(f"cp {compose_file_name} {backup_file_name}", shell=True) print(f"Downloading new compose file from {composefileurl}") req = requests.Request('GET', f'{composefileurl}') prepared = req.prepare() resp = Helpers.send_request(prepared, print_response=False) if not resp.ok: print(f" Errored downloading file {composefileurl}. Exitting ... ") sys.exit() composefile_yaml = yaml.safe_load(resp.content) # TODO AutoApprove prompt_external_db = input( "Do you want to configure data directory for the ledger [Y/n]?:") if Helpers.check_Yes(prompt_external_db): composefile_yaml = Docker.merge_external_db_config( composefile_yaml) def represent_none(self, _): return self.represent_scalar('tag:yaml.org,2002:null', '') yaml.add_representer(type(None), represent_none) network_id = Base.get_network_id() genesis_json_location = Base.path_to_genesis_json(network_id) composefile_yaml = Docker.merge_network_info(composefile_yaml, network_id, genesis_json_location) composefile_yaml = Docker.merge_keyfile_path(composefile_yaml, file_location) composefile_yaml = Docker.merge_transactions_env_var( composefile_yaml, "true" if enable_transactions else "false") if os.getenv(IMAGE_OVERRIDE, "False") in ("true", "yes"): composefile_yaml = Docker.merge_image_overrides(composefile_yaml) with open(compose_file_name, 'w') as f: yaml.dump(composefile_yaml, f, default_flow_style=False, explicit_start=True, allow_unicode=True)
def setup_nginx_config(nginx_config_location_Url, node_type, nginx_etc_dir, backup_time): SystemD.install_nginx() if node_type == "archivenode": conf_file = 'nginx-archive.conf' elif node_type == "fullnode": conf_file = 'nginx-fullnode.conf' else: print( f"Node type - {node_type} specificed should be either archivenode or fullnode" ) sys.exit() backup_yes = input( "Do you want to backup existing nginx config [Y/n]?:") if Helpers.check_Yes(backup_yes): Path(f"{backup_time}/nginx-config").mkdir(parents=True, exist_ok=True) run_shell_command( f"sudo cp -r {nginx_etc_dir} {backup_time}/nginx-config", shell=True) # TODO AutoApprove continue_nginx = input( "Do you want to continue with nginx setup [Y/n]?:") if Helpers.check_Yes(continue_nginx): run_shell_command([ 'wget', '--no-check-certificate', '-O', 'radixdlt-nginx.zip', nginx_config_location_Url ]) run_shell_command( f'sudo unzip radixdlt-nginx.zip -d {nginx_etc_dir}', shell=True) run_shell_command( f'sudo mv {nginx_etc_dir}/{conf_file} /etc/nginx/nginx.conf', shell=True) run_shell_command(f'sudo mkdir -p /var/cache/nginx/radixdlt-hot', shell=True) return True else: return False
def check_health(self): Helpers.print_coloured_line("Checking status of the node\n", bcolors.BOLD) health = self.health() if health["status"] != "UP": Helpers.print_coloured_line( f"Node status is {health['status']} Rerun the command once node is completely synced", bcolors.WARNING) proceed = input(print("Do you want to continue [Y/n]?")) if not Helpers.check_Yes(proceed): sys.exit() return health
def create_ssl_certs(secrets_dir): SystemD.make_nginx_secrets_directory() if os.path.isfile(f'{secrets_dir}/server.key') and os.path.isfile( f'{secrets_dir}/server.pem'): print( f"Files {secrets_dir}/server.key and os.path.isfile(f'{secrets_dir}/server.pem already exists" ) answer = input("Do you want to regenerate y/n :") if Helpers.check_Yes(answer): run_shell_command(f""" sudo openssl req -nodes -new -x509 -nodes -subj '/CN=localhost' \ -keyout "{secrets_dir}/server.key" \ -out "{secrets_dir}/server.pem" """, shell=True) else: run_shell_command(f""" sudo openssl req -nodes -new -x509 -nodes -subj '/CN=localhost' \ -keyout "{secrets_dir}/server.key" \ -out "{secrets_dir}/server.pem" """, shell=True) if os.path.isfile(f'{secrets_dir}/dhparam.pem'): print(f"File {secrets_dir}/dhparam.pem already exists") answer = input("Do you want to regenerate y/n :") if Helpers.check_Yes(answer): run_shell_command( f"sudo openssl dhparam -out {secrets_dir}/dhparam.pem 4096", shell=True) else: print("Generating a dhparam.pem file") run_shell_command( f"sudo openssl dhparam -out {secrets_dir}/dhparam.pem 4096", shell=True)
def ask_permission_build(operation_groups): print( f"{bcolors.WARNING}\nAbout to update node with following operations{bcolors.ENDC}" ) print(f"") print(f"{bcolors.BOLD}{print(operation_groups)}{bcolors.ENDC}") submit_changes = input( f"{bcolors.BOLD}\nDo you want to continue [Y/n]{bcolors.ENDC}") if Helpers.check_Yes(submit_changes) and len(operation_groups) != 0: return operation_groups else: print( f"{bcolors.WARNING}Changes were not submitted.{bcolors.ENDC} or there are no actions to submit" ) return operation_groups
def download_binaries(binarylocationUrl, node_dir, node_version): run_shell_command([ 'wget', '--no-check-certificate', '-O', 'radixdlt-dist.zip', binarylocationUrl ]) run_shell_command('unzip radixdlt-dist.zip', shell=True) run_shell_command(f'mkdir -p {node_dir}/{node_version}', shell=True) if os.listdir(f'{node_dir}/{node_version}'): print(f"Directory {node_dir}/{node_version} is not empty") okay = input("Should the directory be removed [Y/n]?:") if Helpers.check_Yes(okay): run_shell_command(f"rm -rf {node_dir}/{node_version}/*", shell=True) unzipped_folder_name = os.getenv(UNZIPPED_NODE_DIST_FOLDER, f"radixdlt-{node_version}") run_shell_command( f'mv {unzipped_folder_name}/* {node_dir}/{node_version}', shell=True)
def add_validation_fee(actions: List, validator_info: EntityResponse): print("\n--------Validator fees-----\n") print( f"{bcolors.WARNING}\nValidator fee may be decreased at any time, but increasing it incurs a delay of " f"approx. 2 weeks. Please set it carefully{bcolors.ENDC}") validator_fee = [ x for x in validator_info.data_objects if x.type == 'PreparedValidatorFee' ] Helpers.print_coloured_line( f"Current validator fees are {int(validator_fee[0]['fee']) / 100}", bcolors.OKBLUE) ask_validator_fee_setup = input( "Do you want to setup or update validator fees [Y/n]?:") if Helpers.check_Yes(ask_validator_fee_setup): validatorFee = int(Helpers.check_validatorFee_input() * 100) actions.append(Action.set_validator_fee(validatorFee)) return actions return actions
def check_latest_cli(): cli_latest_version = latest_release("radixdlt/node-runner") if os.getenv(DISABLE_VERSION_CHECK, "False").lower() not in ("true", "yes"): if cli_version() != cli_latest_version: os_name = "ubuntu-20.04" print( f"Radixnode CLI latest version is {cli_latest_version} and current version of the binary is {cli_version()}.\n." ) print(f""" --------------------------------------------------------------- Update the CLI by running these commands wget -O radixnode https://github.com/radixdlt/node-runner/releases/download/{cli_latest_version}/radixnode-{os_name} chmod +x radixnode sudo mv radixnode /usr/local/bin """) abort = input( "Do you want to ABORT the command now to update the cli Y/n?:") if Helpers.check_Yes(abort): sys.exit()
def signal_candidate_fork_readiness(args): core_api_helper = CoreApiHelper(False) health = DefaultApiHelper(False).health() if health['fork_vote_status'] == 'VOTE_REQUIRED': candidate_fork_name = core_api_helper.engine_configuration( )["forks"][-1]['name'] print( f"{bcolors.WARNING}NOTICE: Because the validator is running software with a candidate fork ({candidate_fork_name}{bcolors.WARNING}), " + "by performing this action, the validator will signal the readiness to run this fork onto the ledger.\n" + f"If you later choose to downgrade the software to a version that no longer includes this fork configuration, you should manually retract your readiness signal by using the retract-candidate-fork-readiness-signal subcommand.{bcolors.ENDC}" ) should_vote = input( f"Do you want to signal the readiness for {core_api_helper.engine_configuration().forks[-1]['name']} now? [Y/n]{bcolors.ENDC}" ) if Helpers.check_Yes(should_vote): core_api_helper.vote(print_response=True) else: print( f"{bcolors.WARNING}There's no need to signal the readiness for any candidate fork.{bcolors.ENDC}" )
def generatekey(keyfile_path, keyfile_name="node-keystore.ks", keygen_tag="1.0.0"): print('-----------------------------') if os.path.isfile(f'{keyfile_path}/{keyfile_name}'): # TODO AutoApprove print(f"Node key file already exist at location {keyfile_path}") keystore_password = getpass.getpass( f"Enter the password of the existing keystore file '{keyfile_name}':" ) else: # TODO AutoApprove ask_keystore_exists = input \ (f"Do you have keystore file named '{keyfile_name}' already from previous node Y/n?:") if Helpers.check_Yes(ask_keystore_exists): print( f"Copy the keystore file '{keyfile_name}' to the location {keyfile_path} and then rerun the command" ) sys.exit() else: print(f""" Generating new keystore file. Don't forget to backup the key from location {keyfile_path}/{keyfile_name} """) keystore_password = getpass.getpass( f"Enter the password of the new file '{keyfile_name}':") # TODO keygen image needs to be updated run_shell_command([ 'docker', 'run', '--rm', '-v', keyfile_path + ':/keygen/key', f'radixdlt/keygen:{keygen_tag}', f'--keystore=/keygen/key/{keyfile_name}', '--password='******'sudo', 'chmod', '644', f'{keyfile_path}/{keyfile_name}']) return keystore_password, f'{keyfile_path}/{keyfile_name}'
def setup(args): if not args.release: release = latest_release() else: release = args.release if not args.nginxrelease: nginx_release = latest_release("radixdlt/radixdlt-nginx") else: nginx_release = args.nginxrelease if args.nodetype == "archivenode": Helpers.archivenode_deprecate_message() node_type_name = 'fullnode' node_dir = '/etc/radixdlt/node' nginx_dir = '/etc/nginx' nginx_secrets_dir = f"{nginx_dir}/secrets" node_secrets_dir = f"{node_dir}/secrets" nodebinaryUrl = os.getenv( NODE_BINARY_OVERIDE, f"https://github.com/radixdlt/radixdlt/releases/download/{release}/radixdlt-dist-{release}.zip" ) # TODO add method to fetch latest nginx release nginxconfigUrl = os.getenv( NGINX_BINARY_OVERIDE, f"https://github.com/radixdlt/radixdlt-nginx/releases/download/{nginx_release}/radixdlt-nginx-{node_type_name}-conf.zip" ) # TODO AutoApprove continue_setup = input( f"Going to setup node type {args.nodetype} for version {release} from location {nodebinaryUrl} and {nginxconfigUrl}. \n Do you want to continue Y/n:" ) if not Helpers.check_Yes(continue_setup): print(" Quitting ....") sys.exit() backup_time = Helpers.get_current_date_time() SystemD.checkUser() keystore_password, keyfile_location = SystemD.generatekey( node_secrets_dir, keygen_tag=release) trustednode_ip = Helpers.parse_trustednode(args.trustednode) SystemD.backup_file(node_secrets_dir, f"environment", backup_time) SystemD.set_environment_variables(keystore_password, node_secrets_dir) SystemD.backup_file(node_dir, f"default.config", backup_time) SystemD.setup_default_config(trustednode=args.trustednode, hostip=args.hostip, node_dir=node_dir, node_type=args.nodetype, transactions_enable=args.enabletransactions) node_version = nodebinaryUrl.rsplit('/', 2)[-2] SystemD.backup_file("/etc/systemd/system", "radixdlt-node.service", backup_time) SystemD.setup_service_file(node_version, node_dir=node_dir, node_secrets_path=node_secrets_dir) SystemD.download_binaries(nodebinaryUrl, node_dir=node_dir, node_version=node_version) SystemD.backup_file("/lib/systemd/system", f"nginx.service", backup_time) nginx_configured = SystemD.setup_nginx_config( nginx_config_location_Url=nginxconfigUrl, node_type=args.nodetype, nginx_etc_dir=nginx_dir, backup_time=backup_time) SystemD.create_ssl_certs(nginx_secrets_dir) if not args.update: SystemD.start_node_service() else: SystemD.restart_node_service() if nginx_configured and not args.update: SystemD.start_nginx_service() elif nginx_configured and args.update: SystemD.start_nginx_service() else: print("Nginx not configured or not updated")