commit
7bd67d3df2
|
|
@ -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
|
||||||
|
|
@ -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
|
.DS_Store
|
||||||
.AppleDouble
|
.DS_Store?
|
||||||
.LSOverride
|
._*
|
||||||
*.pyc
|
.Spotlight-V100
|
||||||
.idea
|
.Trashes
|
||||||
venv
|
ehthumbs.db
|
||||||
/.venv/
|
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
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -13,6 +13,14 @@ https://docs.djangoproject.com/en/1.11/ref/settings/
|
||||||
import os
|
import os
|
||||||
from django.utils.translation import gettext_lazy as _
|
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, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
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/
|
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# 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!
|
# 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
|
# Application definition
|
||||||
|
|
||||||
|
|
@ -96,6 +105,7 @@ TEMPLATES = [
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'baseTemplate.context_processors.version_context',
|
'baseTemplate.context_processors.version_context',
|
||||||
|
'baseTemplate.context_processors.cosmetic_context',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -110,19 +120,19 @@ WSGI_APPLICATION = 'CyberCP.wsgi.application'
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'NAME': 'cyberpanel',
|
'NAME': os.getenv('DB_NAME', 'cyberpanel'),
|
||||||
'USER': 'cyberpanel',
|
'USER': os.getenv('DB_USER', 'cyberpanel'),
|
||||||
'PASSWORD': 'SLTUIUxqhulwsh',
|
'PASSWORD': os.getenv('DB_PASSWORD', 'SLTUIUxqhulwsh'),
|
||||||
'HOST': 'localhost',
|
'HOST': os.getenv('DB_HOST', 'localhost'),
|
||||||
'PORT':''
|
'PORT': os.getenv('DB_PORT', '3306'),
|
||||||
},
|
},
|
||||||
'rootdb': {
|
'rootdb': {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'NAME': 'mysql',
|
'NAME': os.getenv('ROOT_DB_NAME', 'mysql'),
|
||||||
'USER': 'root',
|
'USER': os.getenv('ROOT_DB_USER', 'root'),
|
||||||
'PASSWORD': 'SLTUIUxqhulwsh',
|
'PASSWORD': os.getenv('ROOT_DB_PASSWORD', 'SLTUIUxqhulwsh'),
|
||||||
'HOST': 'localhost',
|
'HOST': os.getenv('ROOT_DB_HOST', 'localhost'),
|
||||||
'PORT': '',
|
'PORT': os.getenv('ROOT_DB_PORT', '3306'),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
DATABASE_ROUTERS = ['backup.backupRouter.backupRouter']
|
DATABASE_ROUTERS = ['backup.backupRouter.backupRouter']
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr
|
||||||
- 🎓 [Docs (Old)](https://community.cyberpanel.net/docs)
|
- 🎓 [Docs (Old)](https://community.cyberpanel.net/docs)
|
||||||
- 📖 [Additional Guides](guides/INDEX.md) - Detailed guides for Docker, AI Scanner, Mautic, and more
|
- 📖 [Additional Guides](guides/INDEX.md) - Detailed guides for Docker, AI Scanner, Mautic, and more
|
||||||
- 📚 [Local Documentation](guides/) - All guides available in this repository
|
- 📚 [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)
|
- ✅ [Changelog](https://community.cyberpanel.net/t/change-logs/161)
|
||||||
- 💬 [Forums](https://community.cyberpanel.net)
|
- 💬 [Forums](https://community.cyberpanel.net)
|
||||||
- 📢 [Discord](https://discord.gg/g8k8Db3)
|
- 📢 [Discord](https://discord.gg/g8k8Db3)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -2074,6 +2074,8 @@ app.controller('scheduleBackup', function ($scope, $http, $window) {
|
||||||
$scope.allSites = response.data.allSites;
|
$scope.allSites = response.data.allSites;
|
||||||
$scope.lastRun = response.data.lastRun;
|
$scope.lastRun = response.data.lastRun;
|
||||||
$scope.currentStatus = response.data.currentStatus;
|
$scope.currentStatus = response.data.currentStatus;
|
||||||
|
$scope.backupFrequency = response.data.currently;
|
||||||
|
$scope.backupRetention = response.data.retention;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
new PNotify({
|
new PNotify({
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,19 @@ def version_context(request):
|
||||||
'CYBERPANEL_BUILD': BUILD,
|
'CYBERPANEL_BUILD': BUILD,
|
||||||
'CYBERPANEL_FULL_VERSION': f"{VERSION}.{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
|
||||||
|
}
|
||||||
|
|
@ -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/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 }}">
|
<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 -->
|
<!-- Core Scripts -->
|
||||||
<script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}"></script>
|
<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>
|
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -504,6 +504,73 @@ class preFlightsChecks:
|
||||||
self.stdOut("Install psmisc")
|
self.stdOut("Install psmisc")
|
||||||
self.install_package("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):
|
def download_install_CyberPanel(self, mysqlPassword, mysql):
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
@ -549,50 +616,12 @@ password="%s"
|
||||||
|
|
||||||
logging.InstallLog.writeToFile("Updating /root/.my.cnf!")
|
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()
|
logging.InstallLog.writeToFile("Environment configuration generated successfully!")
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
if self.remotemysql == 'ON':
|
if self.remotemysql == 'ON':
|
||||||
command = "sed -i 's|localhost|%s|g' %s" % (self.mysqlhost, path)
|
command = "sed -i 's|localhost|%s|g' %s" % (self.mysqlhost, path)
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,16 @@
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.password-hint {
|
||||||
|
color: var(--text-secondary, #64748b);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
@ -324,7 +334,7 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Website" %}</label>
|
<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>
|
<option value="">{% trans "Choose a website..." %}</option>
|
||||||
{% for items in websiteList %}
|
{% for items in websiteList %}
|
||||||
<option value="{{ items }}">{{ items }}</option>
|
<option value="{{ items }}">{{ items }}</option>
|
||||||
|
|
@ -345,7 +355,7 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Email Account" %}</label>
|
<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 value="">{% trans "Choose an email account..." %}</option>
|
||||||
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
|
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -374,19 +384,19 @@
|
||||||
<label class="form-label">{% trans "Generated Password" %}</label>
|
<label class="form-label">{% trans "Generated Password" %}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" name="email" class="form-control"
|
<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">
|
<button type="button" ng-click="usePassword()" class="btn-secondary">
|
||||||
<i class="fas fa-check"></i>
|
<i class="fas fa-check"></i>
|
||||||
{% trans "Use This Password" %}
|
{% trans "Use This Password" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
<i class="fas fa-info-circle"></i>
|
||||||
{% trans "Make sure to save this password in a secure location" %}
|
{% trans "Make sure to save this password in a secure location" %}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 2rem;">
|
<div class="action-section">
|
||||||
<button type="button" ng-click="changePassword()" class="btn-primary">
|
<button type="button" ng-click="changePassword()" class="btn-primary">
|
||||||
<i class="fas fa-save"></i>
|
<i class="fas fa-save"></i>
|
||||||
{% trans "Change Password" %}
|
{% trans "Change Password" %}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@
|
||||||
background: var(--bg-hover, #f8f9ff);
|
background: var(--bg-hover, #f8f9ff);
|
||||||
border: 1px solid var(--border-color, #e8e9ff);
|
border: 1px solid var(--border-color, #e8e9ff);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
min-width: -webkit-fill-available;
|
||||||
min-width: fit-content;
|
min-width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,6 +305,16 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--success-color, #10b981);
|
background: var(--success-color, #10b981);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.password-hint {
|
||||||
|
color: var(--text-secondary, #64748b);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="modern-container" ng-controller="createEmailAccount">
|
<div class="modern-container" ng-controller="createEmailAccount">
|
||||||
|
|
@ -320,7 +331,7 @@
|
||||||
<h2 class="card-title">
|
<h2 class="card-title">
|
||||||
<i class="fas fa-user-plus"></i>
|
<i class="fas fa-user-plus"></i>
|
||||||
{% trans "New Email Account" %}
|
{% 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">
|
class="test-email-link">
|
||||||
<i class="fas fa-vial"></i>
|
<i class="fas fa-vial"></i>
|
||||||
{% trans "Test Email Delivery" %}
|
{% trans "Test Email Delivery" %}
|
||||||
|
|
@ -346,7 +357,7 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Website" %}</label>
|
<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>
|
<option value="">{% trans "Choose a website..." %}</option>
|
||||||
{% for items in websiteList %}
|
{% for items in websiteList %}
|
||||||
<option value="{{ items }}">{{ items }}</option>
|
<option value="{{ items }}">{{ items }}</option>
|
||||||
|
|
@ -397,19 +408,19 @@
|
||||||
<label class="form-label">{% trans "Generated Password" %}</label>
|
<label class="form-label">{% trans "Generated Password" %}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" name="email" class="form-control"
|
<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">
|
<button type="button" ng-click="usePassword()" class="btn-secondary">
|
||||||
<i class="fas fa-check"></i>
|
<i class="fas fa-check"></i>
|
||||||
{% trans "Use This Password" %}
|
{% trans "Use This Password" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
<i class="fas fa-info-circle"></i>
|
||||||
{% trans "Make sure to save this password in a secure location" %}
|
{% trans "Make sure to save this password in a secure location" %}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 2rem;">
|
<div class="action-section">
|
||||||
<button type="button" ng-click="createEmailAccount()" class="btn-primary">
|
<button type="button" ng-click="createEmailAccount()" class="btn-primary">
|
||||||
<i class="fas fa-plus-circle"></i>
|
<i class="fas fa-plus-circle"></i>
|
||||||
{% trans "Create Email Account" %}
|
{% trans "Create Email Account" %}
|
||||||
|
|
|
||||||
|
|
@ -323,7 +323,7 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Website" %}</label>
|
<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>
|
<option value="">{% trans "Choose a website..." %}</option>
|
||||||
{% for items in websiteList %}
|
{% for items in websiteList %}
|
||||||
<option value="{{ items }}">{{ items }}</option>
|
<option value="{{ items }}">{{ items }}</option>
|
||||||
|
|
@ -339,7 +339,7 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Email Account" %}</label>
|
<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 value="">{% trans "Choose an email account..." %}</option>
|
||||||
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
|
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,50 @@
|
||||||
margin-right: 0.5rem;
|
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 {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
@ -359,7 +403,7 @@
|
||||||
<h1 class="page-title">
|
<h1 class="page-title">
|
||||||
<i class="fas fa-shield-alt"></i>
|
<i class="fas fa-shield-alt"></i>
|
||||||
{% trans "DKIM Manager" %}
|
{% 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>
|
<i class="fas fa-book"></i>
|
||||||
{% trans "DKIM Docs" %}
|
{% trans "DKIM Docs" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -378,7 +422,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="install-notice">
|
<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>
|
<h3>{% trans "OpenDKIM is not installed" %}</h3>
|
||||||
<p class="mb-3">{% trans "OpenDKIM is required to manage DKIM keys for your domains" %}</p>
|
<p class="mb-3">{% trans "OpenDKIM is required to manage DKIM keys for your domains" %}</p>
|
||||||
<a href="" ng-click="installOpenDKIM()" class="install-link">
|
<a href="" ng-click="installOpenDKIM()" class="install-link">
|
||||||
|
|
@ -407,12 +451,12 @@
|
||||||
|
|
||||||
<!-- Installation Log -->
|
<!-- Installation Log -->
|
||||||
<div ng-hide="openDKIMInstallBox">
|
<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>
|
<i class="fas fa-terminal"></i>
|
||||||
{% trans "Installation Progress" %}
|
{% trans "Installation Progress" %}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="terminal-box">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -438,7 +482,7 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Website" %}</label>
|
<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>
|
<option value="">{% trans "Choose a website..." %}</option>
|
||||||
{% for items in websiteList %}
|
{% for items in websiteList %}
|
||||||
<option value="{{ items }}">{{ items }}</option>
|
<option value="{{ items }}">{{ items }}</option>
|
||||||
|
|
@ -465,29 +509,29 @@
|
||||||
<table class="keys-table">
|
<table class="keys-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 20%;">{% trans "Domain" %}</th>
|
<th class="table-domain">{% trans "Domain" %}</th>
|
||||||
<th style="width: 40%;">{% trans "Private Key" %}</th>
|
<th class="table-private-key">{% trans "Private Key" %}</th>
|
||||||
<th style="width: 40%;">{% trans "Public Key" %}</th>
|
<th class="table-public-key">{% trans "Public Key" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<i class="fas fa-globe"></i>
|
||||||
<span ng-bind="domainName"></span>
|
<span ng-bind="domainName"></span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<textarea ng-bind="privateKey" rows="10" class="key-textarea" readonly></textarea>
|
<textarea ng-bind="privateKey" rows="10" class="key-textarea" readonly aria-label="{% trans 'Private Key' %}"></textarea>
|
||||||
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block;">
|
<small class="key-hint">
|
||||||
<i class="fas fa-lock"></i>
|
<i class="fas fa-lock"></i>
|
||||||
{% trans "Keep this private key secure" %}
|
{% trans "Keep this private key secure" %}
|
||||||
</small>
|
</small>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<textarea ng-bind="publicKey" rows="10" class="key-textarea" readonly></textarea>
|
<textarea ng-bind="publicKey" rows="10" class="key-textarea" readonly aria-label="{% trans 'Public Key' %}"></textarea>
|
||||||
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block;">
|
<small class="key-hint">
|
||||||
<i class="fas fa-info-circle"></i>
|
<i class="fas fa-info-circle"></i>
|
||||||
{% trans "Add this as a TXT record in your DNS" %}
|
{% trans "Add this as a TXT record in your DNS" %}
|
||||||
</small>
|
</small>
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,40 @@
|
||||||
display: inline-block;
|
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 {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
@ -329,7 +363,7 @@
|
||||||
<h1 class="page-title">
|
<h1 class="page-title">
|
||||||
<i class="fas fa-share-square"></i>
|
<i class="fas fa-share-square"></i>
|
||||||
{% trans "Setup Email Forwarding" %}
|
{% 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>
|
<i class="fas fa-book"></i>
|
||||||
{% trans "Forwarding Docs" %}
|
{% trans "Forwarding Docs" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -348,7 +382,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if not status %}
|
{% if not status %}
|
||||||
<div class="disabled-notice">
|
<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>
|
<h3>{% trans "Postfix is disabled" %}</h3>
|
||||||
<p class="mb-3">{% trans "You need to enable Postfix to setup email forwarding" %}</p>
|
<p class="mb-3">{% trans "You need to enable Postfix to setup email forwarding" %}</p>
|
||||||
<a href="{% url 'managePostfix' %}" class="btn-primary">
|
<a href="{% url 'managePostfix' %}" class="btn-primary">
|
||||||
|
|
@ -363,7 +397,7 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Website" %}</label>
|
<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>
|
<option value="">{% trans "Choose a website..." %}</option>
|
||||||
{% for items in websiteList %}
|
{% for items in websiteList %}
|
||||||
<option value="{{ items }}">{{ items }}</option>
|
<option value="{{ items }}">{{ items }}</option>
|
||||||
|
|
@ -379,7 +413,7 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Email Account" %}</label>
|
<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 value="">{% trans "Choose an email account..." %}</option>
|
||||||
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
|
<option ng-repeat="email in emails track by $index" value="{$ email.email $}">{$ email.email $}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -387,7 +421,7 @@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Forwarding Options" %}</label>
|
<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="Forward to email">{% trans "Forward to email" %}</option>
|
||||||
<option value="Pipe to program">{% trans "Pipe to program" %}</option>
|
<option value="Pipe to program">{% trans "Pipe to program" %}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -402,21 +436,21 @@
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<label class="form-label">{% trans "Source Email" %}</label>
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-group mb-3">
|
<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>
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<label class="form-label"> </label>
|
<label class="form-label"> </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>
|
<i class="fas fa-plus-circle"></i>
|
||||||
{% trans "Add Forwarding" %}
|
{% trans "Add Forwarding" %}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -427,17 +461,17 @@
|
||||||
<table class="forwarding-table">
|
<table class="forwarding-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 10%;">{% trans "ID" %}</th>
|
<th class="table-id">{% trans "ID" %}</th>
|
||||||
<th style="width: 35%;">{% trans "Source" %}</th>
|
<th class="table-source">{% trans "Source" %}</th>
|
||||||
<th style="width: 40%;">{% trans "Destination" %}</th>
|
<th class="table-destination">{% trans "Destination" %}</th>
|
||||||
<th style="width: 15%; text-align: center;">{% trans "Actions" %}</th>
|
<th class="table-actions">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="record in records track by $index">
|
<tr ng-repeat="record in records track by $index">
|
||||||
<td><strong ng-bind="record.id"></strong></td>
|
<td><strong ng-bind="record.id"></strong></td>
|
||||||
<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>
|
<span ng-bind="record.source"></span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
@ -447,9 +481,9 @@
|
||||||
<span class="option-badge" ng-hide="record.destination.indexOf('@') > -1">
|
<span class="option-badge" ng-hide="record.destination.indexOf('@') > -1">
|
||||||
<i class="fas fa-terminal"></i> Program
|
<i class="fas fa-terminal"></i> Program
|
||||||
</span>
|
</span>
|
||||||
<span style="margin-left: 0.5rem;" ng-bind="record.destination"></span>
|
<span class="destination-text" ng-bind="record.destination"></span>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: center;">
|
<td class="table-actions">
|
||||||
<button type="button" ng-click="deleteForwarding(record.source, record.destination)" class="btn-danger">
|
<button type="button" ng-click="deleteForwarding(record.source, record.destination)" class="btn-danger">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
{% trans "Delete" %}
|
{% trans "Delete" %}
|
||||||
|
|
|
||||||
|
|
@ -164,12 +164,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix for Firefox on Windows */
|
/* Fix for Firefox on Windows */
|
||||||
@-moz-document url-prefix() {
|
|
||||||
select.form-control {
|
select.form-control {
|
||||||
color: var(--text-dark, #2f3640) !important;
|
color: var(--text-dark, #2f3640) !important;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure selected option is always visible */
|
/* Ensure selected option is always visible */
|
||||||
select.form-control::-ms-value {
|
select.form-control::-ms-value {
|
||||||
|
|
@ -397,6 +395,30 @@
|
||||||
flex-wrap: wrap;
|
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 */
|
/* Modern Modal Styles */
|
||||||
.modal-content {
|
.modal-content {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
@ -593,7 +615,7 @@
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Domain" %}</label>
|
<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>
|
<option value="">{% trans "Choose a domain..." %}</option>
|
||||||
{% for items in websiteList %}
|
{% for items in websiteList %}
|
||||||
<option value="{{ items }}">{{ items }}</option>
|
<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>
|
<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>
|
||||||
</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>
|
<i class="fas fa-wrench"></i>
|
||||||
{% trans "Fix SSL Now" %}
|
{% trans "Fix SSL Now" %}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -731,11 +753,11 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="record in records track by $index">
|
<tr ng-repeat="record in records track by $index">
|
||||||
<td>
|
<td>
|
||||||
<i class="fas fa-user-circle" style="color: var(--accent-color, #5b5fcf); margin-right: 0.5rem;"></i>
|
<i class="fas fa-user-circle email-icon"></i>
|
||||||
<strong style="font-size: 0.9375rem; color: var(--text-primary, #1e293b);" ng-bind="record.email"></strong>
|
<strong class="email-address" ng-bind="record.email"></strong>
|
||||||
</td>
|
</td>
|
||||||
<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 }}
|
{{ record.DiskUsage }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -767,7 +789,7 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
<h4 class="modal-title">Change Password
|
<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>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
@ -775,7 +797,7 @@
|
||||||
<div ng-hide="installationDetailsForm" class="form-group">
|
<div ng-hide="installationDetailsForm" class="form-group">
|
||||||
<label class="col-sm-3 control-label">{% trans "Email" %}</label>
|
<label class="col-sm-3 control-label">{% trans "Email" %}</label>
|
||||||
<div class="col-sm-6">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -784,7 +806,7 @@
|
||||||
<div ng-hide="installationDetailsForm" class="form-group">
|
<div ng-hide="installationDetailsForm" class="form-group">
|
||||||
<label class="col-sm-3 control-label">{% trans "Password" %}</label>
|
<label class="col-sm-3 control-label">{% trans "Password" %}</label>
|
||||||
<div class="col-sm-6">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</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>
|
<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>
|
||||||
</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>
|
<p>{% trans "Are you sure you want to continue?" %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,7 @@
|
||||||
color: var(--text-primary, #2f3640);
|
color: var(--text-primary, #2f3640);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
|
|
||||||
|
|
@ -411,7 +411,7 @@
|
||||||
<div class="package-selector-card">
|
<div class="package-selector-card">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Package to Delete" %}</label>
|
<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>
|
<option value="">-- {% trans "Select a package" %} --</option>
|
||||||
{% for items in packageList %}
|
{% for items in packageList %}
|
||||||
<option>{{ items }}</option>
|
<option>{{ items }}</option>
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,10 @@
|
||||||
color: var(--warning-dark);
|
color: var(--warning-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Action buttons */
|
/* Action buttons */
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -344,6 +348,7 @@
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
|
@ -483,7 +488,7 @@
|
||||||
<td>{$ record.ftpAccounts $}</td>
|
<td>{$ record.ftpAccounts $}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="status-badge" ng-class="{'enabled': record.allowFullDomain, 'disabled': !record.allowFullDomain}">
|
<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 "Enabled" %}</span>
|
||||||
<span ng-if="!record.allowFullDomain">{% trans "Disabled" %}</span>
|
<span ng-if="!record.allowFullDomain">{% trans "Disabled" %}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -526,7 +531,7 @@
|
||||||
<form name="editPackageForm">
|
<form name="editPackageForm">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Package Name" %}</label>
|
<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>
|
||||||
|
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
|
|
@ -537,14 +542,14 @@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Domains" %}</label>
|
<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 class="help-text">{% trans "Number of domains allowed (0 = Unlimited)" %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Disk Space" %}</label>
|
<label class="form-label">{% trans "Disk Space" %}</label>
|
||||||
<div class="input-group">
|
<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>
|
<span class="input-suffix">MB</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-text">{% trans "Disk space in MB (0 = Unlimited)" %}</div>
|
<div class="help-text">{% trans "Disk space in MB (0 = Unlimited)" %}</div>
|
||||||
|
|
@ -553,7 +558,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Bandwidth" %}</label>
|
<label class="form-label">{% trans "Bandwidth" %}</label>
|
||||||
<div class="input-group">
|
<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>
|
<span class="input-suffix">MB</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-text">{% trans "Monthly bandwidth in MB (0 = Unlimited)" %}</div>
|
<div class="help-text">{% trans "Monthly bandwidth in MB (0 = Unlimited)" %}</div>
|
||||||
|
|
@ -568,17 +573,17 @@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Email Accounts" %}</label>
|
<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>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Databases" %}</label>
|
<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>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "FTP Accounts" %}</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,7 @@
|
||||||
color: var(--text-primary, #2f3640);
|
color: var(--text-primary, #2f3640);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
|
@ -393,7 +394,7 @@
|
||||||
<div class="package-selector-card">
|
<div class="package-selector-card">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Select Package to Modify" %}</label>
|
<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>
|
<option value="">-- {% trans "Select a package" %} --</option>
|
||||||
{% for items in packList %}
|
{% for items in packList %}
|
||||||
<option>{{ items }}</option>
|
<option>{{ items }}</option>
|
||||||
|
|
@ -417,7 +418,7 @@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Domains" %}</label>
|
<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 class="help-text">{% trans "Number of domains allowed (0 = Unlimited)" %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -425,7 +426,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Disk Space" %}</label>
|
<label class="form-label">{% trans "Disk Space" %}</label>
|
||||||
<div class="input-group">
|
<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>
|
<span class="input-suffix">MB</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-text">{% trans "Disk space in MB (0 = Unlimited)" %}</div>
|
<div class="help-text">{% trans "Disk space in MB (0 = Unlimited)" %}</div>
|
||||||
|
|
@ -434,7 +435,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Bandwidth" %}</label>
|
<label class="form-label">{% trans "Bandwidth" %}</label>
|
||||||
<div class="input-group">
|
<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>
|
<span class="input-suffix">MB</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-text">{% trans "Monthly bandwidth in MB (0 = Unlimited)" %}</div>
|
<div class="help-text">{% trans "Monthly bandwidth in MB (0 = Unlimited)" %}</div>
|
||||||
|
|
@ -451,20 +452,20 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "FTP Accounts" %}</label>
|
<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 class="help-text">{% trans "Number of FTP accounts" %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Databases" %}</label>
|
<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 class="help-text">{% trans "Number of MySQL databases" %}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{% trans "Email Accounts" %}</label>
|
<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 class="help-text">{% trans "Number of email accounts" %}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -37,3 +37,4 @@ asyncssh==2.21.0
|
||||||
python-jose==3.4.0
|
python-jose==3.4.0
|
||||||
websockets==15.0.1
|
websockets==15.0.1
|
||||||
PyJWT
|
PyJWT
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,7 @@
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -190,6 +191,14 @@
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
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 {
|
.page-subtitle {
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
color: var(--text-on-gradient);
|
color: var(--text-on-gradient);
|
||||||
|
|
@ -207,6 +216,7 @@
|
||||||
|
|
||||||
.stat-badge {
|
.stat-badge {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
padding: 0.5rem 1.5rem;
|
padding: 0.5rem 1.5rem;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
|
@ -560,6 +570,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
-webkit-backdrop-filter: blur(4px);
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -760,7 +771,7 @@
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<h1 class="page-title">
|
<h1 class="page-title">
|
||||||
<div class="package-icon">
|
<div class="package-icon">
|
||||||
<i class="fas fa-cube" style="font-size: 1.75rem;"></i>
|
<i class="fas fa-cube"></i>
|
||||||
</div>
|
</div>
|
||||||
{% trans "System Package Manager" %}
|
{% trans "System Package Manager" %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
@ -786,15 +797,15 @@
|
||||||
<div class="tabs-container">
|
<div class="tabs-container">
|
||||||
<!-- Tab Navigation -->
|
<!-- Tab Navigation -->
|
||||||
<div class="modern-tabs">
|
<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>
|
<i class="fas fa-sync-alt tab-icon"></i>
|
||||||
{% trans "Available Updates" %}
|
{% trans "Available Updates" %}
|
||||||
</button>
|
</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>
|
<i class="fas fa-list tab-icon"></i>
|
||||||
{% trans "All Packages" %}
|
{% trans "All Packages" %}
|
||||||
</button>
|
</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>
|
<i class="fas fa-shield-alt tab-icon"></i>
|
||||||
{% trans "CyberPanel Packages" %}
|
{% trans "CyberPanel Packages" %}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -807,20 +818,20 @@
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<i class="fas fa-search search-icon"></i>
|
<i class="fas fa-search search-icon"></i>
|
||||||
<input type="text" class="search-input" placeholder="{% trans 'Search packages...' %}"
|
<input type="text" class="search-input" placeholder="{% trans 'Search packages...' %}"
|
||||||
ng-model="packSearch">
|
ng-model="packSearch" aria-label="{% trans 'Search packages' %}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<select ng-model="recordsToShow" class="select-control"
|
<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="10">10 {% trans "per page" %}</option>
|
||||||
<option value="50">50 {% trans "per page" %}</option>
|
<option value="50">50 {% trans "per page" %}</option>
|
||||||
<option value="100">100 {% trans "per page" %}</option>
|
<option value="100">100 {% trans "per page" %}</option>
|
||||||
<option value="500">500 {% trans "per page" %}</option>
|
<option value="500">500 {% trans "per page" %}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button class="btn btn-success" ng-click="updatePackage('all')"
|
<button type="button" class="btn btn-success" ng-click="updatePackage('all')"
|
||||||
ng-show="currentTab === 'upgrade'">
|
ng-show="currentTab === 'upgrade'" aria-label="{% trans 'Update all packages' %}">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
{% trans "Update All" %}
|
{% trans "Update All" %}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -865,24 +876,24 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="lock-toggle"
|
<button type="button" class="lock-toggle"
|
||||||
ng-class="{'locked': package.lock == 1, 'unlocked': package.lock == 0}"
|
ng-class="{'locked': package.lock == 1, 'unlocked': package.lock == 0}"
|
||||||
ng-click="lockStatus(package.package, package.lock == 1 ? 1 : 0)"
|
ng-click="lockStatus(package.package, package.lock == 1 ? 1 : 0)"
|
||||||
title="{$ package.lock == 1 ? 'Package is locked' : 'Package is unlocked' $}">
|
aria-label="{$ package.lock == 1 ? 'Unlock package' : 'Lock package' $}">
|
||||||
<i class="fas"
|
<i class="fas"
|
||||||
ng-class="{'fa-lock': package.lock == 1, 'fa-lock-open': package.lock == 0}"></i>
|
ng-class="{'fa-lock': package.lock == 1, 'fa-lock-open': package.lock == 0}"></i>
|
||||||
</a>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button class="btn btn-info btn-sm"
|
<button type="button" class="btn btn-info btn-sm"
|
||||||
ng-click="showPackageDetails(package.package)">
|
ng-click="showPackageDetails(package.package)" aria-label="{% trans 'View package details' %}">
|
||||||
<i class="fas fa-info-circle"></i>
|
<i class="fas fa-info-circle"></i>
|
||||||
{% trans "Details" %}
|
{% trans "Details" %}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary btn-sm"
|
<button type="button" class="btn btn-primary btn-sm"
|
||||||
ng-click="showUpdateModal(package.package)"
|
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>
|
<i class="fas fa-download"></i>
|
||||||
{% trans "Update" %}
|
{% trans "Update" %}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -912,7 +923,7 @@
|
||||||
<div class="pagination-controls">
|
<div class="pagination-controls">
|
||||||
<span>{% trans "Page:" %}</span>
|
<span>{% trans "Page:" %}</span>
|
||||||
<select ng-model="currentPage" class="page-select"
|
<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 $}">
|
<option ng-repeat="page in pagination" value="{$ $index + 1 $}">
|
||||||
{$ $index + 1 $}
|
{$ $index + 1 $}
|
||||||
</option>
|
</option>
|
||||||
|
|
@ -931,7 +942,7 @@
|
||||||
<i class="fas fa-info-circle"></i>
|
<i class="fas fa-info-circle"></i>
|
||||||
{% trans "Package Details:" %} {$ selectedPackage $}
|
{% trans "Package Details:" %} {$ selectedPackage $}
|
||||||
</h3>
|
</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>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -948,9 +959,9 @@
|
||||||
<h3 class="modal-title">
|
<h3 class="modal-title">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
{% trans "Updating Package:" %} {$ updatingPackage $}
|
{% 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>
|
</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>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1729,6 +1729,8 @@ app.controller('scheduleBackup', function ($scope, $http, $window) {
|
||||||
$scope.allSites = response.data.allSites;
|
$scope.allSites = response.data.allSites;
|
||||||
$scope.lastRun = response.data.lastRun;
|
$scope.lastRun = response.data.lastRun;
|
||||||
$scope.currentStatus = response.data.currentStatus;
|
$scope.currentStatus = response.data.currentStatus;
|
||||||
|
$scope.backupFrequency = response.data.currently;
|
||||||
|
$scope.backupRetention = response.data.retention;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
new PNotify({
|
new PNotify({
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
Loading…
Reference in New Issue