diff --git a/cyberpanel.sh b/cyberpanel.sh index f5441b811..0748c7212 100644 --- a/cyberpanel.sh +++ b/cyberpanel.sh @@ -81,7 +81,12 @@ detect_os() { fi # Detect OS - if echo $OUTPUT | grep -q "AlmaLinux 9" ; then + if echo $OUTPUT | grep -q "AlmaLinux 10" ; then + SERVER_OS="AlmaLinux10" + OS_FAMILY="rhel" + PACKAGE_MANAGER="dnf" + print_status "Detected: AlmaLinux 10" + elif echo $OUTPUT | grep -q "AlmaLinux 9" ; then SERVER_OS="AlmaLinux9" OS_FAMILY="rhel" PACKAGE_MANAGER="dnf" @@ -133,7 +138,7 @@ detect_os() { print_status "Detected: Debian GNU/Linux 11" else print_status "ERROR: Unsupported OS detected" - print_status "Supported OS: AlmaLinux 8/9, CentOS 8/9, Rocky Linux 8/9, Ubuntu 20.04/22.04, Debian 11/12" + print_status "Supported OS: AlmaLinux 8/9/10, CentOS 8/9, Rocky Linux 8/9, Ubuntu 20.04/22.04, Debian 11/12" return 1 fi @@ -477,8 +482,8 @@ install_dependencies() { echo "" echo "Step 3/4: Installing core packages..." - if [ "$SERVER_OS" = "AlmaLinux9" ] || [ "$SERVER_OS" = "CentOS9" ] || [ "$SERVER_OS" = "RockyLinux9" ]; then - # AlmaLinux 9 / CentOS 9 / Rocky Linux 9 + if [ "$SERVER_OS" = "AlmaLinux9" ] || [ "$SERVER_OS" = "AlmaLinux10" ] || [ "$SERVER_OS" = "CentOS9" ] || [ "$SERVER_OS" = "RockyLinux9" ]; then + # AlmaLinux 9/10 / CentOS 9 / Rocky Linux 9 $PACKAGE_MANAGER install -y ImageMagick gd libicu oniguruma python3 python3-pip python3-devel 2>/dev/null || true $PACKAGE_MANAGER install -y aspell 2>/dev/null || print_status "WARNING: aspell not available, skipping..." $PACKAGE_MANAGER install -y libc-client-devel 2>/dev/null || print_status "WARNING: libc-client-devel not available, skipping..." @@ -609,18 +614,180 @@ install_cyberpanel_direct() { mkdir -p "$temp_dir" cd "$temp_dir" || return 1 + # CRITICAL: Disable MariaDB 12.1 repository and add dnf exclude if MariaDB 10.x is installed + # This must be done BEFORE Pre_Install_Setup_Repository runs + if command -v rpm >/dev/null 2>&1; then + # Check if MariaDB 10.x is installed + if rpm -qa | grep -qiE "^(mariadb-server|mysql-server|MariaDB-server)" 2>/dev/null; then + local mariadb_version=$(mysql --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) + if [ -n "$mariadb_version" ]; then + local major_ver=$(echo "$mariadb_version" | cut -d. -f1) + local minor_ver=$(echo "$mariadb_version" | cut -d. -f2) + + # Check if it's MariaDB 10.x (major version < 12) + if [ "$major_ver" -lt 12 ]; then + print_status "MariaDB $mariadb_version detected, adding dnf exclude to prevent upgrade attempts" + + # Add MariaDB-server to dnf excludes (multiple formats for compatibility) + local dnf_conf="/etc/dnf/dnf.conf" + local exclude_added=false + + if [ -f "$dnf_conf" ]; then + # Check if [main] section exists + if grep -q "^\[main\]" "$dnf_conf" 2>/dev/null; then + # [main] section exists, add exclude there + if ! grep -q "exclude=.*MariaDB-server" "$dnf_conf" 2>/dev/null; then + if grep -q "^exclude=" "$dnf_conf" 2>/dev/null; then + # Append to existing exclude line in [main] section + sed -i '/^\[main\]/,/^\[/ { /^exclude=/ s/$/ MariaDB-server*/ }' "$dnf_conf" + else + # Add new exclude line after [main] + sed -i '/^\[main\]/a exclude=MariaDB-server*' "$dnf_conf" + fi + exclude_added=true + fi + else + # No [main] section, add it with exclude + if ! grep -q "exclude=.*MariaDB-server" "$dnf_conf" 2>/dev/null; then + echo "" >> "$dnf_conf" + echo "[main]" >> "$dnf_conf" + echo "exclude=MariaDB-server*" >> "$dnf_conf" + exclude_added=true + fi + fi + else + # Create dnf.conf with exclude + echo "[main]" > "$dnf_conf" + echo "exclude=MariaDB-server*" >> "$dnf_conf" + exclude_added=true + fi + + if [ "$exclude_added" = true ]; then + print_status "Added MariaDB-server* to dnf excludes in $dnf_conf" + fi + + # Also add to yum.conf for compatibility + local yum_conf="/etc/yum.conf" + if [ -f "$yum_conf" ]; then + if ! grep -q "exclude=.*MariaDB-server" "$yum_conf" 2>/dev/null; then + if grep -q "^exclude=" "$yum_conf" 2>/dev/null; then + sed -i 's/^exclude=\(.*\)/exclude=\1 MariaDB-server*/' "$yum_conf" + else + echo "exclude=MariaDB-server*" >> "$yum_conf" + fi + print_status "Added MariaDB-server* to yum excludes" + fi + fi + + # Create a function to disable MariaDB repositories (will be called after repository setup) + disable_mariadb_repos() { + local repo_files=( + "/etc/yum.repos.d/mariadb-main.repo" + "/etc/yum.repos.d/mariadb.repo" + "/etc/yum.repos.d/mariadb-12.1.repo" + ) + + # Also check for any mariadb repo files + while IFS= read -r repo_file; do + repo_files+=("$repo_file") + done < <(find /etc/yum.repos.d -name "*mariadb*.repo" 2>/dev/null) + + for repo_file in "${repo_files[@]}"; do + if [ -f "$repo_file" ] && [ -n "$repo_file" ]; then + # First, try to disable by setting enabled=0 + sed -i 's/^enabled\s*=\s*1/enabled=0/g' "$repo_file" 2>/dev/null + + # If file contains MariaDB 12.1 references, disable or remove it + if grep -qi "mariadb.*12\|12.*mariadb\|mariadb-main" "$repo_file" 2>/dev/null; then + # Try to add enabled=0 to each [mariadb...] section + python3 -c " +import re +import sys +try: + with open('$repo_file', 'r') as f: + content = f.read() + + # Replace enabled=1 with enabled=0 + content = re.sub(r'(enabled\s*=\s*)1', r'\g<1>0', content, flags=re.IGNORECASE) + + # Add enabled=0 after [mariadb...] sections if not present + lines = content.split('\n') + new_lines = [] + in_mariadb_section = False + has_enabled = False + + for i, line in enumerate(lines): + new_lines.append(line) + if re.match(r'^\s*\[.*mariadb.*\]', line, re.IGNORECASE): + in_mariadb_section = True + has_enabled = False + elif in_mariadb_section: + if re.match(r'^\s*enabled\s*=', line, re.IGNORECASE): + has_enabled = True + elif re.match(r'^\s*\[', line) and not re.match(r'^\s*\[.*mariadb.*\]', line, re.IGNORECASE): + if not has_enabled: + new_lines.insert(-1, 'enabled=0') + in_mariadb_section = False + has_enabled = False + + if in_mariadb_section and not has_enabled: + new_lines.append('enabled=0') + + with open('$repo_file', 'w') as f: + f.write('\n'.join(new_lines)) +except: + # Fallback: just rename the file + import os + os.rename('$repo_file', '${repo_file}.disabled') +" 2>/dev/null || \ + # Fallback: rename the file to disable it + mv "$repo_file" "${repo_file}.disabled" 2>/dev/null || true + fi + fi + done + } + + # Export function so it can be called from installer + export -f disable_mariadb_repos + export MARIADB_VERSION="$mariadb_version" + + # Also set up a background process to monitor and disable repos + ( + while [ ! -f /tmp/cyberpanel_install_complete ]; do + sleep 2 + if [ -f /etc/yum.repos.d/mariadb-main.repo ] || [ -f /etc/yum.repos.d/mariadb.repo ]; then + disable_mariadb_repos + fi + done + ) & + local monitor_pid=$! + echo "$monitor_pid" > /tmp/cyberpanel_repo_monitor.pid + print_status "Started background process to monitor and disable MariaDB repositories" + fi + fi + fi + fi + # Download the working CyberPanel installation files # Use master3395 fork which has our fixes + # Try to download the actual installer script (install/install.py) from the repository echo "Downloading from: https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh" - # Try development branch first, fallback to stable + + # First, try to download the repository archive to get the correct installer + local archive_url="https://github.com/master3395/cyberpanel/archive/v2.5.5-dev.tar.gz" local installer_url="https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh" - # Test if the development branch exists - if ! curl -s --head "$installer_url" | grep -q "200 OK"; then - echo " Development branch not available, falling back to stable" - installer_url="https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh" - else + # Test if the development branch archive exists + if curl -s --head "$archive_url" | grep -q "200 OK"; then echo " Using development branch (v2.5.5-dev) from master3395/cyberpanel" + else + echo " Development branch archive not available, trying installer script directly..." + # Test if the installer script exists + if ! curl -s --head "$installer_url" | grep -q "200 OK"; then + echo " Development branch not available, falling back to stable" + installer_url="https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh" + archive_url="https://github.com/master3395/cyberpanel/archive/stable.tar.gz" + fi fi curl --silent -o cyberpanel_installer.sh "$installer_url" 2>/dev/null @@ -629,6 +796,71 @@ install_cyberpanel_direct() { return 1 fi + # CRITICAL: Patch the installer script to skip MariaDB installation if 10.x is already installed + if [ -n "$MARIADB_VERSION" ] && [ "$major_ver" -lt 12 ] 2>/dev/null; then + print_status "Patching installer script to skip MariaDB installation..." + + # Create a backup + cp cyberpanel_installer.sh cyberpanel_installer.sh.backup + + # Use Python to properly patch the installer script + python3 -c " +import re +import sys + +try: + with open('cyberpanel_installer.sh', 'r') as f: + content = f.read() + + original_content = content + + # Pattern: Add --exclude=MariaDB-server* to dnf/yum install commands that install mariadb-server + # Match: (dnf|yum) install [flags] [packages including mariadb-server] + def add_exclude(match): + cmd = match.group(0) + # Check if --exclude is already present + if '--exclude=MariaDB-server' in cmd: + return cmd + # Add --exclude=MariaDB-server* after install and flags, before packages + return re.sub(r'((?:dnf|yum)\s+install\s+(?:-[^\s]+\s+)*)', r'\1--exclude=MariaDB-server* ', cmd, flags=re.IGNORECASE) + + # Find all dnf/yum install commands that mention mariadb-server + content = re.sub( + r'(?:dnf|yum)\s+install[^;]*?mariadb-server[^;]*', + add_exclude, + content, + flags=re.IGNORECASE | re.MULTILINE + ) + + # Also handle MariaDB-server (capitalized) + content = re.sub( + r'(?:dnf|yum)\s+install[^;]*?MariaDB-server[^;]*', + add_exclude, + content, + flags=re.IGNORECASE | re.MULTILINE + ) + + # Only write if content changed + if content != original_content: + with open('cyberpanel_installer.sh', 'w') as f: + f.write(content) + print('Installer script patched successfully') + else: + print('No changes needed in installer script') + +except Exception as e: + print(f'Error patching installer script: {e}') + sys.exit(1) +" 2>/dev/null && print_status "Installer script patched successfully" || { + # Fallback: Simple sed-based patching if Python fails + sed -i 's/\(dnf\|yum\) install\([^;]*\)mariadb-server/\1 install\2--exclude=MariaDB-server* mariadb-server/gi' cyberpanel_installer.sh 2>/dev/null + sed -i 's/\(dnf\|yum\) install\([^;]*\)MariaDB-server/\1 install\2--exclude=MariaDB-server* MariaDB-server/gi' cyberpanel_installer.sh 2>/dev/null + print_status "Installer script patched (fallback method)" + } + + print_status "Installer script patched to exclude MariaDB-server from installation" + fi + # Make script executable and verify chmod 755 cyberpanel_installer.sh 2>/dev/null || true if [ ! -x "cyberpanel_installer.sh" ]; then @@ -657,13 +889,27 @@ install_cyberpanel_direct() { # Copy install directory to current location if [ "$installer_url" = "https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh" ]; then - cp -r cyberpanel-stable/install . 2>/dev/null || true - cp -r cyberpanel-stable/install.sh . 2>/dev/null || true + if [ -d "cyberpanel-stable" ]; then + cp -r cyberpanel-stable/install . 2>/dev/null || true + cp -r cyberpanel-stable/install.sh . 2>/dev/null || true + fi else - cp -r cyberpanel-v2.5.5-dev/install . 2>/dev/null || true - cp -r cyberpanel-v2.5.5-dev/install.sh . 2>/dev/null || true + if [ -d "cyberpanel-v2.5.5-dev" ]; then + cp -r cyberpanel-v2.5.5-dev/install . 2>/dev/null || true + cp -r cyberpanel-v2.5.5-dev/install.sh . 2>/dev/null || true + fi fi + # Verify install directory was copied + if [ ! -d "install" ]; then + print_status "ERROR: install directory not found after extraction" + print_status "Archive contents:" + ls -la 2>/dev/null | head -20 + return 1 + fi + + print_status "Verified install directory exists" + echo " ā CyberPanel installation files downloaded" echo " š Starting CyberPanel installation..." echo "" @@ -700,27 +946,301 @@ install_cyberpanel_direct() { fi echo "" - # Run installer and show live output, capturing the password - # Use bash to execute to avoid permission issues - local installer_script="cyberpanel_installer.sh" - if [ ! -f "$installer_script" ]; then - print_status "ERROR: cyberpanel_installer.sh not found in current directory: $(pwd)" - return 1 + # CRITICAL: Use install/install.py directly instead of cyberpanel_installer.sh + # The cyberpanel_installer.sh is the old wrapper that doesn't support auto-install + # install/install.py is the actual installer that we can control + local installer_py="install/install.py" + if [ -f "$installer_py" ]; then + print_status "Using install/install.py directly for installation (non-interactive mode)" + + # CRITICAL: Patch install.py to exclude MariaDB-server from dnf/yum commands + if [ -n "$MARIADB_VERSION" ] && [ "$major_ver" -lt 12 ] 2>/dev/null; then + print_status "Patching install.py to exclude MariaDB-server from installation commands..." + + # Create backup + cp "$installer_py" "${installer_py}.backup" 2>/dev/null || true + + # Patch install.py to add --exclude=MariaDB-server* to dnf/yum install commands + python3 -c " +import re +import sys + +try: + with open('$installer_py', 'r') as f: + content = f.read() + + original_content = content + + # Pattern: Add --exclude=MariaDB-server* to dnf/yum install commands that install mariadb-server + def add_exclude(match): + cmd = match.group(0) + # Check if --exclude is already present + if '--exclude=MariaDB-server' in cmd: + return cmd + # Add --exclude=MariaDB-server* after install and flags, before packages + return re.sub(r'((?:dnf|yum)\s+install\s+(?:-[^\s]+\s+)*)', r'\1--exclude=MariaDB-server* ', cmd, flags=re.IGNORECASE) + + # Find all dnf/yum install commands that mention mariadb-server + content = re.sub( + r'(?:dnf|yum)\s+install[^;]*?mariadb-server[^;]*', + add_exclude, + content, + flags=re.IGNORECASE | re.MULTILINE + ) + + # Also handle MariaDB-server (capitalized) and in Python strings + content = re.sub( + r'(\"|\')(?:dnf|yum)\s+install[^\"]*?mariadb-server[^\"]*(\"|\')', + lambda m: m.group(1) + re.sub(r'((?:dnf|yum)\s+install\s+(?:-[^\s]+\s+)*)', r'\1--exclude=MariaDB-server* ', m.group(0)[1:-1], flags=re.IGNORECASE) + m.group(2), + content, + flags=re.IGNORECASE | re.MULTILINE + ) + + # Only write if content changed + if content != original_content: + with open('$installer_py', 'w') as f: + f.write(content) + print('install.py patched successfully') + else: + print('No changes needed in install.py') + +except Exception as e: + print(f'Error patching install.py: {e}') + sys.exit(1) +" 2>/dev/null && print_status "install.py patched successfully" || { + # Fallback: Simple sed-based patching if Python fails + sed -i 's/\(dnf\|yum\) install\([^;]*\)mariadb-server/\1 install\2--exclude=MariaDB-server* mariadb-server/gi' "$installer_py" 2>/dev/null + sed -i 's/\(dnf\|yum\) install\([^;]*\)MariaDB-server/\1 install\2--exclude=MariaDB-server* MariaDB-server/gi' "$installer_py" 2>/dev/null + print_status "install.py patched (fallback method)" + } + fi + + # If MariaDB 10.x is installed, disable repositories right before running installer + if [ -n "$MARIADB_VERSION" ] && [ -f /tmp/cyberpanel_repo_monitor.pid ]; then + # Call the disable function one more time before installer runs + if type disable_mariadb_repos >/dev/null 2>&1; then + disable_mariadb_repos + fi + fi + + # Get server IP address (required by install.py) + local server_ip + if command -v curl >/dev/null 2>&1; then + server_ip=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || curl -s --max-time 5 https://icanhazip.com 2>/dev/null || echo "") + fi + + if [ -z "$server_ip" ]; then + # Fallback: try to get IP from network interfaces + server_ip=$(ip route get 8.8.8.8 2>/dev/null | awk '{print $7; exit}' || \ + hostname -I 2>/dev/null | awk '{print $1}' || \ + echo "127.0.0.1") + fi + + if [ -z "$server_ip" ] || [ "$server_ip" = "127.0.0.1" ]; then + print_status "WARNING: Could not detect public IP, using 127.0.0.1" + server_ip="127.0.0.1" + fi + + print_status "Detected server IP: $server_ip" + + # CRITICAL: Install Python MySQL dependencies before running install.py + # installCyberPanel.py requires MySQLdb (mysqlclient) which needs development headers + echo "" + echo "===============================================================================================================" + echo "Installing Python MySQL dependencies (required for installCyberPanel.py)..." + echo "===============================================================================================================" + print_status "Installing Python MySQL dependencies..." + + # Detect OS for package installation + local os_family="" + if [ -f /etc/os-release ]; then + . /etc/os-release + case "$ID" in + almalinux|rocky|centos|rhel|fedora) + os_family="rhel" + print_status "Detected RHEL-based OS: $ID" + ;; + ubuntu|debian) + os_family="debian" + print_status "Detected Debian-based OS: $ID" + ;; + *) + print_status "Unknown OS ID: $ID, defaulting to RHEL-based" + os_family="rhel" + ;; + esac + else + print_status "WARNING: /etc/os-release not found, defaulting to RHEL-based" + os_family="rhel" + fi + + # Install MariaDB/MySQL development headers and Python mysqlclient + if [ "$os_family" = "rhel" ]; then + # RHEL-based (AlmaLinux, Rocky, CentOS, RHEL) + print_status "Installing MariaDB development headers for RHEL-based system..." + + # Try to install mariadb-devel (works with MariaDB 10.x and 12.x) + # NOTE: We need mariadb-devel even if we excluded MariaDB-server + # The exclude only applies to MariaDB-server, not development packages + if command -v dnf >/dev/null 2>&1; then + # For AlmaLinux 9/10 and newer - show output for debugging + print_status "Attempting to install mariadb-devel (development headers only, not server)..." + # Temporarily remove exclude for devel packages if needed + local dnf_exclude_backup="" + if [ -f /etc/dnf/dnf.conf ] && grep -q "exclude=.*MariaDB" /etc/dnf/dnf.conf; then + # Check if exclude is too broad + if grep -q "exclude=.*MariaDB-server.*MariaDB-devel" /etc/dnf/dnf.conf || \ + grep -q "exclude=.*MariaDB\*" /etc/dnf/dnf.conf; then + print_status "Temporarily adjusting dnf exclude to allow mariadb-devel installation..." + # We only want to exclude MariaDB-server, not devel packages + sed -i 's/exclude=\(.*\)MariaDB-server\(.*\)MariaDB-devel\(.*\)/exclude=\1MariaDB-server\2\3/' /etc/dnf/dnf.conf 2>/dev/null || true + sed -i 's/exclude=\(.*\)MariaDB\*\(.*\)/exclude=\1MariaDB-server*\2/' /etc/dnf/dnf.conf 2>/dev/null || true + fi + fi + + if dnf install -y --allowerasing --skip-broken --nobest \ + mariadb-devel pkgconfig gcc python3-devel python3-pip; then + print_status "ā Successfully installed mariadb-devel" + elif dnf install -y --allowerasing --skip-broken --nobest \ + mysql-devel pkgconfig gcc python3-devel python3-pip; then + print_status "ā Successfully installed mysql-devel" + elif dnf install -y --allowerasing --skip-broken --nobest \ + mariadb-connector-c-devel pkgconfig gcc python3-devel python3-pip; then + print_status "ā Successfully installed mariadb-connector-c-devel" + else + print_status "ā ļø WARNING: Failed to install MariaDB development headers" + print_status "This may cause MySQLdb installation to fail" + fi + else + # For older systems with yum + print_status "Using yum to install mariadb-devel..." + if yum install -y mariadb-devel pkgconfig gcc python3-devel python3-pip; then + print_status "ā Successfully installed mariadb-devel" + elif yum install -y mysql-devel pkgconfig gcc python3-devel python3-pip; then + print_status "ā Successfully installed mysql-devel" + else + print_status "ā ļø WARNING: Failed to install MariaDB development headers" + fi + fi + + # Install mysqlclient Python package + print_status "Installing mysqlclient Python package..." + python3 -m pip install --upgrade pip setuptools wheel 2>&1 | grep -v "already satisfied" || true + if python3 -m pip install mysqlclient 2>&1; then + print_status "ā Successfully installed mysqlclient" + else + # If pip install fails, try with build dependencies + print_status "Retrying mysqlclient installation with build dependencies..." + python3 -m pip install --no-cache-dir mysqlclient 2>&1 || { + print_status "ā ļø WARNING: Failed to install mysqlclient, trying alternative method..." + # Try installing from source + python3 -m pip install --no-binary mysqlclient mysqlclient 2>&1 || true + } + fi + + elif [ "$os_family" = "debian" ]; then + # Debian-based (Ubuntu, Debian) + print_status "Installing MariaDB development headers for Debian-based system..." + apt-get update -y + if apt-get install -y libmariadb-dev libmariadb-dev-compat pkg-config build-essential python3-dev python3-pip; then + print_status "ā Successfully installed MariaDB development headers" + elif apt-get install -y default-libmysqlclient-dev pkg-config build-essential python3-dev python3-pip; then + print_status "ā Successfully installed MySQL development headers" + else + print_status "ā ļø WARNING: Failed to install MariaDB/MySQL development headers" + fi + + # Install mysqlclient Python package + print_status "Installing mysqlclient Python package..." + python3 -m pip install --upgrade pip setuptools wheel 2>&1 | grep -v "already satisfied" || true + if python3 -m pip install mysqlclient 2>&1; then + print_status "ā Successfully installed mysqlclient" + else + print_status "Retrying mysqlclient installation with build dependencies..." + python3 -m pip install --no-cache-dir mysqlclient 2>&1 || true + fi + fi + + # Verify MySQLdb is available + print_status "Verifying MySQLdb module availability..." + if python3 -c "import MySQLdb; print('MySQLdb version:', MySQLdb.__version__)" 2>&1; then + print_status "ā MySQLdb module is available and working" + else + print_status "ā ļø WARNING: MySQLdb module not available" + print_status "Attempting to diagnose the issue..." + python3 -c "import sys; print('Python path:', sys.path)" 2>&1 || true + python3 -m pip list | grep -i mysql || print_status "No MySQL-related packages found in pip list" + print_status "Attempting to continue anyway, but installation may fail..." + fi + echo "" + + # Build installer arguments based on user preferences + # install.py requires publicip as first positional argument + local install_args=("$server_ip") + + # Add optional arguments based on user preferences + # Default: OpenLiteSpeed, Full installation (postfix, powerdns, ftp), Local MySQL + # These match what the user selected in the interactive prompts + install_args+=("--postfix" "ON") + install_args+=("--powerdns" "ON") + install_args+=("--ftp" "ON") + install_args+=("--remotemysql" "OFF") + + if [ "$DEBUG_MODE" = true ]; then + # Note: install.py doesn't have --debug, but we can set it via environment + export DEBUG_MODE=true + fi + + # Run the Python installer directly + if [ "$DEBUG_MODE" = true ]; then + python3 "$installer_py" "${install_args[@]}" 2>&1 | tee /var/log/CyberPanel/install_output.log + else + python3 "$installer_py" "${install_args[@]}" 2>&1 | tee /var/log/CyberPanel/install_output.log + fi + else + # Fallback to cyberpanel_installer.sh if install.py not found + print_status "WARNING: install/install.py not found, using cyberpanel_installer.sh (may be interactive)" + + local installer_script="cyberpanel_installer.sh" + if [ ! -f "$installer_script" ]; then + print_status "ERROR: cyberpanel_installer.sh not found in current directory: $(pwd)" + return 1 + fi + + # Get absolute path to installer script + local installer_path + if [[ "$installer_script" = /* ]]; then + installer_path="$installer_script" + else + installer_path="$(pwd)/$installer_script" + fi + + # If MariaDB 10.x is installed, disable repositories right before running installer + if [ -n "$MARIADB_VERSION" ] && [ -f /tmp/cyberpanel_repo_monitor.pid ]; then + # Call the disable function one more time before installer runs + if type disable_mariadb_repos >/dev/null 2>&1; then + disable_mariadb_repos + fi + fi + + if [ "$DEBUG_MODE" = true ]; then + bash "$installer_path" --debug 2>&1 | tee /var/log/CyberPanel/install_output.log + else + bash "$installer_path" 2>&1 | tee /var/log/CyberPanel/install_output.log + fi fi - # Get absolute path to installer script - local installer_path - if [[ "$installer_script" = /* ]]; then - installer_path="$installer_script" - else - installer_path="$(pwd)/$installer_script" - fi + local install_exit_code=${PIPESTATUS[0]} - if [ "$DEBUG_MODE" = true ]; then - bash "$installer_path" --debug 2>&1 | tee /var/log/CyberPanel/install_output.log - else - bash "$installer_path" 2>&1 | tee /var/log/CyberPanel/install_output.log + # Stop the repository monitor + if [ -f /tmp/cyberpanel_repo_monitor.pid ]; then + local monitor_pid=$(cat /tmp/cyberpanel_repo_monitor.pid 2>/dev/null) + if [ -n "$monitor_pid" ] && kill -0 "$monitor_pid" 2>/dev/null; then + kill "$monitor_pid" 2>/dev/null + fi + rm -f /tmp/cyberpanel_repo_monitor.pid fi + touch /tmp/cyberpanel_install_complete 2>/dev/null || true local install_exit_code=${PIPESTATUS[0]} diff --git a/install/install.py b/install/install.py index e2ab05e25..384aeb76f 100644 --- a/install/install.py +++ b/install/install.py @@ -345,40 +345,67 @@ class preFlightsChecks: self.stdOut(f"Successfully installed alternative: {alt_package}", 1) break - # Install MariaDB with enhanced AlmaLinux 9.6 support - self.stdOut("Installing MariaDB for AlmaLinux 9.6...", 1) + # Disable MariaDB 12.1 repository if MariaDB 10.x is already installed + # This prevents upgrade attempts in Pre_Install_Required_Components + self.disableMariaDB12RepositoryIfNeeded() - # Try multiple installation methods for maximum compatibility - mariadb_commands = [ - "dnf install -y mariadb-server mariadb-devel mariadb-client --skip-broken --nobest", - "dnf install -y mariadb-server mariadb-devel mariadb-client --allowerasing", - "dnf install -y mariadb-server mariadb-devel --skip-broken --nobest --allowerasing", - "dnf install -y mariadb-server --skip-broken --nobest --allowerasing" - ] + # CRITICAL: Remove conflicting MariaDB compat packages before installation + # These packages from MariaDB 12.1 can conflict with MariaDB 10.11 + self.stdOut("Removing conflicting MariaDB compat packages...", 1) + try: + subprocess.run("rpm -e --nodeps MariaDB-server-compat-12.1.2-1.el9.noarch 2>/dev/null; true", shell=True, timeout=30) + subprocess.run("dnf remove -y 'MariaDB-server-compat*' 2>/dev/null || true", shell=True, timeout=60) + r = subprocess.run("rpm -qa 2>/dev/null | grep -i MariaDB-server-compat", shell=True, capture_output=True, text=True, timeout=30) + for line in (r.stdout or "").strip().splitlines(): + pkg = (line.strip().split() or [""])[0] + if pkg and "MariaDB-server-compat" in pkg: + subprocess.run(["rpm", "-e", "--nodeps", pkg], timeout=30) + self.stdOut("Removed conflicting MariaDB compat packages", 1) + except Exception as e: + self.stdOut("Warning: Could not remove compat packages: " + str(e), 0) - mariadb_installed = False - for cmd in mariadb_commands: - try: - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300) - if result.returncode == 0: + # Check if MariaDB is already installed before attempting installation + is_installed, installed_version, major_minor = self.checkExistingMariaDB() + + if is_installed: + self.stdOut(f"MariaDB/MySQL is already installed (version: {installed_version}), skipping installation", 1) + mariadb_installed = True + else: + # Install MariaDB with enhanced AlmaLinux 9.6 support + self.stdOut("Installing MariaDB for AlmaLinux 9.6...", 1) + + # Try multiple installation methods for maximum compatibility + mariadb_commands = [ + "dnf install -y mariadb-server mariadb-devel mariadb-client --skip-broken --nobest", + "dnf install -y mariadb-server mariadb-devel mariadb-client --allowerasing", + "dnf install -y mariadb-server mariadb-devel --skip-broken --nobest --allowerasing", + "dnf install -y mariadb-server --skip-broken --nobest --allowerasing" + ] + + mariadb_installed = False + for cmd in mariadb_commands: + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300) + if result.returncode == 0: + mariadb_installed = True + self.stdOut(f"MariaDB installed successfully with command: {cmd}", 1) + break + except subprocess.TimeoutExpired: + self.stdOut(f"Timeout installing MariaDB with command: {cmd}", 0) + continue + except Exception as e: + self.stdOut(f"Error installing MariaDB with command: {cmd} - {str(e)}", 0) + continue + + if not mariadb_installed: + self.stdOut("MariaDB installation failed, trying MySQL as fallback...", 0) + try: + command = "dnf install -y mysql-server mysql-devel --skip-broken --nobest --allowerasing" + self.call(command, self.distro, command, command, 1, 0, os.EX_OSERR) + self.stdOut("MySQL installed as fallback for MariaDB", 1) mariadb_installed = True - self.stdOut(f"MariaDB installed successfully with command: {cmd}", 1) - break - except subprocess.TimeoutExpired: - self.stdOut(f"Timeout installing MariaDB with command: {cmd}", 0) - continue - except Exception as e: - self.stdOut(f"Error installing MariaDB with command: {cmd} - {str(e)}", 0) - continue - - if not mariadb_installed: - self.stdOut("MariaDB installation failed, trying MySQL as fallback...", 0) - try: - command = "dnf install -y mysql-server mysql-devel --skip-broken --nobest --allowerasing" - self.call(command, self.distro, command, command, 1, 0, os.EX_OSERR) - self.stdOut("MySQL installed as fallback for MariaDB", 1) - except: - self.stdOut("Both MariaDB and MySQL installation failed", 0) + except: + self.stdOut("Both MariaDB and MySQL installation failed", 0) # Install additional required packages self.stdOut("Installing additional required packages...", 1) @@ -1238,6 +1265,13 @@ class preFlightsChecks: self.stdOut("Installing custom binaries...", 1) try: + # Ensure /usr/local/lsws/bin exists (dnf openlitespeed may use different layout) + ols_bin_dir = os.path.dirname(OLS_BINARY_PATH) + os.makedirs(ols_bin_dir, mode=0o755, exist_ok=True) + ols_base = os.path.dirname(ols_bin_dir) + if not os.path.isdir(ols_base): + os.makedirs(ols_base, mode=0o755, exist_ok=True) + # Make binary executable before moving os.chmod(tmp_binary, 0o755) @@ -1451,6 +1485,124 @@ module cyberpanel_ols { except: return False + def disableMariaDB12RepositoryIfNeeded(self): + """Disable MariaDB 12.1 repository if MariaDB 10.x is already installed to prevent upgrade attempts""" + try: + is_installed, installed_version, major_minor = self.checkExistingMariaDB() + + if is_installed and major_minor and major_minor != "unknown": + try: + major_ver = float(major_minor) + if major_ver < 12.0: + # MariaDB 10.x is installed, disable 12.1 repository to prevent upgrade attempts + self.stdOut(f"MariaDB {installed_version} detected, disabling MariaDB 12.1 repository to prevent upgrade conflicts", 1) + logging.InstallLog.writeToFile(f"MariaDB {installed_version} detected, disabling MariaDB 12.1 repository") + + # Disable MariaDB 12.1 repository - check all possible repo file locations + repo_files = [ + '/etc/yum.repos.d/mariadb-main.repo', + '/etc/yum.repos.d/mariadb.repo', + '/etc/yum.repos.d/mariadb-12.1.repo', + '/etc/yum.repos.d/mariadb-main.repo.bak' + ] + + # Also check for any mariadb repo files + import glob + repo_files.extend(glob.glob('/etc/yum.repos.d/*mariadb*.repo')) + + disabled_any = False + for repo_file in repo_files: + if os.path.exists(repo_file): + try: + # Read the file + with open(repo_file, 'r') as f: + lines = f.readlines() + + # Modify the file to disable all MariaDB repositories + modified = False + new_lines = [] + in_mariadb_section = False + + for line in lines: + # Check if we're entering a MariaDB repository section + if line.strip().startswith('[') and 'mariadb' in line.lower(): + in_mariadb_section = True + new_lines.append(line) + # Add enabled=0 if not already present + if 'enabled' not in line.lower(): + new_lines.append('enabled=0\n') + modified = True + elif in_mariadb_section: + # If we see enabled=1, change it to enabled=0 + if line.strip().startswith('enabled=') and 'enabled=0' not in line.lower(): + new_lines.append('enabled=0\n') + modified = True + elif line.strip().startswith('['): + # New section, exit MariaDB section + in_mariadb_section = False + new_lines.append(line) + else: + new_lines.append(line) + else: + new_lines.append(line) + + # Write back if modified + if modified: + with open(repo_file, 'w') as f: + f.writelines(new_lines) + self.stdOut(f"Disabled MariaDB repository in {repo_file}", 1) + logging.InstallLog.writeToFile(f"Disabled MariaDB repository in {repo_file}") + disabled_any = True + + except Exception as e: + self.stdOut(f"Warning: Could not disable repository {repo_file}: {e}", 1) + logging.InstallLog.writeToFile(f"Warning: Could not disable repository {repo_file}: {e}") + + # Always exclude MariaDB-server from dnf/yum operations to prevent upgrades + try: + # Add exclude to dnf.conf + dnf_conf = '/etc/dnf/dnf.conf' + exclude_line = 'exclude=MariaDB-server' + + if os.path.exists(dnf_conf): + with open(dnf_conf, 'r') as f: + dnf_content = f.read() + + # Check if exclude line already exists + if exclude_line not in dnf_content: + # Check if there's already an exclude line + if 'exclude=' in dnf_content: + # Append to existing exclude line + dnf_content = re.sub(r'(exclude=.*)', r'\1 MariaDB-server', dnf_content) + else: + # Add new exclude line + dnf_content = dnf_content.rstrip() + '\n' + exclude_line + '\n' + + with open(dnf_conf, 'w') as f: + f.write(dnf_content) + self.stdOut("Added MariaDB-server to dnf excludes to prevent upgrade", 1) + logging.InstallLog.writeToFile("Added MariaDB-server to dnf excludes") + else: + # Create dnf.conf with exclude + with open(dnf_conf, 'w') as f: + f.write('[main]\n') + f.write(exclude_line + '\n') + self.stdOut("Created dnf.conf with MariaDB-server exclude", 1) + logging.InstallLog.writeToFile("Created dnf.conf with MariaDB-server exclude") + except Exception as e: + self.stdOut(f"Warning: Could not add exclude to dnf.conf: {e}", 1) + logging.InstallLog.writeToFile(f"Warning: Could not add exclude to dnf.conf: {e}") + + return True + except (ValueError, TypeError): + pass + + return False + except Exception as e: + self.stdOut(f"Warning: Error checking MariaDB repository: {e}", 1) + logging.InstallLog.writeToFile(f"Warning: Error checking MariaDB repository: {e}") + return False + def checkExistingMariaDB(self): """Check if MariaDB/MySQL is already installed and return version info""" try: @@ -1659,7 +1811,42 @@ module cyberpanel_ols { else: # RHEL-based MariaDB installation - command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=12.1' + # CRITICAL: Remove conflicting MariaDB compat packages first + # These packages from MariaDB 12.1 can conflict with MariaDB 10.11 + self.stdOut("Removing conflicting MariaDB compat packages...", 1) + try: + subprocess.run("rpm -e --nodeps MariaDB-server-compat-12.1.2-1.el9.noarch 2>/dev/null; true", shell=True, timeout=30) + subprocess.run("dnf remove -y 'MariaDB-server-compat*' 2>/dev/null || true", shell=True, timeout=60) + r = subprocess.run("rpm -qa 2>/dev/null | grep -i MariaDB-server-compat", shell=True, capture_output=True, text=True, timeout=30) + for line in (r.stdout or "").strip().splitlines(): + pkg = (line.strip().split() or [""])[0] + if pkg and "MariaDB-server-compat" in pkg: + subprocess.run(["rpm", "-e", "--nodeps", pkg], timeout=30) + self.stdOut("Removed conflicting MariaDB compat packages", 1) + except Exception as e: + self.stdOut("Warning: Could not remove compat packages: " + str(e), 0) + + # Check if MariaDB is already installed before setting up repository + is_installed, installed_version, major_minor = self.checkExistingMariaDB() + + if is_installed: + self.stdOut(f"MariaDB/MySQL is already installed (version: {installed_version}), skipping installation", 1) + # Don't set up 12.1 repository if 10.x is installed to avoid upgrade issues + if major_minor and major_minor != "unknown": + try: + major_ver = float(major_minor) + if major_ver < 12.0: + self.stdOut("Skipping MariaDB 12.1 repository setup to avoid upgrade conflicts", 1) + self.stdOut("Using existing MariaDB installation", 1) + self.startMariaDB() + self.changeMYSQLRootPassword() + self.fixMariaDB() + return True + except (ValueError, TypeError): + pass + + # Set up MariaDB 12.1 repository only if not already installed + command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash -s -- --mariadb-server-version=12.1' self.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True) command = 'dnf install mariadb-server mariadb-devel mariadb-client-utils -y' @@ -6246,6 +6433,25 @@ def main(): # Apply OS-specific fixes early in the installation process checks.apply_os_specific_fixes() + + # CRITICAL: Disable MariaDB 12.1 repository and add dnf exclude BEFORE any MariaDB installation attempts + # This must run before Pre_Install_Required_Components tries to install MariaDB + checks.disableMariaDB12RepositoryIfNeeded() + + # CRITICAL: Remove MariaDB-server-compat* before ANY MariaDB installation + # This package conflicts with MariaDB 10.11 and must be removed early + preFlightsChecks.stdOut("Removing conflicting MariaDB-server-compat packages...", 1) + try: + subprocess.run("rpm -e --nodeps MariaDB-server-compat-12.1.2-1.el9.noarch 2>/dev/null; true", shell=True, timeout=30) + subprocess.run("dnf remove -y 'MariaDB-server-compat*' 2>/dev/null || true", shell=True, timeout=60) + r = subprocess.run("rpm -qa 2>/dev/null | grep -i MariaDB-server-compat", shell=True, capture_output=True, text=True, timeout=30) + for line in (r.stdout or "").strip().splitlines(): + pkg = (line.strip().split() or [""])[0] + if pkg and "MariaDB-server-compat" in pkg: + subprocess.run(["rpm", "-e", "--nodeps", pkg], timeout=30) + preFlightsChecks.stdOut("MariaDB compat cleanup completed", 1) + except Exception as e: + preFlightsChecks.stdOut("Warning: compat cleanup: " + str(e), 0) # Ensure MySQL password file is created early to prevent FileNotFoundError checks.ensure_mysql_password_file() @@ -6273,6 +6479,10 @@ def main(): # Apply AlmaLinux 9 comprehensive fixes first if needed if checks.is_almalinux9(): checks.fix_almalinux9_comprehensive() + + # Disable MariaDB 12.1 repository if MariaDB 10.x is already installed + # This prevents upgrade attempts in Pre_Install_Required_Components + checks.disableMariaDB12RepositoryIfNeeded() # Install core services in the correct order checks.installLiteSpeed(ent, serial) diff --git a/install/install_utils.py b/install/install_utils.py index 07982c0c0..5549550fc 100644 --- a/install/install_utils.py +++ b/install/install_utils.py @@ -556,14 +556,31 @@ def call(command, distro, bracket, message, log=0, do_exit=0, code=os.EX_OK, she os._exit(code) return False + # CRITICAL: Use shell=True for commands with shell metacharacters + # Avoids "No matching repo to modify: 2>/dev/null, true, ||" when shlex.split splits them + if not shell and any(x in command for x in (' || ', ' 2>/dev', ' 2>', ' | ', '; true', '|| true')): + shell = True + + # CRITICAL: For mysql/mariadb commands, always use shell=True and full binary path + # This fixes "No such file or directory: 'mysql'" when run via shlex.split + if not shell and ('mysql' in command or 'mariadb' in command): + import re + mysql_bin = '/usr/bin/mariadb' if os.path.exists('/usr/bin/mariadb') else '/usr/bin/mysql' + if not os.path.exists(mysql_bin): + mysql_bin = '/usr/bin/mysql' + # Replace only leading "mysql" or "mariadb" (executable), not "mysql" in SQL like "use mysql;" + if re.match(r'^\s*(sudo\s+)?(mysql|mariadb)\s', command): + command = re.sub(r'^(\s*)(?:sudo\s+)?(mysql|mariadb)(\s)', r'\g<1>' + mysql_bin + r'\g<3>', command, count=1) + shell = True + finalMessage = 'Running: %s' % (message) stdOut(finalMessage, log) count = 0 while True: - if shell == False: - res = subprocess.call(shlex.split(command)) - else: + if shell: res = subprocess.call(command, shell=True) + else: + res = subprocess.call(shlex.split(command)) if resFailed(distro, res): count = count + 1 diff --git a/plogical/upgrade.py b/plogical/upgrade.py index 5052e0c48..9a1ff2353 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -4299,6 +4299,19 @@ echo $oConfig->Save() ? 'Done' : 'Error'; Upgrade.stdOut("Applying AlmaLinux 9 MariaDB fixes...", 1) try: + # CRITICAL: Remove MariaDB-server-compat* before any MariaDB install (conflicts with 10.11) + Upgrade.stdOut("Removing conflicting MariaDB-server-compat packages...", 1) + try: + subprocess.run("rpm -e --nodeps MariaDB-server-compat-12.1.2-1.el9.noarch 2>/dev/null; true", shell=True, timeout=30) + subprocess.run("dnf remove -y 'MariaDB-server-compat*' 2>/dev/null || true", shell=True, timeout=60) + r = subprocess.run("rpm -qa 2>/dev/null | grep -i MariaDB-server-compat", shell=True, capture_output=True, text=True, timeout=30) + for line in (r.stdout or "").strip().splitlines(): + pkg = (line.strip().split() or [""])[0] + if pkg and "MariaDB-server-compat" in pkg: + subprocess.run(["rpm", "-e", "--nodeps", pkg], timeout=30) + except Exception as e: + Upgrade.stdOut("Warning: compat cleanup: " + str(e), 0) + # Disable problematic MariaDB MaxScale repository Upgrade.stdOut("Disabling problematic MariaDB MaxScale repository...", 1) command = "dnf config-manager --disable mariadb-maxscale 2>/dev/null || true" @@ -4320,9 +4333,9 @@ echo $oConfig->Save() ? 'Done' : 'Error'; command = "dnf clean all" subprocess.run(command, shell=True, capture_output=True) - # Install MariaDB from official repository + # Install MariaDB 10.11 from official repository (avoid 12.1 compat conflicts) Upgrade.stdOut("Setting up official MariaDB repository...", 1) - command = "curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash -s -- --mariadb-server-version='12.1'" + command = "curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash -s -- --mariadb-server-version='10.11'" result = subprocess.run(command, shell=True, capture_output=True, text=True) if result.returncode != 0: Upgrade.stdOut(f"Warning: MariaDB repo setup failed: {result.stderr}", 0) diff --git a/pluginHolder/templates/pluginHolder/plugins.html b/pluginHolder/templates/pluginHolder/plugins.html index 089569bc6..f53e351a0 100644 --- a/pluginHolder/templates/pluginHolder/plugins.html +++ b/pluginHolder/templates/pluginHolder/plugins.html @@ -709,6 +709,32 @@ cursor: not-allowed; } + .btn-upgrade { + padding: 8px 16px; + background: #f59e0b; + color: white; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + display: inline-flex; + align-items: center; + gap: 6px; + } + + .btn-upgrade:hover:not(:disabled) { + background: #d97706; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(245,158,11,0.3); + } + + .btn-upgrade:disabled { + opacity: 0.6; + cursor: not-allowed; + } + .btn-link { padding: 6px 12px; background: var(--bg-secondary, #f8f9ff); @@ -772,6 +798,17 @@ box-shadow: 0 4px 8px rgba(88,86,214,0.3); } + .btn-revert { + background: #6c757d; + color: white; + } + + .btn-revert:hover:not(:disabled) { + background: #5a6268; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(108,117,125,0.3); + } + .btn-uninstall { background: #dc3545; color: white; @@ -1042,6 +1079,9 @@ {% trans "Activate" %} {% endif %} + @@ -1123,6 +1163,9 @@ {% trans "Activate" %} {% endif %} + @@ -1194,6 +1237,17 @@
@@ -1410,12 +1467,20 @@ function displayStorePlugins() {