#! /usr/bin/env python3 import sys, os, argparse, json, shutil, tempfile, subprocess, traceback def run_script(script_path: str) -> int: """ Helper to handle subprocess execution Args: script_path (str): script path to execute Returns: int: 0 if both succeed, 1 if any step fails """ if not os.path.exists(script_path): print(f"Error: Script not found at {script_path}", file=sys.stderr) return 1 try: subprocess.run(f'{script_path}', check=True, capture_output=True, text=True) return 0 except subprocess.CalledProcessError as e: print(f"Error: {os.path.basename(script_path)} failed (Exit {e.returncode})", file=sys.stderr) print(f"Details: {e.stderr.strip() or e.stdout.strip()}", file=sys.stderr) return 1 except PermissionError: print(f"Error: {os.path.basename(script_path)} is not executable. Check permissions.", file=sys.stderr) return 1 def handle_start(src_dir, install_path): """ Start application Args: src_dir (str): Path to source directory (Working directory) install_path (str): Path to target installation directory Returns: int: 0 -> success """ try: # disable app if os.path.exists(os.path.join(install_path, 'disable')): subprocess.check_output(f'{install_path}/disable') # copy all source files to installation path shutil.copytree(src_dir, install_path, dirs_exist_ok=True) # start application if run_script(f'{install_path}/start') != 0: return 1 return 0 except Exception as ex: print(f'Error: {ex}') return 1 def handle_stop(install_path: str): """ Stop application Args: install_path (str): Path to installation directory Returns: int: 0 -> success """ if not os.path.exists(os.path.join(install_path, 'stop')): print(f"Error: stop script not found at {install_path}") return 1 try: # stop application if run_script(f'{install_path}/stop') != 0: return 1 return 0 except subprocess.CalledProcessError as e: print(f"Error: Stop script failed with exit code {e.returncode}") print(f"Output: {e.stderr or e.stdout}") return 1 def handle_restart(src_path: str, install_path: str): """ Restart application Args: install_path (str): Path to installation directory Returns: int: 0 -> success """ if handle_stop(install_path) != 0: print("Restart aborted: Stop script execution failed.", file=sys.stderr) return 1 if handle_start(src_path, install_path) != 0: print("Restart aborted: Start script execution failed.", file=sys.stderr) return 1 print('Application restarted successfully.') return 0 # script constants utils_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) temp_dir = tempfile.mkdtemp() build_dir = os.path.join(temp_dir, 'build') staging_dir = os.path.join(temp_dir, 'staging') working_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) src_path = os.path.join(working_dir, 'src') CONFIG_JSON = os.path.join(working_dir, 'config.json') templates_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'hosted-app-utils', 'templates')) template_files = { f'{templates_dir}/start': f'{working_dir}/src/start', f'{templates_dir}/stop': f'{working_dir}/src/stop', f'{templates_dir}/disable': f'{working_dir}/src/disable' } # command line arguments parser = argparse.ArgumentParser( prog="clientappctl", description="Manage client application operations. [ start, stop, restart ]") parser.add_argument("-c", "--config", default=CONFIG_JSON, help=f"Path to application configuration (default: {CONFIG_JSON})") subparsers = parser.add_subparsers(dest="command", required=True) start_parser = subparsers.add_parser("start", help="Start the application") stop_parser = subparsers.add_parser("stop", help="Stop the application") restart_parser = subparsers.add_parser("restart", help="Restart the application") def main() -> int: # parse user args args = parser.parse_args() # load configuration try: config_path = args.config if not os.path.exists(config_path): raise ValueError("Configuration not found.") with open(config_path) as f: config_data = json.load(f) name_slug = config_data['name'].strip().replace(' ', '_').replace('-', '_').lower() org_slug = config_data['org'].strip().replace(' ', '_').replace('-', '_').lower() version = config_data['version'] install_path = f'/var/signalytic/clientapps/{org_slug}/{name_slug}' # copy temp files into working dir for src, dest in template_files.items(): if not os.path.exists(src): continue shutil.copy(src, dest) if args.command == "start": print(f"Starting app: {config_data['name']}-{version}") if not os.path.exists(install_path): os.makedirs(install_path) return handle_start(src_path, install_path) elif args.command == "stop": print(f"Stopping app: {config_data['name']}-{version}") return handle_stop(install_path) elif args.command == "restart": print(f"Restarting app: {config_data['name']}-{version}") return handle_restart(src_path, install_path) else: raise ValueError('Invalid operation passed.') except Exception as ex: print(f'Error: {ex}') return 1 if __name__ == '__main__': sys.exit(main())