From 9ee84252a2ae7a5e3ae4edff2db02cb12c12c89a Mon Sep 17 00:00:00 2001 From: master3395 Date: Sat, 7 Mar 2026 21:12:45 +0100 Subject: [PATCH 1/2] pluginHolder: single upgrade confirmation (keep WARNING), refresh list after upgrade - Remove second 'Final confirmation' dialog; keep only the WARNING backup dialog - After successful upgrade, refetch plugin store and refresh upgrades list so upgraded plugins no longer appear under Upgrades Available --- pluginHolder/templates/pluginHolder/plugins.html | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pluginHolder/templates/pluginHolder/plugins.html b/pluginHolder/templates/pluginHolder/plugins.html index 0c1aefb25..e4ec2b2e2 100644 --- a/pluginHolder/templates/pluginHolder/plugins.html +++ b/pluginHolder/templates/pluginHolder/plugins.html @@ -2554,11 +2554,6 @@ function upgradePlugin(pluginName, currentVersion, newVersion) { return; } - // Double confirmation - if (!confirm(`Final confirmation: Upgrade ${pluginName} now?\n\nThis action cannot be undone.`)) { - return; - } - const btn = event.target.closest('.btn-upgrade') || event.target; const originalText = btn.innerHTML; btn.disabled = true; @@ -2583,10 +2578,10 @@ function upgradePlugin(pluginName, currentVersion, newVersion) { } else { alert('Success: ' + (data.message || `Plugin ${pluginName} upgraded successfully`)); } - // Reload page after short delay to show success message - setTimeout(() => { - location.reload(); - }, 1500); + // Refetch store so upgrades list and badge update (plugin no longer shows as upgradable) + if (typeof loadPluginStore === 'function') { + loadPluginStore(true); + } } else { if (typeof PNotify !== 'undefined') { new PNotify({ From 91e8fb9aa40b926ede3df3663d8975dd98d9121a Mon Sep 17 00:00:00 2001 From: master3395 Date: Sat, 7 Mar 2026 22:04:52 +0100 Subject: [PATCH 2/2] pluginHolder: harden upgrade - verify meta sync, retry, fsync so version updates reliably --- pluginHolder/views.py | 47 +++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/pluginHolder/views.py b/pluginHolder/views.py index 8e7d1dee1..24938697a 100644 --- a/pluginHolder/views.py +++ b/pluginHolder/views.py @@ -953,7 +953,8 @@ def _compare_versions(version1, version2): return 0 def _get_installed_version(plugin_dir, plugin_install_dir): - """Get installed version of a plugin from meta.xml""" + """Get installed version of a plugin from meta.xml. + Supports both and root elements.""" installed_path = os.path.join(plugin_install_dir, plugin_dir) meta_path = os.path.join(installed_path, 'meta.xml') @@ -973,21 +974,36 @@ def _sync_meta_xml_from_github(plugin_name, plugin_install_dir='/usr/local/Cyber """ 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). + Verifies write by re-reading version. Returns True if synced and version readable, False otherwise. """ 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: + for attempt in (1, 2): + try: + req = urllib.request.Request(meta_url, headers={'User-Agent': 'CyberPanel-Plugin-Store/1.0'}) + with urllib.request.urlopen(req, timeout=15) as resp: + content = resp.read() + if not content: + if attempt == 2: + logging.writeToFile(f"Sync meta.xml for {plugin_name}: empty response from GitHub") + continue 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)}") + f.flush() + if hasattr(os, 'fsync'): + try: + f.fsync() + except Exception: + pass + # Verify we can read version back (ensures file is valid and readable) + ver = _get_installed_version(plugin_name, plugin_install_dir) + if ver: + logging.writeToFile(f"Synced meta.xml for {plugin_name} from GitHub raw (version {ver})") + return True + if attempt == 2: + logging.writeToFile(f"Sync meta.xml for {plugin_name}: wrote file but could not parse version") + except Exception as e: + logging.writeToFile(f"Could not sync meta.xml for {plugin_name} from GitHub (attempt {attempt}): {str(e)}") return False def _create_plugin_backup(plugin_name, plugin_install_dir='/usr/local/CyberCP'): @@ -1530,9 +1546,14 @@ def upgrade_plugin(request, plugin_name): # 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') + # 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}")