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

V2.5.5 dev - Enhance bandwidth usage calculation with resource management
This commit is contained in:
Usman Nasir 2025-09-17 14:22:23 +05:00 committed by GitHub
commit 32a3fb7b67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 4274 additions and 9306 deletions

View File

@ -47,21 +47,29 @@ fi
### OS Detection
Server_OS=""
Server_OS_Version=""
if grep -q -E "CentOS Linux 7|CentOS Linux 8" /etc/os-release ; then
if grep -q -E "CentOS Linux 7|CentOS Linux 8|CentOS Stream" /etc/os-release ; then
Server_OS="CentOS"
elif grep -q -E "AlmaLinux-8|AlmaLinux-9|AlmaLinux-10" /etc/os-release ; then
elif grep -q "Red Hat Enterprise Linux" /etc/os-release ; then
Server_OS="RedHat"
elif grep -q "AlmaLinux-8" /etc/os-release ; then
Server_OS="AlmaLinux"
elif grep -q "AlmaLinux-9" /etc/os-release ; then
Server_OS="AlmaLinux"
elif grep -q "AlmaLinux-10" /etc/os-release ; then
Server_OS="AlmaLinux"
elif grep -q -E "CloudLinux 7|CloudLinux 8" /etc/os-release ; then
Server_OS="CloudLinux"
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" /etc/os-release ; then
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, CentOS 7, CentOS 8, AlmaLinux 8, RockyLinux 8, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03...\n"
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, RockyLinux 9, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03...\n"
exit
fi
@ -69,10 +77,13 @@ Server_OS_Version=$(grep VERSION_ID /etc/os-release | awk -F[=,] '{print $2}' |
echo -e "System: $Server_OS $Server_OS_Version detected...\n"
if [[ $Server_OS = "CloudLinux" ]] || [[ "$Server_OS" = "AlmaLinux" ]] || [[ "$Server_OS" = "RockyLinux" ]] ; then
if [[ $Server_OS = "CloudLinux" ]] || [[ "$Server_OS" = "AlmaLinux" ]] || [[ "$Server_OS" = "RockyLinux" ]] || [[ "$Server_OS" = "RedHat" ]] ; then
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
#treat CloudLinux, Rocky, Alma and RedHat as CentOS
elif [[ "$Server_OS" = "Debian" ]] ; then
Server_OS="Ubuntu"
#Treat Debian as Ubuntu for package management (both use apt-get)
fi
if [[ $Server_OS = "CentOS" ]] && [[ "$Server_OS_Version" = "7" ]] ; then
@ -114,6 +125,29 @@ elif [[ $Server_OS = "CentOS" ]] && [[ "$Server_OS_Version" = "8" ]] ; then
freshclam -v
elif [[ $Server_OS = "CentOS" ]] && [[ "$Server_OS_Version" = "9" ]] ; then
setenforce 0
dnf install -y perl dnf-utils perl-CPAN
dnf --enablerepo=crb install -y perl-IO-stringy
dnf install -y gcc cpp perl bzip2 zip make patch automake rpm-build perl-Archive-Zip perl-Filesys-Df perl-OLE-Storage_Lite perl-Net-CIDR perl-DBI perl-MIME-tools perl-DBD-SQLite binutils glibc-devel perl-Filesys-Df zlib unzip zlib-devel wget mlocate clamav clamav-update "perl(DBD::mysql)"
# Install unrar for AlmaLinux 9 (using EPEL)
dnf install -y unrar
export PERL_MM_USE_DEFAULT=1
curl -L https://cpanmin.us | perl - App::cpanminus
perl -MCPAN -e 'install Encoding::FixLatin'
perl -MCPAN -e 'install Digest::SHA1'
perl -MCPAN -e 'install Geo::IP'
perl -MCPAN -e 'install Razor2::Client::Agent'
perl -MCPAN -e 'install Sys::Hostname::Long'
perl -MCPAN -e 'install Sys::SigAction'
perl -MCPAN -e 'install Net::Patricia'
freshclam -v
elif [ "$CLNVERSION" = "ID=\"cloudlinux\"" ]; then
setenforce 0

View File

@ -4,21 +4,29 @@
### OS Detection
Server_OS=""
Server_OS_Version=""
if grep -q -E "CentOS Linux 7|CentOS Linux 8" /etc/os-release ; then
if grep -q -E "CentOS Linux 7|CentOS Linux 8|CentOS Stream" /etc/os-release ; then
Server_OS="CentOS"
elif grep -q -E "AlmaLinux-8|AlmaLinux-9|AlmaLinux-10" /etc/os-release ; then
elif grep -q "Red Hat Enterprise Linux" /etc/os-release ; then
Server_OS="RedHat"
elif grep -q "AlmaLinux-8" /etc/os-release ; then
Server_OS="AlmaLinux"
elif grep -q "AlmaLinux-9" /etc/os-release ; then
Server_OS="AlmaLinux"
elif grep -q "AlmaLinux-10" /etc/os-release ; then
Server_OS="AlmaLinux"
elif grep -q -E "CloudLinux 7|CloudLinux 8" /etc/os-release ; then
Server_OS="CloudLinux"
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" /etc/os-release ; then
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, CentOS 7, CentOS 8, AlmaLinux 8, RockyLinux 8, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03...\n"
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, RockyLinux 9, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03...\n"
exit
fi
@ -26,10 +34,13 @@ Server_OS_Version=$(grep VERSION_ID /etc/os-release | awk -F[=,] '{print $2}' |
echo -e "System: $Server_OS $Server_OS_Version detected...\n"
if [[ $Server_OS = "CloudLinux" ]] || [[ "$Server_OS" = "AlmaLinux" ]] || [[ "$Server_OS" = "RockyLinux" ]] ; then
if [[ $Server_OS = "CloudLinux" ]] || [[ "$Server_OS" = "AlmaLinux" ]] || [[ "$Server_OS" = "RockyLinux" ]] || [[ "$Server_OS" = "RedHat" ]] ; then
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
#treat CloudLinux, Rocky, Alma and RedHat as CentOS
elif [[ "$Server_OS" = "Debian" ]] ; then
Server_OS="Ubuntu"
#Treat Debian as Ubuntu for package management (both use apt-get)
fi
systemctl stop mailscanner

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

@ -14,7 +14,7 @@ Web Hosting Control Panel powered by OpenLiteSpeed, designed to simplify hosting
- 📧 **Email Support** (SnappyMail).
- 🕌 **File Manager** for quick file access.
- 🌐 **PHP Management** made easy.
- 🔒 **Firewall** (FirewallD & ConfigServer Firewall Integration).
- 🔒 **Firewall** (FirewallD Integration with One-Click IP Blocking).
- 📀 **One-click Backups and Restores**.
- 🐳 **Docker Management** with command execution capabilities.
- 🤖 **AI-Powered Security Scanner** for enhanced protection.
@ -30,6 +30,8 @@ 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
- 🛡️ **[Firewall Blocking Feature](guides/FIREWALL_BLOCKING_FEATURE.md)** - One-click IP blocking from dashboard
---
@ -39,9 +41,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 +81,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 +98,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 +113,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 +127,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,17 +163,21 @@ 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+
- 🛡️ [Firewall Blocking Feature](guides/FIREWALL_BLOCKING_FEATURE.md) - One-click IP blocking from dashboard
- 📚 [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 |
| 📊 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 |
| 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 |
| 🛡️ Firewall | [Firewall Blocking Feature](guides/FIREWALL_BLOCKING_FEATURE.md) | One-click IP blocking from dashboard |
| 📧 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 +186,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

@ -1,193 +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.

View File

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

View File

@ -23,4 +23,30 @@ def cosmetic_context(request):
cosmetic.save()
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.
@ -11,4 +12,19 @@ class version(models.Model):
build = models.IntegerField()
class CyberPanelCosmetic(models.Model):
MainDashboardCSS = models.TextField(default='')
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

@ -980,6 +980,10 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.showAddonRequired = false;
$scope.addonInfo = {};
// IP Blocking functionality
$scope.blockingIP = null;
$scope.blockedIPs = {};
$scope.analyzeSSHSecurity = function() {
$scope.loadingSecurityAnalysis = true;
$scope.showAddonRequired = false;
@ -999,6 +1003,64 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.loadingSecurityAnalysis = false;
});
};
$scope.blockIPAddress = function(ipAddress) {
if (!$scope.blockingIP) {
$scope.blockingIP = ipAddress;
var data = {
ip_address: ipAddress
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post('/base/blockIPAddress', data, config).then(function (response) {
$scope.blockingIP = null;
if (response.data && response.data.status === 1) {
// Mark IP as blocked
$scope.blockedIPs[ipAddress] = true;
// Show success notification
new PNotify({
title: 'Success',
text: `IP address ${ipAddress} has been blocked successfully using ${response.data.firewall.toUpperCase()}`,
type: 'success',
delay: 5000
});
// Refresh security analysis to update alerts
$scope.analyzeSSHSecurity();
} else {
// Show error notification
new PNotify({
title: 'Error',
text: response.data && response.data.error ? response.data.error : 'Failed to block IP address',
type: 'error',
delay: 5000
});
}
}, function (err) {
$scope.blockingIP = null;
var errorMessage = 'Failed to block IP address';
if (err.data && err.data.error) {
errorMessage = err.data.error;
} else if (err.data && err.data.message) {
errorMessage = err.data.message;
}
new PNotify({
title: 'Error',
text: errorMessage,
type: 'error',
delay: 5000
});
});
}
};
// Initial fetch
$scope.refreshTopProcesses();

View File

@ -663,6 +663,23 @@
<strong style="font-size: 12px; color: #1e293b;">Recommendation:</strong>
<p style="margin: 4px 0 0 0; font-size: 12px; color: #475569; white-space: pre-line;">{$ alert.recommendation $}</p>
</div>
<!-- Add to Firewall Button for Brute Force Attacks -->
<div ng-if="alert.title === 'Brute Force Attack Detected' && alert.details && alert.details['IP Address']" style="margin-top: 12px;">
<button ng-click="blockIPAddress(alert.details['IP Address'])"
ng-disabled="blockingIP === alert.details['IP Address']"
style="background: #dc2626; color: white; border: none; padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;"
onmouseover="this.style.background='#b91c1c'"
onmouseout="this.style.background='#dc2626'">
<i class="fas fa-ban" ng-if="blockingIP !== alert.details['IP Address']"></i>
<i class="fas fa-spinner fa-spin" ng-if="blockingIP === alert.details['IP Address']"></i>
<span ng-if="blockingIP !== alert.details['IP Address']">Block IP</span>
<span ng-if="blockingIP === alert.details['IP Address']">Blocking...</span>
</button>
<span ng-if="blockedIPs && blockedIPs[alert.details['IP Address']]"
style="margin-left: 10px; color: #10b981; font-size: 12px; font-weight: 600;">
<i class="fas fa-check-circle"></i> Blocked
</span>
</div>
</div>
<span style="background: {$ alert.severity === 'high' ? '#fee2e2' : (alert.severity === 'medium' ? '#fef3c7' : (alert.severity === 'low' ? '#dbeafe' : '#d1fae5')) $};
color: {$ alert.severity === 'high' ? '#dc2626' : (alert.severity === 'medium' ? '#f59e0b' : (alert.severity === 'low' ? '#3b82f6' : '#10b981')) $};

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,8 @@ 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'^blockIPAddress$', views.blockIPAddress, name='blockIPAddress'),
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
@ -820,25 +820,18 @@ def analyzeSSHSecurity(request):
alerts = []
# Detect which firewall is in use
firewall_cmd = ''
# Use firewalld (CSF has been discontinued)
firewall_cmd = 'firewalld'
try:
# Check for CSF
csf_check = ProcessUtilities.outputExecutioner('which csf')
if csf_check and '/csf' in csf_check:
firewall_cmd = 'csf'
# Verify firewalld is active
firewalld_check = ProcessUtilities.outputExecutioner('systemctl is-active firewalld')
if not (firewalld_check and 'active' in firewalld_check):
# Firewalld not active, but continue analysis with firewalld commands
pass
except:
# Continue with firewalld as default
pass
if not firewall_cmd:
try:
# Check for firewalld
firewalld_check = ProcessUtilities.outputExecutioner('systemctl is-active firewalld')
if firewalld_check and 'active' in firewalld_check:
firewall_cmd = 'firewalld'
except:
firewall_cmd = 'firewalld' # Default to firewalld
# Determine log path
distro = ProcessUtilities.decideDistro()
if distro in [ProcessUtilities.ubuntu, ProcessUtilities.ubuntu20]:
@ -941,10 +934,7 @@ def analyzeSSHSecurity(request):
# High severity: Brute force attacks
for ip, count in failed_passwords.items():
if count >= 10:
if firewall_cmd == 'csf':
recommendation = f'Block this IP immediately:\ncsf -d {ip} "Brute force attack - {count} failed attempts"'
else:
recommendation = f'Block this IP immediately:\nfirewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address={ip} drop" && firewall-cmd --reload'
recommendation = f'Block this IP immediately:\nfirewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address={ip} drop" && firewall-cmd --reload'
alerts.append({
'title': 'Brute Force Attack Detected',
@ -1108,6 +1098,144 @@ def analyzeSSHSecurity(request):
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_POST
def blockIPAddress(request):
"""
Block an IP address using the appropriate firewall (CSF or firewalld)
"""
try:
user_id = request.session.get('userID')
if not user_id:
return HttpResponse(json.dumps({'error': 'Not logged in'}), content_type='application/json', status=403)
currentACL = ACLManager.loadedACL(user_id)
if not currentACL.get('admin', 0):
return HttpResponse(json.dumps({'error': 'Admin only'}), content_type='application/json', status=403)
# Check if user has CyberPanel addons
if not ACLManager.CheckForPremFeature('all'):
return HttpResponse(json.dumps({
'status': 0,
'error': 'Premium feature required'
}), content_type='application/json', status=403)
data = json.loads(request.body)
ip_address = data.get('ip_address', '').strip()
if not ip_address:
return HttpResponse(json.dumps({
'status': 0,
'error': 'IP address is required'
}), content_type='application/json', status=400)
# Validate IP address format and check for private/reserved ranges
import re
import ipaddress
ip_pattern = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
if not re.match(ip_pattern, ip_address):
return HttpResponse(json.dumps({
'status': 0,
'error': 'Invalid IP address format'
}), content_type='application/json', status=400)
# Check for private/reserved IP ranges to prevent self-blocking
try:
ip_obj = ipaddress.ip_address(ip_address)
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local or ip_obj.is_reserved:
return HttpResponse(json.dumps({
'status': 0,
'error': 'Cannot block private, loopback, link-local, or reserved IP addresses'
}), content_type='application/json', status=400)
# Additional check for common problematic ranges
if (ip_address.startswith('127.') or # Loopback
ip_address.startswith('169.254.') or # Link-local
ip_address.startswith('224.') or # Multicast
ip_address.startswith('255.') or # Broadcast
ip_address in ['0.0.0.0', '::1']): # Invalid/loopback
return HttpResponse(json.dumps({
'status': 0,
'error': 'Cannot block system or reserved IP addresses'
}), content_type='application/json', status=400)
except ValueError:
return HttpResponse(json.dumps({
'status': 0,
'error': 'Invalid IP address'
}), content_type='application/json', status=400)
# Use firewalld (CSF has been discontinued)
firewall_cmd = 'firewalld'
try:
# Verify firewalld is active using subprocess for better security
import subprocess
firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'],
capture_output=True, text=True, timeout=10)
if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout):
return HttpResponse(json.dumps({
'status': 0,
'error': 'Firewalld is not active. Please enable firewalld service.'
}), content_type='application/json', status=500)
except subprocess.TimeoutExpired:
return HttpResponse(json.dumps({
'status': 0,
'error': 'Timeout checking firewalld status'
}), content_type='application/json', status=500)
except Exception as e:
return HttpResponse(json.dumps({
'status': 0,
'error': f'Cannot check firewalld status: {str(e)}'
}), content_type='application/json', status=500)
# Block the IP address using firewalld with subprocess for better security
success = False
error_message = ''
try:
# Use subprocess with explicit argument lists to prevent injection
rich_rule = f'rule family=ipv4 source address={ip_address} drop'
add_rule_cmd = ['firewall-cmd', '--permanent', '--add-rich-rule', rich_rule]
# Execute the add rule command
result = subprocess.run(add_rule_cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
# Reload firewall rules
reload_cmd = ['firewall-cmd', '--reload']
reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30)
if reload_result.returncode == 0:
success = True
else:
error_message = f'Failed to reload firewall rules: {reload_result.stderr}'
else:
error_message = f'Failed to add firewall rule: {result.stderr}'
except subprocess.TimeoutExpired:
error_message = 'Firewall command timed out'
except Exception as e:
error_message = f'Firewall command failed: {str(e)}'
if success:
# Log the action
import plogical.CyberCPLogFileWriter as logging
logging.CyberCPLogFileWriter.writeToFile(f'IP address {ip_address} blocked via CyberPanel dashboard by user {user_id}')
return HttpResponse(json.dumps({
'status': 1,
'message': f'Successfully blocked IP address {ip_address}',
'firewall': firewall_cmd
}), content_type='application/json')
else:
return HttpResponse(json.dumps({
'status': 0,
'error': error_message or 'Failed to block IP address'
}), content_type='application/json', status=500)
except Exception as e:
return HttpResponse(json.dumps({
'status': 0,
'error': f'Server error: {str(e)}'
}), content_type='application/json', status=500)
@csrf_exempt
@require_POST
def getSSHUserActivity(request):
@ -1305,3 +1433,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

@ -270,6 +270,11 @@ setup_epel_repo() {
yum install -y https://cyberpanel.sh/dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
Check_Return "yum repo" "no_exit"
;;
"10")
# AlmaLinux 10 EPEL support
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
Check_Return "yum repo" "no_exit"
;;
esac
}
@ -309,11 +314,12 @@ gpgcheck=1
EOF
elif [[ "$Server_OS_Version" = "10" ]] && uname -m | grep -q 'x86_64'; then
cat <<EOF >/etc/yum.repos.d/MariaDB.repo
# MariaDB 10.11 CentOS repository list - created 2021-08-06 02:01 UTC
# MariaDB 10.11 RHEL10 repository list - AlmaLinux 10 compatible
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.11/rhel9-amd64/
baseurl = http://yum.mariadb.org/10.11/rhel10-amd64/
module_hotfixes=1
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
enabled=1
gpgcheck=1
@ -549,12 +555,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 +576,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
@ -1112,8 +1123,14 @@ log_function_start "Pre_Install_Setup_Repository"
log_info "Setting up package repositories for $Server_OS $Server_OS_Version"
if [[ $Server_OS = "CentOS" ]] ; then
log_debug "Importing LiteSpeed GPG key"
rpm --import https://cyberpanel.sh/rpms.litespeedtech.com/centos/RPM-GPG-KEY-litespeed
#import the LiteSpeed GPG key
# Import LiteSpeed GPG key with fallback
rpm --import https://cyberpanel.sh/rpms.litespeedtech.com/centos/RPM-GPG-KEY-litespeed || {
warning "Primary GPG key import failed, trying alternative source"
rpm --import https://rpms.litespeedtech.com/centos/RPM-GPG-KEY-litespeed || {
error "Failed to import LiteSpeed GPG key from all sources"
return 1
}
}
yum clean all
yum autoremove -y epel-release
@ -1146,7 +1163,12 @@ if [[ $Server_OS = "CentOS" ]] ; then
dnf config-manager --set-enabled crb
fi
yum install -y https://rpms.remirepo.net/enterprise/remi-release-9.rpm
# Install appropriate remi-release based on version
if [[ "$Server_OS_Version" = "9" ]]; then
yum install -y https://rpms.remirepo.net/enterprise/remi-release-9.rpm
elif [[ "$Server_OS_Version" = "10" ]]; then
yum install -y https://rpms.remirepo.net/enterprise/remi-release-10.rpm
fi
Check_Return "yum repo" "no_exit"
fi
@ -1336,8 +1358,22 @@ if [[ "$Server_OS" = "CentOS" ]] || [[ "$Server_OS" = "openEuler" ]] ; then
#!/bin/bash
dnf install -y libnsl zip wget strace net-tools curl which bc telnet htop libevent-devel gcc libattr-devel xz-devel MariaDB-server MariaDB-client MariaDB-devel curl-devel git platform-python-devel tar socat python3 zip unzip bind-utils gpgme-devel openssl-devel
dnf install -y libnsl zip wget strace net-tools curl which bc telnet htop libevent-devel gcc libattr-devel xz-devel MariaDB-server MariaDB-client MariaDB-devel curl-devel git platform-python-devel tar socat python3 zip unzip bind-utils gpgme-devel openssl-devel boost-devel boost-program-options
Check_Return
# Fix boost library compatibility for galera-4 on AlmaLinux 10
if [[ "$Server_OS_Version" = "10" ]]; then
# Create symlink for boost libraries if needed
if [ ! -f /usr/lib64/libboost_program_options.so.1.75.0 ]; then
BOOST_VERSION=$(find /usr/lib64 -name "libboost_program_options.so.*" | head -1 | sed 's/.*libboost_program_options\.so\.//')
if [ -n "$BOOST_VERSION" ]; then
ln -sf /usr/lib64/libboost_program_options.so.$BOOST_VERSION /usr/lib64/libboost_program_options.so.1.75.0
log_info "Created boost library symlink for galera-4 compatibility: $BOOST_VERSION -> 1.75.0"
else
warning "Could not find boost libraries, galera-4 may not work properly"
fi
fi
fi
elif [[ "$Server_OS_Version" = "20" ]] || [[ "$Server_OS_Version" = "22" ]] || [[ "$Server_OS_Version" = "24" ]] ; then
dnf install -y libnsl zip wget strace net-tools curl which bc telnet htop libevent-devel gcc libattr-devel xz-devel mariadb-devel curl-devel git python3-devel tar socat python3 zip unzip bind-utils gpgme-devel
Check_Return
@ -2278,7 +2314,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']:
# Do not allow priviledged port numbers
if int(data[item]) < 1024 or int(data[item]) > 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]
# 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 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 = {}
for index, volume in volList.items():
volumes[volume['src']] = {'bind': volume['dest'],
'mode': 'rw'}
if volList:
for index, volume in volList.items():
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")
container.remove(force=True)
data_ret = {'createContainerStatus': 0, 'error_message': str(err)}
try:
container.remove(force=True)
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

@ -0,0 +1,121 @@
# Firewall Rules Export/Import Feature
## Overview
This feature allows CyberPanel administrators to export and import firewall rules between servers, making it easy to replicate security configurations across multiple servers.
## Features
### Export Functionality
- Exports all custom firewall rules to a JSON file
- Excludes default CyberPanel rules (CyberPanel Admin, SSHCustom) to prevent conflicts
- Includes metadata such as export timestamp and rule count
- Downloads file directly to the user's browser
### Import Functionality
- Imports firewall rules from a previously exported JSON file
- Validates file format before processing
- Skips duplicate rules (same name, protocol, port, and IP address)
- Excludes default CyberPanel rules from import
- Provides detailed import summary (imported, skipped, error counts)
- Shows specific error messages for failed imports
## Usage
### Exporting Rules
1. Navigate to the Firewall section in CyberPanel
2. Click the "Export Rules" button in the Firewall Rules panel header
3. The system will generate and download a JSON file containing your custom rules
### Importing Rules
1. Navigate to the Firewall section in CyberPanel
2. Click the "Import Rules" button in the Firewall Rules panel header
3. Select a previously exported JSON file
4. The system will process the import and show a summary of results
## File Format
The exported JSON file has the following structure:
```json
{
"version": "1.0",
"exported_at": "2024-01-15 14:30:25",
"total_rules": 5,
"rules": [
{
"name": "Custom Web Server",
"proto": "tcp",
"port": "8080",
"ipAddress": "0.0.0.0/0"
},
{
"name": "Database Access",
"proto": "tcp",
"port": "3306",
"ipAddress": "192.168.1.0/24"
}
]
}
```
## Security Considerations
- Only administrators can export/import firewall rules
- Default CyberPanel rules are excluded to prevent system conflicts
- Import process validates file format and rule data
- Failed imports are logged for troubleshooting
- Duplicate rules are automatically skipped
## Error Handling
The system provides comprehensive error handling:
- Invalid file format detection
- Missing required fields validation
- Individual rule import error tracking
- Detailed error messages for troubleshooting
- Import summary with counts of successful, skipped, and failed imports
## Technical Implementation
### Backend Components
- `exportFirewallRules()` method in `FirewallManager`
- `importFirewallRules()` method in `FirewallManager`
- New URL patterns for export/import endpoints
- File upload handling for import functionality
### Frontend Components
- Export/Import buttons in firewall UI
- File download handling for exports
- File upload dialog for imports
- Progress indicators and error messaging
- Import summary display
### Database Integration
- Uses existing `FirewallRules` model
- Maintains referential integrity
- Preserves rule relationships and constraints
## Benefits
1. **Time Efficiency**: Significantly reduces time to replicate firewall rules across servers
2. **Error Reduction**: Minimizes human error in manual rule creation
3. **Consistency**: Ensures identical security policies across multiple servers
4. **Backup**: Provides a way to backup and restore firewall configurations
5. **Migration**: Simplifies server migration and setup processes
## Compatibility
- Compatible with CyberPanel's existing firewall system
- Works with both TCP and UDP protocols
- Supports all IP address formats (single IPs, CIDR ranges)
- Maintains compatibility with existing firewall utilities
## Future Enhancements
Potential future improvements could include:
- Rule conflict detection and resolution
- Selective rule import (choose specific rules)
- Rule templates and presets
- Bulk rule management
- Integration with configuration management tools

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']
@ -146,6 +168,79 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def editRule(self, userID = None, data = None):
"""
Edit an existing firewall rule
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('edit_status', 0)
ruleID = data['id']
newRuleName = data['ruleName']
newRuleProtocol = data['ruleProtocol']
newRulePort = data['rulePort']
newRuleIP = data['ruleIP']
# Get the existing rule
try:
existingRule = FirewallRules.objects.get(id=ruleID)
except FirewallRules.DoesNotExist:
final_dic = {'status': 0, 'edit_status': 0, 'error_message': 'Rule not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Store old values for system firewall update
oldProtocol = existingRule.proto
oldPort = existingRule.port
oldIP = existingRule.ipAddress
# Check if any values actually changed
if (existingRule.name == newRuleName and
existingRule.proto == newRuleProtocol and
existingRule.port == newRulePort and
existingRule.ipAddress == newRuleIP):
final_dic = {'status': 1, 'edit_status': 1, 'error_message': "No changes detected"}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Check if another rule with the same name already exists (excluding current rule)
if existingRule.name != newRuleName:
duplicateRule = FirewallRules.objects.filter(name=newRuleName).exclude(id=ruleID).first()
if duplicateRule:
final_dic = {'status': 0, 'edit_status': 0, 'error_message': f'A rule with name "{newRuleName}" already exists'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Update the rule in the system firewall
# First remove the old rule
FirewallUtilities.deleteRule(oldProtocol, oldPort, oldIP)
# Then add the new rule
FirewallUtilities.addRule(newRuleProtocol, newRulePort, newRuleIP)
# Update the database record
existingRule.name = newRuleName
existingRule.proto = newRuleProtocol
existingRule.port = newRulePort
existingRule.ipAddress = newRuleIP
existingRule.save()
logging.CyberCPLogFileWriter.writeToFile(f"Firewall rule edited successfully. ID: {ruleID}, Name: {newRuleName}")
final_dic = {'status': 1, 'edit_status': 1, 'error_message': "None"}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'edit_status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def reloadFirewall(self, userID = None, data = None):
try:
@ -1733,6 +1828,164 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def exportFirewallRules(self, userID = None):
"""
Export all custom firewall rules to a JSON file, excluding default CyberPanel rules
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('exportStatus', 0)
# Get all firewall rules
rules = FirewallRules.objects.all()
# Default CyberPanel rules to exclude
default_rules = ['CyberPanel Admin', 'SSHCustom']
# Filter out default rules
custom_rules = []
for rule in rules:
if rule.name not in default_rules:
custom_rules.append({
'name': rule.name,
'proto': rule.proto,
'port': rule.port,
'ipAddress': rule.ipAddress
})
# Create export data with metadata
export_data = {
'version': '1.0',
'exported_at': time.strftime('%Y-%m-%d %H:%M:%S'),
'total_rules': len(custom_rules),
'rules': custom_rules
}
# Create JSON response with file download
json_content = json.dumps(export_data, indent=2)
logging.CyberCPLogFileWriter.writeToFile(f"Firewall rules exported successfully. Total rules: {len(custom_rules)}")
# Return file as download
response = HttpResponse(json_content, content_type='application/json')
response['Content-Disposition'] = f'attachment; filename="firewall_rules_export_{int(time.time())}.json"'
return response
except BaseException as msg:
final_dic = {'exportStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def importFirewallRules(self, userID = None, data = None):
"""
Import firewall rules from a JSON file
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('importStatus', 0)
# Handle file upload
if hasattr(self.request, 'FILES') and 'import_file' in self.request.FILES:
import_file = self.request.FILES['import_file']
# Read file content
import_data = json.loads(import_file.read().decode('utf-8'))
else:
# Fallback to file path method
import_file_path = data.get('import_file_path', '')
if not import_file_path or not os.path.exists(import_file_path):
final_dic = {'importStatus': 0, 'error_message': 'Import file not found or invalid path'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Read and parse the import file
with open(import_file_path, 'r') as f:
import_data = json.load(f)
# Validate the import data structure
if 'rules' not in import_data:
final_dic = {'importStatus': 0, 'error_message': 'Invalid import file format. Missing rules array.'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
imported_count = 0
skipped_count = 0
error_count = 0
errors = []
# Default CyberPanel rules to exclude from import
default_rules = ['CyberPanel Admin', 'SSHCustom']
for rule_data in import_data['rules']:
try:
# Skip default rules
if rule_data.get('name', '') in default_rules:
skipped_count += 1
continue
# Check if rule already exists
existing_rule = FirewallRules.objects.filter(
name=rule_data['name'],
proto=rule_data['proto'],
port=rule_data['port'],
ipAddress=rule_data['ipAddress']
).first()
if existing_rule:
skipped_count += 1
continue
# Add the rule to the system firewall
FirewallUtilities.addRule(
rule_data['proto'],
rule_data['port'],
rule_data['ipAddress']
)
# Add the rule to the database
new_rule = FirewallRules(
name=rule_data['name'],
proto=rule_data['proto'],
port=rule_data['port'],
ipAddress=rule_data['ipAddress']
)
new_rule.save()
imported_count += 1
except Exception as e:
error_count += 1
errors.append(f"Rule '{rule_data.get('name', 'Unknown')}': {str(e)}")
logging.CyberCPLogFileWriter.writeToFile(f"Error importing rule {rule_data.get('name', 'Unknown')}: {str(e)}")
logging.CyberCPLogFileWriter.writeToFile(f"Firewall rules import completed. Imported: {imported_count}, Skipped: {skipped_count}, Errors: {error_count}")
final_dic = {
'importStatus': 1,
'error_message': "None",
'imported_count': imported_count,
'skipped_count': skipped_count,
'error_count': error_count,
'errors': errors
}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'importStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)

View File

@ -16,6 +16,10 @@ app.controller('firewallController', function ($scope, $http) {
$scope.couldNotConnect = true;
$scope.rulesDetails = false;
// Edit modal variables
$scope.showEditModal = false;
$scope.editingRule = {};
firewallStatus();
populateCurrentRecords();
@ -503,6 +507,249 @@ app.controller('firewallController', function ($scope, $http) {
};
// Export/Import Functions
$scope.exportRules = function () {
$scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
url = "/firewall/exportFirewallRules";
var data = {};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(exportSuccess, exportError);
function exportSuccess(response) {
$scope.rulesLoading = true;
// Check if response is JSON (error) or file download
if (typeof response.data === 'string' && response.data.includes('{')) {
try {
var errorData = JSON.parse(response.data);
if (errorData.exportStatus === 0) {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = errorData.error_message;
return;
}
} catch (e) {
// If not JSON, assume it's the file content
}
}
// If we get here, it's a successful file download
$scope.actionFailed = true;
$scope.actionSuccess = false;
}
function exportError(response) {
$scope.rulesLoading = true;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
}
};
$scope.importRules = function () {
// Create file input element
var input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.display = 'none';
input.onchange = function(event) {
var file = event.target.files[0];
if (file) {
var reader = new FileReader();
reader.onload = function(e) {
try {
var importData = JSON.parse(e.target.result);
// Validate file format
if (!importData.rules || !Array.isArray(importData.rules)) {
$scope.$apply(function() {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = "Invalid import file format. Please select a valid firewall rules export file.";
});
return;
}
// Upload file to server
uploadImportFile(file);
} catch (error) {
$scope.$apply(function() {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = "Invalid JSON file. Please select a valid firewall rules export file.";
});
}
};
reader.readAsText(file);
}
};
document.body.appendChild(input);
input.click();
document.body.removeChild(input);
};
function uploadImportFile(file) {
$scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
var formData = new FormData();
formData.append('import_file', file);
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': undefined
},
transformRequest: angular.identity
};
$http.post("/firewall/importFirewallRules", formData, config).then(importSuccess, importError);
function importSuccess(response) {
$scope.rulesLoading = true;
if (response.data.importStatus === 1) {
$scope.actionFailed = true;
$scope.actionSuccess = false;
// Refresh rules list
populateCurrentRecords();
// Show import summary
var summary = `Import completed successfully!\n` +
`Imported: ${response.data.imported_count} rules\n` +
`Skipped: ${response.data.skipped_count} rules\n` +
`Errors: ${response.data.error_count} rules`;
if (response.data.errors && response.data.errors.length > 0) {
summary += `\n\nErrors:\n${response.data.errors.join('\n')}`;
}
alert(summary);
} else {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = response.data.error_message;
}
}
function importError(response) {
$scope.rulesLoading = true;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
}
}
// Edit Rule Functions
$scope.editRule = function(rule) {
$scope.editingRule = {
id: rule.id,
name: rule.name,
proto: rule.proto,
port: rule.port,
ipAddress: rule.ipAddress
};
$scope.showEditModal = true;
};
$scope.closeEditModal = function() {
$scope.showEditModal = false;
$scope.editingRule = {};
};
$scope.saveEditedRule = function() {
// Basic validation
if (!$scope.editingRule.name || !$scope.editingRule.port || !$scope.editingRule.ipAddress) {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = "Please fill in all required fields.";
return;
}
$scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
url = "/firewall/editRule";
var data = {
id: $scope.editingRule.id,
ruleName: $scope.editingRule.name,
ruleProtocol: $scope.editingRule.proto,
rulePort: $scope.editingRule.port,
ruleIP: $scope.editingRule.ipAddress
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(editSuccess, editError);
function editSuccess(response) {
$scope.rulesLoading = true;
if (response.data.edit_status === 1) {
// Close modal and refresh rules
$scope.closeEditModal();
populateCurrentRecords();
$scope.actionFailed = true;
$scope.actionSuccess = false;
} else {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = response.data.error_message;
}
}
function editError(response) {
$scope.rulesLoading = true;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
}
};
// Close modal when clicking outside or pressing Escape
$scope.$on('$locationChangeStart', function() {
$scope.closeEditModal();
});
// Keyboard support for modal
$(document).on('keydown', function(e) {
if ($scope.showEditModal && e.keyCode === 27) { // Escape key
$scope.$apply(function() {
$scope.closeEditModal();
});
}
});
// Focus management for modal
$scope.$watch('showEditModal', function(newVal) {
if (newVal) {
setTimeout(function() {
$('#editRuleModal input[name="ruleName"]').focus();
}, 100);
}
});
});

View File

@ -421,6 +421,205 @@
transform: scale(1.1);
}
.btn-edit {
background: var(--bg-info-light, #dbeafe);
color: var(--info-color, #3b82f6);
width: 32px;
height: 32px;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 700;
border: none;
}
.btn-edit:hover {
background: var(--info-color, #3b82f6);
color: var(--text-light, white);
transform: scale(1.1);
}
/* Export/Import Buttons */
.export-import-buttons {
display: flex;
gap: 0.75rem;
align-items: center;
}
.btn-export, .btn-import {
padding: 0.5rem 1rem;
border-radius: 8px;
font-weight: 500;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.3s ease;
border: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.2);
color: var(--text-light, white);
backdrop-filter: blur(10px);
}
.btn-export:hover, .btn-import:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.btn-export:disabled, .btn-import:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
/* Edit Modal Styles */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background: var(--bg-secondary, white);
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
animation: slideInUp 0.3s ease-out;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
padding: 1.5rem 2rem;
border-bottom: 1px solid var(--border-color, #e8e9ff);
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(135deg, var(--firewall-gradient-start, #ef4444) 0%, var(--firewall-gradient-end, #dc2626) 100%);
color: var(--text-light, white);
border-radius: 16px 16px 0 0;
}
.modal-title {
font-size: 1.25rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.75rem;
margin: 0;
}
.modal-close {
background: rgba(255, 255, 255, 0.2);
border: none;
color: var(--text-light, white);
width: 32px;
height: 32px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.modal-close:hover {
background: rgba(255, 255, 255, 0.3);
transform: scale(1.1);
}
.modal-body {
padding: 2rem;
}
.edit-rule-form {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.modal-footer {
padding: 1.5rem 2rem;
border-top: 1px solid var(--border-color, #e8e9ff);
display: flex;
gap: 1rem;
justify-content: flex-end;
background: var(--bg-tertiary, #f8f9ff);
border-radius: 0 0 16px 16px;
}
.btn-cancel, .btn-save {
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 500;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.3s ease;
border: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-width: 120px;
justify-content: center;
}
.btn-cancel {
background: var(--bg-muted, #f1f5f9);
color: var(--text-secondary, #475569);
}
.btn-cancel:hover {
background: var(--bg-muted-hover, #e2e8f0);
transform: translateY(-2px);
}
.btn-save {
background: var(--firewall-accent, #ef4444);
color: var(--text-light, white);
}
.btn-save:hover {
background: var(--firewall-accent-dark, #dc2626);
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--firewall-shadow, rgba(239, 68, 68, 0.3));
}
.btn-save:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
@ -600,7 +799,25 @@
</div>
{% trans "Firewall Rules" %}
</div>
<div ng-show="rulesLoading" class="loading-spinner"></div>
<div style="display: flex; align-items: center; gap: 1rem;">
<div ng-show="rulesLoading" class="loading-spinner"></div>
<div class="export-import-buttons">
<button type="button"
ng-click="exportRules()"
class="btn-export"
ng-disabled="rulesLoading">
<i class="fas fa-download"></i>
{% trans "Export Rules" %}
</button>
<button type="button"
ng-click="importRules()"
class="btn-import"
ng-disabled="rulesLoading">
<i class="fas fa-upload"></i>
{% trans "Import Rules" %}
</button>
</div>
</div>
</div>
<!-- Add Rule Section -->
@ -680,12 +897,20 @@
<span class="port-number">{$ rule.port $}</span>
</td>
<td>
<button type="button"
ng-click="deleteRule(rule.id, rule.proto, rule.port, rule.ipAddress)"
class="btn-delete"
title="{% trans 'Delete Rule' %}">
<i class="fas fa-times"></i>
</button>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<button type="button"
ng-click="editRule(rule)"
class="btn-edit"
title="{% trans 'Edit Rule' %}">
<i class="fas fa-edit"></i>
</button>
<button type="button"
ng-click="deleteRule(rule.id, rule.proto, rule.port, rule.ipAddress)"
class="btn-delete"
title="{% trans 'Delete Rule' %}">
<i class="fas fa-times"></i>
</button>
</div>
</td>
</tr>
</tbody>
@ -717,6 +942,75 @@
</div>
</div>
</div>
<!-- Edit Rule Modal -->
<div id="editRuleModal" class="modal" ng-show="showEditModal" ng-click="closeEditModal()">
<div class="modal-content" ng-click="$event.stopPropagation()">
<div class="modal-header">
<h3 class="modal-title">
<i class="fas fa-edit"></i>
{% trans "Edit Firewall Rule" %}
</h3>
<button type="button" class="modal-close" ng-click="closeEditModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<form class="edit-rule-form">
<div class="form-group">
<label class="form-label">{% trans "Rule Name" %}</label>
<input type="text"
class="form-control"
ng-model="editingRule.name"
name="ruleName"
placeholder="{% trans 'e.g., Allow SSH' %}"
required>
</div>
<div class="form-group">
<label class="form-label">{% trans "Protocol" %}</label>
<select ng-model="editingRule.proto" class="form-control select-control">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
</div>
<div class="form-group">
<label class="form-label">{% trans "IP Address" %}</label>
<input type="text"
class="form-control"
ng-model="editingRule.ipAddress"
placeholder="{% trans '0.0.0.0/0 for all IPs' %}"
required>
</div>
<div class="form-group">
<label class="form-label">{% trans "Port" %}</label>
<input type="text"
class="form-control"
ng-model="editingRule.port"
placeholder="{% trans 'e.g., 80' %}"
required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button"
class="btn-cancel"
ng-click="closeEditModal()">
<i class="fas fa-times"></i>
{% trans "Cancel" %}
</button>
<button type="button"
class="btn-save"
ng-click="saveEditedRule()"
ng-disabled="rulesLoading">
<i class="fas fa-save"></i>
{% trans "Save Changes" %}
</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -32,6 +32,13 @@ urlpatterns = [
path('modSecRulesPacks', views.modSecRulesPacks, name='modSecRulesPacks'),
path('getOWASPAndComodoStatus', views.getOWASPAndComodoStatus, name='getOWASPAndComodoStatus'),
path('installModSecRulesPack', views.installModSecRulesPack, name='installModSecRulesPack'),
# Firewall Export/Import
path('exportFirewallRules', views.exportFirewallRules, name='exportFirewallRules'),
path('importFirewallRules', views.importFirewallRules, name='importFirewallRules'),
# Firewall Rule Edit
path('editRule', views.editRule, name='editRule'),
path('getRulesFiles', views.getRulesFiles, name='getRulesFiles'),
path('enableDisableRuleFile', views.enableDisableRuleFile, name='enableDisableRuleFile'),

View File

@ -648,3 +648,36 @@ def saveLitespeed_conf(request):
return fm.saveLitespeed_conf(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def exportFirewallRules(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.exportFirewallRules(userID)
except KeyError:
return redirect(loadLoginPage)
def importFirewallRules(request):
try:
userID = request.session['userID']
fm = FirewallManager(request)
# Handle file upload
if request.method == 'POST' and 'import_file' in request.FILES:
return fm.importFirewallRules(userID, None)
else:
# Handle JSON data
return fm.importFirewallRules(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def editRule(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.editRule(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)

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

@ -0,0 +1,185 @@
# Firewall Blocking Feature for CyberPanel
## Overview
This feature adds a convenient "Block IP" button directly in the CyberPanel dashboard's SSH Security Analysis section, allowing administrators to quickly block malicious IP addresses without needing to access SSH or manually run firewall commands.
## Features
- **One-Click IP Blocking**: Block malicious IPs directly from the dashboard
- **Firewalld Integration**: Works with firewalld (the standard Linux firewall)
- **Visual Feedback**: Loading states, success notifications, and blocked status indicators
- **Security Integration**: Automatically appears on "Brute Force Attack Detected" alerts
- **Admin-Only Access**: Restricted to administrators with CyberPanel addons
## Implementation Details
### Backend Changes
#### 1. New API Endpoint (`/base/blockIPAddress`)
- **File**: `cyberpanel/baseTemplate/views.py`
- **Method**: POST
- **Authentication**: Admin-only with CyberPanel addons
- **Functionality**:
- Validates IP address format
- Verifies firewalld is active
- Blocks IP using firewalld commands
- Logs the action for audit purposes
#### 2. URL Configuration
- **File**: `cyberpanel/baseTemplate/urls.py`
- **Route**: `re_path(r'^blockIPAddress$', views.blockIPAddress, name='blockIPAddress')`
### Frontend Changes
#### 1. Template Updates
- **File**: `cyberpanel/baseTemplate/templates/baseTemplate/homePage.html`
- **Changes**:
- Added "Block IP" button for brute force attack alerts
- Visual feedback for blocking status
- Success indicators for blocked IPs
#### 2. JavaScript Functionality
- **File**: `cyberpanel/baseTemplate/static/baseTemplate/custom-js/system-status.js`
- **Features**:
- `blockIPAddress()` function for handling IP blocking
- Loading states and error handling
- Success notifications using PNotify
- Automatic security analysis refresh
## Usage
### Prerequisites
1. CyberPanel with admin privileges
2. CyberPanel addons enabled
3. Active firewalld service
### How to Use
1. Navigate to **Dashboard** in CyberPanel
2. Click on **SSH Logs** tab in the Activity Board
3. Click **Refresh Analysis** to scan for security threats
4. Look for **"Brute Force Attack Detected"** alerts
5. Click the **"Block IP"** button next to malicious IP addresses
6. Confirm the blocking action in the success notification
### Visual Indicators
- **Red "Block IP" Button**: Available for blocking
- **Spinning Icon**: Blocking in progress
- **Green "Blocked" Status**: IP successfully blocked
- **Notifications**: Success/error messages with details
## Firewall Commands Used
### firewalld
```bash
firewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address=<ip_address> drop"
firewall-cmd --reload
```
## Security Considerations
1. **Admin-Only Access**: Feature restricted to administrators
2. **Premium Feature**: Requires CyberPanel addons
3. **Enhanced IP Validation**: Validates IP address format and prevents blocking private/reserved ranges
4. **Command Injection Protection**: Uses subprocess with explicit argument lists
5. **Timeout Protection**: Prevents hanging processes with configurable timeouts
6. **Firewalld Verification**: Ensures firewalld service is active
7. **Audit Logging**: All blocking actions are logged
8. **Comprehensive Error Handling**: Detailed error messages with captured stderr
## Error Handling
The feature includes robust error handling for:
- Invalid IP addresses and formats
- Private/reserved IP address ranges
- Firewalld service not active
- Firewall command failures and timeouts
- Network connectivity issues
- Permission errors
- Command injection attempts
- Process timeouts
## Testing
A test script is provided for basic functionality testing:
- `test_firewall_blocking.py` - Basic functionality testing
The feature is best tested through the web interface with various IP address types.
## Enhanced Security Features
### IP Range Validation
The system now prevents blocking of:
- **Private IP ranges**: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
- **Loopback addresses**: 127.0.0.0/8
- **Link-local addresses**: 169.254.0.0/16
- **Multicast addresses**: 224.0.0.0/4
- **Broadcast addresses**: 255.255.255.255
- **Reserved addresses**: 0.0.0.0
### Command Execution Security
- **Subprocess with explicit arguments**: Prevents command injection
- **Timeout protection**: 10s for status checks, 30s for firewall commands
- **Error capture**: Captures both stdout and stderr for better debugging
- **No shell interpretation**: Eliminates shell injection vulnerabilities
### Example Error Messages
```
"Cannot block private, loopback, link-local, or reserved IP addresses"
"Cannot block system or reserved IP addresses"
"Timeout checking firewalld status"
"Firewall command timed out"
```
## Browser Compatibility
The feature uses modern web technologies and is compatible with:
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
## Future Enhancements
Potential improvements for future versions:
1. Bulk IP blocking for multiple threats
2. Temporary blocking with automatic unblocking
3. Integration with threat intelligence feeds
4. Custom blocking rules and policies
5. Blocking history and management interface
## Troubleshooting
### Common Issues
1. **"Premium feature required" error**
- Ensure CyberPanel addons are enabled
- Verify admin privileges
2. **"Failed to block IP address" error**
- Check firewalld service status: `systemctl status firewalld`
- Verify admin has necessary permissions
- Check firewalld configuration
3. **Button not appearing**
- Ensure SSH Security Analysis is enabled
- Check for brute force attack alerts
- Verify JavaScript is enabled
### Debug Information
Check CyberPanel logs for detailed error information:
- `/usr/local/CyberCP/logs/cyberpanel.log`
- Firewalld logs: `journalctl -u firewalld`
## Support
For issues or questions regarding this feature:
1. Check CyberPanel documentation
2. Review firewall configuration
3. Check system logs for detailed error messages
4. Contact CyberPanel support if needed
---
**Note**: This feature enhances CyberPanel's security capabilities by providing a streamlined way to block malicious IP addresses directly from the web interface, improving the overall user experience for server administrators.

View File

@ -11,9 +11,18 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
### 🤖 AI & Security
- **[AI Scanner Documentation](AIScannerDocs.md)** - Complete guide for CyberPanel's AI-powered security scanner
### 🛡️ Firewall & Security
- **[Firewall Blocking Feature](FIREWALL_BLOCKING_FEATURE.md)** - One-click IP blocking from dashboard with firewalld integration
### 📧 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 +30,22 @@ 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. **Need firewall protection?** Check the [Firewall Blocking Feature](FIREWALL_BLOCKING_FEATURE.md)
7. **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)
- **Firewall Protection**: [Firewall Blocking Feature](FIREWALL_BLOCKING_FEATURE.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
@ -39,12 +54,18 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
- Docker container management
- Command execution
- Security scanning
- Firewall IP blocking
### 🔧 **Integrations**
- Mautic email marketing
- 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

20
key.pem
View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDNDCCAhwCCQDEgz2Vkmv5NDANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJV
UzEPMA0GA1UECAwGRGVuaWFsMRQwEgYDVQQHDAtTcHJpbmdmaWVsZDEMMAoGA1UE
CgwDRGlzMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wHhcNMjIwODI2MTEwNzEy
WhcNMzIwODIzMTEwNzEyWjBcMQswCQYDVQQGEwJVUzEPMA0GA1UECAwGRGVuaWFs
MRQwEgYDVQQHDAtTcHJpbmdmaWVsZDEMMAoGA1UECgwDRGlzMRgwFgYDVQQDDA93
d3cuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq
BesL5DNNansmBYyn0DSqJyBOwGEIH/Ur4KFbKICrhy376gec2UyGG5lurgQa8Nz6
Gt7Z1B9LbLVE3bXS1f82bJHyPFUP8WmQuC+/3ZMRPFiG7/4n//0QwMptkDvGb5E1
0NXfJjuGHNfpVaHAs83v9mvUKc7oOjxwa+lbkhobD8HKByzAi/fpD90WQS9JRUJX
8lGquw+k5flL31AkqCyZOJw22VEoIpoF7RSh0xZvpsLze6G1thY5R27YWChcOR0E
m9/TUZ9/oCaP3WBvCldjr5OT6eZxJJzlt1jYndQKUyO5OtaJuNd4MkCNz9u9wwjE
CfZF7VQj8FABR9tqcVEfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGCNMmGXem6k
iqvJG2eFvuSdIh+x7x8EnXpRi2lW0ZqLYZfZuiMQL1fedhVoix+rzo9/Qhj6BTnt
5cC5oexhj/s76gqehNatMC0HAbIcK+MpvmwNoA/7U/bQAlbNxR3aLKSV0/B5YlTP
9yUoMtBqGEiesqAVAD26jOG5Ch1fHHElPtp3rE6qxGsAL0Eu+2Gezq3OqW2ejlvY
hZFpB/ZEynmYDUjT02+2J+3bAhfGaeUXC75YsbyfRAdc0OHa9/r7RhK+tgzmUhJt
sKUDW2OIwOTumUOSDgh1ayeTBddRAcMyIoGFeHF9degfJFiSo4vQrmaqFr9/bUFp
pn+y1/A3gNY=
-----END CERTIFICATE-----

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,65 +1,157 @@
import sys
sys.path.append('/usr/local/CyberCP')
import os
import gc
import time
from plogical import CyberCPLogFileWriter as logging
import shlex
import subprocess
import validators
import resource
class findBWUsage:
# Configuration constants
MAX_MEMORY_MB = 512 # Maximum memory usage in MB
MAX_PROCESSING_TIME = 300 # Maximum processing time in seconds (5 minutes)
MAX_LOG_LINES_PER_BATCH = 10000 # Process logs in batches
MAX_FILE_SIZE_MB = 100 # Skip files larger than 100MB
@staticmethod
def parse_last_digits(line):
return line.split(' ')
"""Safely parse log line and extract bandwidth data"""
try:
parts = line.split(' ')
if len(parts) < 10:
return None
# Extract the size field (index 9) and clean it
size_str = parts[9].replace('"', '').strip()
if size_str == '-':
return 0
return int(size_str)
except (ValueError, IndexError, AttributeError):
return None
@staticmethod
def get_file_size_mb(filepath):
"""Get file size in MB"""
try:
return os.path.getsize(filepath) / (1024 * 1024)
except OSError:
return 0
@staticmethod
def set_memory_limit():
"""Set memory limit to prevent system overload"""
try:
# Set memory limit to MAX_MEMORY_MB
memory_limit = findBWUsage.MAX_MEMORY_MB * 1024 * 1024 # Convert to bytes
resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Failed to set memory limit: {str(e)}")
@staticmethod
def calculateBandwidth(domainName):
"""Calculate bandwidth usage for a domain with memory protection"""
start_time = time.time()
try:
path = "/home/"+domainName+"/logs/"+domainName+".access_log"
path = "/home/" + domainName + "/logs/" + domainName + ".access_log"
if not os.path.exists(path):
return 0
from processUtilities import ProcessUtilities
logData = ProcessUtilities.outputExecutioner('cat %s' % (path), 'nobody').splitlines()
logDataLines = len(logData)
# Check file size before processing
file_size_mb = findBWUsage.get_file_size_mb(path)
if file_size_mb > findBWUsage.MAX_FILE_SIZE_MB:
logging.CyberCPLogFileWriter.writeToFile(f"Skipping large file {path} ({file_size_mb:.2f}MB)")
return 0
if not os.path.exists("/home/"+domainName+"/logs"):
if not os.path.exists("/home/" + domainName + "/logs"):
return 0
bwmeta = "/home/cyberpanel/%s.bwmeta" % (domainName)
if not os.path.exists(path):
writeMeta = open(bwmeta, 'w')
writeMeta.writelines('0\n0\n')
writeMeta.close()
os.chmod(bwmeta, 0o600)
return 1
# Initialize metadata
currentUsed = 0
currentLinesRead = 0
# Read existing metadata
if os.path.exists(bwmeta):
data = open(bwmeta).readlines()
currentUsed = int(data[0].strip("\n"))
currentLinesRead = int(data[1].strip("\n"))
if currentLinesRead > logDataLines:
try:
with open(bwmeta, 'r') as f:
data = f.readlines()
if len(data) >= 2:
currentUsed = int(data[0].strip("\n"))
currentLinesRead = int(data[1].strip("\n"))
except (ValueError, IndexError):
currentUsed = 0
currentLinesRead = 0
else:
currentUsed = 0
currentLinesRead = 0
startLine = currentLinesRead
# Process log file in streaming mode to avoid memory issues
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as logfile:
# Skip to the last processed line
for _ in range(currentLinesRead):
try:
next(logfile)
except StopIteration:
break
lines_processed = 0
batch_size = 0
for line in logfile:
# Check processing time limit
if time.time() - start_time > findBWUsage.MAX_PROCESSING_TIME:
logging.CyberCPLogFileWriter.writeToFile(f"Processing timeout for {domainName}")
break
line = line.strip()
if len(line) > 10:
bandwidth = findBWUsage.parse_last_digits(line)
if bandwidth is not None:
currentUsed += bandwidth
currentLinesRead += 1
lines_processed += 1
batch_size += 1
# Process in batches to manage memory
if batch_size >= findBWUsage.MAX_LOG_LINES_PER_BATCH:
# Force garbage collection
gc.collect()
batch_size = 0
# Check memory usage
try:
import psutil
process = psutil.Process()
memory_mb = process.memory_info().rss / (1024 * 1024)
if memory_mb > findBWUsage.MAX_MEMORY_MB:
logging.CyberCPLogFileWriter.writeToFile(f"Memory limit reached for {domainName}")
break
except ImportError:
pass # psutil not available, continue processing
except (IOError, OSError) as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error reading log file {path}: {str(e)}")
return 0
for line in logData[startLine:]:
line = line.strip('"\n')
currentLinesRead = currentLinesRead + 1
if len(line)>10:
currentUsed = int(findBWUsage.parse_last_digits(line)[9].replace('"', '')) + currentUsed
# Write updated metadata
try:
with open(bwmeta, 'w') as f:
f.write(f"{currentUsed}\n{currentLinesRead}\n")
os.chmod(bwmeta, 0o600)
except (IOError, OSError) as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error writing metadata {bwmeta}: {str(e)}")
return 0
writeMeta = open(bwmeta,'w')
writeMeta.writelines(str(currentUsed)+"\n")
writeMeta.writelines(str(currentLinesRead) + "\n")
writeMeta.close()
# Log processing statistics
processing_time = time.time() - start_time
if processing_time > 10: # Log if processing took more than 10 seconds
logging.CyberCPLogFileWriter.writeToFile(f"Processed {domainName}: {lines_processed} lines in {processing_time:.2f}s")
os.chmod(bwmeta, 0o600)
except BaseException as msg:
except Exception as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [calculateBandwidth]")
return 0
@ -67,67 +159,95 @@ class findBWUsage:
@staticmethod
def startCalculations():
"""Start bandwidth calculations with resource protection"""
try:
# Set memory limit
findBWUsage.set_memory_limit()
start_time = time.time()
domains_processed = 0
for directories in os.listdir("/home"):
# Check overall processing time
if time.time() - start_time > findBWUsage.MAX_PROCESSING_TIME * 2:
logging.CyberCPLogFileWriter.writeToFile("Overall processing timeout reached")
break
if validators.domain(directories):
findBWUsage.calculateBandwidth(directories)
except BaseException as msg:
try:
result = findBWUsage.calculateBandwidth(directories)
domains_processed += 1
# Force garbage collection after each domain
gc.collect()
# Small delay to prevent system overload
time.sleep(0.1)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error processing domain {directories}: {str(e)}")
continue
total_time = time.time() - start_time
logging.CyberCPLogFileWriter.writeToFile(f"Bandwidth calculation completed: {domains_processed} domains in {total_time:.2f}s")
except Exception as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [startCalculations]")
return 0
@staticmethod
def findDomainBW(domainName,totalAllowed):
def findDomainBW(domainName, totalAllowed):
"""Find domain bandwidth usage with improved error handling"""
try:
path = "/home/"+domainName+"/logs/"+domainName+".access_log"
path = "/home/" + domainName + "/logs/" + domainName + ".access_log"
if not os.path.exists("/home/"+domainName+"/logs"):
return [0,0]
if not os.path.exists("/home/" + domainName + "/logs"):
return [0, 0]
bwmeta = "/home/" + domainName + "/logs/bwmeta"
bwmeta = "/home/cyberpanel/%s.bwmeta" % (domainName)
if not os.path.exists(path):
return [0,0]
return [0, 0]
if os.path.exists(bwmeta):
try:
data = open(bwmeta).readlines()
with open(bwmeta, 'r') as f:
data = f.readlines()
if len(data) < 1:
return [0, 0]
currentUsed = int(data[0].strip("\n"))
inMB = int(float(currentUsed) / (1024.0 * 1024.0))
inMB = int(float(currentUsed)/(1024.0*1024.0))
if totalAllowed <= 0:
totalAllowed = 999999
percentage = float(100) / float(totalAllowed)
percentage = float(percentage) * float(inMB)
except:
return [0,0]
if percentage > 100.0:
percentage = 100
if percentage > 100.0:
percentage = 100
return [inMB,percentage]
return [inMB, percentage]
except (ValueError, IndexError, IOError):
return [0, 0]
else:
return [0, 0]
except OSError as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [findDomainBW]")
return 0
return [0, 0]
except ValueError as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [findDomainBW]")
return 0
return 1
return [0, 0]
@staticmethod
def changeSystemLanguage():
"""Change system language with improved error handling"""
try:
command = 'localectl set-locale LANG=en_US.UTF-8'
cmd = shlex.split(command)
res = subprocess.call(cmd)
if res == 1:
@ -135,12 +255,10 @@ class findBWUsage:
else:
pass
print("###############################################")
print(" Language Changed to English ")
print("###############################################")
except OSError as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [changeSystemLanguage]")
return 0
@ -151,4 +269,5 @@ class findBWUsage:
return 1
findBWUsage.startCalculations()
if __name__ == "__main__":
findBWUsage.startCalculations()

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')
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'
Upgrade.executioner(command, 'Install PHP 74, 0')
if Upgrade.installedOutput.find('lsphp80') == -1:
command = 'yum install lsphp80* -y'
subprocess.call(command, shell=True)
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'
Upgrade.stdOut("Installing PHP versions based on OS compatibility...", 1)
# Get available PHP versions
available_versions = Upgrade.get_available_php_versions()
if not available_versions:
Upgrade.stdOut("No PHP versions available for installation", 0)
return
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)
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()
@ -4158,6 +4350,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
@ -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:
@ -4200,6 +4419,42 @@ pm.max_spare_servers = 3
Upgrade.executioner(command, 'Start LSCPD', 0)
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'):

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

@ -1,5 +0,0 @@
<?
phpinfo();
?>

View File

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 "$@"

64
test_firewall_blocking.py Normal file
View File

@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
Test script for the new firewall blocking functionality
This script tests the blockIPAddress API endpoint
"""
import requests
import json
import sys
def test_firewall_blocking():
"""
Test the firewall blocking functionality
Note: This is a basic test script. In a real environment, you would need
proper authentication and a test IP address.
"""
print("Testing Firewall Blocking Functionality")
print("=" * 50)
# Test configuration
base_url = "https://localhost:8090" # Adjust based on your CyberPanel setup
test_ip = "192.168.1.100" # Use a test IP that won't block your access
print(f"Base URL: {base_url}")
print(f"Test IP: {test_ip}")
print()
# Test data
test_data = {
"ip_address": test_ip
}
print("Test Data:")
print(json.dumps(test_data, indent=2))
print()
print("Note: This test requires:")
print("1. Valid CyberPanel session with admin privileges")
print("2. CyberPanel addons enabled")
print("3. Active firewalld service")
print()
print("To test manually:")
print("1. Login to CyberPanel dashboard")
print("2. Go to Dashboard -> SSH Security Analysis")
print("3. Look for 'Brute Force Attack Detected' alerts")
print("4. Click the 'Block IP' button next to malicious IPs")
print()
print("Expected behavior:")
print("- Button shows loading state during blocking")
print("- Success notification appears on successful blocking")
print("- IP is marked as 'Blocked' in the interface")
print("- Security analysis refreshes to update alerts")
print()
print("Firewall Commands:")
print("- firewalld: firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=<ip> drop'")
print("- firewalld reload: firewall-cmd --reload")
print()
if __name__ == "__main__":
test_firewall_blocking()

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.