diff --git a/CyberCP/settings.py b/CyberCP/settings.py index 1edb42b25..2008956ad 100644 --- a/CyberCP/settings.py +++ b/CyberCP/settings.py @@ -65,7 +65,8 @@ INSTALLED_APPS = [ # Apps with multiple or complex dependencies 'emailPremium', - 'emailMarketing', # Depends on websiteFunctions and loginSystem + # Optional plugins (e.g. emailMarketing, discordWebhooks) - install via Plugin Store + # from https://github.com/master3395/cyberpanel-plugins - plugin installer adds them 'cloudAPI', # Depends on websiteFunctions 'containerization', # Depends on websiteFunctions 'IncBackups', # Depends on websiteFunctions and loginSystem diff --git a/CyberCP/urls.py b/CyberCP/urls.py index ea5ccf382..db097f5a0 100644 --- a/CyberCP/urls.py +++ b/CyberCP/urls.py @@ -51,7 +51,8 @@ urlpatterns = [ path('CloudLinux/', include('CLManager.urls')), path('IncrementalBackups/', include('IncBackups.urls')), path('aiscanner/', include('aiScanner.urls')), - path('emailMarketing/', include('emailMarketing.urls')), + # Optional plugin routes - added by plugin installer when plugins are installed from Plugin Store + # path('emailMarketing/', include('emailMarketing.urls')), # path('Terminal/', include('WebTerminal.urls')), path('', include('loginSystem.urls')), ] diff --git a/baseTemplate/views.py b/baseTemplate/views.py index 1c7d05433..172e86731 100644 --- a/baseTemplate/views.py +++ b/baseTemplate/views.py @@ -129,6 +129,11 @@ def getAdminStatus(request): def getSystemStatus(request): + default_fallback = { + 'cpuUsage': 0, 'ramUsage': 0, 'diskUsage': 0, + 'cpuCores': 2, 'ramTotalMB': 4096, 'diskTotalGB': 100, + 'diskFreeGB': 100, 'uptime': 'N/A' + } try: val = request.session['userID'] currentACL = ACLManager.loadedACL(val) @@ -215,31 +220,13 @@ def getSystemStatus(request): except KeyError as e: logging.CyberCPLogFileWriter.writeToFile(f'[getSystemStatus] KeyError - No session userID: {str(e)}') - # Return default values on error - default_data = { - 'cpuUsage': 0, - 'ramUsage': 0, - 'diskUsage': 0, - 'cpuCores': 2, - 'ramTotalMB': 4096, - 'diskTotalGB': 100, - 'diskFreeGB': 100, - 'uptime': 'N/A' - } - return HttpResponse(json.dumps(default_data)) + return HttpResponse(json.dumps(default_fallback)) except Exception as e: - # Return default values on error - default_data = { - 'cpuUsage': 0, - 'ramUsage': 0, - 'diskUsage': 0, - 'cpuCores': 2, - 'ramTotalMB': 4096, - 'diskTotalGB': 100, - 'diskFreeGB': 100, - 'uptime': 'N/A' - } - return HttpResponse(json.dumps(default_data)) + logging.CyberCPLogFileWriter.writeToFile(f'[getSystemStatus] Exception: {str(e)}') + try: + return HttpResponse(json.dumps(default_fallback)) + except Exception: + return HttpResponse('{"cpuUsage":0,"ramUsage":0,"diskUsage":0,"cpuCores":2,"ramTotalMB":4096,"diskTotalGB":100,"diskFreeGB":100,"uptime":"N/A"}', content_type='application/json') def getLoadAverage(request): diff --git a/deploy_phpmyadmin_redirect.sh b/deploy_phpmyadmin_redirect.sh deleted file mode 100644 index 203971f78..000000000 --- a/deploy_phpmyadmin_redirect.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -# CyberPanel phpMyAdmin Access Control Deployment Script -# This script implements redirect functionality for unauthenticated phpMyAdmin access - -echo "=== CyberPanel phpMyAdmin Access Control Deployment ===" - -# Check if running as root -if [ "$EUID" -ne 0 ]; then - echo "Please run this script as root" - exit 1 -fi - -# Backup original phpMyAdmin index.php if it exists -if [ -f "/usr/local/CyberCP/public/phpmyadmin/index.php" ]; then - echo "Backing up original phpMyAdmin index.php..." - cp /usr/local/CyberCP/public/phpmyadmin/index.php /usr/local/CyberCP/public/phpmyadmin/index.php.backup.$(date +%Y%m%d_%H%M%S) -fi - -# Deploy the redirect index.php -echo "Deploying phpMyAdmin access control..." -cp /usr/local/CyberCP/phpmyadmin_index_redirect.php /usr/local/CyberCP/public/phpmyadmin/index.php - -# Deploy .htaccess for additional protection -echo "Deploying .htaccess protection..." -cp /usr/local/CyberCP/phpmyadmin_htaccess /usr/local/CyberCP/public/phpmyadmin/.htaccess - -# Set proper permissions -echo "Setting permissions..." -chown lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin/index.php -chmod 644 /usr/local/CyberCP/public/phpmyadmin/index.php -chown lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin/.htaccess -chmod 644 /usr/local/CyberCP/public/phpmyadmin/.htaccess - -# Restart LiteSpeed to ensure changes take effect -echo "Restarting LiteSpeed..." -systemctl restart lscpd - -echo "=== Deployment Complete ===" -echo "" -echo "phpMyAdmin access control has been deployed successfully!" -echo "" -echo "What this does:" -echo "- Users trying to access phpMyAdmin directly without being logged into CyberPanel" -echo " will now be redirected to the CyberPanel login page (/base/)" -echo "- Authenticated users will continue to access phpMyAdmin normally" -echo "" -echo "To revert changes, restore the backup:" -echo "cp /usr/local/CyberCP/public/phpmyadmin/index.php.backup.* /usr/local/CyberCP/public/phpmyadmin/index.php" -echo "" -echo "Test the implementation by:" -echo "1. Opening an incognito/private browser window" -echo "2. Going to https://your-server:2087/phpmyadmin/" -echo "3. You should be redirected to the CyberPanel login page" diff --git a/emailMarketing/meta.xml b/emailMarketing/meta.xml index 2c5d8e719..d125a471f 100644 --- a/emailMarketing/meta.xml +++ b/emailMarketing/meta.xml @@ -1,7 +1,7 @@ Email Marketing - plugin + Utility Email Marketing plugin for CyberPanel. - 1.0.0 + 1.0.1 \ No newline at end of file diff --git a/examplePlugin/meta.xml b/examplePlugin/meta.xml index 55990a577..9e7e58b4d 100644 --- a/examplePlugin/meta.xml +++ b/examplePlugin/meta.xml @@ -1,8 +1,8 @@ examplePlugin - plugin + Utility This is an example plugin - 1.0.0 + 1.0.1 usmannasir \ No newline at end of file diff --git a/pluginHolder/templates/pluginHolder/help.html b/pluginHolder/templates/pluginHolder/help.html index 4624368ac..7892ed3a3 100644 --- a/pluginHolder/templates/pluginHolder/help.html +++ b/pluginHolder/templates/pluginHolder/help.html @@ -274,6 +274,100 @@ text-decoration: underline; } + /* Collapsible sections */ + .help-section { + border: 1px solid var(--border-primary, #e8e9ff); + border-radius: 8px; + margin-bottom: 12px; + overflow: hidden; + } + + .help-section-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + background: var(--bg-secondary, #f8f9ff); + cursor: pointer; + user-select: none; + transition: background 0.2s ease; + } + + .help-section-header:hover { + background: #eef0ff; + } + + .help-section-header h2 { + margin: 0; + padding: 0; + border: none; + font-size: 18px; + font-weight: 600; + color: var(--text-primary, #2f3640); + } + + .help-section-header .section-toggle { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: rgba(88,86,214,0.15); + color: #5856d6; + transition: transform 0.25s ease; + } + + .help-section.expanded .section-toggle { + transform: rotate(180deg); + } + + .help-section-body { + max-height: 0; + overflow: hidden; + transition: max-height 0.35s ease-out; + } + + .help-section.expanded .help-section-body { + max-height: 8000px; + transition: max-height 0.5s ease-in; + } + + .help-section-content { + padding: 20px 24px 24px; + border-top: 1px solid var(--border-primary, #e8e9ff); + } + + .help-section-content h2 { + display: none; + } + + .help-section-content > h3:first-child { + margin-top: 0; + } + + .help-expand-all { + display: flex; + gap: 12px; + margin-top: 12px; + flex-wrap: wrap; + } + + .help-expand-all button { + padding: 6px 14px; + font-size: 13px; + border-radius: 6px; + border: 1px solid var(--border-primary, #e8e9ff); + background: var(--bg-primary, white); + color: #5856d6; + cursor: pointer; + transition: background 0.2s; + } + + .help-expand-all button:hover { + background: #eef0ff; + } + @media (max-width: 768px) { .help-wrapper { padding: 15px; @@ -314,7 +408,13 @@
-
+ -

{% trans "Introduction" %}

+
+

{% trans "Introduction" %}

+

{% trans "CyberPanel's plugin system allows developers to extend the control panel's functionality with custom features. Plugins integrate seamlessly with CyberPanel's Django-based architecture, providing access to the full power of the platform while maintaining security and consistency." %}

{% trans "What Can Plugins Do?" %}

@@ -347,8 +458,11 @@
  • {% trans "Create custom reporting tools" %}
  • {% trans "Integrate security features" %}
  • +
    -

    {% trans "Prerequisites" %}

    +
    +

    {% trans "Prerequisites" %}

    +

    {% trans "Required Knowledge" %}

    • Python 3.6+: {% trans "Basic to intermediate Python knowledge" %}
    • @@ -357,8 +471,11 @@
    • Linux/Unix: {% trans "Basic command-line familiarity" %}
    • XML: {% trans "Understanding of XML structure for meta.xml" %}
    +
    -

    {% trans "Plugin Architecture Overview" %}

    +
    +

    {% trans "Plugin Architecture Overview" %}

    +

    {% trans "How Plugins Work" %}

    1. {% trans "Plugin Source Location" %}: /home/cyberpanel/plugins/ @@ -374,8 +491,11 @@
    +
    -

    {% trans "Creating Your First Plugin" %}

    +
    +

    {% trans "Creating Your First Plugin" %}

    +

    {% trans "Step 1: Create Plugin Directory Structure" %}

    # Navigate to plugins directory
     cd /home/cyberpanel/plugins
    @@ -401,6 +521,10 @@ mkdir -p migrations
    <settings_url>/plugins/myFirstPlugin/settings/</settings_url> </cyberpanelPluginConfig> +
    + {% trans "Required: Category (type)" %}: {% trans "The <type> field is required. See the Plugin Categories section below for valid options. Plugins without a valid category will not appear in the Plugin Store." %} +
    +

    {% trans "Step 3: Create urls.py" %}

    from django.urls import path
     from . import views
    @@ -458,8 +582,11 @@ def main_view(request):
             
    {% trans "Important" %}: {% trans "Always use the @cyberpanel_login_required decorator for all views to ensure users are authenticated." %}
    +
    -

    {% trans "Plugin Structure & Files" %}

    +
    +

    {% trans "Plugin Structure & Files" %}

    +

    {% trans "Required Files" %}

    • __init__.py - {% trans "Required" %} - {% trans "Python package marker" %}
    • @@ -476,8 +603,11 @@ def main_view(request):
    • templates/ - {% trans "Optional" %} - {% trans "HTML templates" %}
    • static/ - {% trans "Optional" %} - {% trans "CSS, JS, images" %}
    +
    -

    {% trans "Version Numbering (Semantic Versioning)" %}

    +
    +

    {% trans "Version Numbering (Semantic Versioning)" %}

    +

    {% trans "CyberPanel plugins use semantic versioning (SemVer) with a three-number format (X.Y.Z) to help users understand the impact of each update:" %}

    @@ -505,8 +635,107 @@ def main_view(request):

    {% trans "Version Format in meta.xml" %}

    <version>1.0.0</version>

    {% trans "Never use formats like '1.0' or 'v1.0'. Always use the full semantic version: '1.0.0'" %}

    +
    -

    {% trans "Core Components" %}

    +
    +

    {% trans "Plugin Categories" %}

    +
    +

    {% trans "The <type> field in meta.xml determines how your plugin is grouped in the Plugin Store. Use exactly one of these values (case-sensitive):" %}

    + + + + + + + + + + + + + + + + + + +
    {% trans "Category" %}{% trans "Purpose" %}
    Utility{% trans "General-purpose tools, helpers, and utilities" %}
    Security{% trans "Security features: firewalls, fail2ban, SSL, etc." %}
    Backup{% trans "Backup, snapshot, and restore functionality" %}
    Performance{% trans "Caching, optimization, and performance tuning" %}
    Monitoring{% trans "Monitoring, alerts, and health checks" %}
    Integration{% trans "Third-party integrations: Discord, Slack, webhooks, APIs" %}
    Email{% trans "Email marketing, SMTP, mail management" %}
    Development{% trans "Developer tools: PM2, Node.js, deployment" %}
    Analytics{% trans "Analytics, tracking, and reporting" %}
    +
    + +
    +

    {% trans "Freshness Badges" %}

    +
    +

    {% trans "The Plugin Store and Installed Plugins views display freshness badges based on the last update date (modify_date from GitHub commit or meta.xml file mtime). These help users quickly see how actively maintained a plugin is:" %}

    + + + + + + + + + + + + + + +
    {% trans "Badge" %}{% trans "Condition" %}{% trans "Meaning" %}
    NEW{% trans "Updated within last 90 days" %}{% trans "Recently released or actively maintained" %}
    Stable{% trans "Updated within last 365 days" %}{% trans "Updated within the past year" %}
    Unstable{% trans "1–2 years since last update" %}{% trans "May need maintenance; consider forking or updating" %}
    STALE{% trans "Over 2 years since last update" %}{% trans "Not updated recently; use with caution" %}
    +

    {% trans "Badges are calculated automatically from the plugin's modify_date. For plugins in the Plugin Store, this comes from the GitHub repository's last commit. For installed plugins, it uses the meta.xml file modification time." %}

    +
    + +
    +

    {% trans "Premium/Paid Plugin Creation" %}

    +
    +

    {% trans "You can create premium (paid) plugins and implement your own verification system. This includes optional encryption between the plugin and your verification site to prevent unauthorized bypass." %}

    + +

    {% trans "1. Mark Your Plugin as Paid in meta.xml" %}

    +
    <paid>true</paid>
    +<patreon_tier>Your Tier Name</patreon_tier>
    +<patreon_url>https://www.patreon.com/your-page</patreon_url>
    +

    {% trans "Set <paid>true</paid> to display the Premium badge and subscription prompts in the Plugin Store." %}

    + +

    {% trans "2. Build Your Own Verification System" %}

    +

    {% trans "Premium plugins typically verify access via a remote API. You can host this on your own site. Common verification methods:" %}

    +
      +
    • {% trans "Patreon" %}: {% trans "Verify membership via Patreon OAuth/API" %}
    • +
    • {% trans "PayPal" %}: {% trans "Verify one-time or recurring payments" %}
    • +
    • {% trans "Plugin Grants" %}: {% trans "Admin panel where you manually grant access by email, IP, or domain" %}
    • +
    • {% trans "Activation Keys" %}: {% trans "Generate unique keys when granting access; users enter the key in the plugin" %}
    • +
    + +

    {% trans "3. Optional: Encrypt Plugin–API Communication" %}

    +

    {% trans "To protect against users modifying your plugin to bypass verification, you can encrypt the communication between the plugin and your verification API using AES-256-CBC. This ensures:" %}

    +
      +
    • {% trans "Verification requests cannot be easily intercepted or forged" %}
    • +
    • {% trans "Responses cannot be tampered with" %}
    • +
    • {% trans "Your verification logic remains on your server; the plugin only encrypts/decrypts with a shared key" %}
    • +
    +

    {% trans "Implementation outline:" %}

    +
      +
    1. {% trans "Generate a 32-byte secret key and store it in your API config (e.g. config.php)" %}
    2. +
    3. {% trans "In your plugin (Python), encrypt outgoing requests and decrypt responses using the same key" %}
    4. +
    5. {% trans "On your API (PHP/Python), decrypt incoming requests and encrypt responses" %}
    6. +
    7. {% trans "Use the X-Encrypted: 1 header to indicate encrypted payloads" %}
    8. +
    +

    {% trans "Both sides must use the same AES-256-CBC key. Keep the key secret and never commit it to public repositories. Store it in a protected config file outside the web root or in environment variables." %}

    + +

    {% trans "4. Typical Premium Plugin Flow" %}

    +
      +
    1. {% trans "User installs your premium plugin" %}
    2. +
    3. {% trans "Plugin shows an activation screen (Patreon/PayPal links, or activation key input)" %}
    4. +
    5. {% trans "Plugin calls your verification API with user identifier (email, domain, IP) or activation key" %}
    6. +
    7. {% trans "Your API checks: Plugin Grants, activation key, Patreon, or PayPal — in your preferred order" %}
    8. +
    9. {% trans "If access is granted, API returns success; plugin unlocks features and optionally stores the key locally" %}
    10. +
    +
    + {% trans "Tip" %}: {% trans "Provide multiple verification paths: Patreon for subscribers, PayPal for one-time purchasers, Plugin Grants for beta testers or sponsors, and activation keys for manual grants. Encryption is optional but recommended for paid plugins to deter bypass attempts." %} +
    +
    + +
    +

    {% trans "Core Components" %}

    +

    {% trans "1. Authentication & Security" %}

    {% trans "Always use the cyberpanel_login_required decorator:" %}

    @cyberpanel_login_required
    @@ -532,8 +761,11 @@ urlpatterns = [
     {% load static %}
     {% load i18n %}
    {% endverbatim %} +
    -

    {% trans "Advanced Features" %}

    +
    +

    {% trans "Advanced Features" %}

    +

    {% trans "Database Models" %}

    from django.db import models
     
    @@ -558,8 +790,11 @@ class MyForm(forms.Form):
     def api_endpoint(request):
         data = {'status': 'success'}
         return JsonResponse(data)
    +
    -

    {% trans "Best Practices" %}

    +
    +

    {% trans "Best Practices" %}

    +
    • {% trans "Keep files under 500 lines - split into modules if needed" %}
    • {% trans "Use descriptive names for functions and variables" %}
    • @@ -569,8 +804,11 @@ def api_endpoint(request):
    • {% trans "Use Django ORM instead of raw SQL" %}
    • {% trans "Test your plugin thoroughly before distribution" %}
    +
    -

    {% trans "Security Guidelines" %}

    +
    +

    {% trans "Security Guidelines" %}

    +
    {% trans "Security is Critical" %}: {% trans "Always validate input, use parameterized queries, sanitize output, and check user permissions." %}
    @@ -582,8 +820,11 @@ def api_endpoint(request):
  • {% trans "Use HTTPS for sensitive operations" %}
  • {% trans "Implement rate limiting for API endpoints" %}
  • +
    -

    {% trans "Testing & Debugging" %}

    +
    +

    {% trans "Testing & Debugging" %}

    +

    {% trans "Common Issues" %}

    • {% trans "Template Not Found" %}: {% trans "Check template path and name" %}
    • @@ -601,16 +842,22 @@ xmllint --noout meta.xml # View logs tail -f /usr/local/lscp/logs/error.log
      +
    -

    {% trans "Packaging & Distribution" %}

    +
    +

    {% trans "Packaging & Distribution" %}

    +

    {% trans "Create Plugin Package" %}

    cd /home/cyberpanel/plugins/myPlugin
     zip -r myPlugin-v1.0.0.zip . \
         -x "*.pyc" \
         -x "__pycache__/*" \
         -x "*.log"
    +
    -

    {% trans "Troubleshooting" %}

    +
    +

    {% trans "Troubleshooting" %}

    +

    {% trans "Installation Issues" %}

    • {% trans "Check meta.xml format and validity" %}
    • @@ -626,8 +873,11 @@ zip -r myPlugin-v1.0.0.zip . \
    • {% trans "Review template paths" %}
    • {% trans "Check for JavaScript errors" %}
    +
    -

    {% trans "Examples & References" %}

    +
    +

    {% trans "Examples & References" %}

    +

    {% trans "Reference Plugins" %}

    • examplePlugin: {% trans "Basic plugin structure" %} @@ -663,28 +913,40 @@ zip -r myPlugin-v1.0.0.zip . \
      {% trans "Ready to Start?" %} {% trans "Begin with a simple plugin and gradually add more features as you become familiar with the system. Check the examplePlugin and testPlugin directories for complete working examples." %}
      +

    {% trans "Author" %}: master3395 | - {% trans "Version" %}: 2.0.0 | - {% trans "Last Updated" %}: 2026-01-04 + {% trans "Version" %}: 2.1.0 | + {% trans "Last Updated" %}: 2026-02-02

    {% endblock %} \ No newline at end of file diff --git a/pluginHolder/views.py b/pluginHolder/views.py index 7ff70153a..1dec3c48e 100644 --- a/pluginHolder/views.py +++ b/pluginHolder/views.py @@ -53,6 +53,33 @@ def _is_plugin_enabled(plugin_name): return True # Default to enabled if file read fails return True # Default to enabled if state file doesn't exist + +def _get_freshness_badge(modify_date): + """ + Return freshness badge (NEW/Stable/STALE) based on modify_date. + modify_date format: 'YYYY-MM-DD HH:MM:SS' or 'N/A' + - 0-90 days: NEW (yellow) + - 90-365 days: Stable (green) + - 730+ days: STALE (red) + - 365-730 days: no badge + """ + if not modify_date or modify_date == 'N/A' or not isinstance(modify_date, str): + return None + try: + dt = datetime.strptime(modify_date[:19], '%Y-%m-%d %H:%M:%S') + days_ago = (datetime.now() - dt).days + if days_ago <= 90: + return {'badge': 'NEW', 'class': 'freshness-badge-new', 'title': 'This plugin was released/updated within the last 3 months'} + elif days_ago <= 365: + return {'badge': 'Stable', 'class': 'freshness-badge-stable', 'title': 'This plugin was updated within the last year'} + elif days_ago < 730: + return {'badge': 'Unstable', 'class': 'freshness-badge-unstable', 'title': 'This plugin has not been updated in over 1 year'} + else: + return {'badge': 'STALE', 'class': 'freshness-badge-stale', 'title': 'This plugin has not been updated in over 2 years'} + except (ValueError, TypeError): + pass + return None + def _set_plugin_state(plugin_name, enabled): """Set plugin enabled/disabled state""" state_file = _get_plugin_state_file(plugin_name) @@ -115,20 +142,27 @@ def installed(request): desc_elem = root.find('description') version_elem = root.find('version') - # Type field is optional (testPlugin doesn't have it) - if name_elem is None or desc_elem is None or version_elem is None: - errorPlugins.append({'name': plugin, 'error': 'Missing required metadata fields (name, description, or version)'}) + # All fields required including type (category) - no default + if name_elem is None or type_elem is None or desc_elem is None or version_elem is None: + errorPlugins.append({'name': plugin, 'error': 'Missing required metadata fields (name, type/category, description, or version)'}) logging.writeToFile(f"Plugin {plugin}: Missing required metadata fields in meta.xml") continue - # Check if text is None (empty elements) - if name_elem.text is None or desc_elem.text is None or version_elem.text is None: - errorPlugins.append({'name': plugin, 'error': 'Empty metadata fields'}) + # Check if text is None or empty (all required) + type_text = type_elem.text.strip() if type_elem.text else '' + if name_elem.text is None or desc_elem.text is None or version_elem.text is None or not type_text: + errorPlugins.append({'name': plugin, 'error': 'Empty metadata fields (name, type/category, description, or version required)'}) logging.writeToFile(f"Plugin {plugin}: Empty metadata fields in meta.xml") continue + # Valid categories only: Utility, Security, Backup, Performance (Plugin category removed) + if type_text.lower() not in ('utility', 'security', 'backup', 'performance', 'monitoring', 'integration', 'email', 'development', 'analytics'): + errorPlugins.append({'name': plugin, 'error': f'Invalid category "{type_text}". Use: Utility, Security, Backup, or Performance.'}) + logging.writeToFile(f"Plugin {plugin}: Invalid category '{type_text}'") + continue + data['name'] = name_elem.text - data['type'] = type_elem.text if type_elem is not None and type_elem.text is not None else 'Plugin' + data['type'] = type_text data['desc'] = desc_elem.text data['version'] = version_elem.text data['plugin_dir'] = plugin # Plugin directory name @@ -158,6 +192,7 @@ def installed(request): modify_date = 'N/A' data['modify_date'] = modify_date + data['freshness_badge'] = _get_freshness_badge(modify_date) # Extract settings URL or main URL for "Manage" button settings_url_elem = root.find('settings_url') @@ -252,20 +287,28 @@ def installed(request): pluginMetaData = ElementTree.parse(metaXmlPath) root = pluginMetaData.getroot() - # Validate required fields + # Validate required fields (including type/category - no default) name_elem = root.find('name') type_elem = root.find('type') desc_elem = root.find('description') version_elem = root.find('version') - if name_elem is None or desc_elem is None or version_elem is None: + if name_elem is None or type_elem is None or desc_elem is None or version_elem is None: + errorPlugins.append({'name': plugin, 'error': 'Missing required metadata (name, type/category, description, or version)'}) continue - if name_elem.text is None or desc_elem.text is None or version_elem.text is None: + type_text = type_elem.text.strip() if type_elem.text else '' + if name_elem.text is None or desc_elem.text is None or version_elem.text is None or not type_text: + errorPlugins.append({'name': plugin, 'error': 'Empty metadata (type/category required)'}) + continue + + # Valid categories only: Utility, Security, Backup, Performance (Plugin category removed) + if type_text.lower() not in ('utility', 'security', 'backup', 'performance', 'monitoring', 'integration', 'email', 'development', 'analytics'): + errorPlugins.append({'name': plugin, 'error': f'Invalid category "{type_text}". Use: Utility, Security, Backup, or Performance.'}) continue data['name'] = name_elem.text - data['type'] = type_elem.text if type_elem is not None and type_elem.text is not None else 'Plugin' + data['type'] = type_text data['desc'] = desc_elem.text data['version'] = version_elem.text data['plugin_dir'] = plugin @@ -287,6 +330,7 @@ def installed(request): modify_date = 'N/A' data['modify_date'] = modify_date + data['freshness_badge'] = _get_freshness_badge(modify_date) # Extract settings URL or main URL settings_url_elem = root.find('settings_url') @@ -943,6 +987,7 @@ def _enrich_store_plugins(plugins): elif 'is_paid' not in plugin or plugin.get('is_paid') is None: # Try to check from local meta.xml if available meta_path = None + source_path = os.path.join(plugin_source_dir, plugin_dir) if os.path.exists(installed_path): meta_path = os.path.join(installed_path, 'meta.xml') elif os.path.exists(source_path): @@ -1050,10 +1095,21 @@ def _fetch_plugins_from_github(): patreon_url_elem = root.find('patreon_url') patreon_url = patreon_url_elem.text if patreon_url_elem is not None else 'https://www.patreon.com/c/newstargeted/membership' + # Category (type) is required - valid: Utility, Security, Backup, Performance (Plugin removed) + type_elem = root.find('type') + if type_elem is None or not type_elem.text or not type_elem.text.strip(): + logging.writeToFile(f"Plugin {plugin_name}: Missing required type/category in meta.xml, skipping") + continue + type_text = type_elem.text.strip().lower() + if type_text not in ('utility', 'security', 'backup', 'performance', 'monitoring', 'integration', 'email', 'development', 'analytics'): + logging.writeToFile(f"Plugin {plugin_name}: Invalid category '{type_elem.text}', skipping (use Utility, Security, Backup, or Performance)") + continue + + freshness = _get_freshness_badge(modify_date) plugin_data = { 'plugin_dir': plugin_name, 'name': root.find('name').text if root.find('name') is not None else plugin_name, - 'type': root.find('type').text if root.find('type') is not None else 'Plugin', + 'type': type_elem.text.strip(), 'description': root.find('description').text if root.find('description') is not None else '', 'version': root.find('version').text if root.find('version') is not None else '1.0.0', 'url': root.find('url').text if root.find('url') is not None else f'/plugins/{plugin_name}/', @@ -1062,6 +1118,7 @@ def _fetch_plugins_from_github(): 'github_url': f'https://github.com/master3395/cyberpanel-plugins/tree/main/{plugin_name}', 'about_url': f'https://github.com/master3395/cyberpanel-plugins/tree/main/{plugin_name}', 'modify_date': modify_date, + 'freshness_badge': freshness, 'is_paid': is_paid, 'patreon_tier': patreon_tier, 'patreon_url': patreon_url @@ -1110,21 +1167,29 @@ def _fetch_plugins_from_github(): @require_http_methods(["GET"]) def fetch_plugin_store(request): """Fetch plugins from the plugin store with caching""" - mailUtilities.checkHome() - - # Try to get from cache first - cached_plugins = _get_cached_plugins() - if cached_plugins is not None: - # Enrich cached plugins with installed/enabled status - enriched_plugins = _enrich_store_plugins(cached_plugins) - return JsonResponse({ - 'success': True, - 'plugins': enriched_plugins, - 'cached': True - }) - - # Cache miss or expired - fetch from GitHub try: + mailUtilities.checkHome() + except Exception as e: + logging.writeToFile(f"fetch_plugin_store: checkHome failed: {str(e)}") + return JsonResponse({ + 'success': False, + 'error': 'Authentication required. Please log in again.', + 'plugins': [] + }, status=401) + + try: + # Try to get from cache first + cached_plugins = _get_cached_plugins() + if cached_plugins is not None: + # Enrich cached plugins with installed/enabled status + enriched_plugins = _enrich_store_plugins(cached_plugins) + return JsonResponse({ + 'success': True, + 'plugins': enriched_plugins, + 'cached': True + }) + + # Cache miss or expired - fetch from GitHub plugins = _fetch_plugins_from_github() # Enrich plugins with installed/enabled status @@ -1139,7 +1204,7 @@ def fetch_plugin_store(request): 'plugins': enriched_plugins, 'cached': False }) - + except Exception as e: error_message = str(e) diff --git a/rollback_phpmyadmin_redirect.sh b/rollback_phpmyadmin_redirect.sh deleted file mode 100644 index 159ec00a3..000000000 --- a/rollback_phpmyadmin_redirect.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -# CyberPanel phpMyAdmin Access Control Rollback Script -# This script reverts the phpMyAdmin access control changes - -echo "=== CyberPanel phpMyAdmin Access Control Rollback ===" - -# Check if running as root -if [ "$EUID" -ne 0 ]; then - echo "Please run this script as root" - exit 1 -fi - -# Find the most recent backup -LATEST_BACKUP=$(ls -t /usr/local/CyberCP/public/phpmyadmin/index.php.backup.* 2>/dev/null | head -n1) - -if [ -z "$LATEST_BACKUP" ]; then - echo "No backup found. Cannot rollback changes." - echo "You may need to reinstall phpMyAdmin or restore from your own backup." - exit 1 -fi - -echo "Found backup: $LATEST_BACKUP" -echo "Restoring original phpMyAdmin index.php..." - -# Restore the original index.php -cp "$LATEST_BACKUP" /usr/local/CyberCP/public/phpmyadmin/index.php - -# Remove the .htaccess file if it exists -if [ -f "/usr/local/CyberCP/public/phpmyadmin/.htaccess" ]; then - echo "Removing .htaccess file..." - rm /usr/local/CyberCP/public/phpmyadmin/.htaccess -fi - -# Set proper permissions -echo "Setting permissions..." -chown lscpd:lscpd /usr/local/CyberCP/public/phpmyadmin/index.php -chmod 644 /usr/local/CyberCP/public/phpmyadmin/index.php - -# Restart LiteSpeed to ensure changes take effect -echo "Restarting LiteSpeed..." -systemctl restart lscpd - -echo "=== Rollback Complete ===" -echo "" -echo "phpMyAdmin access control has been reverted!" -echo "phpMyAdmin should now work as it did before the changes." -echo "" -echo "Backup file used: $LATEST_BACKUP" diff --git a/simple_install.sh b/simple_install.sh new file mode 100644 index 000000000..3d3cdb5b8 --- /dev/null +++ b/simple_install.sh @@ -0,0 +1,114 @@ +#!/bin/sh + +# Simplified CyberPanel Installation Script +# Based on 2.4.4 approach with AlmaLinux 9 fixes + +OUTPUT=$(cat /etc/*release) + +# Detect OS and set appropriate variables +if echo $OUTPUT | grep -q "AlmaLinux 9" ; then + echo -e "\nDetecting AlmaLinux 9...\n" + SERVER_OS="AlmaLinux9" + PKG_MGR="dnf" +elif echo $OUTPUT | grep -q "AlmaLinux 8" ; then + echo -e "\nDetecting AlmaLinux 8...\n" + SERVER_OS="AlmaLinux8" + PKG_MGR="yum" +elif echo $OUTPUT | grep -q "Ubuntu 22.04" ; then + echo -e "\nDetecting Ubuntu 22.04...\n" + SERVER_OS="Ubuntu2204" + PKG_MGR="apt" +elif echo $OUTPUT | grep -q "Ubuntu 20.04" ; then + echo -e "\nDetecting Ubuntu 20.04...\n" + SERVER_OS="Ubuntu2004" + PKG_MGR="apt" +elif echo $OUTPUT | grep -q "CentOS Linux 8" ; then + echo -e "\nDetecting CentOS 8...\n" + SERVER_OS="CentOS8" + PKG_MGR="yum" +else + echo -e "\nUnsupported OS detected. This script supports:\n" + echo -e "AlmaLinux: 8, 9\n" + echo -e "Ubuntu: 20.04, 22.04\n" + echo -e "CentOS: 8\n" + exit 1 +fi + +echo "Installing basic dependencies..." + +# Install basic packages +if [ "$PKG_MGR" = "dnf" ]; then + dnf update -y + dnf install -y epel-release + dnf install -y wget curl unzip zip rsync firewalld git python3 python3-pip + dnf install -y mariadb-server mariadb-client + dnf install -y ImageMagick gd libicu oniguruma aspell libc-client +elif [ "$PKG_MGR" = "yum" ]; then + yum update -y + yum install -y epel-release + yum install -y wget curl unzip zip rsync firewalld git python3 python3-pip + yum install -y mariadb-server mariadb-client + yum install -y ImageMagick gd libicu oniguruma aspell libc-client +elif [ "$PKG_MGR" = "apt" ]; then + apt update -y + apt install -y wget curl unzip zip rsync git python3 python3-pip + apt install -y mariadb-server mariadb-client + apt install -y imagemagick php-gd php-intl php-mbstring php-pspell +fi + +# Start and enable MariaDB +echo "Starting MariaDB..." +systemctl enable mariadb +systemctl start mariadb + +# Create MySQL password file +echo "Setting up MySQL..." +mkdir -p /etc/cyberpanel +echo "cyberpanel123" > /etc/cyberpanel/mysqlPassword +chmod 600 /etc/cyberpanel/mysqlPassword + +# Secure MySQL installation +mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'cyberpanel123';" 2>/dev/null || true +mysql -u root -pcyberpanel123 -e "DELETE FROM mysql.user WHERE User='';" 2>/dev/null || true +mysql -u root -pcyberpanel123 -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');" 2>/dev/null || true +mysql -u root -pcyberpanel123 -e "DROP DATABASE IF EXISTS test;" 2>/dev/null || true +mysql -u root -pcyberpanel123 -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';" 2>/dev/null || true +mysql -u root -pcyberpanel123 -e "FLUSH PRIVILEGES;" 2>/dev/null || true + +# Configure firewall +echo "Configuring firewall..." +if [ "$PKG_MGR" = "dnf" ] || [ "$PKG_MGR" = "yum" ]; then + systemctl enable firewalld + systemctl start firewalld + firewall-cmd --permanent --add-port=8090/tcp + firewall-cmd --permanent --add-port=7080/tcp + firewall-cmd --permanent --add-port=80/tcp + firewall-cmd --permanent --add-port=443/tcp + firewall-cmd --permanent --add-port=21/tcp + firewall-cmd --permanent --add-port=25/tcp + firewall-cmd --permanent --add-port=587/tcp + firewall-cmd --permanent --add-port=465/tcp + firewall-cmd --permanent --add-port=110/tcp + firewall-cmd --permanent --add-port=143/tcp + firewall-cmd --permanent --add-port=993/tcp + firewall-cmd --permanent --add-port=995/tcp + firewall-cmd --permanent --add-port=53/tcp + firewall-cmd --permanent --add-port=53/udp + firewall-cmd --reload +fi + +# Download and install CyberPanel +echo "Downloading CyberPanel..." +rm -f cyberpanel.sh +curl --silent -o cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null + +if [ -f "cyberpanel.sh" ]; then + echo "Installing CyberPanel..." + chmod +x cyberpanel.sh + ./cyberpanel.sh +else + echo "Failed to download CyberPanel installer!" + exit 1 +fi + +echo "Installation completed!" diff --git a/to-do/MARIADB_INSTALLATION_FIXES.md b/to-do/MARIADB_INSTALLATION_FIXES.md new file mode 100644 index 000000000..f133868d3 --- /dev/null +++ b/to-do/MARIADB_INSTALLATION_FIXES.md @@ -0,0 +1,88 @@ +# MariaDB Installation Fixes + +## Issues Fixed + +### 1. MariaDB-server-compat Package Conflict +**Problem**: `MariaDB-server-compat-12.1.2-1.el9.noarch` was conflicting with MariaDB 10.11 installation, causing transaction test errors. + +**Solution**: +- Enhanced compat package removal with multiple aggressive removal attempts +- Added `--allowerasing` flag to dnf remove commands +- Added dnf exclude configuration to prevent compat package reinstallation +- Verification step to ensure all compat packages are removed before installation + +**Files Modified**: +- `cyberpanel-repo/plogical/upgrade.py` - `fix_almalinux9_mariadb()` function +- `cyberpanel-repo/install/install.py` - `installMySQL()` function + +### 2. MySQL Command Not Found Error +**Problem**: After MariaDB installation failed, the `changeMYSQLRootPassword()` function tried to use the `mysql` command which didn't exist, causing `FileNotFoundError`. + +**Solution**: +- Added verification that MariaDB binaries exist before attempting password change +- Added check for mysql/mariadb command availability +- Added MariaDB service status verification before password change +- Added wait time for MariaDB to be ready after service start + +**Files Modified**: +- `cyberpanel-repo/install/install.py` - `changeMYSQLRootPassword()` function +- `cyberpanel-repo/install/install.py` - `installMySQL()` function + +### 3. MariaDB Installation Verification +**Problem**: Installation was proceeding even when MariaDB wasn't actually installed successfully. + +**Solution**: +- Added binary existence check after installation +- Added service status verification +- Added proper error handling and return values +- Installation now fails gracefully if MariaDB wasn't installed + +**Files Modified**: +- `cyberpanel-repo/plogical/upgrade.py` - `fix_almalinux9_mariadb()` function +- `cyberpanel-repo/install/install.py` - `installMySQL()` function + +## Changes Made + +### upgrade.py +1. **Enhanced compat package removal**: + - Multiple removal attempts (dnf remove, rpm -e, individual package removal) + - Added `--allowerasing` flag + - Added dnf exclude configuration + - Verification step + +2. **Improved MariaDB installation**: + - Added `--exclude='MariaDB-server-compat*'` to dnf install command + - Added fallback with `--allowerasing` if conflicts occur + - Added binary existence verification after installation + - Proper error handling and return values + +### install.py +1. **Enhanced compat package removal** (same as upgrade.py) + +2. **Improved installation verification**: + - Check for MariaDB binaries after installation + - Verify service is running before password change + - Added wait time for service to be ready + - Proper error handling + +3. **Improved password change function**: + - Verify mysql/mariadb command exists before attempting password change + - Better error messages + - Graceful failure handling + +## Testing Recommendations + +1. Test on clean AlmaLinux 9 system +2. Test with existing MariaDB-server-compat package installed +3. Test with MariaDB 10.x already installed +4. Test with MariaDB 12.x already installed +5. Verify MariaDB service starts correctly +6. Verify mysql/mariadb commands are available +7. Verify password change succeeds + +## Notes + +- The fixes maintain backward compatibility +- All changes include proper error handling +- Installation now fails gracefully with clear error messages +- Compat package removal is more aggressive to handle edge cases diff --git a/to-do/OLD-REPO-CHECKLIST-BEFORE-REMOVAL.md b/to-do/OLD-REPO-CHECKLIST-BEFORE-REMOVAL.md new file mode 100644 index 000000000..39371c4fe --- /dev/null +++ b/to-do/OLD-REPO-CHECKLIST-BEFORE-REMOVAL.md @@ -0,0 +1,132 @@ +# What Was in the Old cyberpanel-fix Repo – Pre-Removal Checklist + +Before removing `/home/cyberpanel-fix-backup-20260202`, verify the merged repo has everything you need. + +--- + +## 1. Files ONLY in cyberpanel-repo (not in old fix) ✅ + +These are in the merged repo and were not in the old fix: + +| File | Purpose | +|------|---------| +| `commit_and_push.sh`, `commit_changes.py`, `push_fix.py`, `push_fix.sh` | Dev/utility scripts | +| `fix_todo_git.py`, `remove_todo.py`, `remove_todo_from_git.sh` | Git helpers | +| `olves issue -1654: Hostname SSL setup...` | Patch file (typo in filename) | +| `pluginHolder/patreon_verifier.py.bak`, `plugin_access.py.bak` | Backups | +| `pluginHolder/templates/pluginHolder/plugins.html.backup` | Template backup | +| `static/userManagment/modifyUser.html` | UI change | +| `to-do/PLUGIN-DEFAULT-REMOVAL-2026-02-01.md` | Notes | +| `to-do/REPO-MERGE-2026-02-02.md` | Merge notes | + +**Action:** None. These are already in the merged repo. + +--- + +## 2. Files COPIED from old fix into repo ✅ + +These were only in the old fix and were copied into repo during the merge: + +| File | Purpose | +|------|---------| +| `cyberpanel_clean.sh` | Clean install script | +| `cyberpanel_complete.sh` | Complete install script | +| `cyberpanel_simple.sh` | Simple install script | +| `cyberpanel_standalone.sh` | Standalone install script | +| `fix_installation_issues.sh` | Installation fixes | +| `install_phpmyadmin.sh` | phpMyAdmin installer | +| `simple_install.sh` | Simple installer | +| `INSTALLER_SUMMARY.md` | Installer docs | +| `UNIVERSAL_OS_COMPATIBILITY.md` | OS compatibility docs | +| `to-do/MARIADB_INSTALLATION_FIXES.md` | MariaDB fixes | + +**Action:** Confirm these exist in `/home/cyberpanel-repo/`. + +--- + +## 3. Files that DIFFER – repo is the intended version + +The merged repo keeps the **cyberpanel-repo** versions. Old fix had older or different logic. + +### CyberCP/settings.py +- **Repo:** `emailMarketing` is commented out (install via Plugin Store) +- **Old fix:** `emailMarketing` was in `INSTALLED_APPS` + +**Check:** Plugin Store for emailMarketing works; no need for it in core install. + +### CyberCP/urls.py +- **Repo:** `path('emailMarketing/', ...)` is commented out +- **Old fix:** `path('emailMarketing/', ...)` was active + +**Check:** Same as above; emailMarketing via Plugin Store. + +### plogical/mailUtilities.py +- **Repo:** DNS fallback logic – falls back to **local DNS** when external API fails +- **Old fix:** Returns empty `[]` when external API fails; no local fallback + +**Check:** Hostname SSL / rDNS works when cyberpanel.net API is down or unreachable. + +### emailMarketing/meta.xml +- **Repo:** version `1.0.1`, category `Email` +- **Old fix:** version `1.0.0` + +### examplePlugin/meta.xml +- **Repo:** version `1.0.1`, category `Utility` +- **Old fix:** version `1.0.0` + +**Check:** Plugin Store shows correct versions and categories. + +--- + +## 4. PluginHolder / Plugin Store (in repo) + +The merged repo has: + +- Collapsible help sections +- Freshness badges (NEW/Stable/Unstable/STALE) +- Activate All / Deactivate All +- Updated categories and premium docs +- Version 2.1.0 in the help footer + +**Check:** `/plugins/help/` and `/plugins/installed` behave as expected. + +--- + +## 5. Quick verification commands + +```bash +# Copied files exist +ls -la /home/cyberpanel-repo/cyberpanel_clean.sh \ + /home/cyberpanel-repo/fix_installation_issues.sh \ + /home/cyberpanel-repo/install_phpmyadmin.sh + +# Symlink works +ls -la /home/cyberpanel-fix +# Should show: cyberpanel-fix -> cyberpanel-repo + +# Live deployment +ls -la /usr/local/CyberCP/pluginHolder/templates/pluginHolder/help.html +# Should have collapsible sections and version 2.1.0 +``` + +--- + +## 6. Safe to remove when + +- [ ] Plugin Store loads and filters work +- [ ] Plugin Development Guide (help) shows collapsible sections and 2.1.0 +- [ ] Hostname SSL / rDNS works (or you accept no local DNS fallback) +- [ ] emailMarketing is installed via Plugin Store, not core (if used) +- [ ] Install scripts (`cyberpanel_clean.sh`, etc.) are present and used as needed + +--- + +## Remove backup + +```bash +rm -rf /home/cyberpanel-fix-backup-20260202 +``` + +--- + +**Created:** 2026-02-02 diff --git a/to-do/PLUGIN-DEFAULT-REMOVAL-2026-02-01.md b/to-do/PLUGIN-DEFAULT-REMOVAL-2026-02-01.md new file mode 100644 index 000000000..302845ded --- /dev/null +++ b/to-do/PLUGIN-DEFAULT-REMOVAL-2026-02-01.md @@ -0,0 +1,13 @@ +# Plugin Default Removal - 2026-02-01 + +## Summary +CyberPanel repository no longer requires any plugins by default. Plugins are installed by users from the [Plugin Store](https://github.com/master3395/cyberpanel-plugins) via the CyberPanel Plugin Manager. + +## Changes +- **settings.py**: Removed `emailMarketing` from `INSTALLED_APPS` +- **urls.py**: Commented out `emailMarketing` route (plugin installer adds it when plugin is installed) + +## Plugin Installation +Users install plugins from: https://github.com/master3395/cyberpanel-plugins + +The plugin installer adds apps to `INSTALLED_APPS` and URL routes when plugins are installed via the Plugin Store UI. diff --git a/to-do/REPO-MERGE-2026-02-02.md b/to-do/REPO-MERGE-2026-02-02.md new file mode 100644 index 000000000..79b96232e --- /dev/null +++ b/to-do/REPO-MERGE-2026-02-02.md @@ -0,0 +1,38 @@ +# CyberPanel Repo Merge – 2026-02-02 + +## Summary + +`cyberpanel-repo` and `cyberpanel-fix` have been merged into a single working directory. + +## What Was Done + +1. **Unique files copied from cyberpanel-fix into cyberpanel-repo:** + - `cyberpanel_clean.sh` + - `cyberpanel_complete.sh` + - `cyberpanel_simple.sh` + - `cyberpanel_standalone.sh` + - `fix_installation_issues.sh` + - `install_phpmyadmin.sh` + - `simple_install.sh` + - `INSTALLER_SUMMARY.md` + - `UNIVERSAL_OS_COMPATIBILITY.md` + - `to-do/MARIADB_INSTALLATION_FIXES.md` + +2. **cyberpanel-fix backup:** Renamed to `cyberpanel-fix-backup-20260202` + +3. **Symlink created:** `cyberpanel-fix` → `cyberpanel-repo` + - Paths like `/home/cyberpanel-fix/` now resolve to `/home/cyberpanel-repo/` + +## Single Source of Truth + +Use **`/home/cyberpanel-repo`** (or `/home/cyberpanel-fix` via symlink) for all CyberPanel development and deployment. + +## Backup Location + +The previous cyberpanel-fix tree is preserved at: +`/home/cyberpanel-fix-backup-20260202` + +You can remove it after confirming everything works: +```bash +rm -rf /home/cyberpanel-fix-backup-20260202 +```