Merge pull request #1503 from master3395/v2.5.5-dev

V2.5.5 dev - Add Debian 13 support
This commit is contained in:
Usman Nasir
2025-09-16 14:31:14 +05:00
committed by GitHub
39 changed files with 2571 additions and 8979 deletions

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
"""
phpMyAdmin Access Control Middleware
This middleware checks if users are trying to access phpMyAdmin directly
without being logged into CyberPanel and redirects them to the login page.
"""
from django.shortcuts import redirect
from django.http import HttpResponseRedirect
from django.urls import reverse
class PhpMyAdminAccessMiddleware:
"""
Middleware to control phpMyAdmin access and redirect unauthenticated users to login page.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Check if the request is for phpMyAdmin
if request.path.startswith('/phpmyadmin/'):
# Check if user is authenticated (has session)
if 'userID' not in request.session:
# Redirect to CyberPanel login page
login_url = '/base/'
return HttpResponseRedirect(login_url)
response = self.get_response(request)
return response

View File

@@ -87,7 +87,8 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'CyberCP.secMiddleware.secMiddleware'
'CyberCP.secMiddleware.secMiddleware',
'CyberCP.phpmyadminMiddleware.PhpMyAdminAccessMiddleware'
]
ROOT_URLCONF = 'CyberCP.urls'
@@ -106,6 +107,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages',
'baseTemplate.context_processors.version_context',
'baseTemplate.context_processors.cosmetic_context',
'baseTemplate.context_processors.notification_preferences_context',
],
},
},

View File

@@ -30,6 +30,7 @@ CyberPanel comes with comprehensive documentation and step-by-step guides:
- 🐳 **[Docker Command Execution](guides/Docker_Command_Execution_Guide.md)** - Execute commands in Docker containers
- 🤖 **[AI Scanner Setup](guides/AIScannerDocs.md)** - Configure AI-powered security scanning
- 📧 **[Mautic Installation](guides/MAUTIC_INSTALLATION_GUIDE.md)** - Email marketing platform setup
- 🎨 **[Custom CSS Guide](guides/CUSTOM_CSS_GUIDE.md)** - Create custom themes for CyberPanel 2.5.5-dev
---
@@ -39,9 +40,9 @@ CyberPanel supports a wide range of PHP versions across different operating syst
### ☑️ **Currently Supported PHP Versions**
- **PHP 8.5** - Latest stable version (EOL: Dec 2028)
- **PHP 8.5** - Latest stable version (EOL: Dec 2028)**NEW!**
- **PHP 8.4** - Stable version (EOL: Dec 2027)
- **PHP 8.3** - Stable version (EOL: Dec 2027)
- **PHP 8.3** - **Default version** - Stable version (EOL: Dec 2027) 🎯
- **PHP 8.2** - Stable version (EOL: Dec 2026)
- **PHP 8.1** - Stable version (EOL: Dec 2025)
- **PHP 8.0** - Legacy support (EOL: Nov 2023)
@@ -79,6 +80,9 @@ CyberPanel runs on x86_64 architecture and supports the following operating syst
- **Ubuntu 24.04.3** - Supported until April 2029 ⭐ **NEW!**
- **Ubuntu 22.04** - Supported until April 2027
- **Ubuntu 20.04** - Supported until April 2025
- **Debian 13** - Supported until 2029 ⭐ **NEW!**
- **Debian 12** - Supported until 2027
- **Debian 11** - Supported until 2026
- **AlmaLinux 10** - Supported until May 2030 ⭐ **NEW!**
- **AlmaLinux 9** - Supported until May 2032
- **AlmaLinux 8** - Supported until May 2029
@@ -93,7 +97,6 @@ CyberPanel runs on x86_64 architecture and supports the following operating syst
Additional operating systems may be supported through third-party repositories or community efforts:
- **Debian** - May work with Ubuntu-compatible packages
- **openEuler** - Community-supported with limited testing
- **Other RHEL derivatives** - May work with AlmaLinux/RockyLinux packages
@@ -109,7 +112,6 @@ Install CyberPanel easily with the following command:
sh <(curl https://cyberpanel.net/install.sh || wget -O - https://cyberpanel.net/install.sh)
```
---
## 📊 Upgrading CyberPanel
@@ -124,17 +126,20 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr
## 🆕 Recent Updates & Fixes
### **Bandwidth Reset Issue Fixed** (January 2025)
### **Bandwidth Reset Issue Fixed** (September 2025)
- **Issue**: Monthly bandwidth usage was not resetting, causing cumulative values to grow indefinitely
- **Solution**: Implemented automatic monthly bandwidth reset for all websites and child domains
- **Affected OS**: All supported operating systems (Ubuntu, AlmaLinux, RockyLinux, RHEL, CloudLinux, CentOS)
- **Manual Reset**: Use `/usr/local/CyberCP/scripts/reset_bandwidth.sh` for immediate reset
- **Documentation**: See [Bandwidth Reset Fix Guide](to-do/cyberpanel-bandwidth-reset-fix.md)
### **New Operating System Support Added** (January 2025)
### **New Operating System Support Added** (September 2025)
- **Ubuntu 24.04.3**: Full compatibility with latest Ubuntu LTS
- **Debian 13**: Full compatibility with latest Debian stable release
- **AlmaLinux 10**: Full compatibility with latest AlmaLinux release
- **Long-term Support**: Both supported until 2029-2030
- **Long-term Support**: All supported until 2029-2030
---
@@ -157,15 +162,17 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr
- 🐳 [Docker Command Execution](guides/Docker_Command_Execution_Guide.md) - Execute commands in Docker containers
- 🤖 [AI Scanner Setup](guides/AIScannerDocs.md) - Configure AI-powered security scanning
- 📧 [Mautic Installation](guides/MAUTIC_INSTALLATION_GUIDE.md) - Email marketing platform setup
- 🎨 [Custom CSS Guide](guides/CUSTOM_CSS_GUIDE.md) - Create custom themes for CyberPanel 2.5.5+
- 📚 [All Guides Index](guides/INDEX.md) - Complete documentation hub
### 🔗 **Direct Guide Links**
| Feature | Guide | Description |
| ----------- | ---------------------------------------------------------- | ------------------------------ |
| ------------ | ---------------------------------------------------------- | ---------------------------------- |
| 🐳 Docker | [Command Execution](guides/Docker_Command_Execution_Guide.md) | Execute commands in containers |
| 🤖 Security | [AI Scanner](guides/AIScannerDocs.md) | AI-powered security scanning |
| 📧 Email | [Mautic Setup](guides/MAUTIC_INSTALLATION_GUIDE.md) | Email marketing platform |
| 🎨 Design | [Custom CSS Guide](guides/CUSTOM_CSS_GUIDE.md) | Create custom themes for 2.5.5-dev |
| 📊 Bandwidth | [Reset Fix Guide](to-do/cyberpanel-bandwidth-reset-fix.md) | Fix bandwidth reset issues |
| 📚 All | [Complete Index](guides/INDEX.md) | Browse all available guides |
@@ -176,12 +183,13 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr
### **Common Issues & Solutions**
#### **Bandwidth Not Resetting Monthly**
- **Issue**: Bandwidth usage shows cumulative values instead of monthly usage
- **Solution**: Run the bandwidth reset script: `/usr/local/CyberCP/scripts/reset_bandwidth.sh`
- **Prevention**: Ensure monthly cron job is running: `0 0 1 * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/postfixSenderPolicy/client.py monthlyCleanup`
#### **General Support**
- Check logs: `/usr/local/lscp/logs/error.log`
- Verify cron jobs: `crontab -l`
- Test manual reset: Use provided scripts in `/usr/local/CyberCP/scripts/`

View File

@@ -6,3 +6,6 @@ from django.apps import AppConfig
class BasetemplateConfig(AppConfig):
name = 'baseTemplate'
def ready(self):
import baseTemplate.signals

View File

@@ -24,3 +24,29 @@ def cosmetic_context(request):
return {
'cosmetic': cosmetic
}
def notification_preferences_context(request):
"""Add user notification preferences to all templates"""
try:
if 'userID' in request.session:
from .models import UserNotificationPreferences
from loginSystem.models import Administrator
user = Administrator.objects.get(pk=request.session['userID'])
try:
preferences = UserNotificationPreferences.objects.get(user=user)
return {
'backup_notification_dismissed': preferences.backup_notification_dismissed,
'ai_scanner_notification_dismissed': preferences.ai_scanner_notification_dismissed
}
except UserNotificationPreferences.DoesNotExist:
return {
'backup_notification_dismissed': False,
'ai_scanner_notification_dismissed': False
}
except:
pass
return {
'backup_notification_dismissed': False,
'ai_scanner_notification_dismissed': False
}

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2024-01-01 00:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('baseTemplate', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='UserNotificationPreferences',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('backup_notification_dismissed', models.BooleanField(default=False, help_text='Whether user has dismissed the backup notification')),
('ai_scanner_notification_dismissed', models.BooleanField(default=False, help_text='Whether user has dismissed the AI scanner notification')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='notification_preferences', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'User Notification Preferences',
'verbose_name_plural': 'User Notification Preferences',
},
),
]

View File

@@ -2,6 +2,7 @@
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
@@ -12,3 +13,18 @@ class version(models.Model):
class CyberPanelCosmetic(models.Model):
MainDashboardCSS = models.TextField(default='')
class UserNotificationPreferences(models.Model):
"""Model to store user notification dismissal preferences"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='notification_preferences')
backup_notification_dismissed = models.BooleanField(default=False, help_text="Whether user has dismissed the backup notification")
ai_scanner_notification_dismissed = models.BooleanField(default=False, help_text="Whether user has dismissed the AI scanner notification")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "User Notification Preferences"
verbose_name_plural = "User Notification Preferences"
def __str__(self):
return f"Notification Preferences for {self.user.username}"

23
baseTemplate/signals.py Normal file
View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserNotificationPreferences
@receiver(post_save, sender=User)
def create_user_notification_preferences(sender, instance, created, **kwargs):
"""Create default notification preferences when a new user is created"""
if created:
UserNotificationPreferences.objects.create(
user=instance,
backup_notification_dismissed=False,
ai_scanner_notification_dismissed=False
)
@receiver(post_save, sender=User)
def save_user_notification_preferences(sender, instance, **kwargs):
"""Save notification preferences when user is saved"""
if hasattr(instance, 'notification_preferences'):
instance.notification_preferences.save()

View File

@@ -1841,10 +1841,10 @@
// Backup notification banner logic
function checkBackupStatus() {
// Check if user has dismissed the notification in this session
if (sessionStorage.getItem('backupNotificationDismissed') === 'true') {
return;
}
// Check if user has dismissed the notification permanently (from server-side context)
{% if backup_notification_dismissed %}
return; // Notification already dismissed permanently
{% endif %}
// Check if user has backup configured (you'll need to implement this API)
// For now, we'll show it by default unless they have a backup plan
@@ -1871,16 +1871,34 @@
const body = document.body;
banner.classList.remove('show');
body.classList.remove('notification-shown');
// Remember dismissal for this session
sessionStorage.setItem('backupNotificationDismissed', 'true');
// Dismiss permanently via API
fetch('/base/dismiss_backup_notification', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
}
})
.then(response => response.json())
.then(data => {
if (data.status === 1) {
console.log('Backup notification dismissed permanently');
} else {
console.error('Failed to dismiss backup notification:', data.error);
}
})
.catch(error => {
console.error('Error dismissing backup notification:', error);
});
}
// AI Scanner Notification Functions
function checkAIScannerStatus() {
// Check if user has dismissed the notification in this session
if (sessionStorage.getItem('aiScannerNotificationDismissed') === 'true') {
return;
}
// Check if user has dismissed the notification permanently (from server-side context)
{% if ai_scanner_notification_dismissed %}
return; // Notification already dismissed permanently
{% endif %}
// Check if we're not already on the AI Scanner page
if (!window.location.href.includes('aiscanner')) {
@@ -1900,8 +1918,26 @@
const body = document.body;
banner.classList.remove('show');
body.classList.remove('ai-scanner-shown');
// Remember dismissal for this session
sessionStorage.setItem('aiScannerNotificationDismissed', 'true');
// Dismiss permanently via API
fetch('/base/dismiss_ai_scanner_notification', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
}
})
.then(response => response.json())
.then(data => {
if (data.status === 1) {
console.log('AI scanner notification dismissed permanently');
} else {
console.error('Failed to dismiss AI scanner notification:', data.error);
}
})
.catch(error => {
console.error('Error dismissing AI scanner notification:', error);
});
}
// Check both notification statuses when page loads

View File

@@ -24,4 +24,7 @@ urlpatterns = [
re_path(r'^getSSHUserActivity$', views.getSSHUserActivity, name='getSSHUserActivity'),
re_path(r'^getTopProcesses$', views.getTopProcesses, name='getTopProcesses'),
re_path(r'^analyzeSSHSecurity$', views.analyzeSSHSecurity, name='analyzeSSHSecurity'),
re_path(r'^dismiss_backup_notification$', views.dismiss_backup_notification, name='dismiss_backup_notification'),
re_path(r'^dismiss_ai_scanner_notification$', views.dismiss_ai_scanner_notification, name='dismiss_ai_scanner_notification'),
re_path(r'^get_notification_preferences$', views.get_notification_preferences, name='get_notification_preferences'),
]

View File

@@ -6,7 +6,7 @@ from django.http import HttpResponse
from plogical.getSystemInformation import SystemInformation
import json
from loginSystem.views import loadLoginPage
from .models import version
from .models import version, UserNotificationPreferences
import requests
import subprocess
import shlex
@@ -1305,3 +1305,88 @@ def getTopProcesses(request):
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_POST
def dismiss_backup_notification(request):
"""API endpoint to permanently dismiss the backup notification for the current user"""
try:
user_id = request.session.get('userID')
if not user_id:
return HttpResponse(json.dumps({'status': 0, 'error': 'Not logged in'}), content_type='application/json', status=403)
# Get or create user notification preferences
user = Administrator.objects.get(pk=user_id)
preferences, created = UserNotificationPreferences.objects.get_or_create(
user=user,
defaults={
'backup_notification_dismissed': False,
'ai_scanner_notification_dismissed': False
}
)
# Mark backup notification as dismissed
preferences.backup_notification_dismissed = True
preferences.save()
return HttpResponse(json.dumps({'status': 1, 'message': 'Backup notification dismissed permanently'}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_POST
def dismiss_ai_scanner_notification(request):
"""API endpoint to permanently dismiss the AI scanner notification for the current user"""
try:
user_id = request.session.get('userID')
if not user_id:
return HttpResponse(json.dumps({'status': 0, 'error': 'Not logged in'}), content_type='application/json', status=403)
# Get or create user notification preferences
user = Administrator.objects.get(pk=user_id)
preferences, created = UserNotificationPreferences.objects.get_or_create(
user=user,
defaults={
'backup_notification_dismissed': False,
'ai_scanner_notification_dismissed': False
}
)
# Mark AI scanner notification as dismissed
preferences.ai_scanner_notification_dismissed = True
preferences.save()
return HttpResponse(json.dumps({'status': 1, 'message': 'AI scanner notification dismissed permanently'}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_GET
def get_notification_preferences(request):
"""API endpoint to get current user's notification preferences"""
try:
user_id = request.session.get('userID')
if not user_id:
return HttpResponse(json.dumps({'status': 0, 'error': 'Not logged in'}), content_type='application/json', status=403)
# Get user notification preferences
user = Administrator.objects.get(pk=user_id)
try:
preferences = UserNotificationPreferences.objects.get(user=user)
return HttpResponse(json.dumps({
'status': 1,
'backup_notification_dismissed': preferences.backup_notification_dismissed,
'ai_scanner_notification_dismissed': preferences.ai_scanner_notification_dismissed
}), content_type='application/json')
except UserNotificationPreferences.DoesNotExist:
# Return default values if preferences don't exist yet
return HttpResponse(json.dumps({
'status': 1,
'backup_notification_dismissed': False,
'ai_scanner_notification_dismissed': False
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500)

View File

@@ -549,12 +549,14 @@ elif grep -q -E "Rocky Linux" /etc/os-release ; then
Server_OS="RockyLinux"
elif grep -q -E "Ubuntu 18.04|Ubuntu 20.04|Ubuntu 20.10|Ubuntu 22.04|Ubuntu 24.04" /etc/os-release ; then
Server_OS="Ubuntu"
elif grep -q -E "Debian GNU/Linux 11|Debian GNU/Linux 12|Debian GNU/Linux 13" /etc/os-release ; then
Server_OS="Debian"
elif grep -q -E "openEuler 20.03|openEuler 22.03" /etc/os-release ; then
Server_OS="openEuler"
else
echo -e "Unable to detect your system..."
echo -e "\nCyberPanel is supported on x86_64 based Ubuntu 18.04, Ubuntu 20.04, Ubuntu 20.10, Ubuntu 22.04, Ubuntu 24.04, Ubuntu 24.04.3, CentOS 7, CentOS 8, CentOS 9, RHEL 8, RHEL 9, AlmaLinux 8, AlmaLinux 9, AlmaLinux 10, RockyLinux 8, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03...\n"
Debug_Log2 "CyberPanel is supported on x86_64 based Ubuntu 18.04, Ubuntu 20.04, Ubuntu 20.10, Ubuntu 22.04, Ubuntu 24.04, Ubuntu 24.04.3, CentOS 7, CentOS 8, CentOS 9, RHEL 8, RHEL 9, AlmaLinux 8, AlmaLinux 9, AlmaLinux 10, RockyLinux 8, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03... [404]"
echo -e "\nCyberPanel is supported on x86_64 based Ubuntu 18.04, Ubuntu 20.04, Ubuntu 20.10, Ubuntu 22.04, Ubuntu 24.04, Ubuntu 24.04.3, Debian 11, Debian 12, Debian 13, CentOS 7, CentOS 8, CentOS 9, RHEL 8, RHEL 9, AlmaLinux 8, AlmaLinux 9, AlmaLinux 10, RockyLinux 8, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03...\n"
Debug_Log2 "CyberPanel is supported on x86_64 based Ubuntu 18.04, Ubuntu 20.04, Ubuntu 20.10, Ubuntu 22.04, Ubuntu 24.04, Ubuntu 24.04.3, Debian 11, Debian 12, Debian 13, CentOS 7, CentOS 8, CentOS 9, RHEL 8, RHEL 9, AlmaLinux 8, AlmaLinux 9, AlmaLinux 10, RockyLinux 8, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03... [404]"
exit
fi
@@ -568,6 +570,9 @@ if [[ $Server_OS = "CloudLinux" ]] || [[ "$Server_OS" = "AlmaLinux" ]] || [[ "$S
Server_OS="CentOS"
#CloudLinux gives version id like 7.8, 7.9, so cut it to show first number only
#treat CloudLinux, Rocky and Alma as CentOS
elif [[ "$Server_OS" = "Debian" ]] ; then
Server_OS="Ubuntu"
#Treat Debian as Ubuntu for package management (both use apt-get)
fi
if [[ "$Debug" = "On" ]] ; then
@@ -2278,7 +2283,7 @@ echo "echo \$@ > /etc/cyberpanel/adminPass" >> /usr/bin/adminPass
chmod 700 /usr/bin/adminPass
rm -f /usr/bin/php
ln -s /usr/local/lsws/lsphp80/bin/php /usr/bin/php
ln -s /usr/local/lsws/lsphp83/bin/php /usr/bin/php
if [[ "$Server_OS" = "CentOS" ]] ; then
#all centos 7/8 post change goes here

View File

@@ -0,0 +1,54 @@
#!/bin/bash
# CyberPanel phpMyAdmin Access Control Deployment Script
# This script implements redirect functionality for unauthenticated phpMyAdmin access
echo "=== CyberPanel phpMyAdmin Access Control Deployment ==="
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run this script as root"
exit 1
fi
# Backup original phpMyAdmin index.php if it exists
if [ -f "/usr/local/CyberCP/public/phpmyadmin/index.php" ]; then
echo "Backing up original phpMyAdmin index.php..."
cp /usr/local/CyberCP/public/phpmyadmin/index.php /usr/local/CyberCP/public/phpmyadmin/index.php.backup.$(date +%Y%m%d_%H%M%S)
fi
# Deploy the redirect index.php
echo "Deploying phpMyAdmin access control..."
cp /usr/local/CyberCP/phpmyadmin_index_redirect.php /usr/local/CyberCP/public/phpmyadmin/index.php
# Deploy .htaccess for additional protection
echo "Deploying .htaccess protection..."
cp /usr/local/CyberCP/phpmyadmin_htaccess /usr/local/CyberCP/public/phpmyadmin/.htaccess
# Set proper permissions
echo "Setting permissions..."
chown lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin/index.php
chmod 644 /usr/local/CyberCP/public/phpmyadmin/index.php
chown lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin/.htaccess
chmod 644 /usr/local/CyberCP/public/phpmyadmin/.htaccess
# Restart LiteSpeed to ensure changes take effect
echo "Restarting LiteSpeed..."
systemctl restart lscpd
echo "=== Deployment Complete ==="
echo ""
echo "phpMyAdmin access control has been deployed successfully!"
echo ""
echo "What this does:"
echo "- Users trying to access phpMyAdmin directly without being logged into CyberPanel"
echo " will now be redirected to the CyberPanel login page (/base/)"
echo "- Authenticated users will continue to access phpMyAdmin normally"
echo ""
echo "To revert changes, restore the backup:"
echo "cp /usr/local/CyberCP/public/phpmyadmin/index.php.backup.* /usr/local/CyberCP/public/phpmyadmin/index.php"
echo ""
echo "Test the implementation by:"
echo "1. Opening an incognito/private browser window"
echo "2. Going to https://your-server:2087/phpmyadmin/"
echo "3. You should be redirected to the CyberPanel login page"

View File

@@ -323,17 +323,26 @@ class ContainerManager(multi.Thread):
if 'ExposedPorts' in inspectImage['Config']:
for item in inspectImage['Config']['ExposedPorts']:
# Check if port data exists and is valid
if item in data and data[item]:
try:
port_num = int(data[item])
# Do not allow priviledged port numbers
if int(data[item]) < 1024 or int(data[item]) > 65535:
if port_num < 1024 or port_num > 65535:
data_ret = {'createContainerStatus': 0, 'error_message': "Choose port between 1024 and 65535"}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
portConfig[item] = data[item]
except (ValueError, TypeError):
data_ret = {'createContainerStatus': 0, 'error_message': f"Invalid port number: {data[item]}"}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
volumes = {}
if volList:
for index, volume in volList.items():
volumes[volume['src']] = {'bind': volume['dest'],
'mode': 'rw'}
if isinstance(volume, dict) and 'src' in volume and 'dest' in volume:
volumes[volume['src']] = {'bind': volume['dest'], 'mode': 'rw'}
## Create Configurations
admin = Administrator.objects.get(userName=dockerOwner)
@@ -351,10 +360,15 @@ class ContainerManager(multi.Thread):
try:
container = client.containers.create(**containerArgs)
except Exception as err:
if "port is already allocated" in err: # We need to delete container if port is not available
# Check if it's a port allocation error by converting to string first
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")
try:
container.remove(force=True)
data_ret = {'createContainerStatus': 0, 'error_message': str(err)}
except:
pass # Container might not exist yet
data_ret = {'createContainerStatus': 0, 'error_message': error_message}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -376,7 +390,9 @@ class ContainerManager(multi.Thread):
except Exception as msg:
data_ret = {'createContainerStatus': 0, 'error_message': str(msg)}
# Ensure error message is properly converted to string
error_message = str(msg) if msg else "Unknown error occurred"
data_ret = {'createContainerStatus': 0, 'error_message': error_message}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)

View File

@@ -273,6 +273,54 @@ 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() {
// Get container information from form
@@ -1434,101 +1482,6 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) {
});
};
// 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;
}
// Helper function to generate Docker Compose YAML (for runContainer)
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;
}
$scope.showTop = function () {
$scope.topHead = [];

14
faq.sh
View File

@@ -28,7 +28,7 @@ ${BLUE}------------------------------------------------------------${NC}
${PURPLE}3.${NC} How to access LiteSpeed webadmin console ?
Please check this post: ${GREEN}https://forums.cyberpanel.net/discussion/87/tutorial-how-to-setup-and-login-to-openlitespeed-webadmin-console/p1${NC}
Please check this post: ${GREEN}https://community.cyberpanel.net/c/support/55${NC}
${BLUE}------------------------------------------------------------${NC}
@@ -52,9 +52,9 @@ ${BLUE}------------------------------------------------------------${NC}
${PURPLE}6.${NC} How to raise upload limit for cyberpanel's phpMyAdmin and File Manager?
edit file ${RED}/usr/local/lsws/lsphp73/etc/php.ini${NC} for CentOS or openEuler
edit file ${RED}/usr/local/lsws/lsphp83/etc/php.ini${NC} for CentOS or openEuler
${RED}/usr/local/lsws/lsphp73/etc/php/7.3/litespeed/php.ini${NC} for Ubbuntu
${RED}/usr/local/lsws/lsphp83/etc/php/8.3/litespeed/php.ini${NC} for Ubuntu
find 2 configurations:
@@ -66,9 +66,9 @@ ${BLUE}------------------------------------------------------------${NC}
${PURPLE}7.${NC} How to add more IPs to my website(s) ?
For OpenLiteSpeed, please check this post: ${GREEN}https://forums.cyberpanel.net/discussion/126/tutorial-how-to-add-2nd-ip-for-websites/p1${NC}
For OpenLiteSpeed, please check this post: ${GREEN}https://community.cyberpanel.net/c/support/55${NC}
For LiteSpeed Enterprise, please check this post: ${GREEN}https://forums.cyberpanel.net/discussion/3745/tutorial-how-to-add-2nd-ip-for-litespeed-enterprise/p1${NC}
For LiteSpeed Enterprise, please check this post: ${GREEN}https://community.cyberpanel.net/c/support/55${NC}
${BLUE}------------------------------------------------------------${NC}
@@ -80,7 +80,7 @@ ${BLUE}------------------------------------------------------------${NC}
${PURPLE}9.${NC} How to enable Auto-Index for my site ?
Please check this post ${GREEN}https://forums.cyberpanel.net/discussion/3850/tutorial-how-to-enable-auto-index-on-openlitespeed-and-litespeed-enterprise${NC}
Please check this post ${GREEN}https://community.cyberpanel.net/c/support/55${NC}
${BLUE}------------------------------------------------------------${NC}
@@ -111,5 +111,5 @@ ${BLUE}------------------------------------------------------------${NC}
${PURPLE}13.${NC} How to enable PHP error log ?
Please check this post ${GREEN}https://forums.cyberpanel.net/discussion/3977/tutorial-how-to-enable-php-error-log/p1${NC}
Please check this post ${GREEN}https://community.cyberpanel.net/c/support/55${NC}
"

View File

@@ -60,6 +60,30 @@ class FirewallManager:
rules = FirewallRules.objects.all()
# Ensure CyberPanel port 7080 rule exists in database for visibility
cyberpanel_rule_exists = False
for rule in rules:
if rule.port == '7080':
cyberpanel_rule_exists = True
break
if not cyberpanel_rule_exists:
# Create database entry for port 7080 (already enabled in system firewall)
try:
cyberpanel_rule = FirewallRules(
name="CyberPanel Admin",
proto="tcp",
port="7080",
ipAddress="0.0.0.0/0"
)
cyberpanel_rule.save()
logging.CyberCPLogFileWriter.writeToFile("Added CyberPanel port 7080 to firewall database for UI visibility")
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Failed to add CyberPanel port 7080 to database: {str(e)}")
# Refresh rules after potential creation
rules = FirewallRules.objects.all()
json_data = "["
checker = 0
@@ -96,7 +120,6 @@ class FirewallManager:
else:
return ACLManager.loadErrorJson('add_status', 0)
ruleName = data['ruleName']
ruleProtocol = data['ruleProtocol']
rulePort = data['rulePort']
@@ -126,7 +149,6 @@ class FirewallManager:
else:
return ACLManager.loadErrorJson('delete_status', 0)
ruleID = data['id']
ruleProtocol = data['proto']
rulePort = data['port']

829
guides/CUSTOM_CSS_GUIDE.md Normal file
View File

@@ -0,0 +1,829 @@
# CyberPanel 2.5.5-dev Custom CSS Guide
A comprehensive guide for creating custom CSS that fully works with the new design system of CyberPanel 2.5.5-dev.
## Table of Contents
1. [Overview](#overview)
2. [Design System Architecture](#design-system-architecture)
3. [CSS Variables Reference](#css-variables-reference)
4. [Component Structure](#component-structure)
5. [Customization Examples](#customization-examples)
6. [Best Practices](#best-practices)
7. [Troubleshooting](#troubleshooting)
8. [Advanced Techniques](#advanced-techniques)
## Overview
CyberPanel 2.5.5-dev features a modern, CSS-variable-based design system that supports both light and dark themes. The system is built with:
- **CSS Custom Properties (Variables)** for consistent theming
- **Modern CSS Grid and Flexbox** layouts
- **Responsive design** principles
- **Dark mode support** with automatic theme switching
- **Component-based architecture** for easy customization
## Design System Architecture
### Core Structure
The design system is built around CSS custom properties defined in `:root` and `[data-theme="dark"]` selectors:
```css
:root {
/* Light Theme Variables */
--bg-primary: #f0f0ff;
--bg-secondary: white;
--text-primary: #2f3640;
--accent-color: #5856d6;
/* ... more variables */
}
[data-theme="dark"] {
/* Dark Theme Variables */
--bg-primary: #0f0f23;
--bg-secondary: #1a1a3e;
--text-primary: #e4e4e7;
--accent-color: #7c7ff3;
/* ... more variables */
}
```
### Key Components
1. **Header** (`#header`) - Top navigation bar
2. **Sidebar** (`#sidebar`) - Left navigation panel
3. **Main Content** (`#main-content`) - Page content area
4. **Cards** (`.content-card`) - Content containers
5. **Buttons** (`.btn`) - Interactive elements
6. **Forms** (`.form-*`) - Input elements
## CSS Variables Reference
### Color Variables
#### Background Colors
```css
--bg-primary /* Main background color */
--bg-secondary /* Card/container background */
--bg-sidebar /* Sidebar background */
--bg-sidebar-item /* Sidebar menu item background */
--bg-hover /* Hover state background */
```
#### Text Colors
```css
--text-primary /* Main text color */
--text-secondary /* Secondary text color */
--text-heading /* Heading text color */
```
#### Accent Colors
```css
--accent-color /* Primary accent color */
--accent-hover /* Accent hover state */
--danger-color /* Error/danger color */
--success-color /* Success color */
```
#### Border & Shadow
```css
--border-color /* Default border color */
--shadow-color /* Default shadow color */
```
### Special Variables
#### Gradients
```css
--warning-bg /* Warning banner gradient */
--ai-banner-bg /* AI scanner banner gradient */
```
#### Status Colors
```css
--success-bg /* Success background */
--success-border /* Success border */
--danger-bg /* Danger background */
--danger-border /* Danger border */
--warning-bg /* Warning background */
--info-bg /* Info background */
```
## Component Structure
### Header Component
```css
#header {
background: var(--bg-secondary);
height: 80px;
display: flex;
align-items: center;
padding: 0 30px;
box-shadow: 0 2px 12px var(--shadow-color);
position: fixed;
top: 0;
left: 260px;
right: 0;
z-index: 1000;
}
```
**Customization Example:**
```css
/* Change header height and add custom styling */
#header {
height: 100px;
background: linear-gradient(135deg, var(--accent-color), var(--accent-hover));
border-bottom: 3px solid var(--accent-color);
}
#header .logo-text .brand {
font-size: 32px;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
```
### Sidebar Component
```css
#sidebar {
width: 260px;
background: var(--bg-sidebar);
height: 100vh;
position: fixed;
left: 0;
top: 0;
overflow-y: auto;
z-index: 1001;
}
```
**Customization Example:**
```css
/* Make sidebar wider with custom styling */
#sidebar {
width: 300px;
background: linear-gradient(180deg, var(--bg-sidebar), var(--bg-secondary));
border-right: 2px solid var(--accent-color);
}
#sidebar .menu-item {
margin: 4px 20px;
border-radius: 12px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
#sidebar .menu-item:hover {
transform: translateX(5px);
box-shadow: 0 4px 12px var(--shadow-color);
}
```
### Content Cards
```css
.content-card {
background: var(--bg-secondary);
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px var(--shadow-color);
border: 1px solid var(--border-color);
margin-bottom: 25px;
}
```
**Customization Example:**
```css
/* Add glassmorphism effect to cards */
.content-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
[data-theme="dark"] .content-card {
background: rgba(26, 26, 62, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
```
## Customization Examples
### 1. Complete Theme Override
```css
/* Custom Purple Theme */
:root {
--bg-primary: #f8f4ff;
--bg-secondary: #ffffff;
--bg-sidebar: #f3f0ff;
--bg-hover: #e8e0ff;
--text-primary: #2d1b69;
--text-secondary: #6b46c1;
--accent-color: #8b5cf6;
--accent-hover: #7c3aed;
--border-color: #e0d7ff;
--shadow-color: rgba(139, 92, 246, 0.1);
}
[data-theme="dark"] {
--bg-primary: #1a0b2e;
--bg-secondary: #2d1b69;
--bg-sidebar: #1e0a3e;
--bg-hover: #3d2a7a;
--text-primary: #f3f0ff;
--text-secondary: #c4b5fd;
--accent-color: #a78bfa;
--accent-hover: #8b5cf6;
--border-color: #4c1d95;
--shadow-color: rgba(139, 92, 246, 0.3);
}
```
### 2. Custom Button Styles
```css
/* Custom button variants */
.btn-custom {
background: linear-gradient(135deg, var(--accent-color), var(--accent-hover));
border: none;
border-radius: 20px;
padding: 12px 24px;
color: white;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 4px 15px rgba(139, 92, 246, 0.3);
transition: all 0.3s ease;
}
.btn-custom:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(139, 92, 246, 0.4);
}
.btn-custom:active {
transform: translateY(0);
}
```
### 3. Custom Sidebar Menu Items
```css
/* Animated sidebar menu items */
#sidebar .menu-item {
position: relative;
overflow: hidden;
}
#sidebar .menu-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
#sidebar .menu-item:hover::before {
left: 100%;
}
#sidebar .menu-item .icon-wrapper {
position: relative;
z-index: 1;
}
```
### 4. Custom Form Styling
```css
/* Modern form inputs */
.form-control {
border: 2px solid var(--border-color);
border-radius: 12px;
padding: 12px 16px;
font-size: 14px;
transition: all 0.3s ease;
background: var(--bg-secondary);
}
.form-control:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.1);
transform: translateY(-1px);
}
.form-control::placeholder {
color: var(--text-secondary);
opacity: 0.7;
}
```
### 5. Custom Notifications
```css
/* Custom notification banners */
.notification-banner {
background: linear-gradient(135deg, var(--accent-color), var(--accent-hover));
border-radius: 16px;
padding: 20px;
margin: 20px;
box-shadow: 0 8px 32px rgba(139, 92, 246, 0.3);
position: relative;
overflow: hidden;
}
.notification-banner::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4);
animation: rainbow 3s linear infinite;
}
@keyframes rainbow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
```
## Best Practices
### 1. Use CSS Variables
Always use CSS variables instead of hardcoded values:
```css
/* ✅ Good */
.custom-element {
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
/* ❌ Bad */
.custom-element {
background: white;
color: #2f3640;
border: 1px solid #e8e9ff;
}
```
### 2. Support Both Themes
Always provide both light and dark theme support:
```css
.custom-element {
background: var(--bg-secondary);
color: var(--text-primary);
}
/* Dark theme specific adjustments */
[data-theme="dark"] .custom-element {
/* Additional dark theme styling if needed */
}
```
### 3. Use Semantic Class Names
```css
/* ✅ Good */
.primary-button { }
.content-container { }
.navigation-item { }
/* ❌ Bad */
.red-button { }
.big-box { }
.item1 { }
```
### 4. Maintain Responsive Design
```css
.custom-element {
padding: 20px;
font-size: 16px;
}
@media (max-width: 768px) {
.custom-element {
padding: 15px;
font-size: 14px;
}
}
```
### 5. Use Modern CSS Features
```css
.custom-element {
/* Use CSS Grid for layouts */
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
/* Use Flexbox for alignment */
align-items: center;
justify-content: space-between;
/* Use CSS custom properties for calculations */
--element-height: 60px;
height: var(--element-height);
/* Use modern selectors */
&:hover { }
&:focus-within { }
}
```
## Troubleshooting
### Common Issues
#### 1. Custom CSS Not Applying
**Problem:** Custom CSS doesn't appear to be working.
**Solution:**
- Check CSS specificity - use more specific selectors
- Ensure CSS is placed after the base styles
- Use `!important` sparingly and only when necessary
```css
/* Increase specificity */
#main-content .content-card .custom-element {
background: var(--bg-secondary);
}
```
#### 2. Dark Mode Not Working
**Problem:** Custom styles don't adapt to dark mode.
**Solution:**
- Always use CSS variables
- Test both light and dark themes
- Provide dark mode specific overrides when needed
```css
.custom-element {
background: var(--bg-secondary);
color: var(--text-primary);
}
[data-theme="dark"] .custom-element {
/* Dark mode specific adjustments */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
```
#### 3. Responsive Issues
**Problem:** Custom styles break on mobile devices.
**Solution:**
- Use relative units (rem, em, %)
- Test on different screen sizes
- Use CSS Grid and Flexbox for responsive layouts
```css
.custom-element {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
@media (max-width: 768px) {
.custom-element {
padding: 0.5rem;
}
}
```
## Advanced Techniques
### 1. CSS Animations
```css
/* Smooth page transitions */
.page-transition {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Hover animations */
.interactive-element {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.interactive-element:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 8px 25px var(--shadow-color);
}
```
### 2. CSS Grid Layouts
```css
/* Advanced grid layouts */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
padding: 20px;
}
.dashboard-card {
grid-column: span 1;
background: var(--bg-secondary);
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px var(--shadow-color);
}
.dashboard-card.featured {
grid-column: span 2;
}
@media (max-width: 768px) {
.dashboard-card.featured {
grid-column: span 1;
}
}
```
### 3. CSS Custom Properties with JavaScript
```css
/* Dynamic theming with CSS variables */
:root {
--custom-accent: #5856d6;
--custom-accent-hover: #4644c0;
}
.custom-theme {
--accent-color: var(--custom-accent);
--accent-hover: var(--custom-accent-hover);
}
```
### 4. Advanced Selectors
```css
/* Complex selectors for specific styling */
#sidebar .menu-item:not(.active):hover {
background: var(--bg-hover);
transform: translateX(5px);
}
.content-card > *:first-child {
margin-top: 0;
}
.content-card > *:last-child {
margin-bottom: 0;
}
/* Attribute selectors */
[data-status="success"] {
border-left: 4px solid var(--success-color);
}
[data-status="error"] {
border-left: 4px solid var(--danger-color);
}
```
### 5. CSS Functions and Calculations
```css
/* Using CSS functions */
.responsive-text {
font-size: clamp(14px, 2.5vw, 18px);
line-height: calc(1.5em + 0.5vw);
}
.dynamic-spacing {
padding: calc(1rem + 2vw);
margin: calc(0.5rem + 1vw);
}
/* CSS custom properties with calculations */
:root {
--base-size: 16px;
--scale-factor: 1.2;
--large-size: calc(var(--base-size) * var(--scale-factor));
}
```
## Implementation Guide
### Step 1: Access the Design Page
1. Log into CyberPanel
2. Navigate to **Design** in the sidebar
3. Scroll down to the **Custom CSS** section
### Step 2: Add Your Custom CSS
1. Paste your custom CSS into the textarea
2. Click **Save Changes**
3. Refresh the page to see your changes
### Step 3: Test Your Changes
1. Test in both light and dark modes
2. Test on different screen sizes
3. Verify all interactive elements work correctly
### Step 4: Iterate and Refine
1. Make adjustments as needed
2. Test thoroughly before finalizing
3. Document your customizations
## Example: Complete Custom Theme
Here's a complete example of a custom theme that you can use as a starting point:
```css
/* Custom CyberPanel Theme - Ocean Blue */
/* Light Theme */
:root {
--bg-primary: #f0f9ff;
--bg-secondary: #ffffff;
--bg-sidebar: #e0f2fe;
--bg-sidebar-item: #ffffff;
--bg-hover: #bae6fd;
--text-primary: #0c4a6e;
--text-secondary: #0369a1;
--text-heading: #0c4a6e;
--border-color: #bae6fd;
--shadow-color: rgba(6, 105, 161, 0.1);
--accent-color: #0284c7;
--accent-hover: #0369a1;
--danger-color: #dc2626;
--success-color: #059669;
--warning-bg: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
--ai-banner-bg: linear-gradient(135deg, #0284c7 0%, #0369a1 50%, #0c4a6e 100%);
}
/* Dark Theme */
[data-theme="dark"] {
--bg-primary: #0c4a6e;
--bg-secondary: #075985;
--bg-sidebar: #0c4a6e;
--bg-sidebar-item: #075985;
--bg-hover: #0369a1;
--text-primary: #e0f2fe;
--text-secondary: #bae6fd;
--text-heading: #f0f9ff;
--border-color: #0369a1;
--shadow-color: rgba(0, 0, 0, 0.3);
--accent-color: #38bdf8;
--accent-hover: #0ea5e9;
--danger-color: #f87171;
--success-color: #34d399;
--warning-bg: linear-gradient(135deg, #78350f 0%, #92400e 100%);
--ai-banner-bg: linear-gradient(135deg, #0c4a6e 0%, #075985 50%, #0369a1 100%);
}
/* Custom Header Styling */
#header {
background: linear-gradient(135deg, var(--accent-color), var(--accent-hover));
box-shadow: 0 4px 20px var(--shadow-color);
}
#header .logo-text .brand {
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Custom Sidebar Styling */
#sidebar {
background: linear-gradient(180deg, var(--bg-sidebar), var(--bg-secondary));
border-right: 3px solid var(--accent-color);
}
#sidebar .menu-item {
border-radius: 12px;
margin: 4px 20px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
#sidebar .menu-item:hover {
transform: translateX(8px);
box-shadow: 0 4px 15px var(--shadow-color);
}
#sidebar .menu-item.active {
background: linear-gradient(135deg, var(--accent-color), var(--accent-hover));
box-shadow: 0 4px 15px rgba(2, 132, 199, 0.3);
}
/* Custom Content Cards */
.content-card {
background: var(--bg-secondary);
border-radius: 16px;
box-shadow: 0 4px 20px var(--shadow-color);
border: 1px solid var(--border-color);
position: relative;
overflow: hidden;
}
.content-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent-color), var(--accent-hover));
}
/* Custom Buttons */
.btn {
border-radius: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-primary {
background: linear-gradient(135deg, var(--accent-color), var(--accent-hover));
box-shadow: 0 4px 15px rgba(2, 132, 199, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(2, 132, 199, 0.4);
}
/* Custom Form Elements */
.form-control {
border: 2px solid var(--border-color);
border-radius: 12px;
transition: all 0.3s ease;
}
.form-control:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 4px rgba(2, 132, 199, 0.1);
transform: translateY(-1px);
}
/* Custom Animations */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.content-card {
animation: slideIn 0.5s ease-out;
}
/* Responsive Design */
@media (max-width: 768px) {
#sidebar {
width: 100%;
height: auto;
position: relative;
}
#header {
left: 0;
}
.content-card {
margin: 10px;
padding: 20px;
}
}
```
This guide provides everything you need to create beautiful, functional custom CSS for CyberPanel 2.5.5+. Remember to always test your changes thoroughly and use the CSS variables for consistency across themes.

View File

@@ -0,0 +1,327 @@
# Debian 13 Installation Guide for CyberPanel
## 🎯 Overview
This guide provides step-by-step instructions for installing CyberPanel on Debian 13 (Bookworm). Debian 13 support has been added to CyberPanel with full compatibility for package management, service configuration, and web server setup.
## 📋 Prerequisites
### System Requirements
- **OS**: Debian 13 (Bookworm) x86_64
- **RAM**: Minimum 1GB (2GB+ recommended)
- **Storage**: Minimum 10GB free space (20GB+ recommended)
- **CPU**: 2+ cores recommended
- **Network**: Internet connection required
### Supported Debian Versions
-**Debian 13** (Bookworm) - Full Support
-**Debian 12** (Bookworm) - Full Support
-**Debian 11** (Bullseye) - Full Support
## 🚀 Installation Steps
### Step 1: Update System
```bash
# Update package lists
sudo apt update
# Upgrade system packages
sudo apt upgrade -y
# Install essential packages
sudo apt install -y curl wget git
```
### Step 2: Download and Run CyberPanel Installer
```bash
# Download the latest CyberPanel installer
wget https://cyberpanel.sh/install.sh
# Make the installer executable
chmod +x install.sh
# Run the installer
sudo ./install.sh
```
### Step 3: Follow Installation Prompts
The installer will guide you through:
1. **License Agreement**: Accept the terms
2. **Installation Type**: Choose between:
- OpenLiteSpeed (Free)
- LiteSpeed Enterprise (Requires license)
3. **MySQL Configuration**:
- Single MySQL instance (recommended)
- Double MySQL instance (for high availability)
4. **Additional Services**:
- Postfix/Dovecot (Email server)
- PowerDNS (DNS server)
- PureFTPD (FTP server)
### Step 4: Verify Installation
```bash
# Check CyberPanel service status
sudo systemctl status lscpd
# Check web server status
sudo systemctl status apache2
# Check if CyberPanel is accessible
curl -I http://localhost:8090
```
## 🔧 Post-Installation Configuration
### Access CyberPanel
1. Open your web browser
2. Navigate to: `http://your-server-ip:8090`
3. Default login credentials:
- **Username**: `admin`
- **Password**: `123456` (change immediately!)
### Change Default Password
```bash
# Login to CyberPanel CLI
sudo cyberpanel
# Change admin password
cyberpanel --change-password admin
```
### Configure Firewall
```bash
# Allow CyberPanel ports
sudo ufw allow 8090/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 21/tcp
sudo ufw allow 25/tcp
sudo ufw allow 53/tcp
sudo ufw allow 587/tcp
sudo ufw allow 993/tcp
sudo ufw allow 995/tcp
# Enable firewall
sudo ufw enable
```
## 🐛 Troubleshooting
### Common Issues
#### 1. OS Detection Failed
**Problem**: Installer doesn't recognize Debian 13
**Solution**: Ensure you're running the latest installer version
```bash
# Download latest installer
wget https://cyberpanel.sh/install.sh
chmod +x install.sh
sudo ./install.sh
```
#### 2. Package Installation Failed
**Problem**: apt-get errors during installation
**Solution**: Update repositories and retry
```bash
# Update package lists
sudo apt update
# Fix broken packages
sudo apt --fix-broken install
# Retry installation
sudo ./install.sh
```
#### 3. Service Won't Start
**Problem**: CyberPanel service fails to start
**Solution**: Check logs and restart services
```bash
# Check service status
sudo systemctl status lscpd
# Check logs
sudo journalctl -u lscpd -f
# Restart service
sudo systemctl restart lscpd
```
#### 4. Web Server Issues
**Problem**: Apache2 configuration problems
**Solution**: Reconfigure web server
```bash
# Check Apache2 status
sudo systemctl status apache2
# Test configuration
sudo apache2ctl configtest
# Restart Apache2
sudo systemctl restart apache2
```
### Log Files
Important log locations:
- **CyberPanel**: `/usr/local/CyberCP/logs/`
- **Apache2**: `/var/log/apache2/`
- **System**: `/var/log/syslog`
- **Installation**: `/root/cyberpanel-install.log`
## 🔒 Security Considerations
### Initial Security Setup
1. **Change Default Password**
```bash
sudo cyberpanel --change-password admin
```
2. **Update System**
```bash
sudo apt update && sudo apt upgrade -y
```
3. **Configure Firewall**
```bash
sudo ufw enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
```
4. **Enable Fail2Ban**
```bash
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
```
### SSL Certificate Setup
1. **Access CyberPanel Web Interface**
2. **Navigate to**: SSL → Let's Encrypt
3. **Enter your domain name**
4. **Click "Issue" to get free SSL certificate**
## 📊 Performance Optimization
### System Optimization
```bash
# Optimize Apache2 for Debian
sudo nano /etc/apache2/apache2.conf
# Add these lines:
ServerTokens Prod
ServerSignature Off
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
```
### PHP Optimization
1. **Access CyberPanel Web Interface**
2. **Navigate to**: PHP → PHP Settings
3. **Configure**:
- Memory limit: 256M
- Max execution time: 300
- Upload max filesize: 64M
## 🔄 Updates and Maintenance
### Update CyberPanel
```bash
# Update to latest version
sudo cyberpanel --update
# Or use the upgrade script
sudo ./cyberpanel_upgrade.sh
```
### System Maintenance
```bash
# Update system packages
sudo apt update && sudo apt upgrade -y
# Clean package cache
sudo apt autoremove -y
sudo apt autoclean
# Check disk usage
df -h
# Check memory usage
free -h
```
## 📚 Additional Resources
### Documentation
- [CyberPanel Official Docs](https://cyberpanel.net/docs/)
- [Debian 13 Release Notes](https://www.debian.org/releases/bookworm/releasenotes)
- [Apache2 Configuration Guide](https://httpd.apache.org/docs/2.4/)
### Community Support
- [CyberPanel Community Forum](https://forums.cyberpanel.net/)
- [GitHub Issues](https://github.com/usmannasir/cyberpanel/issues)
- [Discord Server](https://discord.gg/cyberpanel)
### Testing Compatibility
Run the compatibility test script:
```bash
# Download test script
wget https://raw.githubusercontent.com/cyberpanel/cyberpanel/main/test_debian13_support.sh
# Make executable
chmod +x test_debian13_support.sh
# Run test
sudo ./test_debian13_support.sh
```
## ✅ Verification Checklist
After installation, verify these components:
- [ ] CyberPanel web interface accessible
- [ ] Admin password changed
- [ ] SSL certificate installed
- [ ] Firewall configured
- [ ] Email server working (if installed)
- [ ] DNS server working (if installed)
- [ ] FTP server working (if installed)
- [ ] System updates applied
- [ ] Logs are clean
- [ ] Services are running
## 🆘 Getting Help
If you encounter issues:
1. **Check the logs** (see Troubleshooting section)
2. **Run the compatibility test**
3. **Search the documentation**
4. **Ask in the community forum**
5. **Create a GitHub issue** with detailed information
---
**Note**: This guide is specifically for Debian 13. For other operating systems, refer to the main CyberPanel documentation.

View File

@@ -14,6 +14,12 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
### 📧 Email & Marketing
- **[Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)** - Step-by-step guide for installing and configuring Mautic email marketing platform
### 🐧 Operating System Support
- **[Debian 13 Installation Guide](DEBIAN_13_INSTALLATION_GUIDE.md)** - Complete installation and configuration guide for CyberPanel on Debian 13 (Bookworm)
### 🎨 Customization & Design
- **[Custom CSS Guide](CUSTOM_CSS_GUIDE.md)** - Complete guide for creating custom CSS that works with CyberPanel 2.5.5-dev design system
### 📖 General Documentation
- **[README](../README.md)** - Main CyberPanel documentation with installation instructions and feature overview
- **[Contributing Guide](CONTRIBUTING.md)** - Guidelines for contributing to the CyberPanel project
@@ -21,16 +27,20 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
## 🚀 Quick Start
1. **New to CyberPanel?** Start with the [README](../README.md) for installation and basic setup
2. **Need Docker help?** Check the [Docker Command Execution Guide](Docker_Command_Execution_Guide.md)
3. **Setting up email marketing?** Follow the [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)
4. **Want to contribute?** Read the [Contributing Guide](CONTRIBUTING.md)
2. **Installing on Debian 13?** Follow the [Debian 13 Installation Guide](DEBIAN_13_INSTALLATION_GUIDE.md)
3. **Need Docker help?** Check the [Docker Command Execution Guide](Docker_Command_Execution_Guide.md)
4. **Setting up email marketing?** Follow the [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)
5. **Want to customize the interface?** Check the [Custom CSS Guide](CUSTOM_CSS_GUIDE.md)
6. **Want to contribute?** Read the [Contributing Guide](CONTRIBUTING.md)
## 🔍 Finding What You Need
- **Installation & Setup**: [README](../README.md)
- **Debian 13 Installation**: [Debian 13 Installation Guide](DEBIAN_13_INSTALLATION_GUIDE.md)
- **Docker Features**: [Docker Command Execution Guide](Docker_Command_Execution_Guide.md)
- **Security Features**: [AI Scanner Documentation](AIScannerDocs.md)
- **Email Marketing**: [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)
- **Customization & Design**: [Custom CSS Guide](CUSTOM_CSS_GUIDE.md)
- **Development**: [Contributing Guide](CONTRIBUTING.md)
## 📝 Guide Categories
@@ -45,6 +55,11 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
- Third-party applications
- Custom configurations
### 🎨 **Customization**
- Custom CSS theming
- Design system integration
- Interface personalization
### 📖 **Documentation**
- Installation guides
- Configuration tutorials

View File

@@ -1,190 +0,0 @@
import os
import shutil
import pathlib
import stat
def mkdir_p(path, exist_ok=True):
"""
Creates the directory and paths leading up to it like unix mkdir -p .
Defaults to exist_ok so if it exists were not throwing fatal errors
https://docs.python.org/3.7/library/os.html#os.makedirs
"""
if not os.path.exists(path):
print('creating directory: ' + path)
os.makedirs(path, exist_ok)
def chmod_digit(file_path, perms):
"""
Helper function to chmod like you would in unix without having to preface 0o or converting to octal yourself.
Credits: https://stackoverflow.com/a/60052847/1621381
"""
try:
os.chmod(file_path, int(str(perms), base=8))
except:
print(f'Could not chmod : {file_path} to {perms}')
pass
def touch(filepath: str, exist_ok=True):
"""
Touches a file like unix `touch somefile` would.
"""
try:
pathlib.Path(filepath).touch(exist_ok)
except FileExistsError:
print('Could touch : ' + filepath)
pass
def symlink(src, dst):
"""
Symlink a path to another if the src exists.
"""
try:
if os.access(src, os.R_OK):
os.symlink(src, dst)
except:
print(f'Could not symlink Source: {src} > Destination: {dst}')
pass
def chown(path, user, group=-1):
"""
Chown file/path to user/group provided. Passing -1 to user or group will leave it unchanged.
Useful if just changing user or group vs both.
"""
try:
shutil.chown(path, user, group)
except PermissionError:
print(f'Could not change permissions for: {path} to {user}:{group}')
pass
def recursive_chown(path, owner, group=-1):
"""
Recursively chown a path and contents to owner.
https://docs.python.org/3/library/shutil.html
"""
for dirpath, dirnames, filenames in os.walk(path):
try:
shutil.chown(dirpath, owner, group)
except PermissionError:
print('Could not change permissions for: ' + dirpath + ' to: ' + owner)
pass
for filename in filenames:
try:
shutil.chown(os.path.join(dirpath, filename), owner, group)
except PermissionError:
print('Could not change permissions for: ' + os.path.join(dirpath, filename) + ' to: ' + owner)
pass
def recursive_permissions(path, dir_mode=755, file_mode=644, topdir=True):
"""
Recursively chmod a path and contents to mode.
Defaults to chmod top level directory but can be optionally
toggled off when you want to chmod only contents of like a user's homedir vs homedir itself
https://docs.python.org/3.6/library/os.html#os.walk
"""
# Here we are converting the integers to string and then to octal.
# so this function doesn't need to be called with 0o prefixed for the file and dir mode
dir_mode = int(str(dir_mode), base=8)
file_mode = int(str(file_mode), base=8)
if topdir:
# Set chmod on top level path
try:
os.chmod(path, dir_mode)
except:
print('Could not chmod :' + path + ' to ' + str(dir_mode))
for root, dirs, files in os.walk(path):
for d in dirs:
try:
os.chmod(os.path.join(root, d), dir_mode)
except:
print('Could not chmod :' + os.path.join(root, d) + ' to ' + str(dir_mode))
pass
for f in files:
try:
os.chmod(os.path.join(root, f), file_mode)
except:
print('Could not chmod :' + path + ' to ' + str(file_mode))
pass
# Left intentionally here for reference.
# Set recursive chown for a path
# recursive_chown(my_path, 'root', 'root')
# for changing group recursively without affecting user
# recursive_chown('/usr/local/lscp/cyberpanel/rainloop/data', -1, 'lscpd')
# explicitly set permissions for directories/folders to 0755 and files to 0644
# recursive_permissions(my_path, 755, 644)
# Fix permissions and use default values
# recursive_permissions(my_path)
# =========================================================
# Below is a helper class for getting and working with permissions
# Original credits to : https://github.com/keysemble/perfm
def perm_octal_digit(rwx):
digit = 0
if rwx[0] == 'r':
digit += 4
if rwx[1] == 'w':
digit += 2
if rwx[2] == 'x':
digit += 1
return digit
class FilePerm:
def __init__(self, filepath):
filemode = stat.filemode(os.stat(filepath).st_mode)
permissions = [filemode[-9:][i:i + 3] for i in range(0, len(filemode[-9:]), 3)]
self.filepath = filepath
self.access_dict = dict(zip(['user', 'group', 'other'], [list(perm) for perm in permissions]))
def mode(self):
mode = 0
for shift, digit in enumerate(self.octal()[::-1]):
mode += digit << (shift * 3)
return mode
def digits(self):
"""Get the octal chmod equivalent value 755 in single string"""
return "".join(map(str, self.octal()))
def octal(self):
"""Get the octal value in a list [7, 5, 5]"""
return [perm_octal_digit(p) for p in self.access_dict.values()]
def access_bits(self, access):
if access in self.access_dict.keys():
r, w, x = self.access_dict[access]
return [r == 'r', w == 'w', x == 'x']
def update_bitwise(self, settings):
def perm_list(read=False, write=False, execute=False):
pl = ['-', '-', '-']
if read:
pl[0] = 'r'
if write:
pl[1] = 'w'
if execute:
pl[2] = 'x'
return pl
self.access_dict = dict(
[(access, perm_list(read=r, write=w, execute=x)) for access, [r, w, x] in settings.items()])
os.chmod(self.filepath, self.mode())
# project_directory = os.path.abspath(os.path.dirname(sys.argv[0]))
# home_directory = os.path.expanduser('~')
# print(f'Path: {home_directory} Mode: {FilePerm(home_directory).mode()} Octal: {FilePerm(home_directory).octal()} '
# f'Digits: {FilePerm(home_directory).digits()}')
# Example: Output
# Path: /home/cooluser Mode: 493 Octal: [7, 5, 5] Digits: 755

View File

@@ -2969,7 +2969,7 @@ echo $oConfig->Save() ? 'Done' : 'Error';
writeToFile.write(content)
writeToFile.close()
command = '/usr/local/lsws/lsphp72/bin/php /usr/local/CyberCP/public/snappymail.php'
command = '/usr/local/lsws/lsphp83/bin/php /usr/local/CyberCP/public/snappymail.php'
subprocess.call(shlex.split(command))
command = "chown -R lscpd:lscpd /usr/local/lscp/cyberpanel/snappymail/data"

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,62 @@ FetchCloudLinuxAlmaVersionVersion = install_utils.FetchCloudLinuxAlmaVersionVers
class InstallCyberPanel:
mysql_Root_password = ""
mysqlPassword = ""
def is_almalinux9(self):
"""Check if running on AlmaLinux 9"""
if os.path.exists('/etc/almalinux-release'):
try:
with open('/etc/almalinux-release', 'r') as f:
content = f.read()
return 'release 9' in content
except:
return False
return False
def fix_almalinux9_mariadb(self):
"""Fix AlmaLinux 9 MariaDB installation issues"""
if not self.is_almalinux9():
return
self.stdOut("Applying AlmaLinux 9 MariaDB fixes...", 1)
try:
# Disable problematic MariaDB MaxScale repository
self.stdOut("Disabling problematic MariaDB MaxScale repository...", 1)
command = "dnf config-manager --disable mariadb-maxscale 2>/dev/null || true"
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
# Remove problematic repository files
self.stdOut("Removing problematic repository files...", 1)
problematic_repos = [
'/etc/yum.repos.d/mariadb-maxscale.repo',
'/etc/yum.repos.d/mariadb-maxscale.repo.rpmnew'
]
for repo_file in problematic_repos:
if os.path.exists(repo_file):
os.remove(repo_file)
self.stdOut(f"Removed {repo_file}", 1)
# Clean DNF cache
self.stdOut("Cleaning DNF cache...", 1)
command = "dnf clean all"
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
# Install MariaDB from official repository
self.stdOut("Setting up official MariaDB repository...", 1)
command = "curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash -s -- --mariadb-server-version='10.11'"
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
# Install MariaDB packages
self.stdOut("Installing MariaDB packages...", 1)
mariadb_packages = "MariaDB-server MariaDB-client MariaDB-backup MariaDB-devel"
command = f"dnf install -y {mariadb_packages}"
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
self.stdOut("AlmaLinux 9 MariaDB fixes completed", 1)
except Exception as e:
self.stdOut(f"Error applying AlmaLinux 9 MariaDB fixes: {str(e)}", 0)
CloudLinux8 = 0
def install_package(self, package_name, options=""):
@@ -335,7 +391,7 @@ class InstallCyberPanel:
return self.reStartLiteSpeed()
def installAllPHPVersions(self):
php_versions = ['71', '72', '73', '74', '80', '81', '82', '83']
php_versions = ['71', '72', '73', '74', '80', '81', '82', '83', '84', '85']
if self.distro == ubuntu:
# Install base PHP 7.x packages
@@ -563,6 +619,12 @@ gpgcheck=1
if self.remotemysql == 'OFF':
############## Start mariadb ######################
# Check if AlmaLinux 9 and apply fixes
if self.is_almalinux9():
self.stdOut("AlmaLinux 9 detected - applying MariaDB fixes", 1)
self.fix_almalinux9_mariadb()
self.manage_service('mariadb', 'start')
############## Enable mariadb at system startup ######################

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,121 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Mail Functions - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2>{% trans "Mail Functions" %}</h2>
<p>{% trans "Manage email accounts on this page." %}</p>
</div>
<div class="panel col-md-12">
<div class="panel-body">
<h3 class="content-box-header">
{% trans "Available Functions" %}
</h3>
<div class="example-box-wrapper">
<div class="row">
<div class="col-md-3 btn-min-width">
<a href="{% url 'createEmailAccount' %}" title="{% trans 'Create Email' %}"
class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "Create Email" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-plus-square"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a href="{% url 'listEmails' %}" title="{% trans 'List Emails' %}"
class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "List Emails" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-plus-square"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a href="{% url 'deleteEmailAccount' %}" title="{% trans 'Delete Email' %}"
class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "Delete Email" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-trash"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a href="{% url 'emailForwarding' %}" title="{% trans 'Email Forwarding' %}"
class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "Email Forwarding" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-plus-square"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a href="{% url 'changeEmailAccountPassword' %}" title="{% trans 'Change Password' %}"
class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "Change Password" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-key"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a href="{% url 'dkimManager' %}" title="{% trans 'DKIM Manager' %}"
class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "DKIM Manager" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-key"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a href="/snappymail/index.php" title="{% trans 'Access Webmail' %}"
class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "Access Webmail" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-key"></i>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

25
phpmyadmin_htaccess Normal file
View File

@@ -0,0 +1,25 @@
# CyberPanel phpMyAdmin Access Control
# Place this file as /usr/local/CyberCP/public/phpmyadmin/.htaccess
# Enable rewrite engine
RewriteEngine On
# Check if user is not authenticated and redirect to login
RewriteCond %{HTTP_COOKIE} !sessionid=
RewriteRule ^(.*)$ /base/ [R=302,L]
# Additional security headers
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
Header always set X-XSS-Protection "1; mode=block"
# Prevent direct access to sensitive files
<Files "config.inc.php">
Order Allow,Deny
Deny from all
</Files>
<Files "*.log">
Order Allow,Deny
Deny from all
</Files>

View File

@@ -0,0 +1,22 @@
<?php
/**
* phpMyAdmin Access Control - Direct Access Redirect
*
* This file should be placed at /usr/local/CyberCP/public/phpmyadmin/index.php
* to replace the default phpMyAdmin index.php and redirect unauthenticated users
* to the CyberPanel login page.
*/
// Check if user is logged into CyberPanel
session_start();
if (!isset($_SESSION['userID'])) {
// Redirect to CyberPanel login page
header('Location: /base/');
exit();
}
// If user is authenticated, redirect to the actual phpMyAdmin interface
// through the proper CyberPanel route
header('Location: /dataBases/phpMyAdmin');
exit();
?>

View File

@@ -1372,7 +1372,7 @@ echo $oConfig->Save() ? 'Done' : 'Error';
command = 'chmod 640 /usr/local/lscp/cyberpanel/logs/access.log'
ProcessUtilities.executioner(command, 'root', True)
command = '/usr/local/lsws/lsphp72/bin/php /usr/local/CyberCP/public/snappymail.php'
command = '/usr/local/lsws/lsphp83/bin/php /usr/local/CyberCP/public/snappymail.php'
ProcessUtilities.executioner(command, 'root', True)
command = 'chmod 600 /usr/local/CyberCP/public/snappymail.php'

View File

@@ -1,190 +0,0 @@
import os
import shutil
import pathlib
import stat
def mkdir_p(path, exist_ok=True):
"""
Creates the directory and paths leading up to it like unix mkdir -p .
Defaults to exist_ok so if it exists were not throwing fatal errors
https://docs.python.org/3.7/library/os.html#os.makedirs
"""
if not os.path.exists(path):
print('creating directory: ' + path)
os.makedirs(path, exist_ok)
def chmod_digit(file_path, perms):
"""
Helper function to chmod like you would in unix without having to preface 0o or converting to octal yourself.
Credits: https://stackoverflow.com/a/60052847/1621381
"""
try:
os.chmod(file_path, int(str(perms), base=8))
except:
print(f'Could not chmod : {file_path} to {perms}')
pass
def touch(filepath: str, exist_ok=True):
"""
Touches a file like unix `touch somefile` would.
"""
try:
pathlib.Path(filepath).touch(exist_ok)
except FileExistsError:
print('Could touch : ' + filepath)
pass
def symlink(src, dst):
"""
Symlink a path to another if the src exists.
"""
try:
if os.access(src, os.R_OK):
os.symlink(src, dst)
except:
print(f'Could not symlink Source: {src} > Destination: {dst}')
pass
def chown(path, user, group=-1):
"""
Chown file/path to user/group provided. Passing -1 to user or group will leave it unchanged.
Useful if just changing user or group vs both.
"""
try:
shutil.chown(path, user, group)
except PermissionError:
print(f'Could not change permissions for: {path} to {user}:{group}')
pass
def recursive_chown(path, owner, group=-1):
"""
Recursively chown a path and contents to owner.
https://docs.python.org/3/library/shutil.html
"""
for dirpath, dirnames, filenames in os.walk(path):
try:
shutil.chown(dirpath, owner, group)
except PermissionError:
print('Could not change permissions for: ' + dirpath + ' to: ' + owner)
pass
for filename in filenames:
try:
shutil.chown(os.path.join(dirpath, filename), owner, group)
except PermissionError:
print('Could not change permissions for: ' + os.path.join(dirpath, filename) + ' to: ' + owner)
pass
def recursive_permissions(path, dir_mode=755, file_mode=644, topdir=True):
"""
Recursively chmod a path and contents to mode.
Defaults to chmod top level directory but can be optionally
toggled off when you want to chmod only contents of like a user's homedir vs homedir itself
https://docs.python.org/3.6/library/os.html#os.walk
"""
# Here we are converting the integers to string and then to octal.
# so this function doesn't need to be called with 0o prefixed for the file and dir mode
dir_mode = int(str(dir_mode), base=8)
file_mode = int(str(file_mode), base=8)
if topdir:
# Set chmod on top level path
try:
os.chmod(path, dir_mode)
except:
print('Could not chmod :' + path + ' to ' + str(dir_mode))
for root, dirs, files in os.walk(path):
for d in dirs:
try:
os.chmod(os.path.join(root, d), dir_mode)
except:
print('Could not chmod :' + os.path.join(root, d) + ' to ' + str(dir_mode))
pass
for f in files:
try:
os.chmod(os.path.join(root, f), file_mode)
except:
print('Could not chmod :' + path + ' to ' + str(file_mode))
pass
# Left intentionally here for reference.
# Set recursive chown for a path
# recursive_chown(my_path, 'root', 'root')
# for changing group recursively without affecting user
# recursive_chown('/usr/local/lscp/cyberpanel/rainloop/data', -1, 'lscpd')
# explicitly set permissions for directories/folders to 0755 and files to 0644
# recursive_permissions(my_path, 755, 644)
# Fix permissions and use default values
# recursive_permissions(my_path)
# =========================================================
# Below is a helper class for getting and working with permissions
# Original credits to : https://github.com/keysemble/perfm
def perm_octal_digit(rwx):
digit = 0
if rwx[0] == 'r':
digit += 4
if rwx[1] == 'w':
digit += 2
if rwx[2] == 'x':
digit += 1
return digit
class FilePerm:
def __init__(self, filepath):
filemode = stat.filemode(os.stat(filepath).st_mode)
permissions = [filemode[-9:][i:i + 3] for i in range(0, len(filemode[-9:]), 3)]
self.filepath = filepath
self.access_dict = dict(zip(['user', 'group', 'other'], [list(perm) for perm in permissions]))
def mode(self):
mode = 0
for shift, digit in enumerate(self.octal()[::-1]):
mode += digit << (shift * 3)
return mode
def digits(self):
"""Get the octal chmod equivalent value 755 in single string"""
return "".join(map(str, self.octal()))
def octal(self):
"""Get the octal value in a list [7, 5, 5]"""
return [perm_octal_digit(p) for p in self.access_dict.values()]
def access_bits(self, access):
if access in self.access_dict.keys():
r, w, x = self.access_dict[access]
return [r == 'r', w == 'w', x == 'x']
def update_bitwise(self, settings):
def perm_list(read=False, write=False, execute=False):
pl = ['-', '-', '-']
if read:
pl[0] = 'r'
if write:
pl[1] = 'w'
if execute:
pl[2] = 'x'
return pl
self.access_dict = dict(
[(access, perm_list(read=r, write=w, execute=x)) for access, [r, w, x] in settings.items()])
os.chmod(self.filepath, self.mode())
# project_directory = os.path.abspath(os.path.dirname(sys.argv[0]))
# home_directory = os.path.expanduser('~')
# print(f'Path: {home_directory} Mode: {FilePerm(home_directory).mode()} Octal: {FilePerm(home_directory).octal()} '
# f'Digits: {FilePerm(home_directory).digits()}')
# Example: Output
# Path: /home/cooluser Mode: 493 Octal: [7, 5, 5] Digits: 755

View File

@@ -1,5 +1,12 @@
<?php
// Check if user is logged into CyberPanel
session_start();
if (!isset($_SESSION['userID'])) {
// Redirect to CyberPanel login page
header('Location: /base/');
exit();
}
define("PMA_SIGNON_INDEX", 1);

View File

@@ -861,7 +861,7 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
command = f'wget -q -O /usr/local/CyberCP/snappymail_cyberpanel.php https://raw.githubusercontent.com/the-djmaze/snappymail/master/integrations/cyberpanel/install.php'
Upgrade.executioner_silent(command, 'verify certificate', 0)
command = f'/usr/local/lsws/lsphp80/bin/php /usr/local/CyberCP/snappymail_cyberpanel.php'
command = f'/usr/local/lsws/lsphp83/bin/php /usr/local/CyberCP/snappymail_cyberpanel.php'
Upgrade.executioner_silent(command, 'verify certificate', 0)
# labsPath = '/usr/local/lscp/cyberpanel/rainloop/data/_data_/_default_/configs/application.ini'
@@ -3121,7 +3121,7 @@ echo $oConfig->Save() ? 'Done' : 'Error';
command = 'chmod 640 /usr/local/lscp/cyberpanel/logs/access.log'
Upgrade.executioner(command, 0)
command = '/usr/local/lsws/lsphp72/bin/php /usr/local/CyberCP/public/snappymail.php'
command = '/usr/local/lsws/lsphp83/bin/php /usr/local/CyberCP/public/snappymail.php'
Upgrade.executioner_silent(command, 'Configure SnappyMail')
command = 'chmod 600 /usr/local/CyberCP/public/snappymail.php'
@@ -3182,44 +3182,221 @@ echo $oConfig->Save() ? 'Done' : 'Error';
command = '/root/.acme.sh/acme.sh --set-default-ca --server letsencrypt'
Upgrade.executioner(command, command, 0)
@staticmethod
def check_package_availability(package_name):
"""Check if a package is available in the repositories"""
try:
# Try to search for the package without installing
if os.path.exists('/etc/yum.repos.d/') or os.path.exists('/etc/dnf/dnf.conf'):
# RHEL-based systems
command = f"dnf search --quiet {package_name} 2>/dev/null | grep -q '^Last metadata expiration' || yum search --quiet {package_name} 2>/dev/null | head -1"
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.returncode == 0
else:
# Ubuntu/Debian systems
command = f"apt-cache search {package_name} 2>/dev/null | head -1"
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.returncode == 0 and result.stdout.strip() != ""
except Exception as e:
Upgrade.stdOut(f"Error checking package availability for {package_name}: {str(e)}", 0)
return False
@staticmethod
def is_almalinux9():
"""Check if running on AlmaLinux 9"""
if os.path.exists('/etc/almalinux-release'):
try:
with open('/etc/almalinux-release', 'r') as f:
content = f.read()
return 'release 9' in content
except:
return False
return False
@staticmethod
def fix_almalinux9_mariadb():
"""Fix AlmaLinux 9 MariaDB installation issues"""
if not Upgrade.is_almalinux9():
return
Upgrade.stdOut("Applying AlmaLinux 9 MariaDB fixes...", 1)
try:
# Disable problematic MariaDB MaxScale repository
Upgrade.stdOut("Disabling problematic MariaDB MaxScale repository...", 1)
command = "dnf config-manager --disable mariadb-maxscale 2>/dev/null || true"
subprocess.run(command, shell=True, capture_output=True)
# Remove problematic repository files
Upgrade.stdOut("Removing problematic repository files...", 1)
problematic_repos = [
'/etc/yum.repos.d/mariadb-maxscale.repo',
'/etc/yum.repos.d/mariadb-maxscale.repo.rpmnew'
]
for repo_file in problematic_repos:
if os.path.exists(repo_file):
os.remove(repo_file)
Upgrade.stdOut(f"Removed {repo_file}", 1)
# Clean DNF cache
Upgrade.stdOut("Cleaning DNF cache...", 1)
command = "dnf clean all"
subprocess.run(command, shell=True, capture_output=True)
# Install MariaDB from official repository
Upgrade.stdOut("Setting up official MariaDB repository...", 1)
command = "curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash -s -- --mariadb-server-version='10.11'"
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0:
Upgrade.stdOut(f"Warning: MariaDB repo setup failed: {result.stderr}", 0)
# Install MariaDB packages
Upgrade.stdOut("Installing MariaDB packages...", 1)
mariadb_packages = "MariaDB-server MariaDB-client MariaDB-backup MariaDB-devel"
command = f"dnf install -y {mariadb_packages}"
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0:
Upgrade.stdOut(f"Warning: MariaDB installation issues: {result.stderr}", 0)
# Start and enable MariaDB service
Upgrade.stdOut("Starting MariaDB service...", 1)
services = ['mariadb', 'mysql', 'mysqld']
for service in services:
try:
command = f"systemctl start {service}"
result = subprocess.run(command, shell=True, capture_output=True)
if result.returncode == 0:
command = f"systemctl enable {service}"
subprocess.run(command, shell=True, capture_output=True)
Upgrade.stdOut(f"MariaDB service started as {service}", 1)
break
except:
continue
Upgrade.stdOut("AlmaLinux 9 MariaDB fixes completed", 1)
except Exception as e:
Upgrade.stdOut(f"Error applying AlmaLinux 9 MariaDB fixes: {str(e)}", 0)
@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:
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]:
php_versions = ['74', '80', '81', '82', '83', '84', '85']
else:
php_versions = ['71', '72', '73', '74', '80', '81', '82', '83', '84', '85']
# Check availability of each version
available_versions = []
for version in php_versions:
if Upgrade.check_package_availability(f'lsphp{version}'):
available_versions.append(version)
else:
Upgrade.stdOut(f"PHP {version} not available on this OS", 0)
return available_versions
@staticmethod
def fixLiteSpeedConfig():
"""Fix LiteSpeed configuration issues by creating missing files"""
try:
Upgrade.stdOut("Checking and fixing LiteSpeed configuration...", 1)
# Check if LiteSpeed is installed
if not os.path.exists('/usr/local/lsws'):
Upgrade.stdOut("LiteSpeed not found at /usr/local/lsws", 0)
return
# Create missing configuration files
config_files = [
"/usr/local/lsws/conf/httpd_config.xml",
"/usr/local/lsws/conf/httpd.conf",
"/usr/local/lsws/conf/modsec.conf"
]
for config_file in config_files:
if not os.path.exists(config_file):
Upgrade.stdOut(f"Missing LiteSpeed config: {config_file}", 0)
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(config_file), exist_ok=True)
# Create minimal config file
if config_file.endswith('httpd_config.xml'):
with open(config_file, 'w') as f:
f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
f.write('<httpServerConfig>\n')
f.write(' <!-- Minimal LiteSpeed configuration -->\n')
f.write(' <listener>\n')
f.write(' <name>Default</name>\n')
f.write(' <address>*:8088</address>\n')
f.write(' </listener>\n')
f.write('</httpServerConfig>\n')
elif config_file.endswith('httpd.conf'):
with open(config_file, 'w') as f:
f.write('# Minimal LiteSpeed HTTP configuration\n')
f.write('# This file will be updated by CyberPanel\n')
elif config_file.endswith('modsec.conf'):
with open(config_file, 'w') as f:
f.write('# ModSecurity configuration\n')
f.write('# This file will be updated by CyberPanel\n')
Upgrade.stdOut(f"Created minimal config: {config_file}", 1)
else:
Upgrade.stdOut(f"LiteSpeed config exists: {config_file}", 1)
except Exception as e:
Upgrade.stdOut(f"Error fixing LiteSpeed config: {str(e)}", 0)
@staticmethod
def installPHP73():
try:
if Upgrade.installedOutput.find('lsphp73') == -1:
command = 'yum install -y lsphp73 lsphp73-json lsphp73-xmlrpc lsphp73-xml lsphp73-tidy lsphp73-soap lsphp73-snmp ' \
'lsphp73-recode lsphp73-pspell lsphp73-process lsphp73-pgsql lsphp73-pear lsphp73-pdo lsphp73-opcache ' \
'lsphp73-odbc lsphp73-mysqlnd lsphp73-mcrypt lsphp73-mbstring lsphp73-ldap lsphp73-intl lsphp73-imap ' \
'lsphp73-gmp lsphp73-gd lsphp73-enchant lsphp73-dba lsphp73-common lsphp73-bcmath'
Upgrade.executioner(command, 'Install PHP 73, 0')
Upgrade.stdOut("Installing PHP versions based on OS compatibility...", 1)
if Upgrade.installedOutput.find('lsphp74') == -1:
command = 'yum install -y lsphp74 lsphp74-json lsphp74-xmlrpc lsphp74-xml lsphp74-tidy lsphp74-soap lsphp74-snmp ' \
'lsphp74-recode lsphp74-pspell lsphp74-process lsphp74-pgsql lsphp74-pear lsphp74-pdo lsphp74-opcache ' \
'lsphp74-odbc lsphp74-mysqlnd lsphp74-mcrypt lsphp74-mbstring lsphp74-ldap lsphp74-intl lsphp74-imap ' \
'lsphp74-gmp lsphp74-gd lsphp74-enchant lsphp74-dba lsphp74-common lsphp74-bcmath'
# Get available PHP versions
available_versions = Upgrade.get_available_php_versions()
Upgrade.executioner(command, 'Install PHP 74, 0')
if not available_versions:
Upgrade.stdOut("No PHP versions available for installation", 0)
return
if Upgrade.installedOutput.find('lsphp80') == -1:
command = 'yum install lsphp80* -y'
Upgrade.stdOut(f"Installing available PHP versions: {', '.join(available_versions)}", 1)
for version in available_versions:
try:
if version in ['71', '72', '73', '74']:
# PHP 7.x versions with specific extensions
if Upgrade.installedOutput.find(f'lsphp{version}') == -1:
extensions = ['json', 'xmlrpc', 'xml', 'tidy', 'soap', 'snmp', 'recode', 'pspell', 'process', 'pgsql', 'pear', 'pdo', 'opcache', 'odbc', 'mysqlnd', 'mcrypt', 'mbstring', 'ldap', 'intl', 'imap', 'gmp', 'gd', 'enchant', 'dba', 'common', 'bcmath']
package_list = f"lsphp{version} " + " ".join([f"lsphp{version}-{ext}" for ext in extensions])
command = f"yum install -y {package_list}"
Upgrade.executioner(command, f'Install PHP {version}', 0)
else:
# PHP 8.x versions
if Upgrade.installedOutput.find(f'lsphp{version}') == -1:
command = f"yum install lsphp{version}* -y"
subprocess.call(command, shell=True)
Upgrade.stdOut(f"Installed PHP {version}", 1)
if Upgrade.installedOutput.find('lsphp81') == -1:
command = 'yum install lsphp81* -y'
subprocess.call(command, shell=True)
if Upgrade.installedOutput.find('lsphp82') == -1:
command = 'yum install lsphp82* -y'
subprocess.call(command, shell=True)
command = 'yum install lsphp83* -y'
subprocess.call(command, shell=True)
command = 'yum install lsphp84* -y'
subprocess.call(command, shell=True)
command = 'yum install lsphp85* -y'
subprocess.call(command, shell=True)
except Exception as e:
Upgrade.stdOut(f"Error installing PHP {version}: {str(e)}", 0)
continue
except:
command = 'DEBIAN_FRONTEND=noninteractive apt-get -y install ' \
@@ -3997,9 +4174,20 @@ pm.max_spare_servers = 3
@staticmethod
def setupPHPSymlink():
try:
# Check if PHP 8.3 exists
if not os.path.exists('/usr/local/lsws/lsphp83/bin/php'):
Upgrade.stdOut("PHP 8.3 not found, installing it first...")
# Try to find available PHP version (prioritize modern stable versions)
# Priority: 8.3 (recommended), 8.2, 8.4, 8.5, 8.1, 8.0, then older versions
php_versions = ['83', '82', '84', '85', '81', '80', '74', '73', '72', '71']
selected_php = None
for version in php_versions:
if os.path.exists(f'/usr/local/lsws/lsphp{version}/bin/php'):
selected_php = version
Upgrade.stdOut(f"Found PHP {version}, using as default", 1)
break
if not selected_php:
# Try to install PHP 8.3 as fallback (modern stable version)
Upgrade.stdOut("No PHP found, installing PHP 8.3 as fallback...")
# Install PHP 8.3 based on OS
if os.path.exists(Upgrade.CentOSPath) or os.path.exists(Upgrade.openEulerPath):
@@ -4013,16 +4201,17 @@ pm.max_spare_servers = 3
if not os.path.exists('/usr/local/lsws/lsphp83/bin/php'):
Upgrade.stdOut('[ERROR] Failed to install PHP 8.3')
return 0
selected_php = '83'
# Remove existing PHP symlink if it exists
if os.path.exists('/usr/bin/php'):
os.remove('/usr/bin/php')
# Create symlink to PHP 8.3
command = 'ln -s /usr/local/lsws/lsphp83/bin/php /usr/bin/php'
Upgrade.executioner(command, 'Setup PHP Symlink to 8.3', 0)
# Create symlink to selected PHP version
command = f'ln -s /usr/local/lsws/lsphp{selected_php}/bin/php /usr/bin/php'
Upgrade.executioner(command, f'Setup PHP Symlink to {selected_php}', 0)
Upgrade.stdOut("PHP symlink updated to PHP 8.3 successfully.")
Upgrade.stdOut(f"PHP symlink updated to PHP {selected_php} successfully.")
except BaseException as msg:
Upgrade.stdOut('[ERROR] ' + str(msg) + " [setupPHPSymlink]")
@@ -4150,6 +4339,9 @@ pm.max_spare_servers = 3
Upgrade.manageServiceMigrations()
Upgrade.enableServices()
# Apply AlmaLinux 9 fixes before other installations
Upgrade.fix_almalinux9_mariadb()
Upgrade.installPHP73()
Upgrade.setupCLI()
Upgrade.someDirectories()
@@ -4159,6 +4351,9 @@ pm.max_spare_servers = 3
## Fix Apache configuration issues after upgrade
Upgrade.fixApacheConfiguration()
# Fix LiteSpeed configuration files if missing
Upgrade.fixLiteSpeedConfig()
### General migrations are not needed any more
# Upgrade.GeneralMigrations()
@@ -4191,8 +4386,32 @@ pm.max_spare_servers = 3
except:
pass
command = 'cp /usr/local/lsws/lsphp80/bin/lsphp %s' % (phpPath)
# Try to find available PHP binary in order of preference (modern stable first)
php_versions = ['83', '82', '84', '85', '81', '80', '74', '73', '72', '71']
php_binary_found = False
for version in php_versions:
php_binary = f'/usr/local/lsws/lsphp{version}/bin/lsphp'
if os.path.exists(php_binary):
command = f'cp {php_binary} {phpPath}'
Upgrade.executioner(command, 0)
Upgrade.stdOut(f"Using PHP {version} for LSCPD", 1)
php_binary_found = True
break
if not php_binary_found:
Upgrade.stdOut("Warning: No PHP binary found for LSCPD", 0)
# Try to create a symlink to any available PHP
try:
command = 'find /usr/local/lsws -name "lsphp" -type f 2>/dev/null | head -1'
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.stdout.strip():
php_binary = result.stdout.strip()
command = f'cp {php_binary} {phpPath}'
Upgrade.executioner(command, 0)
Upgrade.stdOut(f"Using found PHP binary: {php_binary}", 1)
except:
pass
if Upgrade.SoftUpgrade == 0:
try:
@@ -4201,6 +4420,42 @@ pm.max_spare_servers = 3
except:
pass
# Try to start other services if they exist
# Enhanced service startup with AlmaLinux 9 support
services_to_start = ['fastapi_ssh_server', 'cyberpanel']
# Special handling for AlmaLinux 9 MariaDB service
if Upgrade.is_almalinux9():
Upgrade.stdOut("AlmaLinux 9 detected - applying enhanced service management", 1)
mariadb_services = ['mariadb', 'mysql', 'mysqld']
for service in mariadb_services:
try:
check_command = f"systemctl list-unit-files | grep -q {service}"
result = subprocess.run(check_command, shell=True, capture_output=True)
if result.returncode == 0:
command = f"systemctl restart {service}"
Upgrade.executioner(command, f'Restart {service} for AlmaLinux 9', 0)
command = f"systemctl enable {service}"
Upgrade.executioner(command, f'Enable {service} for AlmaLinux 9', 0)
Upgrade.stdOut(f"MariaDB service managed as {service} on AlmaLinux 9", 1)
break
except Exception as e:
Upgrade.stdOut(f"Could not manage MariaDB service {service}: {str(e)}", 0)
continue
for service in services_to_start:
try:
# Check if service exists
check_command = f"systemctl list-unit-files | grep -q {service}"
result = subprocess.run(check_command, shell=True, capture_output=True)
if result.returncode == 0:
command = f"systemctl start {service}"
Upgrade.executioner(command, f'Start {service}', 0)
else:
Upgrade.stdOut(f"Service {service} not found, skipping", 0)
except Exception as e:
Upgrade.stdOut(f"Could not start {service}: {str(e)}", 0)
# Remove CSF if installed and restore firewalld (CSF is being discontinued on August 31, 2025)
if os.path.exists('/etc/csf'):
print("CSF detected - removing CSF and restoring firewalld...")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
#!/bin/bash
# CyberPanel phpMyAdmin Access Control Rollback Script
# This script reverts the phpMyAdmin access control changes
echo "=== CyberPanel phpMyAdmin Access Control Rollback ==="
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run this script as root"
exit 1
fi
# Find the most recent backup
LATEST_BACKUP=$(ls -t /usr/local/CyberCP/public/phpmyadmin/index.php.backup.* 2>/dev/null | head -n1)
if [ -z "$LATEST_BACKUP" ]; then
echo "No backup found. Cannot rollback changes."
echo "You may need to reinstall phpMyAdmin or restore from your own backup."
exit 1
fi
echo "Found backup: $LATEST_BACKUP"
echo "Restoring original phpMyAdmin index.php..."
# Restore the original index.php
cp "$LATEST_BACKUP" /usr/local/CyberCP/public/phpmyadmin/index.php
# Remove the .htaccess file if it exists
if [ -f "/usr/local/CyberCP/public/phpmyadmin/.htaccess" ]; then
echo "Removing .htaccess file..."
rm /usr/local/CyberCP/public/phpmyadmin/.htaccess
fi
# Set proper permissions
echo "Setting permissions..."
chown lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin/index.php
chmod 644 /usr/local/CyberCP/public/phpmyadmin/index.php
# Restart LiteSpeed to ensure changes take effect
echo "Restarting LiteSpeed..."
systemctl restart lscpd
echo "=== Rollback Complete ==="
echo ""
echo "phpMyAdmin access control has been reverted!"
echo "phpMyAdmin should now work as it did before the changes."
echo ""
echo "Backup file used: $LATEST_BACKUP"

28
run_migration.py Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Migration script for UserNotificationPreferences model
Run this script to apply the database migration for notification preferences
"""
import os
import sys
import django
# Add the project directory to Python path
sys.path.append('/usr/local/CyberCP')
# Set up Django environment
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CyberCP.settings')
django.setup()
from django.core.management import execute_from_command_line
if __name__ == '__main__':
print("Running migration for UserNotificationPreferences...")
try:
execute_from_command_line(['manage.py', 'migrate', 'baseTemplate'])
print("Migration completed successfully!")
except Exception as e:
print(f"Migration failed: {e}")
sys.exit(1)

View File

@@ -10,7 +10,9 @@ The CyberPanel Test Plugin is designed to work seamlessly across all CyberPanel-
|------------------|---------|----------------|----------------|-----------------|-----------------|
| **Ubuntu** | 22.04 | ✅ Full Support | 3.10+ | apt-get | systemctl |
| **Ubuntu** | 20.04 | ✅ Full Support | 3.8+ | apt-get | systemctl |
| **Debian** | 11+ | ✅ Full Support | 3.9+ | apt-get | systemctl |
| **Debian** | 13 | ✅ Full Support | 3.11+ | apt-get | systemctl |
| **Debian** | 12 | ✅ Full Support | 3.10+ | apt-get | systemctl |
| **Debian** | 11 | ✅ Full Support | 3.9+ | apt-get | systemctl |
| **AlmaLinux** | 10 | ✅ Full Support | 3.11+ | dnf | systemctl |
| **AlmaLinux** | 9 | ✅ Full Support | 3.9+ | dnf | systemctl |
| **AlmaLinux** | 8 | ✅ Full Support | 3.6+ | dnf/yum | systemctl |
@@ -457,6 +459,6 @@ sudo rm -f /home/cyberpanel/plugins/testPlugin
---
**Last Updated**: December 2024
**Last Updated**: September 2025
**Compatibility Version**: 1.0.0
**Next Review**: March 2025
**Next Review**: March 2026

430
test_debian13_support.sh Normal file
View File

@@ -0,0 +1,430 @@
#!/bin/bash
# Debian 13 Support Test Script for CyberPanel
# This script tests the compatibility of CyberPanel with Debian 13
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Test results
TESTS_PASSED=0
TESTS_FAILED=0
TESTS_TOTAL=0
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
((TESTS_PASSED++))
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
((TESTS_FAILED++))
}
print_test_header() {
echo -e "\n${BLUE}=== $1 ===${NC}"
((TESTS_TOTAL++))
}
# Function to run a test
run_test() {
local test_name="$1"
local test_command="$2"
local expected_result="$3"
print_test_header "$test_name"
if eval "$test_command" >/dev/null 2>&1; then
if [[ "$expected_result" == "success" ]]; then
print_success "$test_name passed"
return 0
else
print_error "$test_name failed (unexpected success)"
return 1
fi
else
if [[ "$expected_result" == "failure" ]]; then
print_success "$test_name passed (expected failure)"
return 0
else
print_error "$test_name failed"
return 1
fi
fi
}
# Function to check OS detection
test_os_detection() {
print_test_header "OS Detection Test"
# Check if we're on Debian 13
if [[ -f /etc/os-release ]]; then
source /etc/os-release
if [[ "$ID" == "debian" && "$VERSION_ID" == "13" ]]; then
print_success "Debian 13 detected correctly"
else
print_warning "Not running on Debian 13 (Current: $ID $VERSION_ID)"
print_status "This test is designed for Debian 13, but will continue with current OS"
fi
else
print_error "Cannot detect OS - /etc/os-release not found"
return 1
fi
}
# Function to test CyberPanel OS detection logic
test_cyberpanel_os_detection() {
print_test_header "CyberPanel OS Detection Logic Test"
# Test the OS detection logic from cyberpanel.sh
if grep -q -E "Debian GNU/Linux 11|Debian GNU/Linux 12|Debian GNU/Linux 13" /etc/os-release; then
print_success "CyberPanel OS detection logic recognizes Debian 11/12/13"
else
print_error "CyberPanel OS detection logic does not recognize current Debian version"
return 1
fi
}
# Function to test package manager compatibility
test_package_manager() {
print_test_header "Package Manager Compatibility Test"
# Test apt-get availability
if command -v apt-get >/dev/null 2>&1; then
print_success "apt-get package manager is available"
else
print_error "apt-get package manager not found"
return 1
fi
# Test apt-get update (dry run)
if apt-get update --dry-run >/dev/null 2>&1; then
print_success "apt-get update works correctly"
else
print_warning "apt-get update failed (may be network related)"
fi
}
# Function to test systemd compatibility
test_systemd_compatibility() {
print_test_header "Systemd Compatibility Test"
# Test systemctl availability
if command -v systemctl >/dev/null 2>&1; then
print_success "systemctl is available"
else
print_error "systemctl not found"
return 1
fi
# Test systemd status
if systemctl is-system-running >/dev/null 2>&1; then
print_success "systemd is running"
else
print_warning "systemd status unclear"
fi
}
# Function to test Python compatibility
test_python_compatibility() {
print_test_header "Python Compatibility Test"
# Test Python 3 availability
if command -v python3 >/dev/null 2>&1; then
local python_version=$(python3 --version 2>&1 | cut -d' ' -f2)
print_success "Python 3 is available: $python_version"
# Check if Python version is compatible (3.6+)
local major_version=$(echo "$python_version" | cut -d'.' -f1)
local minor_version=$(echo "$python_version" | cut -d'.' -f2)
if [[ $major_version -ge 3 && $minor_version -ge 6 ]]; then
print_success "Python version is compatible (3.6+)"
else
print_warning "Python version may not be fully compatible (requires 3.6+)"
fi
else
print_error "Python 3 not found"
return 1
fi
# Test pip3 availability
if command -v pip3 >/dev/null 2>&1; then
print_success "pip3 is available"
else
print_warning "pip3 not found (may need to be installed)"
fi
}
# Function to test web server compatibility
test_web_server_compatibility() {
print_test_header "Web Server Compatibility Test"
# Test Apache2 availability
if command -v apache2 >/dev/null 2>&1; then
print_success "Apache2 is available"
else
print_warning "Apache2 not found (will be installed by CyberPanel)"
fi
# Test if Apache2 can be installed
if apt-cache show apache2 >/dev/null 2>&1; then
print_success "Apache2 package is available in repositories"
else
print_error "Apache2 package not found in repositories"
return 1
fi
}
# Function to test required packages availability
test_required_packages() {
print_test_header "Required Packages Availability Test"
local required_packages=(
"curl"
"wget"
"git"
"build-essential"
"python3-dev"
"python3-pip"
"python3-venv"
"software-properties-common"
"apt-transport-https"
"ca-certificates"
"gnupg"
)
local available_count=0
local total_count=${#required_packages[@]}
for package in "${required_packages[@]}"; do
if apt-cache show "$package" >/dev/null 2>&1; then
print_success "$package is available"
((available_count++))
else
print_warning "$package not found in repositories"
fi
done
print_status "Available packages: $available_count/$total_count"
if [[ $available_count -ge $((total_count * 8 / 10)) ]]; then
print_success "Most required packages are available"
else
print_warning "Many required packages are missing"
fi
}
# Function to test LiteSpeed repository compatibility
test_litespeed_repo_compatibility() {
print_test_header "LiteSpeed Repository Compatibility Test"
# Test if we can access LiteSpeed Debian repository
if curl -s --head "http://rpms.litespeedtech.com/debian/" | head -n 1 | grep -q "200 OK"; then
print_success "LiteSpeed Debian repository is accessible"
else
print_warning "LiteSpeed Debian repository may not be accessible"
fi
# Test if we can download the repository setup script
if wget --spider "http://rpms.litespeedtech.com/debian/enable_lst_debian_repo.sh" 2>/dev/null; then
print_success "LiteSpeed repository setup script is available"
else
print_warning "LiteSpeed repository setup script may not be available"
fi
}
# Function to test MariaDB compatibility
test_mariadb_compatibility() {
print_test_header "MariaDB Compatibility Test"
# Test MariaDB repository accessibility
if curl -s --head "https://mariadb.org/mariadb_release_signing_key.pgp" | head -n 1 | grep -q "200 OK"; then
print_success "MariaDB signing key is accessible"
else
print_warning "MariaDB signing key may not be accessible"
fi
# Test if MariaDB packages are available
if apt-cache show mariadb-server >/dev/null 2>&1; then
print_success "MariaDB packages are available in repositories"
else
print_warning "MariaDB packages not found in default repositories"
fi
}
# Function to test network connectivity
test_network_connectivity() {
print_test_header "Network Connectivity Test"
local test_urls=(
"https://github.com"
"https://pypi.org"
"http://rpms.litespeedtech.com"
"https://mariadb.org"
)
local accessible_count=0
local total_count=${#test_urls[@]}
for url in "${test_urls[@]}"; do
if curl -s --head "$url" >/dev/null 2>&1; then
print_success "$url is accessible"
((accessible_count++))
else
print_warning "$url is not accessible"
fi
done
print_status "Accessible URLs: $accessible_count/$total_count"
if [[ $accessible_count -ge $((total_count * 3 / 4)) ]]; then
print_success "Network connectivity is good"
else
print_warning "Network connectivity may be limited"
fi
}
# Function to test system resources
test_system_resources() {
print_test_header "System Resources Test"
# Test available memory
local total_memory=$(free -m | awk 'NR==2{print $2}')
if [[ $total_memory -ge 1024 ]]; then
print_success "Sufficient memory available: ${total_memory}MB"
else
print_warning "Low memory: ${total_memory}MB (recommended: 1GB+)"
fi
# Test available disk space
local available_space=$(df / | awk 'NR==2{print $4}')
local available_gb=$((available_space / 1024 / 1024))
if [[ $available_gb -ge 10 ]]; then
print_success "Sufficient disk space: ${available_gb}GB"
else
print_warning "Low disk space: ${available_gb}GB (recommended: 10GB+)"
fi
# Test CPU cores
local cpu_cores=$(nproc)
if [[ $cpu_cores -ge 2 ]]; then
print_success "Sufficient CPU cores: $cpu_cores"
else
print_warning "Limited CPU cores: $cpu_cores (recommended: 2+)"
fi
}
# Function to run all tests
run_all_tests() {
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} CyberPanel Debian 13 Compatibility Test${NC}"
echo -e "${BLUE}========================================${NC}\n"
test_os_detection
test_cyberpanel_os_detection
test_package_manager
test_systemd_compatibility
test_python_compatibility
test_web_server_compatibility
test_required_packages
test_litespeed_repo_compatibility
test_mariadb_compatibility
test_network_connectivity
test_system_resources
echo -e "\n${BLUE}========================================${NC}"
echo -e "${BLUE} Test Results Summary${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "Total Tests: $TESTS_TOTAL"
echo -e "Passed: ${GREEN}$TESTS_PASSED${NC}"
echo -e "Failed: ${RED}$TESTS_FAILED${NC}"
if [[ $TESTS_FAILED -eq 0 ]]; then
echo -e "\n${GREEN}✅ All tests passed! Debian 13 appears to be compatible with CyberPanel.${NC}"
return 0
elif [[ $TESTS_FAILED -le 2 ]]; then
echo -e "\n${YELLOW}⚠️ Most tests passed. Debian 13 should be compatible with minor issues.${NC}"
return 0
else
echo -e "\n${RED}❌ Multiple tests failed. Debian 13 may have compatibility issues.${NC}"
return 1
fi
}
# Function to show usage
show_usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -v, --verbose Enable verbose output"
echo " --quick Run only essential tests"
echo ""
echo "This script tests CyberPanel compatibility with Debian 13."
echo "Run as root for best results."
}
# Main execution
main() {
local verbose=false
local quick=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-v|--verbose)
verbose=true
shift
;;
--quick)
quick=true
shift
;;
*)
echo "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Check if running as root
if [[ $EUID -ne 0 ]]; then
print_warning "Not running as root. Some tests may fail."
print_status "Consider running: sudo $0"
fi
# Run tests
if [[ "$quick" == "true" ]]; then
print_status "Running quick compatibility test..."
test_os_detection
test_cyberpanel_os_detection
test_package_manager
test_systemd_compatibility
else
run_all_tests
fi
}
# Run main function
main "$@"

View File

@@ -1,192 +0,0 @@
# CyberPanel Secure Installation Guide
## Overview
This document describes the secure installation process for CyberPanel that eliminates hardcoded passwords and implements environment-based configuration.
## Security Improvements
### ✅ **Fixed Security Vulnerabilities**
1. **Hardcoded Database Passwords** - Now generated securely during installation
2. **Hardcoded Django Secret Key** - Now generated using cryptographically secure random generation
3. **Environment Variables** - All sensitive configuration moved to `.env` file
4. **File Permissions** - `.env` file set to 600 (owner read/write only)
### 🔐 **Security Features**
- **Cryptographically Secure Passwords**: Uses Python's `secrets` module for password generation
- **Environment-based Configuration**: Sensitive data stored in `.env` file, not in code
- **Secure File Permissions**: Environment files protected with 600 permissions
- **Credential Backup**: Automatic backup of credentials for recovery
- **Fallback Security**: Maintains backward compatibility with fallback method
## Installation Process
### 1. **Automatic Secure Installation**
The installation script now automatically:
1. Generates secure random passwords for:
- MySQL root user
- CyberPanel database user
- Django secret key
2. Creates `.env` file with secure configuration:
```bash
# Generated during installation
SECRET_KEY=your_64_character_secure_key
DB_PASSWORD=your_24_character_secure_password
ROOT_DB_PASSWORD=your_24_character_secure_password
```
3. Creates `.env.backup` file for credential recovery
4. Sets secure file permissions (600) on all environment files
### 2. **Manual Installation** (if needed)
If you need to manually generate environment configuration:
```bash
cd /usr/local/CyberCP
python install/env_generator.py /usr/local/CyberCP
```
## File Structure
```
/usr/local/CyberCP/
├── .env # Main environment configuration (600 permissions)
├── .env.backup # Credential backup (600 permissions)
├── .env.template # Template for manual configuration
├── .gitignore # Prevents .env files from being committed
└── CyberCP/
└── settings.py # Updated to use environment variables
```
## Security Best Practices
### ✅ **Do's**
- Keep `.env` and `.env.backup` files secure
- Record credentials from `.env.backup` and delete the file after installation
- Use strong, unique passwords for production deployments
- Regularly rotate database passwords
- Monitor access to environment files
### ❌ **Don'ts**
- Never commit `.env` files to version control
- Don't share `.env` files via insecure channels
- Don't use default passwords in production
- Don't leave `.env.backup` files on the system after recording credentials
## Recovery
### **Lost Credentials**
If you lose your database credentials:
1. Check if `.env.backup` file exists:
```bash
sudo cat /usr/local/CyberCP/.env.backup
```
2. If backup doesn't exist, you'll need to reset MySQL passwords using MySQL recovery procedures
### **Regenerate Environment**
To regenerate environment configuration:
```bash
cd /usr/local/CyberCP
sudo python install/env_generator.py /usr/local/CyberCP
```
## Configuration Options
### **Environment Variables**
| Variable | Description | Default |
|----------|-------------|---------|
| `SECRET_KEY` | Django secret key | Generated (64 chars) |
| `DB_PASSWORD` | CyberPanel DB password | Generated (24 chars) |
| `ROOT_DB_PASSWORD` | MySQL root password | Generated (24 chars) |
| `DEBUG` | Debug mode | False |
| `ALLOWED_HOSTS` | Allowed hosts | localhost,127.0.0.1,hostname |
### **Custom Configuration**
To use custom passwords during installation:
```bash
python install/env_generator.py /usr/local/CyberCP "your_root_password" "your_db_password"
```
## Troubleshooting
### **Installation Fails**
If the new secure installation fails:
1. Check installation logs for error messages
2. The system will automatically fallback to the original installation method
3. Verify Python dependencies are installed:
```bash
pip install python-dotenv
```
### **Environment Loading Issues**
If Django can't load environment variables:
1. Ensure `.env` file exists and has correct permissions:
```bash
ls -la /usr/local/CyberCP/.env
# Should show: -rw------- 1 root root
```
2. Install python-dotenv if missing:
```bash
pip install python-dotenv
```
## Migration from Old Installation
### **Existing Installations**
For existing CyberPanel installations with hardcoded passwords:
1. **Backup current configuration**:
```bash
cp /usr/local/CyberCP/CyberCP/settings.py /usr/local/CyberCP/CyberCP/settings.py.backup
```
2. **Generate new environment configuration**:
```bash
cd /usr/local/CyberCP
python install/env_generator.py /usr/local/CyberCP
```
3. **Update settings.py** (already done in new installations):
- The settings.py file now supports environment variables
- It will fallback to hardcoded values if .env is not available
4. **Test the configuration**:
```bash
cd /usr/local/CyberCP
python manage.py check
```
## Support
For issues with the secure installation:
1. Check the installation logs
2. Verify file permissions
3. Ensure all dependencies are installed
4. Review the fallback installation method if needed
---
**Security Notice**: This installation method significantly improves security by eliminating hardcoded credentials. Always ensure proper file permissions and secure handling of environment files.