From 6213ff8fddc3388f4b6c32932986aa00df48e9c5 Mon Sep 17 00:00:00 2001 From: Master3395 Date: Thu, 18 Sep 2025 22:04:05 +0200 Subject: [PATCH] Add subdomain log fix functionality and update templates. Introduce new views and URLs for fixing subdomain logs, enhance log configuration handling in the upgrade script, and update vHost configuration paths for better log management. Include a new menu item for accessing the log fix interface. --- .../templates/baseTemplate/index.html | 3 + .../management/commands/fix_subdomain_logs.py | 190 ++++++++++ plogical/upgrade.py | 153 ++++++++ plogical/vhostConfs.py | 10 +- test_subdomain_log_fix.py | 120 +++++++ .../websiteFunctions/websiteFunctions.js | 68 ++++ .../websiteFunctions/domainAlias.html | 77 ++-- .../websiteFunctions/fixSubdomainLogs.html | 334 ++++++++++++++++++ websiteFunctions/urls.py | 4 + websiteFunctions/website.py | 172 +++++++++ 10 files changed, 1095 insertions(+), 36 deletions(-) create mode 100644 plogical/management/commands/fix_subdomain_logs.py create mode 100644 test_subdomain_log_fix.py create mode 100644 websiteFunctions/templates/websiteFunctions/fixSubdomainLogs.html diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index 63928508b..882cea420 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -1180,6 +1180,9 @@ List Sub/Addon Domains + + Fix Subdomain Logs + {% if admin or modifyWebsite %} Modify Website diff --git a/plogical/management/commands/fix_subdomain_logs.py b/plogical/management/commands/fix_subdomain_logs.py new file mode 100644 index 000000000..bfbe287b9 --- /dev/null +++ b/plogical/management/commands/fix_subdomain_logs.py @@ -0,0 +1,190 @@ +#!/usr/local/CyberCP/bin/python +""" +Django management command to fix subdomain log configurations +Usage: python manage.py fix_subdomain_logs [--dry-run] [--domain ] +""" + +import os +import sys +import django +import re +import shutil +from datetime import datetime + +# Add CyberPanel to Python path +sys.path.append('/usr/local/CyberCP') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings") +django.setup() + +from django.core.management.base import BaseCommand, CommandError +from websiteFunctions.models import ChildDomains, Websites +from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging +from plogical.processUtilities import ProcessUtilities + + +class Command(BaseCommand): + help = 'Fix subdomain log configurations to use separate log files' + + def add_arguments(self, parser): + parser.add_argument( + '--dry-run', + action='store_true', + help='Show what would be changed without making changes', + ) + parser.add_argument( + '--domain', + type=str, + help='Fix logs for a specific domain only', + ) + parser.add_argument( + '--backup', + action='store_true', + help='Create backup of original configurations', + ) + + def handle(self, *args, **options): + dry_run = options['dry_run'] + specific_domain = options['domain'] + create_backup = options['backup'] + + if dry_run: + self.stdout.write( + self.style.WARNING('DRY RUN MODE - No changes will be made') + ) + + self.stdout.write('Starting subdomain log configuration fix...') + + try: + if specific_domain: + # Fix specific domain + self.fix_domain_logs(specific_domain, dry_run, create_backup) + else: + # Fix all child domains + child_domains = ChildDomains.objects.all() + fixed_count = 0 + + for child_domain in child_domains: + if self.fix_domain_logs(child_domain.domain, dry_run, create_backup): + fixed_count += 1 + + self.stdout.write( + self.style.SUCCESS(f'Fixed log configurations for {fixed_count} child domains') + ) + + except Exception as e: + raise CommandError(f'Failed to fix subdomain logs: {str(e)}') + + def fix_domain_logs(self, domain_name, dry_run=False, create_backup=False): + """Fix log configuration for a specific domain""" + try: + # Get child domain info + try: + child_domain = ChildDomains.objects.get(domain=domain_name) + master_domain = child_domain.master.domain + domain_path = child_domain.path + except ChildDomains.DoesNotExist: + self.stdout.write( + self.style.WARNING(f'Domain {domain_name} is not a child domain, skipping') + ) + return False + + vhost_conf_path = f"/usr/local/lsws/conf/vhosts/{domain_name}/vhost.conf" + + if not os.path.exists(vhost_conf_path): + self.stdout.write( + self.style.WARNING(f'VHost config not found for {domain_name}: {vhost_conf_path}') + ) + return False + + # Read current configuration + with open(vhost_conf_path, 'r') as f: + config_content = f.read() + + # Check if fix is needed + if f'{master_domain}.error_log' not in config_content and f'{master_domain}.access_log' not in config_content: + self.stdout.write(f'✓ {domain_name} already has correct log configuration') + return True + + self.stdout.write(f'Fixing log configuration for {domain_name}...') + + if dry_run: + self.stdout.write(f' [DRY RUN] Would fix log paths in {vhost_conf_path}') + return True + + # Create backup if requested + if create_backup: + backup_path = f"{vhost_conf_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}" + shutil.copy2(vhost_conf_path, backup_path) + self.stdout.write(f' Created backup: {backup_path}') + + # Fix the configuration + fixed_content = config_content + + # Fix error log path + fixed_content = re.sub( + rf'errorlog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.error_log', + f'errorlog $VH_ROOT/logs/{domain_name}.error_log', + fixed_content + ) + + # Fix access log path + fixed_content = re.sub( + rf'accesslog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.access_log', + f'accesslog $VH_ROOT/logs/{domain_name}.access_log', + fixed_content + ) + + # Fix CustomLog paths (for Apache configurations) + fixed_content = re.sub( + rf'CustomLog\s+/home/{re.escape(master_domain)}/logs/{re.escape(master_domain)}\.access_log', + f'CustomLog /home/{domain_name}/logs/{domain_name}.access_log', + fixed_content + ) + + # Write the fixed configuration + with open(vhost_conf_path, 'w') as f: + f.write(fixed_content) + + # Set proper ownership + ProcessUtilities.executioner(f'chown lsadm:lsadm {vhost_conf_path}') + + # Create the log directory if it doesn't exist + log_dir = f"/home/{master_domain}/logs" + if not os.path.exists(log_dir): + os.makedirs(log_dir, exist_ok=True) + ProcessUtilities.executioner(f'chown -R {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_dir}') + + # Create separate log files for the child domain + error_log_path = f"{log_dir}/{domain_name}.error_log" + access_log_path = f"{log_dir}/{domain_name}.access_log" + + # Create empty log files if they don't exist + for log_path in [error_log_path, access_log_path]: + if not os.path.exists(log_path): + with open(log_path, 'w') as f: + f.write('') + ProcessUtilities.executioner(f'chown {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_path}') + ProcessUtilities.executioner(f'chmod 644 {log_path}') + + # Restart LiteSpeed to apply changes + ProcessUtilities.executioner('systemctl restart lsws') + + self.stdout.write(f'✓ Fixed log configuration for {domain_name}') + logging.writeToFile(f'Fixed subdomain log configuration for {domain_name}') + + return True + + except Exception as e: + self.stdout.write( + self.style.ERROR(f'Failed to fix logs for {domain_name}: {str(e)}') + ) + logging.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}') + return False + + def show_help_examples(self): + """Show usage examples""" + self.stdout.write('\nUsage examples:') + self.stdout.write(' python manage.py fix_subdomain_logs --dry-run') + self.stdout.write(' python manage.py fix_subdomain_logs --domain diabetes.example.com') + self.stdout.write(' python manage.py fix_subdomain_logs --backup') + self.stdout.write(' python manage.py fix_subdomain_logs --domain diabetes.example.com --backup --dry-run') diff --git a/plogical/upgrade.py b/plogical/upgrade.py index 6386b2538..3588c5d7a 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -2611,6 +2611,156 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL except: pass + @staticmethod + def fixSubdomainLogConfigurations(): + """Fix subdomain log configurations during upgrade""" + try: + # Check if this fix has already been applied + fix_marker_file = '/usr/local/lscp/logs/subdomain_log_fix_applied' + if os.path.exists(fix_marker_file): + Upgrade.stdOut("Subdomain log fix already applied - skipping") + return + + Upgrade.stdOut("=== FIXING SUBDOMAIN LOG CONFIGURATIONS ===") + + # Import required modules + import sys + import os + sys.path.append('/usr/local/CyberCP') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings") + + try: + import django + django.setup() + + from websiteFunctions.models import ChildDomains + from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging + from plogical.processUtilities import ProcessUtilities + import re + import shutil + from datetime import datetime + + # Get all child domains + child_domains = ChildDomains.objects.all() + + if not child_domains: + Upgrade.stdOut("No child domains found - skipping subdomain log fix") + return + + Upgrade.stdOut(f"Found {len(child_domains)} child domains to check") + + fixed_count = 0 + skipped_count = 0 + + for child_domain in child_domains: + domain_name = child_domain.domain + master_domain = child_domain.master.domain + + vhost_conf_path = f"/usr/local/lsws/conf/vhosts/{domain_name}/vhost.conf" + + if not os.path.exists(vhost_conf_path): + Upgrade.stdOut(f"⚠️ Skipping {domain_name}: vHost config not found") + skipped_count += 1 + continue + + try: + # Read current configuration + with open(vhost_conf_path, 'r') as f: + config_content = f.read() + + # Check if fix is needed + if f'{master_domain}.error_log' not in config_content and f'{master_domain}.access_log' not in config_content: + Upgrade.stdOut(f"✅ {domain_name}: Already has correct log configuration") + skipped_count += 1 + continue + + # Create backup + backup_path = f"{vhost_conf_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}" + shutil.copy2(vhost_conf_path, backup_path) + + # Fix the configuration + fixed_content = config_content + + # Fix error log path + fixed_content = re.sub( + rf'errorlog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.error_log', + f'errorlog $VH_ROOT/logs/{domain_name}.error_log', + fixed_content + ) + + # Fix access log path + fixed_content = re.sub( + rf'accesslog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.access_log', + f'accesslog $VH_ROOT/logs/{domain_name}.access_log', + fixed_content + ) + + # Fix CustomLog paths (for Apache configurations) + fixed_content = re.sub( + rf'CustomLog\s+/home/{re.escape(master_domain)}/logs/{re.escape(master_domain)}\.access_log', + f'CustomLog /home/{domain_name}/logs/{domain_name}.access_log', + fixed_content + ) + + # Write the fixed configuration + with open(vhost_conf_path, 'w') as f: + f.write(fixed_content) + + # Set proper ownership + ProcessUtilities.executioner(f'chown lsadm:lsadm {vhost_conf_path}') + + # Create the log directory if it doesn't exist + log_dir = f"/home/{master_domain}/logs" + if not os.path.exists(log_dir): + os.makedirs(log_dir, exist_ok=True) + ProcessUtilities.executioner(f'chown -R {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_dir}') + + # Create separate log files for the child domain + error_log_path = f"{log_dir}/{domain_name}.error_log" + access_log_path = f"{log_dir}/{domain_name}.access_log" + + # Create empty log files if they don't exist + for log_path in [error_log_path, access_log_path]: + if not os.path.exists(log_path): + with open(log_path, 'w') as f: + f.write('') + ProcessUtilities.executioner(f'chown {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_path}') + ProcessUtilities.executioner(f'chmod 644 {log_path}') + + Upgrade.stdOut(f"✅ Fixed log configuration for {domain_name}") + logging.writeToFile(f'Fixed subdomain log configuration for {domain_name} during upgrade') + fixed_count += 1 + + except Exception as e: + Upgrade.stdOut(f"❌ Failed to fix {domain_name}: {str(e)}") + logging.writeToFile(f'Error fixing subdomain logs for {domain_name} during upgrade: {str(e)}') + + # Restart LiteSpeed to apply changes if any were made + if fixed_count > 0: + Upgrade.stdOut("Restarting LiteSpeed to apply log configuration changes...") + ProcessUtilities.executioner('systemctl restart lsws') + + Upgrade.stdOut(f"=== SUBDOMAIN LOG FIX COMPLETE ===") + Upgrade.stdOut(f"Fixed: {fixed_count} domains") + Upgrade.stdOut(f"Skipped: {skipped_count} domains") + + # Create marker file to indicate fix has been applied + try: + with open(fix_marker_file, 'w') as f: + f.write(f"Subdomain log fix applied on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write(f"Fixed domains: {fixed_count}\n") + f.write(f"Skipped domains: {skipped_count}\n") + except: + pass + + except ImportError as e: + Upgrade.stdOut(f"⚠️ Django not available during upgrade: {str(e)}") + Upgrade.stdOut("Subdomain log fix will be applied on next CyberPanel restart") + + except Exception as e: + Upgrade.stdOut(f"❌ Error in subdomain log fix: {str(e)}") + logging.writeToFile(f'Error in subdomain log fix during upgrade: {str(e)}') + @staticmethod def enableServices(): try: @@ -4396,6 +4546,9 @@ pm.max_spare_servers = 3 # Fix LiteSpeed configuration files if missing Upgrade.fixLiteSpeedConfig() + + # Fix subdomain log configurations + Upgrade.fixSubdomainLogConfigurations() ### General migrations are not needed any more diff --git a/plogical/vhostConfs.py b/plogical/vhostConfs.py index 21bc461f6..d9e88471f 100644 --- a/plogical/vhostConfs.py +++ b/plogical/vhostConfs.py @@ -110,13 +110,13 @@ index { indexFiles index.php, index.html } -errorlog $VH_ROOT/logs/{masterDomain}.error_log { +errorlog $VH_ROOT/logs/{virtualHostName}.error_log { useServer 0 logLevel WARN rollingSize 10M } -accesslog $VH_ROOT/logs/{masterDomain}.access_log { +accesslog $VH_ROOT/logs/{virtualHostName}.access_log { useServer 0 logFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" logHeaders 5 @@ -204,7 +204,7 @@ context /.well-known/acme-challenge { SuexecUserGroup {externalApp} {externalApp} DocumentRoot {path} Alias /.well-known/acme-challenge /home/{virtualHostName}/public_html/.well-known/acme-challenge - CustomLog /home/{masterDomain}/logs/{masterDomain}.access_log combined + CustomLog /home/{virtualHostName}/logs/{virtualHostName}.access_log combined AddHandler application/x-httpd-php{php} .php .php7 .phtml CacheRoot lscache @@ -474,7 +474,7 @@ pm.max_spare_servers = {pmMaxSpareServers} "phpVersion": {php}, "custom_conf": { ServerAdmin {administratorEmail} - CustomLog /home/{masterDomain}/logs/{masterDomain}.access_log combined + CustomLog /home/{virtualHostName}/logs/{virtualHostName}.access_log combined CacheRoot /home/{masterDomain}/lscache @@ -489,7 +489,7 @@ pm.max_spare_servers = {pmMaxSpareServers} "phpVersion": {php}, "custom_conf": { ServerAdmin {administratorEmail} - CustomLog /home/{masterDomain}/logs/{masterDomain}.access_log combined + CustomLog /home/{virtualHostName}/logs/{virtualHostName}.access_log combined CacheRoot /home/{masterDomain}/lscache diff --git a/test_subdomain_log_fix.py b/test_subdomain_log_fix.py new file mode 100644 index 000000000..37b67d501 --- /dev/null +++ b/test_subdomain_log_fix.py @@ -0,0 +1,120 @@ +#!/usr/local/CyberCP/bin/python +""" +Test script for subdomain log fix +This script tests the subdomain log fix functionality +""" + +import os +import sys +import django + +# Add CyberPanel to Python path +sys.path.append('/usr/local/CyberCP') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings") +django.setup() + +from websiteFunctions.models import ChildDomains +from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging + + +def test_subdomain_log_configuration(): + """Test if subdomain log configurations are correct""" + print("Testing subdomain log configurations...") + + issues_found = 0 + child_domains = ChildDomains.objects.all() + + if not child_domains: + print("No child domains found.") + return True + + for child_domain in child_domains: + domain_name = child_domain.domain + master_domain = child_domain.master.domain + + vhost_conf_path = f"/usr/local/lsws/conf/vhosts/{domain_name}/vhost.conf" + + if not os.path.exists(vhost_conf_path): + print(f"⚠️ VHost config not found for {domain_name}") + issues_found += 1 + continue + + try: + with open(vhost_conf_path, 'r') as f: + config_content = f.read() + + # Check for incorrect log paths + if f'{master_domain}.error_log' in config_content: + print(f"❌ {domain_name}: Using master domain error log") + issues_found += 1 + else: + print(f"✅ {domain_name}: Error log configuration OK") + + if f'{master_domain}.access_log' in config_content: + print(f"❌ {domain_name}: Using master domain access log") + issues_found += 1 + else: + print(f"✅ {domain_name}: Access log configuration OK") + + except Exception as e: + print(f"❌ {domain_name}: Error reading config - {str(e)}") + issues_found += 1 + + if issues_found == 0: + print("\n🎉 All subdomain log configurations are correct!") + return True + else: + print(f"\n⚠️ Found {issues_found} issues with subdomain log configurations") + return False + + +def test_management_command(): + """Test the management command""" + print("\nTesting management command...") + + try: + from django.core.management import call_command + from io import StringIO + + # Test dry run + out = StringIO() + call_command('fix_subdomain_logs', '--dry-run', stdout=out) + print("✅ Management command dry run works") + + return True + except Exception as e: + print(f"❌ Management command test failed: {str(e)}") + return False + + +def main(): + """Main test function""" + print("=" * 60) + print("SUBDOMAIN LOG FIX TEST") + print("=" * 60) + + # Test 1: Check current configurations + config_test = test_subdomain_log_configuration() + + # Test 2: Test management command + cmd_test = test_management_command() + + print("\n" + "=" * 60) + print("TEST SUMMARY") + print("=" * 60) + + if config_test and cmd_test: + print("🎉 All tests passed!") + print("\nTo fix any issues found:") + print("1. Via Web Interface: Websites > Fix Subdomain Logs") + print("2. Via CLI: python manage.py fix_subdomain_logs --all") + print("3. For specific domain: python manage.py fix_subdomain_logs --domain example.com") + return True + else: + print("⚠️ Some tests failed. Check the output above.") + return False + + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) diff --git a/websiteFunctions/static/websiteFunctions/websiteFunctions.js b/websiteFunctions/static/websiteFunctions/websiteFunctions.js index 7a4d76d04..562eeddb8 100644 --- a/websiteFunctions/static/websiteFunctions/websiteFunctions.js +++ b/websiteFunctions/static/websiteFunctions/websiteFunctions.js @@ -13289,6 +13289,16 @@ app.controller('manageAliasController', function ($scope, $http, $timeout, $wind $scope.manageAliasLoading = true; $scope.operationSuccess = true; + // Initialize the page to show aliases list + $scope.showAliasesList = function() { + $scope.aliasTable = true; + $scope.addAliasButton = true; + $scope.domainAliasForm = false; + }; + + // Auto-show aliases list on page load + $scope.showAliasesList(); + $scope.createAliasEnter = function ($event) { var keyCode = $event.which || $event.keyCode; if (keyCode === 13) { @@ -13536,6 +13546,64 @@ app.controller('manageAliasController', function ($scope, $http, $timeout, $wind }; + $scope.issueAliasSSL = function (masterDomain, aliasDomain) { + $scope.manageAliasLoading = false; + + url = "/websites/issueAliasSSL"; + + var data = { + masterDomain: masterDomain, + aliasDomain: aliasDomain + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas); + + function ListInitialDatas(response) { + if (response.data.issueAliasSSL === 1) { + $scope.aliasTable = false; + $scope.addAliasButton = true; + $scope.domainAliasForm = true; + $scope.aliasError = true; + $scope.couldNotConnect = true; + $scope.aliasCreated = true; + $scope.manageAliasLoading = true; + $scope.operationSuccess = false; + + $timeout(function () { + $window.location.reload(); + }, 3000); + } else { + $scope.aliasTable = false; + $scope.addAliasButton = true; + $scope.domainAliasForm = true; + $scope.aliasError = false; + $scope.couldNotConnect = true; + $scope.aliasCreated = true; + $scope.manageAliasLoading = true; + $scope.operationSuccess = true; + + $scope.errorMessage = response.data.error_message; + } + } + + function cantLoadInitialDatas(response) { + $scope.aliasTable = false; + $scope.addAliasButton = true; + $scope.domainAliasForm = true; + $scope.aliasError = true; + $scope.couldNotConnect = false; + $scope.aliasCreated = true; + $scope.manageAliasLoading = true; + $scope.operationSuccess = true; + } + }; + ////// create domain part diff --git a/websiteFunctions/templates/websiteFunctions/domainAlias.html b/websiteFunctions/templates/websiteFunctions/domainAlias.html index 8e2162449..5ad7e01b7 100644 --- a/websiteFunctions/templates/websiteFunctions/domainAlias.html +++ b/websiteFunctions/templates/websiteFunctions/domainAlias.html @@ -175,37 +175,52 @@
- - - - - - - - - - - - - - - - - - - -
DomainLaunchPathSSLDelete
- - - - -
+ +
+

{% trans "No domain aliases found for this domain." %}

+
+ + +
+ + + + + + + + + + + + {% for alias in aliases %} + + + + + + + + {% endfor %} + +
{% trans "Alias Domain" %}{% trans "Master Domain" %}{% trans "Launch" %}{% trans "SSL" %}{% trans "Delete" %}
{{ alias }}{{ masterDomain }} + + + + + + + +
+
diff --git a/websiteFunctions/templates/websiteFunctions/fixSubdomainLogs.html b/websiteFunctions/templates/websiteFunctions/fixSubdomainLogs.html new file mode 100644 index 000000000..3a12cfc8a --- /dev/null +++ b/websiteFunctions/templates/websiteFunctions/fixSubdomainLogs.html @@ -0,0 +1,334 @@ +{% extends "baseTemplate/index.html" %} +{% load static %} +{% load i18n %} + +{% block title %}Fix Subdomain Logs{% endblock %} + +{% block content %} +
+ + +
+
+ +
+
+

Subdomain Log Issue Fix

+
+
+
+
Issue Description
+

This tool fixes a known issue where child domains (subdomains) share log files with their master domains, causing log contamination. This fix ensures each subdomain has its own separate log files.

+
+ +
+
What This Fix Does
+
    +
  • Updates vHost configurations to use separate log files for each subdomain
  • +
  • Creates individual log files: subdomain.error_log and subdomain.access_log
  • +
  • Restarts LiteSpeed to apply changes
  • +
  • Maintains proper file ownership and permissions
  • +
+
+
+
+ + +
+
+

Fix Options

+
+
+
+ {% csrf_token %} + +
+
+
+ + +
+
+ + +
+ +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ +
+ + +
+
+
+
+ + + + + +
+
+

Current Child Domains

+
+
+
+ + + + + + + + + + + + +
DomainMaster DomainLog StatusActions
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/websiteFunctions/urls.py b/websiteFunctions/urls.py index 891d8837c..8d56bc1bb 100644 --- a/websiteFunctions/urls.py +++ b/websiteFunctions/urls.py @@ -207,5 +207,9 @@ urlpatterns = [ path('get_website_resources/', views.get_website_resources, name='get_website_resources'), + # Subdomain Log Fix + path('fixSubdomainLogs', views.fixSubdomainLogs, name='fixSubdomainLogs'), + path('fixSubdomainLogsAction', views.fixSubdomainLogsAction, name='fixSubdomainLogsAction'), + ] diff --git a/websiteFunctions/website.py b/websiteFunctions/website.py index 51cbeeff4..083707355 100644 --- a/websiteFunctions/website.py +++ b/websiteFunctions/website.py @@ -3936,6 +3936,7 @@ context /cyberpanel_suspension_page.html { if is_child: # Use child domain template default_config = vhostConfs.olsChildConf + default_config = default_config.replace('{virtualHostName}', virtualHost) default_config = default_config.replace('{path}', path) default_config = default_config.replace('{masterDomain}', master_domain) default_config = default_config.replace('{adminEmails}', admin_email) @@ -8200,3 +8201,174 @@ StrictHostKeyChecking no json_data = json.dumps(data_ret) return HttpResponse(json_data) + def fixSubdomainLogs(self, request): + """Display subdomain log fix interface""" + try: + currentACL = ACLManager.loadedACL(request.user.pk) + admin = ACLManager.loadedAdmin(request.user.pk) + + if ACLManager.currentContextPermission(currentACL, 'websites') == 0: + return ACLManager.loadErrorJson('websites', 0) + + return render(request, 'websiteFunctions/fixSubdomainLogs.html', { + 'acls': currentACL, + 'admin': admin + }) + + except BaseException as msg: + logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [fixSubdomainLogs]") + return ACLManager.loadErrorJson('websites', 0) + + def fixSubdomainLogsAction(self, request): + """Execute subdomain log fix""" + try: + currentACL = ACLManager.loadedACL(request.user.pk) + admin = ACLManager.loadedAdmin(request.user.pk) + + if ACLManager.currentContextPermission(currentACL, 'websites') == 0: + return ACLManager.loadErrorJson('websites', 0) + + action = request.POST.get('action') + domain = request.POST.get('domain', '').strip() + dry_run = request.POST.get('dry_run', 'false').lower() == 'true' + create_backup = request.POST.get('create_backup', 'false').lower() == 'true' + + if action == 'fix_all': + # Fix all child domains + from websiteFunctions.models import ChildDomains + child_domains = ChildDomains.objects.all() + fixed_count = 0 + failed_domains = [] + + for child_domain in child_domains: + if self._fix_single_domain_logs(child_domain.domain, dry_run, create_backup): + fixed_count += 1 + else: + failed_domains.append(child_domain.domain) + + if failed_domains: + message = f"Fixed {fixed_count} domains. Failed: {', '.join(failed_domains)}" + else: + message = f"Successfully fixed {fixed_count} child domains" + + data_ret = {'status': 1, 'message': message} + + elif action == 'fix_domain' and domain: + # Fix specific domain + if self._fix_single_domain_logs(domain, dry_run, create_backup): + data_ret = {'status': 1, 'message': f"Successfully fixed logs for {domain}"} + else: + data_ret = {'status': 0, 'error_message': f"Failed to fix logs for {domain}"} + else: + data_ret = {'status': 0, 'error_message': "Invalid action or missing domain"} + + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + except BaseException as msg: + logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [fixSubdomainLogsAction]") + data_ret = {'status': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + def _fix_single_domain_logs(self, domain_name, dry_run=False, create_backup=False): + """Fix log configuration for a single domain""" + try: + import re + import shutil + from datetime import datetime + from websiteFunctions.models import ChildDomains + + # Get child domain info + try: + child_domain = ChildDomains.objects.get(domain=domain_name) + master_domain = child_domain.master.domain + domain_path = child_domain.path + except ChildDomains.DoesNotExist: + logging.CyberCPLogFileWriter.writeToFile(f'Domain {domain_name} is not a child domain') + return False + + vhost_conf_path = f"/usr/local/lsws/conf/vhosts/{domain_name}/vhost.conf" + + if not os.path.exists(vhost_conf_path): + logging.CyberCPLogFileWriter.writeToFile(f'VHost config not found for {domain_name}') + return False + + # Read current configuration + with open(vhost_conf_path, 'r') as f: + config_content = f.read() + + # Check if fix is needed + if f'{master_domain}.error_log' not in config_content and f'{master_domain}.access_log' not in config_content: + logging.CyberCPLogFileWriter.writeToFile(f'{domain_name} already has correct log configuration') + return True + + if dry_run: + logging.CyberCPLogFileWriter.writeToFile(f'[DRY RUN] Would fix log paths for {domain_name}') + return True + + # Create backup if requested + if create_backup: + backup_path = f"{vhost_conf_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}" + shutil.copy2(vhost_conf_path, backup_path) + logging.CyberCPLogFileWriter.writeToFile(f'Created backup: {backup_path}') + + # Fix the configuration + fixed_content = config_content + + # Fix error log path + fixed_content = re.sub( + rf'errorlog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.error_log', + f'errorlog $VH_ROOT/logs/{domain_name}.error_log', + fixed_content + ) + + # Fix access log path + fixed_content = re.sub( + rf'accesslog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.access_log', + f'accesslog $VH_ROOT/logs/{domain_name}.access_log', + fixed_content + ) + + # Fix CustomLog paths (for Apache configurations) + fixed_content = re.sub( + rf'CustomLog\s+/home/{re.escape(master_domain)}/logs/{re.escape(master_domain)}\.access_log', + f'CustomLog /home/{domain_name}/logs/{domain_name}.access_log', + fixed_content + ) + + # Write the fixed configuration + with open(vhost_conf_path, 'w') as f: + f.write(fixed_content) + + # Set proper ownership + ProcessUtilities.executioner(f'chown lsadm:lsadm {vhost_conf_path}') + + # Create the log directory if it doesn't exist + log_dir = f"/home/{master_domain}/logs" + if not os.path.exists(log_dir): + os.makedirs(log_dir, exist_ok=True) + ProcessUtilities.executioner(f'chown -R {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_dir}') + + # Create separate log files for the child domain + error_log_path = f"{log_dir}/{domain_name}.error_log" + access_log_path = f"{log_dir}/{domain_name}.access_log" + + # Create empty log files if they don't exist + for log_path in [error_log_path, access_log_path]: + if not os.path.exists(log_path): + with open(log_path, 'w') as f: + f.write('') + ProcessUtilities.executioner(f'chown {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_path}') + ProcessUtilities.executioner(f'chmod 644 {log_path}') + + # Restart LiteSpeed to apply changes + ProcessUtilities.executioner('systemctl restart lsws') + + logging.CyberCPLogFileWriter.writeToFile(f'Fixed subdomain log configuration for {domain_name}') + return True + + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}') + return False +