From 236ecd7ea8525644df97968331f73569f10c47c3 Mon Sep 17 00:00:00 2001 From: usmannasir Date: Mon, 29 Sep 2025 12:18:03 +0500 Subject: [PATCH] fix: Comprehensive MariaDB socket authentication support - Add execute_mysql_command() helper in install.py for robust auth fallback - Update mysqlUtilities.py with socket auth fallback for all operations: * Database creation * User creation * Privilege granting * Privilege flushing - Fix PowerDNS database setup to use new authentication helper - Improve cyberpanel user connection verification - Add proper error handling and logging throughout This ensures the installation works correctly when MariaDB is pre-installed with socket authentication (common with dependency installations). Fixes the 'Cannot update settings with empty passwords' installation failure. --- install/install.py | 87 ++++++++++++++++++++++++++++++--------- install/mysqlUtilities.py | 51 ++++++++++++++++++++--- 2 files changed, 112 insertions(+), 26 deletions(-) diff --git a/install/install.py b/install/install.py index c73a085c3..6a17b0bb6 100644 --- a/install/install.py +++ b/install/install.py @@ -1036,6 +1036,46 @@ class preFlightsChecks: self.stdOut(f"Error changing MySQL root password: {str(e)}", 0) return False + def execute_mysql_command(self, sql_command, description="MySQL command"): + """Execute MySQL command with proper authentication fallback""" + try: + # Try password-based authentication first if password file exists + try: + passFile = "/etc/cyberpanel/mysqlPassword" + if os.path.exists(passFile): + with open(passFile, 'r') as f: + password = f.read().split('\n', 1)[0] + + # Try mariadb first, then mysql + for cmd_base in ['mariadb', 'mysql']: + if self.command_exists(cmd_base): + command = f'{cmd_base} -u root -p{password} -e "{sql_command}"' + result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30) + if result.returncode == 0: + self.stdOut(f"✓ {description} executed successfully with password auth", 1) + return True + except: + pass + + # Fallback to socket authentication + for cmd_base in ['sudo mariadb', 'sudo mysql']: + try: + if self.command_exists(cmd_base.split()[-1]): + command = f'{cmd_base} -e "{sql_command}"' + result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30) + if result.returncode == 0: + self.stdOut(f"✓ {description} executed successfully with socket auth", 1) + return True + except: + continue + + self.stdOut(f"✗ Failed to execute {description}: {sql_command}", 0) + return False + + except Exception as e: + self.stdOut(f"Error executing MySQL command: {str(e)}", 0) + return False + def ensure_mysql_password_file(self): """Ensure MySQL password file exists and is properly configured""" try: @@ -4783,21 +4823,22 @@ vmail preFlightsChecks.stdOut("Setting up PowerDNS database access...", 1) # Create PowerDNS database tables if they don't exist - db_commands = [ - "mysql -e \"CREATE DATABASE IF NOT EXISTS powerdns;\"", - "mysql -e \"CREATE USER IF NOT EXISTS 'powerdns'@'localhost' IDENTIFIED BY 'cyberpanel';\"", - "mysql -e \"GRANT ALL PRIVILEGES ON powerdns.* TO 'powerdns'@'localhost';\"", - "mysql -e \"FLUSH PRIVILEGES;\"" + sql_commands = [ + ("CREATE DATABASE IF NOT EXISTS powerdns", "PowerDNS database creation"), + ("CREATE USER IF NOT EXISTS 'powerdns'@'localhost' IDENTIFIED BY 'cyberpanel'", "PowerDNS user creation"), + ("GRANT ALL PRIVILEGES ON powerdns.* TO 'powerdns'@'localhost'", "PowerDNS privileges"), + ("FLUSH PRIVILEGES", "PowerDNS privilege flush") ] - - for cmd in db_commands: - preFlightsChecks.call(cmd, self.distro, f"PowerDNS DB: {cmd}", cmd, 1, 0, os.EX_OSERR) - + + for sql_cmd, desc in sql_commands: + if not self.execute_mysql_command(sql_cmd, desc): + self.stdOut(f"Warning: {desc} may have failed", 0) + # Import PowerDNS schema if tables don't exist - schema_check = "mysql -e \"USE powerdns; SHOW TABLES;\"" - result = subprocess.run(schema_check, shell=True, capture_output=True, text=True) - - if not result.stdout.strip() or 'domains' not in result.stdout: + schema_check_success = self.execute_mysql_command("USE powerdns; SHOW TABLES", "PowerDNS schema check") + + # For now, assume schema needs import if we can't check properly + if not schema_check_success: preFlightsChecks.stdOut("Importing PowerDNS database schema...", 1) # Try to find and import PowerDNS schema schema_files = [ @@ -5310,13 +5351,19 @@ def main(): # Verify the user was created by testing the connection try: - import subprocess - test_cmd = f"mysql -u cyberpanel -p{checks.cyberpanel_db_password} -e 'SELECT 1;'" - test_result = subprocess.call(test_cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - if test_result == 0: - logging.InstallLog.writeToFile("✅ Verified: cyberpanel user can connect to MySQL") - else: - logging.InstallLog.writeToFile("❌ WARNING: cyberpanel user cannot connect to MySQL - authentication may have failed") + test_success = False + # Try mariadb first, then mysql + for cmd_base in ['mariadb', 'mysql']: + if checks.command_exists(cmd_base): + test_cmd = f"{cmd_base} -u cyberpanel -p{checks.cyberpanel_db_password} -e 'SELECT 1;'" + test_result = subprocess.call(test_cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + if test_result == 0: + logging.InstallLog.writeToFile("✅ Verified: cyberpanel user can connect to MySQL") + test_success = True + break + + if not test_success: + logging.InstallLog.writeToFile("⚠️ Warning: Could not verify cyberpanel user connection") except Exception as verify_error: logging.InstallLog.writeToFile(f"Could not verify MySQL connection: {str(verify_error)}") else: diff --git a/install/mysqlUtilities.py b/install/mysqlUtilities.py index 7038bc958..5e125c8b0 100644 --- a/install/mysqlUtilities.py +++ b/install/mysqlUtilities.py @@ -32,13 +32,21 @@ class mysqlUtilities: except: passFile = "/etc/cyberpanel/mysqlPassword" - f = open(passFile) - data = f.read() - password = data.split('\n', 1)[0] + try: + f = open(passFile) + data = f.read() + password = data.split('\n', 1)[0] + f.close() - initCommand = 'mariadb -u root -p' + password + ' -e "' - remote = 0 - logging.InstallLog.writeToFile("Using local MySQL configuration") + # Try password-based authentication first + initCommand = 'mariadb -u root -p' + password + ' -e "' + remote = 0 + logging.InstallLog.writeToFile("Using local MySQL configuration with password") + except: + # Fallback to socket authentication for fresh MariaDB installs + initCommand = 'sudo mariadb -e "' + remote = 0 + logging.InstallLog.writeToFile("Using local MySQL configuration with socket authentication") command = initCommand + createDB + '"' @@ -51,6 +59,14 @@ class mysqlUtilities: cmd = shlex.split(command) res = subprocess.call(cmd) + # If command failed and we're using password auth, try socket auth as fallback + if res != 0 and not remote and 'sudo' not in initCommand: + logging.InstallLog.writeToFile(f"Password-based auth failed (code {res}), trying socket authentication...") + initCommand = 'sudo mariadb -e "' + command = initCommand + createDB + '"' + cmd = shlex.split(command) + res = subprocess.call(cmd) + if res != 0: logging.InstallLog.writeToFile(f"ERROR: Database creation failed with return code: {res}") return 0 @@ -89,6 +105,14 @@ class mysqlUtilities: cmd = shlex.split(command) res = subprocess.call(cmd) + # If user creation failed and we're using password auth, try socket auth as fallback + if res != 0 and not remote and 'sudo' not in initCommand: + logging.InstallLog.writeToFile(f"User creation with password failed (code {res}), trying socket authentication...") + initCommand = 'sudo mariadb -e "' + command = initCommand + createUser + '"' + cmd = shlex.split(command) + res = subprocess.call(cmd) + if res != 0: logging.InstallLog.writeToFile(f"ERROR: User creation failed with return code: {res}") return 0 @@ -135,6 +159,14 @@ class mysqlUtilities: cmd = shlex.split(command) res = subprocess.call(cmd) + # If privilege granting failed and we're using password auth, try socket auth as fallback + if res != 0 and not remote and 'sudo' not in initCommand: + logging.InstallLog.writeToFile(f"Grant privileges with password failed (code {res}), trying socket authentication...") + initCommand = 'sudo mariadb -e "' + command = initCommand + grantPrivileges + '"' + cmd = shlex.split(command) + res = subprocess.call(cmd) + if res != 0: logging.InstallLog.writeToFile(f"ERROR: Grant privileges failed with return code: {res}") return 0 @@ -148,6 +180,13 @@ class mysqlUtilities: cmd = shlex.split(flushCommand) res = subprocess.call(cmd) + # If flush failed and we're using password auth, try socket auth as fallback + if res != 0 and not remote and 'sudo' not in initCommand: + logging.InstallLog.writeToFile(f"FLUSH PRIVILEGES with password failed (code {res}), trying socket authentication...") + flushCommand = 'sudo mariadb -e "FLUSH PRIVILEGES"' + cmd = shlex.split(flushCommand) + res = subprocess.call(cmd) + if res != 0: logging.InstallLog.writeToFile(f"WARNING: FLUSH PRIVILEGES failed with return code: {res}") else: