Merge pull request #1622 from master3395/v2.5.5-dev

Implement dynamic PHP version detection and configuration updates
This commit is contained in:
Master3395 2025-12-17 19:54:05 +01:00 committed by GitHub
commit ffaa0ca63d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 556 additions and 32 deletions

View File

@ -175,6 +175,52 @@ curl --silent https://cyberpanel.sh/misc/faq.sh | sudo -u nobody bash | less -r
exit
}
detect_default_php() {
# Detect default PHP version dynamically
# Priority: Check symlink, then find highest available version
local php_version=""
local php_version_formatted=""
# Method 1: Check default PHP symlink
if [[ -L /usr/local/lscp/fcgi-bin/lsphp ]]; then
local default_php_path=$(readlink -f /usr/local/lscp/fcgi-bin/lsphp 2>/dev/null)
if [[ -n "$default_php_path" ]]; then
# Extract version from path like /usr/local/lsws/lsphp82/bin/lsphp
# Use sed for better portability (works on all systems)
php_version=$(echo "$default_php_path" | sed -n 's|.*/lsphp\([0-9][0-9]\)/.*|\1|p')
if [[ -n "$php_version" ]] && [[ ${#php_version} -ge 2 ]]; then
php_version_formatted="${php_version:0:1}.${php_version:1}"
fi
fi
fi
# Method 2: Find highest available PHP version (fallback)
if [[ -z "$php_version" ]]; then
# Priority: 85, 84, 83, 82, 81, 80, 74, 73, 72 (newest to oldest, supporting 7.4-8.5)
local php_versions=('85' '84' '83' '82' '81' '80' '74' '73' '72')
for ver in "${php_versions[@]}"; do
if [[ -d "/usr/local/lsws/lsphp${ver}" ]] && [[ -f "/usr/local/lsws/lsphp${ver}/bin/lsphp" ]]; then
php_version="$ver"
if [[ ${#ver} -ge 2 ]]; then
php_version_formatted="${ver:0:1}.${ver:1}"
fi
break
fi
done
fi
# Fallback to PHP 7.4 if nothing found (backwards compatibility)
if [[ -z "$php_version" ]]; then
if [[ -d "/usr/local/lsws/lsphp74" ]]; then
php_version="74"
php_version_formatted="7.4"
fi
fi
echo "$php_version|$php_version_formatted"
}
addons() {
echo -e "\nPlease choose:"
echo -e "\n1. Install Memcached extension for PHP."
@ -205,29 +251,49 @@ addons() {
}
phpmyadmin_limits() {
echo -e "This will change following parameters for PHP 7.3:"
# Detect default PHP version dynamically
local php_info=$(detect_default_php)
local php_version=$(echo "$php_info" | cut -d'|' -f1)
local php_version_formatted=$(echo "$php_info" | cut -d'|' -f2)
if [[ -z "$php_version" ]] || [[ -z "$php_version_formatted" ]]; then
echo -e "\nError: Could not detect PHP version. Please ensure PHP is installed."
exit 1
fi
echo -e "This will change following parameters for PHP ${php_version_formatted}:"
echo -e "Post Max Size from default 8M to 500M"
echo -e "Upload Max Filesize from default 2M to 500M"
echo -e "Memory Limit from default 128M to 768M"
echo -e "Max Execution Time from default 30 to 600"
echo -e "\nPlease note this will also apply to all sites use PHP 7.3"
echo -e "\nPlease note this will also apply to all sites using PHP ${php_version_formatted}"
printf "%s" "Please confirm to proceed: [Y/n]: "
read TMP_YN
if [[ $TMP_YN == "Y" ]] || [[ $TMP_YN == "y" ]] ; then
local php_ini_path=""
# Determine php.ini path based on OS and PHP version
if [[ "$SERVER_OS" == "CentOS" ]] || [[ "$SERVER_OS" == "openEuler" ]] ; then
php_ini_path="/usr/local/lsws/lsphp73/etc/php.ini"
fi
if [[ "$SERVER_OS" == "Ubuntu" ]] ; then
php_ini_path="/usr/local/lsws/lsphp73/etc/php/7.3/litespeed/php.ini"
fi
sed -i 's|post_max_size = 8M|post_max_size = 500M|g' $php_ini_path
sed -i 's|upload_max_filesize = 2M|upload_max_filesize = 500M |g' $php_ini_path
sed -i 's|memory_limit = 128M|memory_limit = 768M|g' $php_ini_path
sed -i 's|max_execution_time = 30|max_execution_time = 600|g' $php_ini_path
systemctl restart lscpd
echo "Change applied..."
php_ini_path="/usr/local/lsws/lsphp${php_version}/etc/php.ini"
elif [[ "$SERVER_OS" == "Ubuntu" ]] ; then
php_ini_path="/usr/local/lsws/lsphp${php_version}/etc/php/${php_version_formatted}/litespeed/php.ini"
fi
# Verify php.ini file exists
if [[ ! -f "$php_ini_path" ]]; then
echo -e "\nError: PHP configuration file not found at: $php_ini_path"
echo -e "Please verify PHP ${php_version_formatted} is properly installed."
exit 1
fi
# Apply changes
sed -i 's|post_max_size = 8M|post_max_size = 500M|g' "$php_ini_path"
sed -i 's|upload_max_filesize = 2M|upload_max_filesize = 500M |g' "$php_ini_path"
sed -i 's|memory_limit = 128M|memory_limit = 768M|g' "$php_ini_path"
sed -i 's|max_execution_time = 30|max_execution_time = 600|g' "$php_ini_path"
systemctl restart lscpd
echo "Change applied to PHP ${php_version_formatted} configuration..."
else
echo -e "Please enter Y or n."
exit
@ -235,14 +301,21 @@ phpmyadmin_limits() {
}
install_php_redis() {
# Install Redis extension for PHP 7.4-8.5 (backwards compatible)
if [[ $SERVER_OS == "CentOS" ]] ; then
yum install -y lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis lsphp70-redis lsphp56-redis lsphp55-redis lsphp54-redis
yum install -y lsphp85-redis lsphp84-redis lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis lsphp70-redis lsphp56-redis lsphp55-redis lsphp54-redis 2>/dev/null || \
yum install -y lsphp84-redis lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis lsphp70-redis lsphp56-redis lsphp55-redis lsphp54-redis 2>/dev/null || \
yum install -y lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis lsphp70-redis lsphp56-redis lsphp55-redis lsphp54-redis
fi
if [[ $SERVER_OS == "Ubuntu" ]] ; then
DEBIAN_FRONTEND=noninteractive apt install -y lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis lsphp70-redis
DEBIAN_FRONTEND=noninteractive apt install -y lsphp85-redis lsphp84-redis lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis lsphp70-redis 2>/dev/null || \
DEBIAN_FRONTEND=noninteractive apt install -y lsphp84-redis lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis lsphp70-redis 2>/dev/null || \
DEBIAN_FRONTEND=noninteractive apt install -y lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis lsphp70-redis
fi
if [[ $SERVER_OS == "openEuler" ]] ; then
dnf install -y lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis
dnf install -y lsphp85-redis lsphp84-redis lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis 2>/dev/null || \
dnf install -y lsphp84-redis lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis 2>/dev/null || \
dnf install -y lsphp83-redis lsphp82-redis lsphp81-redis lsphp80-redis lsphp74-redis lsphp73-redis lsphp72-redis lsphp71-redis
fi
echo -e "\nRedis extension for PHP has been installed..."
exit
@ -359,14 +432,21 @@ read TMP_YN
}
install_php_memcached() {
# Install Memcached extension for PHP 7.4-8.5 (backwards compatible)
if [[ $SERVER_OS == "CentOS" ]] ; then
yum install -y lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached lsphp70-memcached lsphp56-pecl-memcached lsphp55-pecl-memcached lsphp54-pecl-memcached
yum install -y lsphp85-memcached lsphp84-memcached lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached lsphp70-memcached lsphp56-pecl-memcached lsphp55-pecl-memcached lsphp54-pecl-memcached 2>/dev/null || \
yum install -y lsphp84-memcached lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached lsphp70-memcached lsphp56-pecl-memcached lsphp55-pecl-memcached lsphp54-pecl-memcached 2>/dev/null || \
yum install -y lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached lsphp70-memcached lsphp56-pecl-memcached lsphp55-pecl-memcached lsphp54-pecl-memcached
fi
if [[ $SERVER_OS == "Ubuntu" ]] ; then
DEBIAN_FRONTEND=noninteractive apt install -y lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached lsphp70-memcached
DEBIAN_FRONTEND=noninteractive apt install -y lsphp85-memcached lsphp84-memcached lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached lsphp70-memcached 2>/dev/null || \
DEBIAN_FRONTEND=noninteractive apt install -y lsphp84-memcached lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached lsphp70-memcached 2>/dev/null || \
DEBIAN_FRONTEND=noninteractive apt install -y lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached lsphp70-memcached
fi
if [[ $SERVER_OS == "openEuler" ]] ; then
dnf install -y lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached
dnf install -y lsphp85-memcached lsphp84-memcached lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached 2>/dev/null || \
dnf install -y lsphp84-memcached lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached 2>/dev/null || \
dnf install -y lsphp83-memcached lsphp82-memcached lsphp81-memcached lsphp80-memcached lsphp74-memcached lsphp73-memcached lsphp72-memcached lsphp71-memcached
fi
echo -e "\nMemcached extension for PHP has been installed..."
exit

View File

@ -829,13 +829,196 @@ class preFlightsChecks:
if any(distro in content for distro in ['red hat', 'almalinux', 'rocky', 'cloudlinux', 'centos']):
return 'rhel9'
# Default to rhel9 if can't detect (safer default for newer systems)
self.stdOut("WARNING: Could not detect platform, defaulting to rhel9", 1)
return 'rhel9'
# Default to rhel8 if can't detect (safer default - rhel9 binaries may require GLIBC 2.35)
self.stdOut("WARNING: Could not detect platform, defaulting to rhel8", 1)
return 'rhel8'
except Exception as msg:
self.stdOut(f"ERROR detecting platform: {msg}, defaulting to rhel9", 0)
return 'rhel9'
self.stdOut(f"ERROR detecting platform: {msg}, defaulting to rhel8", 0)
return 'rhel8'
def getSystemGLIBCVersion(self):
"""Get the system's GLIBC version"""
try:
import subprocess
# Try to get GLIBC version from ldd
result = subprocess.run(['ldd', '--version'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
# ldd --version output format: "ldd (GNU libc) 2.34"
for line in result.stdout.split('\n'):
if 'GNU libc' in line or 'glibc' in line.lower():
import re
version_match = re.search(r'(\d+)\.(\d+)', line)
if version_match:
major = int(version_match.group(1))
minor = int(version_match.group(2))
return (major, minor)
# Fallback: try to read from libc.so.6
try:
result = subprocess.run(['/lib64/libc.so.6'], capture_output=True, text=True, timeout=5)
if result.returncode != 0 and 'version' in result.stderr.lower():
import re
version_match = re.search(r'(\d+)\.(\d+)', result.stderr)
if version_match:
major = int(version_match.group(1))
minor = int(version_match.group(2))
return (major, minor)
except:
pass
# If we can't detect, assume a safe minimum
self.stdOut("WARNING: Could not detect GLIBC version, assuming 2.34", 1)
return (2, 34)
except Exception as msg:
self.stdOut(f"WARNING: Error detecting GLIBC version: {msg}, assuming 2.34", 0)
return (2, 34)
def checkBinaryGLIBCRequirements(self, binary_path):
"""Check GLIBC version requirements of a binary file"""
try:
import subprocess
import re
# Use objdump to check GLIBC version requirements
# objdump -T shows dynamic symbols and their GLIBC version requirements
result = subprocess.run(['objdump', '-T', binary_path], capture_output=True, text=True, timeout=10)
if result.returncode != 0:
# objdump might not be available, try readelf
result = subprocess.run(['readelf', '-d', binary_path], capture_output=True, text=True, timeout=10)
if result.returncode != 0:
self.stdOut("WARNING: Could not check binary GLIBC requirements (objdump/readelf not available)", 1)
return None
# Look for GLIBC version requirements in the output
# Format: GLIBC_2.35, GLIBC_2.34, etc.
max_version = None
for line in result.stdout.split('\n') + result.stderr.split('\n'):
# Look for GLIBC version symbols
matches = re.findall(r'GLIBC_(\d+)\.(\d+)', line)
for match in matches:
major = int(match[0])
minor = int(match[1])
if max_version is None or (major, minor) > max_version:
max_version = (major, minor)
return max_version
except FileNotFoundError:
# objdump/readelf not available
self.stdOut("WARNING: objdump/readelf not available, skipping GLIBC check", 1)
return None
except Exception as msg:
self.stdOut(f"WARNING: Error checking binary GLIBC requirements: {msg}", 0)
return None
def verifyBinaryCompatibility(self, binary_path):
"""Verify that a binary is compatible with the system's GLIBC version"""
try:
system_glibc = self.getSystemGLIBCVersion()
binary_glibc = self.checkBinaryGLIBCRequirements(binary_path)
if binary_glibc is None:
# Can't check, but we can try a test run
self.stdOut("Cannot verify GLIBC requirements, performing test run...", 1)
return self.testBinaryExecution(binary_path)
self.stdOut(f"System GLIBC: {system_glibc[0]}.{system_glibc[1]}", 1)
self.stdOut(f"Binary requires GLIBC: {binary_glibc[0]}.{binary_glibc[1]}", 1)
# Check if binary requires newer GLIBC than system has
if binary_glibc > system_glibc:
self.stdOut(f"ERROR: Binary requires GLIBC {binary_glibc[0]}.{binary_glibc[1]}, but system has {system_glibc[0]}.{system_glibc[1]}", 1)
return False
self.stdOut("GLIBC compatibility check passed", 1)
return True
except Exception as msg:
self.stdOut(f"WARNING: Error verifying binary compatibility: {msg}", 0)
# If we can't verify, try test execution
return self.testBinaryExecution(binary_path)
def testBinaryExecution(self, binary_path):
"""Test if binary can execute (checks GLIBC compatibility indirectly)"""
try:
import subprocess
# Try to run the binary with --version or -v flag
# This will fail immediately if GLIBC is incompatible
# The error format is: "./binary: /lib64/libc.so.6: version `GLIBC_X.Y' not found"
result = subprocess.run([binary_path, '--version'], capture_output=True, text=True, timeout=5)
# Check both stdout and stderr for GLIBC errors
output = result.stdout + result.stderr
# Look for GLIBC version not found errors
if 'GLIBC' in output and ('not found' in output or 'version' in output.lower()):
# Extract the required GLIBC version from error message
import re
glibc_match = re.search(r"GLIBC_(\d+)\.(\d+)'?\s+not found", output)
if glibc_match:
required_major = int(glibc_match.group(1))
required_minor = int(glibc_match.group(2))
self.stdOut(f"ERROR: Binary requires GLIBC {required_major}.{required_minor} which is not available", 1)
else:
self.stdOut(f"ERROR: Binary GLIBC compatibility test failed: {output[:200]}", 1)
return False
# If binary executed (even with non-zero return code for --version), it's compatible
if result.returncode == 0 or len(result.stdout) > 0:
return True
# If we get here, binary might not support --version, try -v
result = subprocess.run([binary_path, '-v'], capture_output=True, text=True, timeout=5)
output = result.stdout + result.stderr
if 'GLIBC' in output and ('not found' in output or 'version' in output.lower()):
import re
glibc_match = re.search(r"GLIBC_(\d+)\.(\d+)'?\s+not found", output)
if glibc_match:
required_major = int(glibc_match.group(1))
required_minor = int(glibc_match.group(2))
self.stdOut(f"ERROR: Binary requires GLIBC {required_major}.{required_minor} which is not available", 1)
else:
self.stdOut(f"ERROR: Binary GLIBC compatibility test failed: {output[:200]}", 1)
return False
# If no GLIBC error and we got some output, assume compatible
if len(output) > 0:
return True
# If binary doesn't support --version/-v, try to check if it's executable
# by checking file type
try:
result = subprocess.run(['file', binary_path], capture_output=True, text=True, timeout=5)
if 'ELF' in result.stdout and 'executable' in result.stdout:
self.stdOut("WARNING: Cannot test binary execution, but file appears valid", 1)
return True
except:
pass
# Conservative approach: if we can't verify, assume incompatible to be safe
self.stdOut("WARNING: Could not verify binary execution, skipping installation for safety", 1)
return False
except subprocess.TimeoutExpired:
self.stdOut("WARNING: Binary test timed out, assuming compatible", 1)
return True
except FileNotFoundError as e:
# Binary file not found or command not found
self.stdOut(f"ERROR: Binary test failed - file or command not found: {e}", 1)
return False
except Exception as msg:
# Check if it's a GLIBC error in the exception itself
error_str = str(msg)
if 'GLIBC' in error_str and 'not found' in error_str:
self.stdOut(f"ERROR: Binary GLIBC compatibility test failed: {error_str}", 1)
return False
self.stdOut(f"WARNING: Could not test binary execution: {msg}", 0)
# Conservative approach: if we can't test, assume incompatible to be safe
return False
def downloadCustomBinary(self, url, destination, expected_sha256=None):
"""Download custom binary file with optional checksum verification"""
@ -964,6 +1147,23 @@ class preFlightsChecks:
self.stdOut("Continuing with standard OLS", 1)
return True # Not fatal, continue with standard OLS
# CRITICAL: Verify GLIBC compatibility before installation
self.stdOut("Verifying GLIBC compatibility...", 1)
if not self.verifyBinaryCompatibility(tmp_binary):
self.stdOut("=" * 50, 1)
self.stdOut("ERROR: Binary GLIBC requirements incompatible with system", 1)
self.stdOut("This binary would cause OpenLiteSpeed to fail to start", 1)
self.stdOut("Skipping custom binary installation to preserve system stability", 1)
self.stdOut("Standard OLS binary from package manager will be used", 1)
self.stdOut("=" * 50, 1)
# Clean up downloaded binary
try:
if os.path.exists(tmp_binary):
os.remove(tmp_binary)
except:
pass
return True # Not fatal, continue with standard OLS
# Download module with checksum verification (if available)
module_downloaded = False
if MODULE_URL and MODULE_SHA256:
@ -979,11 +1179,31 @@ class preFlightsChecks:
self.stdOut("Installing custom binaries...", 1)
try:
# Make binary executable before moving
os.chmod(tmp_binary, 0o755)
# Final compatibility test before installation
if not self.testBinaryExecution(tmp_binary):
self.stdOut("ERROR: Final binary compatibility test failed", 1)
self.stdOut("Skipping installation to prevent OpenLiteSpeed failure", 1)
try:
if os.path.exists(tmp_binary):
os.remove(tmp_binary)
except:
pass
return True # Not fatal, continue with standard OLS
shutil.move(tmp_binary, OLS_BINARY_PATH)
os.chmod(OLS_BINARY_PATH, 0o755)
self.stdOut("Installed OpenLiteSpeed binary", 1)
except Exception as e:
self.stdOut(f"ERROR: Failed to install binary: {e}", 1)
# Try to restore backup if installation failed
try:
if os.path.exists(f"{backup_dir}/openlitespeed.backup"):
shutil.copy2(f"{backup_dir}/openlitespeed.backup", OLS_BINARY_PATH)
self.stdOut("Restored original binary from backup", 1)
except:
pass
return False
# Install module (if downloaded)

View File

@ -674,13 +674,200 @@ class Upgrade:
if any(distro in content for distro in ['red hat', 'almalinux', 'rocky', 'cloudlinux', 'centos']):
return 'rhel9'
# Default to rhel9 if can't detect (safer default for newer systems)
Upgrade.stdOut("WARNING: Could not detect platform, defaulting to rhel9", 0)
return 'rhel9'
# Default to rhel8 if can't detect (safer default - rhel9 binaries may require GLIBC 2.35)
Upgrade.stdOut("WARNING: Could not detect platform, defaulting to rhel8", 0)
return 'rhel8'
except Exception as msg:
Upgrade.stdOut(f"ERROR detecting platform: {msg}, defaulting to rhel9", 0)
return 'rhel9'
Upgrade.stdOut(f"ERROR detecting platform: {msg}, defaulting to rhel8", 0)
return 'rhel8'
@staticmethod
def getSystemGLIBCVersion():
"""Get the system's GLIBC version"""
try:
import subprocess
# Try to get GLIBC version from ldd
result = subprocess.run(['ldd', '--version'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
# ldd --version output format: "ldd (GNU libc) 2.34"
for line in result.stdout.split('\n'):
if 'GNU libc' in line or 'glibc' in line.lower():
import re
version_match = re.search(r'(\d+)\.(\d+)', line)
if version_match:
major = int(version_match.group(1))
minor = int(version_match.group(2))
return (major, minor)
# Fallback: try to read from libc.so.6
try:
result = subprocess.run(['/lib64/libc.so.6'], capture_output=True, text=True, timeout=5)
if result.returncode != 0 and 'version' in result.stderr.lower():
import re
version_match = re.search(r'(\d+)\.(\d+)', result.stderr)
if version_match:
major = int(version_match.group(1))
minor = int(version_match.group(2))
return (major, minor)
except:
pass
# If we can't detect, assume a safe minimum
Upgrade.stdOut("WARNING: Could not detect GLIBC version, assuming 2.34", 0)
return (2, 34)
except Exception as msg:
Upgrade.stdOut(f"WARNING: Error detecting GLIBC version: {msg}, assuming 2.34", 0)
return (2, 34)
@staticmethod
def checkBinaryGLIBCRequirements(binary_path):
"""Check GLIBC version requirements of a binary file"""
try:
import subprocess
import re
# Use objdump to check GLIBC version requirements
# objdump -T shows dynamic symbols and their GLIBC version requirements
result = subprocess.run(['objdump', '-T', binary_path], capture_output=True, text=True, timeout=10)
if result.returncode != 0:
# objdump might not be available, try readelf
result = subprocess.run(['readelf', '-d', binary_path], capture_output=True, text=True, timeout=10)
if result.returncode != 0:
Upgrade.stdOut("WARNING: Could not check binary GLIBC requirements (objdump/readelf not available)", 0)
return None
# Look for GLIBC version requirements in the output
# Format: GLIBC_2.35, GLIBC_2.34, etc.
max_version = None
for line in result.stdout.split('\n') + result.stderr.split('\n'):
# Look for GLIBC version symbols
matches = re.findall(r'GLIBC_(\d+)\.(\d+)', line)
for match in matches:
major = int(match[0])
minor = int(match[1])
if max_version is None or (major, minor) > max_version:
max_version = (major, minor)
return max_version
except FileNotFoundError:
# objdump/readelf not available
Upgrade.stdOut("WARNING: objdump/readelf not available, skipping GLIBC check", 0)
return None
except Exception as msg:
Upgrade.stdOut(f"WARNING: Error checking binary GLIBC requirements: {msg}", 0)
return None
@staticmethod
def verifyBinaryCompatibility(binary_path):
"""Verify that a binary is compatible with the system's GLIBC version"""
try:
system_glibc = Upgrade.getSystemGLIBCVersion()
binary_glibc = Upgrade.checkBinaryGLIBCRequirements(binary_path)
if binary_glibc is None:
# Can't check, but we can try a test run
Upgrade.stdOut("Cannot verify GLIBC requirements, performing test run...", 0)
return Upgrade.testBinaryExecution(binary_path)
Upgrade.stdOut(f"System GLIBC: {system_glibc[0]}.{system_glibc[1]}", 0)
Upgrade.stdOut(f"Binary requires GLIBC: {binary_glibc[0]}.{binary_glibc[1]}", 0)
# Check if binary requires newer GLIBC than system has
if binary_glibc > system_glibc:
Upgrade.stdOut(f"ERROR: Binary requires GLIBC {binary_glibc[0]}.{binary_glibc[1]}, but system has {system_glibc[0]}.{system_glibc[1]}", 0)
return False
Upgrade.stdOut("GLIBC compatibility check passed", 0)
return True
except Exception as msg:
Upgrade.stdOut(f"WARNING: Error verifying binary compatibility: {msg}", 0)
# If we can't verify, try test execution
return Upgrade.testBinaryExecution(binary_path)
@staticmethod
def testBinaryExecution(binary_path):
"""Test if binary can execute (checks GLIBC compatibility indirectly)"""
try:
import subprocess
# Try to run the binary with --version or -v flag
# This will fail immediately if GLIBC is incompatible
# The error format is: "./binary: /lib64/libc.so.6: version `GLIBC_X.Y' not found"
result = subprocess.run([binary_path, '--version'], capture_output=True, text=True, timeout=5)
# Check both stdout and stderr for GLIBC errors
output = result.stdout + result.stderr
# Look for GLIBC version not found errors
if 'GLIBC' in output and ('not found' in output or 'version' in output.lower()):
# Extract the required GLIBC version from error message
import re
glibc_match = re.search(r"GLIBC_(\d+)\.(\d+)'?\s+not found", output)
if glibc_match:
required_major = int(glibc_match.group(1))
required_minor = int(glibc_match.group(2))
Upgrade.stdOut(f"ERROR: Binary requires GLIBC {required_major}.{required_minor} which is not available", 0)
else:
Upgrade.stdOut(f"ERROR: Binary GLIBC compatibility test failed: {output[:200]}", 0)
return False
# If binary executed (even with non-zero return code for --version), it's compatible
if result.returncode == 0 or len(result.stdout) > 0:
return True
# If we get here, binary might not support --version, try -v
result = subprocess.run([binary_path, '-v'], capture_output=True, text=True, timeout=5)
output = result.stdout + result.stderr
if 'GLIBC' in output and ('not found' in output or 'version' in output.lower()):
import re
glibc_match = re.search(r"GLIBC_(\d+)\.(\d+)'?\s+not found", output)
if glibc_match:
required_major = int(glibc_match.group(1))
required_minor = int(glibc_match.group(2))
Upgrade.stdOut(f"ERROR: Binary requires GLIBC {required_major}.{required_minor} which is not available", 0)
else:
Upgrade.stdOut(f"ERROR: Binary GLIBC compatibility test failed: {output[:200]}", 0)
return False
# If no GLIBC error and we got some output, assume compatible
if len(output) > 0:
return True
# If binary doesn't support --version/-v, try to check if it's executable
# by checking file type
try:
result = subprocess.run(['file', binary_path], capture_output=True, text=True, timeout=5)
if 'ELF' in result.stdout and 'executable' in result.stdout:
Upgrade.stdOut("WARNING: Cannot test binary execution, but file appears valid", 0)
return True
except:
pass
# Conservative approach: if we can't verify, assume incompatible to be safe
Upgrade.stdOut("WARNING: Could not verify binary execution, skipping installation for safety", 0)
return False
except subprocess.TimeoutExpired:
Upgrade.stdOut("WARNING: Binary test timed out, assuming compatible", 0)
return True
except FileNotFoundError as e:
# Binary file not found or command not found
Upgrade.stdOut(f"ERROR: Binary test failed - file or command not found: {e}", 0)
return False
except Exception as msg:
# Check if it's a GLIBC error in the exception itself
error_str = str(msg)
if 'GLIBC' in error_str and 'not found' in error_str:
Upgrade.stdOut(f"ERROR: Binary GLIBC compatibility test failed: {error_str}", 0)
return False
Upgrade.stdOut(f"WARNING: Could not test binary execution: {msg}", 0)
# Conservative approach: if we can't test, assume incompatible to be safe
return False
@staticmethod
def downloadCustomBinary(url, destination, expected_sha256=None):
@ -811,6 +998,23 @@ class Upgrade:
Upgrade.stdOut("Continuing with standard OLS", 0)
return True # Not fatal, continue with standard OLS
# CRITICAL: Verify GLIBC compatibility before installation
Upgrade.stdOut("Verifying GLIBC compatibility...", 0)
if not Upgrade.verifyBinaryCompatibility(tmp_binary):
Upgrade.stdOut("=" * 50, 0)
Upgrade.stdOut("ERROR: Binary GLIBC requirements incompatible with system", 0)
Upgrade.stdOut("This binary would cause OpenLiteSpeed to fail to start", 0)
Upgrade.stdOut("Skipping custom binary installation to preserve system stability", 0)
Upgrade.stdOut("Standard OLS binary from package manager will be used", 0)
Upgrade.stdOut("=" * 50, 0)
# Clean up downloaded binary
try:
if os.path.exists(tmp_binary):
os.remove(tmp_binary)
except:
pass
return True # Not fatal, continue with standard OLS
# Download module with checksum verification (if available)
module_downloaded = False
if MODULE_URL and MODULE_SHA256:
@ -826,11 +1030,31 @@ class Upgrade:
Upgrade.stdOut("Installing custom binaries...", 0)
try:
# Make binary executable before moving
os.chmod(tmp_binary, 0o755)
# Final compatibility test before installation
if not Upgrade.testBinaryExecution(tmp_binary):
Upgrade.stdOut("ERROR: Final binary compatibility test failed", 0)
Upgrade.stdOut("Skipping installation to prevent OpenLiteSpeed failure", 0)
try:
if os.path.exists(tmp_binary):
os.remove(tmp_binary)
except:
pass
return True # Not fatal, continue with standard OLS
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)
# Try to restore backup if installation failed
try:
if os.path.exists(f"{backup_dir}/openlitespeed.backup"):
shutil.copy2(f"{backup_dir}/openlitespeed.backup", OLS_BINARY_PATH)
Upgrade.stdOut("Restored original binary from backup", 0)
except:
pass
return False
# Install module (if downloaded)