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

V2.5.5 dev
This commit is contained in:
Usman Nasir
2025-09-14 22:14:52 +05:00
committed by GitHub
26 changed files with 1318 additions and 167 deletions

36
.env.template Normal file
View File

@@ -0,0 +1,36 @@
# CyberPanel Environment Configuration Template
# Copy this file to .env and update with your actual values
# NEVER commit .env to version control!
# Django Configuration
SECRET_KEY=your_very_long_random_secret_key_here_minimum_50_characters
DEBUG=False
ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.com
# Database Configuration - CyberPanel Database
DB_NAME=cyberpanel
DB_USER=cyberpanel
DB_PASSWORD=your_secure_cyberpanel_db_password_here
DB_HOST=localhost
DB_PORT=3306
# Root Database Configuration - MySQL Root Access
ROOT_DB_NAME=mysql
ROOT_DB_USER=root
ROOT_DB_PASSWORD=your_secure_mysql_root_password_here
ROOT_DB_HOST=localhost
ROOT_DB_PORT=3306
# Security Settings
SECURE_SSL_REDIRECT=False
SECURE_HSTS_SECONDS=0
SECURE_HSTS_INCLUDE_SUBDOMAINS=False
SECURE_HSTS_PRELOAD=False
SESSION_COOKIE_SECURE=False
CSRF_COOKIE_SECURE=False
# File Upload Settings
DATA_UPLOAD_MAX_MEMORY_SIZE=2147483648
# Logging Configuration
LOG_LEVEL=INFO

125
.gitignore vendored
View File

@@ -1,7 +1,120 @@
# CyberPanel .gitignore
# Environment variables (CRITICAL - Contains sensitive data)
.env
.env.backup
.env.local
.env.production
.env.staging
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
env/
ENV/
env.bak/
venv.bak/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
.AppleDouble
.LSOverride
*.pyc
.idea
venv
/.venv/
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
*.log
logs/
log/
# Database
*.db
*.sqlite3
# Temporary files
tmp/
temp/
*.tmp
# Backup files
*.bak
*.backup
# Node modules (if any frontend build tools are used)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Coverage reports
htmlcov/
.coverage
.coverage.*
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# CyberPanel specific
/usr/local/CyberCP/
/etc/cyberpanel/
cyberpanel_password.txt
mysql_password.txt

249
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,249 @@
# Contributing to CyberPanel
Thank you for your interest in contributing to CyberPanel! This document provides guidelines and information for contributors.
## 🌿 Branch Structure
CyberPanel uses a structured branching strategy to manage development and releases:
### **Branch Types**
1. **`stable`** - Production-ready stable branch
2. **`vX.X.X`** - Version-specific stable branch (e.g., `v2.4.3`)
3. **`vX.X.X-dev`** - Development branch for specific version (e.g., `v2.4.3-dev`)
## 🔄 Development Lifecycle
### **Development Process**
1. **Default Branch**: The latest `vX.X.X-dev` branch serves as the default (master) branch
2. **Contributions**: All contributors must push to the latest `vX.X.X-dev` branch
3. **Stability Check**: Once development is complete and believed to be stable, a new `vX.X.X` stable branch is created from the dev branch
4. **Merge Process**: The `vX.X.X` stable branch is then merged into the main `stable` branch
5. **New Development**: A new `vX.X.X-dev` branch is created and becomes the default branch
6. **Cleanup**: Old dev branches are deleted to save space
### **Important Rules**
-**DO**: Create pull requests only for the latest dev branch
-**DON'T**: Create pull requests for any other branches (stable, old dev branches, etc.)
- 🔄 **Development**: All development happens only in the latest dev branch
- 🗑️ **Cleanup**: Old dev branches are deleted after merging to stable
## 🚀 Getting Started
### **Prerequisites**
- Python 3.6+ (see supported versions in README.md)
- Django framework knowledge
- Basic understanding of web hosting control panels
- Git version control
### **Setup Development Environment**
1. **Fork the Repository**
```bash
# Fork the repository on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/cyberpanel.git
cd cyberpanel
```
2. **Add Upstream Remote**
```bash
git remote add upstream https://github.com/usmannasir/cyberpanel.git
```
3. **Create Development Branch**
```bash
# Checkout the latest dev branch
git checkout vX.X.X-dev
git pull upstream vX.X.X-dev
```
4. **Install Dependencies**
```bash
# Install Python dependencies
pip install -r requirements.txt
```
## 📝 Making Contributions
### **Code Style Guidelines**
- Follow PEP 8 for Python code
- Use meaningful variable and function names
- Add comments for complex logic
- Write comprehensive docstrings for functions and classes
- Ensure all code is properly tested
### **Commit Message Format**
Use clear, descriptive commit messages:
```
type(scope): brief description
Detailed description of changes made.
- List specific changes
- Explain why changes were made
- Reference any related issues
Fixes #123
```
**Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
### **Pull Request Process**
1. **Create Feature Branch**
```bash
git checkout -b feature/your-feature-name
```
2. **Make Changes**
- Write your code
- Add tests if applicable
- Update documentation if needed
3. **Test Your Changes**
```bash
# Run tests
python manage.py test
# Check for linting issues
flake8 .
```
4. **Commit Changes**
```bash
git add .
git commit -m "feat(module): add new feature"
```
5. **Push and Create PR**
```bash
git push origin feature/your-feature-name
```
Then create a pull request on GitHub targeting the latest dev branch.
## 🧪 Testing
### **Test Requirements**
- All new features must include tests
- Bug fixes must include regression tests
- Ensure all existing tests pass
- Maintain or improve test coverage
### **Running Tests**
```bash
# Run all tests
python manage.py test
# Run specific test module
python manage.py test module_name.tests
# Run with coverage
coverage run --source='.' manage.py test
coverage report
```
## 📋 Issue Reporting
### **Before Reporting**
- Check existing issues to avoid duplicates
- Ensure you're using the latest version
- Verify the issue exists in the latest dev branch
### **Issue Template**
When creating an issue, include:
- **OS and Version**: Your operating system and CyberPanel version
- **Steps to Reproduce**: Clear, numbered steps
- **Expected Behavior**: What should happen
- **Actual Behavior**: What actually happens
- **Screenshots**: If applicable
- **Logs**: Relevant error logs from `/usr/local/lscp/logs/`
## 🔒 Security
### **Security Issues**
For security-related issues:
- **DO NOT** create public issues
- Email security concerns to: security@cyberpanel.net
- Include detailed information about the vulnerability
- Allow time for the team to address before public disclosure
## 📚 Documentation
### **Documentation Guidelines**
- Update relevant documentation when adding features
- Use clear, concise language
- Include code examples where helpful
- Follow the existing documentation style
- Update README.md if adding new features or changing installation process
## 🤝 Code Review Process
### **Review Criteria**
- Code quality and style
- Test coverage
- Documentation updates
- Security considerations
- Performance impact
- Backward compatibility
### **Review Timeline**
- Initial review: Within 48 hours
- Follow-up reviews: Within 24 hours
- Merge decision: Within 1 week (for approved PRs)
## 🏷️ Release Process
### **Version Numbering**
CyberPanel follows semantic versioning (MAJOR.MINOR.PATCH):
- **MAJOR**: Breaking changes
- **MINOR**: New features (backward compatible)
- **PATCH**: Bug fixes (backward compatible)
### **Release Schedule**
- **Stable Releases**: Monthly or as needed
- **Hotfixes**: As critical issues arise
- **Development**: Continuous integration
## 💬 Community
### **Getting Help**
- 📚 [Documentation](https://cyberpanel.net/KnowledgeBase/)
- 💬 [Discord](https://discord.gg/g8k8Db3)
- 📢 [Forums](https://community.cyberpanel.net)
- 📵 [Facebook Group](https://www.facebook.com/groups/cyberpanel)
### **Contributing Guidelines**
- Be respectful and constructive
- Help others learn and grow
- Follow the code of conduct
- Ask questions when unsure
## 📄 License
By contributing to CyberPanel, you agree that your contributions will be licensed under the same license as the project (GPL-3.0).
---
**Thank you for contributing to CyberPanel!** 🎉
Your contributions help make web hosting management easier for thousands of users worldwide.

View File

@@ -13,6 +13,14 @@ https://docs.djangoproject.com/en/1.11/ref/settings/
import os
from django.utils.translation import gettext_lazy as _
# Load environment variables from .env file
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
# dotenv not available, continue without it
pass
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -20,12 +28,13 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'xr%j*p!*$0d%(-(e%@-*hyoz4$f%y77coq0u)6pwmjg4)q&19f'
SECRET_KEY = os.getenv('SECRET_KEY', 'xr%j*p!*$0d%(-(e%@-*hyoz4$f%y77coq0u)6pwmjg4)q&19f')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
ALLOWED_HOSTS = ['*']
# Allow configuration via environment variable, fallback to wildcard for backward compatibility
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '*').split(',')
# Application definition
@@ -96,6 +105,7 @@ TEMPLATES = [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'baseTemplate.context_processors.version_context',
'baseTemplate.context_processors.cosmetic_context',
],
},
},
@@ -110,19 +120,19 @@ WSGI_APPLICATION = 'CyberCP.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'cyberpanel',
'USER': 'cyberpanel',
'PASSWORD': 'SLTUIUxqhulwsh',
'HOST': 'localhost',
'PORT':''
'NAME': os.getenv('DB_NAME', 'cyberpanel'),
'USER': os.getenv('DB_USER', 'cyberpanel'),
'PASSWORD': os.getenv('DB_PASSWORD', 'SLTUIUxqhulwsh'),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', '3306'),
},
'rootdb': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mysql',
'USER': 'root',
'PASSWORD': 'SLTUIUxqhulwsh',
'HOST': 'localhost',
'PORT': '',
'NAME': os.getenv('ROOT_DB_NAME', 'mysql'),
'USER': os.getenv('ROOT_DB_USER', 'root'),
'PASSWORD': os.getenv('ROOT_DB_PASSWORD', 'SLTUIUxqhulwsh'),
'HOST': os.getenv('ROOT_DB_HOST', 'localhost'),
'PORT': os.getenv('ROOT_DB_PORT', '3306'),
},
}
DATABASE_ROUTERS = ['backup.backupRouter.backupRouter']

View File

@@ -145,6 +145,7 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr
- 🎓 [Docs (Old)](https://community.cyberpanel.net/docs)
- 📖 [Additional Guides](guides/INDEX.md) - Detailed guides for Docker, AI Scanner, Mautic, and more
- 📚 [Local Documentation](guides/) - All guides available in this repository
- 🤝 [Contributing Guide](CONTRIBUTING.md) - How to contribute to CyberPanel development
- ✅ [Changelog](https://community.cyberpanel.net/t/change-logs/161)
- 💬 [Forums](https://community.cyberpanel.net)
- 📢 [Discord](https://discord.gg/g8k8Db3)

193
SECURITY_INSTALLATION.md Normal file
View File

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

View File

@@ -2074,6 +2074,8 @@ app.controller('scheduleBackup', function ($scope, $http, $window) {
$scope.allSites = response.data.allSites;
$scope.lastRun = response.data.lastRun;
$scope.currentStatus = response.data.currentStatus;
$scope.backupFrequency = response.data.currently;
$scope.backupRetention = response.data.retention;
} else {
new PNotify({

View File

@@ -7,4 +7,20 @@ def version_context(request):
'CYBERPANEL_VERSION': VERSION,
'CYBERPANEL_BUILD': BUILD,
'CYBERPANEL_FULL_VERSION': f"{VERSION}.{BUILD}"
}
}
def cosmetic_context(request):
"""Add cosmetic data (custom CSS) to all templates"""
try:
from .models import CyberPanelCosmetic
cosmetic = CyberPanelCosmetic.objects.get(pk=1)
return {
'cosmetic': cosmetic
}
except:
from .models import CyberPanelCosmetic
cosmetic = CyberPanelCosmetic()
cosmetic.save()
return {
'cosmetic': cosmetic
}

View File

@@ -15,6 +15,11 @@
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/bootstrap/css/bootstrap.min.css' %}?v={{ CP_VERSION }}">
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/bootstrap-toggle.min.css' %}?v={{ CP_VERSION }}">
<!-- Custom CSS -->
<style>
{{ cosmetic.MainDashboardCSS | safe }}
</style>
<!-- Core Scripts -->
<script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

View File

@@ -1,14 +0,0 @@
Branches
1.Stable-> Stable branch
2.vX.X.X-> vX.X.X Stable branch
3.vX.X.X-dev-> v.X.X.X Dev branch
Development Lifecycle
vX.X.X-dev will be default(master) branch. All contributors must push to latest vX.X.X-dev branch. Once development
is complete(believed to be stable) new vX.X.X Stable branch will be created from Dev branch. Then vX.X.X Stable will
be merged into Stable branch. After that a new vX.X.X-dev branch will be created and it will be default(master)
branch. Old dev branch will be deleted at this stage(to save space) and no development will happen on old stable or
dev(if not deleted) branch. All development will only take place in latest dev branch. You must not create pull
request for any other branches other than latest dev branch.

177
install/env_generator.py Normal file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""
CyberPanel Environment Configuration Generator
Generates secure .env file with random passwords during installation
"""
import os
import sys
import secrets
import string
from pathlib import Path
def generate_secure_password(length=24):
"""
Generate a cryptographically secure password
Args:
length: Length of the password to generate (default 24)
Returns:
str: Random password containing uppercase, lowercase, digits and safe special chars
"""
# Use safe characters that don't require escaping in most contexts
safe_chars = string.ascii_letters + string.digits + '!@#$%^&*'
return ''.join(secrets.choice(safe_chars) for _ in range(length))
def generate_secret_key(length=64):
"""
Generate a cryptographically secure Django secret key
Args:
length: Length of the secret key to generate (default 64)
Returns:
str: Random secret key
"""
chars = string.ascii_letters + string.digits + '!@#$%^&*(-_=+)'
return ''.join(secrets.choice(chars) for _ in range(length))
def create_env_file(cyberpanel_path, mysql_root_password=None, cyberpanel_db_password=None):
"""
Create .env file with generated secure credentials
Args:
cyberpanel_path: Path to CyberPanel installation directory
mysql_root_password: Optional MySQL root password (will generate if None)
cyberpanel_db_password: Optional CyberPanel DB password (will generate if None)
"""
# Generate secure passwords if not provided
if not mysql_root_password:
mysql_root_password = generate_secure_password(24)
if not cyberpanel_db_password:
cyberpanel_db_password = generate_secure_password(24)
secret_key = generate_secret_key(64)
# Get hostname for ALLOWED_HOSTS
import socket
try:
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
except:
hostname = 'localhost'
local_ip = '127.0.0.1'
# Create .env content
env_content = f"""# CyberPanel Environment Configuration
# Generated automatically during installation - DO NOT EDIT MANUALLY
# Generated on: {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
# Django Configuration
SECRET_KEY={secret_key}
DEBUG=False
ALLOWED_HOSTS=localhost,127.0.0.1,{hostname},{local_ip}
# Database Configuration - CyberPanel Database
DB_NAME=cyberpanel
DB_USER=cyberpanel
DB_PASSWORD={cyberpanel_db_password}
DB_HOST=localhost
DB_PORT=3306
# Root Database Configuration - MySQL Root Access
ROOT_DB_NAME=mysql
ROOT_DB_USER=root
ROOT_DB_PASSWORD={mysql_root_password}
ROOT_DB_HOST=localhost
ROOT_DB_PORT=3306
# Security Settings
SECURE_SSL_REDIRECT=False
SECURE_HSTS_SECONDS=0
SECURE_HSTS_INCLUDE_SUBDOMAINS=False
SECURE_HSTS_PRELOAD=False
SESSION_COOKIE_SECURE=False
CSRF_COOKIE_SECURE=False
# File Upload Settings
DATA_UPLOAD_MAX_MEMORY_SIZE=2147483648
# Logging Configuration
LOG_LEVEL=INFO
"""
# Write .env file
env_file_path = os.path.join(cyberpanel_path, '.env')
with open(env_file_path, 'w') as f:
f.write(env_content)
# Set secure permissions (owner read/write only)
os.chmod(env_file_path, 0o600)
print(f"✓ Generated secure .env file at: {env_file_path}")
print(f"✓ MySQL Root Password: {mysql_root_password}")
print(f"✓ CyberPanel DB Password: {cyberpanel_db_password}")
print(f"✓ Django Secret Key: {secret_key[:20]}...")
return {
'mysql_root_password': mysql_root_password,
'cyberpanel_db_password': cyberpanel_db_password,
'secret_key': secret_key
}
def create_env_backup(cyberpanel_path, credentials):
"""
Create a secure backup of credentials for recovery purposes
Args:
cyberpanel_path: Path to CyberPanel installation directory
credentials: Dictionary containing generated credentials
"""
backup_content = f"""# CyberPanel Credentials Backup
# Generated: {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
#
# IMPORTANT: Store this file securely and delete it after recording credentials
# These are your database passwords and should be kept confidential
MySQL Root Password: {credentials['mysql_root_password']}
CyberPanel Database Password: {credentials['cyberpanel_db_password']}
Django Secret Key: {credentials['secret_key']}
# To restore these credentials, copy them to your .env file
"""
backup_file_path = os.path.join(cyberpanel_path, '.env.backup')
with open(backup_file_path, 'w') as f:
f.write(backup_content)
# Set secure permissions (owner read/write only)
os.chmod(backup_file_path, 0o600)
print(f"✓ Created credentials backup at: {backup_file_path}")
print("⚠️ IMPORTANT: Record these credentials and delete the backup file for security")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python env_generator.py <cyberpanel_path> [mysql_root_password] [cyberpanel_db_password]")
sys.exit(1)
cyberpanel_path = sys.argv[1]
mysql_root_password = sys.argv[2] if len(sys.argv) > 2 else None
cyberpanel_db_password = sys.argv[3] if len(sys.argv) > 3 else None
if not os.path.exists(cyberpanel_path):
print(f"Error: CyberPanel path does not exist: {cyberpanel_path}")
sys.exit(1)
try:
credentials = create_env_file(cyberpanel_path, mysql_root_password, cyberpanel_db_password)
create_env_backup(cyberpanel_path, credentials)
print("\n✓ Environment configuration generated successfully!")
print("✓ Remember to delete .env.backup after recording credentials")
except Exception as e:
print(f"Error generating environment configuration: {e}")
sys.exit(1)

View File

@@ -504,6 +504,73 @@ class preFlightsChecks:
self.stdOut("Install psmisc")
self.install_package("psmisc")
def generate_secure_env_file(self, mysql_root_password, cyberpanel_db_password):
"""
Generate secure .env file with random passwords during installation
"""
try:
import sys
import socket
# Import the environment generator
sys.path.append(os.path.join(self.cyberPanelPath, 'install'))
from env_generator import create_env_file, create_env_backup
# Generate secure credentials
credentials = create_env_file(
self.cyberPanelPath,
mysql_root_password,
cyberpanel_db_password
)
# Create backup for recovery
create_env_backup(self.cyberPanelPath, credentials)
logging.InstallLog.writeToFile("✓ Secure .env file generated successfully")
logging.InstallLog.writeToFile("✓ Credentials backup created for recovery")
return credentials
except Exception as e:
logging.InstallLog.writeToFile(f"[ERROR] Failed to generate secure environment file: {str(e)}")
# Fallback to original method if environment generation fails
self.fallback_settings_update(mysql_root_password, cyberpanel_db_password)
def fallback_settings_update(self, mysqlPassword, password):
"""
Fallback method to update settings.py directly if environment generation fails
"""
logging.InstallLog.writeToFile("Using fallback method for settings.py update")
path = self.cyberPanelPath + "/CyberCP/settings.py"
data = open(path, "r").readlines()
writeDataToFile = open(path, "w")
counter = 0
for items in data:
if items.find('SECRET_KEY') > -1:
SK = "SECRET_KEY = '%s'\n" % (generate_pass(50))
writeDataToFile.writelines(SK)
continue
if items.find("'PASSWORD':") > -1:
if counter == 0:
writeDataToFile.writelines(" 'PASSWORD': '" + mysqlPassword + "'," + "\n")
counter = counter + 1
else:
writeDataToFile.writelines(" 'PASSWORD': '" + password + "'," + "\n")
elif items.find('127.0.0.1') > -1:
writeDataToFile.writelines(" 'HOST': 'localhost',\n")
elif items.find("'PORT':'3307'") > -1:
writeDataToFile.writelines(" 'PORT': '',\n")
else:
writeDataToFile.writelines(items)
if self.distro == ubuntu:
os.fchmod(writeDataToFile.fileno(), stat.S_IRUSR | stat.S_IWUSR)
writeDataToFile.close()
def download_install_CyberPanel(self, mysqlPassword, mysql):
##
@@ -549,50 +616,12 @@ password="%s"
logging.InstallLog.writeToFile("Updating /root/.my.cnf!")
logging.InstallLog.writeToFile("Updating settings.py!")
logging.InstallLog.writeToFile("Generating secure environment configuration!")
path = self.cyberPanelPath + "/CyberCP/settings.py"
# Generate secure environment file instead of hardcoding passwords
self.generate_secure_env_file(mysqlPassword, password)
data = open(path, "r").readlines()
writeDataToFile = open(path, "w")
counter = 0
for items in data:
if items.find('SECRET_KEY') > -1:
SK = "SECRET_KEY = '%s'\n" % (generate_pass(50))
writeDataToFile.writelines(SK)
continue
if mysql == 'Two':
if items.find("'PASSWORD':") > -1:
if counter == 0:
writeDataToFile.writelines(" 'PASSWORD': '" + mysqlPassword + "'," + "\n")
counter = counter + 1
else:
writeDataToFile.writelines(" 'PASSWORD': '" + password + "'," + "\n")
else:
writeDataToFile.writelines(items)
else:
if items.find("'PASSWORD':") > -1:
if counter == 0:
writeDataToFile.writelines(" 'PASSWORD': '" + mysqlPassword + "'," + "\n")
counter = counter + 1
else:
writeDataToFile.writelines(" 'PASSWORD': '" + password + "'," + "\n")
elif items.find('127.0.0.1') > -1:
writeDataToFile.writelines(" 'HOST': 'localhost',\n")
elif items.find("'PORT':'3307'") > -1:
writeDataToFile.writelines(" 'PORT': '',\n")
else:
writeDataToFile.writelines(items)
if self.distro == ubuntu:
os.fchmod(writeDataToFile.fileno(), stat.S_IRUSR | stat.S_IWUSR)
writeDataToFile.close()
logging.InstallLog.writeToFile("Environment configuration generated successfully!")
if self.remotemysql == 'ON':
command = "sed -i 's|localhost|%s|g' %s" % (self.mysqlhost, path)

View File

@@ -242,6 +242,16 @@
margin-right: 0.5rem;
}
.password-hint {
color: var(--text-secondary, #64748b);
margin-top: 0.5rem;
display: block;
}
.action-section {
margin-top: 2rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@@ -324,7 +334,7 @@
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Select Website" %}</label>
<select ng-change="showEmailDetails()" ng-model="emailDomain" class="form-control">
<select ng-change="showEmailDetails()" ng-model="emailDomain" class="form-control" aria-label="{% trans 'Select Website' %}">
<option value="">{% trans "Choose a website..." %}</option>
{% for items in websiteList %}
<option value="{{ items }}">{{ items }}</option>
@@ -345,7 +355,7 @@
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Select Email Account" %}</label>
<select ng-model="selectedEmail" class="form-control">
<select ng-model="selectedEmail" class="form-control" aria-label="{% trans 'Select Email Account' %}">
<option value="">{% trans "Choose an email account..." %}</option>
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
</select>
@@ -374,19 +384,19 @@
<label class="form-label">{% trans "Generated Password" %}</label>
<div class="input-group">
<input type="text" name="email" class="form-control"
ng-model="emailPassword" readonly>
ng-model="emailPassword" readonly aria-label="{% trans 'Generated Password' %}">
<button type="button" ng-click="usePassword()" class="btn-secondary">
<i class="fas fa-check"></i>
{% trans "Use This Password" %}
</button>
</div>
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block;">
<small class="password-hint">
<i class="fas fa-info-circle"></i>
{% trans "Make sure to save this password in a secure location" %}
</small>
</div>
<div style="margin-top: 2rem;">
<div class="action-section">
<button type="button" ng-click="changePassword()" class="btn-primary">
<i class="fas fa-save"></i>
{% trans "Change Password" %}

View File

@@ -135,6 +135,7 @@
background: var(--bg-hover, #f8f9ff);
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 8px;
min-width: -webkit-fill-available;
min-width: fit-content;
}
@@ -304,6 +305,16 @@
width: 100%;
background: var(--success-color, #10b981);
}
.password-hint {
color: var(--text-secondary, #64748b);
margin-top: 0.5rem;
display: block;
}
.action-section {
margin-top: 2rem;
}
</style>
<div class="modern-container" ng-controller="createEmailAccount">
@@ -320,7 +331,7 @@
<h2 class="card-title">
<i class="fas fa-user-plus"></i>
{% trans "New Email Account" %}
<a target="_blank" href="https://platform.cyberpersons.com/MailTester/MailTester?utm_source=from-cyberpanel-inside&utm_medium=from-cyberpanel-inside&utm_campaign=from-cyberpanel-inside&utm_id=from-cyberpanel-inside&utm_term=from-cyberpanel-inside"
<a target="_blank" rel="noopener" href="https://platform.cyberpersons.com/MailTester/MailTester?utm_source=from-cyberpanel-inside&utm_medium=from-cyberpanel-inside&utm_campaign=from-cyberpanel-inside&utm_id=from-cyberpanel-inside&utm_term=from-cyberpanel-inside"
class="test-email-link">
<i class="fas fa-vial"></i>
{% trans "Test Email Delivery" %}
@@ -346,7 +357,7 @@
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Select Website" %}</label>
<select ng-change="showEmailDetails()" ng-model="emailDomain" class="form-control">
<select ng-change="showEmailDetails()" ng-model="emailDomain" class="form-control" aria-label="{% trans 'Select Website' %}">
<option value="">{% trans "Choose a website..." %}</option>
{% for items in websiteList %}
<option value="{{ items }}">{{ items }}</option>
@@ -397,19 +408,19 @@
<label class="form-label">{% trans "Generated Password" %}</label>
<div class="input-group">
<input type="text" name="email" class="form-control"
ng-model="emailPassword" readonly>
ng-model="emailPassword" readonly aria-label="{% trans 'Generated Password' %}">
<button type="button" ng-click="usePassword()" class="btn-secondary">
<i class="fas fa-check"></i>
{% trans "Use This Password" %}
</button>
</div>
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block;">
<small class="password-hint">
<i class="fas fa-info-circle"></i>
{% trans "Make sure to save this password in a secure location" %}
</small>
</div>
<div style="margin-top: 2rem;">
<div class="action-section">
<button type="button" ng-click="createEmailAccount()" class="btn-primary">
<i class="fas fa-plus-circle"></i>
{% trans "Create Email Account" %}

View File

@@ -323,7 +323,7 @@
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Select Website" %}</label>
<select ng-change="showEmailDetails()" ng-model="emailDomain" class="form-control">
<select ng-change="showEmailDetails()" ng-model="emailDomain" class="form-control" aria-label="{% trans 'Select Website' %}">
<option value="">{% trans "Choose a website..." %}</option>
{% for items in websiteList %}
<option value="{{ items }}">{{ items }}</option>
@@ -339,7 +339,7 @@
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Select Email Account" %}</label>
<select ng-model="selectedEmail" class="form-control">
<select ng-model="selectedEmail" class="form-control" aria-label="{% trans 'Select Email Account' %}">
<option value="">{% trans "Choose an email account..." %}</option>
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
</select>

View File

@@ -307,6 +307,50 @@
margin-right: 0.5rem;
}
.install-icon {
color: #5b5fcf;
}
.progress-title {
color: var(--text-primary, #1e293b);
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
}
.terminal-pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
.table-domain {
width: 20%;
}
.table-private-key {
width: 40%;
}
.table-public-key {
width: 40%;
}
.domain-badge {
padding: 0.5rem 1rem;
background: var(--accent-light, #e0e7ff);
color: #5b5fcf;
border-radius: 6px;
font-weight: 600;
display: inline-block;
}
.key-hint {
color: var(--text-secondary, #64748b);
margin-top: 0.5rem;
display: block;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@@ -359,7 +403,7 @@
<h1 class="page-title">
<i class="fas fa-shield-alt"></i>
{% trans "DKIM Manager" %}
<a target="_blank" href="https://cyberpanel.net/KnowledgeBase/home/dkim-set-up-and-configurations/" class="docs-link">
<a target="_blank" rel="noopener" href="https://cyberpanel.net/KnowledgeBase/home/dkim-set-up-and-configurations/" class="docs-link">
<i class="fas fa-book"></i>
{% trans "DKIM Docs" %}
</a>
@@ -378,7 +422,7 @@
</div>
<div class="card-body">
<div class="install-notice">
<i class="fas fa-exclamation-circle fa-3x mb-3" style="color: #5b5fcf;"></i>
<i class="fas fa-exclamation-circle fa-3x mb-3 install-icon"></i>
<h3>{% trans "OpenDKIM is not installed" %}</h3>
<p class="mb-3">{% trans "OpenDKIM is required to manage DKIM keys for your domains" %}</p>
<a href="" ng-click="installOpenDKIM()" class="install-link">
@@ -407,12 +451,12 @@
<!-- Installation Log -->
<div ng-hide="openDKIMInstallBox">
<h3 style="color: var(--text-primary, #1e293b); font-size: 1.125rem; font-weight: 600; margin-bottom: 1rem;">
<h3 class="progress-title">
<i class="fas fa-terminal"></i>
{% trans "Installation Progress" %}
</h3>
<div class="terminal-box">
<pre ng-bind="requestData" style="margin: 0; white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre ng-bind="requestData" class="terminal-pre"></pre>
</div>
</div>
</div>
@@ -438,7 +482,7 @@
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Select Website" %}</label>
<select ng-change="fetchKeys()" ng-model="domainName" class="form-control">
<select ng-change="fetchKeys()" ng-model="domainName" class="form-control" aria-label="{% trans 'Select Website' %}">
<option value="">{% trans "Choose a website..." %}</option>
{% for items in websiteList %}
<option value="{{ items }}">{{ items }}</option>
@@ -465,29 +509,29 @@
<table class="keys-table">
<thead>
<tr>
<th style="width: 20%;">{% trans "Domain" %}</th>
<th style="width: 40%;">{% trans "Private Key" %}</th>
<th style="width: 40%;">{% trans "Public Key" %}</th>
<th class="table-domain">{% trans "Domain" %}</th>
<th class="table-private-key">{% trans "Private Key" %}</th>
<th class="table-public-key">{% trans "Public Key" %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span style="padding: 0.5rem 1rem; background: var(--accent-light, #e0e7ff); color: #5b5fcf; border-radius: 6px; font-weight: 600; display: inline-block;">
<span class="domain-badge">
<i class="fas fa-globe"></i>
<span ng-bind="domainName"></span>
</span>
</td>
<td>
<textarea ng-bind="privateKey" rows="10" class="key-textarea" readonly></textarea>
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block;">
<textarea ng-bind="privateKey" rows="10" class="key-textarea" readonly aria-label="{% trans 'Private Key' %}"></textarea>
<small class="key-hint">
<i class="fas fa-lock"></i>
{% trans "Keep this private key secure" %}
</small>
</td>
<td>
<textarea ng-bind="publicKey" rows="10" class="key-textarea" readonly></textarea>
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block;">
<textarea ng-bind="publicKey" rows="10" class="key-textarea" readonly aria-label="{% trans 'Public Key' %}"></textarea>
<small class="key-hint">
<i class="fas fa-info-circle"></i>
{% trans "Add this as a TXT record in your DNS" %}
</small>

View File

@@ -277,6 +277,40 @@
display: inline-block;
}
.warning-icon {
color: var(--warning-color, #ffa000);
}
.table-id {
width: 10%;
}
.table-source {
width: 35%;
}
.table-destination {
width: 40%;
}
.table-actions {
width: 15%;
text-align: center;
}
.email-icon {
color: var(--accent-color, #5b5fcf);
margin-right: 0.5rem;
}
.destination-text {
margin-left: 0.5rem;
}
.full-width-btn {
width: 100%;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@@ -329,7 +363,7 @@
<h1 class="page-title">
<i class="fas fa-share-square"></i>
{% trans "Setup Email Forwarding" %}
<a target="_blank" href="https://cyberpanel.net/KnowledgeBase/home/email-forwarding/" class="docs-link">
<a target="_blank" rel="noopener" href="https://cyberpanel.net/KnowledgeBase/home/email-forwarding/" class="docs-link">
<i class="fas fa-book"></i>
{% trans "Forwarding Docs" %}
</a>
@@ -348,7 +382,7 @@
<div class="card-body">
{% if not status %}
<div class="disabled-notice">
<i class="fas fa-exclamation-triangle fa-3x mb-3" style="color: var(--warning-color, #ffa000);"></i>
<i class="fas fa-exclamation-triangle fa-3x mb-3 warning-icon"></i>
<h3>{% trans "Postfix is disabled" %}</h3>
<p class="mb-3">{% trans "You need to enable Postfix to setup email forwarding" %}</p>
<a href="{% url 'managePostfix' %}" class="btn-primary">
@@ -363,7 +397,7 @@
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Select Website" %}</label>
<select ng-change="showEmailDetails()" ng-model="emailDomain" class="form-control">
<select ng-change="showEmailDetails()" ng-model="emailDomain" class="form-control" aria-label="{% trans 'Select Website' %}">
<option value="">{% trans "Choose a website..." %}</option>
{% for items in websiteList %}
<option value="{{ items }}">{{ items }}</option>
@@ -379,7 +413,7 @@
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Select Email Account" %}</label>
<select ng-change="selectForwardingEmail()" ng-model="selectedEmail" class="form-control">
<select ng-change="selectForwardingEmail()" ng-model="selectedEmail" class="form-control" aria-label="{% trans 'Select Email Account' %}">
<option value="">{% trans "Choose an email account..." %}</option>
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
</select>
@@ -387,7 +421,7 @@
<div class="form-group">
<label class="form-label">{% trans "Forwarding Options" %}</label>
<select ng-change="selectForwardingEmail()" ng-model="forwardingOption" class="form-control">
<select ng-change="selectForwardingEmail()" ng-model="forwardingOption" class="form-control" aria-label="{% trans 'Forwarding Options' %}">
<option value="Forward to email">{% trans "Forward to email" %}</option>
<option value="Pipe to program">{% trans "Pipe to program" %}</option>
</select>
@@ -402,21 +436,21 @@
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label">{% trans "Source Email" %}</label>
<input placeholder="{% trans 'Source' %}" type="email" class="form-control" ng-model="selectedEmail" readonly>
<input placeholder="{% trans 'Source' %}" type="email" class="form-control" ng-model="selectedEmail" readonly aria-label="{% trans 'Source Email' %}">
</div>
</div>
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label">{% trans "Destination" %} <span ng-show="forwardingOption == 'Pipe to program'">{% trans "(Path to program)" %}</span></label>
<input placeholder="{% trans 'Enter destination email or program path' %}" type="text" class="form-control" ng-model="destinationEmail" required>
<input placeholder="{% trans 'Enter destination email or program path' %}" type="text" class="form-control" ng-model="destinationEmail" required aria-label="{% trans 'Destination' %}">
</div>
</div>
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label">&nbsp;</label>
<button type="button" ng-click="forwardEmail()" class="btn-primary" style="width: 100%;">
<button type="button" ng-click="forwardEmail()" class="btn-primary full-width-btn">
<i class="fas fa-plus-circle"></i>
{% trans "Add Forwarding" %}
</button>
@@ -427,17 +461,17 @@
<table class="forwarding-table">
<thead>
<tr>
<th style="width: 10%;">{% trans "ID" %}</th>
<th style="width: 35%;">{% trans "Source" %}</th>
<th style="width: 40%;">{% trans "Destination" %}</th>
<th style="width: 15%; text-align: center;">{% trans "Actions" %}</th>
<th class="table-id">{% trans "ID" %}</th>
<th class="table-source">{% trans "Source" %}</th>
<th class="table-destination">{% trans "Destination" %}</th>
<th class="table-actions">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td><strong ng-bind="record.id"></strong></td>
<td>
<i class="fas fa-envelope" style="color: var(--accent-color, #5b5fcf); margin-right: 0.5rem;"></i>
<i class="fas fa-envelope email-icon"></i>
<span ng-bind="record.source"></span>
</td>
<td>
@@ -447,9 +481,9 @@
<span class="option-badge" ng-hide="record.destination.indexOf('@') > -1">
<i class="fas fa-terminal"></i> Program
</span>
<span style="margin-left: 0.5rem;" ng-bind="record.destination"></span>
<span class="destination-text" ng-bind="record.destination"></span>
</td>
<td style="text-align: center;">
<td class="table-actions">
<button type="button" ng-click="deleteForwarding(record.source, record.destination)" class="btn-danger">
<i class="fas fa-trash"></i>
{% trans "Delete" %}

View File

@@ -164,11 +164,9 @@
}
/* Fix for Firefox on Windows */
@-moz-document url-prefix() {
select.form-control {
color: var(--text-dark, #2f3640) !important;
text-shadow: none;
}
select.form-control {
color: var(--text-dark, #2f3640) !important;
text-shadow: none;
}
/* Ensure selected option is always visible */
@@ -397,6 +395,30 @@
flex-wrap: wrap;
}
.email-icon {
color: var(--accent-color, #5b5fcf);
margin-right: 0.5rem;
}
.email-address {
font-size: 0.9375rem;
color: var(--text-primary, #1e293b);
}
.disk-usage-badge {
padding: 0.375rem 0.875rem;
background: var(--bg-gradient, #f0f1ff);
color: var(--accent-color, #5b5fcf);
border-radius: 4px;
font-weight: 600;
font-size: 0.875rem;
}
.email-to-delete {
color: var(--danger-color, #ef4444);
font-weight: 600;
}
/* Modern Modal Styles */
.modal-content {
border-radius: 12px;
@@ -593,7 +615,7 @@
<div class="col-md-6">
<div class="form-group">
<label class="form-label">{% trans "Select Domain" %}</label>
<select ng-change="populateCurrentRecords()" ng-model="selectedDomain" class="form-control">
<select ng-change="populateCurrentRecords()" ng-model="selectedDomain" class="form-control" aria-label="{% trans 'Select Domain' %}">
<option value="">{% trans "Choose a domain..." %}</option>
{% for items in websiteList %}
<option value="{{ items }}">{{ items }}</option>
@@ -613,7 +635,7 @@
<a href="https://community.cyberpanel.net/t/6-self-signed-ssl-error-on-outlook-thunderbird/207" target="_blank" rel="noopener" style="color: var(--danger-text, #991b1b); text-decoration: underline;">{% trans "Learn more" %}</a>
</div>
</div>
<button ng-hide="mailConfigured==1" ng-click='fixMailSSL()' class="btn-primary mb-4">
<button type="button" ng-hide="mailConfigured==1" ng-click='fixMailSSL()' class="btn-primary mb-4">
<i class="fas fa-wrench"></i>
{% trans "Fix SSL Now" %}
</button>
@@ -731,11 +753,11 @@
<tbody>
<tr ng-repeat="record in records track by $index">
<td>
<i class="fas fa-user-circle" style="color: var(--accent-color, #5b5fcf); margin-right: 0.5rem;"></i>
<strong style="font-size: 0.9375rem; color: var(--text-primary, #1e293b);" ng-bind="record.email"></strong>
<i class="fas fa-user-circle email-icon"></i>
<strong class="email-address" ng-bind="record.email"></strong>
</td>
<td>
<span style="padding: 0.375rem 0.875rem; background: var(--bg-gradient, #f0f1ff); color: var(--accent-color, #5b5fcf); border-radius: 4px; font-weight: 600; font-size: 0.875rem;">
<span class="disk-usage-badge">
{{ record.DiskUsage }}
</span>
</td>
@@ -767,7 +789,7 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Change Password
<img ng-hide="cyberpanelLoading" src="{% static 'images/loading.gif' %}">
<img ng-hide="cyberpanelLoading" src="{% static 'images/loading.gif' %}" alt="Loading...">
</h4>
</div>
<div class="modal-body">
@@ -775,7 +797,7 @@
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Email" %}</label>
<div class="col-sm-6">
<input name="name" type="text" class="form-control" ng-model="email" readonly>
<input name="name" type="text" class="form-control" ng-model="email" readonly aria-label="Email address">
</div>
</div>
@@ -784,7 +806,7 @@
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Password" %}</label>
<div class="col-sm-6">
<input type="password" class="form-control" ng-model="$parent.password">
<input type="password" class="form-control" ng-model="$parent.password" aria-label="Password">
</div>
</div>
</form>
@@ -837,7 +859,7 @@
<p style="margin: 0.5rem 0 0 0;">{% trans "This will permanently delete the email account and all associated data including emails, folders, and settings." %}</p>
</div>
</div>
<p><strong>{% trans "Email to delete:" %}</strong> <span style="color: var(--danger-color, #ef4444); font-weight: 600;">{$ emailToDelete $}</span></p>
<p><strong>{% trans "Email to delete:" %}</strong> <span class="email-to-delete">{$ emailToDelete $}</span></p>
<p>{% trans "Are you sure you want to continue?" %}</p>
</div>
<div class="modal-footer">

View File

@@ -208,6 +208,7 @@
color: var(--text-primary, #2f3640);
font-weight: 600;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
display: block;
margin-bottom: 4px;

View File

@@ -411,7 +411,7 @@
<div class="package-selector-card">
<div class="form-group">
<label class="form-label">{% trans "Select Package to Delete" %}</label>
<select ng-change="fetchPackageDetails()" ng-model="packageToBeDeleted" class="form-control">
<select ng-change="fetchPackageDetails()" ng-model="packageToBeDeleted" class="form-control" aria-label="{% trans 'Select Package to Delete' %}">
<option value="">-- {% trans "Select a package" %} --</option>
{% for items in packageList %}
<option>{{ items }}</option>

View File

@@ -139,6 +139,10 @@
color: var(--warning-dark);
}
.status-icon {
font-size: 8px;
}
/* Action buttons */
.action-buttons {
display: flex;
@@ -344,6 +348,7 @@
color: var(--text-primary);
font-weight: 600;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
display: block;
margin-bottom: 4px;
@@ -483,7 +488,7 @@
<td>{$ record.ftpAccounts $}</td>
<td>
<span class="status-badge" ng-class="{'enabled': record.allowFullDomain, 'disabled': !record.allowFullDomain}">
<i class="fas fa-circle" style="font-size: 8px;"></i>
<i class="fas fa-circle status-icon"></i>
<span ng-if="record.allowFullDomain">{% trans "Enabled" %}</span>
<span ng-if="!record.allowFullDomain">{% trans "Disabled" %}</span>
</span>
@@ -526,7 +531,7 @@
<form name="editPackageForm">
<div class="form-group">
<label class="form-label">{% trans "Package Name" %}</label>
<input type="text" class="form-control" ng-model="name" readonly>
<input type="text" class="form-control" ng-model="name" readonly aria-label="{% trans 'Package Name' %}">
</div>
<div class="form-section">
@@ -537,14 +542,14 @@
<div class="form-group">
<label class="form-label">{% trans "Domains" %}</label>
<input type="number" class="form-control" ng-model="allowedDomains" required>
<input type="number" class="form-control" ng-model="allowedDomains" required aria-label="{% trans 'Number of domains allowed' %}">
<div class="help-text">{% trans "Number of domains allowed (0 = Unlimited)" %}</div>
</div>
<div class="form-group">
<label class="form-label">{% trans "Disk Space" %}</label>
<div class="input-group">
<input type="number" class="form-control" ng-model="diskSpace" required>
<input type="number" class="form-control" ng-model="diskSpace" required aria-label="{% trans 'Disk space in MB' %}">
<span class="input-suffix">MB</span>
</div>
<div class="help-text">{% trans "Disk space in MB (0 = Unlimited)" %}</div>
@@ -553,7 +558,7 @@
<div class="form-group">
<label class="form-label">{% trans "Bandwidth" %}</label>
<div class="input-group">
<input type="number" class="form-control" ng-model="bandwidth" required>
<input type="number" class="form-control" ng-model="bandwidth" required aria-label="{% trans 'Monthly bandwidth in MB' %}">
<span class="input-suffix">MB</span>
</div>
<div class="help-text">{% trans "Monthly bandwidth in MB (0 = Unlimited)" %}</div>
@@ -568,17 +573,17 @@
<div class="form-group">
<label class="form-label">{% trans "Email Accounts" %}</label>
<input type="number" class="form-control" ng-model="emails" required>
<input type="number" class="form-control" ng-model="emails" required aria-label="{% trans 'Number of email accounts' %}">
</div>
<div class="form-group">
<label class="form-label">{% trans "Databases" %}</label>
<input type="number" class="form-control" ng-model="dataBases" required>
<input type="number" class="form-control" ng-model="dataBases" required aria-label="{% trans 'Number of databases' %}">
</div>
<div class="form-group">
<label class="form-label">{% trans "FTP Accounts" %}</label>
<input type="number" class="form-control" ng-model="ftpAccounts" required>
<input type="number" class="form-control" ng-model="ftpAccounts" required aria-label="{% trans 'Number of FTP accounts' %}">
</div>
</div>

View File

@@ -243,6 +243,7 @@
color: var(--text-primary, #2f3640);
font-weight: 600;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
display: block;
margin-bottom: 4px;
@@ -393,7 +394,7 @@
<div class="package-selector-card">
<div class="form-group">
<label class="form-label">{% trans "Select Package to Modify" %}</label>
<select ng-change="fetchDetails()" ng-model="packageToBeModified" class="form-control">
<select ng-change="fetchDetails()" ng-model="packageToBeModified" class="form-control" aria-label="{% trans 'Select Package to Modify' %}">
<option value="">-- {% trans "Select a package" %} --</option>
{% for items in packList %}
<option>{{ items }}</option>
@@ -417,7 +418,7 @@
<div class="form-group">
<label class="form-label">{% trans "Domains" %}</label>
<input type="number" class="form-control" ng-model="allowedDomains" required>
<input type="number" class="form-control" ng-model="allowedDomains" required aria-label="{% trans 'Number of domains allowed' %}">
<div class="help-text">{% trans "Number of domains allowed (0 = Unlimited)" %}</div>
</div>
@@ -425,7 +426,7 @@
<div class="form-group">
<label class="form-label">{% trans "Disk Space" %}</label>
<div class="input-group">
<input type="number" class="form-control" ng-model="diskSpace" required>
<input type="number" class="form-control" ng-model="diskSpace" required aria-label="{% trans 'Disk space in MB' %}">
<span class="input-suffix">MB</span>
</div>
<div class="help-text">{% trans "Disk space in MB (0 = Unlimited)" %}</div>
@@ -434,7 +435,7 @@
<div class="form-group">
<label class="form-label">{% trans "Bandwidth" %}</label>
<div class="input-group">
<input type="number" class="form-control" ng-model="bandwidth" required>
<input type="number" class="form-control" ng-model="bandwidth" required aria-label="{% trans 'Monthly bandwidth in MB' %}">
<span class="input-suffix">MB</span>
</div>
<div class="help-text">{% trans "Monthly bandwidth in MB (0 = Unlimited)" %}</div>
@@ -451,20 +452,20 @@
<div class="form-row">
<div class="form-group">
<label class="form-label">{% trans "FTP Accounts" %}</label>
<input type="number" class="form-control" ng-model="ftpAccounts" required>
<input type="number" class="form-control" ng-model="ftpAccounts" required aria-label="{% trans 'Number of FTP accounts' %}">
<div class="help-text">{% trans "Number of FTP accounts" %}</div>
</div>
<div class="form-group">
<label class="form-label">{% trans "Databases" %}</label>
<input type="number" class="form-control" ng-model="dataBases" required>
<input type="number" class="form-control" ng-model="dataBases" required aria-label="{% trans 'Number of MySQL databases' %}">
<div class="help-text">{% trans "Number of MySQL databases" %}</div>
</div>
</div>
<div class="form-group">
<label class="form-label">{% trans "Email Accounts" %}</label>
<input type="number" class="form-control" ng-model="emails" required>
<input type="number" class="form-control" ng-model="emails" required aria-label="{% trans 'Number of email accounts' %}">
<div class="help-text">{% trans "Number of email accounts" %}</div>
</div>
</div>

View File

@@ -37,3 +37,4 @@ asyncssh==2.21.0
python-jose==3.4.0
websockets==15.0.1
PyJWT
python-dotenv==1.0.0

View File

@@ -182,6 +182,7 @@
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border-radius: 12px;
display: flex;
@@ -190,6 +191,14 @@
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.package-icon i {
font-size: 1.75rem;
}
.loading-spinner-inline {
margin-left: 1rem;
}
.page-subtitle {
font-size: 1.125rem;
color: var(--text-on-gradient);
@@ -207,6 +216,7 @@
.stat-badge {
background: rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
padding: 0.5rem 1.5rem;
border-radius: 20px;
@@ -560,6 +570,7 @@
align-items: center;
justify-content: center;
z-index: 1000;
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
}
@@ -760,7 +771,7 @@
<div class="header-content">
<h1 class="page-title">
<div class="package-icon">
<i class="fas fa-cube" style="font-size: 1.75rem;"></i>
<i class="fas fa-cube"></i>
</div>
{% trans "System Package Manager" %}
</h1>
@@ -786,15 +797,15 @@
<div class="tabs-container">
<!-- Tab Navigation -->
<div class="modern-tabs">
<button class="tab-item active" ng-click="fetchPackages('upgrade')" data-tab="updates">
<button type="button" class="tab-item active" ng-click="fetchPackages('upgrade')" data-tab="updates" aria-label="{% trans 'View available updates' %}">
<i class="fas fa-sync-alt tab-icon"></i>
{% trans "Available Updates" %}
</button>
<button class="tab-item" ng-click="fetchPackages()" data-tab="all">
<button type="button" class="tab-item" ng-click="fetchPackages()" data-tab="all" aria-label="{% trans 'View all packages' %}">
<i class="fas fa-list tab-icon"></i>
{% trans "All Packages" %}
</button>
<button class="tab-item" ng-click="fetchPackages('CyberPanel')" data-tab="cyberpanel">
<button type="button" class="tab-item" ng-click="fetchPackages('CyberPanel')" data-tab="cyberpanel" aria-label="{% trans 'View CyberPanel packages' %}">
<i class="fas fa-shield-alt tab-icon"></i>
{% trans "CyberPanel Packages" %}
</button>
@@ -807,20 +818,20 @@
<div class="search-box">
<i class="fas fa-search search-icon"></i>
<input type="text" class="search-input" placeholder="{% trans 'Search packages...' %}"
ng-model="packSearch">
ng-model="packSearch" aria-label="{% trans 'Search packages' %}">
</div>
<div class="control-group">
<select ng-model="recordsToShow" class="select-control"
ng-change="fetchPackages(currentTab)">
ng-change="fetchPackages(currentTab)" aria-label="{% trans 'Records per page' %}">
<option value="10">10 {% trans "per page" %}</option>
<option value="50">50 {% trans "per page" %}</option>
<option value="100">100 {% trans "per page" %}</option>
<option value="500">500 {% trans "per page" %}</option>
</select>
<button class="btn btn-success" ng-click="updatePackage('all')"
ng-show="currentTab === 'upgrade'">
<button type="button" class="btn btn-success" ng-click="updatePackage('all')"
ng-show="currentTab === 'upgrade'" aria-label="{% trans 'Update all packages' %}">
<i class="fas fa-download"></i>
{% trans "Update All" %}
</button>
@@ -865,24 +876,24 @@
</span>
</td>
<td>
<a class="lock-toggle"
ng-class="{'locked': package.lock == 1, 'unlocked': package.lock == 0}"
ng-click="lockStatus(package.package, package.lock == 1 ? 1 : 0)"
title="{$ package.lock == 1 ? 'Package is locked' : 'Package is unlocked' $}">
<button type="button" class="lock-toggle"
ng-class="{'locked': package.lock == 1, 'unlocked': package.lock == 0}"
ng-click="lockStatus(package.package, package.lock == 1 ? 1 : 0)"
aria-label="{$ package.lock == 1 ? 'Unlock package' : 'Lock package' $}">
<i class="fas"
ng-class="{'fa-lock': package.lock == 1, 'fa-lock-open': package.lock == 0}"></i>
</a>
</button>
</td>
<td>
<div class="action-buttons">
<button class="btn btn-info btn-sm"
ng-click="showPackageDetails(package.package)">
<button type="button" class="btn btn-info btn-sm"
ng-click="showPackageDetails(package.package)" aria-label="{% trans 'View package details' %}">
<i class="fas fa-info-circle"></i>
{% trans "Details" %}
</button>
<button class="btn btn-primary btn-sm"
<button type="button" class="btn btn-primary btn-sm"
ng-click="showUpdateModal(package.package)"
ng-show="currentTab === 'upgrade' || currentTab === 'cyberpanel'">
ng-show="currentTab === 'upgrade' || currentTab === 'cyberpanel'" aria-label="{% trans 'Update package' %}">
<i class="fas fa-download"></i>
{% trans "Update" %}
</button>
@@ -912,7 +923,7 @@
<div class="pagination-controls">
<span>{% trans "Page:" %}</span>
<select ng-model="currentPage" class="page-select"
ng-change="fetchPackages(currentTab)">
ng-change="fetchPackages(currentTab)" aria-label="{% trans 'Select page' %}">
<option ng-repeat="page in pagination" value="{$ $index + 1 $}">
{$ $index + 1 $}
</option>
@@ -931,7 +942,7 @@
<i class="fas fa-info-circle"></i>
{% trans "Package Details:" %} {$ selectedPackage $}
</h3>
<button class="modal-close" ng-click="closeDetails()">
<button type="button" class="modal-close" ng-click="closeDetails()" aria-label="{% trans 'Close details modal' %}">
<i class="fas fa-times"></i>
</button>
</div>
@@ -948,9 +959,9 @@
<h3 class="modal-title">
<i class="fas fa-download"></i>
{% trans "Updating Package:" %} {$ updatingPackage $}
<span ng-hide="cyberpanelLoading" class="loading-spinner" style="margin-left: 1rem;"></span>
<span ng-hide="cyberpanelLoading" class="loading-spinner loading-spinner-inline"></span>
</h3>
<button class="modal-close" ng-click="closeUpdate()" ng-disabled="!updateComplete">
<button type="button" class="modal-close" ng-click="closeUpdate()" ng-disabled="!updateComplete" aria-label="{% trans 'Close update modal' %}">
<i class="fas fa-times"></i>
</button>
</div>

View File

@@ -1729,6 +1729,8 @@ app.controller('scheduleBackup', function ($scope, $http, $window) {
$scope.allSites = response.data.allSites;
$scope.lastRun = response.data.lastRun;
$scope.currentStatus = response.data.currentStatus;
$scope.backupFrequency = response.data.currently;
$scope.backupRetention = response.data.retention;
} else {
new PNotify({

View File

@@ -0,0 +1,192 @@
# 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.