From bb61871b28a8067adb4520e872516e7c43900bb5 Mon Sep 17 00:00:00 2001 From: master3395 Date: Tue, 24 Mar 2026 22:28:08 +0100 Subject: [PATCH] pluginHolder: compat installPlugin() without zip_path (legacy pluginInstaller) Detect inspect.signature for zip_path; fall back to chdir + pluginName.zip for store install, upload install, and upgrade from GitHub. --- pluginHolder/views.py | 208 +++++++++++++++++++++++------------------- 1 file changed, 113 insertions(+), 95 deletions(-) diff --git a/pluginHolder/views.py b/pluginHolder/views.py index 24938697a..6ff114a88 100644 --- a/pluginHolder/views.py +++ b/pluginHolder/views.py @@ -17,6 +17,7 @@ import sys import urllib.request import urllib.error import time +import inspect sys.path.append('/usr/local/CyberCP') from pluginInstaller.pluginInstaller import pluginInstaller from .patreon_verifier import PatreonVerifier @@ -43,6 +44,36 @@ PLUGIN_SOURCE_PATHS = ['/home/cyberpanel/plugins', '/home/cyberpanel-plugins'] # These plugins show "Built-in" badge and only Settings button (no Deactivate/Uninstall) BUILTIN_PLUGINS = frozenset(['emailMarketing', 'emailPremium']) + +def _install_plugin_compat(plugin_name, zip_path_abs): + """ + Call pluginInstaller.installPlugin with zip_path when supported (newer CyberPanel). + Older installs only accept installPlugin(pluginName) and expect pluginName.zip in CWD. + """ + zip_path_abs = os.path.abspath(zip_path_abs) + work_dir = os.path.dirname(zip_path_abs) + use_zip_kw = False + try: + sig = inspect.signature(pluginInstaller.installPlugin) + use_zip_kw = 'zip_path' in sig.parameters + except (TypeError, ValueError): + use_zip_kw = False + if use_zip_kw: + pluginInstaller.installPlugin(plugin_name, zip_path=zip_path_abs) + return + prev_cwd = os.getcwd() + try: + os.chdir(work_dir) + expected_zip = os.path.join(work_dir, plugin_name + '.zip') + if zip_path_abs != expected_zip: + shutil.copy2(zip_path_abs, expected_zip) + pluginInstaller.installPlugin(plugin_name) + finally: + try: + os.chdir(prev_cwd) + except Exception: + pass + # Core CyberPanel app dirs under /usr/local/CyberCP that must not be counted as "installed plugins" # (matches pluginHolder.urls so Installed count = store/plugin dirs only, not core apps) RESERVED_PLUGIN_DIRS = frozenset([ @@ -638,13 +669,11 @@ def install_plugin(request, plugin_name): zip_path_abs = os.path.abspath(zip_path) if not os.path.exists(zip_path_abs): raise Exception(f'Zip file not found: {zip_path_abs}') - original_cwd = os.getcwd() - os.chdir(temp_dir) try: - # Install using pluginInstaller with explicit zip path (avoids cwd races) + # Install using pluginInstaller (zip_path kw when supported; else legacy CWD + pluginName.zip) try: - pluginInstaller.installPlugin(plugin_name, zip_path=zip_path_abs) + _install_plugin_compat(plugin_name, zip_path_abs) except Exception as install_error: # Log the full error for debugging error_msg = str(install_error) @@ -680,7 +709,6 @@ def install_plugin(request, plugin_name): 'message': f'Plugin {plugin_name} installed successfully' }) finally: - os.chdir(original_cwd) # Cleanup shutil.rmtree(temp_dir, ignore_errors=True) @@ -1520,55 +1548,50 @@ def upgrade_plugin(request, plugin_name): zip_path_abs = os.path.abspath(zip_path) if not os.path.exists(zip_path_abs): raise Exception(f'Zip file not found: {zip_path_abs}') - original_cwd = os.getcwd() - os.chdir(temp_dir) + logging.writeToFile(f"Upgrading plugin using pluginInstaller (zip={zip_path_abs})") + + # Install using pluginInstaller (zip_path kw when supported; else legacy) try: - logging.writeToFile(f"Upgrading plugin using pluginInstaller (zip={zip_path_abs})") - - # Install using pluginInstaller with explicit zip path (this will overwrite existing files) - try: - pluginInstaller.installPlugin(plugin_name, zip_path=zip_path_abs) - except Exception as install_error: - error_msg = str(install_error) - logging.writeToFile(f"pluginInstaller.installPlugin raised exception: {error_msg}") - # Check if plugin directory exists despite the error - if not os.path.exists(pluginInstalled): - raise Exception(f'Plugin upgrade failed: {error_msg}') - - # Wait for file system to sync - import time - time.sleep(3) - - # Verify plugin was upgraded + _install_plugin_compat(plugin_name, zip_path_abs) + except Exception as install_error: + error_msg = str(install_error) + logging.writeToFile(f"pluginInstaller.installPlugin raised exception: {error_msg}") + # Check if plugin directory exists despite the error if not os.path.exists(pluginInstalled): - raise Exception(f'Plugin upgrade failed: {pluginInstalled} does not exist after upgrade') - - # Sync meta.xml from GitHub raw so version matches store (archive ZIP can be cached/stale) + raise Exception(f'Plugin upgrade failed: {error_msg}') + + # Wait for file system to sync + import time + time.sleep(3) + + # Verify plugin was upgraded + if not os.path.exists(pluginInstalled): + raise Exception(f'Plugin upgrade failed: {pluginInstalled} does not exist after upgrade') + + # Sync meta.xml from GitHub raw so version matches store (archive ZIP can be cached/stale) + _sync_meta_xml_from_github(plugin_name, '/usr/local/CyberCP') + new_version = _get_installed_version(plugin_name, '/usr/local/CyberCP') + # If version unchanged, meta sync may have failed (e.g. network); retry once + if new_version == installed_version: + logging.writeToFile(f"Plugin {plugin_name}: version unchanged after first meta sync, retrying sync") _sync_meta_xml_from_github(plugin_name, '/usr/local/CyberCP') new_version = _get_installed_version(plugin_name, '/usr/local/CyberCP') - # If version unchanged, meta sync may have failed (e.g. network); retry once - if new_version == installed_version: - logging.writeToFile(f"Plugin {plugin_name}: version unchanged after first meta sync, retrying sync") - _sync_meta_xml_from_github(plugin_name, '/usr/local/CyberCP') - new_version = _get_installed_version(plugin_name, '/usr/local/CyberCP') - if new_version == installed_version: - logging.writeToFile(f"Plugin {plugin_name}: version still {installed_version} after upgrade; meta.xml may not have been updated from GitHub") - - logging.writeToFile(f"Plugin {plugin_name} upgraded successfully from {installed_version} to {new_version}") - - backup_message = '' - if backup_path: - backup_message = f' Backup created at: {backup_info.get("timestamp", "unknown")}' - - return JsonResponse({ - 'success': True, - 'message': f'Plugin {plugin_name} upgraded successfully from {installed_version} to {new_version}.{backup_message}', - 'backup_created': backup_path is not None, - 'backup_path': backup_path if backup_path else None - }) - finally: - os.chdir(original_cwd) + if new_version == installed_version: + logging.writeToFile(f"Plugin {plugin_name}: version still {installed_version} after upgrade; meta.xml may not have been updated from GitHub") + + logging.writeToFile(f"Plugin {plugin_name} upgraded successfully from {installed_version} to {new_version}") + + backup_message = '' + if backup_path: + backup_message = f' Backup created at: {backup_info.get("timestamp", "unknown")}' + + return JsonResponse({ + 'success': True, + 'message': f'Plugin {plugin_name} upgraded successfully from {installed_version} to {new_version}.{backup_message}', + 'backup_created': backup_path is not None, + 'backup_path': backup_path if backup_path else None + }) finally: # Cleanup @@ -1792,55 +1815,50 @@ def install_from_store(request, plugin_name): # Pass absolute path so extraction does not depend on cwd (installPlugin may change cwd) zip_path_abs = os.path.abspath(zip_path) - original_cwd = os.getcwd() - os.chdir(temp_dir) + logging.writeToFile(f"Installing plugin using pluginInstaller (zip={zip_path_abs})") + + # Install using pluginInstaller (zip_path kw when supported; else legacy) try: - logging.writeToFile(f"Installing plugin using pluginInstaller (zip={zip_path_abs})") - - # Install using pluginInstaller with explicit zip path (avoids cwd races) - try: - pluginInstaller.installPlugin(plugin_name, zip_path=zip_path_abs) - except Exception as install_error: - # Log the full error for debugging - error_msg = str(install_error) - logging.writeToFile(f"pluginInstaller.installPlugin raised exception: {error_msg}") - # Check if plugin directory exists despite the error - pluginInstalled = '/usr/local/CyberCP/' + plugin_name - if os.path.exists(pluginInstalled): - logging.writeToFile(f"Plugin directory exists despite error, continuing...") - else: - raise Exception(f'Plugin installation failed: {error_msg}') - - # Wait a moment for file system to sync and service to restart - import time - time.sleep(3) # Increased wait time for file system sync - - # Verify plugin was actually installed + _install_plugin_compat(plugin_name, zip_path_abs) + except Exception as install_error: + # Log the full error for debugging + error_msg = str(install_error) + logging.writeToFile(f"pluginInstaller.installPlugin raised exception: {error_msg}") + # Check if plugin directory exists despite the error pluginInstalled = '/usr/local/CyberCP/' + plugin_name - if not os.path.exists(pluginInstalled): - # Exclude README.md - main CyberPanel repo has it at root - root_files = ['apps.py', 'meta.xml', 'urls.py', 'views.py'] - found_root_files = [f for f in root_files if os.path.exists(os.path.join('/usr/local/CyberCP', f))] - if found_root_files: - raise Exception(f'Plugin installation failed: Files extracted to wrong location. Found {found_root_files} in /usr/local/CyberCP/ root instead of {pluginInstalled}/') - raise Exception(f'Plugin installation failed: {pluginInstalled} does not exist after installation') - - # Sync meta.xml from GitHub raw so version matches store - _sync_meta_xml_from_github(plugin_name, '/usr/local/CyberCP') - - logging.writeToFile(f"Plugin {plugin_name} installed successfully") - - # Set plugin to enabled by default after installation - _set_plugin_state(plugin_name, True) - - _ensure_plugin_meta_xml(plugin_name) - return JsonResponse({ - 'success': True, - 'message': f'Plugin {plugin_name} installed successfully from store' - }) - finally: - os.chdir(original_cwd) + if os.path.exists(pluginInstalled): + logging.writeToFile(f"Plugin directory exists despite error, continuing...") + else: + raise Exception(f'Plugin installation failed: {error_msg}') + + # Wait a moment for file system to sync and service to restart + import time + time.sleep(3) # Increased wait time for file system sync + + # Verify plugin was actually installed + pluginInstalled = '/usr/local/CyberCP/' + plugin_name + if not os.path.exists(pluginInstalled): + # Exclude README.md - main CyberPanel repo has it at root + root_files = ['apps.py', 'meta.xml', 'urls.py', 'views.py'] + found_root_files = [f for f in root_files if os.path.exists(os.path.join('/usr/local/CyberCP', f))] + if found_root_files: + raise Exception(f'Plugin installation failed: Files extracted to wrong location. Found {found_root_files} in /usr/local/CyberCP/ root instead of {pluginInstalled}/') + raise Exception(f'Plugin installation failed: {pluginInstalled} does not exist after installation') + + # Sync meta.xml from GitHub raw so version matches store + _sync_meta_xml_from_github(plugin_name, '/usr/local/CyberCP') + + logging.writeToFile(f"Plugin {plugin_name} installed successfully") + + # Set plugin to enabled by default after installation + _set_plugin_state(plugin_name, True) + + _ensure_plugin_meta_xml(plugin_name) + return JsonResponse({ + 'success': True, + 'message': f'Plugin {plugin_name} installed successfully from store' + }) finally: # Cleanup