diff --git a/install/install.py b/install/install.py index 4e23bcbfa..f0a904668 100644 --- a/install/install.py +++ b/install/install.py @@ -5831,7 +5831,7 @@ milter_default_action = accept # Create symlink to the best available PHP version # Try to find and use the best available PHP version - # Priority: 85 (beta), 84, 83, 82, 81, 80, 74 (newest to oldest) + # Priority: 85, 84, 83, 82, 81, 80, 74 (newest to oldest) php_versions = ['85', '84', '83', '82', '81', '80', '74'] php_symlink_source = None @@ -5882,7 +5882,7 @@ milter_default_action = accept logging.InstallLog.writeToFile("[setup_lsphp_symlink] Removed existing lsphp file/symlink") # Try to find and use the best available PHP version - # Priority: 85 (beta), 84, 83, 82, 81, 80, 74 (newest to oldest) + # Priority: 85, 84, 83, 82, 81, 80, 74 (newest to oldest) php_versions = ['85', '84', '83', '82', '81', '80', '74'] lsphp_source = None diff --git a/install/installCyberPanel.py b/install/installCyberPanel.py index 8f04b70a4..cc2d7c009 100644 --- a/install/installCyberPanel.py +++ b/install/installCyberPanel.py @@ -755,18 +755,18 @@ module cyberpanel_ols { return self.reStartLiteSpeed() def installAllPHPVersions(self): - php_versions = ['71', '72', '73', '74', '80', '81', '82', '83', '84', '85'] - + # OS-aligned matrix (same base list as upgrade.get_available_php_versions pre-filter) + php_versions = install_utils.get_lsphp_install_suffixes() + if self.distro == ubuntu: - # Install base PHP 7.x packages + # Install base PHP 7.x packages (wildcard) plus explicit matrix suffixes command = 'DEBIAN_FRONTEND=noninteractive apt-get -y install ' \ 'lsphp7? lsphp7?-common lsphp7?-curl lsphp7?-dev lsphp7?-imap lsphp7?-intl lsphp7?-json ' \ 'lsphp7?-ldap lsphp7?-mysql lsphp7?-opcache lsphp7?-pspell lsphp7?-recode ' \ 'lsphp7?-sqlite3 lsphp7?-tidy' os.system(command) - - # Install PHP 8.x versions - for version in php_versions[4:]: # 80, 81, 82, 83 + + for version in php_versions: self.install_package(f'lsphp{version}*') elif self.distro == centos: diff --git a/install/install_utils.py b/install/install_utils.py index 53285a8d8..99090908d 100644 --- a/install/install_utils.py +++ b/install/install_utils.py @@ -289,6 +289,88 @@ openeuler = 3 debian12 = 4 +def get_lsphp_install_suffixes(): + """ + LiteSpeed lsphp* two-digit version suffixes to install for this OS (pre-repo check). + Mirrors the base list in plogical/upgrade.py get_available_php_versions() before + check_package_availability filtering. + + Returns: + list[str]: e.g. ['74','80',...,'85'] on AlmaLinux 9+/10+ and modern EL9/Ubuntu24+/Debian13+, + or ['71',...,'85'] on older platforms where 7.1–7.3 packages exist. + """ + long_list = ['71', '72', '73', '74', '80', '81', '82', '83', '84', '85'] + short_list = ['74', '80', '81', '82', '83', '84', '85'] + + # AlmaLinux: explicit release file (matches upgrade.get_available_php_versions) + if exists('/etc/almalinux-release'): + try: + with open('/etc/almalinux-release', 'r') as f: + content = f.read().lower() + if 'release 9' in content or 'release 10' in content: + return list(short_list) + except (OSError, IOError, UnicodeError): + pass + return list(long_list) + + # Other RHEL family (Rocky/RHEL/CentOS Stream) without almalinux-release: EL9+ uses short list + if exists('/etc/redhat-release'): + try: + with open('/etc/redhat-release', 'r') as f: + data = f.read().lower() + if ( + 'release 9' in data + or 'release 10' in data + or 'stream 9' in data + or 'stream 10' in data + ): + return list(short_list) + except (OSError, IOError, UnicodeError): + pass + + # Ubuntu 24.04+ (upgrade: Ubuntu24 branch) + if exists('/etc/lsb-release'): + try: + with open('/etc/lsb-release', 'r') as f: + lsb = f.read() + if 'DISTRIB_ID=Ubuntu' in lsb: + for line in lsb.splitlines(): + if line.startswith('DISTRIB_RELEASE='): + rel = line.split('=', 1)[1].strip().strip('"').strip("'") + try: + parts = rel.split('.') + major = int(parts[0]) + minor = int(parts[1]) if len(parts) > 1 else 0 + if major > 24 or (major == 24 and minor >= 4): + return list(short_list) + except (ValueError, IndexError): + pass + break + except (OSError, IOError, UnicodeError): + pass + + # Debian 13+ (trixie+): upgrade uses Debian13 for short list + if exists('/etc/os-release'): + try: + with open('/etc/os-release', 'r') as f: + osr = f.read() + osr_l = osr.lower().replace(' ', '') + if 'id=debian' in osr_l: + for line in osr.splitlines(): + if line.upper().startswith('VERSION_ID='): + vid = line.split('=', 1)[1].strip().strip('"').strip("'") + try: + if int(vid.split('.')[0]) >= 13: + return list(short_list) + except (ValueError, IndexError): + pass + break + except (OSError, IOError, UnicodeError): + pass + + return list(long_list) + + def get_distro(): """ Detect Linux distribution diff --git a/plogical/upgrade.py b/plogical/upgrade.py index 3c3142be2..9420efd7d 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -166,110 +166,151 @@ except ImportError: print("WARNING: Cannot import CyberCP settings. Attempting recovery...") def recover_database_credentials(): - """Attempt to recover or reset database credentials""" - - # First, ensure we have root MySQL password + """Recover CyberPanel DB credentials without changing passwords (upgrade policy). + + Never drops or re-hashes the `cyberpanel`@`localhost` user or rotates passwords + unless CYBERPANEL_ALLOW_DB_CREDENTIAL_RESET=1 is set (legacy recovery only). + """ if not os.path.exists('/etc/cyberpanel/mysqlPassword'): print("FATAL: Cannot find MySQL root password file at /etc/cyberpanel/mysqlPassword") print("Manual intervention required.") sys.exit(1) - - root_password = open('/etc/cyberpanel/mysqlPassword', 'r').read().strip() + + def _read_mysql_root_password_file(): + raw = open('/etc/cyberpanel/mysqlPassword', 'r').read().strip() + if raw.startswith('{') and raw.rstrip().endswith('}'): + try: + data = json.loads(raw) + for key in ('mysqlpassword', 'mysql_password', 'MYSQL_PASSWORD'): + v = data.get(key) + if v: + return str(v).strip() + except Exception: + pass + return raw + + def _collect_integration_password_candidates(): + """Passwords already stored in mail/FTP/DNS configs (same DB user).""" + found = [] + def add(pw): + pw = (pw or '').strip() + if pw and pw not in found: + found.append(pw) + patterns = [ + ('/etc/pure-ftpd/pureftpd-mysql.conf', r'^MYSQLPassword\s+(\S+)'), + ('/etc/pure-ftpd/db/mysql.conf', r'^MYSQLPassword\s+(\S+)'), + ('/etc/pdns/pdns.conf', r'^gmysql-password=(\S+)'), + ('/etc/powerdns/pdns.conf', r'^gmysql-password=(\S+)'), + ('/etc/postfix/mysql-virtual_domains.cf', r'^password\s*=\s*(\S+)'), + ] + for fpath, pat in patterns: + if not os.path.isfile(fpath): + continue + try: + with open(fpath, 'r') as fh: + for line in fh: + m = re.match(pat, line.strip()) + if m: + add(m.group(1)) + break + except Exception: + pass + return found + + def _try_cyberpanel_connect(pw): + try: + c = mysql.connect(host='localhost', user='cyberpanel', passwd=pw, db='cyberpanel') + c.close() + return True + except Exception: + return False + + root_password = _read_mysql_root_password_file() cyberpanel_password = None - - # Try to read existing settings.py to get cyberpanel password settings_path = '/usr/local/CyberCP/CyberCP/settings.py' + if os.path.exists(settings_path): try: with open(settings_path, 'r') as f: settings_content = f.read() - - import re - # Extract cyberpanel database password db_pattern = r"'default':[^}]*'USER':\s*'cyberpanel'[^}]*'PASSWORD':\s*'([^']+)'" match = re.search(db_pattern, settings_content, re.DOTALL) - + if not match: + db_pattern2 = r'"USER":\s*"cyberpanel"[^}]*"PASSWORD":\s*"([^"]+)"' + match = re.search(db_pattern2, settings_content, re.DOTALL) if match: cyberpanel_password = match.group(1) print("Found existing cyberpanel password in settings.py") - - # Test if this password actually works - try: - test_conn = mysql.connect(host='localhost', user='cyberpanel', - passwd=cyberpanel_password, db='cyberpanel') - test_conn.close() + if _try_cyberpanel_connect(cyberpanel_password): print("Verified cyberpanel database credentials are valid") - except: - print("Found password in settings.py but it doesn't work, will reset") + else: + print("WARNING: Password from settings.py does not authenticate as cyberpanel@localhost.") cyberpanel_password = None except Exception as e: print("Could not extract password from settings.py: %s" % str(e)) - - # If we couldn't get a working password, we need to reset it - if cyberpanel_password is None: - print("Resetting cyberpanel database user password...") - - # Check if we're on Ubuntu or CentOS - # On Ubuntu, cyberpanel uses root password; on CentOS, it uses a separate password + + working_pw = None + if cyberpanel_password and _try_cyberpanel_connect(cyberpanel_password): + working_pw = cyberpanel_password + else: + for cand in _collect_integration_password_candidates(): + if _try_cyberpanel_connect(cand): + working_pw = cand + print("Recovered working cyberpanel DB password from integration config (FTP/DNS/Postfix).") + break + if working_pw: + cyberpanel_password = working_pw + + allow_reset = os.environ.get('CYBERPANEL_ALLOW_DB_CREDENTIAL_RESET', '').strip() in ('1', 'true', 'yes', 'YES', 'TRUE') + + if (not cyberpanel_password) or (not _try_cyberpanel_connect(cyberpanel_password)): + if not allow_reset: + print("FATAL: Cannot verify `cyberpanel` database credentials.") + print("CyberPanel upgrade will NOT reset MySQL passwords or drop the `cyberpanel` user (upgrade policy).") + print("Fix DATABASES['default']['PASSWORD'] in /usr/local/CyberCP/CyberCP/settings.py to match MariaDB,") + print("or align Pure-FTPd / PowerDNS / Postfix MySQL config passwords, then re-run the upgrade.") + print("Legacy auto-reset (old behaviour): export CYBERPANEL_ALLOW_DB_CREDENTIAL_RESET=1 and re-run (not recommended).") + sys.exit(1) + + print("WARNING: CYBERPANEL_ALLOW_DB_CREDENTIAL_RESET=1 — performing legacy cyberpanel DB user reset...") if os.path.exists('/etc/lsb-release'): - # Ubuntu - use root password cyberpanel_password = root_password reset_to_root = True else: - # CentOS/others - generate new password chars = string.ascii_letters + string.digits cyberpanel_password = ''.join(random.choice(chars) for _ in range(14)) reset_to_root = False - try: - # Connect as root and reset cyberpanel user conn = mysql.connect(host='localhost', user='root', passwd=root_password) cursor = conn.cursor() - - # Check if cyberpanel database exists cursor.execute("SHOW DATABASES LIKE 'cyberpanel'") if not cursor.fetchone(): print("Creating cyberpanel database...") cursor.execute("CREATE DATABASE IF NOT EXISTS cyberpanel") - - # Reset cyberpanel user - drop and recreate to ensure clean state cursor.execute("DROP USER IF EXISTS 'cyberpanel'@'localhost'") cursor.execute("CREATE USER 'cyberpanel'@'localhost' IDENTIFIED BY '%s'" % cyberpanel_password) cursor.execute("GRANT ALL PRIVILEGES ON cyberpanel.* TO 'cyberpanel'@'localhost'") cursor.execute("FLUSH PRIVILEGES") - conn.close() - if reset_to_root: print("Reset cyberpanel user password to match root password (Ubuntu style)") else: print("Reset cyberpanel user with new generated password (CentOS style)") - - # Update all configuration files with the new password print("Updating all service configuration files with new password...") update_all_config_files_with_password(cyberpanel_password) - - # Restart affected services to pick up new configuration print("Restarting affected services...") restart_affected_services() - - # Save the password to a temporary file for the upgrade process temp_pass_file = '/tmp/cyberpanel_recovered_password' with open(temp_pass_file, 'w') as f: f.write(cyberpanel_password) os.chmod(temp_pass_file, 0o600) print("Saved recovered password to temporary file") - except Exception as e: print("Failed to reset cyberpanel database user: %s" % str(e)) - print("Manual intervention required. Please run:") - print(" mariadb -u root -p") - print(" CREATE DATABASE IF NOT EXISTS cyberpanel;") - print(" GRANT ALL PRIVILEGES ON cyberpanel.* TO 'cyberpanel'@'localhost' IDENTIFIED BY 'your_password';") - print(" FLUSH PRIVILEGES;") sys.exit(1) - + return cyberpanel_password, root_password + # Perform recovery cyberpanel_password, root_password = recover_database_credentials() @@ -5368,27 +5409,33 @@ echo $oConfig->Save() ? 'Done' : 'Error'; @staticmethod def get_available_php_versions(): """Get list of available PHP versions based on OS""" - # Check for AlmaLinux 9+ first - if os.path.exists('/etc/almalinux-release'): - try: + php_versions = ['71', '72', '73', '74', '80', '81', '82', '83', '84', '85'] + try: + import importlib + import sys + _here = os.path.dirname(os.path.abspath(__file__)) + for _root in ( + os.path.join(os.path.dirname(_here), 'install'), + '/usr/local/CyberCP/install', + '/usr/local/CyberPanel/install', + ): + if os.path.isfile(os.path.join(_root, 'install_utils.py')) and _root not in sys.path: + sys.path.insert(0, _root) + iu = importlib.import_module('install_utils') + if hasattr(iu, 'get_lsphp_install_suffixes'): + php_versions = iu.get_lsphp_install_suffixes() + except Exception: + pass + + try: + if os.path.exists('/etc/almalinux-release'): with open('/etc/almalinux-release', 'r') as f: - content = f.read() - if 'release 9' in content or 'release 10' in content: - Upgrade.stdOut("AlmaLinux 9+ detected - checking available PHP versions", 1) - # AlmaLinux 9+ doesn't have PHP 7.1, 7.2, 7.3 - php_versions = ['74', '80', '81', '82', '83', '84', '85'] - else: - php_versions = ['71', '72', '73', '74', '80', '81', '82', '83', '84', '85'] - except: - php_versions = ['71', '72', '73', '74', '80', '81', '82', '83', '84', '85'] - else: - # Check other OS versions - os_info = Upgrade.findOperatingSytem() - if os_info in [Ubuntu24, CENTOS8, Debian13]: - php_versions = ['74', '80', '81', '82', '83', '84', '85'] - else: - php_versions = ['71', '72', '73', '74', '80', '81', '82', '83', '84', '85'] - + _c = f.read().lower() + if 'release 9' in _c or 'release 10' in _c: + Upgrade.stdOut("AlmaLinux 9+ detected - checking available PHP versions", 1) + except Exception: + pass + # Check availability of each version available_versions = [] for version in php_versions: @@ -7278,7 +7325,7 @@ extprocessor proxyApacheBackendSSL { Upgrade.executioner(command, f'Restart {apache_service}', 1) # 5. Fix PHP-FPM socket permissions and restart services - for version in ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']: + for version in ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']: if Upgrade.FindOperatingSytem() in [CENTOS7, CENTOS8, openEuler20, openEuler22]: php_service = f'php{version.replace(".", "")}-php-fpm' socket_dir = '/var/run/php-fpm' @@ -7960,11 +8007,11 @@ RewriteRule ^(.*)$ https://proxyApacheBackendSSL/$1 [P,L] # Restart PHP-FPM services if osType in [CENTOS7, CENTOS8, CloudLinux7, CloudLinux8]: - for version in ['54', '55', '56', '70', '71', '72', '73', '74', '80', '81', '82', '83', '84']: + for version in ['54', '55', '56', '70', '71', '72', '73', '74', '80', '81', '82', '83', '84', '85']: command = f'systemctl restart php{version}-php-fpm' Upgrade.executioner(command, command, 0, True) else: - for version in ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']: + for version in ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']: command = f'systemctl restart php{version}-fpm' Upgrade.executioner(command, command, 0, True) diff --git a/upgrade_modules/03_mariadb.sh b/upgrade_modules/03_mariadb.sh index dd0666999..7b6b0af18 100644 --- a/upgrade_modules/03_mariadb.sh +++ b/upgrade_modules/03_mariadb.sh @@ -16,7 +16,9 @@ Pre_Upgrade_CentOS7_MySQL() { systemctl start mariadb 2>/dev/null || systemctl start mysql mariadb-upgrade --force -uroot -p"$MySQL_Password" 2>/dev/null || mysql_upgrade --force -uroot -p"$MySQL_Password" 2>/dev/null || true fi - mariadb -uroot -p"$MySQL_Password" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '$MySQL_Password';flush privileges" 2>/dev/null || mysql -uroot -p"$MySQL_Password" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '$MySQL_Password';flush privileges" + # Do not use IDENTIFIED BY here - it re-hashes the root password and can break apps that still use the old hash. + # Privileges only; leave the existing MariaDB root password unchanged. + mariadb -uroot -p"$MySQL_Password" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES" 2>/dev/null || mysql -uroot -p"$MySQL_Password" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES" Ensure_MariaDB_Client_No_SSL }