diff --git a/install.sh b/install.sh
index 18088684d..c487174e9 100644
--- a/install.sh
+++ b/install.sh
@@ -8,6 +8,62 @@ set -e
# Get script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Early logging function (before main functions are defined)
+early_log() {
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
+}
+
+# Check if script is being executed via curl/wget (SCRIPT_DIR will be /dev/fd/...)
+# In that case, we need to clone the repository first
+if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then
+ # Script is being executed via curl/wget, need to clone repo first
+ early_log "๐ฅ Detected curl/wget execution - cloning repository..."
+
+ # Determine branch from arguments or use default
+ BRANCH_NAME="v2.5.5-dev"
+ ARGS=("$@")
+ for i in "${!ARGS[@]}"; do
+ if [[ "${ARGS[i]}" == "-b" ]] || [[ "${ARGS[i]}" == "--branch" ]]; then
+ if [[ -n "${ARGS[i+1]}" ]]; then
+ BRANCH_NAME="${ARGS[i+1]}"
+ fi
+ break
+ fi
+ done
+
+ # Clone repository to temporary directory
+ TEMP_DIR="/tmp/cyberpanel-installer-$$"
+ mkdir -p "$TEMP_DIR"
+
+ early_log "๐ฆ Cloning CyberPanel repository (branch: $BRANCH_NAME)..."
+ if git clone --depth 1 --branch "$BRANCH_NAME" https://github.com/master3395/cyberpanel.git "$TEMP_DIR/cyberpanel" 2>/dev/null; then
+ SCRIPT_DIR="$TEMP_DIR/cyberpanel"
+ cd "$SCRIPT_DIR"
+ early_log "โ
Repository cloned successfully"
+ else
+ # Fallback: try to download install.sh from the old method
+ early_log "โ ๏ธ Git clone failed, falling back to legacy installer..."
+ OUTPUT=$(cat /etc/*release 2>/dev/null || echo "")
+ if echo "$OUTPUT" | grep -qE "(CentOS|AlmaLinux|CloudLinux|Rocky)" ; then
+ SERVER_OS="CentOS8"
+ yum install -y -q curl wget 2>/dev/null || true
+ elif echo "$OUTPUT" | grep -qE "Ubuntu" ; then
+ SERVER_OS="Ubuntu"
+ apt install -y -qq wget curl 2>/dev/null || true
+ else
+ early_log "โ Unable to detect OS for fallback installer"
+ exit 1
+ fi
+
+ rm -f /tmp/cyberpanel.sh
+ curl --silent -o /tmp/cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null || \
+ wget -q -O /tmp/cyberpanel.sh "https://cyberpanel.sh/?dl&$SERVER_OS" 2>/dev/null
+ chmod +x /tmp/cyberpanel.sh
+ exec /tmp/cyberpanel.sh "$@"
+ fi
+fi
+
MODULES_DIR="$SCRIPT_DIR/modules"
# Colors for output
diff --git a/install/install.py b/install/install.py
index 1402a8bb9..e2ab05e25 100644
--- a/install/install.py
+++ b/install/install.py
@@ -1451,9 +1451,189 @@ module cyberpanel_ols {
except:
return False
+ def checkExistingMariaDB(self):
+ """Check if MariaDB/MySQL is already installed and return version info"""
+ try:
+ # Check if MariaDB/MySQL server package is installed
+ if self.distro == ubuntu:
+ command = 'dpkg -l | grep -iE "^(ii|rc).*mariadb-server|mysql-server" 2>/dev/null || echo ""'
+ else:
+ command = 'rpm -qa | grep -iE "^(mariadb-server|mysql-server|MariaDB-server)" 2>/dev/null || echo ""'
+
+ result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True)
+
+ if result.stdout.strip():
+ # MariaDB/MySQL server package is installed, get version
+ version_command = 'mysql --version 2>/dev/null || mariadb --version 2>/dev/null || echo ""'
+ version_result = subprocess.run(version_command, shell=True, capture_output=True, universal_newlines=True)
+ version_output = version_result.stdout.strip()
+
+ if version_output:
+ # Extract version number (e.g., "10.11.15" from "mysql Ver 10.11.15-MariaDB")
+ import re
+ version_match = re.search(r'(\d+\.\d+\.\d+)', version_output)
+ if version_match:
+ installed_version = version_match.group(1)
+ major_minor = '.'.join(installed_version.split('.')[:2]) # e.g., "10.11"
+ logging.InstallLog.writeToFile(f"Found existing MariaDB installation: {installed_version} (major.minor: {major_minor})")
+ return True, installed_version, major_minor
+
+ logging.InstallLog.writeToFile("Found MariaDB/MySQL package but could not determine version")
+ return True, "unknown", "unknown"
+
+ # Also check if MariaDB service exists and data directory exists (might be installed but package query failed)
+ if os.path.exists('/var/lib/mysql') and os.listdir('/var/lib/mysql'):
+ logging.InstallLog.writeToFile("Found MariaDB data directory, assuming MariaDB is installed")
+ # Try to get version one more time
+ version_command = 'mysql --version 2>/dev/null || mariadb --version 2>/dev/null || echo ""'
+ version_result = subprocess.run(version_command, shell=True, capture_output=True, universal_newlines=True)
+ version_output = version_result.stdout.strip()
+ if version_output:
+ import re
+ version_match = re.search(r'(\d+\.\d+\.\d+)', version_output)
+ if version_match:
+ installed_version = version_match.group(1)
+ major_minor = '.'.join(installed_version.split('.')[:2])
+ return True, installed_version, major_minor
+ return True, "unknown", "unknown"
+
+ return False, None, None
+ except Exception as e:
+ logging.InstallLog.writeToFile(f"Error checking existing MariaDB: {str(e)}")
+ return False, None, None
+
+ def _attemptMariaDBUpgrade(self):
+ """Attempt to upgrade MariaDB to 12.1. Returns True if successful, False otherwise."""
+ try:
+ if self.distro == ubuntu:
+ # Ubuntu MariaDB upgrade
+ command = 'DEBIAN_FRONTEND=noninteractive apt-get install software-properties-common apt-transport-https curl -y'
+ result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True)
+ if result.returncode != 0:
+ logging.InstallLog.writeToFile(f"Failed to install prerequisites: {result.stderr}")
+ return False
+
+ command = "mkdir -p /etc/apt/keyrings"
+ subprocess.run(command, shell=True, check=False)
+
+ command = "curl -o /etc/apt/keyrings/mariadb-keyring.pgp 'https://mariadb.org/mariadb_release_signing_key.pgp'"
+ result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True)
+ if result.returncode != 0:
+ logging.InstallLog.writeToFile(f"Failed to download MariaDB keyring: {result.stderr}")
+ return False
+
+ # Setup MariaDB 12.1 repository
+ command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=12.1'
+ result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True)
+ if result.returncode != 0:
+ logging.InstallLog.writeToFile(f"Failed to setup MariaDB repository: {result.stderr}")
+ return False
+
+ command = 'DEBIAN_FRONTEND=noninteractive apt-get update -y'
+ result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True)
+ if result.returncode != 0:
+ logging.InstallLog.writeToFile(f"Failed to update package list: {result.stderr}")
+ return False
+
+ # Attempt to install MariaDB 12.1
+ command = "DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server -y"
+ result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True)
+ if result.returncode != 0:
+ # Check if error is due to upgrade restrictions
+ error_output = result.stderr + result.stdout
+ if "upgrade" in error_output.lower() or "manual" in error_output.lower():
+ logging.InstallLog.writeToFile("MariaDB upgrade blocked - requires manual intervention")
+ else:
+ logging.InstallLog.writeToFile(f"MariaDB installation failed: {error_output}")
+ return False
+
+ return True
+ else:
+ # RHEL-based MariaDB upgrade
+ # Setup MariaDB 12.1 repository
+ command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=12.1'
+ result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True)
+ if result.returncode != 0:
+ logging.InstallLog.writeToFile(f"Failed to setup MariaDB repository: {result.stderr}")
+ return False
+
+ # Attempt to install MariaDB 12.1
+ # Use --allowerasing to allow package replacements if needed
+ command = 'dnf install mariadb-server mariadb-devel mariadb-client-utils -y --allowerasing'
+ result = subprocess.run(command, shell=True, capture_output=True, universal_newlines=True)
+ if result.returncode != 0:
+ # Check if error is due to upgrade restrictions
+ error_output = result.stderr + result.stdout
+ if "PREIN scriptlet failed" in error_output or "upgrade" in error_output.lower() or "manual" in error_output.lower():
+ logging.InstallLog.writeToFile("MariaDB upgrade blocked by package manager - requires manual intervention")
+ else:
+ logging.InstallLog.writeToFile(f"MariaDB installation failed: {error_output}")
+ return False
+
+ return True
+
+ except Exception as e:
+ logging.InstallLog.writeToFile(f"Exception during MariaDB upgrade attempt: {str(e)}")
+ return False
+
def installMySQL(self, mysql):
"""Install MySQL/MariaDB"""
try:
+ # Check if MariaDB is already installed
+ is_installed, installed_version, major_minor = self.checkExistingMariaDB()
+
+ if is_installed:
+ self.stdOut(f"MariaDB/MySQL is already installed (version: {installed_version})", 1)
+
+ # Check if we need to upgrade
+ should_try_upgrade = False
+ if major_minor and major_minor != "unknown":
+ try:
+ major_ver = float(major_minor)
+ if major_ver < 12.0:
+ should_try_upgrade = True
+ self.stdOut(f"Existing MariaDB {major_minor} detected. Attempting to upgrade to MariaDB 12.1...", 1)
+ self.stdOut("If upgrade fails, we will use the existing MariaDB installation.", 1)
+ except (ValueError, TypeError):
+ pass
+
+ # If MariaDB 10.x is installed, try to upgrade to 12.1 first
+ if should_try_upgrade:
+ try:
+ self.stdOut("Attempting to install MariaDB 12.1...", 1)
+ upgrade_success = self._attemptMariaDBUpgrade()
+ if upgrade_success:
+ self.stdOut("โ
Successfully upgraded to MariaDB 12.1", 1)
+ self.startMariaDB()
+ self.changeMYSQLRootPassword()
+ self.fixMariaDB()
+ return True
+ else:
+ self.stdOut("โ ๏ธ MariaDB 12.1 upgrade failed, using existing MariaDB installation", 1)
+ self.startMariaDB()
+ return True
+ except Exception as upgrade_error:
+ error_msg = str(upgrade_error)
+ logging.InstallLog.writeToFile(f"MariaDB upgrade attempt failed: {error_msg}")
+
+ # Check if error is due to upgrade restrictions
+ if "PREIN scriptlet failed" in error_msg or "upgrade" in error_msg.lower() or "manual" in error_msg.lower():
+ self.stdOut("โ ๏ธ MariaDB upgrade blocked by package manager (10.x to 12.x requires manual upgrade)", 1)
+ self.stdOut(f"Using existing MariaDB {installed_version} installation", 1)
+ else:
+ self.stdOut(f"โ ๏ธ MariaDB upgrade failed: {error_msg}", 1)
+ self.stdOut(f"Using existing MariaDB {installed_version} installation", 1)
+
+ # Fall back to existing installation
+ self.startMariaDB()
+ return True
+
+ # MariaDB 12.x or higher already installed, or version unknown but working
+ # Just ensure it's running
+ self.stdOut("Using existing MariaDB installation", 1)
+ self.startMariaDB()
+ return True
+
self.stdOut("Installing MySQL/MariaDB...", 1)
if self.distro == ubuntu:
diff --git a/to-do/PAID-PLUGINS-GUIDE.md b/to-do/PAID-PLUGINS-GUIDE.md
new file mode 100644
index 000000000..146ee81f1
--- /dev/null
+++ b/to-do/PAID-PLUGINS-GUIDE.md
@@ -0,0 +1,214 @@
+# Paid Plugins Support for CyberPanel
+
+## Overview
+
+CyberPanel now supports paid plugins that require Patreon subscription. Users can install paid plugins, but they cannot run them without an active Patreon subscription to the specified tier.
+
+## Features
+
+- โ
Paid plugin detection from `meta.xml`
+- โ
Patreon subscription verification
+- โ
Installable but non-functional without subscription
+- โ
Visual indicators (badges) for paid plugins in all views:
+ - Grid View: Green "Free" or Yellow "Paid" badge next to version
+ - Table View: Green "Free" or Yellow "Paid" badge next to version
+ - Store View: Separate "Pricing" column with Free/Paid badges
+- โ
Subscription required page when accessing without subscription
+- โ
"Subscribe on Patreon" button in subscription warning
+- โ
API endpoint for checking subscription status
+
+## Plugin Structure
+
+### Meta.xml for Paid Plugins
+
+Add the following fields to your plugin's `meta.xml`:
+
+```xml
+true
+CyberPanel Paid Plugin
+https://www.patreon.com/membership/27789984
+```
+
+### Example meta.xml
+
+```xml
+
+
+ Premium Plugin Example
+ Utility
+ 1.0.0
+ An example paid plugin
+ master3395
+ true
+ CyberPanel Paid Plugin
+ https://www.patreon.com/membership/27789984
+ /plugins/premiumPlugin/
+ /plugins/premiumPlugin/settings/
+
+```
+
+## Implementation in Plugin Views
+
+### Using the Premium Plugin Decorator
+
+```python
+from pluginHolder.plugin_access import check_plugin_access
+
+def premium_plugin_required(view_func):
+ """
+ Decorator that checks if user has Patreon subscription
+ """
+ @wraps(view_func)
+ def _wrapped_view(request, *args, **kwargs):
+ # Check login first
+ try:
+ userID = request.session['userID']
+ except KeyError:
+ from loginSystem.views import loadLoginPage
+ return redirect(loadLoginPage)
+
+ # Check plugin access
+ plugin_meta = {
+ 'is_paid': True,
+ 'patreon_tier': 'CyberPanel Paid Plugin',
+ 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership'
+ }
+
+ access_check = check_plugin_access(request, 'yourPluginName', plugin_meta)
+
+ if not access_check['has_access']:
+ # Show subscription required page
+ context = {
+ 'plugin_name': 'Your Plugin Name',
+ 'is_paid': True,
+ 'patreon_tier': access_check.get('patreon_tier', 'CyberPanel Paid Plugin'),
+ 'patreon_url': access_check.get('patreon_url'),
+ 'message': access_check.get('message', 'Patreon subscription required')
+ }
+ proc = httpProc(request, 'yourPlugin/subscription_required.html', context, 'admin')
+ return proc.render()
+
+ # User has access - proceed
+ return view_func(request, *args, **kwargs)
+
+ return _wrapped_view
+
+@cyberpanel_login_required
+@premium_plugin_required
+def your_view(request):
+ # Your view code here
+ pass
+```
+
+## Patreon Configuration
+
+### Environment Variables
+
+Set the following environment variables in CyberPanel:
+
+```bash
+export PATREON_CLIENT_ID="your_client_id"
+export PATREON_CLIENT_SECRET="your_client_secret"
+export PATREON_CREATOR_ID="your_creator_id"
+```
+
+Or add them to `/usr/local/CyberCP/CyberCP/settings.py`:
+
+```python
+import os
+
+PATREON_CLIENT_ID = os.environ.get('PATREON_CLIENT_ID', '')
+PATREON_CLIENT_SECRET = os.environ.get('PATREON_CLIENT_SECRET', '')
+PATREON_CREATOR_ID = os.environ.get('PATREON_CREATOR_ID', '')
+```
+
+### User Token Storage
+
+Users need to authorize CyberPanel to check their Patreon membership. Tokens are stored in:
+
+```
+/home/cyberpanel/patreon_tokens/{user_email}.token
+```
+
+## API Endpoints
+
+### Check Subscription Status
+
+```
+GET /plugins/api/check-subscription//
+```
+
+Response:
+```json
+{
+ "success": true,
+ "has_access": true,
+ "is_paid": true,
+ "message": "Access granted",
+ "patreon_url": null
+}
+```
+
+## Example Plugin
+
+A complete example paid plugin is available at:
+
+```
+/home/cyberpanel-plugins/premiumPlugin/
+```
+
+This plugin demonstrates:
+- Paid plugin meta.xml structure
+- Subscription verification
+- Subscription required page
+- Protected views
+
+## User Experience
+
+### For Users Without Subscription
+
+1. Plugin appears in installed plugins list with "Premium" badge and "Paid" pricing badge
+2. Plugin appears in CyberPanel Plugin Store with "Paid" badge in the Pricing column
+3. Plugin can be installed
+4. When accessing plugin, subscription required page is shown
+5. Link to Patreon subscription page is provided with "Subscribe on Patreon" button
+
+### For Users With Subscription
+
+1. Plugin works normally
+2. All features are accessible
+3. Settings page is available
+4. No restrictions
+
+## Files Created/Modified
+
+### New Files
+
+- `/home/cyberpanel-repo/pluginHolder/patreon_verifier.py` - Patreon API integration
+- `/home/cyberpanel-repo/pluginHolder/plugin_access.py` - Plugin access control
+- `/home/cyberpanel-plugins/premiumPlugin/` - Example paid plugin
+
+### Modified Files
+
+- `/home/cyberpanel-repo/pluginHolder/views.py` - Added paid plugin parsing and subscription check API
+- `/home/cyberpanel-repo/pluginHolder/templates/pluginHolder/plugins.html` - Added paid plugin badges and warnings
+- `/home/cyberpanel-repo/pluginHolder/urls.py` - Added subscription check endpoint
+
+## Testing
+
+1. Install the example premium plugin
+2. Try accessing it without subscription (should show subscription required page)
+3. Subscribe to Patreon tier "CyberPanel Paid Plugin"
+4. Authorize CyberPanel to check membership
+5. Access plugin again (should work normally)
+
+## Notes
+
+- Subscription checks are cached for 5 minutes to reduce API calls
+- Users must authorize CyberPanel to check their Patreon membership
+- The system checks for the exact tier name specified in `patreon_tier`
+- Free plugins work normally without any changes
+
+## Author
+
+master3395
diff --git a/to-do/PATREON-CONFIGURATION.md b/to-do/PATREON-CONFIGURATION.md
new file mode 100644
index 000000000..c29697ce5
--- /dev/null
+++ b/to-do/PATREON-CONFIGURATION.md
@@ -0,0 +1,58 @@
+# Patreon Configuration for CyberPanel Paid Plugins
+
+## Configuration Complete
+
+Patreon credentials have been configured in CyberPanel settings.
+
+### Credentials Configuration
+
+**SECURITY WARNING**: Never commit secrets to the repository!
+
+Set these via environment variables:
+
+```bash
+export PATREON_CLIENT_ID="your_client_id_here"
+export PATREON_CLIENT_SECRET="your_client_secret_here"
+export PATREON_MEMBERSHIP_TIER_ID="your_tier_id_here"
+export PATREON_CREATOR_ACCESS_TOKEN="your_access_token_here"
+export PATREON_CREATOR_REFRESH_TOKEN="your_refresh_token_here"
+```
+
+Or add to `/etc/environment` or systemd service file.
+
+### Location
+
+Credentials are stored in:
+- `/usr/local/CyberCP/CyberCP/settings.py` (or `/home/cyberpanel-repo/CyberCP/settings.py`)
+
+### How It Works
+
+1. Users install paid plugins (no subscription required)
+2. When accessing the plugin, system checks for Patreon membership
+3. If user has active subscription to tier `27789984`, access is granted
+4. If not, subscription required page is shown
+
+### Testing
+
+To test the configuration:
+
+1. Install the `premiumPlugin` example plugin
+2. Try accessing it without subscription (should show subscription page)
+3. Subscribe to Patreon tier "CyberPanel Paid Plugin" (ID: 27789984)
+4. Authorize CyberPanel to check membership
+5. Access plugin again (should work)
+
+### Membership Verification
+
+The system checks for:
+- Tier ID: `27789984`
+- Tier Name: "CyberPanel Paid Plugin" (fallback)
+- Active patron status
+- Currently entitled amount > 0
+
+### Notes
+
+- Membership checks are cached for 5 minutes
+- Users must authorize CyberPanel via OAuth to check membership
+- Creator access token can be used for server-side verification
+
diff --git a/to-do/PATREON-SETUP-SECURE.md b/to-do/PATREON-SETUP-SECURE.md
new file mode 100644
index 000000000..0779e3601
--- /dev/null
+++ b/to-do/PATREON-SETUP-SECURE.md
@@ -0,0 +1,115 @@
+# Secure Patreon Configuration Setup
+
+## โ ๏ธ SECURITY WARNING
+
+**NEVER commit Patreon secrets to the repository!**
+
+All secrets must be configured via environment variables on the production server.
+
+## Setup Instructions
+
+### 1. Get Your Patreon Credentials
+
+From your Patreon Developer Dashboard:
+- Client ID
+- Client Secret
+- Membership Tier ID (e.g., `27789984`)
+- Creator Access Token (optional, for server-side verification)
+- Creator Refresh Token (optional)
+
+### 2. Configure Environment Variables
+
+#### Option A: Systemd Service (Recommended)
+
+Edit `/etc/systemd/system/lscpd.service`:
+
+```ini
+[Service]
+Environment="PATREON_CLIENT_ID=your_client_id"
+Environment="PATREON_CLIENT_SECRET=your_client_secret"
+Environment="PATREON_MEMBERSHIP_TIER_ID=your_tier_id"
+Environment="PATREON_CREATOR_ACCESS_TOKEN=your_access_token"
+Environment="PATREON_CREATOR_REFRESH_TOKEN=your_refresh_token"
+```
+
+Then reload and restart:
+```bash
+systemctl daemon-reload
+systemctl restart lscpd
+```
+
+#### Option B: /etc/environment
+
+Add to `/etc/environment`:
+```bash
+PATREON_CLIENT_ID=your_client_id
+PATREON_CLIENT_SECRET=your_client_secret
+PATREON_MEMBERSHIP_TIER_ID=your_tier_id
+PATREON_CREATOR_ACCESS_TOKEN=your_access_token
+PATREON_CREATOR_REFRESH_TOKEN=your_refresh_token
+```
+
+#### Option C: Secure Config File (Not in Repo)
+
+Create `/usr/local/CyberCP/patreon_config.py` (add to .gitignore):
+
+```python
+# Patreon Configuration - DO NOT COMMIT TO REPOSITORY
+PATREON_CLIENT_ID = 'your_client_id'
+PATREON_CLIENT_SECRET = 'your_client_secret'
+PATREON_MEMBERSHIP_TIER_ID = 'your_tier_id'
+PATREON_CREATOR_ACCESS_TOKEN = 'your_access_token'
+PATREON_CREATOR_REFRESH_TOKEN = 'your_refresh_token'
+```
+
+Then import in settings.py:
+```python
+try:
+ from .patreon_config import *
+except ImportError:
+ pass # Use environment variables instead
+```
+
+### 3. Verify Configuration
+
+Test that secrets are loaded:
+```bash
+python3 -c "
+import os
+print('Client ID:', 'SET' if os.environ.get('PATREON_CLIENT_ID') else 'NOT SET')
+print('Client Secret:', 'SET' if os.environ.get('PATREON_CLIENT_SECRET') else 'NOT SET')
+print('Tier ID:', os.environ.get('PATREON_MEMBERSHIP_TIER_ID', 'NOT SET'))
+"
+```
+
+### 4. Security Checklist
+
+- [ ] Secrets removed from repository
+- [ ] Environment variables set on production server
+- [ ] `/usr/local/CyberCP/patreon_config.py` added to .gitignore (if used)
+- [ ] CyberPanel service restarted
+- [ ] Configuration verified working
+
+## For Plugin Developers
+
+When creating paid plugins:
+
+1. **Never hardcode secrets** in plugin code
+2. **Use environment variables** or Django settings
+3. **Document required variables** in README
+4. **Provide example** with placeholder values only
+
+Example meta.xml:
+```xml
+true
+Your Tier Name
+https://www.patreon.com/c/yourname/membership
+```
+
+## Troubleshooting
+
+If membership checks fail:
+1. Verify environment variables are set: `env | grep PATREON`
+2. Check CyberPanel logs: `/home/lscp/logs/error.log`
+3. Verify tier ID matches your Patreon tier
+4. Ensure user has authorized OAuth access
diff --git a/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md b/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md
new file mode 100644
index 000000000..053149677
--- /dev/null
+++ b/to-do/REMOTE-VERIFICATION-ARCHITECTURE.md
@@ -0,0 +1,89 @@
+# Remote Verification Architecture for Paid Plugins
+
+## Problem
+
+When users install plugins, they can see all files on their system, which could expose:
+- Patreon API credentials
+- Verification logic
+- Access tokens
+
+## Solution: Remote Verification Server
+
+Move all Patreon verification to **YOUR server** (not the user's server).
+
+### Architecture
+
+```
+User's Server (Plugin) Your Server Patreon API
+ | | |
+ |-- Verify Request ----------->| |
+ | |-- Check Membership --->|
+ | |<-- Membership Status --|
+ |<-- Access Granted/Denied ----| |
+```
+
+### Benefits
+
+1. **No secrets on user's server** - All credentials stay on your server
+2. **Users can't intercept** - Verification happens server-to-server
+3. **Centralized control** - You can revoke access, update logic, etc.
+4. **Plugin code can be public** - Only makes API calls, no secrets
+
+## Implementation
+
+### 1. Your Verification Server
+
+Create an API endpoint on your server (e.g., `api.newstargeted.com`):
+
+```python
+# Your server endpoint: /api/verify-patreon-membership
+POST /api/verify-patreon-membership
+{
+ "user_email": "user@example.com",
+ "plugin_name": "premiumPlugin",
+ "tier_id": "27789984"
+}
+
+Response:
+{
+ "has_access": true,
+ "expires_at": "2026-02-25T00:00:00Z"
+}
+```
+
+### 2. Plugin Code (Public, No Secrets)
+
+The plugin only makes HTTP requests to your server:
+
+```python
+def check_remote_membership(user_email, plugin_name):
+ response = requests.post(
+ 'https://api.newstargeted.com/api/verify-patreon-membership',
+ json={
+ 'user_email': user_email,
+ 'plugin_name': plugin_name,
+ 'tier_id': '27789984'
+ },
+ headers={'X-Plugin-Version': '1.0.0'}
+ )
+ return response.json()
+```
+
+### 3. Security Measures
+
+- **Rate limiting** - Prevent abuse
+- **IP whitelisting** - Only allow from CyberPanel servers (optional)
+- **Plugin signature** - Verify requests come from legitimate plugins
+- **Caching** - Reduce API calls to Patreon
+- **HTTPS only** - Encrypt all communication
+
+## Alternative: Encrypted Plugin Package
+
+If you want to encrypt the entire plugin:
+
+1. **Encrypt plugin files** before distribution
+2. **Decrypt on install** using a license key
+3. **License key** tied to user's Patreon subscription
+4. **Your server** generates license keys
+
+This is more complex but provides stronger protection.
diff --git a/to-do/REMOTE-VERIFICATION-COMPLETE.md b/to-do/REMOTE-VERIFICATION-COMPLETE.md
new file mode 100644
index 000000000..2012db765
--- /dev/null
+++ b/to-do/REMOTE-VERIFICATION-COMPLETE.md
@@ -0,0 +1,115 @@
+# Remote Verification Setup Complete โ
+
+## Summary
+
+All components are now set up for secure remote verification of paid plugins. Users can install plugins and see all code, but **NO secrets are exposed** because all Patreon API calls happen on YOUR server.
+
+## โ
What Was Completed
+
+### 1. Remote Verification API
+- **Endpoint**: `https://api.newstargeted.com/api/verify-patreon-membership`
+- **Location**: `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php`
+- **Status**: โ
Working and tested
+- **Permissions**: โ
newst3922:newst3922 (644)
+
+### 2. Plugin Updated
+- **File**: `/home/cyberpanel-plugins/premiumPlugin/views.py`
+- **Method**: Remote verification (no secrets)
+- **Status**: โ
Updated and tested
+- **Permissions**: โ
newst3922:newst3922 (644)
+
+### 3. Configuration
+- **Patreon credentials**: Added to `/home/newstargeted.com/api.newstargeted.com/config.php`
+- **Permissions**: โ
600 (secure, readable only by owner)
+- **Owner**: โ
newst3922:newst3922
+
+### 4. Routing
+- **.htaccess**: Updated with API routing rules
+- **API Router**: Created at `/api/index.php`
+- **Status**: โ
Working
+
+## ๐ Security Features
+
+โ
**No secrets in plugin** - All code is public-safe
+โ
**Credentials on your server only** - Never exposed to users
+โ
**Rate limiting** - 60 requests/hour per IP
+โ
**Caching** - 5 minute cache to reduce API calls
+โ
**HTTPS only** - All communication encrypted
+โ
**Proper permissions** - Config files protected (600)
+
+## ๐ File Permissions Summary
+
+### API Files
+- `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php`: 644, newst3922:newst3922
+- `/home/newstargeted.com/api.newstargeted.com/api/index.php`: 644, newst3922:newst3922
+- `/home/newstargeted.com/api.newstargeted.com/config.php`: 600, newst3922:newst3922 (secure)
+- `/home/newstargeted.com/api.newstargeted.com/.htaccess`: 644, newst3922:newst3922
+
+### Plugin Files
+- `/home/cyberpanel-plugins/premiumPlugin/`: All files 644, directories 755, newst3922:newst3922
+- `/home/cyberpanel/plugins/premiumPlugin/`: All files 644, directories 755, newst3922:newst3922
+
+## ๐งช Testing
+
+### API Endpoint Test
+```bash
+curl -X POST https://api.newstargeted.com/api/verify-patreon-membership \
+ -H "Content-Type: application/json" \
+ -d '{"user_email":"test@example.com","plugin_name":"premiumPlugin","tier_id":"27789984"}'
+```
+
+**Result**: โ
Returns proper JSON response
+
+### Plugin Test
+1. Install plugin from CyberPanel
+2. Try accessing plugin
+3. Should show subscription required page (if not subscribed)
+4. Plugin makes API call to your server (no secrets exposed)
+
+## ๐ Next Steps
+
+1. **Implement OAuth Flow** (optional but recommended)
+ - Users authorize CyberPanel via Patreon OAuth
+ - Store access tokens securely (database recommended)
+ - Link tokens to user emails
+
+2. **Test Full Flow**
+ - Subscribe to Patreon tier
+ - Authorize CyberPanel
+ - Access plugin (should work)
+
+3. **Monitor**
+ - Check API logs for errors
+ - Monitor rate limiting
+ - Verify caching is working
+
+## ๐ฏ Plugin is Safe to Publish
+
+The `premiumPlugin` can now be:
+- โ
Published to public repositories
+- โ
Shared with users
+- โ
Installed on any server
+- โ
Code reviewed by anyone
+
+**No secrets will be exposed** because all verification happens on your server!
+
+## ๐ Files Created/Modified
+
+### Created
+- `/home/newstargeted.com/api.newstargeted.com/api/verify-patreon-membership.php`
+- `/home/newstargeted.com/api.newstargeted.com/api/index.php`
+- `/home/cyberpanel-plugins/premiumPlugin/views.py` (remote version)
+- `/home/cyberpanel-plugins/premiumPlugin/SECURITY.md`
+
+### Modified
+- `/home/newstargeted.com/api.newstargeted.com/config.php` (added Patreon credentials)
+- `/home/newstargeted.com/api.newstargeted.com/.htaccess` (added API routing)
+- `/home/cyberpanel-plugins/premiumPlugin/views.py` (updated to remote verification)
+
+## โจ Benefits
+
+1. **Security**: Secrets never leave your server
+2. **Control**: You can revoke access, update logic centrally
+3. **Transparency**: Plugin code can be open source
+4. **Scalability**: Centralized verification handles all requests
+5. **Maintenance**: Update verification logic in one place
diff --git a/to-do/SETUP-COMPLETE.md b/to-do/SETUP-COMPLETE.md
new file mode 100644
index 000000000..c344e6eb1
--- /dev/null
+++ b/to-do/SETUP-COMPLETE.md
@@ -0,0 +1,86 @@
+# Remote Verification Setup Complete โ
+
+## What Was Done
+
+### 1. Remote Verification API Created
+- **Location**: `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php`
+- **URL**: `https://api.newstargeted.com/api/verify-patreon-membership`
+- **Route**: Added to `.htaccess` for clean URL routing
+
+### 2. Plugin Updated to Use Remote Verification
+- **File**: `/home/cyberpanel-plugins/premiumPlugin/views.py`
+- **Method**: All Patreon checks now go through your server
+- **No Secrets**: Plugin code contains zero credentials
+
+### 3. Configuration Added
+- **Patreon credentials** added to `/home/newstargeted.com/api.newstargeted.com/config.php`
+- **Secure permissions**: config.php set to 600 (readable only by owner)
+
+### 4. File Permissions Set
+- โ
API files: `newst3922:newst3922` (644 for files, 755 for directories)
+- โ
Plugin files: `newst3922:newst3922` (644 for files, 755 for directories)
+- โ
Config file: `newst3922:newst3922` (600 - secure)
+
+## Security Features
+
+โ
**No secrets in plugin** - Users can see all code
+โ
**All credentials on your server** - Never exposed
+โ
**Rate limiting** - 60 requests/hour per IP
+โ
**Caching** - 5 minute cache to reduce API calls
+โ
**HTTPS only** - All communication encrypted
+โ
**Error handling** - Graceful failures
+
+## How It Works
+
+1. User installs plugin (no subscription needed)
+2. User tries to access plugin
+3. Plugin makes API call to YOUR server
+4. Your server checks Patreon API (credentials stay on your server)
+5. Your server returns access status
+6. Plugin shows content or subscription page
+
+## Testing
+
+### Test API Endpoint
+```bash
+curl -X POST https://api.newstargeted.com/api/verify-patreon-membership \
+ -H "Content-Type: application/json" \
+ -d '{
+ "user_email": "test@example.com",
+ "plugin_name": "premiumPlugin",
+ "tier_id": "27789984"
+ }'
+```
+
+### Expected Response
+```json
+{
+ "success": true,
+ "has_access": false,
+ "patreon_tier": "CyberPanel Paid Plugin",
+ "patreon_url": "https://www.patreon.com/c/newstargeted/membership",
+ "message": "Patreon subscription required..."
+}
+```
+
+## Next Steps
+
+1. **Test the API endpoint** - Verify it's accessible
+2. **Implement OAuth flow** - For users to authorize Patreon access
+3. **Store user tokens** - Link Patreon tokens to user emails
+4. **Test full flow** - Install plugin and verify access control
+
+## Files Modified
+
+- `/home/newstargeted.com/api.newstargeted.com/config.php` - Added Patreon credentials
+- `/home/newstargeted.com/api.newstargeted.com/.htaccess` - Added API route
+- `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php` - Created
+- `/home/cyberpanel-plugins/premiumPlugin/views.py` - Updated to use remote verification
+- `/home/cyberpanel/plugins/premiumPlugin/views.py` - Updated (installed version)
+
+## Plugin is Now Safe to Publish
+
+โ
No secrets in code
+โ
All verification happens on your server
+โ
Users can see all plugin files without security risk
+โ
Centralized control and updates
diff --git a/to-do/v2.5.5-dev-installation-fixes.md b/to-do/v2.5.5-dev-installation-fixes.md
new file mode 100644
index 000000000..23cdc92c5
--- /dev/null
+++ b/to-do/v2.5.5-dev-installation-fixes.md
@@ -0,0 +1,132 @@
+# CyberPanel v2.5.5-dev Installation Fixes
+
+## Issues Identified
+
+### Issue 1: install.sh Module Loading Failure
+**Problem**: When executing `install.sh` via `sh <(curl ...)`, the script tries to load modules from `/dev/fd/.../modules/` which doesn't exist because the script is executed directly from stdin.
+
+**Error Message**:
+```
+โ Module not found: /dev/fd/modules/os/detect.sh
+โ Failed to load OS detection module
+```
+
+**Root Cause**: The script assumes it's running from a cloned repository directory, but when executed via curl, `SCRIPT_DIR` becomes `/dev/fd/...` which doesn't contain the modules.
+
+### Issue 2: MariaDB 10.x to 12.x Upgrade Blocked
+**Problem**: The installer attempts to install MariaDB 12.1 on systems that already have MariaDB 10.11.15 installed. MariaDB's package pre-installation script blocks the upgrade because direct upgrades from 10.x to 12.x are not safe without manual dump/restore.
+
+**Error Message**:
+```
+error: %prein(MariaDB-server-12.1.2-1.el9.x86_64) scriptlet failed, exit status 1
+Error in PREIN scriptlet in rpm package MariaDB-server
+```
+
+**Root Cause**: The installer doesn't check for existing MariaDB installations before attempting to install MariaDB 12.1.
+
+## Fixes Implemented
+
+### Fix 1: install.sh - Handle Curl/Wget Execution
+
+**Location**: `install/install.sh`
+
+**Changes**:
+1. Added detection for curl/wget execution (checks if `SCRIPT_DIR` is `/dev/fd/*` or modules directory doesn't exist)
+2. When detected, the script now:
+ - Clones the CyberPanel repository to a temporary directory
+ - Extracts branch name from command-line arguments
+ - Falls back to legacy installer if git clone fails
+3. Properly handles branch argument parsing for both `-b` and `--branch` flags
+
+**Code Added**:
+```bash
+# Check if script is being executed via curl/wget
+if [[ "$SCRIPT_DIR" == /dev/fd/* ]] || [[ ! -d "$SCRIPT_DIR/modules" ]]; then
+ # Clone repository first
+ # ... (see install.sh for full implementation)
+fi
+```
+
+### Fix 2: install.py - MariaDB Installation Detection and Upgrade Attempt
+
+**Location**: `install/install.py`
+
+**Changes**:
+1. Added `checkExistingMariaDB()` method that:
+ - Checks for installed MariaDB/MySQL server packages (RPM or DEB)
+ - Detects MariaDB data directory existence
+ - Extracts version information from `mysql --version` output
+ - Returns version info including major.minor version
+
+2. Added `_attemptMariaDBUpgrade()` method that:
+ - Attempts to install MariaDB 12.1 on systems with MariaDB 10.x
+ - Sets up MariaDB 12.1 repository
+ - Attempts installation with appropriate flags (`--allowerasing` for RHEL)
+ - Returns `True` if successful, `False` if blocked or fails
+ - Handles upgrade restriction errors gracefully
+
+3. Modified `installMySQL()` method to:
+ - Check for existing MariaDB installation before attempting install
+ - **If MariaDB 10.x is detected**: Attempt to upgrade to 12.1 first
+ - If upgrade succeeds: Use new MariaDB 12.1 installation
+ - If upgrade fails/blocked: Fall back to existing MariaDB 10.x
+ - If MariaDB 12.x or higher is detected: Skip installation and use existing
+ - Only attempt new installation if no MariaDB is found
+
+**Key Logic**:
+```python
+# Check if MariaDB is already installed
+is_installed, installed_version, major_minor = self.checkExistingMariaDB()
+
+if is_installed:
+ if major_minor and major_minor != "unknown":
+ major_ver = float(major_minor)
+ if major_ver < 12.0:
+ # Try to upgrade to 12.1 first
+ upgrade_success = self._attemptMariaDBUpgrade()
+ if upgrade_success:
+ # Use new MariaDB 12.1
+ return True
+ else:
+ # Fall back to existing MariaDB 10.x
+ self.startMariaDB()
+ return True
+ # Use existing MariaDB 12.x+ installation
+ self.startMariaDB()
+ return True
+```
+
+## Testing Recommendations
+
+1. **Test install.sh with curl**:
+ ```bash
+ sh <(curl -s https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/install.sh)
+ ```
+
+2. **Test on AlmaLinux 9 with existing MariaDB 10.x**:
+ - System should detect existing MariaDB 10.x
+ - Should attempt to upgrade to MariaDB 12.1 first
+ - If upgrade is blocked, should fall back to using existing MariaDB 10.x
+ - Should log appropriate messages about upgrade attempt and fallback
+
+3. **Test on clean AlmaLinux 9 system**:
+ - Should install MariaDB 12.1 successfully
+ - Should complete full installation
+
+## Files Modified
+
+1. `install/install.sh` - Added curl/wget execution handling
+2. `install/install.py` - Added MariaDB detection and existing installation handling
+
+## Notes
+
+- The installer now **attempts to upgrade MariaDB 10.x to 12.1 first**, and only falls back to using existing 10.x if the upgrade is blocked by the package manager
+- This provides the best of both worlds: tries to get the latest version, but safely falls back if upgrade restrictions prevent it
+- The upgrade attempt uses `--allowerasing` flag for RHEL-based systems to allow package replacements
+- If MariaDB's pre-installation scriptlet blocks the upgrade (as it does for 10.x to 12.x), the installer gracefully falls back to the existing installation
+- The curl/wget execution now properly clones the repository before attempting to load modules
+
+## Related Issues
+
+- MariaDB 10.x to 12.x upgrades require manual dump/restore per MariaDB documentation
+- The installer now respects existing installations to prevent data loss