diff --git a/ModuleDeveloperGuide.pdf b/ModuleDeveloperGuide.pdf new file mode 100644 index 000000000..f9572aa76 Binary files /dev/null and b/ModuleDeveloperGuide.pdf differ diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index 742a8f49f..5c32520af 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -805,10 +805,195 @@ .notification-shown.ai-scanner-shown #main-content { padding-top: 220px; } - + .notification-shown .ai-scanner-banner { top: 130px; } + + /* .htaccess Feature Banner */ + .htaccess-feature-banner { + position: fixed; + top: 80px; + left: 260px; + right: 0; + background: linear-gradient(135deg, #10b981 0%, #059669 50%, #047857 100%); + border-bottom: 2px solid #065f46; + padding: 18px 30px; + z-index: 997; + box-shadow: 0 6px 25px rgba(16, 185, 129, 0.3); + animation: slideDown 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); + display: none; + } + + .htaccess-feature-banner.show { + display: block; + } + + .htaccess-content { + display: flex; + align-items: center; + gap: 1.5rem; + max-width: 1400px; + margin: 0 auto; + } + + .htaccess-icon { + background: rgba(255, 255, 255, 0.25); + border-radius: 14px; + padding: 14px; + backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.4); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + } + + .htaccess-icon i { + color: white; + font-size: 1.75rem; + display: block; + } + + .htaccess-text { + flex: 1; + display: flex; + flex-direction: column; + gap: 5px; + } + + .htaccess-main-text { + color: white; + font-size: 1.1rem; + font-weight: 700; + line-height: 1.4; + letter-spacing: 0.3px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .htaccess-sub-text { + color: rgba(255, 255, 255, 0.9); + font-size: 0.875rem; + font-weight: 500; + display: flex; + align-items: center; + gap: 1rem; + } + + .htaccess-sub-text span { + display: flex; + align-items: center; + gap: 0.4rem; + } + + .htaccess-sub-text i { + font-size: 0.75rem; + } + + .htaccess-actions { + display: flex; + gap: 12px; + } + + .htaccess-btn { + background: white; + color: #047857; + padding: 14px 28px; + border-radius: 10px; + text-decoration: none; + font-weight: 700; + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 10px; + transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); + position: relative; + overflow: hidden; + border: 2px solid transparent; + } + + .htaccess-btn:hover { + background: #f0fdf4; + color: #065f46; + transform: translateY(-2px) scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2); + text-decoration: none; + border-color: white; + } + + .htaccess-btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(16, 185, 129, 0.1); + transform: translate(-50%, -50%); + transition: width 0.6s ease, height 0.6s ease; + } + + .htaccess-btn:hover::before { + width: 300px; + height: 300px; + } + + .htaccess-btn span { + position: relative; + z-index: 1; + } + + .htaccess-btn i { + font-size: 1rem; + position: relative; + z-index: 1; + } + + .htaccess-close { + background: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; + font-size: 1.1rem; + cursor: pointer; + padding: 10px; + border-radius: 8px; + transition: all 0.3s ease; + backdrop-filter: blur(12px); + } + + .htaccess-close:hover { + background: rgba(255, 255, 255, 0.25); + transform: scale(1.1) rotate(90deg); + } + + /* Adjust main content when .htaccess banner is shown */ + .htaccess-shown #main-content { + padding-top: 190px; + } + + /* Multiple banners adjustments */ + .notification-shown.htaccess-shown #main-content { + padding-top: 240px; + } + + .ai-scanner-shown.htaccess-shown #main-content { + padding-top: 270px; + } + + .notification-shown.ai-scanner-shown.htaccess-shown #main-content { + padding-top: 320px; + } + + .notification-shown .htaccess-feature-banner { + top: 130px; + } + + .ai-scanner-shown .htaccess-feature-banner { + top: 160px; + } + + .notification-shown.ai-scanner-shown .htaccess-feature-banner { + top: 210px; + } /* Mobile responsive styles for AI Scanner banner */ @media (max-width: 768px) { @@ -816,54 +1001,110 @@ left: 0; padding: 12px 20px; } - + .ai-scanner-content { gap: 1rem; flex-wrap: wrap; } - + .ai-scanner-text { min-width: 200px; } - + .ai-scanner-main-text { font-size: 0.9rem; } - + .ai-scanner-sub-text { font-size: 0.8rem; } - + .ai-scanner-btn { padding: 10px 20px; font-size: 0.8rem; } - + .ai-scanner-icon { padding: 10px; } - + .ai-scanner-icon i { font-size: 1.25rem; } + + /* .htaccess banner mobile styles */ + .htaccess-feature-banner { + left: 0; + padding: 14px 20px; + } + + .htaccess-content { + gap: 1rem; + flex-wrap: wrap; + } + + .htaccess-text { + min-width: 200px; + } + + .htaccess-main-text { + font-size: 0.95rem; + } + + .htaccess-sub-text { + font-size: 0.8rem; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .htaccess-btn { + padding: 12px 22px; + font-size: 0.85rem; + } + + .htaccess-icon { + padding: 12px; + } + + .htaccess-icon i { + font-size: 1.4rem; + } } - + @media (max-width: 480px) { .ai-scanner-content { flex-direction: column; align-items: stretch; gap: 12px; } - + .ai-scanner-actions { justify-content: center; } - + .ai-scanner-close { position: absolute; top: 8px; right: 8px; } + + /* .htaccess banner small mobile styles */ + .htaccess-content { + flex-direction: column; + align-items: stretch; + gap: 12px; + } + + .htaccess-actions { + justify-content: center; + } + + .htaccess-close { + position: absolute; + top: 10px; + right: 10px; + } } /* Scrollbar */ @@ -1760,7 +2001,33 @@ - + + +
+
+
+ +
+
+ ✨ Revolutionary .htaccess Support Now Live! + + Full .htaccess support + PHP configuration now works + Zero rule rewrites needed + +
+ + +
+
+
{% block content %}{% endblock %} @@ -1841,10 +2108,10 @@ // Backup notification banner logic function checkBackupStatus() { - // Check if user has dismissed the notification permanently (from server-side context) - {% if backup_notification_dismissed %} - return; // Notification already dismissed permanently - {% endif %} + // Check if user has dismissed the notification in this session + if (sessionStorage.getItem('backupNotificationDismissed') === 'true') { + return; + } // Check if user has backup configured (you'll need to implement this API) // For now, we'll show it by default unless they have a backup plan @@ -1871,81 +2138,83 @@ const body = document.body; banner.classList.remove('show'); body.classList.remove('notification-shown'); - - // Dismiss permanently via API - fetch('/base/dismiss_backup_notification', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': getCookie('csrftoken') - } - }) - .then(response => response.json()) - .then(data => { - if (data.status === 1) { - console.log('Backup notification dismissed permanently'); - } else { - console.error('Failed to dismiss backup notification:', data.error); - } - }) - .catch(error => { - console.error('Error dismissing backup notification:', error); - }); + // Remember dismissal for this session + sessionStorage.setItem('backupNotificationDismissed', 'true'); } // AI Scanner Notification Functions function checkAIScannerStatus() { - // Check if user has dismissed the notification permanently (from server-side context) - {% if ai_scanner_notification_dismissed %} - return; // Notification already dismissed permanently - {% endif %} - + // Check if user has dismissed the notification in this session + if (sessionStorage.getItem('aiScannerNotificationDismissed') === 'true') { + return; + } + // Check if we're not already on the AI Scanner page if (!window.location.href.includes('aiscanner')) { showAIScannerNotification(); } } - + function showAIScannerNotification() { const banner = document.getElementById('ai-scanner-notification'); const body = document.body; banner.classList.add('show'); body.classList.add('ai-scanner-shown'); } - + function dismissAIScannerNotification() { const banner = document.getElementById('ai-scanner-notification'); const body = document.body; banner.classList.remove('show'); body.classList.remove('ai-scanner-shown'); - - // Dismiss permanently via API - fetch('/base/dismiss_ai_scanner_notification', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': getCookie('csrftoken') - } - }) - .then(response => response.json()) - .then(data => { - if (data.status === 1) { - console.log('AI scanner notification dismissed permanently'); - } else { - console.error('Failed to dismiss AI scanner notification:', data.error); - } - }) - .catch(error => { - console.error('Error dismissing AI scanner notification:', error); - }); + // Remember dismissal for this session + sessionStorage.setItem('aiScannerNotificationDismissed', 'true'); } - - // Check both notification statuses when page loads + + // .htaccess Feature Notification Functions + function checkHtaccessStatus() { + // Check if user has dismissed the notification permanently (localStorage for longer persistence) + if (localStorage.getItem('htaccessNotificationDismissed') === 'true') { + return; + } + + // Check if notification has been shown today + const lastShown = localStorage.getItem('htaccessNotificationLastShown'); + const today = new Date().toDateString(); + + if (lastShown === today) { + return; + } + + // Show the notification + showHtaccessNotification(); + localStorage.setItem('htaccessNotificationLastShown', today); + } + + function showHtaccessNotification() { + const banner = document.getElementById('htaccess-notification'); + const body = document.body; + banner.classList.add('show'); + body.classList.add('htaccess-shown'); + } + + function dismissHtaccessNotification() { + const banner = document.getElementById('htaccess-notification'); + const body = document.body; + banner.classList.remove('show'); + body.classList.remove('htaccess-shown'); + // Remember dismissal permanently + localStorage.setItem('htaccessNotificationDismissed', 'true'); + } + + // Check all notification statuses when page loads document.addEventListener('DOMContentLoaded', function() { checkBackupStatus(); // Show AI Scanner notification with a slight delay for better UX setTimeout(checkAIScannerStatus, 1000); - + // Show .htaccess notification with additional delay for staggered effect + setTimeout(checkHtaccessStatus, 1500); + // Set active menu state based on current URL setActiveMenuState(); }); diff --git a/cyberpanel.sh b/cyberpanel.sh index c1e4d195e..2e4752201 100644 --- a/cyberpanel.sh +++ b/cyberpanel.sh @@ -1986,9 +1986,30 @@ Current_Dir="$(pwd)" rm -f /usr/local/lsws/cyberpanel-tmp mkdir /usr/local/lsws/cyberpanel-tmp cd /usr/local/lsws/cyberpanel-tmp || exit + +# Try to download timezonedb, but continue if it fails wget -O timezonedb.tgz https://cyberpanel.sh/pecl.php.net/get/timezonedb +if [ ! -f timezonedb.tgz ] || [ ! -s timezonedb.tgz ]; then + log_info "WARNING: Failed to download timezonedb, skipping installation" + cd "$Current_Dir" || exit + rm -rf /usr/local/lsws/cyberpanel-tmp + return 0 +fi + tar xzvf timezonedb.tgz -cd timezonedb-* || exit +if [ ! -d timezonedb-* ]; then + log_info "WARNING: Failed to extract timezonedb, skipping installation" + cd "$Current_Dir" || exit + rm -rf /usr/local/lsws/cyberpanel-tmp + return 0 +fi + +cd timezonedb-* || { + log_info "WARNING: Cannot enter timezonedb directory, skipping installation" + cd "$Current_Dir" || exit + rm -rf /usr/local/lsws/cyberpanel-tmp + return 0 +} # Install required packages for building PHP extensions if [[ "$Server_OS" = "Ubuntu" ]] ; then diff --git a/firewall/static/firewall/firewall.js b/firewall/static/firewall/firewall.js index e70ca46e8..878d8151c 100644 --- a/firewall/static/firewall/firewall.js +++ b/firewall/static/firewall/firewall.js @@ -1471,8 +1471,8 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) { var owaspInstalled = false; var comodoInstalled = false; - var counterOWASP = 0; - var counterComodo = 0; + var owaspInitialized = false; + var comodoInitialized = false; $('#owaspInstalled').change(function () { @@ -1480,15 +1480,13 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) { owaspInstalled = $(this).prop('checked'); $scope.ruleFiles = true; - if (counterOWASP !== 0) { + if (owaspInitialized) { if (owaspInstalled === true) { installModSecRulesPack('installOWASP'); } else { installModSecRulesPack('disableOWASP') } } - - counterOWASP = counterOWASP + 1; }); $('#comodoInstalled').change(function () { @@ -1496,7 +1494,7 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) { $scope.ruleFiles = true; comodoInstalled = $(this).prop('checked'); - if (counterComodo !== 0) { + if (comodoInitialized) { if (comodoInstalled === true) { installModSecRulesPack('installComodo'); @@ -1505,8 +1503,6 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) { } } - counterComodo = counterComodo + 1; - }); @@ -1545,6 +1541,9 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) { $('#owaspInstalled').prop('checked', false); $scope.owaspDisable = true; } + // Mark as initialized after setting initial state + owaspInitialized = true; + if (response.data.comodoInstalled === 1) { $('#comodoInstalled').prop('checked', true); $scope.comodoDisable = false; @@ -1552,6 +1551,8 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) { $('#comodoInstalled').prop('checked', false); $scope.comodoDisable = true; } + // Mark as initialized after setting initial state + comodoInitialized = true; } else { if (response.data.owaspInstalled === 1) { diff --git a/install/installCyberPanel.py b/install/installCyberPanel.py index 582b0bc86..1ff6cb0d6 100644 --- a/install/installCyberPanel.py +++ b/install/installCyberPanel.py @@ -270,10 +270,346 @@ class InstallCyberPanel: # Fallback to known latest version return "6.3.4" + def detectArchitecture(self): + """Detect system architecture - custom binaries only for x86_64""" + try: + import platform + arch = platform.machine() + return arch == "x86_64" + except Exception as msg: + logging.InstallLog.writeToFile(str(msg) + " [detectArchitecture]") + return False + + def detectBinarySuffix(self): + """Detect which binary suffix to use based on OS distribution + Returns 'ubuntu' for Ubuntu/Debian systems + Returns 'rhel8' for RHEL/AlmaLinux/Rocky 8.x systems + Returns 'rhel9' for RHEL/AlmaLinux/Rocky 9.x systems + """ + try: + # Check /etc/os-release first for more accurate detection + if os.path.exists('/etc/os-release'): + with open('/etc/os-release', 'r') as f: + os_release = f.read().lower() + + # Check for Ubuntu/Debian FIRST + if 'ubuntu' in os_release or 'debian' in os_release: + return 'ubuntu' + + # Check for RHEL-based distributions and extract version + if any(x in os_release for x in ['almalinux', 'rocky', 'rhel', 'centos stream']): + # Extract version number + for line in os_release.split('\n'): + if 'version_id' in line: + version = line.split('=')[1].strip('"').split('.')[0] + if version == '9': + return 'rhel9' + elif version == '8': + return 'rhel8' + # Default to rhel9 if version extraction fails + return 'rhel9' + + # Fallback: Use distro variable + # Ubuntu/Debian → ubuntu suffix + if self.distro == ubuntu: + return 'ubuntu' + + # CentOS 8+/AlmaLinux/Rocky/OpenEuler → rhel9 by default + elif self.distro == cent8 or self.distro == openeuler: + return 'rhel9' + + # CentOS 7 → ubuntu suffix (uses libcrypt.so.1) + elif self.distro == centos: + return 'ubuntu' + + # Default to ubuntu for unknown distros + else: + InstallCyberPanel.stdOut("Unknown OS distribution, defaulting to Ubuntu binaries", 1) + return 'ubuntu' + + except Exception as msg: + logging.InstallLog.writeToFile(str(msg) + " [detectBinarySuffix]") + InstallCyberPanel.stdOut("Error detecting OS, defaulting to Ubuntu binaries", 1) + return 'ubuntu' + + def downloadCustomBinary(self, url, destination): + """Download custom binary file""" + try: + InstallCyberPanel.stdOut(f"Downloading {os.path.basename(destination)}...", 1) + + # Use wget for better progress display + command = f'wget -q --show-progress {url} -O {destination}' + install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR) + + # Check if file was downloaded successfully by verifying it exists and has reasonable size + if os.path.exists(destination): + file_size = os.path.getsize(destination) + # Verify file size is reasonable (at least 10KB to avoid error pages/empty files) + if file_size > 10240: # 10KB + if file_size > 1048576: # 1MB + InstallCyberPanel.stdOut(f"Downloaded successfully ({file_size / (1024*1024):.2f} MB)", 1) + else: + InstallCyberPanel.stdOut(f"Downloaded successfully ({file_size / 1024:.2f} KB)", 1) + return True + else: + InstallCyberPanel.stdOut(f"ERROR: Downloaded file too small ({file_size} bytes)", 1) + return False + else: + InstallCyberPanel.stdOut("ERROR: Download failed - file not found", 1) + return False + + except Exception as msg: + logging.InstallLog.writeToFile(str(msg) + " [downloadCustomBinary]") + InstallCyberPanel.stdOut(f"ERROR: {msg}", 1) + return False + + def verifyCustomBinary(self, binary_path): + """Verify custom binary has correct dependencies and can run""" + try: + InstallCyberPanel.stdOut("Verifying custom binary compatibility...", 1) + + # Check library dependencies + command = f'ldd {binary_path}' + result = subprocess.run(command, shell=True, capture_output=True, text=True) + + if result.returncode != 0: + InstallCyberPanel.stdOut("ERROR: Failed to check binary dependencies", 1) + return False + + # Check for missing libraries + if 'not found' in result.stdout: + InstallCyberPanel.stdOut("ERROR: Binary has missing library dependencies:", 1) + for line in result.stdout.split('\n'): + if 'not found' in line: + InstallCyberPanel.stdOut(f" {line.strip()}", 1) + return False + + # Try to run the binary with -v to check if it can execute + command = f'{binary_path} -v' + result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=5) + + if result.returncode != 0: + InstallCyberPanel.stdOut("ERROR: Binary failed to execute", 1) + if result.stderr: + InstallCyberPanel.stdOut(f" Error: {result.stderr.strip()}", 1) + return False + + InstallCyberPanel.stdOut("Binary verification successful", 1) + return True + + except subprocess.TimeoutExpired: + InstallCyberPanel.stdOut("ERROR: Binary verification timed out", 1) + return False + except Exception as msg: + logging.InstallLog.writeToFile(str(msg) + " [verifyCustomBinary]") + InstallCyberPanel.stdOut(f"ERROR: Verification failed: {msg}", 1) + return False + + def rollbackCustomBinary(self, backup_dir, binary_path, module_path): + """Rollback to original binary if custom binary fails""" + try: + InstallCyberPanel.stdOut("Rolling back to original binary...", 1) + + backup_binary = f"{backup_dir}/openlitespeed.backup" + + # Restore original binary if backup exists + if os.path.exists(backup_binary): + shutil.copy2(backup_binary, binary_path) + os.chmod(binary_path, 0o755) + InstallCyberPanel.stdOut("Original binary restored successfully", 1) + else: + InstallCyberPanel.stdOut("WARNING: No backup found, cannot restore", 1) + + # Remove failed custom module + if os.path.exists(module_path): + os.remove(module_path) + InstallCyberPanel.stdOut("Custom module removed", 1) + + InstallCyberPanel.stdOut("Rollback completed", 1) + return True + + except Exception as msg: + logging.InstallLog.writeToFile(str(msg) + " [rollbackCustomBinary]") + InstallCyberPanel.stdOut(f"ERROR: Rollback failed: {msg}", 1) + return False + + def installCustomOLSBinaries(self): + """Install custom OpenLiteSpeed binaries with PHP config support""" + try: + InstallCyberPanel.stdOut("Installing Custom OpenLiteSpeed Binaries", 1) + InstallCyberPanel.stdOut("=" * 50, 1) + + # Check architecture + if not self.detectArchitecture(): + InstallCyberPanel.stdOut("WARNING: Custom binaries only available for x86_64", 1) + InstallCyberPanel.stdOut("Skipping custom binary installation", 1) + InstallCyberPanel.stdOut("Standard OLS will be used", 1) + return True # Not a failure, just skip + + # Detect OS and select appropriate binary suffix + binary_suffix = self.detectBinarySuffix() + InstallCyberPanel.stdOut(f"Detected OS type: using '{binary_suffix}' binaries", 1) + + # URLs for custom binaries with OS-specific paths + BASE_URL = "https://cyberpanel.net/binaries" + + # Set URLs based on OS type + if binary_suffix == 'rhel8': + OLS_BINARY_URL = f"{BASE_URL}/rhel8/openlitespeed-phpconfig-x86_64-rhel8" + MODULE_URL = f"{BASE_URL}/rhel8/cyberpanel_ols_x86_64_rhel8.so" + elif binary_suffix == 'rhel9': + OLS_BINARY_URL = f"{BASE_URL}/rhel9/openlitespeed-phpconfig-x86_64-rhel" + MODULE_URL = f"{BASE_URL}/rhel9/cyberpanel_ols_x86_64_rhel.so" + else: # ubuntu + OLS_BINARY_URL = f"{BASE_URL}/ubuntu/openlitespeed-phpconfig-x86_64-ubuntu" + MODULE_URL = f"{BASE_URL}/ubuntu/cyberpanel_ols_x86_64_ubuntu.so" + + OLS_BINARY_PATH = "/usr/local/lsws/bin/openlitespeed" + MODULE_PATH = "/usr/local/lsws/modules/cyberpanel_ols.so" + + # Create backup + from datetime import datetime + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + backup_dir = f"/usr/local/lsws/backup-{timestamp}" + + try: + os.makedirs(backup_dir, exist_ok=True) + if os.path.exists(OLS_BINARY_PATH): + shutil.copy2(OLS_BINARY_PATH, f"{backup_dir}/openlitespeed.backup") + InstallCyberPanel.stdOut(f"Backup created at: {backup_dir}", 1) + except Exception as e: + InstallCyberPanel.stdOut(f"WARNING: Could not create backup: {e}", 1) + + # Download binaries to temp location + tmp_binary = "/tmp/openlitespeed-custom" + tmp_module = "/tmp/cyberpanel_ols.so" + + InstallCyberPanel.stdOut("Downloading custom binaries...", 1) + + # Download OpenLiteSpeed binary + if not self.downloadCustomBinary(OLS_BINARY_URL, tmp_binary): + InstallCyberPanel.stdOut("ERROR: Failed to download OLS binary", 1) + InstallCyberPanel.stdOut("Continuing with standard OLS", 1) + return True # Not fatal, continue with standard OLS + + # Download module + if not self.downloadCustomBinary(MODULE_URL, tmp_module): + InstallCyberPanel.stdOut("ERROR: Failed to download module", 1) + InstallCyberPanel.stdOut("Continuing with standard OLS", 1) + return True # Not fatal, continue with standard OLS + + # Install OpenLiteSpeed binary + InstallCyberPanel.stdOut("Installing custom binaries...", 1) + + try: + shutil.move(tmp_binary, OLS_BINARY_PATH) + os.chmod(OLS_BINARY_PATH, 0o755) + InstallCyberPanel.stdOut("Installed OpenLiteSpeed binary", 1) + except Exception as e: + InstallCyberPanel.stdOut(f"ERROR: Failed to install binary: {e}", 1) + logging.InstallLog.writeToFile(str(e) + " [installCustomOLSBinaries - binary install]") + return False + + # Install module + try: + os.makedirs(os.path.dirname(MODULE_PATH), exist_ok=True) + shutil.move(tmp_module, MODULE_PATH) + os.chmod(MODULE_PATH, 0o644) + InstallCyberPanel.stdOut("Installed CyberPanel module", 1) + except Exception as e: + InstallCyberPanel.stdOut(f"ERROR: Failed to install module: {e}", 1) + logging.InstallLog.writeToFile(str(e) + " [installCustomOLSBinaries - module install]") + return False + + # Verify installation files exist + if not (os.path.exists(OLS_BINARY_PATH) and os.path.exists(MODULE_PATH)): + InstallCyberPanel.stdOut("ERROR: Installation verification failed - files not found", 1) + return False + + # Verify binary compatibility + if not self.verifyCustomBinary(OLS_BINARY_PATH): + InstallCyberPanel.stdOut("ERROR: Custom binary verification failed", 1) + InstallCyberPanel.stdOut("This usually means wrong binary type for your OS", 1) + + # Rollback to original binary + if os.path.exists(backup_dir): + self.rollbackCustomBinary(backup_dir, OLS_BINARY_PATH, MODULE_PATH) + InstallCyberPanel.stdOut("Continuing with standard OLS", 1) + else: + InstallCyberPanel.stdOut("WARNING: Cannot rollback, no backup found", 1) + + return True # Non-fatal, continue with standard OLS + + # Success! + InstallCyberPanel.stdOut("=" * 50, 1) + InstallCyberPanel.stdOut("Custom Binaries Installed Successfully", 1) + InstallCyberPanel.stdOut("Features enabled:", 1) + InstallCyberPanel.stdOut(" - Apache-style .htaccess support", 1) + InstallCyberPanel.stdOut(" - php_value/php_flag directives", 1) + InstallCyberPanel.stdOut(" - Enhanced header control", 1) + InstallCyberPanel.stdOut(f"Backup: {backup_dir}", 1) + InstallCyberPanel.stdOut("=" * 50, 1) + return True + + except Exception as msg: + logging.InstallLog.writeToFile(str(msg) + " [installCustomOLSBinaries]") + InstallCyberPanel.stdOut(f"ERROR: {msg}", 1) + InstallCyberPanel.stdOut("Continuing with standard OLS", 1) + return True # Non-fatal error, continue + + def configureCustomModule(self): + """Configure CyberPanel module in OpenLiteSpeed config""" + try: + InstallCyberPanel.stdOut("Configuring CyberPanel module...", 1) + + CONFIG_FILE = "/usr/local/lsws/conf/httpd_config.conf" + + if not os.path.exists(CONFIG_FILE): + InstallCyberPanel.stdOut("WARNING: Config file not found", 1) + InstallCyberPanel.stdOut("Module will be auto-loaded", 1) + return True + + # Check if module is already configured + with open(CONFIG_FILE, 'r') as f: + content = f.read() + if 'cyberpanel_ols' in content: + InstallCyberPanel.stdOut("Module already configured", 1) + return True + + # Add module configuration + module_config = """ +module cyberpanel_ols { + ls_enabled 1 +} +""" + # Backup config + shutil.copy2(CONFIG_FILE, f"{CONFIG_FILE}.backup") + + # Append module config + with open(CONFIG_FILE, 'a') as f: + f.write(module_config) + + InstallCyberPanel.stdOut("Module configured successfully", 1) + return True + + except Exception as msg: + logging.InstallLog.writeToFile(str(msg) + " [configureCustomModule]") + InstallCyberPanel.stdOut(f"WARNING: Module configuration failed: {msg}", 1) + InstallCyberPanel.stdOut("Module may still work via auto-load", 1) + return True # Non-fatal + def installLiteSpeed(self): if self.ent == 0: + # Install standard OpenLiteSpeed package self.install_package('openlitespeed') + # Install custom binaries with PHP config support + # This replaces the standard binary with enhanced version + self.installCustomOLSBinaries() + + # Configure the custom module + self.configureCustomModule() + else: try: try: diff --git a/plogical/DockerSites.py b/plogical/DockerSites.py index 4e6c8f12d..820e9fcda 100644 --- a/plogical/DockerSites.py +++ b/plogical/DockerSites.py @@ -308,7 +308,9 @@ extprocessor docker{port} {{ logging.writeToFile("Context already exists, skipping...") return True - # Add proxy context with proper headers for n8n + # Add proxy context with Origin header for n8n + # Note: OLS proxy automatically adds X-Forwarded-* headers + # Only Origin header needs explicit configuration proxy_context = f''' # N8N Proxy Configuration @@ -319,11 +321,7 @@ context / {{ websocket 1 extraHeaders << 10240: # 10KB + if file_size > 1048576: # 1MB + Upgrade.stdOut(f"Downloaded successfully ({file_size / (1024*1024):.2f} MB)", 0) + else: + Upgrade.stdOut(f"Downloaded successfully ({file_size / 1024:.2f} KB)", 0) + return True + else: + Upgrade.stdOut(f"ERROR: Downloaded file too small ({file_size} bytes)", 0) + return False + else: + Upgrade.stdOut("ERROR: Download failed - file not found", 0) + return False + + except Exception as msg: + Upgrade.stdOut(f"ERROR: {msg} [downloadCustomBinary]", 0) + return False + + @staticmethod + def verifyCustomBinary(binary_path): + """Verify custom binary has correct dependencies and can run""" + try: + Upgrade.stdOut("Verifying custom binary compatibility...", 0) + + # Check library dependencies + command = f'ldd {binary_path}' + result = subprocess.run(command, shell=True, capture_output=True, text=True) + + if result.returncode != 0: + Upgrade.stdOut("ERROR: Failed to check binary dependencies", 0) + return False + + # Check for missing libraries + if 'not found' in result.stdout: + Upgrade.stdOut("ERROR: Binary has missing library dependencies:", 0) + for line in result.stdout.split('\n'): + if 'not found' in line: + Upgrade.stdOut(f" {line.strip()}", 0) + return False + + # Try to run the binary with -v to check if it can execute + command = f'{binary_path} -v' + result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=5) + + if result.returncode != 0: + Upgrade.stdOut("ERROR: Binary failed to execute", 0) + if result.stderr: + Upgrade.stdOut(f" Error: {result.stderr.strip()}", 0) + return False + + Upgrade.stdOut("Binary verification successful", 0) + return True + + except subprocess.TimeoutExpired: + Upgrade.stdOut("ERROR: Binary verification timed out", 0) + return False + except Exception as msg: + Upgrade.stdOut(f"ERROR: Verification failed: {msg}", 0) + return False + + @staticmethod + def rollbackCustomBinary(backup_dir, binary_path, module_path): + """Rollback to original binary if custom binary fails""" + try: + Upgrade.stdOut("Rolling back to original binary...", 0) + + backup_binary = f"{backup_dir}/openlitespeed.backup" + + # Restore original binary if backup exists + if os.path.exists(backup_binary): + shutil.copy2(backup_binary, binary_path) + os.chmod(binary_path, 0o755) + Upgrade.stdOut("Original binary restored successfully", 0) + else: + Upgrade.stdOut("WARNING: No backup found, cannot restore", 0) + + # Remove failed custom module + if os.path.exists(module_path): + os.remove(module_path) + Upgrade.stdOut("Custom module removed", 0) + + Upgrade.stdOut("Rollback completed", 0) + return True + + except Exception as msg: + Upgrade.stdOut(f"ERROR: Rollback failed: {msg}", 0) + return False + + @staticmethod + def installCustomOLSBinaries(): + """Install custom OpenLiteSpeed binaries with PHP config support""" + try: + Upgrade.stdOut("Installing Custom OpenLiteSpeed Binaries", 0) + Upgrade.stdOut("=" * 50, 0) + + # Check architecture + if not Upgrade.detectArchitecture(): + Upgrade.stdOut("WARNING: Custom binaries only available for x86_64", 0) + Upgrade.stdOut("Skipping custom binary installation", 0) + Upgrade.stdOut("Standard OLS will be used", 0) + return True # Not a failure, just skip + + # Detect OS and select appropriate binary suffix + binary_suffix = Upgrade.detectBinarySuffix() + Upgrade.stdOut(f"Detected OS type: using '{binary_suffix}' binaries", 0) + + # URLs for custom binaries with OS-specific paths + BASE_URL = "https://cyberpanel.net/binaries" + + # Set URLs based on OS type + if binary_suffix == 'rhel8': + OLS_BINARY_URL = f"{BASE_URL}/rhel8/openlitespeed-phpconfig-x86_64-rhel8" + MODULE_URL = f"{BASE_URL}/rhel8/cyberpanel_ols_x86_64_rhel8.so" + elif binary_suffix == 'rhel9': + OLS_BINARY_URL = f"{BASE_URL}/rhel9/openlitespeed-phpconfig-x86_64-rhel" + MODULE_URL = f"{BASE_URL}/rhel9/cyberpanel_ols_x86_64_rhel.so" + else: # ubuntu + OLS_BINARY_URL = f"{BASE_URL}/ubuntu/openlitespeed-phpconfig-x86_64-ubuntu" + MODULE_URL = f"{BASE_URL}/ubuntu/cyberpanel_ols_x86_64_ubuntu.so" + + OLS_BINARY_PATH = "/usr/local/lsws/bin/openlitespeed" + MODULE_PATH = "/usr/local/lsws/modules/cyberpanel_ols.so" + + # Create backup + from datetime import datetime + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + backup_dir = f"/usr/local/lsws/backup-{timestamp}" + + try: + os.makedirs(backup_dir, exist_ok=True) + if os.path.exists(OLS_BINARY_PATH): + shutil.copy2(OLS_BINARY_PATH, f"{backup_dir}/openlitespeed.backup") + Upgrade.stdOut(f"Backup created at: {backup_dir}", 0) + except Exception as e: + Upgrade.stdOut(f"WARNING: Could not create backup: {e}", 0) + + # Download binaries to temp location + tmp_binary = "/tmp/openlitespeed-custom" + tmp_module = "/tmp/cyberpanel_ols.so" + + Upgrade.stdOut("Downloading custom binaries...", 0) + + # Download OpenLiteSpeed binary + if not Upgrade.downloadCustomBinary(OLS_BINARY_URL, tmp_binary): + Upgrade.stdOut("ERROR: Failed to download OLS binary", 0) + Upgrade.stdOut("Continuing with standard OLS", 0) + return True # Not fatal, continue with standard OLS + + # Download module + if not Upgrade.downloadCustomBinary(MODULE_URL, tmp_module): + Upgrade.stdOut("ERROR: Failed to download module", 0) + Upgrade.stdOut("Continuing with standard OLS", 0) + return True # Not fatal, continue with standard OLS + + # Install OpenLiteSpeed binary + Upgrade.stdOut("Installing custom binaries...", 0) + + try: + shutil.move(tmp_binary, OLS_BINARY_PATH) + os.chmod(OLS_BINARY_PATH, 0o755) + Upgrade.stdOut("Installed OpenLiteSpeed binary", 0) + except Exception as e: + Upgrade.stdOut(f"ERROR: Failed to install binary: {e}", 0) + return False + + # Install module + try: + os.makedirs(os.path.dirname(MODULE_PATH), exist_ok=True) + shutil.move(tmp_module, MODULE_PATH) + os.chmod(MODULE_PATH, 0o644) + Upgrade.stdOut("Installed CyberPanel module", 0) + except Exception as e: + Upgrade.stdOut(f"ERROR: Failed to install module: {e}", 0) + return False + + # Verify installation files exist + if not (os.path.exists(OLS_BINARY_PATH) and os.path.exists(MODULE_PATH)): + Upgrade.stdOut("ERROR: Installation verification failed - files not found", 0) + return False + + # Verify binary compatibility + if not Upgrade.verifyCustomBinary(OLS_BINARY_PATH): + Upgrade.stdOut("ERROR: Custom binary verification failed", 0) + Upgrade.stdOut("This usually means wrong binary type for your OS", 0) + + # Rollback to original binary + if os.path.exists(backup_dir): + Upgrade.rollbackCustomBinary(backup_dir, OLS_BINARY_PATH, MODULE_PATH) + Upgrade.stdOut("Continuing with standard OLS", 0) + else: + Upgrade.stdOut("WARNING: Cannot rollback, no backup found", 0) + + return True # Non-fatal, continue with standard OLS + + # Success! + Upgrade.stdOut("=" * 50, 0) + Upgrade.stdOut("Custom Binaries Installed Successfully", 0) + Upgrade.stdOut("Features enabled:", 0) + Upgrade.stdOut(" - Apache-style .htaccess support", 0) + Upgrade.stdOut(" - php_value/php_flag directives", 0) + Upgrade.stdOut(" - Enhanced header control", 0) + Upgrade.stdOut(f"Backup: {backup_dir}", 0) + Upgrade.stdOut("=" * 50, 0) + return True + + except Exception as msg: + Upgrade.stdOut(f"ERROR: {msg} [installCustomOLSBinaries]", 0) + Upgrade.stdOut("Continuing with standard OLS", 0) + return True # Non-fatal error, continue + + @staticmethod + def isCustomOLSBinaryInstalled(): + """Detect if custom OpenLiteSpeed binary is installed""" + try: + OLS_BINARY_PATH = "/usr/local/lsws/bin/openlitespeed" + + if not os.path.exists(OLS_BINARY_PATH): + return False + + # Check for PHPConfig function signature in binary + command = f'strings {OLS_BINARY_PATH}' + result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + # Look for custom binary markers + return 'set_php_config_value' in result.stdout or 'PHPConfig LSIAPI' in result.stdout + + return False + + except Exception as msg: + Upgrade.stdOut(f"WARNING: Could not detect OLS binary type: {msg}", 0) + return False + + @staticmethod + def installCompatibleModSecurity(): + """Install ModSecurity compatible with custom OpenLiteSpeed binary""" + try: + Upgrade.stdOut("Installing ModSecurity compatible with custom OpenLiteSpeed binary...", 0) + + MODSEC_PATH = "/usr/local/lsws/modules/mod_security.so" + + # Detect OS and select appropriate ModSecurity binary + binary_suffix = Upgrade.detectBinarySuffix() + BASE_URL = "https://cyberpanel.net/binaries" + + if binary_suffix == 'rhel8': + MODSEC_URL = f"{BASE_URL}/rhel8/mod_security-compatible-rhel8.so" + EXPECTED_SHA256 = "8c769dfb42711851ec539e9b6ea649616c14b0e85a53eb18755d200ce29bc442" + EXPECTED_MD5 = "b7b9eb20de42b7f8c9c8f4c7a019d6ff" + elif binary_suffix == 'rhel9': + MODSEC_URL = f"{BASE_URL}/rhel9/mod_security-compatible-rhel.so" + EXPECTED_SHA256 = "db580afc431fda40d46bdae2249ac74690d9175ff6d8b1843f2837d86f8d602f" + EXPECTED_MD5 = "1efa1e442fe8eedf4705584ac194fc95" + else: # ubuntu + MODSEC_URL = f"{BASE_URL}/ubuntu/mod_security-compatible-ubuntu.so" + EXPECTED_SHA256 = "115971fcd44b74bc7c7b097b9cec33ddcfb0fb07bb9b562ec9f4f0691c388a6b" + EXPECTED_MD5 = "c3987c41182355c1290530b6553658db" + + # Download to temp location + tmp_modsec = "/tmp/mod_security_custom.so" + + Upgrade.stdOut(f"Downloading compatible ModSecurity for {binary_suffix}...", 0) + command = f'wget -q --show-progress {MODSEC_URL} -O {tmp_modsec}' + result = subprocess.call(shlex.split(command)) + + if result != 0 or not os.path.exists(tmp_modsec): + Upgrade.stdOut("ERROR: Failed to download ModSecurity", 0) + return False + + # Verify checksum + Upgrade.stdOut("Verifying checksum...", 0) + result = subprocess.run(f'sha256sum {tmp_modsec}', shell=True, capture_output=True, text=True) + actual_sha256 = result.stdout.split()[0] + + if actual_sha256 != EXPECTED_SHA256: + Upgrade.stdOut(f"ERROR: Checksum verification failed", 0) + Upgrade.stdOut(f" Expected: {EXPECTED_SHA256}", 0) + Upgrade.stdOut(f" Got: {actual_sha256}", 0) + os.remove(tmp_modsec) + return False + + Upgrade.stdOut("Checksum verified successfully", 0) + + # Backup existing ModSecurity if present + if os.path.exists(MODSEC_PATH): + backup_path = f"{MODSEC_PATH}.backup.{int(time.time())}" + shutil.copy2(MODSEC_PATH, backup_path) + Upgrade.stdOut(f"Backed up existing ModSecurity to: {backup_path}", 0) + + # Stop OpenLiteSpeed + Upgrade.stdOut("Stopping OpenLiteSpeed...", 0) + subprocess.run(['/usr/local/lsws/bin/lswsctrl', 'stop'], timeout=30) + time.sleep(2) + + # Install compatible ModSecurity + os.makedirs(os.path.dirname(MODSEC_PATH), exist_ok=True) + shutil.copy2(tmp_modsec, MODSEC_PATH) + os.chmod(MODSEC_PATH, 0o755) + os.remove(tmp_modsec) + + Upgrade.stdOut("Compatible ModSecurity installed successfully", 0) + + # Start OpenLiteSpeed + Upgrade.stdOut("Starting OpenLiteSpeed...", 0) + subprocess.run(['/usr/local/lsws/bin/lswsctrl', 'start'], timeout=30) + + Upgrade.stdOut("✓ ModSecurity updated to compatible version", 0) + return True + + except subprocess.TimeoutExpired: + Upgrade.stdOut("ERROR: Timeout during OpenLiteSpeed restart", 0) + return False + except Exception as msg: + Upgrade.stdOut(f"ERROR: ModSecurity installation failed: {msg}", 0) + return False + + @staticmethod + def handleModSecurityCompatibility(): + """Check and update ModSecurity if custom OLS binary is installed""" + try: + MODSEC_PATH = "/usr/local/lsws/modules/mod_security.so" + + # Check if ModSecurity is installed + if not os.path.exists(MODSEC_PATH): + Upgrade.stdOut("ModSecurity not installed, skipping compatibility check", 0) + return True + + # Check if custom OLS binary is installed + if not Upgrade.isCustomOLSBinaryInstalled(): + Upgrade.stdOut("Stock OLS binary detected, ModSecurity compatibility check not needed", 0) + return True + + Upgrade.stdOut("=" * 50, 0) + Upgrade.stdOut("Detected ModSecurity with custom OpenLiteSpeed binary", 0) + Upgrade.stdOut("Updating to ABI-compatible ModSecurity version...", 0) + Upgrade.stdOut("=" * 50, 0) + + # Install compatible version + if Upgrade.installCompatibleModSecurity(): + Upgrade.stdOut("ModSecurity compatibility update completed", 0) + return True + else: + Upgrade.stdOut("WARNING: ModSecurity compatibility update failed", 0) + Upgrade.stdOut("Server may experience crashes. Please contact support.", 0) + return False + + except Exception as msg: + Upgrade.stdOut(f"ERROR in ModSecurity compatibility check: {msg}", 0) + return False + + @staticmethod + def configureCustomModule(): + """Configure CyberPanel module in OpenLiteSpeed config""" + try: + Upgrade.stdOut("Configuring CyberPanel module...", 0) + + CONFIG_FILE = "/usr/local/lsws/conf/httpd_config.conf" + + if not os.path.exists(CONFIG_FILE): + Upgrade.stdOut("WARNING: Config file not found", 0) + Upgrade.stdOut("Module will be auto-loaded", 0) + return True + + # Check if module is already configured + with open(CONFIG_FILE, 'r') as f: + content = f.read() + if 'cyberpanel_ols' in content: + Upgrade.stdOut("Module already configured", 0) + return True + + # Add module configuration + module_config = """ +module cyberpanel_ols { + ls_enabled 1 +} +""" + # Backup config + shutil.copy2(CONFIG_FILE, f"{CONFIG_FILE}.backup") + + # Append module config + with open(CONFIG_FILE, 'a') as f: + f.write(module_config) + + Upgrade.stdOut("Module configured successfully", 0) + return True + + except Exception as msg: + Upgrade.stdOut(f"WARNING: Module configuration failed: {msg}", 0) + Upgrade.stdOut("Module may still work via auto-load", 0) + return True # Non-fatal + @staticmethod def download_install_phpmyadmin(): try: @@ -4287,7 +4752,7 @@ pm.max_spare_servers = 3 ## Add LSPHP7.4 TO LSWS Ent configs if not os.path.exists('/usr/local/lsws/bin/openlitespeed'): - + # This is Enterprise LSWS if os.path.exists('httpd_config.xml'): os.remove('httpd_config.xml') @@ -4295,6 +4760,25 @@ pm.max_spare_servers = 3 Upgrade.executioner(command, command, 0) # os.remove('/usr/local/lsws/conf/httpd_config.xml') # shutil.copy('httpd_config.xml', '/usr/local/lsws/conf/httpd_config.xml') + else: + # This is OpenLiteSpeed - install/upgrade custom binaries + Upgrade.stdOut("Detected OpenLiteSpeed installation", 0) + Upgrade.stdOut("Installing/upgrading custom binaries with .htaccess PHP config support...", 0) + + # Install custom binaries + if Upgrade.installCustomOLSBinaries(): + # Configure the custom module + Upgrade.configureCustomModule() + + # Check and update ModSecurity compatibility if needed + Upgrade.handleModSecurityCompatibility() + + # Restart OpenLiteSpeed to apply changes + Upgrade.stdOut("Restarting OpenLiteSpeed...", 0) + command = '/usr/local/lsws/bin/lswsctrl restart' + Upgrade.executioner(command, 'Restart OpenLiteSpeed', 0) + else: + Upgrade.stdOut("Custom binary installation failed, continuing with upgrade...", 0) Upgrade.updateRepoURL()