From 35a4e9069814b73e84b95c4292c4c3176dd1db17 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:01:17 +0100 Subject: [PATCH 01/13] Fix dashboard insights 500s: FTPUsers filter, session/ACL safety, /proc hardening - FTPUsers: use domain__domain__in (FK to Websites) not domain__in - Session: use session.get('userID'), return JSON when missing - ACL: use currentACL.get('admin',0) to avoid KeyError - /proc: resilient parsing for net/dev, diskstats, stat - Log errors via CyberCPLogFileWriter; generic user-facing messages - Prevents intermittent 500s that zero out insights (Users, Sites, etc.) --- baseTemplate/views.py | 104 ++++++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 39 deletions(-) diff --git a/baseTemplate/views.py b/baseTemplate/views.py index 90622e843..aee6450b5 100644 --- a/baseTemplate/views.py +++ b/baseTemplate/views.py @@ -537,12 +537,17 @@ def RestartCyberPanel(request): def getDashboardStats(request): try: - val = request.session['userID'] + val = request.session.get('userID') + if val is None: + return HttpResponse( + json.dumps({'status': 0, 'error_message': 'Session required'}), + content_type='application/json' + ) currentACL = ACLManager.loadedACL(val) admin = Administrator.objects.get(pk=val) # Check if user is admin - if currentACL['admin'] == 1: + if currentACL.get('admin', 0) == 1: # Admin can see all resources total_users = Administrator.objects.count() total_sites = Websites.objects.count() @@ -580,7 +585,7 @@ def getDashboardStats(request): total_emails = EUsers.objects.filter(emailOwner__domainOwner__domain__in=website_names).count() # Count FTP users associated with user's domains - total_ftp_users = FTPUsers.objects.filter(domain__in=website_names).count() + total_ftp_users = FTPUsers.objects.filter(domain__domain__in=website_names).count() else: total_wp_sites = 0 total_dbs = 0 @@ -598,18 +603,24 @@ def getDashboardStats(request): } return HttpResponse(json.dumps(data), content_type='application/json') except Exception as e: - return HttpResponse(json.dumps({'status': 0, 'error_message': str(e)}), content_type='application/json') + logging.writeToFile('getDashboardStats error: %s' % str(e)) + return HttpResponse( + json.dumps({'status': 0, 'error_message': 'Failed to load dashboard stats'}), + content_type='application/json' + ) def getTrafficStats(request): try: - val = request.session['userID'] + val = request.session.get('userID') + if val is None: + return HttpResponse( + json.dumps({'status': 0, 'error_message': 'Session required'}), + content_type='application/json' + ) currentACL = ACLManager.loadedACL(val) - - # Only admins should see system-wide network stats if not currentACL.get('admin', 0): return HttpResponse(json.dumps({'status': 0, 'error_message': 'Admin access required', 'admin_only': True}), content_type='application/json') - # Get network stats from /proc/net/dev (Linux) rx = tx = 0 with open('/proc/net/dev', 'r') as f: for line in f.readlines(): @@ -617,42 +628,49 @@ def getTrafficStats(request): continue if ':' in line: parts = line.split() - rx += int(parts[1]) - tx += int(parts[9]) - data = { - 'rx_bytes': rx, - 'tx_bytes': tx, - 'status': 1 - } + try: + if len(parts) >= 10: + rx += int(parts[1]) + tx += int(parts[9]) + except (ValueError, IndexError): + continue + data = {'rx_bytes': rx, 'tx_bytes': tx, 'status': 1} return HttpResponse(json.dumps(data), content_type='application/json') except Exception as e: - return HttpResponse(json.dumps({'status': 0, 'error_message': str(e)}), content_type='application/json') + logging.writeToFile('getTrafficStats error: %s' % str(e)) + return HttpResponse( + json.dumps({'status': 0, 'error_message': 'Failed to load traffic stats'}), + content_type='application/json' + ) def getDiskIOStats(request): try: - val = request.session['userID'] + val = request.session.get('userID') + if val is None: + return HttpResponse( + json.dumps({'status': 0, 'error_message': 'Session required'}), + content_type='application/json' + ) currentACL = ACLManager.loadedACL(val) - - # Only admins should see system-wide disk I/O stats if not currentACL.get('admin', 0): return HttpResponse(json.dumps({'status': 0, 'error_message': 'Admin access required', 'admin_only': True}), content_type='application/json') - # Parse /proc/diskstats for all disks read_sectors = 0 write_sectors = 0 - sector_size = 512 # Most Linux systems use 512 bytes per sector + sector_size = 512 with open('/proc/diskstats', 'r') as f: for line in f: parts = line.split() if len(parts) < 14: continue - # parts[2] is device name, skip loopback/ram devices dev = parts[2] if dev.startswith('loop') or dev.startswith('ram'): continue - # 6th and 10th columns: sectors read/written - read_sectors += int(parts[5]) - write_sectors += int(parts[9]) + try: + read_sectors += int(parts[5]) + write_sectors += int(parts[9]) + except (ValueError, IndexError): + continue data = { 'read_bytes': read_sectors * sector_size, 'write_bytes': write_sectors * sector_size, @@ -660,34 +678,42 @@ def getDiskIOStats(request): } return HttpResponse(json.dumps(data), content_type='application/json') except Exception as e: - return HttpResponse(json.dumps({'status': 0, 'error_message': str(e)}), content_type='application/json') + logging.writeToFile('getDiskIOStats error: %s' % str(e)) + return HttpResponse( + json.dumps({'status': 0, 'error_message': 'Failed to load disk I/O stats'}), + content_type='application/json' + ) def getCPULoadGraph(request): try: - val = request.session['userID'] + val = request.session.get('userID') + if val is None: + return HttpResponse( + json.dumps({'status': 0, 'error_message': 'Session required'}), + content_type='application/json' + ) currentACL = ACLManager.loadedACL(val) - - # Only admins should see system-wide CPU stats if not currentACL.get('admin', 0): return HttpResponse(json.dumps({'status': 0, 'error_message': 'Admin access required', 'admin_only': True}), content_type='application/json') - # Parse /proc/stat for the 'cpu' line + cpu_times = [] with open('/proc/stat', 'r') as f: for line in f: if line.startswith('cpu '): parts = line.strip().split() - # parts[1:] are user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice - cpu_times = [float(x) for x in parts[1:]] + try: + cpu_times = [float(x) for x in parts[1:]] + except (ValueError, IndexError): + pass break - else: - cpu_times = [] - data = { - 'cpu_times': cpu_times, - 'status': 1 - } + data = {'cpu_times': cpu_times, 'status': 1} return HttpResponse(json.dumps(data), content_type='application/json') except Exception as e: - return HttpResponse(json.dumps({'status': 0, 'error_message': str(e)}), content_type='application/json') + logging.writeToFile('getCPULoadGraph error: %s' % str(e)) + return HttpResponse( + json.dumps({'status': 0, 'error_message': 'Failed to load CPU stats'}), + content_type='application/json' + ) @csrf_exempt @require_GET From 17421b6dfe91e49ea3230f8c41516d15f638bca2 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:39:25 +0100 Subject: [PATCH 02/13] Fix v2.5.5-dev installation issues: handle curl execution and MariaDB upgrade attempts - Fix install.sh to detect curl/wget execution and clone repo before loading modules - Add MariaDB detection and upgrade attempt logic in install.py - Attempt MariaDB 12.1 upgrade first, fallback to existing 10.x if blocked - Add comprehensive error handling and logging for upgrade attempts - Update documentation with fix details --- install.sh | 56 ++++++ install/install.py | 180 ++++++++++++++++++ to-do/PAID-PLUGINS-GUIDE.md | 214 ++++++++++++++++++++++ to-do/PATREON-CONFIGURATION.md | 58 ++++++ to-do/PATREON-SETUP-SECURE.md | 115 ++++++++++++ to-do/REMOTE-VERIFICATION-ARCHITECTURE.md | 89 +++++++++ to-do/REMOTE-VERIFICATION-COMPLETE.md | 115 ++++++++++++ to-do/SETUP-COMPLETE.md | 86 +++++++++ to-do/v2.5.5-dev-installation-fixes.md | 132 +++++++++++++ 9 files changed, 1045 insertions(+) create mode 100644 to-do/PAID-PLUGINS-GUIDE.md create mode 100644 to-do/PATREON-CONFIGURATION.md create mode 100644 to-do/PATREON-SETUP-SECURE.md create mode 100644 to-do/REMOTE-VERIFICATION-ARCHITECTURE.md create mode 100644 to-do/REMOTE-VERIFICATION-COMPLETE.md create mode 100644 to-do/SETUP-COMPLETE.md create mode 100644 to-do/v2.5.5-dev-installation-fixes.md diff --git a/install.sh b/install.sh index 18088684d..c487174e9 100644 --- a/install.sh +++ b/install.sh @@ -8,6 +8,62 @@ set -e # Get script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Early logging function (before main functions are defined) +early_log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +# Check if script is being executed via curl/wget (SCRIPT_DIR will be /dev/fd/...) +# In that case, we need to clone the repository first +if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then + # Script is being executed via curl/wget, need to clone repo first + early_log "๐Ÿ“ฅ Detected curl/wget execution - cloning repository..." + + # Determine branch from arguments or use default + BRANCH_NAME="v2.5.5-dev" + ARGS=("$@") + for i in "${!ARGS[@]}"; do + if [[ "${ARGS[i]}" == "-b" ]] || [[ "${ARGS[i]}" == "--branch" ]]; then + if [[ -n "${ARGS[i+1]}" ]]; then + BRANCH_NAME="${ARGS[i+1]}" + fi + break + fi + done + + # Clone repository to temporary directory + TEMP_DIR="/tmp/cyberpanel-installer-$$" + mkdir -p "$TEMP_DIR" + + early_log "๐Ÿ“ฆ Cloning CyberPanel repository (branch: $BRANCH_NAME)..." + if git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" 2>/dev/null; then + SCRIPT_DIR="$TEMP_DIR/cyberpanel" + cd "$SCRIPT_DIR" + early_log "โœ… Repository cloned successfully" + else + # Fallback: try to download install.sh from the old method + early_log "โš ๏ธ Git clone failed, falling back to legacy installer..." + OUTPUT=$(cat /etc/*release 2>/dev/null || echo "") + if echo "$OUTPUT" | grep -qE "(CentOS|AlmaLinux|CloudLinux|Rocky)" ; then + SERVER_OS="CentOS8" + yum install -y -q curl wget 2>/dev/null || true + elif echo "$OUTPUT" | grep -qE "Ubuntu" ; then + SERVER_OS="Ubuntu" + apt install -y -qq wget curl 2>/dev/null || true + else + early_log "โŒ Unable to detect OS for fallback installer" + exit 1 + fi + + rm -f /tmp/cyberpanel.sh + curl --silent -o /tmp/cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null || \ + wget -q -O /tmp/cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null + chmod +x /tmp/cyberpanel.sh + exec /tmp/cyberpanel.sh "$@" + fi +fi + MODULES_DIR="$SCRIPT_DIR/modules" # Colors for output diff --git a/install/install.py b/install/install.py index 1402a8bb9..e2ab05e25 100644 --- a/install/install.py +++ b/install/install.py @@ -1451,9 +1451,189 @@ module cyberpanel_ols { except: return False + def checkExistingMariaDB(self): + """Check if MariaDB/MySQL is already installed and return version info""" + try: + # Check if MariaDB/MySQL server package is installed + if self.distro == ubuntu: + command = 'dpkg -l | grep -iE "^(ii|rc).*mariadb-server|mysql-server" 2>/dev/null || echo ""' + else: + command = 'rpm -qa | grep -iE "^(mariadb-server|mysql-server|MariaDB-server)" 2>/dev/null || echo ""' + + result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True) + + if result.stdout.strip(): + # MariaDB/MySQL server package is installed, get version + version_command = 'mysql --version 2>/dev/null || mariadb --version 2>/dev/null || echo ""' + version_result = subprocess.run(version_command, shell=True, capture_output=True, universal_newlines=True) + version_output = version_result.stdout.strip() + + if version_output: + # Extract version number (e.g., "10.11.15" from "mysql Ver 10.11.15-MariaDB") + import re + version_match = re.search(r'(\d+\.\d+\.\d+)', version_output) + if version_match: + installed_version = version_match.group(1) + major_minor = '.'.join(installed_version.split('.')[:2]) # e.g., "10.11" + logging.InstallLog.writeToFile(f"Found existing MariaDB installation: {installed_version} (major.minor: {major_minor})") + return True, installed_version, major_minor + + logging.InstallLog.writeToFile("Found MariaDB/MySQL package but could not determine version") + return True, "unknown", "unknown" + + # Also check if MariaDB service exists and data directory exists (might be installed but package query failed) + if os.path.exists('/var/lib/mysql') and os.listdir('/var/lib/mysql'): + logging.InstallLog.writeToFile("Found MariaDB data directory, assuming MariaDB is installed") + # Try to get version one more time + version_command = 'mysql --version 2>/dev/null || mariadb --version 2>/dev/null || echo ""' + version_result = subprocess.run(version_command, shell=True, capture_output=True, universal_newlines=True) + version_output = version_result.stdout.strip() + if version_output: + import re + version_match = re.search(r'(\d+\.\d+\.\d+)', version_output) + if version_match: + installed_version = version_match.group(1) + major_minor = '.'.join(installed_version.split('.')[:2]) + return True, installed_version, major_minor + return True, "unknown", "unknown" + + return False, None, None + except Exception as e: + logging.InstallLog.writeToFile(f"Error checking existing MariaDB: {str(e)}") + return False, None, None + + def _attemptMariaDBUpgrade(self): + """Attempt to upgrade MariaDB to 12.1. Returns True if successful, False otherwise.""" + try: + if self.distro == ubuntu: + # Ubuntu MariaDB upgrade + command = 'DEBIAN_FRONTEND=noninteractive apt-get install software-properties-common apt-transport-https curl -y' + result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True) + if result.returncode != 0: + logging.InstallLog.writeToFile(f"Failed to install prerequisites: {result.stderr}") + return False + + command = "mkdir -p /etc/apt/keyrings" + subprocess.run(command, shell=True, check=False) + + command = "curl -o /etc/apt/keyrings/mariadb-keyring.pgp 'https://mariadb.org/mariadb_release_signing_key.pgp'" + result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True) + if result.returncode != 0: + logging.InstallLog.writeToFile(f"Failed to download MariaDB keyring: {result.stderr}") + return False + + # Setup MariaDB 12.1 repository + command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=12.1' + result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True) + if result.returncode != 0: + logging.InstallLog.writeToFile(f"Failed to setup MariaDB repository: {result.stderr}") + return False + + command = 'DEBIAN_FRONTEND=noninteractive apt-get update -y' + result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True) + if result.returncode != 0: + logging.InstallLog.writeToFile(f"Failed to update package list: {result.stderr}") + return False + + # Attempt to install MariaDB 12.1 + command = "DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server -y" + result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True) + if result.returncode != 0: + # Check if error is due to upgrade restrictions + error_output = result.stderr + result.stdout + if "upgrade" in error_output.lower() or "manual" in error_output.lower(): + logging.InstallLog.writeToFile("MariaDB upgrade blocked - requires manual intervention") + else: + logging.InstallLog.writeToFile(f"MariaDB installation failed: {error_output}") + return False + + return True + else: + # RHEL-based MariaDB upgrade + # Setup MariaDB 12.1 repository + command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=12.1' + result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True) + if result.returncode != 0: + logging.InstallLog.writeToFile(f"Failed to setup MariaDB repository: {result.stderr}") + return False + + # Attempt to install MariaDB 12.1 + # Use --allowerasing to allow package replacements if needed + command = 'dnf install mariadb-server mariadb-devel mariadb-client-utils -y --allowerasing' + result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True) + if result.returncode != 0: + # Check if error is due to upgrade restrictions + error_output = result.stderr + result.stdout + if "PREIN scriptlet failed" in error_output or "upgrade" in error_output.lower() or "manual" in error_output.lower(): + logging.InstallLog.writeToFile("MariaDB upgrade blocked by package manager - requires manual intervention") + else: + logging.InstallLog.writeToFile(f"MariaDB installation failed: {error_output}") + return False + + return True + + except Exception as e: + logging.InstallLog.writeToFile(f"Exception during MariaDB upgrade attempt: {str(e)}") + return False + def installMySQL(self, mysql): """Install MySQL/MariaDB""" try: + # Check if MariaDB is already installed + is_installed, installed_version, major_minor = self.checkExistingMariaDB() + + if is_installed: + self.stdOut(f"MariaDB/MySQL is already installed (version: {installed_version})", 1) + + # Check if we need to upgrade + should_try_upgrade = False + if major_minor and major_minor != "unknown": + try: + major_ver = float(major_minor) + if major_ver < 12.0: + should_try_upgrade = True + self.stdOut(f"Existing MariaDB {major_minor} detected. Attempting to upgrade to MariaDB 12.1...", 1) + self.stdOut("If upgrade fails, we will use the existing MariaDB installation.", 1) + except (ValueError, TypeError): + pass + + # If MariaDB 10.x is installed, try to upgrade to 12.1 first + if should_try_upgrade: + try: + self.stdOut("Attempting to install MariaDB 12.1...", 1) + upgrade_success = self._attemptMariaDBUpgrade() + if upgrade_success: + self.stdOut("โœ… Successfully upgraded to MariaDB 12.1", 1) + self.startMariaDB() + self.changeMYSQLRootPassword() + self.fixMariaDB() + return True + else: + self.stdOut("โš ๏ธ MariaDB 12.1 upgrade failed, using existing MariaDB installation", 1) + self.startMariaDB() + return True + except Exception as upgrade_error: + error_msg = str(upgrade_error) + logging.InstallLog.writeToFile(f"MariaDB upgrade attempt failed: {error_msg}") + + # Check if error is due to upgrade restrictions + if "PREIN scriptlet failed" in error_msg or "upgrade" in error_msg.lower() or "manual" in error_msg.lower(): + self.stdOut("โš ๏ธ MariaDB upgrade blocked by package manager (10.x to 12.x requires manual upgrade)", 1) + self.stdOut(f"Using existing MariaDB {installed_version} installation", 1) + else: + self.stdOut(f"โš ๏ธ MariaDB upgrade failed: {error_msg}", 1) + self.stdOut(f"Using existing MariaDB {installed_version} installation", 1) + + # Fall back to existing installation + self.startMariaDB() + return True + + # MariaDB 12.x or higher already installed, or version unknown but working + # Just ensure it's running + self.stdOut("Using existing MariaDB installation", 1) + self.startMariaDB() + return True + self.stdOut("Installing MySQL/MariaDB...", 1) if self.distro == ubuntu: diff --git a/to-do/PAID-PLUGINS-GUIDE.md b/to-do/PAID-PLUGINS-GUIDE.md new file mode 100644 index 000000000..146ee81f1 --- /dev/null +++ b/to-do/PAID-PLUGINS-GUIDE.md @@ -0,0 +1,214 @@ +# Paid Plugins Support for CyberPanel + +## Overview + +CyberPanel now supports paid plugins that require Patreon subscription. Users can install paid plugins, but they cannot run them without an active Patreon subscription to the specified tier. + +## Features + +- โœ… Paid plugin detection from `meta.xml` +- โœ… Patreon subscription verification +- โœ… Installable but non-functional without subscription +- โœ… Visual indicators (badges) for paid plugins in all views: + - Grid View: Green "Free" or Yellow "Paid" badge next to version + - Table View: Green "Free" or Yellow "Paid" badge next to version + - Store View: Separate "Pricing" column with Free/Paid badges +- โœ… Subscription required page when accessing without subscription +- โœ… "Subscribe on Patreon" button in subscription warning +- โœ… API endpoint for checking subscription status + +## Plugin Structure + +### Meta.xml for Paid Plugins + +Add the following fields to your plugin's `meta.xml`: + +```xml +true +CyberPanel Paid Plugin +https://www.patreon.com/membership/27789984 +``` + +### Example meta.xml + +```xml + + + Premium Plugin Example + Utility + 1.0.0 + An example paid plugin + master3395 + true + CyberPanel Paid Plugin + https://www.patreon.com/membership/27789984 + /plugins/premiumPlugin/ + /plugins/premiumPlugin/settings/ + +``` + +## Implementation in Plugin Views + +### Using the Premium Plugin Decorator + +```python +from pluginHolder.plugin_access import check_plugin_access + +def premium_plugin_required(view_func): + """ + Decorator that checks if user has Patreon subscription + """ + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + # Check login first + try: + userID = request.session['userID'] + except KeyError: + from loginSystem.views import loadLoginPage + return redirect(loadLoginPage) + + # Check plugin access + plugin_meta = { + 'is_paid': True, + 'patreon_tier': 'CyberPanel Paid Plugin', + 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership' + } + + access_check = check_plugin_access(request, 'yourPluginName', plugin_meta) + + if not access_check['has_access']: + # Show subscription required page + context = { + 'plugin_name': 'Your Plugin Name', + 'is_paid': True, + 'patreon_tier': access_check.get('patreon_tier', 'CyberPanel Paid Plugin'), + 'patreon_url': access_check.get('patreon_url'), + 'message': access_check.get('message', 'Patreon subscription required') + } + proc = httpProc(request, 'yourPlugin/subscription_required.html', context, 'admin') + return proc.render() + + # User has access - proceed + return view_func(request, *args, **kwargs) + + return _wrapped_view + +@cyberpanel_login_required +@premium_plugin_required +def your_view(request): + # Your view code here + pass +``` + +## Patreon Configuration + +### Environment Variables + +Set the following environment variables in CyberPanel: + +```bash +export PATREON_CLIENT_ID="your_client_id" +export PATREON_CLIENT_SECRET="your_client_secret" +export PATREON_CREATOR_ID="your_creator_id" +``` + +Or add them to `/usr/local/CyberCP/CyberCP/settings.py`: + +```python +import os + +PATREON_CLIENT_ID = os.environ.get('PATREON_CLIENT_ID', '') +PATREON_CLIENT_SECRET = os.environ.get('PATREON_CLIENT_SECRET', '') +PATREON_CREATOR_ID = os.environ.get('PATREON_CREATOR_ID', '') +``` + +### User Token Storage + +Users need to authorize CyberPanel to check their Patreon membership. Tokens are stored in: + +``` +/home/cyberpanel/patreon_tokens/{user_email}.token +``` + +## API Endpoints + +### Check Subscription Status + +``` +GET /plugins/api/check-subscription// +``` + +Response: +```json +{ + "success": true, + "has_access": true, + "is_paid": true, + "message": "Access granted", + "patreon_url": null +} +``` + +## Example Plugin + +A complete example paid plugin is available at: + +``` +/home/cyberpanel-plugins/premiumPlugin/ +``` + +This plugin demonstrates: +- Paid plugin meta.xml structure +- Subscription verification +- Subscription required page +- Protected views + +## User Experience + +### For Users Without Subscription + +1. Plugin appears in installed plugins list with "Premium" badge and "Paid" pricing badge +2. Plugin appears in CyberPanel Plugin Store with "Paid" badge in the Pricing column +3. Plugin can be installed +4. When accessing plugin, subscription required page is shown +5. Link to Patreon subscription page is provided with "Subscribe on Patreon" button + +### For Users With Subscription + +1. Plugin works normally +2. All features are accessible +3. Settings page is available +4. No restrictions + +## Files Created/Modified + +### New Files + +- `/home/cyberpanel-repo/pluginHolder/patreon_verifier.py` - Patreon API integration +- `/home/cyberpanel-repo/pluginHolder/plugin_access.py` - Plugin access control +- `/home/cyberpanel-plugins/premiumPlugin/` - Example paid plugin + +### Modified Files + +- `/home/cyberpanel-repo/pluginHolder/views.py` - Added paid plugin parsing and subscription check API +- `/home/cyberpanel-repo/pluginHolder/templates/pluginHolder/plugins.html` - Added paid plugin badges and warnings +- `/home/cyberpanel-repo/pluginHolder/urls.py` - Added subscription check endpoint + +## Testing + +1. Install the example premium plugin +2. Try accessing it without subscription (should show subscription required page) +3. Subscribe to Patreon tier "CyberPanel Paid Plugin" +4. Authorize CyberPanel to check membership +5. Access plugin again (should work normally) + +## Notes + +- Subscription checks are cached for 5 minutes to reduce API calls +- Users must authorize CyberPanel to check their Patreon membership +- The system checks for the exact tier name specified in `patreon_tier` +- Free plugins work normally without any changes + +## Author + +master3395 diff --git a/to-do/PATREON-CONFIGURATION.md b/to-do/PATREON-CONFIGURATION.md new file mode 100644 index 000000000..c29697ce5 --- /dev/null +++ b/to-do/PATREON-CONFIGURATION.md @@ -0,0 +1,58 @@ +# Patreon Configuration for CyberPanel Paid Plugins + +## Configuration Complete + +Patreon credentials have been configured in CyberPanel settings. + +### Credentials Configuration + +**SECURITY WARNING**: Never commit secrets to the repository! + +Set these via environment variables: + +```bash +export PATREON_CLIENT_ID="your_client_id_here" +export PATREON_CLIENT_SECRET="your_client_secret_here" +export PATREON_MEMBERSHIP_TIER_ID="your_tier_id_here" +export PATREON_CREATOR_ACCESS_TOKEN="your_access_token_here" +export PATREON_CREATOR_REFRESH_TOKEN="your_refresh_token_here" +``` + +Or add to `/etc/environment` or systemd service file. + +### Location + +Credentials are stored in: +- `/usr/local/CyberCP/CyberCP/settings.py` (or `/home/cyberpanel-repo/CyberCP/settings.py`) + +### How It Works + +1. Users install paid plugins (no subscription required) +2. When accessing the plugin, system checks for Patreon membership +3. If user has active subscription to tier `27789984`, access is granted +4. If not, subscription required page is shown + +### Testing + +To test the configuration: + +1. Install the `premiumPlugin` example plugin +2. Try accessing it without subscription (should show subscription page) +3. Subscribe to Patreon tier "CyberPanel Paid Plugin" (ID: 27789984) +4. Authorize CyberPanel to check membership +5. Access plugin again (should work) + +### Membership Verification + +The system checks for: +- Tier ID: `27789984` +- Tier Name: "CyberPanel Paid Plugin" (fallback) +- Active patron status +- Currently entitled amount > 0 + +### Notes + +- Membership checks are cached for 5 minutes +- Users must authorize CyberPanel via OAuth to check membership +- Creator access token can be used for server-side verification + diff --git a/to-do/PATREON-SETUP-SECURE.md b/to-do/PATREON-SETUP-SECURE.md new file mode 100644 index 000000000..0779e3601 --- /dev/null +++ b/to-do/PATREON-SETUP-SECURE.md @@ -0,0 +1,115 @@ +# Secure Patreon Configuration Setup + +## โš ๏ธ SECURITY WARNING + +**NEVER commit Patreon secrets to the repository!** + +All secrets must be configured via environment variables on the production server. + +## Setup Instructions + +### 1. Get Your Patreon Credentials + +From your Patreon Developer Dashboard: +- Client ID +- Client Secret +- Membership Tier ID (e.g., `27789984`) +- Creator Access Token (optional, for server-side verification) +- Creator Refresh Token (optional) + +### 2. Configure Environment Variables + +#### Option A: Systemd Service (Recommended) + +Edit `/etc/systemd/system/lscpd.service`: + +```ini +[Service] +Environment="PATREON_CLIENT_ID=your_client_id" +Environment="PATREON_CLIENT_SECRET=your_client_secret" +Environment="PATREON_MEMBERSHIP_TIER_ID=your_tier_id" +Environment="PATREON_CREATOR_ACCESS_TOKEN=your_access_token" +Environment="PATREON_CREATOR_REFRESH_TOKEN=your_refresh_token" +``` + +Then reload and restart: +```bash +systemctl daemon-reload +systemctl restart lscpd +``` + +#### Option B: /etc/environment + +Add to `/etc/environment`: +```bash +PATREON_CLIENT_ID=your_client_id +PATREON_CLIENT_SECRET=your_client_secret +PATREON_MEMBERSHIP_TIER_ID=your_tier_id +PATREON_CREATOR_ACCESS_TOKEN=your_access_token +PATREON_CREATOR_REFRESH_TOKEN=your_refresh_token +``` + +#### Option C: Secure Config File (Not in Repo) + +Create `/usr/local/CyberCP/patreon_config.py` (add to .gitignore): + +```python +# Patreon Configuration - DO NOT COMMIT TO REPOSITORY +PATREON_CLIENT_ID = 'your_client_id' +PATREON_CLIENT_SECRET = 'your_client_secret' +PATREON_MEMBERSHIP_TIER_ID = 'your_tier_id' +PATREON_CREATOR_ACCESS_TOKEN = 'your_access_token' +PATREON_CREATOR_REFRESH_TOKEN = 'your_refresh_token' +``` + +Then import in settings.py: +```python +try: + from .patreon_config import * +except ImportError: + pass # Use environment variables instead +``` + +### 3. Verify Configuration + +Test that secrets are loaded: +```bash +python3 -c " +import os +print('Client ID:', 'SET' if os.environ.get('PATREON_CLIENT_ID') else 'NOT SET') +print('Client Secret:', 'SET' if os.environ.get('PATREON_CLIENT_SECRET') else 'NOT SET') +print('Tier ID:', os.environ.get('PATREON_MEMBERSHIP_TIER_ID', 'NOT SET')) +" +``` + +### 4. Security Checklist + +- [ ] Secrets removed from repository +- [ ] Environment variables set on production server +- [ ] `/usr/local/CyberCP/patreon_config.py` added to .gitignore (if used) +- [ ] CyberPanel service restarted +- [ ] Configuration verified working + +## For Plugin Developers + +When creating paid plugins: + +1. **Never hardcode secrets** in plugin code +2. **Use environment variables** or Django settings +3. **Document required variables** in README +4. **Provide example** with placeholder values only + +Example meta.xml: +```xml +true +Your Tier Name +https://www.patreon.com/c/yourname/membership +``` + +## Troubleshooting + +If membership checks fail: +1. Verify environment variables are set: `env | grep PATREON` +2. Check CyberPanel logs: `/home/lscp/logs/error.log` +3. Verify tier ID matches your Patreon tier +4. Ensure user has authorized OAuth access diff --git a/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md b/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md new file mode 100644 index 000000000..053149677 --- /dev/null +++ b/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md @@ -0,0 +1,89 @@ +# Remote Verification Architecture for Paid Plugins + +## Problem + +When users install plugins, they can see all files on their system, which could expose: +- Patreon API credentials +- Verification logic +- Access tokens + +## Solution: Remote Verification Server + +Move all Patreon verification to **YOUR server** (not the user's server). + +### Architecture + +``` +User's Server (Plugin) Your Server Patreon API + | | | + |-- Verify Request ----------->| | + | |-- Check Membership --->| + | |<-- Membership Status --| + |<-- Access Granted/Denied ----| | +``` + +### Benefits + +1. **No secrets on user's server** - All credentials stay on your server +2. **Users can't intercept** - Verification happens server-to-server +3. **Centralized control** - You can revoke access, update logic, etc. +4. **Plugin code can be public** - Only makes API calls, no secrets + +## Implementation + +### 1. Your Verification Server + +Create an API endpoint on your server (e.g., `api.newstargeted.com`): + +```python +# Your server endpoint: /api/verify-patreon-membership +POST /api/verify-patreon-membership +{ + "user_email": "user@example.com", + "plugin_name": "premiumPlugin", + "tier_id": "27789984" +} + +Response: +{ + "has_access": true, + "expires_at": "2026-02-25T00:00:00Z" +} +``` + +### 2. Plugin Code (Public, No Secrets) + +The plugin only makes HTTP requests to your server: + +```python +def check_remote_membership(user_email, plugin_name): + response = requests.post( + 'https://api.newstargeted.com/api/verify-patreon-membership', + json={ + 'user_email': user_email, + 'plugin_name': plugin_name, + 'tier_id': '27789984' + }, + headers={'X-Plugin-Version': '1.0.0'} + ) + return response.json() +``` + +### 3. Security Measures + +- **Rate limiting** - Prevent abuse +- **IP whitelisting** - Only allow from CyberPanel servers (optional) +- **Plugin signature** - Verify requests come from legitimate plugins +- **Caching** - Reduce API calls to Patreon +- **HTTPS only** - Encrypt all communication + +## Alternative: Encrypted Plugin Package + +If you want to encrypt the entire plugin: + +1. **Encrypt plugin files** before distribution +2. **Decrypt on install** using a license key +3. **License key** tied to user's Patreon subscription +4. **Your server** generates license keys + +This is more complex but provides stronger protection. diff --git a/to-do/REMOTE-VERIFICATION-COMPLETE.md b/to-do/REMOTE-VERIFICATION-COMPLETE.md new file mode 100644 index 000000000..2012db765 --- /dev/null +++ b/to-do/REMOTE-VERIFICATION-COMPLETE.md @@ -0,0 +1,115 @@ +# Remote Verification Setup Complete โœ… + +## Summary + +All components are now set up for secure remote verification of paid plugins. Users can install plugins and see all code, but **NO secrets are exposed** because all Patreon API calls happen on YOUR server. + +## โœ… What Was Completed + +### 1. Remote Verification API +- **Endpoint**: `https://api.newstargeted.com/api/verify-patreon-membership` +- **Location**: `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php` +- **Status**: โœ… Working and tested +- **Permissions**: โœ… newst3922:newst3922 (644) + +### 2. Plugin Updated +- **File**: `/home/cyberpanel-plugins/premiumPlugin/views.py` +- **Method**: Remote verification (no secrets) +- **Status**: โœ… Updated and tested +- **Permissions**: โœ… newst3922:newst3922 (644) + +### 3. Configuration +- **Patreon credentials**: Added to `/home/newstargeted.com/api.newstargeted.com/config.php` +- **Permissions**: โœ… 600 (secure, readable only by owner) +- **Owner**: โœ… newst3922:newst3922 + +### 4. Routing +- **.htaccess**: Updated with API routing rules +- **API Router**: Created at `/api/index.php` +- **Status**: โœ… Working + +## ๐Ÿ”’ Security Features + +โœ… **No secrets in plugin** - All code is public-safe +โœ… **Credentials on your server only** - Never exposed to users +โœ… **Rate limiting** - 60 requests/hour per IP +โœ… **Caching** - 5 minute cache to reduce API calls +โœ… **HTTPS only** - All communication encrypted +โœ… **Proper permissions** - Config files protected (600) + +## ๐Ÿ“ File Permissions Summary + +### API Files +- `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php`: 644, newst3922:newst3922 +- `/home/newstargeted.com/api.newstargeted.com/api/index.php`: 644, newst3922:newst3922 +- `/home/newstargeted.com/api.newstargeted.com/config.php`: 600, newst3922:newst3922 (secure) +- `/home/newstargeted.com/api.newstargeted.com/.htaccess`: 644, newst3922:newst3922 + +### Plugin Files +- `/home/cyberpanel-plugins/premiumPlugin/`: All files 644, directories 755, newst3922:newst3922 +- `/home/cyberpanel/plugins/premiumPlugin/`: All files 644, directories 755, newst3922:newst3922 + +## ๐Ÿงช Testing + +### API Endpoint Test +```bash +curl -X POST https://api.newstargeted.com/api/verify-patreon-membership \ + -H "Content-Type: application/json" \ + -d '{"user_email":"test@example.com","plugin_name":"premiumPlugin","tier_id":"27789984"}' +``` + +**Result**: โœ… Returns proper JSON response + +### Plugin Test +1. Install plugin from CyberPanel +2. Try accessing plugin +3. Should show subscription required page (if not subscribed) +4. Plugin makes API call to your server (no secrets exposed) + +## ๐Ÿ“‹ Next Steps + +1. **Implement OAuth Flow** (optional but recommended) + - Users authorize CyberPanel via Patreon OAuth + - Store access tokens securely (database recommended) + - Link tokens to user emails + +2. **Test Full Flow** + - Subscribe to Patreon tier + - Authorize CyberPanel + - Access plugin (should work) + +3. **Monitor** + - Check API logs for errors + - Monitor rate limiting + - Verify caching is working + +## ๐ŸŽฏ Plugin is Safe to Publish + +The `premiumPlugin` can now be: +- โœ… Published to public repositories +- โœ… Shared with users +- โœ… Installed on any server +- โœ… Code reviewed by anyone + +**No secrets will be exposed** because all verification happens on your server! + +## ๐Ÿ“ Files Created/Modified + +### Created +- `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php` +- `/home/newstargeted.com/api.newstargeted.com/api/index.php` +- `/home/cyberpanel-plugins/premiumPlugin/views.py` (remote version) +- `/home/cyberpanel-plugins/premiumPlugin/SECURITY.md` + +### Modified +- `/home/newstargeted.com/api.newstargeted.com/config.php` (added Patreon credentials) +- `/home/newstargeted.com/api.newstargeted.com/.htaccess` (added API routing) +- `/home/cyberpanel-plugins/premiumPlugin/views.py` (updated to remote verification) + +## โœจ Benefits + +1. **Security**: Secrets never leave your server +2. **Control**: You can revoke access, update logic centrally +3. **Transparency**: Plugin code can be open source +4. **Scalability**: Centralized verification handles all requests +5. **Maintenance**: Update verification logic in one place diff --git a/to-do/SETUP-COMPLETE.md b/to-do/SETUP-COMPLETE.md new file mode 100644 index 000000000..c344e6eb1 --- /dev/null +++ b/to-do/SETUP-COMPLETE.md @@ -0,0 +1,86 @@ +# Remote Verification Setup Complete โœ… + +## What Was Done + +### 1. Remote Verification API Created +- **Location**: `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php` +- **URL**: `https://api.newstargeted.com/api/verify-patreon-membership` +- **Route**: Added to `.htaccess` for clean URL routing + +### 2. Plugin Updated to Use Remote Verification +- **File**: `/home/cyberpanel-plugins/premiumPlugin/views.py` +- **Method**: All Patreon checks now go through your server +- **No Secrets**: Plugin code contains zero credentials + +### 3. Configuration Added +- **Patreon credentials** added to `/home/newstargeted.com/api.newstargeted.com/config.php` +- **Secure permissions**: config.php set to 600 (readable only by owner) + +### 4. File Permissions Set +- โœ… API files: `newst3922:newst3922` (644 for files, 755 for directories) +- โœ… Plugin files: `newst3922:newst3922` (644 for files, 755 for directories) +- โœ… Config file: `newst3922:newst3922` (600 - secure) + +## Security Features + +โœ… **No secrets in plugin** - Users can see all code +โœ… **All credentials on your server** - Never exposed +โœ… **Rate limiting** - 60 requests/hour per IP +โœ… **Caching** - 5 minute cache to reduce API calls +โœ… **HTTPS only** - All communication encrypted +โœ… **Error handling** - Graceful failures + +## How It Works + +1. User installs plugin (no subscription needed) +2. User tries to access plugin +3. Plugin makes API call to YOUR server +4. Your server checks Patreon API (credentials stay on your server) +5. Your server returns access status +6. Plugin shows content or subscription page + +## Testing + +### Test API Endpoint +```bash +curl -X POST https://api.newstargeted.com/api/verify-patreon-membership \ + -H "Content-Type: application/json" \ + -d '{ + "user_email": "test@example.com", + "plugin_name": "premiumPlugin", + "tier_id": "27789984" + }' +``` + +### Expected Response +```json +{ + "success": true, + "has_access": false, + "patreon_tier": "CyberPanel Paid Plugin", + "patreon_url": "https://www.patreon.com/c/newstargeted/membership", + "message": "Patreon subscription required..." +} +``` + +## Next Steps + +1. **Test the API endpoint** - Verify it's accessible +2. **Implement OAuth flow** - For users to authorize Patreon access +3. **Store user tokens** - Link Patreon tokens to user emails +4. **Test full flow** - Install plugin and verify access control + +## Files Modified + +- `/home/newstargeted.com/api.newstargeted.com/config.php` - Added Patreon credentials +- `/home/newstargeted.com/api.newstargeted.com/.htaccess` - Added API route +- `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php` - Created +- `/home/cyberpanel-plugins/premiumPlugin/views.py` - Updated to use remote verification +- `/home/cyberpanel/plugins/premiumPlugin/views.py` - Updated (installed version) + +## Plugin is Now Safe to Publish + +โœ… No secrets in code +โœ… All verification happens on your server +โœ… Users can see all plugin files without security risk +โœ… Centralized control and updates diff --git a/to-do/v2.5.5-dev-installation-fixes.md b/to-do/v2.5.5-dev-installation-fixes.md new file mode 100644 index 000000000..23cdc92c5 --- /dev/null +++ b/to-do/v2.5.5-dev-installation-fixes.md @@ -0,0 +1,132 @@ +# CyberPanel v2.5.5-dev Installation Fixes + +## Issues Identified + +### Issue 1: install.sh Module Loading Failure +**Problem**: When executing `install.sh` via `sh <(curl ...)`, the script tries to load modules from `/dev/fd/.../modules/` which doesn't exist because the script is executed directly from stdin. + +**Error Message**: +``` +โŒ Module not found: /dev/fd/modules/os/detect.sh +โŒ Failed to load OS detection module +``` + +**Root Cause**: The script assumes it's running from a cloned repository directory, but when executed via curl, `SCRIPT_DIR` becomes `/dev/fd/...` which doesn't contain the modules. + +### Issue 2: MariaDB 10.x to 12.x Upgrade Blocked +**Problem**: The installer attempts to install MariaDB 12.1 on systems that already have MariaDB 10.11.15 installed. MariaDB's package pre-installation script blocks the upgrade because direct upgrades from 10.x to 12.x are not safe without manual dump/restore. + +**Error Message**: +``` +error: %prein(MariaDB-server-12.1.2-1.el9.x86_64) scriptlet failed, exit status 1 +Error in PREIN scriptlet in rpm package MariaDB-server +``` + +**Root Cause**: The installer doesn't check for existing MariaDB installations before attempting to install MariaDB 12.1. + +## Fixes Implemented + +### Fix 1: install.sh - Handle Curl/Wget Execution + +**Location**: `install/install.sh` + +**Changes**: +1. Added detection for curl/wget execution (checks if `SCRIPT_DIR` is `/dev/fd/*` or modules directory doesn't exist) +2. When detected, the script now: + - Clones the CyberPanel repository to a temporary directory + - Extracts branch name from command-line arguments + - Falls back to legacy installer if git clone fails +3. Properly handles branch argument parsing for both `-b` and `--branch` flags + +**Code Added**: +```bash +# Check if script is being executed via curl/wget +if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then + # Clone repository first + # ... (see install.sh for full implementation) +fi +``` + +### Fix 2: install.py - MariaDB Installation Detection and Upgrade Attempt + +**Location**: `install/install.py` + +**Changes**: +1. Added `checkExistingMariaDB()` method that: + - Checks for installed MariaDB/MySQL server packages (RPM or DEB) + - Detects MariaDB data directory existence + - Extracts version information from `mysql --version` output + - Returns version info including major.minor version + +2. Added `_attemptMariaDBUpgrade()` method that: + - Attempts to install MariaDB 12.1 on systems with MariaDB 10.x + - Sets up MariaDB 12.1 repository + - Attempts installation with appropriate flags (`--allowerasing` for RHEL) + - Returns `True` if successful, `False` if blocked or fails + - Handles upgrade restriction errors gracefully + +3. Modified `installMySQL()` method to: + - Check for existing MariaDB installation before attempting install + - **If MariaDB 10.x is detected**: Attempt to upgrade to 12.1 first + - If upgrade succeeds: Use new MariaDB 12.1 installation + - If upgrade fails/blocked: Fall back to existing MariaDB 10.x + - If MariaDB 12.x or higher is detected: Skip installation and use existing + - Only attempt new installation if no MariaDB is found + +**Key Logic**: +```python +# Check if MariaDB is already installed +is_installed, installed_version, major_minor = self.checkExistingMariaDB() + +if is_installed: + if major_minor and major_minor != "unknown": + major_ver = float(major_minor) + if major_ver < 12.0: + # Try to upgrade to 12.1 first + upgrade_success = self._attemptMariaDBUpgrade() + if upgrade_success: + # Use new MariaDB 12.1 + return True + else: + # Fall back to existing MariaDB 10.x + self.startMariaDB() + return True + # Use existing MariaDB 12.x+ installation + self.startMariaDB() + return True +``` + +## Testing Recommendations + +1. **Test install.sh with curl**: + ```bash + sh <(curl -s https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/install.sh) + ``` + +2. **Test on AlmaLinux 9 with existing MariaDB 10.x**: + - System should detect existing MariaDB 10.x + - Should attempt to upgrade to MariaDB 12.1 first + - If upgrade is blocked, should fall back to using existing MariaDB 10.x + - Should log appropriate messages about upgrade attempt and fallback + +3. **Test on clean AlmaLinux 9 system**: + - Should install MariaDB 12.1 successfully + - Should complete full installation + +## Files Modified + +1. `install/install.sh` - Added curl/wget execution handling +2. `install/install.py` - Added MariaDB detection and existing installation handling + +## Notes + +- The installer now **attempts to upgrade MariaDB 10.x to 12.1 first**, and only falls back to using existing 10.x if the upgrade is blocked by the package manager +- This provides the best of both worlds: tries to get the latest version, but safely falls back if upgrade restrictions prevent it +- The upgrade attempt uses `--allowerasing` flag for RHEL-based systems to allow package replacements +- If MariaDB's pre-installation scriptlet blocks the upgrade (as it does for 10.x to 12.x), the installer gracefully falls back to the existing installation +- The curl/wget execution now properly clones the repository before attempting to load modules + +## Related Issues + +- MariaDB 10.x to 12.x upgrades require manual dump/restore per MariaDB documentation +- The installer now respects existing installations to prevent data loss From 399cb06f509ca9ea23d93f047ce22ef5b21cc021 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:40:59 +0100 Subject: [PATCH 03/13] Remove to-do folder and documentation files --- to-do/PAID-PLUGINS-GUIDE.md | 214 ---------------------- to-do/PATREON-CONFIGURATION.md | 58 ------ to-do/PATREON-SETUP-SECURE.md | 115 ------------ to-do/REMOTE-VERIFICATION-ARCHITECTURE.md | 89 --------- to-do/REMOTE-VERIFICATION-COMPLETE.md | 115 ------------ to-do/SETUP-COMPLETE.md | 86 --------- to-do/v2.5.5-dev-installation-fixes.md | 132 ------------- 7 files changed, 809 deletions(-) delete mode 100644 to-do/PAID-PLUGINS-GUIDE.md delete mode 100644 to-do/PATREON-CONFIGURATION.md delete mode 100644 to-do/PATREON-SETUP-SECURE.md delete mode 100644 to-do/REMOTE-VERIFICATION-ARCHITECTURE.md delete mode 100644 to-do/REMOTE-VERIFICATION-COMPLETE.md delete mode 100644 to-do/SETUP-COMPLETE.md delete mode 100644 to-do/v2.5.5-dev-installation-fixes.md diff --git a/to-do/PAID-PLUGINS-GUIDE.md b/to-do/PAID-PLUGINS-GUIDE.md deleted file mode 100644 index 146ee81f1..000000000 --- a/to-do/PAID-PLUGINS-GUIDE.md +++ /dev/null @@ -1,214 +0,0 @@ -# Paid Plugins Support for CyberPanel - -## Overview - -CyberPanel now supports paid plugins that require Patreon subscription. Users can install paid plugins, but they cannot run them without an active Patreon subscription to the specified tier. - -## Features - -- โœ… Paid plugin detection from `meta.xml` -- โœ… Patreon subscription verification -- โœ… Installable but non-functional without subscription -- โœ… Visual indicators (badges) for paid plugins in all views: - - Grid View: Green "Free" or Yellow "Paid" badge next to version - - Table View: Green "Free" or Yellow "Paid" badge next to version - - Store View: Separate "Pricing" column with Free/Paid badges -- โœ… Subscription required page when accessing without subscription -- โœ… "Subscribe on Patreon" button in subscription warning -- โœ… API endpoint for checking subscription status - -## Plugin Structure - -### Meta.xml for Paid Plugins - -Add the following fields to your plugin's `meta.xml`: - -```xml -true -CyberPanel Paid Plugin -https://www.patreon.com/membership/27789984 -``` - -### Example meta.xml - -```xml - - - Premium Plugin Example - Utility - 1.0.0 - An example paid plugin - master3395 - true - CyberPanel Paid Plugin - https://www.patreon.com/membership/27789984 - /plugins/premiumPlugin/ - /plugins/premiumPlugin/settings/ - -``` - -## Implementation in Plugin Views - -### Using the Premium Plugin Decorator - -```python -from pluginHolder.plugin_access import check_plugin_access - -def premium_plugin_required(view_func): - """ - Decorator that checks if user has Patreon subscription - """ - @wraps(view_func) - def _wrapped_view(request, *args, **kwargs): - # Check login first - try: - userID = request.session['userID'] - except KeyError: - from loginSystem.views import loadLoginPage - return redirect(loadLoginPage) - - # Check plugin access - plugin_meta = { - 'is_paid': True, - 'patreon_tier': 'CyberPanel Paid Plugin', - 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership' - } - - access_check = check_plugin_access(request, 'yourPluginName', plugin_meta) - - if not access_check['has_access']: - # Show subscription required page - context = { - 'plugin_name': 'Your Plugin Name', - 'is_paid': True, - 'patreon_tier': access_check.get('patreon_tier', 'CyberPanel Paid Plugin'), - 'patreon_url': access_check.get('patreon_url'), - 'message': access_check.get('message', 'Patreon subscription required') - } - proc = httpProc(request, 'yourPlugin/subscription_required.html', context, 'admin') - return proc.render() - - # User has access - proceed - return view_func(request, *args, **kwargs) - - return _wrapped_view - -@cyberpanel_login_required -@premium_plugin_required -def your_view(request): - # Your view code here - pass -``` - -## Patreon Configuration - -### Environment Variables - -Set the following environment variables in CyberPanel: - -```bash -export PATREON_CLIENT_ID="your_client_id" -export PATREON_CLIENT_SECRET="your_client_secret" -export PATREON_CREATOR_ID="your_creator_id" -``` - -Or add them to `/usr/local/CyberCP/CyberCP/settings.py`: - -```python -import os - -PATREON_CLIENT_ID = os.environ.get('PATREON_CLIENT_ID', '') -PATREON_CLIENT_SECRET = os.environ.get('PATREON_CLIENT_SECRET', '') -PATREON_CREATOR_ID = os.environ.get('PATREON_CREATOR_ID', '') -``` - -### User Token Storage - -Users need to authorize CyberPanel to check their Patreon membership. Tokens are stored in: - -``` -/home/cyberpanel/patreon_tokens/{user_email}.token -``` - -## API Endpoints - -### Check Subscription Status - -``` -GET /plugins/api/check-subscription// -``` - -Response: -```json -{ - "success": true, - "has_access": true, - "is_paid": true, - "message": "Access granted", - "patreon_url": null -} -``` - -## Example Plugin - -A complete example paid plugin is available at: - -``` -/home/cyberpanel-plugins/premiumPlugin/ -``` - -This plugin demonstrates: -- Paid plugin meta.xml structure -- Subscription verification -- Subscription required page -- Protected views - -## User Experience - -### For Users Without Subscription - -1. Plugin appears in installed plugins list with "Premium" badge and "Paid" pricing badge -2. Plugin appears in CyberPanel Plugin Store with "Paid" badge in the Pricing column -3. Plugin can be installed -4. When accessing plugin, subscription required page is shown -5. Link to Patreon subscription page is provided with "Subscribe on Patreon" button - -### For Users With Subscription - -1. Plugin works normally -2. All features are accessible -3. Settings page is available -4. No restrictions - -## Files Created/Modified - -### New Files - -- `/home/cyberpanel-repo/pluginHolder/patreon_verifier.py` - Patreon API integration -- `/home/cyberpanel-repo/pluginHolder/plugin_access.py` - Plugin access control -- `/home/cyberpanel-plugins/premiumPlugin/` - Example paid plugin - -### Modified Files - -- `/home/cyberpanel-repo/pluginHolder/views.py` - Added paid plugin parsing and subscription check API -- `/home/cyberpanel-repo/pluginHolder/templates/pluginHolder/plugins.html` - Added paid plugin badges and warnings -- `/home/cyberpanel-repo/pluginHolder/urls.py` - Added subscription check endpoint - -## Testing - -1. Install the example premium plugin -2. Try accessing it without subscription (should show subscription required page) -3. Subscribe to Patreon tier "CyberPanel Paid Plugin" -4. Authorize CyberPanel to check membership -5. Access plugin again (should work normally) - -## Notes - -- Subscription checks are cached for 5 minutes to reduce API calls -- Users must authorize CyberPanel to check their Patreon membership -- The system checks for the exact tier name specified in `patreon_tier` -- Free plugins work normally without any changes - -## Author - -master3395 diff --git a/to-do/PATREON-CONFIGURATION.md b/to-do/PATREON-CONFIGURATION.md deleted file mode 100644 index c29697ce5..000000000 --- a/to-do/PATREON-CONFIGURATION.md +++ /dev/null @@ -1,58 +0,0 @@ -# Patreon Configuration for CyberPanel Paid Plugins - -## Configuration Complete - -Patreon credentials have been configured in CyberPanel settings. - -### Credentials Configuration - -**SECURITY WARNING**: Never commit secrets to the repository! - -Set these via environment variables: - -```bash -export PATREON_CLIENT_ID="your_client_id_here" -export PATREON_CLIENT_SECRET="your_client_secret_here" -export PATREON_MEMBERSHIP_TIER_ID="your_tier_id_here" -export PATREON_CREATOR_ACCESS_TOKEN="your_access_token_here" -export PATREON_CREATOR_REFRESH_TOKEN="your_refresh_token_here" -``` - -Or add to `/etc/environment` or systemd service file. - -### Location - -Credentials are stored in: -- `/usr/local/CyberCP/CyberCP/settings.py` (or `/home/cyberpanel-repo/CyberCP/settings.py`) - -### How It Works - -1. Users install paid plugins (no subscription required) -2. When accessing the plugin, system checks for Patreon membership -3. If user has active subscription to tier `27789984`, access is granted -4. If not, subscription required page is shown - -### Testing - -To test the configuration: - -1. Install the `premiumPlugin` example plugin -2. Try accessing it without subscription (should show subscription page) -3. Subscribe to Patreon tier "CyberPanel Paid Plugin" (ID: 27789984) -4. Authorize CyberPanel to check membership -5. Access plugin again (should work) - -### Membership Verification - -The system checks for: -- Tier ID: `27789984` -- Tier Name: "CyberPanel Paid Plugin" (fallback) -- Active patron status -- Currently entitled amount > 0 - -### Notes - -- Membership checks are cached for 5 minutes -- Users must authorize CyberPanel via OAuth to check membership -- Creator access token can be used for server-side verification - diff --git a/to-do/PATREON-SETUP-SECURE.md b/to-do/PATREON-SETUP-SECURE.md deleted file mode 100644 index 0779e3601..000000000 --- a/to-do/PATREON-SETUP-SECURE.md +++ /dev/null @@ -1,115 +0,0 @@ -# Secure Patreon Configuration Setup - -## โš ๏ธ SECURITY WARNING - -**NEVER commit Patreon secrets to the repository!** - -All secrets must be configured via environment variables on the production server. - -## Setup Instructions - -### 1. Get Your Patreon Credentials - -From your Patreon Developer Dashboard: -- Client ID -- Client Secret -- Membership Tier ID (e.g., `27789984`) -- Creator Access Token (optional, for server-side verification) -- Creator Refresh Token (optional) - -### 2. Configure Environment Variables - -#### Option A: Systemd Service (Recommended) - -Edit `/etc/systemd/system/lscpd.service`: - -```ini -[Service] -Environment="PATREON_CLIENT_ID=your_client_id" -Environment="PATREON_CLIENT_SECRET=your_client_secret" -Environment="PATREON_MEMBERSHIP_TIER_ID=your_tier_id" -Environment="PATREON_CREATOR_ACCESS_TOKEN=your_access_token" -Environment="PATREON_CREATOR_REFRESH_TOKEN=your_refresh_token" -``` - -Then reload and restart: -```bash -systemctl daemon-reload -systemctl restart lscpd -``` - -#### Option B: /etc/environment - -Add to `/etc/environment`: -```bash -PATREON_CLIENT_ID=your_client_id -PATREON_CLIENT_SECRET=your_client_secret -PATREON_MEMBERSHIP_TIER_ID=your_tier_id -PATREON_CREATOR_ACCESS_TOKEN=your_access_token -PATREON_CREATOR_REFRESH_TOKEN=your_refresh_token -``` - -#### Option C: Secure Config File (Not in Repo) - -Create `/usr/local/CyberCP/patreon_config.py` (add to .gitignore): - -```python -# Patreon Configuration - DO NOT COMMIT TO REPOSITORY -PATREON_CLIENT_ID = 'your_client_id' -PATREON_CLIENT_SECRET = 'your_client_secret' -PATREON_MEMBERSHIP_TIER_ID = 'your_tier_id' -PATREON_CREATOR_ACCESS_TOKEN = 'your_access_token' -PATREON_CREATOR_REFRESH_TOKEN = 'your_refresh_token' -``` - -Then import in settings.py: -```python -try: - from .patreon_config import * -except ImportError: - pass # Use environment variables instead -``` - -### 3. Verify Configuration - -Test that secrets are loaded: -```bash -python3 -c " -import os -print('Client ID:', 'SET' if os.environ.get('PATREON_CLIENT_ID') else 'NOT SET') -print('Client Secret:', 'SET' if os.environ.get('PATREON_CLIENT_SECRET') else 'NOT SET') -print('Tier ID:', os.environ.get('PATREON_MEMBERSHIP_TIER_ID', 'NOT SET')) -" -``` - -### 4. Security Checklist - -- [ ] Secrets removed from repository -- [ ] Environment variables set on production server -- [ ] `/usr/local/CyberCP/patreon_config.py` added to .gitignore (if used) -- [ ] CyberPanel service restarted -- [ ] Configuration verified working - -## For Plugin Developers - -When creating paid plugins: - -1. **Never hardcode secrets** in plugin code -2. **Use environment variables** or Django settings -3. **Document required variables** in README -4. **Provide example** with placeholder values only - -Example meta.xml: -```xml -true -Your Tier Name -https://www.patreon.com/c/yourname/membership -``` - -## Troubleshooting - -If membership checks fail: -1. Verify environment variables are set: `env | grep PATREON` -2. Check CyberPanel logs: `/home/lscp/logs/error.log` -3. Verify tier ID matches your Patreon tier -4. Ensure user has authorized OAuth access diff --git a/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md b/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md deleted file mode 100644 index 053149677..000000000 --- a/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md +++ /dev/null @@ -1,89 +0,0 @@ -# Remote Verification Architecture for Paid Plugins - -## Problem - -When users install plugins, they can see all files on their system, which could expose: -- Patreon API credentials -- Verification logic -- Access tokens - -## Solution: Remote Verification Server - -Move all Patreon verification to **YOUR server** (not the user's server). - -### Architecture - -``` -User's Server (Plugin) Your Server Patreon API - | | | - |-- Verify Request ----------->| | - | |-- Check Membership --->| - | |<-- Membership Status --| - |<-- Access Granted/Denied ----| | -``` - -### Benefits - -1. **No secrets on user's server** - All credentials stay on your server -2. **Users can't intercept** - Verification happens server-to-server -3. **Centralized control** - You can revoke access, update logic, etc. -4. **Plugin code can be public** - Only makes API calls, no secrets - -## Implementation - -### 1. Your Verification Server - -Create an API endpoint on your server (e.g., `api.newstargeted.com`): - -```python -# Your server endpoint: /api/verify-patreon-membership -POST /api/verify-patreon-membership -{ - "user_email": "user@example.com", - "plugin_name": "premiumPlugin", - "tier_id": "27789984" -} - -Response: -{ - "has_access": true, - "expires_at": "2026-02-25T00:00:00Z" -} -``` - -### 2. Plugin Code (Public, No Secrets) - -The plugin only makes HTTP requests to your server: - -```python -def check_remote_membership(user_email, plugin_name): - response = requests.post( - 'https://api.newstargeted.com/api/verify-patreon-membership', - json={ - 'user_email': user_email, - 'plugin_name': plugin_name, - 'tier_id': '27789984' - }, - headers={'X-Plugin-Version': '1.0.0'} - ) - return response.json() -``` - -### 3. Security Measures - -- **Rate limiting** - Prevent abuse -- **IP whitelisting** - Only allow from CyberPanel servers (optional) -- **Plugin signature** - Verify requests come from legitimate plugins -- **Caching** - Reduce API calls to Patreon -- **HTTPS only** - Encrypt all communication - -## Alternative: Encrypted Plugin Package - -If you want to encrypt the entire plugin: - -1. **Encrypt plugin files** before distribution -2. **Decrypt on install** using a license key -3. **License key** tied to user's Patreon subscription -4. **Your server** generates license keys - -This is more complex but provides stronger protection. diff --git a/to-do/REMOTE-VERIFICATION-COMPLETE.md b/to-do/REMOTE-VERIFICATION-COMPLETE.md deleted file mode 100644 index 2012db765..000000000 --- a/to-do/REMOTE-VERIFICATION-COMPLETE.md +++ /dev/null @@ -1,115 +0,0 @@ -# Remote Verification Setup Complete โœ… - -## Summary - -All components are now set up for secure remote verification of paid plugins. Users can install plugins and see all code, but **NO secrets are exposed** because all Patreon API calls happen on YOUR server. - -## โœ… What Was Completed - -### 1. Remote Verification API -- **Endpoint**: `https://api.newstargeted.com/api/verify-patreon-membership` -- **Location**: `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php` -- **Status**: โœ… Working and tested -- **Permissions**: โœ… newst3922:newst3922 (644) - -### 2. Plugin Updated -- **File**: `/home/cyberpanel-plugins/premiumPlugin/views.py` -- **Method**: Remote verification (no secrets) -- **Status**: โœ… Updated and tested -- **Permissions**: โœ… newst3922:newst3922 (644) - -### 3. Configuration -- **Patreon credentials**: Added to `/home/newstargeted.com/api.newstargeted.com/config.php` -- **Permissions**: โœ… 600 (secure, readable only by owner) -- **Owner**: โœ… newst3922:newst3922 - -### 4. Routing -- **.htaccess**: Updated with API routing rules -- **API Router**: Created at `/api/index.php` -- **Status**: โœ… Working - -## ๐Ÿ”’ Security Features - -โœ… **No secrets in plugin** - All code is public-safe -โœ… **Credentials on your server only** - Never exposed to users -โœ… **Rate limiting** - 60 requests/hour per IP -โœ… **Caching** - 5 minute cache to reduce API calls -โœ… **HTTPS only** - All communication encrypted -โœ… **Proper permissions** - Config files protected (600) - -## ๐Ÿ“ File Permissions Summary - -### API Files -- `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php`: 644, newst3922:newst3922 -- `/home/newstargeted.com/api.newstargeted.com/api/index.php`: 644, newst3922:newst3922 -- `/home/newstargeted.com/api.newstargeted.com/config.php`: 600, newst3922:newst3922 (secure) -- `/home/newstargeted.com/api.newstargeted.com/.htaccess`: 644, newst3922:newst3922 - -### Plugin Files -- `/home/cyberpanel-plugins/premiumPlugin/`: All files 644, directories 755, newst3922:newst3922 -- `/home/cyberpanel/plugins/premiumPlugin/`: All files 644, directories 755, newst3922:newst3922 - -## ๐Ÿงช Testing - -### API Endpoint Test -```bash -curl -X POST https://api.newstargeted.com/api/verify-patreon-membership \ - -H "Content-Type: application/json" \ - -d '{"user_email":"test@example.com","plugin_name":"premiumPlugin","tier_id":"27789984"}' -``` - -**Result**: โœ… Returns proper JSON response - -### Plugin Test -1. Install plugin from CyberPanel -2. Try accessing plugin -3. Should show subscription required page (if not subscribed) -4. Plugin makes API call to your server (no secrets exposed) - -## ๐Ÿ“‹ Next Steps - -1. **Implement OAuth Flow** (optional but recommended) - - Users authorize CyberPanel via Patreon OAuth - - Store access tokens securely (database recommended) - - Link tokens to user emails - -2. **Test Full Flow** - - Subscribe to Patreon tier - - Authorize CyberPanel - - Access plugin (should work) - -3. **Monitor** - - Check API logs for errors - - Monitor rate limiting - - Verify caching is working - -## ๐ŸŽฏ Plugin is Safe to Publish - -The `premiumPlugin` can now be: -- โœ… Published to public repositories -- โœ… Shared with users -- โœ… Installed on any server -- โœ… Code reviewed by anyone - -**No secrets will be exposed** because all verification happens on your server! - -## ๐Ÿ“ Files Created/Modified - -### Created -- `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php` -- `/home/newstargeted.com/api.newstargeted.com/api/index.php` -- `/home/cyberpanel-plugins/premiumPlugin/views.py` (remote version) -- `/home/cyberpanel-plugins/premiumPlugin/SECURITY.md` - -### Modified -- `/home/newstargeted.com/api.newstargeted.com/config.php` (added Patreon credentials) -- `/home/newstargeted.com/api.newstargeted.com/.htaccess` (added API routing) -- `/home/cyberpanel-plugins/premiumPlugin/views.py` (updated to remote verification) - -## โœจ Benefits - -1. **Security**: Secrets never leave your server -2. **Control**: You can revoke access, update logic centrally -3. **Transparency**: Plugin code can be open source -4. **Scalability**: Centralized verification handles all requests -5. **Maintenance**: Update verification logic in one place diff --git a/to-do/SETUP-COMPLETE.md b/to-do/SETUP-COMPLETE.md deleted file mode 100644 index c344e6eb1..000000000 --- a/to-do/SETUP-COMPLETE.md +++ /dev/null @@ -1,86 +0,0 @@ -# Remote Verification Setup Complete โœ… - -## What Was Done - -### 1. Remote Verification API Created -- **Location**: `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php` -- **URL**: `https://api.newstargeted.com/api/verify-patreon-membership` -- **Route**: Added to `.htaccess` for clean URL routing - -### 2. Plugin Updated to Use Remote Verification -- **File**: `/home/cyberpanel-plugins/premiumPlugin/views.py` -- **Method**: All Patreon checks now go through your server -- **No Secrets**: Plugin code contains zero credentials - -### 3. Configuration Added -- **Patreon credentials** added to `/home/newstargeted.com/api.newstargeted.com/config.php` -- **Secure permissions**: config.php set to 600 (readable only by owner) - -### 4. File Permissions Set -- โœ… API files: `newst3922:newst3922` (644 for files, 755 for directories) -- โœ… Plugin files: `newst3922:newst3922` (644 for files, 755 for directories) -- โœ… Config file: `newst3922:newst3922` (600 - secure) - -## Security Features - -โœ… **No secrets in plugin** - Users can see all code -โœ… **All credentials on your server** - Never exposed -โœ… **Rate limiting** - 60 requests/hour per IP -โœ… **Caching** - 5 minute cache to reduce API calls -โœ… **HTTPS only** - All communication encrypted -โœ… **Error handling** - Graceful failures - -## How It Works - -1. User installs plugin (no subscription needed) -2. User tries to access plugin -3. Plugin makes API call to YOUR server -4. Your server checks Patreon API (credentials stay on your server) -5. Your server returns access status -6. Plugin shows content or subscription page - -## Testing - -### Test API Endpoint -```bash -curl -X POST https://api.newstargeted.com/api/verify-patreon-membership \ - -H "Content-Type: application/json" \ - -d '{ - "user_email": "test@example.com", - "plugin_name": "premiumPlugin", - "tier_id": "27789984" - }' -``` - -### Expected Response -```json -{ - "success": true, - "has_access": false, - "patreon_tier": "CyberPanel Paid Plugin", - "patreon_url": "https://www.patreon.com/c/newstargeted/membership", - "message": "Patreon subscription required..." -} -``` - -## Next Steps - -1. **Test the API endpoint** - Verify it's accessible -2. **Implement OAuth flow** - For users to authorize Patreon access -3. **Store user tokens** - Link Patreon tokens to user emails -4. **Test full flow** - Install plugin and verify access control - -## Files Modified - -- `/home/newstargeted.com/api.newstargeted.com/config.php` - Added Patreon credentials -- `/home/newstargeted.com/api.newstargeted.com/.htaccess` - Added API route -- `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php` - Created -- `/home/cyberpanel-plugins/premiumPlugin/views.py` - Updated to use remote verification -- `/home/cyberpanel/plugins/premiumPlugin/views.py` - Updated (installed version) - -## Plugin is Now Safe to Publish - -โœ… No secrets in code -โœ… All verification happens on your server -โœ… Users can see all plugin files without security risk -โœ… Centralized control and updates diff --git a/to-do/v2.5.5-dev-installation-fixes.md b/to-do/v2.5.5-dev-installation-fixes.md deleted file mode 100644 index 23cdc92c5..000000000 --- a/to-do/v2.5.5-dev-installation-fixes.md +++ /dev/null @@ -1,132 +0,0 @@ -# CyberPanel v2.5.5-dev Installation Fixes - -## Issues Identified - -### Issue 1: install.sh Module Loading Failure -**Problem**: When executing `install.sh` via `sh <(curl ...)`, the script tries to load modules from `/dev/fd/.../modules/` which doesn't exist because the script is executed directly from stdin. - -**Error Message**: -``` -โŒ Module not found: /dev/fd/modules/os/detect.sh -โŒ Failed to load OS detection module -``` - -**Root Cause**: The script assumes it's running from a cloned repository directory, but when executed via curl, `SCRIPT_DIR` becomes `/dev/fd/...` which doesn't contain the modules. - -### Issue 2: MariaDB 10.x to 12.x Upgrade Blocked -**Problem**: The installer attempts to install MariaDB 12.1 on systems that already have MariaDB 10.11.15 installed. MariaDB's package pre-installation script blocks the upgrade because direct upgrades from 10.x to 12.x are not safe without manual dump/restore. - -**Error Message**: -``` -error: %prein(MariaDB-server-12.1.2-1.el9.x86_64) scriptlet failed, exit status 1 -Error in PREIN scriptlet in rpm package MariaDB-server -``` - -**Root Cause**: The installer doesn't check for existing MariaDB installations before attempting to install MariaDB 12.1. - -## Fixes Implemented - -### Fix 1: install.sh - Handle Curl/Wget Execution - -**Location**: `install/install.sh` - -**Changes**: -1. Added detection for curl/wget execution (checks if `SCRIPT_DIR` is `/dev/fd/*` or modules directory doesn't exist) -2. When detected, the script now: - - Clones the CyberPanel repository to a temporary directory - - Extracts branch name from command-line arguments - - Falls back to legacy installer if git clone fails -3. Properly handles branch argument parsing for both `-b` and `--branch` flags - -**Code Added**: -```bash -# Check if script is being executed via curl/wget -if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then - # Clone repository first - # ... (see install.sh for full implementation) -fi -``` - -### Fix 2: install.py - MariaDB Installation Detection and Upgrade Attempt - -**Location**: `install/install.py` - -**Changes**: -1. Added `checkExistingMariaDB()` method that: - - Checks for installed MariaDB/MySQL server packages (RPM or DEB) - - Detects MariaDB data directory existence - - Extracts version information from `mysql --version` output - - Returns version info including major.minor version - -2. Added `_attemptMariaDBUpgrade()` method that: - - Attempts to install MariaDB 12.1 on systems with MariaDB 10.x - - Sets up MariaDB 12.1 repository - - Attempts installation with appropriate flags (`--allowerasing` for RHEL) - - Returns `True` if successful, `False` if blocked or fails - - Handles upgrade restriction errors gracefully - -3. Modified `installMySQL()` method to: - - Check for existing MariaDB installation before attempting install - - **If MariaDB 10.x is detected**: Attempt to upgrade to 12.1 first - - If upgrade succeeds: Use new MariaDB 12.1 installation - - If upgrade fails/blocked: Fall back to existing MariaDB 10.x - - If MariaDB 12.x or higher is detected: Skip installation and use existing - - Only attempt new installation if no MariaDB is found - -**Key Logic**: -```python -# Check if MariaDB is already installed -is_installed, installed_version, major_minor = self.checkExistingMariaDB() - -if is_installed: - if major_minor and major_minor != "unknown": - major_ver = float(major_minor) - if major_ver < 12.0: - # Try to upgrade to 12.1 first - upgrade_success = self._attemptMariaDBUpgrade() - if upgrade_success: - # Use new MariaDB 12.1 - return True - else: - # Fall back to existing MariaDB 10.x - self.startMariaDB() - return True - # Use existing MariaDB 12.x+ installation - self.startMariaDB() - return True -``` - -## Testing Recommendations - -1. **Test install.sh with curl**: - ```bash - sh <(curl -s https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/install.sh) - ``` - -2. **Test on AlmaLinux 9 with existing MariaDB 10.x**: - - System should detect existing MariaDB 10.x - - Should attempt to upgrade to MariaDB 12.1 first - - If upgrade is blocked, should fall back to using existing MariaDB 10.x - - Should log appropriate messages about upgrade attempt and fallback - -3. **Test on clean AlmaLinux 9 system**: - - Should install MariaDB 12.1 successfully - - Should complete full installation - -## Files Modified - -1. `install/install.sh` - Added curl/wget execution handling -2. `install/install.py` - Added MariaDB detection and existing installation handling - -## Notes - -- The installer now **attempts to upgrade MariaDB 10.x to 12.1 first**, and only falls back to using existing 10.x if the upgrade is blocked by the package manager -- This provides the best of both worlds: tries to get the latest version, but safely falls back if upgrade restrictions prevent it -- The upgrade attempt uses `--allowerasing` flag for RHEL-based systems to allow package replacements -- If MariaDB's pre-installation scriptlet blocks the upgrade (as it does for 10.x to 12.x), the installer gracefully falls back to the existing installation -- The curl/wget execution now properly clones the repository before attempting to load modules - -## Related Issues - -- MariaDB 10.x to 12.x upgrades require manual dump/restore per MariaDB documentation -- The installer now respects existing installations to prevent data loss From 6b46cbafcdfc6a7d961f96fddcaba93093e06bed Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:43:17 +0100 Subject: [PATCH 04/13] Fix OS variable passing to dependency manager - export variables and add safety checks --- install.sh | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index c487174e9..26f43b6b9 100644 --- a/install.sh +++ b/install.sh @@ -144,11 +144,18 @@ detect_operating_system() { print_status "$BLUE" "๐Ÿ” Detecting operating system..." if detect_os; then - # Get OS information - eval $(get_os_info) + # Variables are set by detect_os() in the sourced module + # Export them to ensure they're available to all functions + export SERVER_OS OS_FAMILY PACKAGE_MANAGER ARCHITECTURE print_status "$GREEN" "โœ… OS detected: $SERVER_OS ($OS_FAMILY)" print_status "$GREEN" "โœ… Package manager: $PACKAGE_MANAGER" print_status "$GREEN" "โœ… Architecture: $ARCHITECTURE" + + # Verify variables are set + if [ -z "$SERVER_OS" ] || [ -z "$OS_FAMILY" ] || [ -z "$PACKAGE_MANAGER" ]; then + print_status "$RED" "โŒ OS variables not properly set after detection" + return 1 + fi return 0 else print_status "$RED" "โŒ Failed to detect operating system" @@ -160,6 +167,19 @@ detect_operating_system() { install_dependencies() { print_status "$BLUE" "๐Ÿ“ฆ Installing dependencies..." + # Ensure variables are set (they should be from detect_operating_system) + if [ -z "$SERVER_OS" ] || [ -z "$OS_FAMILY" ] || [ -z "$PACKAGE_MANAGER" ]; then + print_status "$RED" "โŒ OS variables not set. SERVER_OS=$SERVER_OS, OS_FAMILY=$OS_FAMILY, PACKAGE_MANAGER=$PACKAGE_MANAGER" + # Try to get them again + if detect_os; then + eval $(get_os_info) + export SERVER_OS OS_FAMILY PACKAGE_MANAGER ARCHITECTURE + else + print_status "$RED" "โŒ Failed to detect OS for dependency installation" + return 1 + fi + fi + if manage_dependencies "$SERVER_OS" "$OS_FAMILY" "$PACKAGE_MANAGER"; then print_status "$GREEN" "โœ… Dependencies installed successfully" return 0 From 1db7ebfeed7a3e68b12fe484ec7fcbeb21033208 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:45:14 +0100 Subject: [PATCH 05/13] Improve git clone error handling and fallback logic - Add timeout for git clone (180 seconds) - Better error messages showing actual git errors - Verify repository structure before using cloned repo - Improve fallback installer download with better error handling - Support both yum and dnf for RHEL-based systems --- install.sh | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index 26f43b6b9..561629b19 100644 --- a/install.sh +++ b/install.sh @@ -37,13 +37,31 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then mkdir -p "$TEMP_DIR" early_log "๐Ÿ“ฆ Cloning CyberPanel repository (branch: $BRANCH_NAME)..." - if git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" 2>/dev/null; then - SCRIPT_DIR="$TEMP_DIR/cyberpanel" - cd "$SCRIPT_DIR" - early_log "โœ… Repository cloned successfully" + early_log "This may take a minute depending on your connection speed..." + + # Try git clone with timeout and better error handling + if timeout 120 git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" 2>&1 | tee /tmp/git-clone.log; then + if [ -d "$TEMP_DIR/cyberpanel" ] && [ -f "$TEMP_DIR/cyberpanel/install.sh" ]; then + SCRIPT_DIR="$TEMP_DIR/cyberpanel" + cd "$SCRIPT_DIR" + early_log "โœ… Repository cloned successfully" + else + early_log "โš ๏ธ Git clone completed but repository structure is invalid" + early_log "Falling back to legacy installer..." + rm -rf "$TEMP_DIR/cyberpanel" + fi else - # Fallback: try to download install.sh from the old method - early_log "โš ๏ธ Git clone failed, falling back to legacy installer..." + GIT_ERROR=$(cat /tmp/git-clone.log 2>/dev/null | tail -5) + early_log "โš ๏ธ Git clone failed" + if [ -n "$GIT_ERROR" ]; then + early_log "Error details: $GIT_ERROR" + fi + early_log "Falling back to legacy installer..." + rm -rf "$TEMP_DIR/cyberpanel" 2>/dev/null || true + fi + + # If we reach here, git clone failed or was invalid, use fallback + if [ ! -d "$SCRIPT_DIR/modules" ]; then OUTPUT=$(cat /etc/*release 2>/dev/null || echo "") if echo "$OUTPUT" | grep -qE "(CentOS|AlmaLinux|CloudLinux|Rocky)" ; then SERVER_OS="CentOS8" From b6d64f99c665496bb45339068bc3260b3a342d64 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:45:42 +0100 Subject: [PATCH 06/13] Fix git clone fallback logic - properly handle failed clones - Use flag-based approach to track git clone success - Verify repository structure (install.sh and modules/) before using - Fix fallback execution path - Support both yum and dnf for package installation - Better error messages --- install.sh | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/install.sh b/install.sh index 561629b19..ba776ab06 100644 --- a/install.sh +++ b/install.sh @@ -40,32 +40,39 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then early_log "This may take a minute depending on your connection speed..." # Try git clone with timeout and better error handling - if timeout 120 git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" 2>&1 | tee /tmp/git-clone.log; then - if [ -d "$TEMP_DIR/cyberpanel" ] && [ -f "$TEMP_DIR/cyberpanel/install.sh" ]; then - SCRIPT_DIR="$TEMP_DIR/cyberpanel" - cd "$SCRIPT_DIR" - early_log "โœ… Repository cloned successfully" - else - early_log "โš ๏ธ Git clone completed but repository structure is invalid" - early_log "Falling back to legacy installer..." - rm -rf "$TEMP_DIR/cyberpanel" + GIT_CLONE_SUCCESS=false + + if command -v timeout >/dev/null 2>&1; then + if timeout 180 git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" >/tmp/git-clone.log 2>&1; then + GIT_CLONE_SUCCESS=true fi else - GIT_ERROR=$(cat /tmp/git-clone.log 2>/dev/null | tail -5) - early_log "โš ๏ธ Git clone failed" + # No timeout command, try without timeout + if git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" >/tmp/git-clone.log 2>&1; then + GIT_CLONE_SUCCESS=true + fi + fi + + # Verify clone was successful and structure is valid + if [ "$GIT_CLONE_SUCCESS" = true ] && [ -d "$TEMP_DIR/cyberpanel" ] && [ -f "$TEMP_DIR/cyberpanel/install.sh" ] && [ -d "$TEMP_DIR/cyberpanel/modules" ]; then + SCRIPT_DIR="$TEMP_DIR/cyberpanel" + cd "$SCRIPT_DIR" + early_log "โœ… Repository cloned successfully" + else + # Git clone failed or structure invalid, use fallback + GIT_ERROR=$(tail -3 /tmp/git-clone.log 2>/dev/null | tr '\n' ' ') + early_log "โš ๏ธ Git clone failed or incomplete" if [ -n "$GIT_ERROR" ]; then - early_log "Error details: $GIT_ERROR" + early_log "Last error: $GIT_ERROR" fi early_log "Falling back to legacy installer..." rm -rf "$TEMP_DIR/cyberpanel" 2>/dev/null || true - fi - - # If we reach here, git clone failed or was invalid, use fallback - if [ ! -d "$SCRIPT_DIR/modules" ]; then + + # Fallback: try to download install.sh from the old method OUTPUT=$(cat /etc/*release 2>/dev/null || echo "") if echo "$OUTPUT" | grep -qE "(CentOS|AlmaLinux|CloudLinux|Rocky)" ; then SERVER_OS="CentOS8" - yum install -y -q curl wget 2>/dev/null || true + yum install -y -q curl wget 2>/dev/null || dnf install -y -q curl wget 2>/dev/null || true elif echo "$OUTPUT" | grep -qE "Ubuntu" ; then SERVER_OS="Ubuntu" apt install -y -qq wget curl 2>/dev/null || true @@ -77,8 +84,13 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then rm -f /tmp/cyberpanel.sh curl --silent -o /tmp/cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null || \ wget -q -O /tmp/cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null - chmod +x /tmp/cyberpanel.sh - exec /tmp/cyberpanel.sh "$@" + if [ -f /tmp/cyberpanel.sh ]; then + chmod +x /tmp/cyberpanel.sh + exec /tmp/cyberpanel.sh "$@" + else + early_log "โŒ Failed to download fallback installer" + exit 1 + fi fi fi From be80a9e873b1271d7ff4b39d59b64f7f56dca873 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:49:18 +0100 Subject: [PATCH 07/13] Handle disk space issues - try multiple temp directories and fallback gracefully - Try multiple temp directory locations (/tmp, /var/tmp, /root, /root) - Clean up old temporary directories before creating new ones - Skip git clone if no temp directory can be created - Try multiple locations for fallback installer download - Better error messages for disk space issues --- install.sh | 59 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/install.sh b/install.sh index ba776ab06..7604fec38 100644 --- a/install.sh +++ b/install.sh @@ -33,8 +33,33 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then done # Clone repository to temporary directory - TEMP_DIR="/tmp/cyberpanel-installer-$$" - mkdir -p "$TEMP_DIR" + # Try multiple locations if /tmp is full + TEMP_DIR="" + early_log "Checking available disk space..." + + # Try to clean up old temp directories first + rm -rf /tmp/cyberpanel-installer-* 2>/dev/null || true + rm -rf /var/tmp/cyberpanel-installer-* 2>/dev/null || true + + # Try multiple temp locations + for temp_location in "/tmp/cyberpanel-installer-$$" "/var/tmp/cyberpanel-installer-$$" "$HOME/cyberpanel-installer-$$" "/root/cyberpanel-installer-$$"; do + if mkdir -p "$temp_location" 2>/dev/null; then + TEMP_DIR="$temp_location" + early_log "Using temporary directory: $TEMP_DIR" + break + fi + done + + # If we still don't have a temp dir, skip git clone and use fallback + if [ -z "$TEMP_DIR" ]; then + early_log "โš ๏ธ Cannot create temporary directory (disk may be full)" + early_log "Skipping git clone, using fallback installer directly..." + # Skip to fallback installer + TEMP_DIR="" + fi + + # Only attempt git clone if we have a temp directory + if [ -n "$TEMP_DIR" ]; then early_log "๐Ÿ“ฆ Cloning CyberPanel repository (branch: $BRANCH_NAME)..." early_log "This may take a minute depending on your connection speed..." @@ -67,8 +92,12 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then fi early_log "Falling back to legacy installer..." rm -rf "$TEMP_DIR/cyberpanel" 2>/dev/null || true - + fi + + # If git clone failed or we couldn't create temp dir, use fallback + if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR/cyberpanel" ] || [ ! -f "$TEMP_DIR/cyberpanel/install.sh" ]; then # Fallback: try to download install.sh from the old method + early_log "Using fallback installer method..." OUTPUT=$(cat /etc/*release 2>/dev/null || echo "") if echo "$OUTPUT" | grep -qE "(CentOS|AlmaLinux|CloudLinux|Rocky)" ; then SERVER_OS="CentOS8" @@ -81,14 +110,26 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then exit 1 fi - rm -f /tmp/cyberpanel.sh - curl --silent -o /tmp/cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null || \ - wget -q -O /tmp/cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null - if [ -f /tmp/cyberpanel.sh ]; then - chmod +x /tmp/cyberpanel.sh - exec /tmp/cyberpanel.sh "$@" + # Try multiple locations for fallback script + FALLBACK_SCRIPT="" + for fallback_location in "/tmp/cyberpanel.sh" "/var/tmp/cyberpanel.sh" "$HOME/cyberpanel.sh"; do + rm -f "$fallback_location" 2>/dev/null || true + if curl --silent -o "$fallback_location" "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null || \ + wget -q -O "$fallback_location" "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null; then + if [ -f "$fallback_location" ]; then + FALLBACK_SCRIPT="$fallback_location" + chmod +x "$FALLBACK_SCRIPT" + break + fi + fi + done + + if [ -n "$FALLBACK_SCRIPT" ] && [ -f "$FALLBACK_SCRIPT" ]; then + early_log "โœ… Fallback installer downloaded, executing..." + exec "$FALLBACK_SCRIPT" "$@" else early_log "โŒ Failed to download fallback installer" + early_log "Please free up disk space or check your internet connection" exit 1 fi fi From 872704635f68872e88f7c23728031fa396ca3aab Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:51:16 +0100 Subject: [PATCH 08/13] Fix set -e causing exit on mkdir failure - disable temporarily for temp dir creation - Temporarily disable set -e when trying to create temp directories - Prevents script from exiting when /tmp is full - Allows fallback to alternative directories or fallback installer --- install.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 7604fec38..1d24354ea 100644 --- a/install.sh +++ b/install.sh @@ -4,6 +4,7 @@ # This installer uses modules for better organization and maintainability # Each module is kept under 500 lines for easy management +# Note: We use 'set -e' carefully - some operations need to handle failures gracefully set -e # Get script directory @@ -41,7 +42,8 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then rm -rf /tmp/cyberpanel-installer-* 2>/dev/null || true rm -rf /var/tmp/cyberpanel-installer-* 2>/dev/null || true - # Try multiple temp locations + # Try multiple temp locations (disable set -e for this section) + set +e for temp_location in "/tmp/cyberpanel-installer-$$" "/var/tmp/cyberpanel-installer-$$" "$HOME/cyberpanel-installer-$$" "/root/cyberpanel-installer-$$"; do if mkdir -p "$temp_location" 2>/dev/null; then TEMP_DIR="$temp_location" @@ -49,6 +51,7 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then break fi done + set -e # If we still don't have a temp dir, skip git clone and use fallback if [ -z "$TEMP_DIR" ]; then From 1351de7590694362291fdb3d3cd15a7b2e4868b3 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:53:48 +0100 Subject: [PATCH 09/13] Add debug output to diagnose OS variable passing issue --- install.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 1d24354ea..03baac2e0 100644 --- a/install.sh +++ b/install.sh @@ -241,19 +241,31 @@ detect_operating_system() { install_dependencies() { print_status "$BLUE" "๐Ÿ“ฆ Installing dependencies..." + # Debug: Show current variable values + print_status "$YELLOW" "DEBUG: SERVER_OS='$SERVER_OS', OS_FAMILY='$OS_FAMILY', PACKAGE_MANAGER='$PACKAGE_MANAGER'" + # Ensure variables are set (they should be from detect_operating_system) if [ -z "$SERVER_OS" ] || [ -z "$OS_FAMILY" ] || [ -z "$PACKAGE_MANAGER" ]; then - print_status "$RED" "โŒ OS variables not set. SERVER_OS=$SERVER_OS, OS_FAMILY=$OS_FAMILY, PACKAGE_MANAGER=$PACKAGE_MANAGER" + print_status "$YELLOW" "โš ๏ธ OS variables not set, re-detecting..." # Try to get them again if detect_os; then - eval $(get_os_info) + # Variables should be set by detect_os() in the sourced module export SERVER_OS OS_FAMILY PACKAGE_MANAGER ARCHITECTURE + print_status "$GREEN" "โœ… Re-detected: SERVER_OS=$SERVER_OS, OS_FAMILY=$OS_FAMILY, PACKAGE_MANAGER=$PACKAGE_MANAGER" else print_status "$RED" "โŒ Failed to detect OS for dependency installation" return 1 fi fi + # Verify variables one more time before calling + if [ -z "$SERVER_OS" ] || [ -z "$OS_FAMILY" ] || [ -z "$PACKAGE_MANAGER" ]; then + print_status "$RED" "โŒ OS variables still not set after re-detection" + return 1 + fi + + print_status "$BLUE" "Calling manage_dependencies with: SERVER_OS='$SERVER_OS', OS_FAMILY='$OS_FAMILY', PACKAGE_MANAGER='$PACKAGE_MANAGER'" + if manage_dependencies "$SERVER_OS" "$OS_FAMILY" "$PACKAGE_MANAGER"; then print_status "$GREEN" "โœ… Dependencies installed successfully" return 0 From f928578abb5c6518eb14f4b65cd080b3d03213cd Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:54:07 +0100 Subject: [PATCH 10/13] Fix OS variable capture - use get_os_info() to properly retrieve values - Use eval with get_os_info() to capture OS variables - Ensures variables are properly set in current shell scope - Fixes issue where variables were detected but not available to install_dependencies() --- install.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 03baac2e0..c8dffe5c6 100644 --- a/install.sh +++ b/install.sh @@ -218,9 +218,13 @@ detect_operating_system() { print_status "$BLUE" "๐Ÿ” Detecting operating system..." if detect_os; then - # Variables are set by detect_os() in the sourced module + # Get OS information using get_os_info() to ensure we capture the values + # This outputs variable assignments that we can eval + eval $(get_os_info) + # Export them to ensure they're available to all functions export SERVER_OS OS_FAMILY PACKAGE_MANAGER ARCHITECTURE + print_status "$GREEN" "โœ… OS detected: $SERVER_OS ($OS_FAMILY)" print_status "$GREEN" "โœ… Package manager: $PACKAGE_MANAGER" print_status "$GREEN" "โœ… Architecture: $ARCHITECTURE" From 20c51e9edb3ca7347411654c226810a0805a47eb Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:56:15 +0100 Subject: [PATCH 11/13] Fix syntax error (missing fi) and add disk space checking - Fix missing 'fi' closing the TEMP_DIR check block - Add check_disk_space() function to verify minimum 10GB available - Display disk space requirements and available space - Warn if insufficient space but allow continuation --- install.sh | 103 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 31 deletions(-) diff --git a/install.sh b/install.sh index c8dffe5c6..323ae4177 100644 --- a/install.sh +++ b/install.sh @@ -63,40 +63,41 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then # Only attempt git clone if we have a temp directory if [ -n "$TEMP_DIR" ]; then - - early_log "๐Ÿ“ฆ Cloning CyberPanel repository (branch: $BRANCH_NAME)..." - early_log "This may take a minute depending on your connection speed..." - - # Try git clone with timeout and better error handling - GIT_CLONE_SUCCESS=false - - if command -v timeout >/dev/null 2>&1; then - if timeout 180 git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" >/tmp/git-clone.log 2>&1; then - GIT_CLONE_SUCCESS=true + early_log "๐Ÿ“ฆ Cloning CyberPanel repository (branch: $BRANCH_NAME)..." + early_log "This may take a minute depending on your connection speed..." + + # Try git clone with timeout and better error handling + GIT_CLONE_SUCCESS=false + + if command -v timeout >/dev/null 2>&1; then + if timeout 180 git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" >/tmp/git-clone.log 2>&1; then + GIT_CLONE_SUCCESS=true + fi + else + # No timeout command, try without timeout + if git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" >/tmp/git-clone.log 2>&1; then + GIT_CLONE_SUCCESS=true + fi fi - else - # No timeout command, try without timeout - if git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" >/tmp/git-clone.log 2>&1; then - GIT_CLONE_SUCCESS=true + + # Verify clone was successful and structure is valid + if [ "$GIT_CLONE_SUCCESS" = true ] && [ -d "$TEMP_DIR/cyberpanel" ] && [ -f "$TEMP_DIR/cyberpanel/install.sh" ] && [ -d "$TEMP_DIR/cyberpanel/modules" ]; then + SCRIPT_DIR="$TEMP_DIR/cyberpanel" + cd "$SCRIPT_DIR" + early_log "โœ… Repository cloned successfully" + else + # Git clone failed or structure invalid, use fallback + GIT_ERROR=$(tail -3 /tmp/git-clone.log 2>/dev/null | tr '\n' ' ') + early_log "โš ๏ธ Git clone failed or incomplete" + if [ -n "$GIT_ERROR" ]; then + early_log "Last error: $GIT_ERROR" + fi + early_log "Falling back to legacy installer..." + rm -rf "$TEMP_DIR/cyberpanel" 2>/dev/null || true + TEMP_DIR="" # Clear TEMP_DIR to trigger fallback fi fi - # Verify clone was successful and structure is valid - if [ "$GIT_CLONE_SUCCESS" = true ] && [ -d "$TEMP_DIR/cyberpanel" ] && [ -f "$TEMP_DIR/cyberpanel/install.sh" ] && [ -d "$TEMP_DIR/cyberpanel/modules" ]; then - SCRIPT_DIR="$TEMP_DIR/cyberpanel" - cd "$SCRIPT_DIR" - early_log "โœ… Repository cloned successfully" - else - # Git clone failed or structure invalid, use fallback - GIT_ERROR=$(tail -3 /tmp/git-clone.log 2>/dev/null | tr '\n' ' ') - early_log "โš ๏ธ Git clone failed or incomplete" - if [ -n "$GIT_ERROR" ]; then - early_log "Last error: $GIT_ERROR" - fi - early_log "Falling back to legacy installer..." - rm -rf "$TEMP_DIR/cyberpanel" 2>/dev/null || true - fi - # If git clone failed or we couldn't create temp dir, use fallback if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR/cyberpanel" ] || [ ! -f "$TEMP_DIR/cyberpanel/install.sh" ]; then # Fallback: try to download install.sh from the old method @@ -136,7 +137,8 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then exit 1 fi fi -fi + fi # Close the if [ -n "$TEMP_DIR" ] block +fi # Close the if [[ "$SCRIPT_DIR" == /dev/fd/* ]] block MODULES_DIR="$SCRIPT_DIR/modules" @@ -380,6 +382,42 @@ parse_arguments() { done } +# Function to check disk space +check_disk_space() { + local required_gb=10 # Minimum 10GB required + local root_available_gb=0 + + # Get available space on root filesystem + if command -v df >/dev/null 2>&1; then + root_available_gb=$(df -BG / | awk 'NR==2 {print $4}' | sed 's/G//' | cut -d. -f1) + if [ -z "$root_available_gb" ] || [ "$root_available_gb" = "" ]; then + # Try alternative method + root_available_gb=$(df -BG / | tail -1 | awk '{print $4}' | sed 's/G//' | cut -d. -f1) + fi + fi + + # If we couldn't get the value, try without -BG flag + if [ -z "$root_available_gb" ] || ! [[ "$root_available_gb" =~ ^[0-9]+$ ]]; then + root_available_gb=$(df / | awk 'NR==2 {print $4}' | awk '{printf "%.0f", $1/1024/1024}') + fi + + print_status "$BLUE" "๐Ÿ’พ Checking disk space..." + print_status "$BLUE" " Required: ${required_gb}GB minimum" + + if [[ "$root_available_gb" =~ ^[0-9]+$ ]] && [ "$root_available_gb" -ge "$required_gb" ]; then + print_status "$GREEN" " Available: ${root_available_gb}GB (โœ… Sufficient)" + return 0 + elif [[ "$root_available_gb" =~ ^[0-9]+$ ]]; then + print_status "$YELLOW" " Available: ${root_available_gb}GB (โš ๏ธ Less than ${required_gb}GB recommended)" + print_status "$YELLOW" " Installation may fail if disk space runs out" + return 1 + else + print_status "$YELLOW" " Could not determine available disk space" + print_status "$YELLOW" " Please ensure at least ${required_gb}GB is available" + return 1 + fi +} + # Main installation function main() { # Initialize log file @@ -389,6 +427,9 @@ main() { print_status "$BLUE" "๐Ÿš€ Enhanced CyberPanel Installer Starting..." print_status "$BLUE" "Log file: /var/log/cyberpanel_install.log" + # Check disk space before proceeding + check_disk_space + # Parse command line arguments parse_arguments "$@" From 0e7cb5b798cb1892505bd40e12ad071cd1b8afec Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:56:39 +0100 Subject: [PATCH 12/13] Remove extra fi statement causing syntax error --- install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/install.sh b/install.sh index 323ae4177..3960ee88e 100644 --- a/install.sh +++ b/install.sh @@ -137,7 +137,6 @@ if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then exit 1 fi fi - fi # Close the if [ -n "$TEMP_DIR" ] block fi # Close the if [[ "$SCRIPT_DIR" == /dev/fd/* ]] block MODULES_DIR="$SCRIPT_DIR/modules" From 073e6a0b117424afff32baaa8d973ed9d44566e7 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 26 Jan 2026 20:59:45 +0100 Subject: [PATCH 13/13] Ensure file ends with newline --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 3960ee88e..94be2eff8 100644 --- a/install.sh +++ b/install.sh @@ -457,4 +457,4 @@ main() { } # Run main function -main "$@" \ No newline at end of file +main "$@"