Plugin upgrade: sync meta.xml from GitHub raw so version matches store; logging get_current_timestamp

- Add CyberCPLogFileWriter.get_current_timestamp() for errorSanitizer (fixes 500 logging)
- After upgrade/install: fetch meta.xml from raw GitHub and overwrite installed file so
  store version (e.g. 1.1.0) is correct even when archive ZIP is cached/stale
- Upgrade/install: discover ZIP top-level folder and match plugin folder case-insensitively
- Improves redisManager/memcacheManager upgrade and all store installs
This commit is contained in:
master3395
2026-02-03 19:37:36 +01:00
parent 6218cfd064
commit 02c8a9b6ac
2 changed files with 102 additions and 30 deletions

View File

@@ -7,6 +7,11 @@ import smtplib
class CyberCPLogFileWriter:
fileName = "/home/cyberpanel/error-logs.txt"
@staticmethod
def get_current_timestamp():
"""Return current timestamp in same format used for log lines (for errorSanitizer etc)."""
return time.strftime("%m.%d.%Y_%H-%M-%S")
@staticmethod
def AddFromHeader(sender, message):
try:

View File

@@ -887,6 +887,27 @@ def _get_installed_version(plugin_dir, plugin_install_dir):
return None
def _sync_meta_xml_from_github(plugin_name, plugin_install_dir='/usr/local/CyberCP'):
"""
Fetch meta.xml from GitHub raw (main) and overwrite installed meta.xml.
Ensures installed version matches store even when archive ZIP is cached/stale.
Returns True if synced, False on non-fatal failure (logged).
"""
meta_url = f'{GITHUB_RAW_BASE}/{plugin_name}/meta.xml'
meta_path = os.path.join(plugin_install_dir, plugin_name, 'meta.xml')
try:
req = urllib.request.Request(meta_url, headers={'User-Agent': 'CyberPanel-Plugin-Store/1.0'})
with urllib.request.urlopen(req, timeout=10) as resp:
content = resp.read()
if content:
with open(meta_path, 'wb') as f:
f.write(content)
logging.writeToFile(f"Synced meta.xml for {plugin_name} from GitHub raw")
return True
except Exception as e:
logging.writeToFile(f"Could not sync meta.xml for {plugin_name} from GitHub: {str(e)}")
return False
def _create_plugin_backup(plugin_name, plugin_install_dir='/usr/local/CyberCP'):
"""
Create a backup of a plugin before upgrade
@@ -1362,22 +1383,48 @@ def upgrade_plugin(request, plugin_name):
# Extract plugin directory from repository ZIP
repo_zip = zipfile.ZipFile(io.BytesIO(repo_zip_data))
namelist = repo_zip.namelist()
# Find plugin directory in ZIP
plugin_prefix = f'cyberpanel-plugins-main/{plugin_name}/'
plugin_files = [f for f in repo_zip.namelist() if f.startswith(plugin_prefix)]
# 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
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}")
raise Exception(f'Plugin {plugin_name} not found in GitHub repository (checked under {top_level}/)')
plugin_files = [f for f in namelist if f.startswith(plugin_prefix)]
if not plugin_files:
logging.writeToFile(f"Plugin {plugin_name}: no files under prefix {plugin_prefix}")
raise Exception(f'Plugin {plugin_name} not found in GitHub repository')
logging.writeToFile(f"Found {len(plugin_files)} files for plugin {plugin_name} in GitHub")
logging.writeToFile(f"Found {len(plugin_files)} files for plugin {plugin_name} in GitHub (prefix {plugin_prefix})")
# Create plugin ZIP file from GitHub with correct structure
# Create plugin ZIP with correct structure: plugin_name/... for install to /usr/local/CyberCP/plugin_name/
plugin_zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
for file_path in plugin_files:
relative_path = file_path[len(plugin_prefix):]
if relative_path: # Skip directories
if relative_path: # Skip directory-only entries
file_data = repo_zip.read(file_path)
arcname = os.path.join(plugin_name, relative_path)
plugin_zip.writestr(arcname, file_data)
@@ -1420,7 +1467,10 @@ def upgrade_plugin(request, plugin_name):
if not os.path.exists(pluginInstalled):
raise Exception(f'Plugin upgrade failed: {pluginInstalled} does not exist after upgrade')
# Get new version
# 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')
# Get new version (now reflects meta.xml from main)
new_version = _get_installed_version(plugin_name, '/usr/local/CyberCP')
logging.writeToFile(f"Plugin {plugin_name} upgraded successfully from {installed_version} to {new_version}")
@@ -1593,32 +1643,46 @@ def install_from_store(request, plugin_name):
# Extract plugin directory from repository ZIP
repo_zip = zipfile.ZipFile(io.BytesIO(repo_zip_data))
namelist = repo_zip.namelist()
# Find plugin directory in ZIP
plugin_prefix = f'cyberpanel-plugins-main/{plugin_name}/'
plugin_files = [f for f in repo_zip.namelist() if f.startswith(plugin_prefix)]
if not plugin_files:
# 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
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")
use_local_fallback = True
else:
logging.writeToFile(f"Found {len(plugin_files)} files for plugin {plugin_name} in GitHub")
# Create plugin ZIP file from GitHub with correct structure
# The ZIP must contain plugin_name/ directory structure for proper extraction
plugin_zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
for file_path in plugin_files:
# Remove the repository root prefix
relative_path = file_path[len(plugin_prefix):]
if relative_path: # Skip directories
file_data = repo_zip.read(file_path)
# Add plugin_name prefix to maintain directory structure
arcname = os.path.join(plugin_name, relative_path)
plugin_zip.writestr(arcname, file_data)
plugin_zip.close()
repo_zip.close()
plugin_files = [f for f in namelist if f.startswith(plugin_prefix)]
if not plugin_files:
repo_zip.close()
logging.writeToFile(f"Plugin {plugin_name} not found in GitHub repository, trying local source")
use_local_fallback = True
else:
logging.writeToFile(f"Found {len(plugin_files)} files for plugin {plugin_name} in GitHub")
plugin_zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
for file_path in plugin_files:
relative_path = file_path[len(plugin_prefix):]
if relative_path:
file_data = repo_zip.read(file_path)
arcname = os.path.join(plugin_name, relative_path)
plugin_zip.writestr(arcname, file_data)
plugin_zip.close()
repo_zip.close()
except Exception as github_error:
logging.writeToFile(f"GitHub download failed for {plugin_name}: {str(github_error)}, trying local source")
use_local_fallback = True
@@ -1694,6 +1758,9 @@ def install_from_store(request, plugin_name):
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