diff --git a/databases/databaseManager.py b/databases/databaseManager.py index 0b0c7617a..60c817a3b 100644 --- a/databases/databaseManager.py +++ b/databases/databaseManager.py @@ -31,6 +31,11 @@ class DatabaseManager: return proc.render() def phpMyAdmin(self, request = None, userID = None): + try: + from plogical.phpmyadmin_utils import ensure_phpmyadmin_signin_bridge + ensure_phpmyadmin_signin_bridge() + except BaseException: + pass template = 'databases/phpMyAdmin.html' proc = httpProc(request, template, None, 'createDatabase') return proc.render() diff --git a/databases/views.py b/databases/views.py index e32f96371..37ec05ff9 100644 --- a/databases/views.py +++ b/databases/views.py @@ -257,6 +257,11 @@ def generateAccess(request): @csrf_exempt def fetchDetailsPHPMYAdmin(request): try: + try: + from plogical.phpmyadmin_utils import ensure_phpmyadmin_signin_bridge + ensure_phpmyadmin_signin_bridge() + except BaseException: + pass userID = request.session['userID'] admin = Administrator.objects.get(id=userID) diff --git a/install/install.py b/install/install.py index 932091472..f66fc189b 100644 --- a/install/install.py +++ b/install/install.py @@ -3986,7 +3986,7 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout'; writeToFile.close() - os.mkdir('/usr/local/CyberCP/public/phpmyadmin/tmp') + os.makedirs('/usr/local/CyberCP/public/phpmyadmin/tmp', exist_ok=True) command = 'chown -R lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin' preFlightsChecks.call(command, self.distro, '[chown -R lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin]', diff --git a/plogical/phpmyadmin_utils.py b/plogical/phpmyadmin_utils.py new file mode 100644 index 000000000..aa5448e1e --- /dev/null +++ b/plogical/phpmyadmin_utils.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +Ensure phpMyAdmin single-sign-on bridge files exist under public/phpmyadmin/. +Fixes 404 on /phpmyadmin/phpmyadminsignin.php when the file was lost after a partial install or manual change. +""" +from __future__ import annotations + +import os +import shutil + +from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging + +PMA_DIR = '/usr/local/CyberCP/public/phpmyadmin' +SIGNIN_SRC = '/usr/local/CyberCP/plogical/phpmyadminsignin.php' +SIGNIN_NAME = 'phpmyadminsignin.php' + + +def ensure_phpmyadmin_signin_bridge() -> bool: + """ + Copy plogical/phpmyadminsignin.php into the public phpMyAdmin tree if missing, + ensure tmp/ exists, and fix ownership for lscpd. + Returns True if the sign-in file is present afterward. + """ + dst = os.path.join(PMA_DIR, SIGNIN_NAME) + try: + if not os.path.isdir(PMA_DIR): + return False + if not os.path.isfile(SIGNIN_SRC): + logging.writeToFile('phpmyadmin_utils: source signin missing at ' + SIGNIN_SRC) + return os.path.isfile(dst) + need_copy = (not os.path.isfile(dst)) or os.path.getsize(dst) < 32 + if need_copy: + shutil.copy2(SIGNIN_SRC, dst) + tmp_dir = os.path.join(PMA_DIR, 'tmp') + os.makedirs(tmp_dir, exist_ok=True) + try: + from plogical.processUtilities import ProcessUtilities + ProcessUtilities.executioner('chown -R lscpd:lscpd ' + PMA_DIR) + except Exception as ch_ex: + logging.writeToFile('phpmyadmin_utils: chown skipped or failed (non-fatal): ' + str(ch_ex)) + return os.path.isfile(dst) + except Exception as ex: + logging.writeToFile('phpmyadmin_utils: ensure_phpmyadmin_signin_bridge failed: ' + str(ex)) + return os.path.isfile(dst) diff --git a/plogical/upgrade.py b/plogical/upgrade.py index eae86d2cb..f728108ef 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -1388,7 +1388,7 @@ $cfg['Servers'][$i]['port'] = '3306'; writeToFile.writelines("$cfg['TempDir'] = '/usr/local/CyberCP/public/phpmyadmin/tmp';\n") writeToFile.close() - os.mkdir('/usr/local/CyberCP/public/phpmyadmin/tmp') + os.makedirs('/usr/local/CyberCP/public/phpmyadmin/tmp', exist_ok=True) if saved_signon and os.path.isfile(tmp_signon): shutil.copy2(tmp_signon, os.path.join(pma_dir, 'phpmyadminsignin.php')) @@ -1417,6 +1417,12 @@ $cfg['Servers'][$i]['port'] = '3306'; command = 'chown -R lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin/tmp' Upgrade.executioner_silent(command, 'chown phpMyAdmin tmp') + try: + from plogical.phpmyadmin_utils import ensure_phpmyadmin_signin_bridge + ensure_phpmyadmin_signin_bridge() + except Exception: + pass + os.chdir(cwd) except Exception as e: diff --git a/pluginInstaller/pluginInstaller.py b/pluginInstaller/pluginInstaller.py index 1fd41e2e2..0041fe070 100644 --- a/pluginInstaller/pluginInstaller.py +++ b/pluginInstaller/pluginInstaller.py @@ -58,6 +58,35 @@ class pluginInstaller: pluginHome = '/usr/local/CyberCP/' + pluginName return os.path.exists(pluginHome + '/enable_migrations') + @staticmethod + def shouldApplyPluginDatabaseMigrations(pluginName: str) -> bool: + """ + Run Django migrations when the plugin opts in (enable_migrations file) + or when a migrations/ package with real migration modules is shipped. + """ + if pluginInstaller.migrationsEnabled(pluginName): + return True + mig_dir = '/usr/local/CyberCP/' + pluginName + '/migrations' + if not os.path.isdir(mig_dir): + return False + try: + for fn in os.listdir(mig_dir): + if fn.endswith('.py') and fn != '__init__.py': + return True + except OSError: + return False + return False + + @staticmethod + def _manage_python_executable(): + for candidate in ('/usr/local/CyberCP/bin/python', '/usr/local/CyberCP/bin/python3'): + try: + if os.path.isfile(candidate) and os.access(candidate, os.X_OK): + return candidate + except OSError: + continue + return 'python3' + @staticmethod def _write_lines_to_protected_file(target_path, lines): """ @@ -338,12 +367,31 @@ class pluginInstaller: @staticmethod def installMigrations(pluginName): currentDir = os.getcwd() - os.chdir('/usr/local/CyberCP') - command = "python3 /usr/local/CyberCP/manage.py makemigrations %s" % pluginName - subprocess.call(shlex.split(command)) - command = "python3 /usr/local/CyberCP/manage.py migrate %s" % pluginName - subprocess.call(shlex.split(command)) - os.chdir(currentDir) + manage_py = '/usr/local/CyberCP/manage.py' + py = pluginInstaller._manage_python_executable() + try: + os.chdir('/usr/local/CyberCP') + mk = subprocess.call( + [py, manage_py, 'makemigrations', pluginName], + stdin=subprocess.DEVNULL, + ) + if mk != 0: + pluginInstaller.stdOut( + 'makemigrations %s exited %s (ok if no model changes)' % (pluginName, mk) + ) + mig = subprocess.call( + [py, manage_py, 'migrate', pluginName, '--noinput'], + stdin=subprocess.DEVNULL, + ) + if mig != 0: + pluginInstaller.stdOut( + 'migrate %s exited %s — check CyberPanel logs and DB permissions' % (pluginName, mig) + ) + finally: + try: + os.chdir(currentDir) + except OSError: + pass @staticmethod @@ -427,12 +475,14 @@ class pluginInstaller: ## - if pluginInstaller.migrationsEnabled(pluginName): - pluginInstaller.stdOut('Running Migrations..') + if pluginInstaller.shouldApplyPluginDatabaseMigrations(pluginName): + pluginInstaller.stdOut('Running database migrations for %s..' % pluginName) pluginInstaller.installMigrations(pluginName) - pluginInstaller.stdOut('Migrations Completed..') + pluginInstaller.stdOut('Database migrations step finished for %s.' % pluginName) else: - pluginInstaller.stdOut('Migrations not enabled, add file \'enable_migrations\' to plugin to enable') + pluginInstaller.stdOut( + 'No plugin migrations to apply (no migrations/ package and no enable_migrations marker).' + ) ## @@ -625,8 +675,11 @@ class pluginInstaller: def removeMigrations(pluginName): currentDir = os.getcwd() os.chdir('/usr/local/CyberCP') - command = "python3 /usr/local/CyberCP/manage.py migrate %s zero" % pluginName - subprocess.call(shlex.split(command)) + py = pluginInstaller._manage_python_executable() + subprocess.call( + [py, '/usr/local/CyberCP/manage.py', 'migrate', pluginName, 'zero', '--noinput'], + stdin=subprocess.DEVNULL, + ) os.chdir(currentDir) @staticmethod @@ -640,12 +693,12 @@ class pluginInstaller: ## - if pluginInstaller.migrationsEnabled(pluginName): - pluginInstaller.stdOut('Removing migrations..') + if pluginInstaller.shouldApplyPluginDatabaseMigrations(pluginName): + pluginInstaller.stdOut('Reverting database migrations for %s..' % pluginName) pluginInstaller.removeMigrations(pluginName) - pluginInstaller.stdOut('Migrations removed..') + pluginInstaller.stdOut('Database migrations reverted for %s.' % pluginName) else: - pluginInstaller.stdOut('Migrations not enabled, add file \'enable_migrations\' to plugin to enable') + pluginInstaller.stdOut('Skipping migrate zero (no migrations package / marker).') ##