diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..f3d4fca --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,39 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest diff --git a/requirements.txt b/requirements.txt index bdd5674..2e33a05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,6 +36,7 @@ pycryptodome==3.16.0 pyhmy==0.1.0 PyJWT==2.6.0 pyperclip==1.8.2 +pytest>=7.4.2 python-dotenv==0.21.0 requests>=2.28.1 rlp==3.0.0 diff --git a/src/__init__.py b/src/__init__.py index 6bac643..e69de29 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,3 +0,0 @@ -from .library import load_var_file, ask_yes_no, set_var - -__all__ = ['load_var_file', 'ask_yes_no', 'set_var'] \ No newline at end of file diff --git a/src/app.py b/src/app.py index c5a76da..a7d27b5 100644 --- a/src/app.py +++ b/src/app.py @@ -1,4 +1,5 @@ -import os, argparse +import os +import argparse from toolbox import ( run_findora_menu, menu_install_findora, @@ -9,25 +10,28 @@ container_running, run_troubleshooting_process, parse_flags, - check_preflight_setup + check_preflight_setup, ) -from config import findora_env +from shared import findora_env + def main() -> None: # Intro w/ stars below loader_intro() print_stars() - + # Load Vars / Set Network & Region - network, region = check_preflight_setup(findora_env.dotenv_file, findora_env.user_home_dir, findora_env.active_user_name) - + network, region = check_preflight_setup( + findora_env.dotenv_file, findora_env.user_home_dir, findora_env.active_user_name + ) + # Can this user access docker? docker_check() - + # Init parser for extra flags: parser = argparse.ArgumentParser(description="Findora Validator Toolbox - Help Menu") parse_flags(parser, region, network) - + # If `fn` isn't installed, run full installer. if not os.path.exists("/usr/local/bin/fn"): # It does not, let's ask to install! diff --git a/src/config.py b/src/config.py deleted file mode 100644 index fc0773e..0000000 --- a/src/config.py +++ /dev/null @@ -1,29 +0,0 @@ -import os, socket, requests - -def getUrl(timeout=5) -> str: - try: - response = requests.get('https://ident.me', timeout=timeout) - response.raise_for_status() # Raises a HTTPError if the response was unsuccessful - result = response.text - except requests.exceptions.RequestException as x: - print(type(x), x) - result = '0.0.0.0' - return result - -class findora_env: - toolbox_version = '1.3.0' - server_host_name = socket.gethostname() - user_home_dir = os.path.expanduser('~') - dotenv_file = f'{user_home_dir}/.findora.env' - active_user_name = os.path.split(user_home_dir)[-1] - findora_root = '/data/findora' - findora_root_mainnet = f'{findora_root}/mainnet' - findora_root_testnet = f'{findora_root}/testnet' - toolbox_location = os.path.join(user_home_dir, 'findora-toolbox') - staker_memo_path = os.path.join(user_home_dir, 'staker_memo') - our_external_ip = getUrl() - findora_menu = os.path.join(toolbox_location, 'src', 'messages', 'framenu.txt') - container_name = 'findorad' - migrate_dir = os.path.join(user_home_dir, 'migrate') - fra_env = 'prod' - findora_backup = os.path.join(user_home_dir, 'findora_backup') diff --git a/src/installer.py b/src/installer.py index f694dc1..147f534 100644 --- a/src/installer.py +++ b/src/installer.py @@ -1,5 +1,4 @@ import subprocess -from config import findora_env from shared import ( create_directory_with_permissions, install_fn_app, @@ -8,6 +7,7 @@ load_server_data, start_local_validator, get_live_version, + findora_env ) @@ -36,7 +36,7 @@ def run_full_installer(network, region): # Make Directories & Set Permissions create_directory_with_permissions("/data/findora", USERNAME) - + # Create backup directory subprocess.run( ["mkdir", "-p", f"/home/{USERNAME}/findora_backup"], @@ -72,4 +72,6 @@ def run_full_installer(network, region): ) # Start findorad - start_local_validator(ROOT_DIR, FINDORAD_IMG, "installer", network, CONTAINER_NAME, ENDPOINT_STATUS_URL, RETRY_INTERVAL) + start_local_validator( + ROOT_DIR, FINDORAD_IMG, "installer", network, CONTAINER_NAME, ENDPOINT_STATUS_URL, RETRY_INTERVAL + ) diff --git a/src/messages/framenu.txt b/src/messages/framenu.txt deleted file mode 100644 index ac58e7a..0000000 --- a/src/messages/framenu.txt +++ /dev/null @@ -1,20 +0,0 @@ -* Findora Validator Toolbox - Menu Options: -print("*") -print("* 1 - Show 'curl' stats info - Run this to show your local curl stats!") -print("* 2 - Show 'fn' stats info - Run this to show your local fn stats!") -print("* 3 - Show Balance - Check Any Wallet Balance") -print("* 7 - Update fn Application - Pull update for the wallet application, fn") -print(f"* {Fore.CYAN}{Back.RED}The Danger Zone:{Style.RESET_ALL}{Fore.MAGENTA}") -findora_container_update(update) -print("* 9 - Run Safety Clean - Stop your container, reset and download database fresh") -print("* 10 - Update Operating System - Update Ubuntu Operating System Files") -print(f"* {Fore.MAGENTA}{Back.GREEN}Informational Section:{Style.RESET_ALL}{Fore.MAGENTA}") -print("* 13 - Show system disk info - Current drive space status") -print("* 14 - TMI about your Validator - Seriously too much information") -print("* 15 - TMI about your Server - Seriously a lot of info about this server") -print("* 16 - Instructions on Migrating - Run this to read info on migrating to this server.") -print_stars() -migration_menu_option() -print("* 999 - Reboot Server - " + Fore.YELLOW + Back.RED + "WARNING: You will miss blocks during a reboot!" + Style.RESET_ALL + Fore.MAGENTA ) -print("* 0 - Exit Application - Goodbye!") -print_stars() \ No newline at end of file diff --git a/src/safety_clean.py b/src/safety_clean.py index dcc02d5..17dee16 100644 --- a/src/safety_clean.py +++ b/src/safety_clean.py @@ -1,16 +1,16 @@ import subprocess import os -from config import findora_env from shared import ( stop_and_remove_container, chown_dir, get_live_version, start_local_validator, load_server_data, + findora_env, ) -def run_safety_clean(network = os.environ.get("FRA_NETWORK"), region = os.environ.get("FRA_REGION")): +def run_safety_clean(network=os.environ.get("FRA_NETWORK"), region=os.environ.get("FRA_REGION")): USERNAME = findora_env.active_user_name ENV = "prod" server_url = f"https://{ENV}-{network}.{ENV}.findora.org" @@ -30,9 +30,18 @@ def run_safety_clean(network = os.environ.get("FRA_NETWORK"), region = os.enviro # get checkpoint on testnet if network == "testnet": - CHECKPOINT_URL = f"https://{ENV}-{network}-us-west-2-ec2-instance.s3.us-west-2.amazonaws.com/{network}/checkpoint" + CHECKPOINT_URL = ( + f"https://{ENV}-{network}-us-west-2-ec2-instance.s3.us-west-2.amazonaws.com/{network}/checkpoint" + ) subprocess.run(["sudo", "rm", "-rf", f"{ROOT_DIR}/checkpoint.toml"], check=True) - subprocess.run(["wget", "-O", f"{ROOT_DIR}/checkpoint.toml", f"{CHECKPOINT_URL}"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + subprocess.run( + ["wget", "-O", f"{ROOT_DIR}/checkpoint.toml", f"{CHECKPOINT_URL}"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) # Start findorad - start_local_validator(ROOT_DIR, FINDORAD_IMG, "safety_clean", network, CONTAINER_NAME, ENDPOINT_STATUS_URL, RETRY_INTERVAL) + start_local_validator( + ROOT_DIR, FINDORAD_IMG, "safety_clean", network, CONTAINER_NAME, ENDPOINT_STATUS_URL, RETRY_INTERVAL + ) diff --git a/src/shared.py b/src/shared.py index 3a6404a..32d262e 100644 --- a/src/shared.py +++ b/src/shared.py @@ -8,8 +8,44 @@ import urllib.request import tarfile import docker -from colorama import Fore -from config import findora_env +import socket + + +def get_url(timeout=5) -> str: + try: + response = requests.get("https://api.ipify.org?format=json", timeout=timeout) + response.raise_for_status() # Raises a HTTPError if the response was unsuccessful + + # Parse the JSON response + ip_data = response.json() + result = ip_data["ip"] + except requests.exceptions.RequestException as x: + try: + response = requests.get("https://ident.me", timeout=timeout) + response.raise_for_status() # Raises a HTTPError if the response was unsuccessful + result = response.text + except requests.exceptions.RequestException as x: + print(type(x), x) + result = "0.0.0.0" + return result + + +class findora_env: + toolbox_version = "1.3.1" + server_host_name = socket.gethostname() + user_home_dir = os.path.expanduser("~") + dotenv_file = f"{user_home_dir}/.findora.env" + active_user_name = os.path.split(user_home_dir)[-1] + findora_root = "/data/findora" + findora_root_mainnet = f"{findora_root}/mainnet" + findora_root_testnet = f"{findora_root}/testnet" + toolbox_location = os.path.join(user_home_dir, "findora-toolbox") + staker_memo_path = os.path.join(user_home_dir, "staker_memo") + our_external_ip = get_url() + container_name = "findorad" + migrate_dir = os.path.join(user_home_dir, "migrate") + fra_env = "prod" + findora_backup = os.path.join(user_home_dir, "findora_backup") def execute_command(command): @@ -247,7 +283,8 @@ def load_server_data(ENV, network, ROOT_DIR, region): # Check available disk space required_space = snapshot_size * 2.5 - available_space = get_available_space(SNAPSHOT_DIR) + available_space = get_available_space(ROOT_DIR) + final_size = available_space - required_space + snapshot_size if available_space < required_space: print( f"Error: Not enough disk space available. Minimum Required: {format_size(required_space)}+, Available: {format_size(available_space)}." @@ -257,7 +294,7 @@ def load_server_data(ENV, network, ROOT_DIR, region): exit(1) else: print( - f"* Available disk space: {format_size(available_space)} - Estimated required space: {format_size(required_space)} - Estimated available space after unpacking: {format_size(available_space - required_space - snapshot_size)} " + f"* Available disk space: {format_size(available_space)} - Estimated required space: {format_size(required_space)} - Estimated available space after unpacking: {format_size(final_size)} " ) # Extract the tar archive and check the exit status diff --git a/src/toolbox.py b/src/toolbox.py index ae71bcc..92c4cb5 100644 --- a/src/toolbox.py +++ b/src/toolbox.py @@ -1,4 +1,14 @@ -import subprocess, platform, os, time, json, re, shutil, requests, docker, dotenv, psutil, cmd2 +import subprocess +import platform +import os +import time +import json +import shutil +import requests +import docker +import dotenv +import psutil +import cmd2 from datetime import datetime, timezone from simple_term_menu import TerminalMenu from collections import namedtuple @@ -7,10 +17,9 @@ from dotenv import load_dotenv from colorama import Fore, Back, Style from pprint import pprint -from config import findora_env from updater import run_update_restart from safety_clean import run_safety_clean -from shared import ask_yes_no, compare_two_files +from shared import ask_yes_no, compare_two_files, findora_env # from shared import stop_and_remove_container from installer import run_full_installer @@ -50,25 +59,25 @@ def printWhitespace(self) -> None: def loader_intro(): print_stars() p = f"""* -* -* ███████╗██╗███╗ ██╗██████╗ ██████╗ ██████╗ █████╗ -* ██╔════╝██║████╗ ██║██╔══██╗██╔═══██╗██╔══██╗██╔══██╗ -* █████╗ ██║██╔██╗ ██║██║ ██║██║ ██║██████╔╝███████║ -* ██╔══╝ ██║██║╚██╗██║██║ ██║██║ ██║██╔══██╗██╔══██║ -* ██║ ██║██║ ╚████║██████╔╝╚██████╔╝██║ ██║██║ ██║ -* ╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ -* +* +* ███████╗██╗███╗ ██╗██████╗ ██████╗ ██████╗ █████╗ +* ██╔════╝██║████╗ ██║██╔══██╗██╔═══██╗██╔══██╗██╔══██╗ +* █████╗ ██║██╔██╗ ██║██║ ██║██║ ██║██████╔╝███████║ +* ██╔══╝ ██║██║╚██╗██║██║ ██║██║ ██║██╔══██╗██╔══██║ +* ██║ ██║██║ ╚████║██████╔╝╚██████╔╝██║ ██║██║ ██║ +* ╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ +* * ████████╗ ██████╗ ██████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗ * ╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██╔══██╗██╔═══██╗╚██╗██╔╝ -* ██║ ██║ ██║██║ ██║██║ ██████╔╝██║ ██║ ╚███╔╝ -* ██║ ██║ ██║██║ ██║██║ ██╔══██╗██║ ██║ ██╔██╗ +* ██║ ██║ ██║██║ ██║██║ ██████╔╝██║ ██║ ╚███╔╝ +* ██║ ██║ ██║██║ ██║██║ ██╔══██╗██║ ██║ ██╔██╗ * ██║ ╚██████╔╝╚██████╔╝███████╗██████╔╝╚██████╔╝██╔╝ ██╗ * ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ -* +* * Findora Validator Management * created by Patrick @ https://EasyNode.pro -* -* """ +* +*""" print(p) return @@ -111,13 +120,22 @@ def check_preflight_setup(env_file, home_dir, USERNAME=findora_env.active_user_n for tool in ["wget", "curl", "pv", "docker"]: if subprocess.call(["which", tool], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) != 0: print( - f"{Fore.YELLOW}* The package: {Fore.RED}{tool}{Fore.YELLOW}\n* Has not been installed on this system for the user {USERNAME}!\n* Install {tool} by running the following command:\n*\n* {Fore.CYAN}sudo apt install {tool} -y{Fore.MAGENTA}\n*\n* Then re-start the toolbox." + f"{Fore.YELLOW}* The package: {Fore.RED}{tool}{Fore.YELLOW}\n" + + "* Has not been installed on this system for the user {USERNAME}!\n" + + "* Install {tool} by running the following command:\n*\n" + + "* {Fore.CYAN}sudo apt install {tool} -y{Fore.MAGENTA}\n*\n" + + "* Then re-start the toolbox." ) print_stars() print( f"* To run all the prerequisites for toolbox in one command, run the following setup code:\n*\n" - + '* `apt-get update && apt-get upgrade -y && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable" && apt install apt-transport-https ca-certificates curl pv software-properties-common docker-ce docker-ce-cli dnsutils docker-compose containerd.io bind9-dnsutils git python3-pip python3-dotenv unzip -y && systemctl start docker && systemctl enable docker && usermod -aG docker servicefindora`\n' - + "* If you were missing docker, reconnect in a new terminal to gain access on `servicefindora`, then run the toolbox again." + + '* `apt-get update && apt-get upgrade -y && curl -fsSL https://download.docker.com/linux/ubuntu/gpg ' + + '| apt-key add - && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal ' + + 'stable" && apt install apt-transport-https ca-certificates curl pv software-properties-common docker-ce ' + + 'docker-ce-cli dnsutils docker-compose containerd.io bind9-dnsutils git python3-pip python3-dotenv unzip ' + + '-y && systemctl start docker && systemctl enable docker && usermod -aG docker servicefindora`\n' + + "* If you were missing docker, reconnect in a new terminal to gain access on `servicefindora`, then run " + + "the toolbox again." ) print_stars() exit(1) @@ -173,7 +191,7 @@ def menu_error() -> None: def menu_reboot_server() -> str: question = ask_yes_no( Fore.RED - + "* WARNING: YOU WILL MISS BLOCKS WHILE YOU REBOOT YOUR ENTIRE SERVER.\n\n" + + f"* {Fore.RED}WARNING: YOU WILL MISS BLOCKS WHILE YOU REBOOT YOUR ENTIRE SERVER.{Fore.MAGENTA}\n\n" + "* Reconnect after a few moments & Run the Validator Toolbox Menu again with: " + "python3 ~/findora-toolbox/start.py\n" + Fore.WHITE @@ -578,7 +596,8 @@ def get_receiver_address() -> None: # IF we've already got it, check it or ask if environ.get("RECEIVER_WALLET"): question = ask_yes_no( - f'* We have {Fore.YELLOW}{environ.get("RECEIVER_WALLET")}{Fore.MAGENTA} on file. Would you like to send to this address? (Y/N)' + f'* We have {Fore.YELLOW}{environ.get("RECEIVER_WALLET")}{Fore.MAGENTA} on file. Would you like to send to ' + + 'this address? (Y/N)' ) if question: return environ.get("RECEIVER_WALLET") @@ -601,7 +620,8 @@ def get_privacy_option() -> None: # IF we've already got it, check it or ask if environ.get("PRIVACY"): question = ask_yes_no( - f'* We have Privacy = {environ.get("PRIVACY")} on file, Would you like to use this option for this transaction as well? (Y/N) ' + f'* We have Privacy = {environ.get("PRIVACY")} on file, Would you like to use this option for this transaction ' + + 'as well? (Y/N) ' ) if question: return environ.get("PRIVACY") @@ -771,7 +791,8 @@ def do_update(self, arg): print(Fore.MAGENTA) print_stars() print( - f"* Blockchain update completed, please wait at least 1 block before checking for updated information." + f"* Blockchain update completed, please wait at least 1 block before checking for updated " + + "information." ) print_stars() return @@ -827,7 +848,8 @@ def check_address_input(address) -> None: return if address == environ.get("RECEIVER_WALLET"): input( - "* This is already your saved wallet, try again with a new wallet to update this option. Press enter to return to the menu." + "* This is already your saved wallet, try again with a new wallet to update this option. Press enter to return " + + "to the menu." ) return address2 = input(f"*\n* Please re-input the fra1 address you would like to send your FRA for verification: ") @@ -1100,7 +1122,8 @@ def menu_topper() -> None: f"* Catching Up: {Fore.GREEN}{curl_stats['result']['sync_info']['catching_up']}{Fore.MAGENTA}" ) print( - f"* Local Latest Block: {curl_stats['result']['sync_info']['latest_block_height']} * Remote Latest Block: {our_fn_stats['Current Block']}" + f"* Local Latest Block: {curl_stats['result']['sync_info']['latest_block_height']} * Remote Latest Block: " + + f"{our_fn_stats['Current Block']}" ) our_fn_stats.pop("Current Block") print(f"* Proposed Blocks: {our_fn_stats['Proposed Blocks']}") @@ -1135,7 +1158,9 @@ def rescue_menu() -> None: 3: run_safety_clean_launcher, } print( - "* We still don't detect a running container.\n* Sometimes it can take a few minutes before the api starts responding.\n* You can attempt to get stats again for a few minutes, if that doesn't work review docker logs & try the update_version.\n* Here are your options currently:" + "* We still don't detect a running container.\n* Sometimes it can take a few minutes before the api starts responding.\n" + + "* You can attempt to get stats again for a few minutes, if that doesn't work review docker logs & try the " + + "update_version.\n* Here are your options currently:" + "\n* 1 - CURL stats - Keep checking stats" + "\n* 2 - update_version script - Run the update version script as a first option for recovery." + "\n* 3 - safety_clean script - Run the safety_clean script as a last option to reset database data and restart server." @@ -1185,10 +1210,12 @@ def menu_install_findora(network, region) -> None: + f"\n* But...it doesn't look like you have Findora {network} installed." + "\n* We will setup Findora validator software on this server with a temporary key and wallet file." + "\n* After installation finishes, wait for the blockchain to sync before you create a validator or start a migration." - + "\n* Read more about migrating an existing validator here: https://docs.easynode.pro/findora/moving#migrate-your-server-via-validator-toolbox" + + "\n* Read more about migrating an existing validator here: " + + "https://docs.easynode.pro/findora/moving#migrate-your-server-via-validator-toolbox" ) answer = ask_yes_no( - f"* {Fore.RED}Do you want to install {Fore.YELLOW}{network}{Fore.RED} from the {Fore.YELLOW}{region}{Fore.RED} region now? (Y/N){Fore.MAGENTA} " + f"* {Fore.RED}Do you want to install {Fore.YELLOW}{network}{Fore.RED} from the {Fore.YELLOW}{region}{Fore.RED} " + + f"region now? (Y/N){Fore.MAGENTA} " ) if answer: run_full_installer(network, region) @@ -1198,7 +1225,8 @@ def menu_install_findora(network, region) -> None: def run_ubuntu_updates() -> None: question = ask_yes_no( - f"* {Fore.RED}You will miss blocks while upgrades run.\n{Fore.MAGENTA}*{Fore.RED} Are you sure you want to run updates? (Y/N){Fore.MAGENTA} " + f"* {Fore.RED}You will miss blocks while upgrades run.\n{Fore.MAGENTA}*{Fore.RED} Are you sure you want to run " + + f"updates? (Y/N){Fore.MAGENTA} " ) if question: print_stars() @@ -1220,7 +1248,8 @@ def migration_instructions(): + f"\n* 1. Make a folder named {findora_env.migrate_dir}\n* 2. Add your tmp.gen.keypair file into the folder" + "\n* 3. Add your config folder containing your priv_validator_key.json file into ~/migrate" + "\n* 4. If this server is catching_up=False, you can shut off the old server and relaunch the menu here to migrate." - + "\n*\n* The goal is to avoid double signing and a 5% slashing fee!!!\n*\n* Load your files and run this option again!" + + "\n*\n* The goal is to avoid double signing and a 5% slashing fee!!!\n*\n* Load your files and run this " + + "option again!" ) @@ -1234,7 +1263,8 @@ def migrate_to_server() -> None: or os.path.exists(f"{findora_env.migrate_dir}/priv_validator_key.json") ): print( - f"* {findora_env.migrate_dir}/tmp.gen.keypair found!\n* {findora_env.migrate_dir}/config/priv_validator_key.json found!" + f"* {findora_env.migrate_dir}/tmp.gen.keypair found!\n" + + f"* {findora_env.migrate_dir}/config/priv_validator_key.json found!" + "\n* All required files in place, ready for upgrade!" ) # Ask to start migration, warn about double sign again, again @@ -1382,10 +1412,9 @@ def backup_folder_check() -> None: ): # If they are the same we're done, if they are false ask to update question = ask_yes_no( - f"* Your tmp.gen.keypair file in {findora_env.findora_backup} does not match " - + f'your live {findora_env.findora_root}/{environ.get("FRA_NETWORK")}/{environ.get("FRA_NETWORK")}_node.key.' - + f'\n* Do you want to copy the key from {findora_env.findora_root}/{environ.get("FRA_NETWORK")}/{environ.get("FRA_NETWORK")}' - + f"_node.key and OVERWRITE the {findora_env.findora_backup}/tmp.gen.keypair file as a backup of your live key? (Y/N) " + f"* Your file {findora_env.findora_backup}/tmp.gen.keypair does not match " + + f'your live {environ.get("FRA_NETWORK")}_node.key.' + + f'\n* Do you want to copy the live key into the {findora_env.findora_backup} folder now? (Y/N) ' ) if question: # Copy key back @@ -1444,7 +1473,8 @@ def run_update_launcher() -> None: def run_safety_clean_launcher() -> None: question = ask_yes_no( - "* You will miss blocks while downloading the new database, this can take awhile depending on location and connection.\n* Are you sure you want to run a safety_clean? (Y/N) " + f"* {Fore.RED}You will miss blocks while downloading the new database, this can take awhile depending on location " + + f"and connection.{Fore.MAGENTA}\n* Are you sure you want to run a safety_clean? (Y/N) " ) print_stars() if question: diff --git a/src/updater.py b/src/updater.py index 1919e50..25cc91f 100644 --- a/src/updater.py +++ b/src/updater.py @@ -1,10 +1,9 @@ import os import subprocess -from config import findora_env -from shared import chown_dir, start_local_validator, stop_and_remove_container, get_live_version +from shared import chown_dir, start_local_validator, stop_and_remove_container, get_live_version, findora_env -def run_update_restart(network = os.environ.get("FRA_NETWORK")): +def run_update_restart(network=os.environ.get("FRA_NETWORK")): USERNAME = findora_env.active_user_name ENV = "prod" server_url = f"https://{ENV}-{network}.{ENV}.findora.org" @@ -21,9 +20,23 @@ def run_update_restart(network = os.environ.get("FRA_NETWORK")): # get checkpoint on testnet if network == "testnet": - CHECKPOINT_URL = f"https://{ENV}-{network}-us-west-2-ec2-instance.s3.us-west-2.amazonaws.com/{network}/checkpoint" - subprocess.run(["sudo", "rm", "-rf", f"{ROOT_DIR}/checkpoint.toml"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) - subprocess.run(["wget", "-O", f"{ROOT_DIR}/checkpoint.toml", f"{CHECKPOINT_URL}"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + CHECKPOINT_URL = ( + f"https://{ENV}-{network}-us-west-2-ec2-instance.s3.us-west-2.amazonaws.com/{network}/checkpoint" + ) + subprocess.run( + ["sudo", "rm", "-rf", f"{ROOT_DIR}/checkpoint.toml"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) + subprocess.run( + ["wget", "-O", f"{ROOT_DIR}/checkpoint.toml", f"{CHECKPOINT_URL}"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) # Start findorad - start_local_validator(ROOT_DIR, FINDORAD_IMG, "updater", network, CONTAINER_NAME, ENDPOINT_STATUS_URL, RETRY_INTERVAL) + start_local_validator( + ROOT_DIR, FINDORAD_IMG, "updater", network, CONTAINER_NAME, ENDPOINT_STATUS_URL, RETRY_INTERVAL + ) diff --git a/tests/test_shared.py b/tests/test_shared.py new file mode 100644 index 0000000..419efd6 --- /dev/null +++ b/tests/test_shared.py @@ -0,0 +1,98 @@ +import sys +import os +import tempfile +import time + +sys.path.insert(0, os.path.abspath(".")) + +import pytest +from unittest import mock +from unittest.mock import patch +from src.shared import compare_two_files, ask_yes_no, get_file_size, format_size, download_progress_hook + + +def test_compare_two_files(): + # Create two temporary files + with tempfile.NamedTemporaryFile(delete=False) as f1, tempfile.NamedTemporaryFile(delete=False) as f2: + # Write some content to the files + f1.write(b"Test content") + f2.write(b"Test content") + + # Test with identical files + assert compare_two_files(f1.name, f2.name) == True + + # Modify one file + with open(f2.name, "w") as f: + f.write("Different content") + + # Test with different files + assert compare_two_files(f1.name, f2.name) == False + + # Clean up temporary files + os.remove(f1.name) + os.remove(f2.name) + + +def test_ask_yes_no(): + # Test when user inputs 'y' + with patch("builtins.input", return_value="y"): + assert ask_yes_no("Would you like to continue?") == True + + # Test when user inputs 'yes' + with patch("builtins.input", return_value="yes"): + assert ask_yes_no("Would you like to continue?") == True + + # Test when user inputs 'n' + with patch("builtins.input", return_value="n"): + assert ask_yes_no("Would you like to continue?") == False + + # Test when user inputs 'no' + with patch("builtins.input", return_value="no"): + assert ask_yes_no("Would you like to continue?") == False + + # Test when user inputs an invalid option, followed by a valid option + with patch("builtins.input", side_effect=["maybe", "y"]): + assert ask_yes_no("Would you like to continue?") == True + + +def test_get_file_size(): + # Mocking a URL and its corresponding file size + url = "https://easynode.pro/robots.txt" + file_size = 67 + with patch("src.shared.urllib.request.urlopen") as mock_urlopen: + mock_urlopen.return_value.info.return_value.__getitem__.return_value = str(file_size) + assert get_file_size(url) == file_size + + # Testing with an invalid URL + with pytest.raises(ValueError): + get_file_size("invalid_url") + + +def test_format_size(): + assert format_size(1023) == "1023.00 B" + assert format_size(1024) == "1.00 KB" + assert format_size(1048576) == "1.00 MB" + assert format_size(1073741824) == "1.00 GB" + + +# Define start_time as a global variable +start_time = time.time() + + +def test_download_progress_hook(capfd): + # Mock time.time to return different values on subsequent calls + with mock.patch("src.shared.time.time", side_effect=[1234567890, 1234567891]): + # Initialize start_time by calling the function with count equal to 0 + download_progress_hook(0, 1024, 4096) + + # Call the function again with the desired count value + download_progress_hook(1, 1024, 4096) + + # Capture the output + out, err = capfd.readouterr() + + # Assert the printed output is as expected + assert ( + "Downloaded 1.00 KB of 4.00 KB (25%). Speed: 1.00 KB/s. Elapsed Time: 0h 0m 1s. Time remaining: 0h 0m 3s. \r" + in out + ) diff --git a/web/src/app.py b/web/src/app.py index 5c3bb30..9640c28 100644 --- a/web/src/app.py +++ b/web/src/app.py @@ -1,7 +1,7 @@ import subprocess, re, bleach, secrets, time, jwt from os import environ from werkzeug.exceptions import HTTPException -from config import findora_env +from web.src.shared import findora_env from findora_toolbox import load_var_file, set_var, ask_yes_no from flask import ( Flask, diff --git a/web/src/config.py b/web/src/shared.py similarity index 60% rename from web/src/config.py rename to web/src/shared.py index c4c9aee..19f5176 100644 --- a/web/src/config.py +++ b/web/src/shared.py @@ -1,13 +1,23 @@ -import os, socket, requests +import os +import socket +import requests -def getUrl(timeout=5) -> str: +def get_url(timeout=5) -> str: try: - response = requests.get("https://ident.me", timeout=timeout) + response = requests.get("https://api.ipify.org?format=json", timeout=timeout) response.raise_for_status() # Raises a HTTPError if the response was unsuccessful - result = response.text + + # Parse the JSON response + ip_data = response.json() + result = ip_data["ip"] except requests.exceptions.RequestException as x: - print(type(x), x) - result = '0.0.0.0' + try: + response = requests.get("https://ident.me", timeout=timeout) + response.raise_for_status() # Raises a HTTPError if the response was unsuccessful + result = response.text + except requests.exceptions.RequestException as x: + print(type(x), x) + result = "0.0.0.0" return result class findora_env: @@ -21,7 +31,7 @@ class findora_env: findora_root_testnet = f"{findora_root}/testnet" toolbox_location = os.path.join(user_home_dir, "findora-toolbox") web_location = os.path.join(user_home_dir, "findora-toolbox-web") - our_external_ip = getUrl() + our_external_ip = get_url() findora_menu = os.path.join(toolbox_location, "src", "messages", "framenu.txt") container_name = "findorad" migrate_dir = os.path.join(user_home_dir, "migrate")