diff --git a/utils/fn_startStack.py b/utils/fn_startStack.py index 81b2f3c..495b16a 100644 --- a/utils/fn_startStack.py +++ b/utils/fn_startStack.py @@ -5,6 +5,7 @@ import platform import subprocess import time +import threading from colorama import Fore, Style, just_fix_windows_console # Ensure the parent directory is in the sys.path @@ -20,7 +21,7 @@ from utils.cls import cls from utils.generator import generate_dashboard_urls from utils.prompt_helper import ask_question_yn -from utils.helper import is_user_root, is_user_in_docker_group, create_docker_group_if_needed, run_docker_command +from utils.helper import is_user_root, is_user_in_docker_group, create_docker_group_if_needed, run_docker_command, show_spinner # Global config loading and global variables m4b_config_path = os.path.join(parent_dir, "config", "m4b-config.json") @@ -55,22 +56,29 @@ def start_stack(compose_file: str = './docker-compose.yaml', env_file: str = './ time.sleep(sleep_time) return False + event = threading.Event() + spinner_thread = threading.Thread(target=show_spinner, args=(f"Starting stack for '{instance_name}'...", event)) + spinner_thread.start() + use_sudo = not is_user_root() and platform.system().lower() == 'linux' try: command = ["docker", "compose", "-f", compose_file, "--env-file", env_file, "up", "-d", "--remove-orphans"] - exit_code = run_docker_command(command, use_sudo=use_sudo) - if exit_code == 0: + result = run_docker_command(command, use_sudo=use_sudo) + if result == 0: print(f"{Fore.GREEN}All Apps for '{instance_name}' instance started successfully.{Style.RESET_ALL}") logging.info(f"Stack for '{instance_name}' started successfully.") else: print(f"{Fore.RED}Error starting Docker stack for '{instance_name}' instance. Please check that Docker is running and that the configuration is complete, then try again.{Style.RESET_ALL}") - logging.error(f"Stack for '{instance_name}' failed to start with exit code {exit_code}.") - time.sleep(sleep_time) - return exit_code == 0 + logging.error(f"Stack for '{instance_name}' failed to start with exit code {result}.") + time.sleep(sleep_time) + return result == 0 except Exception as e: print(f"{Fore.RED}An unexpected error occurred while starting the stack for '{instance_name}' instance.{Style.RESET_ALL}") logging.error(f"Unexpected error: {str(e)}") time.sleep(sleep_time) + finally: + event.set() + spinner_thread.join() return False @@ -93,18 +101,21 @@ def start_all_stacks(main_compose_file: str = './docker-compose.yaml', main_env_ if platform.system().lower() == 'linux' and not is_user_in_docker_group(): create_docker_group_if_needed() - all_started = start_stack(main_compose_file, main_env_file, main_instance_name, skip_questions=True) - if all_started and os.path.isdir(instances_dir): - for instance in os.listdir(instances_dir): - instance_dir = os.path.join(instances_dir, instance) - compose_file = os.path.join(instance_dir, 'docker-compose.yaml') - env_file = os.path.join(instance_dir, '.env') - if os.path.isfile(compose_file) and os.path.isfile(env_file): - all_started &= start_stack(compose_file, env_file, instance, skip_questions=True) - - if all_started: - generate_dashboard_urls(None, None, main_env_file) - print(f"{Fore.YELLOW}Use the previously generated apps nodes URLs to add your device in any apps dashboard that require node claiming/registration (e.g., Earnapp, ProxyRack, etc.){Style.RESET_ALL}") + try: + all_started = start_stack(main_compose_file, main_env_file, main_instance_name, skip_questions=True) + if all_started and os.path.isdir(instances_dir): + for instance in os.listdir(instances_dir): + instance_dir = os.path.join(instances_dir, instance) + compose_file = os.path.join(instance_dir, 'docker-compose.yaml') + env_file = os.path.join(instance_dir, '.env') + if os.path.isfile(compose_file) and os.path.isfile(env_file): + all_started &= start_stack(compose_file, env_file, instance, skip_questions=True) + + if all_started: + generate_dashboard_urls(None, None, main_env_file) + print(f"{Fore.YELLOW}Use the previously generated apps nodes URLs to add your device in any apps dashboard that require node claiming/registration (e.g., Earnapp, ProxyRack, etc.){Style.RESET_ALL}") + logging.info("All stacks started successfully.") + finally: time.sleep(sleep_time) diff --git a/utils/fn_stopStack.py b/utils/fn_stopStack.py index 9c2b760..0b2980f 100644 --- a/utils/fn_stopStack.py +++ b/utils/fn_stopStack.py @@ -4,6 +4,7 @@ import logging import platform import time +import threading from colorama import Fore, Style, just_fix_windows_console # Ensure the parent directory is in the sys.path @@ -17,7 +18,7 @@ from utils import loader from utils.cls import cls from utils.prompt_helper import ask_question_yn -from utils.helper import is_user_root, is_user_in_docker_group, create_docker_group_if_needed, run_docker_command +from utils.helper import is_user_root, is_user_in_docker_group, create_docker_group_if_needed, run_docker_command, show_spinner # Global config loading and global variables m4b_config_path = os.path.join(parent_dir, "config", "m4b-config.json") @@ -51,22 +52,27 @@ def stop_stack(compose_file: str = './docker-compose.yaml', instance_name: str = time.sleep(sleep_time) return False + event = threading.Event() + spinner_thread = threading.Thread(target=show_spinner, args=(f"Stopping stack for '{instance_name}'...", event)) + spinner_thread.start() + use_sudo = not is_user_root() and platform.system().lower() == 'linux' try: command = ["docker", "compose", "-f", compose_file, "down"] - exit_code = run_docker_command(command, use_sudo=use_sudo) - if exit_code == 0: + result = run_docker_command(command, use_sudo=use_sudo) + if result == 0: print(f"{Fore.GREEN}All Apps for '{instance_name}' instance stopped and stack deleted.{Style.RESET_ALL}") logging.info(f"Stack for '{instance_name}' stopped successfully.") else: print(f"{Fore.RED}Error stopping and deleting Docker stack for '{instance_name}' instance. Please check the configuration and try again.{Style.RESET_ALL}") - logging.error(f"Stack for '{instance_name}' failed to stop with exit code {exit_code}.") - time.sleep(sleep_time) - return exit_code == 0 + logging.error(f"Stack for '{instance_name}' failed to stop with exit code {result}.") + return result == 0 except Exception as e: print(f"{Fore.RED}An unexpected error occurred while stopping the stack for '{instance_name}' instance.{Style.RESET_ALL}") logging.error(f"Unexpected error: {str(e)}") - time.sleep(sleep_time) + finally: + event.set() + spinner_thread.join() return False @@ -88,20 +94,23 @@ def stop_all_stacks(main_compose_file: str = './docker-compose.yaml', main_insta if platform.system().lower() == 'linux' and not is_user_in_docker_group(): create_docker_group_if_needed() - stop_stack(main_compose_file, main_instance_name, skip_questions=True) - if os.path.isdir(instances_dir): - print(f"{Fore.YELLOW}Stopping multi-proxy instances...{Style.RESET_ALL}") - for instance in os.listdir(instances_dir): - instance_dir = os.path.join(instances_dir, instance) - compose_file = os.path.join(instance_dir, 'docker-compose.yaml') - if os.path.isfile(compose_file): - try: - stop_stack(compose_file, instance, skip_questions=True) - except Exception as e: - logging.error(f"Failed to stop instance '{instance}': {str(e)}") - print(f"{Fore.GREEN}All multi-proxy instances stopped successfully.{Style.RESET_ALL}") - else: - logging.warning(f"Multi-proxy instances directory '{instances_dir}' does not exist.") + try: + stop_stack(main_compose_file, main_instance_name, skip_questions=True) + if os.path.isdir(instances_dir): + print(f"{Fore.YELLOW}Stopping multi-proxy instances...{Style.RESET_ALL}") + for instance in os.listdir(instances_dir): + instance_dir = os.path.join(instances_dir, instance) + compose_file = os.path.join(instance_dir, 'docker-compose.yaml') + if os.path.isfile(compose_file): + try: + stop_stack(compose_file, instance, skip_questions=True) + except Exception as e: + logging.error(f"Failed to stop instance '{instance}': {str(e)}") + print(f"{Fore.GREEN}All multi-proxy instances stopped successfully.{Style.RESET_ALL}") + else: + logging.warning(f"Multi-proxy instances directory '{instances_dir}' does not exist.") + finally: + time.sleep(sleep_time) def main(app_config_path: str, m4b_config_path: str, user_config_path: str) -> None: @@ -121,6 +130,7 @@ def main(app_config_path: str, m4b_config_path: str, user_config_path: str) -> N logging.error(f"An unexpected error occurred in main function: {str(e)}") print(f"{Fore.RED}An unexpected error occurred: {str(e)}{Style.RESET_ALL}") + if __name__ == '__main__': parser = argparse.ArgumentParser(description='Stop the Docker Compose stack.') parser.add_argument('--app-config', type=str, required=True, help='Path to app_config JSON file') diff --git a/utils/helper.py b/utils/helper.py index a3e26e9..a896aee 100644 --- a/utils/helper.py +++ b/utils/helper.py @@ -59,7 +59,7 @@ def create_docker_group_if_needed(): def run_docker_command(command, use_sudo=False): """ - Run a Docker command, optionally using sudo, and show real-time output. + Run a Docker command, optionally using sudo, and handle errors gracefully. Args: command (list): The Docker command to run. @@ -74,28 +74,17 @@ def run_docker_command(command, use_sudo=False): logging.info(f"Running command: {' '.join(command)}") try: - process = subprocess.Popen( - command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True - ) - - # Stream the output in real-time - for line in process.stdout: - print(line) - for line in process.stderr: - print(line) - - process.wait() - if process.returncode != 0: - logging.error(f"Command failed with exit code {process.returncode}") - stderr_output = process.stderr.read() - if stderr_output: - logging.error(f"Command error output: {stderr_output}") - return process.returncode + result = subprocess.run(command, capture_output=True, text=True, check=False) + if result.returncode == 0: + logging.info(result.stdout) + else: + logging.error(f"Command failed with exit code {result.returncode}") + logging.error(result.stderr) + print(f"{Fore.RED}Error: {result.stderr.strip()}{Style.RESET_ALL}") + return result.returncode except Exception as e: logging.error(f"{Fore.RED}Failed to run command: {e}{Style.RESET_ALL}") + print(f"{Fore.RED}Unexpected error: {e}{Style.RESET_ALL}") raise RuntimeError(f"Command failed: {e}")