mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-03-25 21:40:07 +01:00
@@ -104,10 +104,11 @@ urlpatterns = [
|
||||
path('api/revert/<str:plugin_name>/', views.revert_plugin, name='revert_plugin'),
|
||||
path('api/debug-plugins/', views.debug_loaded_plugins, name='debug_loaded_plugins'),
|
||||
path('api/check-subscription/<str:plugin_name>/', views.check_plugin_subscription, name='check_plugin_subscription'),
|
||||
path('<str:plugin_name>/settings/', views.plugin_settings_proxy, name='plugin_settings_proxy'),
|
||||
path('<str:plugin_name>/help/', views.plugin_help, name='plugin_help'),
|
||||
]
|
||||
|
||||
# Include each installed plugin's URLs *before* the catch-all so /plugins/<name>/settings/ etc. match
|
||||
# Include each installed plugin's URLs *before* the catch-all so /plugins/<name>/... (other than settings/help) match
|
||||
_loaded_plugins = []
|
||||
_failed_plugins = {}
|
||||
for _plugin_name, _path_parent in _get_installed_plugin_list():
|
||||
|
||||
@@ -54,6 +54,34 @@ RESERVED_PLUGIN_DIRS = frozenset([
|
||||
'websiteFunctions', 'aiScanner', 'dns', 'help', 'installed',
|
||||
])
|
||||
|
||||
def _find_plugin_prefix_in_archive(namelist, plugin_name):
|
||||
"""
|
||||
Find the path prefix for a plugin inside a GitHub archive (e.g. repo-main/pluginName/ or repo-main/Category/pluginName/).
|
||||
Returns (top_level, plugin_prefix) or (None, None) if not found.
|
||||
"""
|
||||
top_level = None
|
||||
for name in namelist:
|
||||
if '/' in name:
|
||||
top_level = name.split('/')[0]
|
||||
break
|
||||
if not top_level:
|
||||
return None, None
|
||||
plugin_name_lower = plugin_name.lower()
|
||||
# Check every path: find one that has a segment equal to plugin_name (e.g. .../pm2Manager/ or .../snappymailAdmin/)
|
||||
for name in namelist:
|
||||
if '/' not in name:
|
||||
continue
|
||||
parts = name.split('/')
|
||||
# parts[0] = top_level, then we need a segment that matches plugin_name
|
||||
for i in range(1, len(parts)):
|
||||
if parts[i].lower() == plugin_name_lower:
|
||||
# Plugin folder is at top_level/parts[1]/.../parts[i]/
|
||||
prefix_parts = [top_level] + parts[1:i + 1]
|
||||
plugin_prefix = '/'.join(prefix_parts) + '/'
|
||||
return top_level, plugin_prefix
|
||||
return top_level, None
|
||||
|
||||
|
||||
def _get_plugin_source_path(plugin_name):
|
||||
"""Return the full path to a plugin's source directory, or None if not found."""
|
||||
for base in PLUGIN_SOURCE_PATHS:
|
||||
@@ -607,19 +635,16 @@ def install_plugin(request, plugin_name):
|
||||
'error': f'Failed to create zip file for {plugin_name}'
|
||||
}, status=500)
|
||||
|
||||
# Copy zip to current directory (pluginInstaller expects it in cwd)
|
||||
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:
|
||||
# Verify zip file exists in current directory
|
||||
zip_file = plugin_name + '.zip'
|
||||
if not os.path.exists(zip_file):
|
||||
raise Exception(f'Zip file {zip_file} not found in temp directory')
|
||||
|
||||
# Install using pluginInstaller
|
||||
# Install using pluginInstaller with explicit zip path (avoids cwd races)
|
||||
try:
|
||||
pluginInstaller.installPlugin(plugin_name)
|
||||
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)
|
||||
@@ -638,8 +663,8 @@ def install_plugin(request, plugin_name):
|
||||
# Verify plugin was actually installed
|
||||
pluginInstalled = '/usr/local/CyberCP/' + plugin_name
|
||||
if not os.path.exists(pluginInstalled):
|
||||
# Check if files were extracted to root instead
|
||||
root_files = ['README.md', 'apps.py', 'meta.xml', 'urls.py', 'views.py']
|
||||
# Check if plugin files were extracted to root (exclude README.md - main 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}/')
|
||||
@@ -1442,29 +1467,10 @@ def upgrade_plugin(request, plugin_name):
|
||||
repo_zip = zipfile.ZipFile(io.BytesIO(repo_zip_data))
|
||||
namelist = repo_zip.namelist()
|
||||
|
||||
# Discover top-level folder (GitHub uses repo-name-branch, e.g. cyberpanel-plugins-main)
|
||||
top_level = None
|
||||
for name in namelist:
|
||||
if '/' in name:
|
||||
top_level = name.split('/')[0]
|
||||
break
|
||||
elif name and not name.endswith('/'):
|
||||
top_level = name
|
||||
break
|
||||
# Find plugin folder (supports flat repo or nested e.g. Category/pluginName)
|
||||
top_level, plugin_prefix = _find_plugin_prefix_in_archive(namelist, plugin_name)
|
||||
if not top_level:
|
||||
raise Exception('GitHub archive has no recognizable structure')
|
||||
|
||||
# Find plugin folder in ZIP (case-insensitive: repo may have RedisManager vs redisManager)
|
||||
plugin_prefix = None
|
||||
plugin_name_lower = plugin_name.lower()
|
||||
for name in namelist:
|
||||
if '/' not in name:
|
||||
continue
|
||||
parts = name.split('/')
|
||||
if len(parts) >= 2 and parts[0] == top_level and parts[1].lower() == plugin_name_lower:
|
||||
# Use the actual casing from the ZIP for reading
|
||||
plugin_prefix = f'{top_level}/{parts[1]}/'
|
||||
break
|
||||
if not plugin_prefix:
|
||||
sample = namelist[:15] if len(namelist) > 15 else namelist
|
||||
logging.writeToFile(f"Plugin {plugin_name} not in archive. Top-level={top_level}, sample paths: {sample}")
|
||||
@@ -1495,20 +1501,18 @@ def upgrade_plugin(request, plugin_name):
|
||||
|
||||
logging.writeToFile(f"Created plugin ZIP: {zip_path}")
|
||||
|
||||
# Copy ZIP to current directory (pluginInstaller expects it in cwd)
|
||||
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:
|
||||
zip_file = plugin_name + '.zip'
|
||||
if not os.path.exists(zip_file):
|
||||
raise Exception(f'Zip file {zip_file} not found in temp directory')
|
||||
logging.writeToFile(f"Upgrading plugin using pluginInstaller (zip={zip_path_abs})")
|
||||
|
||||
logging.writeToFile(f"Upgrading plugin using pluginInstaller")
|
||||
|
||||
# Install using pluginInstaller (this will overwrite existing files)
|
||||
# Install using pluginInstaller with explicit zip path (this will overwrite existing files)
|
||||
try:
|
||||
pluginInstaller.installPlugin(plugin_name)
|
||||
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}")
|
||||
@@ -1702,23 +1706,10 @@ def install_from_store(request, plugin_name):
|
||||
repo_zip = zipfile.ZipFile(io.BytesIO(repo_zip_data))
|
||||
namelist = repo_zip.namelist()
|
||||
|
||||
# Discover top-level folder and find plugin (case-insensitive)
|
||||
top_level = None
|
||||
for name in namelist:
|
||||
if '/' in name:
|
||||
top_level = name.split('/')[0]
|
||||
break
|
||||
# Find plugin folder (supports flat repo or nested e.g. Category/pluginName)
|
||||
top_level, plugin_prefix = _find_plugin_prefix_in_archive(namelist, plugin_name)
|
||||
if not top_level:
|
||||
raise Exception('GitHub archive has no recognizable structure')
|
||||
plugin_prefix = None
|
||||
plugin_name_lower = plugin_name.lower()
|
||||
for name in namelist:
|
||||
if '/' not in name:
|
||||
continue
|
||||
parts = name.split('/')
|
||||
if len(parts) >= 2 and parts[0] == top_level and parts[1].lower() == plugin_name_lower:
|
||||
plugin_prefix = f'{top_level}/{parts[1]}/'
|
||||
break
|
||||
if not plugin_prefix:
|
||||
repo_zip.close()
|
||||
logging.writeToFile(f"Plugin {plugin_name} not found in GitHub repository, trying local source")
|
||||
@@ -1775,21 +1766,20 @@ def install_from_store(request, plugin_name):
|
||||
|
||||
logging.writeToFile(f"Created plugin ZIP: {zip_path}")
|
||||
|
||||
# Copy ZIP to current directory (pluginInstaller expects it in cwd)
|
||||
if not os.path.exists(zip_path):
|
||||
raise Exception(f'Zip file not found: {zip_path}')
|
||||
|
||||
# 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)
|
||||
|
||||
try:
|
||||
# Verify zip file exists in current directory
|
||||
zip_file = plugin_name + '.zip'
|
||||
if not os.path.exists(zip_file):
|
||||
raise Exception(f'Zip file {zip_file} not found in temp directory')
|
||||
logging.writeToFile(f"Installing plugin using pluginInstaller (zip={zip_path_abs})")
|
||||
|
||||
logging.writeToFile(f"Installing plugin using pluginInstaller")
|
||||
|
||||
# Install using pluginInstaller (direct call, not via command line)
|
||||
# Install using pluginInstaller with explicit zip path (avoids cwd races)
|
||||
try:
|
||||
pluginInstaller.installPlugin(plugin_name)
|
||||
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)
|
||||
@@ -1808,8 +1798,8 @@ def install_from_store(request, plugin_name):
|
||||
# Verify plugin was actually installed
|
||||
pluginInstalled = '/usr/local/CyberCP/' + plugin_name
|
||||
if not os.path.exists(pluginInstalled):
|
||||
# Check if files were extracted to root instead
|
||||
root_files = ['README.md', 'apps.py', 'meta.xml', 'urls.py', 'views.py']
|
||||
# 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}/')
|
||||
@@ -1872,6 +1862,38 @@ def debug_loaded_plugins(request):
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=500)
|
||||
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def plugin_settings_proxy(request, plugin_name):
|
||||
"""
|
||||
Proxy for /plugins/<plugin_name>/settings/ so plugin settings pages work even when
|
||||
the plugin was installed after the worker started (dynamic URL list is built at import time).
|
||||
"""
|
||||
mailUtilities.checkHome()
|
||||
plugin_path = '/usr/local/CyberCP/' + plugin_name
|
||||
urls_py = os.path.join(plugin_path, 'urls.py')
|
||||
if not plugin_name or not os.path.isdir(plugin_path) or not os.path.exists(urls_py):
|
||||
from django.http import HttpResponseNotFound
|
||||
return HttpResponseNotFound('Plugin not found or has no URL configuration.')
|
||||
if plugin_name in RESERVED_PLUGIN_DIRS or plugin_name in (
|
||||
'api', 'installed', 'help', 'emailMarketing', 'emailPremium', 'pluginHolder'
|
||||
):
|
||||
from django.http import HttpResponseNotFound
|
||||
return HttpResponseNotFound('Invalid plugin.')
|
||||
try:
|
||||
import importlib
|
||||
views_mod = importlib.import_module(plugin_name + '.views')
|
||||
settings_view = getattr(views_mod, 'settings', None)
|
||||
if not callable(settings_view):
|
||||
from django.http import HttpResponseNotFound
|
||||
return HttpResponseNotFound('Plugin has no settings view.')
|
||||
return settings_view(request)
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"plugin_settings_proxy for {plugin_name}: {str(e)}")
|
||||
from django.http import HttpResponseServerError
|
||||
return HttpResponseServerError(f'Plugin settings error: {str(e)}')
|
||||
|
||||
|
||||
def plugin_help(request, plugin_name):
|
||||
"""Plugin-specific help page - shows plugin information, version history, and help content"""
|
||||
mailUtilities.checkHome()
|
||||
|
||||
@@ -6,6 +6,8 @@ import argparse
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import tempfile
|
||||
import zipfile
|
||||
import django
|
||||
from plogical.processUtilities import ProcessUtilities
|
||||
|
||||
@@ -58,16 +60,96 @@ class pluginInstaller:
|
||||
### Functions Related to plugin installation.
|
||||
|
||||
@staticmethod
|
||||
def extractPlugin(pluginName):
|
||||
pathToPlugin = pluginName + '.zip'
|
||||
command = 'unzip -o ' + pathToPlugin + ' -d /usr/local/CyberCP'
|
||||
result = subprocess.run(shlex.split(command), capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception(f"Failed to extract plugin {pluginName}: {result.stderr}")
|
||||
# Verify extraction succeeded
|
||||
def extractPlugin(pluginName, zip_path=None):
|
||||
"""
|
||||
Extract plugin zip so that all files end up in /usr/local/CyberCP/pluginName/.
|
||||
Handles zips with: (1) top-level folder pluginName/, (2) top-level folder with
|
||||
another name (e.g. repo-main/), or (3) files at root (no top-level folder).
|
||||
If zip_path is given (absolute path), use it; otherwise use pluginName + '.zip' in cwd.
|
||||
"""
|
||||
if zip_path is not None:
|
||||
pathToPlugin = os.path.abspath(zip_path)
|
||||
else:
|
||||
pathToPlugin = os.path.abspath(pluginName + '.zip')
|
||||
if not os.path.exists(pathToPlugin):
|
||||
raise Exception(f"Plugin zip not found: {pathToPlugin}")
|
||||
pluginPath = '/usr/local/CyberCP/' + pluginName
|
||||
if not os.path.exists(pluginPath):
|
||||
raise Exception(f"Plugin extraction failed: {pluginPath} does not exist after extraction")
|
||||
# Remove existing plugin dir so we start clean (e.g. from a previous failed install)
|
||||
if os.path.exists(pluginPath):
|
||||
shutil.rmtree(pluginPath)
|
||||
extract_dir = tempfile.mkdtemp(prefix='cyberpanel_plugin_')
|
||||
try:
|
||||
with zipfile.ZipFile(pathToPlugin, 'r') as zf:
|
||||
zf.extractall(extract_dir)
|
||||
top_level = os.listdir(extract_dir)
|
||||
plugin_name_lower = pluginName.lower()
|
||||
# Prefer a top-level directory whose name matches pluginName (case-insensitive)
|
||||
matching_dir = None
|
||||
for name in top_level:
|
||||
if os.path.isdir(os.path.join(extract_dir, name)) and name.lower() == plugin_name_lower:
|
||||
matching_dir = name
|
||||
break
|
||||
if len(top_level) == 1:
|
||||
single = os.path.join(extract_dir, top_level[0])
|
||||
if os.path.isdir(single):
|
||||
# One top-level directory
|
||||
single_name = top_level[0]
|
||||
# If it's the repo root (e.g. cyberpanel-plugins-main), check for plugin subdir
|
||||
if single_name.lower() != plugin_name_lower:
|
||||
plugin_subdir = os.path.join(single, pluginName)
|
||||
if not os.path.isdir(plugin_subdir):
|
||||
# Try case-insensitive subdir match
|
||||
for entry in os.listdir(single):
|
||||
if os.path.isdir(os.path.join(single, entry)) and entry.lower() == plugin_name_lower:
|
||||
plugin_subdir = os.path.join(single, entry)
|
||||
break
|
||||
if os.path.isdir(plugin_subdir):
|
||||
# Use the plugin subdir as the plugin content (avoid nesting repo root)
|
||||
shutil.move(plugin_subdir, pluginPath)
|
||||
shutil.rmtree(single, ignore_errors=True)
|
||||
else:
|
||||
shutil.move(single, pluginPath)
|
||||
else:
|
||||
shutil.move(single, pluginPath)
|
||||
else:
|
||||
# Single file at root
|
||||
os.makedirs(pluginPath, exist_ok=True)
|
||||
shutil.move(single, os.path.join(pluginPath, top_level[0]))
|
||||
elif matching_dir:
|
||||
# Multiple items: one is a dir matching pluginName - use it as plugin, put rest inside pluginPath
|
||||
os.makedirs(pluginPath, exist_ok=True)
|
||||
src_match = os.path.join(extract_dir, matching_dir)
|
||||
# Move the matching plugin dir to pluginPath (replace if exists)
|
||||
if os.path.exists(pluginPath):
|
||||
shutil.rmtree(pluginPath)
|
||||
shutil.move(src_match, pluginPath)
|
||||
for name in top_level:
|
||||
if name == matching_dir:
|
||||
continue
|
||||
src = os.path.join(extract_dir, name)
|
||||
dst = os.path.join(pluginPath, name)
|
||||
if os.path.exists(dst):
|
||||
if os.path.isdir(dst):
|
||||
shutil.rmtree(dst)
|
||||
else:
|
||||
os.remove(dst)
|
||||
shutil.move(src, dst)
|
||||
else:
|
||||
# Multiple items or empty: place everything inside pluginName/
|
||||
os.makedirs(pluginPath, exist_ok=True)
|
||||
for name in top_level:
|
||||
src = os.path.join(extract_dir, name)
|
||||
dst = os.path.join(pluginPath, name)
|
||||
if os.path.exists(dst):
|
||||
if os.path.isdir(dst):
|
||||
shutil.rmtree(dst)
|
||||
else:
|
||||
os.remove(dst)
|
||||
shutil.move(src, dst)
|
||||
if not os.path.exists(pluginPath):
|
||||
raise Exception(f"Plugin extraction failed: {pluginPath} does not exist after extraction")
|
||||
finally:
|
||||
shutil.rmtree(extract_dir, ignore_errors=True)
|
||||
|
||||
@staticmethod
|
||||
def upgradingSettingsFile(pluginName):
|
||||
@@ -210,12 +292,12 @@ class pluginInstaller:
|
||||
|
||||
|
||||
@staticmethod
|
||||
def installPlugin(pluginName):
|
||||
def installPlugin(pluginName, zip_path=None):
|
||||
try:
|
||||
##
|
||||
|
||||
pluginInstaller.stdOut('Extracting plugin..')
|
||||
pluginInstaller.extractPlugin(pluginName)
|
||||
pluginInstaller.extractPlugin(pluginName, zip_path=zip_path)
|
||||
pluginInstaller.stdOut('Plugin extracted.')
|
||||
|
||||
##
|
||||
@@ -385,41 +467,50 @@ class pluginInstaller:
|
||||
|
||||
@staticmethod
|
||||
def removeFromSettings(pluginName):
|
||||
data = open("/usr/local/CyberCP/CyberCP/settings.py", 'r', encoding='utf-8').readlines()
|
||||
writeToFile = open("/usr/local/CyberCP/CyberCP/settings.py", 'w', encoding='utf-8')
|
||||
|
||||
settings_path = "/usr/local/CyberCP/CyberCP/settings.py"
|
||||
try:
|
||||
with open(settings_path, 'r', encoding='utf-8') as f:
|
||||
data = f.readlines()
|
||||
except (OSError, IOError) as e:
|
||||
raise Exception(f'Cannot read {settings_path}: {e}. Ensure the panel user can read it.')
|
||||
in_installed_apps = False
|
||||
out_lines = []
|
||||
for i, items in enumerate(data):
|
||||
# Track if we're in INSTALLED_APPS section
|
||||
if 'INSTALLED_APPS' in items and '=' in items:
|
||||
in_installed_apps = True
|
||||
elif in_installed_apps and items.strip().startswith(']'):
|
||||
in_installed_apps = False
|
||||
|
||||
# More precise matching: look for plugin name in quotes (e.g., 'pluginName' or "pluginName")
|
||||
# Only match if we're in INSTALLED_APPS section to prevent false positives
|
||||
if in_installed_apps and (f"'{pluginName}'" in items or f'"{pluginName}"' in items):
|
||||
continue
|
||||
else:
|
||||
writeToFile.writelines(items)
|
||||
writeToFile.close()
|
||||
out_lines.append(items)
|
||||
try:
|
||||
with open(settings_path, 'w', encoding='utf-8') as writeToFile:
|
||||
writeToFile.writelines(out_lines)
|
||||
except (OSError, IOError) as e:
|
||||
raise Exception(
|
||||
f'Cannot write {settings_path}: {e}. '
|
||||
'Ensure the file is writable by the panel user (e.g. chgrp lscpd ... ; chmod g+w ...).'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def removeFromURLs(pluginName):
|
||||
data = open("/usr/local/CyberCP/CyberCP/urls.py", 'r', encoding='utf-8').readlines()
|
||||
writeToFile = open("/usr/local/CyberCP/CyberCP/urls.py", 'w', encoding='utf-8')
|
||||
|
||||
urls_path = "/usr/local/CyberCP/CyberCP/urls.py"
|
||||
try:
|
||||
with open(urls_path, 'r', encoding='utf-8') as f:
|
||||
data = f.readlines()
|
||||
except (OSError, IOError) as e:
|
||||
raise Exception(f'Cannot read {urls_path}: {e}.')
|
||||
out_lines = []
|
||||
for items in data:
|
||||
# More precise matching: look for plugin name in path() or include() calls
|
||||
# Match patterns like: path('plugins/pluginName/', include('pluginName.urls'))
|
||||
# This prevents partial matches
|
||||
if (f"plugins/{pluginName}/" in items or f"'{pluginName}.urls'" in items or f'"{pluginName}.urls"' in items or
|
||||
if (f"plugins/{pluginName}/" in items or f"'{pluginName}.urls'" in items or f'"{pluginName}.urls"' in items or
|
||||
f"include('{pluginName}.urls')" in items or f'include("{pluginName}.urls")' in items):
|
||||
continue
|
||||
else:
|
||||
writeToFile.writelines(items)
|
||||
|
||||
writeToFile.close()
|
||||
out_lines.append(items)
|
||||
try:
|
||||
with open(urls_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(out_lines)
|
||||
except (OSError, IOError) as e:
|
||||
raise Exception(f'Cannot write {urls_path}: {e}. Ensure the file is writable by the panel user (chgrp lscpd; chmod g+w).')
|
||||
|
||||
@staticmethod
|
||||
def informCyberPanelRemoval(pluginName):
|
||||
|
||||
@@ -37,12 +37,13 @@ Pre_Upgrade_Required_Components() {
|
||||
# Check if CyberCP directory exists but is incomplete/damaged
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Checking CyberCP directory integrity..." | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
|
||||
# Define essential CyberCP components
|
||||
# Define essential CyberCP components (do not add /usr/local/CyberCP/manage - it does not exist; see usmannasir/cyberpanel#1721)
|
||||
CYBERCP_ESSENTIAL_DIRS=(
|
||||
"/usr/local/CyberCP/CyberCP"
|
||||
"/usr/local/CyberCP/plogical"
|
||||
"/usr/local/CyberCP/websiteFunctions"
|
||||
"/usr/local/CyberCP/manage"
|
||||
"/usr/local/CyberCP/pluginHolder"
|
||||
"/usr/local/CyberCP/pluginInstaller"
|
||||
)
|
||||
|
||||
CYBERCP_MISSING=0
|
||||
|
||||
Reference in New Issue
Block a user