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:
commit
32a3fb7b67
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
43
README.md
43
README.md
|
|
@ -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/`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -6,3 +6,6 @@ from django.apps import AppConfig
|
|||
|
||||
class BasetemplateConfig(AppConfig):
|
||||
name = 'baseTemplate'
|
||||
|
||||
def ready(self):
|
||||
import baseTemplate.signals
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -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}"
|
||||
|
|
@ -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()
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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')) $};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
14
faq.sh
|
|
@ -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}
|
||||
"
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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'),
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
20
key.pem
|
|
@ -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
|
|
@ -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 %}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
?>
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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"
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "$@"
|
||||
|
|
@ -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()
|
||||
|
|
@ -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.
|
||||
Loading…
Reference in New Issue