diff --git a/dockerManager/DOCKER_MANAGER_FIXES.md b/dockerManager/DOCKER_MANAGER_FIXES.md deleted file mode 100644 index 78564d50b..000000000 --- a/dockerManager/DOCKER_MANAGER_FIXES.md +++ /dev/null @@ -1,78 +0,0 @@ -# Docker Manager Module - Critical and Medium Issues Fixed - -## Summary -This document outlines all the critical and medium priority issues that have been fixed in the Docker Manager module of CyberPanel. - -## šŸ”“ Critical Issues Fixed - -### 1. Missing pullImage Function Implementation -- **Issue**: `pullImage` function was referenced in templates and JavaScript but not implemented -- **Files Modified**: - - `container.py` - Added `pullImage()` method with security validation - - `views.py` - Added `pullImage()` view function - - `urls.py` - Added URL route for pullImage -- **Security Features Added**: - - Image name validation to prevent injection attacks - - Proper error handling for Docker API errors - - Admin permission checks - -### 2. Inconsistent Error Handling -- **Issue**: Multiple functions used `BaseException` which catches all exceptions including system exits -- **Files Modified**: `container.py`, `views.py` -- **Changes**: Replaced `BaseException` with `Exception` for better error handling -- **Impact**: Improved debugging and error reporting - -## 🟔 Medium Priority Issues Fixed - -### 3. Security Enhancements -- **Rate Limiting Improvements**: - - Enhanced rate limiting system with JSON-based tracking - - Better error logging for rate limit violations - - Improved fallback handling when rate limiting fails -- **Command Validation**: Already had good validation, enhanced error messages - -### 4. Code Quality Issues -- **Typo Fixed**: `WPemal` → `WPemail` in `recreateappcontainer` function -- **Import Issues**: Fixed undefined `loadImages` reference -- **URL Handling**: Improved redirect handling with proper Django URL reversal - -### 5. Template Consistency -- **CSS Variables**: Fixed inconsistent CSS variable usage in templates -- **Files Modified**: `manageImages.html` -- **Changes**: Standardized `--bg-gradient` variable usage - -## šŸ”§ Technical Details - -### New Functions Added -1. **`pullImage(userID, data)`** - Pulls Docker images with security validation -2. **`_validate_image_name(image_name)`** - Validates Docker image names to prevent injection - -### Enhanced Functions -1. **`_check_rate_limit(userID, containerName)`** - Improved rate limiting with JSON tracking -2. **Error handling** - Replaced BaseException with Exception throughout - -### Security Improvements -- Image name validation using regex pattern: `^[a-zA-Z0-9._/-]+$` -- Enhanced rate limiting with detailed logging -- Better error messages for debugging -- Proper permission checks for all operations - -## šŸ“Š Files Modified -- `cyberpanel/dockerManager/container.py` - Main container management logic -- `cyberpanel/dockerManager/views.py` - Django view functions -- `cyberpanel/dockerManager/urls.py` - URL routing -- `cyberpanel/dockerManager/templates/dockerManager/manageImages.html` - Template consistency - -## āœ… Testing Recommendations -1. Test image pulling functionality with various image names -2. Verify rate limiting works correctly -3. Test error handling with invalid inputs -4. Confirm all URLs are accessible -5. Validate CSS consistency across templates - -## šŸš€ Status -All critical and medium priority issues have been resolved. The Docker Manager module is now more secure, robust, and maintainable. - ---- -*Generated on: $(date)* -*Fixed by: AI Assistant* diff --git a/dockerManager/DOCKER_UPDATE_FEATURES.md b/dockerManager/DOCKER_UPDATE_FEATURES.md deleted file mode 100644 index 3f214c07f..000000000 --- a/dockerManager/DOCKER_UPDATE_FEATURES.md +++ /dev/null @@ -1,199 +0,0 @@ -# Docker Container Update/Upgrade Features - -## Overview -This implementation adds comprehensive Docker container update/upgrade functionality to CyberPanel with full data persistence using Docker volumes. The solution addresses the GitHub issue [#1174](https://github.com/usmannasir/cyberpanel/issues/1174) by providing safe container updates without data loss. - -## Features Implemented - -### 1. Container Update with Data Preservation -- **Function**: `updateContainer()` -- **Purpose**: Update container to new image while preserving all data -- **Data Safety**: Uses Docker volumes to ensure no data loss -- **Process**: - 1. Extracts current container configuration (volumes, environment, ports) - 2. Pulls new image if not available locally - 3. Creates new container with same configuration but new image - 4. Preserves all volumes and data - 5. Removes old container only after successful new container startup - 6. Updates database records - -### 2. Delete Container + Data -- **Function**: `deleteContainerWithData()` -- **Purpose**: Permanently delete container and all associated data -- **Safety**: Includes strong confirmation dialogs -- **Process**: - 1. Identifies all volumes associated with container - 2. Stops and removes container - 3. Deletes all associated Docker volumes - 4. Removes database records - 5. Provides confirmation of deleted volumes - -### 3. Delete Container (Keep Data) -- **Function**: `deleteContainerKeepData()` -- **Purpose**: Delete container but preserve data in volumes -- **Use Case**: When you want to remove container but keep data for future use -- **Process**: - 1. Identifies volumes to preserve - 2. Stops and removes container - 3. Keeps all volumes intact - 4. Reports preserved volumes to user - -## Technical Implementation - -### Backend Changes - -#### Views (`views.py`) -- `updateContainer()` - Handles container updates -- `deleteContainerWithData()` - Handles destructive deletion -- `deleteContainerKeepData()` - Handles data-preserving deletion - -#### URLs (`urls.py`) -- `/docker/updateContainer` - Update endpoint -- `/docker/deleteContainerWithData` - Delete with data endpoint -- `/docker/deleteContainerKeepData` - Delete keep data endpoint - -#### Container Manager (`container.py`) -- `updateContainer()` - Core update logic with volume preservation -- `deleteContainerWithData()` - Complete data removal -- `deleteContainerKeepData()` - Container removal with data preservation - -### Frontend Changes - -#### Template (`listContainers.html`) -- New update button with sync icon -- Dropdown menu for delete options -- Update modal with image/tag selection -- Enhanced styling for new components - -#### JavaScript (`dockerManager.js`) -- `showUpdateModal()` - Opens update dialog -- `performUpdate()` - Executes container update -- `deleteContainerWithData()` - Handles destructive deletion -- `deleteContainerKeepData()` - Handles data-preserving deletion -- Enhanced confirmation dialogs - -## User Interface - -### New Buttons -1. **Update Button** (šŸ”„) - Orange button for container updates -2. **Delete Dropdown** (šŸ—‘ļø) - Red dropdown with two options: - - Delete Container (Keep Data) - Preserves volumes - - Delete Container + Data - Removes everything - -### Update Modal -- Container name (read-only) -- Current image (read-only) -- New image input field -- New tag input field -- Data safety information -- Confirmation buttons - -### Confirmation Dialogs -- **Update**: Confirms image/tag change with data preservation notice -- **Delete + Data**: Strong warning about permanent data loss -- **Delete Keep Data**: Confirms container removal with data preservation - -## Data Safety Features - -### Volume Management -- Automatic detection of container volumes -- Support for both named volumes and bind mounts -- Volume preservation during updates -- Volume cleanup during destructive deletion - -### Error Handling -- Rollback capability if update fails -- Comprehensive error messages -- Operation logging for debugging -- Graceful failure handling - -### Security -- ACL permission checks -- Container ownership verification -- Input validation -- Rate limiting (existing) - -## Usage Examples - -### Updating a Container -1. Click the update button (šŸ”„) next to any container -2. Enter new image name (e.g., `nginx`, `mysql`) -3. Enter new tag (e.g., `latest`, `1.21`, `alpine`) -4. Click "Update Container" -5. Confirm the operation -6. Container updates with all data preserved - -### Deleting with Data Preservation -1. Click the delete dropdown (šŸ—‘ļø) next to any container -2. Select "Delete Container (Keep Data)" -3. Confirm the operation -4. Container is removed but data remains in volumes - -### Deleting Everything -1. Click the delete dropdown (šŸ—‘ļø) next to any container -2. Select "Delete Container + Data" -3. Read the warning carefully -4. Confirm the operation -5. Container and all data are permanently removed - -## Benefits - -### For Users -- **No Data Loss**: Updates preserve all container data -- **Easy Updates**: Simple interface for container updates -- **Flexible Deletion**: Choose between data preservation or complete removal -- **Clear Warnings**: Understand exactly what each operation does - -### For Administrators -- **Safe Operations**: Built-in safety measures prevent accidental data loss -- **Audit Trail**: All operations are logged -- **Rollback Capability**: Failed updates can be rolled back -- **Volume Management**: Clear visibility into data storage - -## Technical Requirements - -### Docker Features Used -- Docker volumes for data persistence -- Container recreation with volume mounting -- Image pulling and management -- Volume cleanup and management - -### Dependencies -- Docker Python SDK -- Existing CyberPanel ACL system -- PNotify for user notifications -- Bootstrap for UI components - -## Testing - -A test script is provided (`test_docker_update.py`) that verifies: -- All new methods are available -- Function signatures are correct -- Error handling is in place -- UI components are properly integrated - -## Future Enhancements - -### Potential Improvements -1. **Bulk Operations**: Update/delete multiple containers -2. **Scheduled Updates**: Automatic container updates -3. **Update History**: Track container update history -4. **Volume Management UI**: Direct volume management interface -5. **Backup Integration**: Automatic backups before updates - -### Monitoring -1. **Update Notifications**: Email notifications for updates -2. **Health Checks**: Verify container health after updates -3. **Performance Metrics**: Track update performance -4. **Error Reporting**: Detailed error reporting and recovery - -## Conclusion - -This implementation provides a complete solution for Docker container updates in CyberPanel while ensuring data safety through Docker volumes. The user-friendly interface makes container management accessible while the robust backend ensures data integrity and system stability. - -The solution addresses the original GitHub issue by providing: -- āœ… Safe container updates without data loss -- āœ… Clear separation between container and data deletion -- āœ… User-friendly interface with proper confirmations -- āœ… Comprehensive error handling and rollback capability -- āœ… Full integration with existing CyberPanel architecture diff --git a/dockerManager/container.py b/dockerManager/container.py index 3761c0db0..8458da9f4 100644 --- a/dockerManager/container.py +++ b/dockerManager/container.py @@ -300,7 +300,24 @@ class ContainerManager(multi.Thread): envList = data['envList'] volList = data['volList'] - inspectImage = dockerAPI.inspect_image(image + ":" + tag) + try: + inspectImage = dockerAPI.inspect_image(image + ":" + tag) + except docker.errors.APIError as err: + error_message = str(err) + data_ret = {'createContainerStatus': 0, 'error_message': f'Failed to inspect image: {error_message}'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + except docker.errors.ImageNotFound as err: + error_message = str(err) + data_ret = {'createContainerStatus': 0, 'error_message': f'Image not found: {error_message}'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + except Exception as err: + error_message = str(err) + data_ret = {'createContainerStatus': 0, 'error_message': f'Error inspecting image: {error_message}'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + portConfig = {} # Formatting envList for usage - handle both simple and advanced modes @@ -359,8 +376,8 @@ class ContainerManager(multi.Thread): try: container = client.containers.create(**containerArgs) - except Exception as err: - # Check if it's a port allocation error by converting to string first + except docker.errors.APIError as err: + # Handle Docker API errors properly error_message = str(err) if "port is already allocated" in error_message: # We need to delete container if port is not available print("Deleting container") @@ -368,7 +385,23 @@ class ContainerManager(multi.Thread): container.remove(force=True) except: pass # Container might not exist yet - data_ret = {'createContainerStatus': 0, 'error_message': error_message} + data_ret = {'createContainerStatus': 0, 'error_message': f'Docker API error: {error_message}'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + except docker.errors.ImageNotFound as err: + error_message = str(err) + data_ret = {'createContainerStatus': 0, 'error_message': f'Image not found: {error_message}'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + except docker.errors.ContainerError as err: + error_message = str(err) + data_ret = {'createContainerStatus': 0, 'error_message': f'Container error: {error_message}'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + except Exception as err: + # Handle any other exceptions + error_message = str(err) if err else "Unknown error occurred" + data_ret = {'createContainerStatus': 0, 'error_message': f'Container creation error: {error_message}'} json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -957,18 +990,67 @@ class ContainerManager(multi.Thread): dockerAPI = docker.APIClient() name = data['name'] + force = data.get('force', False) + try: if name == 0: + # Prune unused images action = client.images.prune() else: - action = client.images.remove(name) + # First, try to remove containers that might be using this image + containers_using_image = [] + try: + for container in client.containers.list(all=True): + container_image = container.attrs['Config']['Image'] + if container_image == name or container_image.startswith(name + ':'): + containers_using_image.append(container) + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error checking containers for image {name}: {str(e)}') + + # Remove containers that are using this image + for container in containers_using_image: + try: + if container.status == 'running': + container.stop() + time.sleep(1) + container.remove(force=True) + logging.CyberCPLogFileWriter.writeToFile(f'Removed container {container.name} that was using image {name}') + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error removing container {container.name}: {str(e)}') + + # Now try to remove the image + try: + if force: + action = client.images.remove(name, force=True) + else: + action = client.images.remove(name) + logging.CyberCPLogFileWriter.writeToFile(f'Successfully removed image {name}') + except docker.errors.APIError as err: + error_msg = str(err) + if "conflict: unable to remove repository reference" in error_msg and "must force" in error_msg: + # Try with force if not already forced + if not force: + logging.CyberCPLogFileWriter.writeToFile(f'Retrying image removal with force: {name}') + action = client.images.remove(name, force=True) + else: + raise err + else: + raise err + print(action) except docker.errors.APIError as err: - data_ret = {'removeImageStatus': 0, 'error_message': str(err)} + error_message = str(err) + # Provide more helpful error messages + if "conflict: unable to remove repository reference" in error_message: + error_message = f"Image {name} is still being used by containers. Use force removal to delete it." + elif "No such image" in error_message: + error_message = f"Image {name} not found or already removed." + + data_ret = {'removeImageStatus': 0, 'error_message': error_message} json_data = json.dumps(data_ret) return HttpResponse(json_data) - except: - data_ret = {'removeImageStatus': 0, 'error_message': 'Unknown'} + except Exception as e: + data_ret = {'removeImageStatus': 0, 'error_message': f'Unknown error: {str(e)}'} json_data = json.dumps(data_ret) return HttpResponse(json_data) diff --git a/dockerManager/static/dockerManager/dockerManager.js b/dockerManager/static/dockerManager/dockerManager.js index 943a4aa95..e049906dd 100644 --- a/dockerManager/static/dockerManager/dockerManager.js +++ b/dockerManager/static/dockerManager/dockerManager.js @@ -127,6 +127,54 @@ app.controller('runContainer', function ($scope, $http) { // Advanced Environment Variable Mode $scope.advancedEnvMode = false; + + // Helper function to generate Docker Compose YAML + $scope.generateDockerComposeYml = function(containerInfo) { + var yml = 'version: \'3.8\'\n\n'; + yml += 'services:\n'; + yml += ' ' + containerInfo.name + ':\n'; + yml += ' image: ' + containerInfo.image + '\n'; + yml += ' container_name: ' + containerInfo.name + '\n'; + + // Add ports + var ports = Object.keys(containerInfo.ports); + if (ports.length > 0) { + yml += ' ports:\n'; + for (var i = 0; i < ports.length; i++) { + var port = ports[i]; + if (containerInfo.ports[port]) { + yml += ' - "' + containerInfo.ports[port] + ':' + port + '"\n'; + } + } + } + + // Add volumes + var volumes = Object.keys(containerInfo.volumes); + if (volumes.length > 0) { + yml += ' volumes:\n'; + for (var i = 0; i < volumes.length; i++) { + var volume = volumes[i]; + if (containerInfo.volumes[volume]) { + yml += ' - ' + containerInfo.volumes[volume] + ':' + volume + '\n'; + } + } + } + + // Add environment variables + var envVars = Object.keys(containerInfo.environment); + if (envVars.length > 0) { + yml += ' environment:\n'; + for (var i = 0; i < envVars.length; i++) { + var envVar = envVars[i]; + yml += ' - ' + envVar + '=' + containerInfo.environment[envVar] + '\n'; + } + } + + // Add restart policy + yml += ' restart: unless-stopped\n'; + + return yml; + }; $scope.advancedEnvText = ''; $scope.advancedEnvCount = 0; $scope.parsedEnvVars = {}; @@ -273,53 +321,6 @@ app.controller('runContainer', function ($scope, $http) { } }; - // Helper function to generate Docker Compose YAML - function generateDockerComposeYml(containerInfo) { - var yml = 'version: \'3.8\'\n\n'; - yml += 'services:\n'; - yml += ' ' + containerInfo.name + ':\n'; - yml += ' image: ' + containerInfo.image + '\n'; - yml += ' container_name: ' + containerInfo.name + '\n'; - - // Add ports - var ports = Object.keys(containerInfo.ports); - if (ports.length > 0) { - yml += ' ports:\n'; - for (var i = 0; i < ports.length; i++) { - var port = ports[i]; - if (containerInfo.ports[port]) { - yml += ' - "' + containerInfo.ports[port] + ':' + port + '"\n'; - } - } - } - - // Add volumes - var volumes = Object.keys(containerInfo.volumes); - if (volumes.length > 0) { - yml += ' volumes:\n'; - for (var i = 0; i < volumes.length; i++) { - var volume = volumes[i]; - if (containerInfo.volumes[volume]) { - yml += ' - ' + containerInfo.volumes[volume] + ':' + volume + '\n'; - } - } - } - - // Add environment variables - var envVars = Object.keys(containerInfo.environment); - if (envVars.length > 0) { - yml += ' environment:\n'; - for (var i = 0; i < envVars.length; i++) { - var envVar = envVars[i]; - yml += ' - ' + envVar + '=' + containerInfo.environment[envVar] + '\n'; - } - } - - // Add restart policy - yml += ' restart: unless-stopped\n'; - - return yml; - } // Docker Compose Functions for runContainer $scope.generateDockerCompose = function() { @@ -344,7 +345,7 @@ app.controller('runContainer', function ($scope, $http) { } // Generate docker-compose.yml content - var composeContent = generateDockerComposeYml(containerInfo); + var composeContent = $scope.generateDockerComposeYml(containerInfo); // Create and download file var blob = new Blob([composeContent], { type: 'text/yaml' }); @@ -1576,7 +1577,7 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) { } // Generate docker-compose.yml content - var composeContent = generateDockerComposeYml(containerInfo); + var composeContent = $scope.generateDockerComposeYml(containerInfo); // Create and download file var blob = new Blob([composeContent], { type: 'text/yaml' }); @@ -2374,7 +2375,7 @@ app.controller('manageImages', function ($scope, $http) { (new PNotify({ title: 'Confirmation Needed', - text: 'Are you sure?', + text: 'Are you sure you want to remove this image?', icon: 'fa fa-question-circle', hide: false, confirm: { @@ -2392,14 +2393,16 @@ app.controller('manageImages', function ($scope, $http) { if (counter == '0') { var name = 0; + var force = false; } else { var name = $("#" + counter).val() + var force = false; } url = "/docker/removeImage"; - var data = {name: name}; + var data = {name: name, force: force}; var config = { headers: { @@ -2416,16 +2419,67 @@ app.controller('manageImages', function ($scope, $http) { if (response.data.removeImageStatus === 1) { new PNotify({ title: 'Image(s) removed', + text: 'Image has been successfully removed', type: 'success' }); window.location.href = "/docker/manageImages"; } else { - new PNotify({ - title: 'Unable to complete request', - text: response.data.error_message, - type: 'error' - }); + var errorMessage = response.data.error_message; + + // Check if it's a conflict error and offer force removal + if (errorMessage && errorMessage.includes("still being used by containers")) { + new PNotify({ + title: 'Image in Use', + text: errorMessage + ' Would you like to force remove it?', + icon: 'fa fa-exclamation-triangle', + hide: false, + confirm: { + confirm: true + }, + buttons: { + closer: false, + sticker: false + }, + history: { + history: false + } + }).get().on('pnotify.confirm', function () { + // Force remove the image + $('#imageLoading').show(); + var forceData = {name: name, force: true}; + $http.post(url, forceData, config).then(function(forceResponse) { + $('#imageLoading').hide(); + if (forceResponse.data.removeImageStatus === 1) { + new PNotify({ + title: 'Image Force Removed', + text: 'Image has been force removed successfully', + type: 'success' + }); + window.location.href = "/docker/manageImages"; + } else { + new PNotify({ + title: 'Force Removal Failed', + text: forceResponse.data.error_message, + type: 'error' + }); + } + }, function(forceError) { + $('#imageLoading').hide(); + new PNotify({ + title: 'Force Removal Failed', + text: 'Could not force remove the image', + type: 'error' + }); + }); + }); + } else { + new PNotify({ + title: 'Unable to complete request', + text: errorMessage, + type: 'error' + }); + } } $('#imageLoading').hide(); } diff --git a/test_firewall_blocking.py b/test_firewall_blocking.py deleted file mode 100644 index 21f485e03..000000000 --- a/test_firewall_blocking.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for the new firewall blocking functionality -This script tests the blockIPAddress API endpoint -""" - -import requests -import json -import sys - -def test_firewall_blocking(): - """ - Test the firewall blocking functionality - Note: This is a basic test script. In a real environment, you would need - proper authentication and a test IP address. - """ - - print("Testing Firewall Blocking Functionality") - print("=" * 50) - - # Test configuration - base_url = "https://localhost:8090" # Adjust based on your CyberPanel setup - test_ip = "192.168.1.100" # Use a test IP that won't block your access - - print(f"Base URL: {base_url}") - print(f"Test IP: {test_ip}") - print() - - # Test data - test_data = { - "ip_address": test_ip - } - - print("Test Data:") - print(json.dumps(test_data, indent=2)) - print() - - print("Note: This test requires:") - print("1. Valid CyberPanel session with admin privileges") - print("2. CyberPanel addons enabled") - print("3. Active firewalld service") - print() - - print("To test manually:") - print("1. Login to CyberPanel dashboard") - print("2. Go to Dashboard -> SSH Security Analysis") - print("3. Look for 'Brute Force Attack Detected' alerts") - print("4. Click the 'Block IP' button next to malicious IPs") - print() - - print("Expected behavior:") - print("- Button shows loading state during blocking") - print("- Success notification appears on successful blocking") - print("- IP is marked as 'Blocked' in the interface") - print("- Security analysis refreshes to update alerts") - print() - - print("Firewall Commands:") - print("- firewalld: firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address= drop'") - print("- firewalld reload: firewall-cmd --reload") - print() - -if __name__ == "__main__": - test_firewall_blocking() diff --git a/test_ssl_integration.py b/test_ssl_integration.py deleted file mode 100644 index 586d4f713..000000000 --- a/test_ssl_integration.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/local/CyberCP/bin/python -""" -Test script for SSL integration -This script tests the SSL reconciliation 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 plogical.sslReconcile import SSLReconcile -from plogical.sslUtilities import sslUtilities -from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging - - -def test_ssl_reconcile_module(): - """Test the SSL reconciliation module""" - print("Testing SSL Reconciliation Module...") - - try: - # Test 1: Check if module can be imported - print("āœ“ SSLReconcile module imported successfully") - - # Test 2: Test utility functions - print("Testing utility functions...") - - # Test trim function - test_text = " test text " - trimmed = SSLReconcile.trim(test_text) - assert trimmed == "test text", f"Trim failed: '{trimmed}'" - print("āœ“ trim() function works correctly") - - # Test 3: Test certificate fingerprint function - print("Testing certificate functions...") - - # Test with non-existent file - fp = SSLReconcile.sha256fp("/nonexistent/file.pem") - assert fp == "", f"Expected empty string for non-existent file, got: '{fp}'" - print("āœ“ sha256fp() handles non-existent files correctly") - - # Test issuer CN function - issuer = SSLReconcile.issuer_cn("/nonexistent/file.pem") - assert issuer == "", f"Expected empty string for non-existent file, got: '{issuer}'" - print("āœ“ issuer_cn() handles non-existent files correctly") - - print("āœ“ All utility functions working correctly") - - return True - - except Exception as e: - print(f"āœ— SSL reconciliation module test failed: {str(e)}") - return False - - -def test_ssl_utilities_integration(): - """Test the enhanced SSL utilities""" - print("\nTesting Enhanced SSL Utilities...") - - try: - # Test 1: Check if new methods exist - assert hasattr(sslUtilities, 'reconcile_ssl_all'), "reconcile_ssl_all method not found" - assert hasattr(sslUtilities, 'reconcile_ssl_domain'), "reconcile_ssl_domain method not found" - assert hasattr(sslUtilities, 'fix_acme_challenge_context'), "fix_acme_challenge_context method not found" - print("āœ“ All new SSL utility methods found") - - # Test 2: Test method signatures - import inspect - - # Check reconcile_ssl_all signature - sig = inspect.signature(sslUtilities.reconcile_ssl_all) - assert len(sig.parameters) == 0, f"reconcile_ssl_all should have no parameters, got: {sig.parameters}" - print("āœ“ reconcile_ssl_all signature correct") - - # Check reconcile_ssl_domain signature - sig = inspect.signature(sslUtilities.reconcile_ssl_domain) - assert 'domain' in sig.parameters, f"reconcile_ssl_domain should have 'domain' parameter, got: {sig.parameters}" - print("āœ“ reconcile_ssl_domain signature correct") - - # Check fix_acme_challenge_context signature - sig = inspect.signature(sslUtilities.fix_acme_challenge_context) - assert 'virtualHostName' in sig.parameters, f"fix_acme_challenge_context should have 'virtualHostName' parameter, got: {sig.parameters}" - print("āœ“ fix_acme_challenge_context signature correct") - - print("āœ“ All SSL utility method signatures correct") - - return True - - except Exception as e: - print(f"āœ— SSL utilities integration test failed: {str(e)}") - return False - - -def test_vhost_configuration_fixes(): - """Test that vhost configuration fixes are applied""" - print("\nTesting VHost Configuration Fixes...") - - try: - from plogical.vhostConfs import vhostConfs - - # Test 1: Check that ACME challenge contexts use $VH_ROOT - ols_master_conf = vhostConfs.olsMasterConf - assert '$VH_ROOT/public_html/.well-known/acme-challenge' in ols_master_conf, "ACME challenge context not fixed in olsMasterConf" - print("āœ“ olsMasterConf ACME challenge context fixed") - - # Test 2: Check child configuration - ols_child_conf = vhostConfs.olsChildConf - assert '$VH_ROOT/public_html/.well-known/acme-challenge' in ols_child_conf, "ACME challenge context not fixed in olsChildConf" - print("āœ“ olsChildConf ACME challenge context fixed") - - # Test 3: Check Apache configurations - apache_conf = vhostConfs.apacheConf - assert '/home/{virtualHostName}/public_html/.well-known/acme-challenge' in apache_conf, "Apache ACME challenge alias not fixed" - print("āœ“ Apache ACME challenge alias fixed") - - print("āœ“ All vhost configuration fixes applied correctly") - - return True - - except Exception as e: - print(f"āœ— VHost configuration fixes test failed: {str(e)}") - return False - - -def test_management_command(): - """Test the Django management command""" - print("\nTesting Django Management Command...") - - try: - import subprocess - - # Test 1: Check if management command exists - result = subprocess.run([ - 'python', 'manage.py', 'ssl_reconcile', '--help' - ], capture_output=True, text=True, cwd='/usr/local/CyberCP') - - if result.returncode == 0: - print("āœ“ SSL reconcile management command exists and responds to --help") - else: - print(f"āœ— SSL reconcile management command failed: {result.stderr}") - return False - - # Test 2: Check command options - help_output = result.stdout - assert '--all' in help_output, "--all option not found in help" - assert '--domain' in help_output, "--domain option not found in help" - assert '--fix-acme' in help_output, "--fix-acme option not found in help" - print("āœ“ All management command options present") - - print("āœ“ Django management command working correctly") - - return True - - except Exception as e: - print(f"āœ— Django management command test failed: {str(e)}") - return False - - -def test_cron_integration(): - """Test that cron integration is properly configured""" - print("\nTesting Cron Integration...") - - try: - # Check if cron file exists and contains SSL reconciliation - cron_paths = [ - '/var/spool/cron/crontabs/root', - '/etc/crontab' - ] - - ssl_reconcile_found = False - - for cron_path in cron_paths: - if os.path.exists(cron_path): - with open(cron_path, 'r') as f: - content = f.read() - if 'ssl_reconcile --all' in content: - ssl_reconcile_found = True - print(f"āœ“ SSL reconciliation cron job found in {cron_path}") - break - - if not ssl_reconcile_found: - print("āœ— SSL reconciliation cron job not found in any cron file") - return False - - print("āœ“ Cron integration working correctly") - - return True - - except Exception as e: - print(f"āœ— Cron integration test failed: {str(e)}") - return False - - -def main(): - """Run all tests""" - print("=" * 60) - print("SSL Integration Test Suite") - print("=" * 60) - - tests = [ - test_ssl_reconcile_module, - test_ssl_utilities_integration, - test_vhost_configuration_fixes, - test_management_command, - test_cron_integration - ] - - passed = 0 - total = len(tests) - - for test in tests: - try: - if test(): - passed += 1 - except Exception as e: - print(f"āœ— Test {test.__name__} failed with exception: {str(e)}") - - print("\n" + "=" * 60) - print(f"Test Results: {passed}/{total} tests passed") - print("=" * 60) - - if passed == total: - print("šŸŽ‰ All tests passed! SSL integration is working correctly.") - return True - else: - print("āŒ Some tests failed. Please check the output above.") - return False - - -if __name__ == "__main__": - success = main() - sys.exit(0 if success else 1) diff --git a/test_subdomain_log_fix.py b/test_subdomain_log_fix.py deleted file mode 100644 index 37b67d501..000000000 --- a/test_subdomain_log_fix.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/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/test_version_fetcher.py b/test_version_fetcher.py deleted file mode 100644 index 2b9a1eb53..000000000 --- a/test_version_fetcher.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 - -""" -Test script for the dynamic version fetcher -""" - -import sys -import os - -# Add the plogical directory to the path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'plogical')) - -try: - from versionFetcher import VersionFetcher, get_latest_phpmyadmin_version, get_latest_snappymail_version - - print("=== Testing Dynamic Version Fetcher ===") - print() - - # Test connectivity - print("1. Testing GitHub API connectivity...") - if VersionFetcher.test_connectivity(): - print(" āœ… GitHub API is accessible") - else: - print(" āŒ GitHub API is not accessible") - print() - - # Test phpMyAdmin version fetching - print("2. Testing phpMyAdmin version fetching...") - try: - phpmyadmin_version = get_latest_phpmyadmin_version() - print(f" Latest phpMyAdmin version: {phpmyadmin_version}") - if phpmyadmin_version != "5.2.2": - print(" āœ… Newer version found!") - else: - print(" ā„¹ļø Using fallback version (API may be unavailable)") - except Exception as e: - print(f" āŒ Error: {e}") - print() - - # Test SnappyMail version fetching - print("3. Testing SnappyMail version fetching...") - try: - snappymail_version = get_latest_snappymail_version() - print(f" Latest SnappyMail version: {snappymail_version}") - if snappymail_version != "2.38.2": - print(" āœ… Newer version found!") - else: - print(" ā„¹ļø Using fallback version (API may be unavailable)") - except Exception as e: - print(f" āŒ Error: {e}") - print() - - # Test all versions - print("4. Testing all versions...") - try: - all_versions = VersionFetcher.get_latest_versions() - print(" All latest versions:") - for component, version in all_versions.items(): - print(f" {component}: {version}") - except Exception as e: - print(f" āŒ Error: {e}") - print() - - print("=== Test Complete ===") - -except ImportError as e: - print(f"āŒ Import error: {e}") - print("Make sure you're running this from the cyberpanel directory") -except Exception as e: - print(f"āŒ Unexpected error: {e}")