mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-02-16 19:46:48 +01:00
16
README.md
16
README.md
@@ -149,6 +149,22 @@ journalctl -u lscpd -f
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### OLS Feature Test Suite
|
||||
|
||||
The OpenLiteSpeed feature test suite (128 tests) validates binary integrity, CyberPanel module, Auto-SSL config, SSL listener auto-mapping, .htaccess processing, ReadApacheConf directives, and more.
|
||||
|
||||
```bash
|
||||
# Run from CyberPanel repo root
|
||||
./tests/ols_test_setup.sh # One-time setup
|
||||
./tests/ols_feature_tests.sh
|
||||
```
|
||||
|
||||
Requires a live CyberPanel + OLS installation.
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
* Official site: [https://cyberpanel.net](https://cyberpanel.net)
|
||||
|
||||
404
cyberpanel.sh
404
cyberpanel.sh
@@ -11,6 +11,7 @@ OS_FAMILY=""
|
||||
PACKAGE_MANAGER=""
|
||||
ARCHITECTURE=""
|
||||
BRANCH_NAME=""
|
||||
MARIADB_VER=""
|
||||
DEBUG_MODE=false
|
||||
AUTO_INSTALL=false
|
||||
INSTALLATION_TYPE=""
|
||||
@@ -579,6 +580,50 @@ cleanup_existing_cyberpanel() {
|
||||
|
||||
# Function to install CyberPanel directly using the working method
|
||||
install_cyberpanel_direct() {
|
||||
# Ask web server (OpenLiteSpeed vs LiteSpeed Enterprise) BEFORE MariaDB; default OpenLiteSpeed
|
||||
if [ -z "$LS_ENT" ]; then
|
||||
if [ "$AUTO_INSTALL" = true ]; then
|
||||
LS_ENT=""
|
||||
echo " Using OpenLiteSpeed (auto mode)."
|
||||
else
|
||||
echo ""
|
||||
echo " Web server: 1) OpenLiteSpeed (default), 2) LiteSpeed Enterprise"
|
||||
read -r -t 60 -p " Enter 1 or 2 [1]: " LS_CHOICE || true
|
||||
LS_CHOICE="${LS_CHOICE:-1}"
|
||||
LS_CHOICE="${LS_CHOICE// /}"
|
||||
if [ "$LS_CHOICE" = "2" ]; then
|
||||
echo " LiteSpeed Enterprise selected. Enter serial/key (required):"
|
||||
read -r -t 120 -p " Serial: " LS_SERIAL || true
|
||||
LS_SERIAL="${LS_SERIAL:-}"
|
||||
if [ -z "$LS_SERIAL" ]; then
|
||||
echo " No serial provided. Defaulting to OpenLiteSpeed."
|
||||
LS_ENT=""
|
||||
else
|
||||
LS_ENT="ent"
|
||||
echo " Using LiteSpeed Enterprise with provided serial."
|
||||
fi
|
||||
else
|
||||
LS_ENT=""
|
||||
echo " Using OpenLiteSpeed."
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ask MariaDB version (after web server choice) if not set via --mariadb-version
|
||||
if [ -z "$MARIADB_VER" ]; then
|
||||
echo ""
|
||||
echo " MariaDB version: 10.11, 11.8 (LTS, default) or 12.1?"
|
||||
read -r -t 60 -p " Enter 10.11, 11.8 or 12.1 [11.8]: " MARIADB_VER || true
|
||||
MARIADB_VER="${MARIADB_VER:-11.8}"
|
||||
MARIADB_VER="${MARIADB_VER// /}"
|
||||
if [ "$MARIADB_VER" != "10.11" ] && [ "$MARIADB_VER" != "11.8" ] && [ "$MARIADB_VER" != "12.1" ]; then
|
||||
MARIADB_VER="11.8"
|
||||
fi
|
||||
echo " Using MariaDB $MARIADB_VER"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo " 🔄 Downloading CyberPanel installation files..."
|
||||
|
||||
# Check if CyberPanel is already installed
|
||||
@@ -609,24 +654,27 @@ install_cyberpanel_direct() {
|
||||
systemctl enable mariadb 2>/dev/null || true
|
||||
systemctl enable lsws 2>/dev/null || true
|
||||
|
||||
# Clear any previous install temp folders so we never use stale extracted files
|
||||
rm -rf /tmp/cyberpanel_install_* 2>/dev/null || true
|
||||
|
||||
# Create temporary directory for installation
|
||||
local temp_dir="/tmp/cyberpanel_install_$$"
|
||||
mkdir -p "$temp_dir"
|
||||
cd "$temp_dir" || return 1
|
||||
|
||||
# CRITICAL: Disable MariaDB 12.1 repository and add dnf exclude if MariaDB 10.x is installed
|
||||
# This must be done BEFORE Pre_Install_Setup_Repository runs
|
||||
# Only add dnf exclude when we want to KEEP the current MariaDB (same version as user chose).
|
||||
# If user chose 11.8 but 10.11 is installed, do NOT exclude — allow install.py to upgrade.
|
||||
if command -v rpm >/dev/null 2>&1; then
|
||||
# Check if MariaDB 10.x is installed
|
||||
if rpm -qa | grep -qiE "^(mariadb-server|mysql-server|MariaDB-server)" 2>/dev/null; then
|
||||
local mariadb_version=$(mysql --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
if [ -n "$mariadb_version" ]; then
|
||||
local major_ver=$(echo "$mariadb_version" | cut -d. -f1)
|
||||
local minor_ver=$(echo "$mariadb_version" | cut -d. -f2)
|
||||
|
||||
# Check if it's MariaDB 10.x (major version < 12)
|
||||
if [ "$major_ver" -lt 12 ]; then
|
||||
print_status "MariaDB $mariadb_version detected, adding dnf exclude to prevent upgrade attempts"
|
||||
local installed_majmin="${major_ver}.${minor_ver}"
|
||||
local chosen_ver="${MARIADB_VER:-11.8}"
|
||||
# Only add exclude when installed version matches user's choice (preserve, no upgrade)
|
||||
if [ "$installed_majmin" = "$chosen_ver" ]; then
|
||||
print_status "MariaDB $mariadb_version matches chosen $chosen_ver, adding dnf exclude to preserve it"
|
||||
|
||||
# Add MariaDB-server to dnf excludes (multiple formats for compatibility)
|
||||
local dnf_conf="/etc/dnf/dnf.conf"
|
||||
@@ -763,6 +811,19 @@ except:
|
||||
local monitor_pid=$!
|
||||
echo "$monitor_pid" > /tmp/cyberpanel_repo_monitor.pid
|
||||
print_status "Started background process to monitor and disable MariaDB repositories"
|
||||
else
|
||||
# User chose a different version (e.g. 11.8) than installed (e.g. 10.11) — allow upgrade
|
||||
print_status "MariaDB $mariadb_version installed but you chose $chosen_ver; not adding dnf exclude (installer will upgrade)"
|
||||
# Remove any existing MariaDB exclude from a previous run so install can proceed
|
||||
for c in /etc/dnf/dnf.conf /etc/yum.conf; do
|
||||
if [ -f "$c" ] && grep -q "exclude=.*MariaDB-server" "$c" 2>/dev/null; then
|
||||
sed -i 's/ *MariaDB-server\* *//g; s/exclude= *$/exclude=/; s/exclude=\s*$/exclude=/' "$c" 2>/dev/null
|
||||
if grep -q "^exclude=\s*$" "$c" 2>/dev/null; then
|
||||
sed -i '/^exclude=\s*$/d' "$c" 2>/dev/null
|
||||
fi
|
||||
print_status "Removed MariaDB-server from excludes in $c to allow upgrade"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -774,19 +835,23 @@ except:
|
||||
echo "Downloading from: https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh"
|
||||
|
||||
# First, try to download the repository archive to get the correct installer
|
||||
local archive_url="https://github.com/master3395/cyberpanel/archive/v2.5.5-dev.tar.gz"
|
||||
# GitHub: branch archives use refs/heads/BRANCH; GitHub returns 302 redirect to codeload, so we must use -L
|
||||
local archive_url=""
|
||||
local installer_url="https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh"
|
||||
|
||||
# Test if the development branch archive exists
|
||||
if curl -s --head "$archive_url" | grep -q "200 OK"; then
|
||||
if curl -s -L --head "https://github.com/master3395/cyberpanel/archive/refs/heads/v2.5.5-dev.tar.gz" | grep -q "200 OK"; then
|
||||
archive_url="https://github.com/master3395/cyberpanel/archive/refs/heads/v2.5.5-dev.tar.gz"
|
||||
echo " Using development branch (v2.5.5-dev) from master3395/cyberpanel"
|
||||
elif curl -s -L --head "https://github.com/master3395/cyberpanel/archive/v2.5.5-dev.tar.gz" | grep -q "200 OK"; then
|
||||
archive_url="https://github.com/master3395/cyberpanel/archive/v2.5.5-dev.tar.gz"
|
||||
echo " Using development branch (v2.5.5-dev) from master3395/cyberpanel"
|
||||
else
|
||||
echo " Development branch archive not available, trying installer script directly..."
|
||||
# Test if the installer script exists
|
||||
if ! curl -s --head "$installer_url" | grep -q "200 OK"; then
|
||||
if ! curl -s -L --head "$installer_url" | grep -q "200 OK"; then
|
||||
echo " Development branch not available, falling back to stable"
|
||||
installer_url="https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh"
|
||||
archive_url="https://github.com/master3395/cyberpanel/archive/stable.tar.gz"
|
||||
else
|
||||
archive_url="https://github.com/master3395/cyberpanel/archive/refs/heads/v2.5.5-dev.tar.gz"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -796,83 +861,22 @@ except:
|
||||
return 1
|
||||
fi
|
||||
|
||||
# CRITICAL: Patch the installer script to skip MariaDB installation if 10.x is already installed
|
||||
if [ -n "$MARIADB_VERSION" ] && [ "$major_ver" -lt 12 ] 2>/dev/null; then
|
||||
print_status "Patching installer script to skip MariaDB installation..."
|
||||
|
||||
# Create a backup
|
||||
cp cyberpanel_installer.sh cyberpanel_installer.sh.backup
|
||||
|
||||
# Use Python to properly patch the installer script
|
||||
python3 -c "
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
with open('cyberpanel_installer.sh', 'r') as f:
|
||||
content = f.read()
|
||||
# Do NOT patch installer to add --exclude=MariaDB-server*: it blocks initial MariaDB install
|
||||
# and causes "MariaDB-server requires MariaDB-client but none of the providers can be installed".
|
||||
|
||||
original_content = content
|
||||
|
||||
# Pattern: Add --exclude=MariaDB-server* to dnf/yum install commands that install mariadb-server
|
||||
# Match: (dnf|yum) install [flags] [packages including mariadb-server]
|
||||
def add_exclude(match):
|
||||
cmd = match.group(0)
|
||||
# Check if --exclude is already present
|
||||
if '--exclude=MariaDB-server' in cmd:
|
||||
return cmd
|
||||
# Add --exclude=MariaDB-server* after install and flags, before packages
|
||||
return re.sub(r'((?:dnf|yum)\s+install\s+(?:-[^\s]+\s+)*)', r'\1--exclude=MariaDB-server* ', cmd, flags=re.IGNORECASE)
|
||||
|
||||
# Find all dnf/yum install commands that mention mariadb-server
|
||||
content = re.sub(
|
||||
r'(?:dnf|yum)\s+install[^;]*?mariadb-server[^;]*',
|
||||
add_exclude,
|
||||
content,
|
||||
flags=re.IGNORECASE | re.MULTILINE
|
||||
)
|
||||
|
||||
# Also handle MariaDB-server (capitalized)
|
||||
content = re.sub(
|
||||
r'(?:dnf|yum)\s+install[^;]*?MariaDB-server[^;]*',
|
||||
add_exclude,
|
||||
content,
|
||||
flags=re.IGNORECASE | re.MULTILINE
|
||||
)
|
||||
|
||||
# Only write if content changed
|
||||
if content != original_content:
|
||||
with open('cyberpanel_installer.sh', 'w') as f:
|
||||
f.write(content)
|
||||
print('Installer script patched successfully')
|
||||
else:
|
||||
print('No changes needed in installer script')
|
||||
|
||||
except Exception as e:
|
||||
print(f'Error patching installer script: {e}')
|
||||
sys.exit(1)
|
||||
" 2>/dev/null && print_status "Installer script patched successfully" || {
|
||||
# Fallback: Simple sed-based patching if Python fails
|
||||
sed -i 's/\(dnf\|yum\) install\([^;]*\)mariadb-server/\1 install\2--exclude=MariaDB-server* mariadb-server/gi' cyberpanel_installer.sh 2>/dev/null
|
||||
sed -i 's/\(dnf\|yum\) install\([^;]*\)MariaDB-server/\1 install\2--exclude=MariaDB-server* MariaDB-server/gi' cyberpanel_installer.sh 2>/dev/null
|
||||
print_status "Installer script patched (fallback method)"
|
||||
}
|
||||
|
||||
print_status "Installer script patched to exclude MariaDB-server from installation"
|
||||
fi
|
||||
|
||||
# Make script executable and verify
|
||||
chmod 755 cyberpanel_installer.sh 2>/dev/null || true
|
||||
# Make script executable (use full path in case cwd has noexec)
|
||||
chmod 755 cyberpanel_installer.sh 2>/dev/null || chmod +x cyberpanel_installer.sh 2>/dev/null || true
|
||||
if [ ! -x "cyberpanel_installer.sh" ]; then
|
||||
print_status "WARNING: Could not make cyberpanel_installer.sh executable, will use bash to execute"
|
||||
print_status "Note: Script will be run with bash (executable bit not set)"
|
||||
fi
|
||||
|
||||
# Download the install directory
|
||||
# Download the install directory (use archive_url set above; may be branch or stable)
|
||||
echo "Downloading installation files..."
|
||||
local archive_url="https://github.com/master3395/cyberpanel/archive/v2.5.5-dev.tar.gz"
|
||||
if [ "$installer_url" = "https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh" ]; then
|
||||
if [ -z "$archive_url" ] || [ "$installer_url" = "https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh" ]; then
|
||||
archive_url="https://github.com/master3395/cyberpanel/archive/stable.tar.gz"
|
||||
fi
|
||||
# Append cache-bust so CDNs/proxies don't serve old installer (GitHub ignores query params)
|
||||
archive_url="${archive_url}?nocache=$(date +%s 2>/dev/null || echo 0)"
|
||||
|
||||
curl --silent -L -o install_files.tar.gz "$archive_url" 2>/dev/null
|
||||
if [ $? -ne 0 ] || [ ! -s "install_files.tar.gz" ]; then
|
||||
@@ -925,8 +929,13 @@ except Exception as e:
|
||||
echo "This may take several minutes. Please be patient."
|
||||
echo ""
|
||||
|
||||
# Create log directory
|
||||
# Create log directory (same as v2.4.4: installer logs go here)
|
||||
mkdir -p /var/log/CyberPanel
|
||||
echo " Installation logs:"
|
||||
echo " • /var/log/CyberPanel/install.log (installer script messages)"
|
||||
echo " • /var/log/CyberPanel/install_output.log (Python installer stdout/stderr)"
|
||||
echo " • /var/log/installLogs.txt (install.py detailed log)"
|
||||
echo ""
|
||||
|
||||
# Run the installer with live output monitoring
|
||||
echo "Starting CyberPanel installer with live progress monitoring..."
|
||||
@@ -953,67 +962,22 @@ except Exception as e:
|
||||
if [ -f "$installer_py" ]; then
|
||||
print_status "Using install/install.py directly for installation (non-interactive mode)"
|
||||
|
||||
# CRITICAL: Patch install.py to exclude MariaDB-server from dnf/yum commands
|
||||
if [ -n "$MARIADB_VERSION" ] && [ "$major_ver" -lt 12 ] 2>/dev/null; then
|
||||
print_status "Patching install.py to exclude MariaDB-server from installation commands..."
|
||||
|
||||
# Create backup
|
||||
cp "$installer_py" "${installer_py}.backup" 2>/dev/null || true
|
||||
|
||||
# Patch install.py to add --exclude=MariaDB-server* to dnf/yum install commands
|
||||
python3 -c "
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
with open('$installer_py', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
original_content = content
|
||||
|
||||
# Pattern: Add --exclude=MariaDB-server* to dnf/yum install commands that install mariadb-server
|
||||
def add_exclude(match):
|
||||
cmd = match.group(0)
|
||||
# Check if --exclude is already present
|
||||
if '--exclude=MariaDB-server' in cmd:
|
||||
return cmd
|
||||
# Add --exclude=MariaDB-server* after install and flags, before packages
|
||||
return re.sub(r'((?:dnf|yum)\s+install\s+(?:-[^\s]+\s+)*)', r'\1--exclude=MariaDB-server* ', cmd, flags=re.IGNORECASE)
|
||||
|
||||
# Find all dnf/yum install commands that mention mariadb-server
|
||||
content = re.sub(
|
||||
r'(?:dnf|yum)\s+install[^;]*?mariadb-server[^;]*',
|
||||
add_exclude,
|
||||
content,
|
||||
flags=re.IGNORECASE | re.MULTILINE
|
||||
)
|
||||
|
||||
# Also handle MariaDB-server (capitalized) and in Python strings
|
||||
content = re.sub(
|
||||
r'(\"|\')(?:dnf|yum)\s+install[^\"]*?mariadb-server[^\"]*(\"|\')',
|
||||
lambda m: m.group(1) + re.sub(r'((?:dnf|yum)\s+install\s+(?:-[^\s]+\s+)*)', r'\1--exclude=MariaDB-server* ', m.group(0)[1:-1], flags=re.IGNORECASE) + m.group(2),
|
||||
content,
|
||||
flags=re.IGNORECASE | re.MULTILINE
|
||||
)
|
||||
|
||||
# Only write if content changed
|
||||
if content != original_content:
|
||||
with open('$installer_py', 'w') as f:
|
||||
f.write(content)
|
||||
print('install.py patched successfully')
|
||||
else:
|
||||
print('No changes needed in install.py')
|
||||
# NOTE: We do NOT patch install.py to add --exclude=MariaDB-server* to dnf install.
|
||||
# That would block the initial MariaDB-server install. install.py now clears dnf exclude
|
||||
# before installing MariaDB and uses official MariaDB-server packages.
|
||||
|
||||
except Exception as e:
|
||||
print(f'Error patching install.py: {e}')
|
||||
sys.exit(1)
|
||||
" 2>/dev/null && print_status "install.py patched successfully" || {
|
||||
# Fallback: Simple sed-based patching if Python fails
|
||||
sed -i 's/\(dnf\|yum\) install\([^;]*\)mariadb-server/\1 install\2--exclude=MariaDB-server* mariadb-server/gi' "$installer_py" 2>/dev/null
|
||||
sed -i 's/\(dnf\|yum\) install\([^;]*\)MariaDB-server/\1 install\2--exclude=MariaDB-server* MariaDB-server/gi' "$installer_py" 2>/dev/null
|
||||
print_status "install.py patched (fallback method)"
|
||||
}
|
||||
fi
|
||||
# Clear MariaDB-server from dnf/yum exclude so the installer can install or reinstall it
|
||||
# (cyberpanel.sh may have added it earlier when 10.x was detected; partial installs leave exclude in place)
|
||||
for conf in /etc/dnf/dnf.conf /etc/yum.conf; do
|
||||
if [ -f "$conf" ] && grep -q "exclude=.*MariaDB-server" "$conf" 2>/dev/null; then
|
||||
sed -i '/^exclude=/s/MariaDB-server\*\s*//g' "$conf"
|
||||
sed -i '/^exclude=/s/\s*MariaDB-server\*//g' "$conf"
|
||||
sed -i '/^exclude=/s/MariaDB-server\s*//g' "$conf"
|
||||
sed -i '/^exclude=\s*$/d' "$conf"
|
||||
sed -i '/^exclude=$/d' "$conf"
|
||||
print_status "Cleared MariaDB-server from exclude in $conf for installation"
|
||||
fi
|
||||
done
|
||||
|
||||
# If MariaDB 10.x is installed, disable repositories right before running installer
|
||||
if [ -n "$MARIADB_VERSION" ] && [ -f /tmp/cyberpanel_repo_monitor.pid ]; then
|
||||
@@ -1161,9 +1125,10 @@ except Exception as e:
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify MySQLdb is available
|
||||
# Verify MySQLdb is available (mysqlclient; some builds lack __version__)
|
||||
print_status "Verifying MySQLdb module availability..."
|
||||
if python3 -c "import MySQLdb; print('MySQLdb version:', MySQLdb.__version__)" 2>&1; then
|
||||
if python3 -c "import MySQLdb; getattr(MySQLdb, '__version__', 'ok'); print('MySQLdb OK')" 2>/dev/null || \
|
||||
python3 -c "import MySQLdb; MySQLdb; print('MySQLdb OK')" 2>/dev/null; then
|
||||
print_status "✓ MySQLdb module is available and working"
|
||||
else
|
||||
print_status "⚠️ WARNING: MySQLdb module not available"
|
||||
@@ -1178,19 +1143,40 @@ except Exception as e:
|
||||
# install.py requires publicip as first positional argument
|
||||
local install_args=("$server_ip")
|
||||
|
||||
# Add optional arguments based on user preferences
|
||||
# Web server: OpenLiteSpeed (default) or LiteSpeed Enterprise (--ent + --serial)
|
||||
if [ -n "$LS_ENT" ] && [ -n "$LS_SERIAL" ]; then
|
||||
install_args+=("--ent" "$LS_ENT" "--serial" "$LS_SERIAL")
|
||||
fi
|
||||
# Default: OpenLiteSpeed, Full installation (postfix, powerdns, ftp), Local MySQL
|
||||
# These match what the user selected in the interactive prompts
|
||||
install_args+=("--postfix" "ON")
|
||||
install_args+=("--powerdns" "ON")
|
||||
install_args+=("--ftp" "ON")
|
||||
install_args+=("--remotemysql" "OFF")
|
||||
# Only pass --mariadb-version if this install.py supports it (avoids "unrecognized arguments" on older archives)
|
||||
if grep -q "mariadb-version\|mariadb_version" "$installer_py" 2>/dev/null; then
|
||||
install_args+=("--mariadb-version" "${MARIADB_VER:-11.8}")
|
||||
fi
|
||||
|
||||
if [ "$DEBUG_MODE" = true ]; then
|
||||
# Note: install.py doesn't have --debug, but we can set it via environment
|
||||
export DEBUG_MODE=true
|
||||
fi
|
||||
|
||||
# CRITICAL: If CyberPanel Python does not exist yet, patch installer to use system Python.
|
||||
# Fixes FileNotFoundError when archive is cached/old and still references /usr/local/CyberPanel/bin/python.
|
||||
if [ ! -f /usr/local/CyberPanel/bin/python ]; then
|
||||
sys_python="/usr/bin/python3"
|
||||
[ -x "$sys_python" ] || sys_python="/usr/local/bin/python3"
|
||||
if [ -x "$sys_python" ]; then
|
||||
for f in install/install_utils.py install/install.py; do
|
||||
if [ -f "$f" ] && grep -q '/usr/local/CyberPanel/bin/python' "$f" 2>/dev/null; then
|
||||
sed -i "s|/usr/local/CyberPanel/bin/python|$sys_python|g" "$f"
|
||||
print_status "Patched $f to use $sys_python (CyberPanel python not yet installed)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run the Python installer directly
|
||||
if [ "$DEBUG_MODE" = true ]; then
|
||||
python3 "$installer_py" "${install_args[@]}" 2>&1 | tee /var/log/CyberPanel/install_output.log
|
||||
@@ -1257,6 +1243,11 @@ except Exception as e:
|
||||
echo " INSTALLATION COMPLETED"
|
||||
echo "==============================================================================================================="
|
||||
echo ""
|
||||
echo " Installation logs (for troubleshooting):"
|
||||
echo " • /var/log/CyberPanel/install.log (installer script messages)"
|
||||
echo " • /var/log/CyberPanel/install_output.log (Python installer stdout/stderr)"
|
||||
echo " • /var/log/installLogs.txt (install.py detailed log)"
|
||||
echo ""
|
||||
|
||||
# Check if installation was successful
|
||||
if [ $install_exit_code -ne 0 ]; then
|
||||
@@ -1352,8 +1343,9 @@ apply_fixes() {
|
||||
systemctl start mariadb 2>/dev/null || true
|
||||
systemctl enable mariadb 2>/dev/null || true
|
||||
|
||||
# Fix LiteSpeed service
|
||||
cat > /etc/systemd/system/lsws.service << 'EOF'
|
||||
# Fix LiteSpeed service only if the web server was actually installed
|
||||
if [ -x /usr/local/lsws/bin/lswsctrl ] || [ -x /usr/local/lsws/bin/lsctrl ] || [ -f /usr/local/lsws/bin/openlitespeed ]; then
|
||||
cat > /etc/systemd/system/lsws.service << 'EOF'
|
||||
[Unit]
|
||||
Description=LiteSpeed Web Server
|
||||
After=network.target
|
||||
@@ -1372,9 +1364,16 @@ RestartSec=5
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable lsws
|
||||
systemctl start lsws
|
||||
systemctl daemon-reload
|
||||
systemctl enable lsws
|
||||
systemctl start lsws || true
|
||||
else
|
||||
echo " • LiteSpeed/OpenLiteSpeed not found at /usr/local/lsws - skipping lsws.service (install may have skipped web server)"
|
||||
echo " • If the installer failed earlier (e.g. Python error), re-run the installer. Once it completes, open ports 8090 and 7080 in your cloud security group (e.g. AWS EC2 Security Group inbound rules)."
|
||||
systemctl disable lsws 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/lsws.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
# Set OpenLiteSpeed admin password to match CyberPanel
|
||||
echo " • Configuring OpenLiteSpeed admin password..."
|
||||
@@ -1395,11 +1394,62 @@ EOF
|
||||
# Give services a moment to start
|
||||
sleep 3
|
||||
|
||||
# Ensure both 8090 (CyberPanel) and 7080 (LiteSpeed/OLS) are accessible
|
||||
echo " • Ensuring ports 8090 and 7080 are accessible..."
|
||||
port_check() {
|
||||
local port=$1
|
||||
command -v ss >/dev/null 2>&1 && ss -tlnp 2>/dev/null | grep -q ":$port " && return 0
|
||||
command -v netstat >/dev/null 2>&1 && netstat -tlnp 2>/dev/null | grep -q ":$port " && return 0
|
||||
return 1
|
||||
}
|
||||
max_attempts=18
|
||||
attempt=0
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
need_restart=false
|
||||
systemctl is-active --quiet mariadb || { systemctl start mariadb 2>/dev/null; need_restart=true; }
|
||||
systemctl is-active --quiet lsws 2>/dev/null || { [ -x /usr/local/lsws/bin/lswsctrl ] && systemctl start lsws 2>/dev/null; need_restart=true; }
|
||||
systemctl is-active --quiet lscpd 2>/dev/null || { systemctl start lscpd 2>/dev/null; need_restart=true; }
|
||||
[ "$need_restart" = true ] && sleep 5
|
||||
if port_check 8090 && port_check 7080; then
|
||||
echo " ✓ Port 8090 (CyberPanel) and 7080 (OpenLiteSpeed) are listening"
|
||||
break
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
[ $attempt -lt $max_attempts ] && sleep 5
|
||||
done
|
||||
if ! port_check 8090 || ! port_check 7080; then
|
||||
systemctl start lscpd 2>/dev/null
|
||||
systemctl start lsws 2>/dev/null
|
||||
sleep 10
|
||||
if port_check 8090 && port_check 7080; then
|
||||
echo " ✓ Port 8090 and 7080 are now listening"
|
||||
else
|
||||
echo " ⚠ One or both ports not yet listening. Run: systemctl start mariadb lsws lscpd"
|
||||
echo " ⚠ On AWS/cloud: add inbound rules for TCP 8090 and 7080 in the instance security group."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " ✓ Post-installation configurations completed"
|
||||
}
|
||||
|
||||
# Helper: check if a port is listening
|
||||
_port_listening() {
|
||||
local port=$1
|
||||
command -v ss >/dev/null 2>&1 && ss -tlnp 2>/dev/null | grep -q ":$port " && return 0
|
||||
command -v netstat >/dev/null 2>&1 && netstat -tlnp 2>/dev/null | grep -q ":$port " && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to show status summary
|
||||
show_status_summary() {
|
||||
# Last-chance: try to start services so 8090 and 7080 are accessible
|
||||
if ! _port_listening 8090 || ! _port_listening 7080; then
|
||||
systemctl start mariadb 2>/dev/null || true
|
||||
systemctl start lsws 2>/dev/null || true
|
||||
systemctl start lscpd 2>/dev/null || true
|
||||
sleep 8
|
||||
fi
|
||||
|
||||
echo "==============================================================================================================="
|
||||
echo " FINAL STATUS CHECK"
|
||||
echo "==============================================================================================================="
|
||||
@@ -1427,6 +1477,22 @@ show_status_summary() {
|
||||
echo " ✓ CyberPanel Application - Running"
|
||||
else
|
||||
echo " ✗ CyberPanel Application - Not Running (may take a moment to start)"
|
||||
all_services_running=false
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Port Accessibility:"
|
||||
if _port_listening 8090; then
|
||||
echo " ✓ Port 8090 (CyberPanel) - Accessible"
|
||||
else
|
||||
echo " ✗ Port 8090 (CyberPanel) - Not listening (run: systemctl start lscpd)"
|
||||
all_services_running=false
|
||||
fi
|
||||
if _port_listening 7080; then
|
||||
echo " ✓ Port 7080 (OpenLiteSpeed) - Accessible"
|
||||
else
|
||||
echo " ✗ Port 7080 (OpenLiteSpeed) - Not listening (run: systemctl start lsws)"
|
||||
all_services_running=false
|
||||
fi
|
||||
|
||||
# Get the actual password that was set
|
||||
@@ -1455,7 +1521,7 @@ show_status_summary() {
|
||||
echo "==============================================================================================================="
|
||||
|
||||
if [ "$all_services_running" = true ]; then
|
||||
echo "✓ Installation completed successfully!"
|
||||
echo "✓ Installation completed successfully! Ports 8090 and 7080 are accessible."
|
||||
else
|
||||
echo "⚠ Installation completed with warnings. Some services may need attention."
|
||||
fi
|
||||
@@ -1664,7 +1730,7 @@ show_version_selection() {
|
||||
echo ""
|
||||
echo " 1. Latest Stable (Recommended)"
|
||||
echo " 2. v2.5.5-dev (Development)"
|
||||
echo " 3. v2.5.4 (Previous Stable)"
|
||||
echo " 3. v2.4.4 (Previous Stable)"
|
||||
echo " 4. Custom Branch Name"
|
||||
echo " 5. Custom Commit Hash"
|
||||
echo ""
|
||||
@@ -1685,7 +1751,7 @@ show_version_selection() {
|
||||
break
|
||||
;;
|
||||
3)
|
||||
BRANCH_NAME="v2.5.4"
|
||||
BRANCH_NAME="v2.4.4"
|
||||
break
|
||||
;;
|
||||
4)
|
||||
@@ -2674,6 +2740,21 @@ parse_arguments() {
|
||||
set -x
|
||||
shift
|
||||
;;
|
||||
--mariadb-version)
|
||||
if [ -n "$2" ] && [ "$2" = "10.11" ]; then
|
||||
MARIADB_VER="10.11"
|
||||
shift 2
|
||||
elif [ -n "$2" ] && [ "$2" = "11.8" ]; then
|
||||
MARIADB_VER="11.8"
|
||||
shift 2
|
||||
elif [ -n "$2" ] && [ "$2" = "12.1" ]; then
|
||||
MARIADB_VER="12.1"
|
||||
shift 2
|
||||
else
|
||||
echo "ERROR: --mariadb-version requires 10.11, 11.8 or 12.1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--auto)
|
||||
AUTO_INSTALL=true
|
||||
shift
|
||||
@@ -2683,8 +2764,9 @@ parse_arguments() {
|
||||
echo "Options:"
|
||||
echo " -b, --branch BRANCH Install from specific branch/commit"
|
||||
echo " -v, --version VER Install specific version (auto-adds v prefix)"
|
||||
echo " --mariadb-version VER MariaDB version: 10.11, 11.8 or 12.1 (asked after web server)"
|
||||
echo " --debug Enable debug mode"
|
||||
echo " --auto Auto mode without prompts"
|
||||
echo " --auto Auto mode: OpenLiteSpeed + MariaDB 11.8 unless --mariadb-version set"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
@@ -2696,6 +2778,9 @@ parse_arguments() {
|
||||
echo " $0 -v 2.4.3 # Install version 2.4.3"
|
||||
echo " $0 -b main # Install from main branch"
|
||||
echo " $0 -b a1b2c3d4 # Install from specific commit"
|
||||
echo " $0 --mariadb-version 10.11 # Use MariaDB 10.11 (same as v2.4.4 style)"
|
||||
echo " $0 --mariadb-version 12.1 # Use MariaDB 12.1 (no prompt)"
|
||||
echo " $0 --auto --mariadb-version 11.8 # Fully non-interactive with MariaDB 11.8"
|
||||
echo ""
|
||||
echo "Standard CyberPanel Installation Methods:"
|
||||
echo " sh <(curl https://cyberpanel.net/install.sh)"
|
||||
@@ -2832,7 +2917,10 @@ main() {
|
||||
|
||||
print_status "SUCCESS: Installation completed successfully!"
|
||||
else
|
||||
# Run interactive mode
|
||||
# Run interactive mode - ensure stdin is the terminal for prompts (e.g. when script was piped from curl)
|
||||
if [ ! -t 0 ]; then
|
||||
exec 0</dev/tty
|
||||
fi
|
||||
show_main_menu
|
||||
fi
|
||||
;;
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
#Please use variable/functions name as MySomething or My_Something, and please try not to use too-short abbreviation :)
|
||||
#Please use On/Off, True/False, Yes/No.
|
||||
|
||||
# Require root immediately so all later steps (logs, /root, /usr/local/CyberCP) succeed
|
||||
if [[ $(id -u) -ne 0 ]] 2>/dev/null; then
|
||||
echo ""
|
||||
echo "This script must be run as root."
|
||||
echo "Run: sudo bash <(curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel_upgrade.sh) -b v2.5.5-dev"
|
||||
echo "Or: sudo su - then run the same command without sudo"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Sudo_Test=$(set)
|
||||
#for SUDO check
|
||||
|
||||
@@ -95,20 +105,21 @@ echo -e "\n${1}" >> /var/log/upgradeLogs.txt
|
||||
|
||||
Check_Root() {
|
||||
echo -e "\nChecking root privileges..."
|
||||
# If we're actually root (uid 0), allow regardless of SUDO in environment (e.g. curl | sudo bash)
|
||||
if [[ $(id -u) -eq 0 ]] 2>/dev/null; then
|
||||
echo -e "\nYou are running as root...\n"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if echo "$Sudo_Test" | grep SUDO >/dev/null; then
|
||||
echo -e "\nYou are using SUDO, please run as root user...\n"
|
||||
echo -e "\nIf you don't have direct access to root user, please run \e[31msudo su -\e[39m command (do NOT miss the \e[31m-\e[39m at end or it will fail) and then run installation command again."
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $(id -u) != 0 ]] >/dev/null; then
|
||||
echo -e "\nYou must run as root user to install CyberPanel...\n"
|
||||
echo -e "or run the following command: (do NOT miss the quotes)"
|
||||
echo -e "\e[31msudo su -c \"sh <(curl https://cyberpanel.sh || wget -O - https://cyberpanel.sh)\"\e[39m"
|
||||
exit 1
|
||||
else
|
||||
echo -e "\nYou are running as root...\n"
|
||||
fi
|
||||
echo -e "\nYou must run as root user to install CyberPanel...\n"
|
||||
echo -e "Run: \e[31msudo su -\e[39m then run this script again, or: curl -sL <url> | sudo bash -s -- <args>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Check_Server_IP() {
|
||||
@@ -234,7 +245,13 @@ if [[ "$1" = *.*.* ]]; then
|
||||
echo -e "\nYou must use version number higher than 2.3.4"
|
||||
exit
|
||||
else
|
||||
Branch_Name="v${1//[[:space:]]/}"
|
||||
raw="${1//[[:space:]]/}"
|
||||
# Do not add "v" if user already passed e.g. v2.5.5-dev (avoids vv2.5.5-dev)
|
||||
if [[ "$raw" = v* ]]; then
|
||||
Branch_Name="$raw"
|
||||
else
|
||||
Branch_Name="v$raw"
|
||||
fi
|
||||
echo -e "\nSet branch name to $Branch_Name...\n"
|
||||
fi
|
||||
else
|
||||
@@ -349,12 +366,12 @@ if [[ "$*" = *"--no-system-update"* ]]; then
|
||||
Skip_System_Update="yes"
|
||||
echo -e "\nUsing --no-system-update: skipping full system package update.\n"
|
||||
fi
|
||||
# Parse --mariadb-version 11.8|12.1 (default 11.8)
|
||||
# Parse --mariadb-version 10.11|11.8|12.1 (default 11.8)
|
||||
if [[ "$*" = *"--mariadb-version "* ]]; then
|
||||
MARIADB_VER=$(echo "$*" | sed -n 's/.*--mariadb-version \([^ ]*\).*/\1/p' | head -1)
|
||||
MARIADB_VER="${MARIADB_VER:-11.8}"
|
||||
fi
|
||||
if [[ "$MARIADB_VER" != "11.8" ]] && [[ "$MARIADB_VER" != "12.1" ]]; then
|
||||
if [[ "$MARIADB_VER" != "10.11" ]] && [[ "$MARIADB_VER" != "11.8" ]] && [[ "$MARIADB_VER" != "12.1" ]]; then
|
||||
MARIADB_VER="11.8"
|
||||
fi
|
||||
}
|
||||
@@ -930,10 +947,21 @@ Pre_Upgrade_Branch_Input() {
|
||||
|
||||
Main_Upgrade() {
|
||||
echo -e "\n[$(date +"%Y-%m-%d %H:%M:%S")] Starting Main_Upgrade function..." | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running: /usr/local/CyberPanel/bin/python upgrade.py $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
|
||||
# Resolve Python for upgrade (avoid FileNotFoundError when /usr/local/CyberPanel/bin/python missing)
|
||||
CP_PYTHON=""
|
||||
for py in /usr/local/CyberPanel/bin/python /usr/local/CyberCP/bin/python /usr/bin/python3 /usr/local/bin/python3; do
|
||||
if [[ -x "$py" ]]; then CP_PYTHON="$py"; break; fi
|
||||
done
|
||||
if [[ -z "$CP_PYTHON" ]]; then
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] ERROR: No Python found for upgrade (tried CyberPanel, CyberCP, python3)" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
exit 1
|
||||
fi
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Using Python: $CP_PYTHON" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running: $CP_PYTHON upgrade.py $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
|
||||
# Run upgrade.py and capture output
|
||||
upgrade_output=$(/usr/local/CyberPanel/bin/python upgrade.py "$Branch_Name" 2>&1)
|
||||
upgrade_output=$("$CP_PYTHON" upgrade.py "$Branch_Name" 2>&1)
|
||||
RETURN_CODE=$?
|
||||
echo "$upgrade_output" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
|
||||
@@ -1200,7 +1228,9 @@ tar xf wsgi-lsapi-2.1.tgz
|
||||
cd wsgi-lsapi-2.1 || exit
|
||||
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Configuring WSGI..." | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
/usr/local/CyberPanel/bin/python ./configure.py 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
PYTHON_CFG="${CP_PYTHON:-/usr/bin/python3}"
|
||||
[[ -x "$PYTHON_CFG" ]] || PYTHON_CFG="/usr/bin/python3"
|
||||
"$PYTHON_CFG" ./configure.py 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
|
||||
# Fix Makefile to use proper optimization flags to avoid _FORTIFY_SOURCE warnings
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Optimizing Makefile for proper compilation..." | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
@@ -1671,12 +1701,16 @@ if [[ "$*" != *"--branch "* ]] && [[ "$*" != *"-b "* ]] ; then
|
||||
Pre_Upgrade_Branch_Input
|
||||
fi
|
||||
|
||||
# Prompt for MariaDB version if not set via --mariadb-version (default 11.8). Downgrade supported (e.g. re-run with --mariadb-version 11.8).
|
||||
# Prompt for MariaDB version if not set via --mariadb-version (default 11.8). Options: 10.11, 11.8, 12.1.
|
||||
if [[ "$*" != *"--mariadb-version "* ]]; then
|
||||
echo -e "\nMariaDB version: \e[31m11.8\e[39m LTS (default) or \e[31m12.1\e[39m. You can switch later by re-running with --mariadb-version 11.8 or 12.1."
|
||||
echo -e "Press Enter for 11.8 LTS, or type \e[31m12.1\e[39m and Enter for 12.1 (5 sec timeout): "
|
||||
echo -e "\nMariaDB version: \e[31m10.11\e[39m, \e[31m11.8\e[39m LTS (default) or \e[31m12.1\e[39m. You can switch later by re-running with --mariadb-version 10.11, 11.8 or 12.1."
|
||||
echo -e "Press Enter for 11.8 LTS, or type \e[31m10.11\e[39m or \e[31m12.1\e[39m (5 sec timeout): "
|
||||
read -r -t 5 Tmp_MariaDB_Ver || true
|
||||
if [[ "$Tmp_MariaDB_Ver" = "12.1" ]]; then
|
||||
Tmp_MariaDB_Ver="${Tmp_MariaDB_Ver// /}"
|
||||
if [[ "$Tmp_MariaDB_Ver" = "10.11" ]]; then
|
||||
MARIADB_VER="10.11"
|
||||
echo -e "MariaDB 10.11 selected.\n"
|
||||
elif [[ "$Tmp_MariaDB_Ver" = "12.1" ]]; then
|
||||
MARIADB_VER="12.1"
|
||||
echo -e "MariaDB 12.1 selected.\n"
|
||||
else
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
# Ensure install dir is on path for ols_binaries_config
|
||||
_install_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
if _install_dir not in sys.path:
|
||||
sys.path.insert(0, _install_dir)
|
||||
import ols_binaries_config
|
||||
|
||||
import subprocess
|
||||
import shutil
|
||||
import installLog as logging
|
||||
import argparse
|
||||
import os
|
||||
import errno
|
||||
import shlex
|
||||
from firewallUtilities import FirewallUtilities
|
||||
@@ -405,48 +413,11 @@ class preFlightsChecks:
|
||||
except Exception as e:
|
||||
self.stdOut("Warning: Could not remove compat packages: " + str(e), 0)
|
||||
|
||||
# Check if MariaDB is already installed before attempting installation
|
||||
# Do NOT install MariaDB here with plain dnf (that would install distro default 10.11).
|
||||
# installMySQL() runs later with the user's chosen version (--mariadb-version: 10.11, 11.8 or 12.1).
|
||||
is_installed, installed_version, major_minor = self.checkExistingMariaDB()
|
||||
|
||||
if is_installed:
|
||||
self.stdOut(f"MariaDB/MySQL is already installed (version: {installed_version}), skipping installation", 1)
|
||||
mariadb_installed = True
|
||||
else:
|
||||
# Install MariaDB with enhanced AlmaLinux 9.6 support
|
||||
self.stdOut("Installing MariaDB for AlmaLinux 9.6...", 1)
|
||||
|
||||
# Try multiple installation methods for maximum compatibility
|
||||
mariadb_commands = [
|
||||
"dnf install -y mariadb-server mariadb-devel mariadb-client --skip-broken --nobest",
|
||||
"dnf install -y mariadb-server mariadb-devel mariadb-client --allowerasing",
|
||||
"dnf install -y mariadb-server mariadb-devel --skip-broken --nobest --allowerasing",
|
||||
"dnf install -y mariadb-server --skip-broken --nobest --allowerasing"
|
||||
]
|
||||
|
||||
mariadb_installed = False
|
||||
for cmd in mariadb_commands:
|
||||
try:
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300)
|
||||
if result.returncode == 0:
|
||||
mariadb_installed = True
|
||||
self.stdOut(f"MariaDB installed successfully with command: {cmd}", 1)
|
||||
break
|
||||
except subprocess.TimeoutExpired:
|
||||
self.stdOut(f"Timeout installing MariaDB with command: {cmd}", 0)
|
||||
continue
|
||||
except Exception as e:
|
||||
self.stdOut(f"Error installing MariaDB with command: {cmd} - {str(e)}", 0)
|
||||
continue
|
||||
|
||||
if not mariadb_installed:
|
||||
self.stdOut("MariaDB installation failed, trying MySQL as fallback...", 0)
|
||||
try:
|
||||
command = "dnf install -y mysql-server mysql-devel --skip-broken --nobest --allowerasing"
|
||||
self.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
self.stdOut("MySQL installed as fallback for MariaDB", 1)
|
||||
mariadb_installed = True
|
||||
except:
|
||||
self.stdOut("Both MariaDB and MySQL installation failed", 0)
|
||||
self.stdOut(f"MariaDB/MySQL already installed (version: {installed_version}), skipping", 1)
|
||||
|
||||
# Install additional required packages
|
||||
self.stdOut("Installing additional required packages...", 1)
|
||||
@@ -1213,30 +1184,7 @@ class preFlightsChecks:
|
||||
platform = self.detectPlatform()
|
||||
self.stdOut(f"Detected platform: {platform}", 1)
|
||||
|
||||
# Platform-specific URLs and checksums (OpenLiteSpeed 1.8.5+ preferred from repo; fallback static build)
|
||||
# Module Build Date: December 28, 2025 - v2.2.0 Brute Force with Progressive Throttle
|
||||
BINARY_CONFIGS = {
|
||||
'rhel8': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel8-static',
|
||||
'sha256': '6ce688a237615102cc1603ee1999b3cede0ff3482d31e1f65705e92396d34b3a',
|
||||
'module_url': 'https://cyberpanel.net/binaries/rhel8/cyberpanel_ols.so',
|
||||
'module_sha256': '7c33d89c7fbcd3ed7b0422fee3f49b5e041713c2c2b7316a5774f6defa147572'
|
||||
},
|
||||
'rhel9': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel9-static',
|
||||
'sha256': '709093d99d5d3e789134c131893614968e17eefd9ade2200f811d9b076b2f02e',
|
||||
'module_url': 'https://cyberpanel.net/binaries/rhel9/cyberpanel_ols.so',
|
||||
'module_sha256': 'ae65337e2d13babc0c675bb4264d469daffa2efb7627c9bf39ac59e42e3ebede'
|
||||
},
|
||||
'ubuntu': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-ubuntu-static',
|
||||
'sha256': '89aaf66474e78cb3c1666784e0e7a417550bd317e6ab148201bdc318d36710cb',
|
||||
'module_url': 'https://cyberpanel.net/binaries/ubuntu/cyberpanel_ols.so',
|
||||
'module_sha256': '62978ede1f174dd2885e5227a3d9cc463d0c27acd77cfc23743d7309ee0c54ea'
|
||||
}
|
||||
}
|
||||
|
||||
config = BINARY_CONFIGS.get(platform)
|
||||
config = ols_binaries_config.BINARY_CONFIGS.get(platform)
|
||||
if not config:
|
||||
self.stdOut(f"ERROR: No binaries available for platform {platform}", 1)
|
||||
self.stdOut("Skipping custom binary installation", 1)
|
||||
@@ -1366,6 +1314,24 @@ class preFlightsChecks:
|
||||
self.stdOut("=" * 50, 1)
|
||||
# Configure module after installation
|
||||
self.configureCustomModule()
|
||||
# Enable Auto-SSL if not already configured
|
||||
conf_path = '/usr/local/lsws/conf/httpd_config.conf'
|
||||
try:
|
||||
if os.path.exists(conf_path):
|
||||
with open(conf_path, 'r') as f:
|
||||
content = f.read()
|
||||
if 'autoSSL' not in content:
|
||||
content = re.sub(
|
||||
r'(adminEmails\s+\S+)',
|
||||
r'\1\nautoSSL 1\nacmeEmail admin@cyberpanel.net',
|
||||
content,
|
||||
count=1
|
||||
)
|
||||
with open(conf_path, 'w') as f:
|
||||
f.write(content)
|
||||
self.stdOut("Auto-SSL enabled in httpd_config.conf", 1)
|
||||
except Exception as e:
|
||||
self.stdOut(f"WARNING: Could not enable Auto-SSL: {e}", 1)
|
||||
return True
|
||||
|
||||
self.stdOut("ERROR: Installation verification failed", 1)
|
||||
@@ -1889,12 +1855,12 @@ module cyberpanel_ols {
|
||||
|
||||
if is_installed:
|
||||
self.stdOut(f"MariaDB/MySQL is already installed (version: {installed_version}), skipping installation", 1)
|
||||
# Use existing if already on 11.x or 12.x
|
||||
# Use existing if already on 10.x, 11.x or 12.x
|
||||
if major_minor and major_minor != "unknown":
|
||||
try:
|
||||
major_ver = float(major_minor)
|
||||
if major_ver >= 11.0:
|
||||
self.stdOut("Using existing MariaDB installation (11.x/12.x)", 1)
|
||||
if major_ver >= 10.0:
|
||||
self.stdOut("Using existing MariaDB installation (10.x/11.x/12.x)", 1)
|
||||
self.startMariaDB()
|
||||
self.changeMYSQLRootPassword()
|
||||
self.fixMariaDB()
|
||||
@@ -1902,12 +1868,45 @@ module cyberpanel_ols {
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Set up MariaDB repository only if not already installed (version from --mariadb-version, default 11.8)
|
||||
# Set up MariaDB repository only if not already installed (version from --mariadb-version: 10.11, 11.8 or 12.1)
|
||||
mariadb_ver = getattr(preFlightsChecks, 'mariadb_version', '11.8')
|
||||
command = f'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash -s -- --mariadb-server-version={mariadb_ver}'
|
||||
self.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
command = 'dnf install mariadb-server mariadb-devel mariadb-client-utils -y'
|
||||
# Allow MariaDB-server to be installed: remove from dnf exclude if present (e.g. from previous run or cyberpanel.sh)
|
||||
dnf_conf = '/etc/dnf/dnf.conf'
|
||||
if os.path.exists(dnf_conf) and ('MariaDB-server' in open(dnf_conf).read()):
|
||||
try:
|
||||
with open(dnf_conf, 'r') as f:
|
||||
dnf_content = f.read()
|
||||
if 'MariaDB-server' in (dnf_content or '') and 'exclude=' in (dnf_content or ''):
|
||||
# Remove MariaDB-server and MariaDB-server* from exclude= line(s)
|
||||
def strip_mariadb_exclude(match):
|
||||
line = match.group(0)
|
||||
rest = re.sub(r'\bMariaDB-server\*?\s*', '', line).strip()
|
||||
if rest == 'exclude=' or rest == 'exclude':
|
||||
return ''
|
||||
return rest.rstrip() + '\n'
|
||||
new_content = re.sub(r'exclude=[^\n]*', strip_mariadb_exclude, dnf_content)
|
||||
new_content = re.sub(r'\n\n+', '\n', new_content)
|
||||
if new_content != dnf_content:
|
||||
with open(dnf_conf, 'w') as f:
|
||||
f.write(new_content)
|
||||
self.stdOut("Temporarily removed MariaDB-server from dnf exclude for installation", 1)
|
||||
except Exception as e:
|
||||
self.stdOut(f"Warning: Could not adjust dnf exclude: {e}", 1)
|
||||
# Fallback: use sed so exclude is cleared even if Python path failed
|
||||
if 'MariaDB-server' in open(dnf_conf).read():
|
||||
subprocess.run(
|
||||
"sed -i '/^exclude=/s/MariaDB-server\\*\\s*//g; /^exclude=/s/\\s*MariaDB-server\\*//g; /^exclude=\\s*$/d' " + dnf_conf,
|
||||
shell=True, timeout=5, capture_output=True
|
||||
)
|
||||
self.stdOut("Temporarily removed MariaDB-server from dnf exclude for installation (fallback)", 1)
|
||||
# Install from official MariaDB repo (capitalized package names); --nobest for 10.11/11.8 on el9
|
||||
mariadb_packages = 'MariaDB-server MariaDB-client MariaDB-backup MariaDB-devel'
|
||||
if mariadb_ver in ('10.11', '11.8'):
|
||||
command = f'dnf install -y --nobest {mariadb_packages}'
|
||||
else:
|
||||
command = f'dnf install -y {mariadb_packages}'
|
||||
self.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
# Verify MariaDB was installed successfully before proceeding
|
||||
@@ -2262,10 +2261,11 @@ module cyberpanel_ols {
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
# Copy the PowerDNS configuration file
|
||||
# Copy the PowerDNS configuration file (cwd may be temp dir with install/ subdir when run from cyberpanel.sh)
|
||||
source_file = os.path.join(self.cwd, "dns-one", "pdns.conf")
|
||||
if not os.path.exists(source_file):
|
||||
# Try alternative location
|
||||
source_file = os.path.join(self.cwd, "install", "dns-one", "pdns.conf")
|
||||
if not os.path.exists(source_file):
|
||||
source_file = os.path.join(self.cwd, "dns", "pdns.conf")
|
||||
|
||||
if os.path.exists(source_file):
|
||||
@@ -2806,6 +2806,13 @@ module cyberpanel_ols {
|
||||
# Using shared function from install_utils
|
||||
@staticmethod
|
||||
def call(command, distro, bracket, message, log=0, do_exit=0, code=os.EX_OK, shell=False):
|
||||
# Fix missing /usr/local/CyberPanel/bin/python on install/upgrade (avoid FileNotFoundError)
|
||||
if isinstance(command, str) and '/usr/local/CyberPanel/bin/python' in command:
|
||||
if not os.path.isfile('/usr/local/CyberPanel/bin/python'):
|
||||
fallback = '/usr/bin/python3' if os.path.isfile('/usr/bin/python3') else '/usr/local/bin/python3'
|
||||
if os.path.isfile(fallback):
|
||||
command = command.replace('/usr/local/CyberPanel/bin/python', fallback, 1)
|
||||
shell = True
|
||||
return install_utils.call(command, distro, bracket, message, log, do_exit, code, shell)
|
||||
|
||||
def checkIfSeLinuxDisabled(self):
|
||||
@@ -3264,7 +3271,36 @@ password="%s"
|
||||
|
||||
logging.InstallLog.writeToFile("settings.py updated!")
|
||||
|
||||
# self.setupVirtualEnv(self.distro)
|
||||
# Create Python venv at /usr/local/CyberCP if missing (install.py run from temp dir does not run venvsetup.sh)
|
||||
if not os.path.exists("/usr/local/CyberCP/bin/python"):
|
||||
logging.InstallLog.writeToFile("Creating Python virtual environment at /usr/local/CyberCP...")
|
||||
preFlightsChecks.stdOut("Creating Python virtual environment...")
|
||||
try:
|
||||
r = subprocess.run(
|
||||
[sys.executable or "python3", "-m", "venv", "/usr/local/CyberCP"],
|
||||
timeout=120, capture_output=True, text=True, cwd="/usr/local/CyberCP"
|
||||
)
|
||||
if r.returncode != 0:
|
||||
logging.InstallLog.writeToFile("venv create stderr: " + (r.stderr or "")[:500])
|
||||
if r.returncode == 0 and os.path.exists("/usr/local/CyberCP/bin/pip"):
|
||||
req_file = "/usr/local/CyberCP/requirments.txt"
|
||||
if not os.path.exists(req_file):
|
||||
req_file = "/usr/local/CyberCP/requirements.txt"
|
||||
if os.path.exists(req_file):
|
||||
subprocess.run(
|
||||
["/usr/local/CyberCP/bin/pip", "install", "-r", req_file, "--quiet"],
|
||||
timeout=600, cwd="/usr/local/CyberCP", capture_output=True
|
||||
)
|
||||
else:
|
||||
subprocess.run(
|
||||
["/usr/local/CyberCP/bin/pip", "install", "Django", "PyMySQL", "requests", "cryptography", "psutil", "--quiet"],
|
||||
timeout=180, cwd="/usr/local/CyberCP", capture_output=True
|
||||
)
|
||||
if os.path.exists("/usr/local/CyberCP/bin/python"):
|
||||
logging.InstallLog.writeToFile("Virtual environment created successfully")
|
||||
preFlightsChecks.stdOut("Virtual environment created", 1)
|
||||
except Exception as e:
|
||||
logging.InstallLog.writeToFile("Venv create warning: " + str(e))
|
||||
|
||||
# Now run Django migrations since we're in /usr/local/CyberCP and database exists
|
||||
os.chdir("/usr/local/CyberCP")
|
||||
@@ -3300,46 +3336,70 @@ password="%s"
|
||||
|
||||
logging.InstallLog.writeToFile("Migration cleanup completed")
|
||||
|
||||
# Ensure virtual environment is properly set up
|
||||
logging.InstallLog.writeToFile("Ensuring virtual environment is properly set up...")
|
||||
# Ensure virtual environment or system Python is available
|
||||
logging.InstallLog.writeToFile("Ensuring Python is available for migrations...")
|
||||
if not self.ensureVirtualEnvironmentSetup():
|
||||
logging.InstallLog.writeToFile("ERROR: Virtual environment setup failed!", 0)
|
||||
preFlightsChecks.stdOut("ERROR: Virtual environment setup failed!", 0)
|
||||
return False
|
||||
logging.InstallLog.writeToFile("WARNING: No venv found; will try system Python", 1)
|
||||
|
||||
# Find the correct Python virtual environment path
|
||||
# Find Python: use only system Python or CyberCP venv (never /usr/local/CyberPanel - often missing on fresh install)
|
||||
python_paths = [
|
||||
"/usr/local/CyberPanel/bin/python",
|
||||
"/usr/local/CyberCP/bin/python",
|
||||
"/usr/local/CyberPanel-venv/bin/python"
|
||||
"/usr/bin/python3",
|
||||
"/usr/local/bin/python3",
|
||||
]
|
||||
|
||||
if sys.executable and sys.executable not in python_paths:
|
||||
python_paths.append(sys.executable)
|
||||
# Only add venv if it exists (avoid FileNotFoundError)
|
||||
if os.path.isfile("/usr/local/CyberCP/bin/python"):
|
||||
python_paths.append("/usr/local/CyberCP/bin/python")
|
||||
|
||||
python_path = None
|
||||
for path in python_paths:
|
||||
if os.path.exists(path):
|
||||
python_path = path
|
||||
logging.InstallLog.writeToFile(f"Found Python virtual environment at: {path}")
|
||||
break
|
||||
|
||||
if not path:
|
||||
continue
|
||||
try:
|
||||
# Skip broken symlinks: resolve and require executable file
|
||||
if os.path.lexists(path):
|
||||
resolved = os.path.realpath(path)
|
||||
if not os.path.isfile(resolved) or not os.access(resolved, os.X_OK):
|
||||
continue
|
||||
elif not os.path.isfile(path) or not os.access(path, os.X_OK):
|
||||
continue
|
||||
except OSError:
|
||||
continue
|
||||
try:
|
||||
r = subprocess.run([path, "--version"], capture_output=True, text=True, timeout=5)
|
||||
if r.returncode == 0:
|
||||
python_path = path
|
||||
logging.InstallLog.writeToFile(f"Using Python at: {path}")
|
||||
break
|
||||
except (FileNotFoundError, OSError, subprocess.SubprocessError):
|
||||
continue
|
||||
|
||||
if not python_path:
|
||||
logging.InstallLog.writeToFile("ERROR: No Python virtual environment found!", 0)
|
||||
preFlightsChecks.stdOut("ERROR: No Python virtual environment found!", 0)
|
||||
logging.InstallLog.writeToFile("ERROR: No working Python found for migrations!", 0)
|
||||
preFlightsChecks.stdOut("ERROR: No working Python found!", 0)
|
||||
return False
|
||||
|
||||
manage_py = "/usr/local/CyberCP/manage.py"
|
||||
if not os.path.isfile(manage_py):
|
||||
logging.InstallLog.writeToFile("ERROR: %s not found" % manage_py, 0)
|
||||
preFlightsChecks.stdOut("ERROR: manage.py not found at %s" % manage_py, 0)
|
||||
return False
|
||||
|
||||
# Create migrations in dependency order - loginSystem first since other apps depend on it
|
||||
logging.InstallLog.writeToFile("Creating migrations for loginSystem first...")
|
||||
command = f"{python_path} manage.py makemigrations loginSystem --noinput"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
|
||||
command = f"{python_path} {manage_py} makemigrations loginSystem --noinput"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
# Now create migrations for all other apps
|
||||
logging.InstallLog.writeToFile("Creating migrations for all other apps...")
|
||||
command = f"{python_path} manage.py makemigrations --noinput"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
|
||||
command = f"{python_path} {manage_py} makemigrations --noinput"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
# Apply all migrations
|
||||
logging.InstallLog.writeToFile("Applying all migrations...")
|
||||
command = f"{python_path} manage.py migrate --noinput"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
|
||||
command = f"{python_path} {manage_py} migrate --noinput"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
logging.InstallLog.writeToFile("Django migrations completed successfully!")
|
||||
preFlightsChecks.stdOut("Django migrations completed successfully!")
|
||||
@@ -3350,8 +3410,8 @@ password="%s"
|
||||
# Download CDN libraries before collectstatic runs
|
||||
self.downloadCDNLibraries()
|
||||
|
||||
command = f"{python_path} manage.py collectstatic --noinput --clear"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
|
||||
command = f"{python_path} {manage_py} collectstatic --noinput --clear"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
## Moving static content to lscpd location
|
||||
command = 'mv static /usr/local/CyberCP/public/'
|
||||
@@ -4956,8 +5016,8 @@ user_query = SELECT email as user, password, 'vmail' as uid, 'vmail' as gid, '/h
|
||||
|
||||
# Determine the correct Python path
|
||||
python_paths = [
|
||||
"/usr/local/CyberPanel/bin/python",
|
||||
"/usr/local/CyberCP/bin/python",
|
||||
"/usr/local/CyberPanel/bin/python",
|
||||
"/usr/bin/python3",
|
||||
"/usr/local/bin/python3"
|
||||
]
|
||||
@@ -5026,7 +5086,7 @@ user_query = SELECT email as user, password, 'vmail' as uid, 'vmail' as gid, '/h
|
||||
def ensureVirtualEnvironmentSetup(self):
|
||||
"""Ensure virtual environment is properly set up and accessible"""
|
||||
try:
|
||||
# Check multiple possible virtual environment locations
|
||||
# Check multiple possible virtual environment locations (prefer CyberCP - app path)
|
||||
venv_paths = [
|
||||
'/usr/local/CyberCP/bin/python',
|
||||
'/usr/local/CyberPanel/bin/python',
|
||||
@@ -5077,8 +5137,8 @@ user_query = SELECT email as user, password, 'vmail' as uid, 'vmail' as gid, '/h
|
||||
|
||||
# Determine the correct Python path
|
||||
python_paths = [
|
||||
"/usr/local/CyberPanel/bin/python",
|
||||
"/usr/local/CyberCP/bin/python",
|
||||
"/usr/local/CyberPanel/bin/python",
|
||||
"/usr/bin/python3",
|
||||
"/usr/local/bin/python3"
|
||||
]
|
||||
@@ -5292,12 +5352,36 @@ user_query = SELECT email as user, password, 'vmail' as uid, 'vmail' as gid, '/h
|
||||
def install_default_keys(self):
|
||||
try:
|
||||
path = "/root/.ssh"
|
||||
key_path = "/root/.ssh/cyberpanel"
|
||||
key_pub = "/root/.ssh/cyberpanel.pub"
|
||||
|
||||
if not os.path.exists(path):
|
||||
os.mkdir(path)
|
||||
|
||||
# Remove existing key files so ssh-keygen never prompts "Overwrite (y/n)?"
|
||||
# Use shell rm -f so removal is reliable (avoids os.remove permission/state issues)
|
||||
subprocess.run(
|
||||
"rm -f /root/.ssh/cyberpanel /root/.ssh/cyberpanel.pub",
|
||||
shell=True,
|
||||
timeout=5,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
# Run ssh-keygen with stdin=input so we never block on "Overwrite (y/n)?"
|
||||
command = "ssh-keygen -f /root/.ssh/cyberpanel -t rsa -N ''"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
preFlightsChecks.stdOut("Running: %s" % command, 1)
|
||||
res = subprocess.run(
|
||||
["ssh-keygen", "-f", "/root/.ssh/cyberpanel", "-t", "rsa", "-N", ""],
|
||||
stdin=subprocess.PIPE,
|
||||
input=b"y\n",
|
||||
timeout=30,
|
||||
capture_output=True,
|
||||
)
|
||||
if res.returncode != 0:
|
||||
err = (res.stderr or b"").decode("utf-8", errors="replace").strip()
|
||||
preFlightsChecks.stdOut("[ERROR] ssh-keygen failed (return %s). %s" % (res.returncode, err), 1)
|
||||
return 0
|
||||
preFlightsChecks.stdOut("Successfully ran: %s." % command, 1)
|
||||
|
||||
except BaseException as msg:
|
||||
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [install_default_keys]")
|
||||
@@ -5687,11 +5771,22 @@ milter_default_action = accept
|
||||
|
||||
os.chdir(self.cwd)
|
||||
|
||||
command = "chmod +x composer.sh"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
# Download composer.sh to a known path so chmod never fails with "cannot access 'composer.sh'"
|
||||
composer_sh = "/tmp/composer.sh"
|
||||
if not os.path.isfile(composer_sh):
|
||||
command = "wget -q https://cyberpanel.sh/composer.sh -O " + composer_sh
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
if not os.path.isfile(composer_sh):
|
||||
command = "curl -sSL https://cyberpanel.sh/composer.sh -o " + composer_sh
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
|
||||
command = "./composer.sh"
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
if os.path.isfile(composer_sh):
|
||||
command = "chmod +x " + composer_sh
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
command = "bash " + composer_sh
|
||||
preFlightsChecks.call(command, self.distro, "composer.sh", command, 1, 0, os.EX_OSERR, True)
|
||||
else:
|
||||
logging.InstallLog.writeToFile("composer.sh download failed, skipping [setupPHPAndComposer]", 0)
|
||||
|
||||
except OSError as msg:
|
||||
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [setupPHPAndComposer]")
|
||||
@@ -6159,8 +6254,10 @@ vmail
|
||||
dnsPath = "/etc/powerdns/pdns.conf"
|
||||
os.makedirs("/etc/powerdns", exist_ok=True)
|
||||
|
||||
# Copy the PowerDNS configuration file
|
||||
# Copy the PowerDNS configuration file (cwd may be temp dir with install/ subdir)
|
||||
source_file = os.path.join(self.cwd, "dns-one", "pdns.conf")
|
||||
if not os.path.exists(source_file):
|
||||
source_file = os.path.join(self.cwd, "install", "dns-one", "pdns.conf")
|
||||
if not os.path.exists(source_file):
|
||||
source_file = os.path.join(self.cwd, "dns", "pdns.conf")
|
||||
|
||||
@@ -6474,12 +6571,12 @@ def main():
|
||||
parser.add_argument('--mysqluser', help='MySQL user if remote is chosen.')
|
||||
parser.add_argument('--mysqlpassword', help='MySQL password if remote is chosen.')
|
||||
parser.add_argument('--mysqlport', help='MySQL port if remote is chosen.')
|
||||
parser.add_argument('--mariadb-version', default='11.8', help='MariaDB version: 11.8 (LTS, default) or 12.1')
|
||||
parser.add_argument('--mariadb-version', default='11.8', help='MariaDB version: 10.11, 11.8 (LTS, default) or 12.1')
|
||||
|
||||
args = parser.parse_args()
|
||||
# Normalize and validate MariaDB version choice (default 11.8)
|
||||
mariadb_ver = (getattr(args, 'mariadb_version', None) or '11.8').strip()
|
||||
if mariadb_ver not in ('11.8', '12.1'):
|
||||
if mariadb_ver not in ('10.11', '11.8', '12.1'):
|
||||
mariadb_ver = '11.8'
|
||||
preFlightsChecks.mariadb_version = mariadb_ver
|
||||
|
||||
|
||||
@@ -85,15 +85,15 @@ class InstallCyberPanel:
|
||||
command = "dnf clean all"
|
||||
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
|
||||
# Install MariaDB from official repository
|
||||
self.stdOut("Setting up official MariaDB repository...", 1)
|
||||
command = "curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash -s -- --mariadb-server-version='10.11'"
|
||||
# Install MariaDB from official repository (11.8 LTS for el9 to avoid client dependency issues)
|
||||
self.stdOut("Setting up official MariaDB repository (11.8 LTS)...", 1)
|
||||
command = "curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash -s -- --mariadb-server-version='11.8'"
|
||||
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
|
||||
# Install MariaDB packages
|
||||
# Install MariaDB packages (server + client from same repo to satisfy dependencies)
|
||||
self.stdOut("Installing MariaDB packages...", 1)
|
||||
mariadb_packages = "MariaDB-server MariaDB-client MariaDB-backup MariaDB-devel"
|
||||
command = f"dnf install -y {mariadb_packages}"
|
||||
command = f"dnf install -y --nobest {mariadb_packages}"
|
||||
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
|
||||
self.stdOut("AlmaLinux 9 MariaDB fixes completed", 1)
|
||||
@@ -902,7 +902,7 @@ gpgcheck=1
|
||||
|
||||
else:
|
||||
|
||||
command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=10.11'
|
||||
command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=11.8'
|
||||
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
command = 'yum remove mariadb* -y'
|
||||
@@ -922,7 +922,27 @@ gpgcheck=1
|
||||
command = 'dnf clean all'
|
||||
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
command = 'dnf install MariaDB-server MariaDB-client MariaDB-backup -y'
|
||||
# Allow MariaDB-server to be installed: remove from dnf exclude if present
|
||||
dnf_conf = '/etc/dnf/dnf.conf'
|
||||
if os.path.exists(dnf_conf):
|
||||
try:
|
||||
with open(dnf_conf, 'r') as f:
|
||||
dnf_content = f.read()
|
||||
if 'MariaDB-server' in dnf_content and 'exclude=' in dnf_content:
|
||||
new_content = re.sub(
|
||||
r'(exclude=[^\n]*)',
|
||||
lambda m: re.sub(r'\bMariaDB-server\*?\s*', '', m.group(1)).strip(),
|
||||
dnf_content
|
||||
)
|
||||
if new_content != dnf_content:
|
||||
with open(dnf_conf, 'w') as f:
|
||||
f.write(new_content)
|
||||
install_utils.writeToFile("Removed MariaDB-server from dnf exclude for installation")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Use --nobest so server+client resolve from same repo (avoids AlmaLinux 9 dependency conflict)
|
||||
command = 'dnf install -y --nobest MariaDB-server MariaDB-client MariaDB-backup'
|
||||
|
||||
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
|
||||
|
||||
|
||||
@@ -548,6 +548,26 @@ def call(command, distro, bracket, message, log=0, do_exit=0, code=os.EX_OK, she
|
||||
Returns:
|
||||
bool: True if successful, False if failed
|
||||
"""
|
||||
# CRITICAL (first): Replace missing CyberPanel Python so old/cached installers never hit FileNotFoundError
|
||||
if isinstance(command, str):
|
||||
bad_path = '/usr/local/CyberPanel/bin/python'
|
||||
if bad_path in command and not os.path.isfile(bad_path):
|
||||
fallback = '/usr/bin/python3'
|
||||
if not os.path.isfile(fallback):
|
||||
fallback = '/usr/local/bin/python3'
|
||||
if os.path.isfile(fallback):
|
||||
command = command.replace(bad_path, fallback)
|
||||
shell = True
|
||||
# Use /tmp/composer.sh when command references relative composer.sh (avoids "chmod: cannot access 'composer.sh'")
|
||||
# Only replace local file refs, not URLs (e.g. https://cyberpanel.sh/composer.sh)
|
||||
if not os.path.isfile(os.path.join(os.getcwd(), 'composer.sh')):
|
||||
if './composer.sh' in command:
|
||||
command = command.replace('./composer.sh', '/tmp/composer.sh')
|
||||
shell = True
|
||||
elif ' composer.sh' in command and 'http' not in command.split('composer.sh')[0][-20:]:
|
||||
command = command.replace(' composer.sh', ' /tmp/composer.sh')
|
||||
shell = True
|
||||
|
||||
# Check for apt lock before running apt commands
|
||||
if 'apt-get' in command or 'apt ' in command:
|
||||
if not wait_for_apt_lock():
|
||||
@@ -557,8 +577,8 @@ def call(command, distro, bracket, message, log=0, do_exit=0, code=os.EX_OK, she
|
||||
return False
|
||||
|
||||
# CRITICAL: Use shell=True for commands with shell metacharacters
|
||||
# Avoids "No matching repo to modify: 2>/dev/null, true, ||" when shlex.split splits them
|
||||
if not shell and any(x in command for x in (' || ', ' 2>/dev', ' 2>', ' | ', '; true', '|| true')):
|
||||
# Avoids "No matching repo to modify: 2>/dev/null, true, ||" and "Could not resolve host: |" when shlex.split splits them
|
||||
if not shell and (any(x in command for x in (' || ', ' 2>/dev', ' 2>', ' | ', '; true', '|| true')) or '|' in command):
|
||||
shell = True
|
||||
|
||||
# CRITICAL: For mysql/mariadb commands, always use shell=True and full binary path
|
||||
@@ -577,10 +597,26 @@ def call(command, distro, bracket, message, log=0, do_exit=0, code=os.EX_OK, she
|
||||
stdOut(finalMessage, log)
|
||||
count = 0
|
||||
while True:
|
||||
if shell:
|
||||
res = subprocess.call(command, shell=True)
|
||||
else:
|
||||
res = subprocess.call(shlex.split(command))
|
||||
try:
|
||||
if shell:
|
||||
res = subprocess.call(command, shell=True)
|
||||
else:
|
||||
res = subprocess.call(shlex.split(command))
|
||||
except FileNotFoundError as e:
|
||||
# Old installer may pass /usr/local/CyberPanel/bin/python; retry with system python once
|
||||
if isinstance(command, str) and '/usr/local/CyberPanel/bin/python' in command:
|
||||
fallback = '/usr/bin/python3'
|
||||
if not os.path.isfile(fallback):
|
||||
fallback = '/usr/local/bin/python3'
|
||||
if os.path.isfile(fallback):
|
||||
command = command.replace('/usr/local/CyberPanel/bin/python', fallback)
|
||||
shell = True
|
||||
stdOut("Retrying with %s (CyberPanel python missing)" % fallback, log)
|
||||
res = subprocess.call(command, shell=True)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
|
||||
if resFailed(distro, res):
|
||||
count = count + 1
|
||||
|
||||
@@ -12,6 +12,8 @@ gracefulRestartTimeout 300
|
||||
mime $SERVER_ROOT/conf/mime.properties
|
||||
showVersionNumber 0
|
||||
adminEmails root@localhost
|
||||
autoSSL 1
|
||||
acmeEmail admin@cyberpanel.net
|
||||
adminRoot $SERVER_ROOT/admin/
|
||||
|
||||
errorlog $SERVER_ROOT/logs/error.log {
|
||||
|
||||
51
install/ols_binaries_config.py
Normal file
51
install/ols_binaries_config.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
OpenLiteSpeed binary configuration - single source of truth for OLS/ModSec URLs and SHA256 hashes.
|
||||
Used by install/install.py, plogical/upgrade.py, and plogical/modSec.py.
|
||||
Update this file when new OLS binaries are released (e.g. v2.4.5).
|
||||
"""
|
||||
# OpenLiteSpeed v2.4.4 - Universal binaries with PHPConfig API, Origin Header Forwarding,
|
||||
# ReadApacheConf with Portmap, Auto-SSL ACME v2, ModSecurity ABI compatibility.
|
||||
# Updated Feb 2026: SSL listener auto-map fix, default VHost wildcard fix.
|
||||
|
||||
BINARY_CONFIGS = {
|
||||
'rhel8': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-2.4.4-x86_64-rhel8',
|
||||
'sha256': 'd08512da7a77468c09d6161de858db60bcc29aed7ce0abf76dca1c72104dc485',
|
||||
'module_url': 'https://cyberpanel.net/cyberpanel_ols-2.4.4-x86_64-rhel8.so',
|
||||
'module_sha256': '27f7fbbb74e83c217708960d4b18e2732b0798beecba8ed6eac01509165cb432',
|
||||
'modsec_url': 'https://cyberpanel.net/mod_security-2.4.4-x86_64-rhel8.so',
|
||||
'modsec_sha256': 'bbbf003bdc7979b98f09b640dffe2cbbe5f855427f41319e4c121403c05837b2',
|
||||
},
|
||||
'rhel9': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-2.4.4-x86_64-rhel9',
|
||||
'sha256': '418d2ea06e29c0f847a2e6cf01f7641d5fb72b65a04e27a8f6b3b54d673cc2df',
|
||||
'module_url': 'https://cyberpanel.net/cyberpanel_ols-2.4.4-x86_64-rhel9.so',
|
||||
'module_sha256': '50cb00fa2b8269ec9b0bf300f1b26d3b76d3791c1b022343e1290a0d25e7fda8',
|
||||
'modsec_url': 'https://cyberpanel.net/mod_security-2.4.4-x86_64-rhel9.so',
|
||||
'modsec_sha256': '19deb2ffbaf1334cf4ce4d46d53f747a75b29e835bf5a01f91ebcc0c78e98629',
|
||||
},
|
||||
'ubuntu': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-2.4.4-x86_64-ubuntu',
|
||||
'sha256': '60edf815379c32705540ad4525ea6d07c0390cabca232b6be12376ee538f4b1b',
|
||||
'module_url': 'https://cyberpanel.net/cyberpanel_ols-2.4.4-x86_64-ubuntu.so',
|
||||
'module_sha256': 'bd47069d13bb098201f3e72d4d56876193c898ebfa0ac2eb26796abebc991a88',
|
||||
'modsec_url': 'https://cyberpanel.net/mod_security-2.4.4-x86_64-ubuntu.so',
|
||||
'modsec_sha256': 'ed02c813136720bd4b9de5925f6e41bdc8392e494d7740d035479aaca6d1e0cd',
|
||||
},
|
||||
}
|
||||
|
||||
# For plogical/modSec.py - compatible ModSecurity binaries (same as BINARY_CONFIGS modsec_*)
|
||||
MODSEC_COMPATIBLE = {
|
||||
'rhel8': {
|
||||
'url': 'https://cyberpanel.net/mod_security-2.4.4-x86_64-rhel8.so',
|
||||
'sha256': 'bbbf003bdc7979b98f09b640dffe2cbbe5f855427f41319e4c121403c05837b2',
|
||||
},
|
||||
'rhel9': {
|
||||
'url': 'https://cyberpanel.net/mod_security-2.4.4-x86_64-rhel9.so',
|
||||
'sha256': '19deb2ffbaf1334cf4ce4d46d53f747a75b29e835bf5a01f91ebcc0c78e98629',
|
||||
},
|
||||
'ubuntu': {
|
||||
'url': 'https://cyberpanel.net/mod_security-2.4.4-x86_64-ubuntu.so',
|
||||
'sha256': 'ed02c813136720bd4b9de5925f6e41bdc8392e494d7740d035479aaca6d1e0cd',
|
||||
},
|
||||
}
|
||||
@@ -101,19 +101,24 @@ class ApplicationInstaller(multi.Thread):
|
||||
|
||||
@staticmethod
|
||||
def setupComposer():
|
||||
|
||||
if os.path.exists('composer.sh'):
|
||||
os.remove('composer.sh')
|
||||
|
||||
composer_sh = '/tmp/composer.sh'
|
||||
if not os.path.exists('/usr/bin/composer'):
|
||||
command = "wget https://cyberpanel.sh/composer.sh"
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
|
||||
command = "chmod +x composer.sh"
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
|
||||
command = "./composer.sh"
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
try:
|
||||
if os.path.exists(composer_sh):
|
||||
os.remove(composer_sh)
|
||||
command = "wget -q https://cyberpanel.sh/composer.sh -O " + composer_sh
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
if not os.path.isfile(composer_sh):
|
||||
command = "curl -sSL https://cyberpanel.sh/composer.sh -o " + composer_sh
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
if not os.path.isfile(composer_sh):
|
||||
return
|
||||
command = "chmod +x " + composer_sh
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
command = "bash " + composer_sh
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def InstallNodeJS(self):
|
||||
|
||||
|
||||
@@ -1412,6 +1412,13 @@ class backupUtilities:
|
||||
if os.path.exists('/root/.ssh/cyberpanel.pub'):
|
||||
pass
|
||||
else:
|
||||
# Remove existing key files so ssh-keygen never prompts "Overwrite (y/n)?"
|
||||
for f in ('/root/.ssh/cyberpanel', '/root/.ssh/cyberpanel.pub'):
|
||||
if os.path.exists(f):
|
||||
try:
|
||||
os.remove(f)
|
||||
except OSError:
|
||||
pass
|
||||
command = "ssh-keygen -f /root/.ssh/cyberpanel -t rsa -N ''"
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import sys
|
||||
sys.path.append('/usr/local/CyberCP')
|
||||
_install_dir = '/usr/local/CyberCP/install'
|
||||
if _install_dir not in sys.path:
|
||||
sys.path.insert(0, _install_dir)
|
||||
import ols_binaries_config
|
||||
from plogical import CyberCPLogFileWriter as logging
|
||||
import subprocess
|
||||
import shlex
|
||||
@@ -18,22 +22,8 @@ class modSec:
|
||||
tempRulesFile = "/home/cyberpanel/tempModSecRules"
|
||||
mirrorPath = "cyberpanel.net"
|
||||
|
||||
# Compatible ModSecurity binaries (built against custom OLS headers)
|
||||
# These prevent ABI incompatibility crashes (Signal 11/SIGSEGV)
|
||||
MODSEC_COMPATIBLE = {
|
||||
'rhel8': {
|
||||
'url': 'https://cyberpanel.net/mod_security-compatible-rhel8.so',
|
||||
'sha256': 'bbbf003bdc7979b98f09b640dffe2cbbe5f855427f41319e4c121403c05837b2'
|
||||
},
|
||||
'rhel9': {
|
||||
'url': 'https://cyberpanel.net/mod_security-compatible-rhel.so',
|
||||
'sha256': '19deb2ffbaf1334cf4ce4d46d53f747a75b29e835bf5a01f91ebcc0c78e98629'
|
||||
},
|
||||
'ubuntu': {
|
||||
'url': 'https://cyberpanel.net/mod_security-compatible-ubuntu.so',
|
||||
'sha256': 'ed02c813136720bd4b9de5925f6e41bdc8392e494d7740d035479aaca6d1e0cd'
|
||||
}
|
||||
}
|
||||
# Compatible ModSecurity binaries (from ols_binaries_config - v2.4.4)
|
||||
MODSEC_COMPATIBLE = ols_binaries_config.MODSEC_COMPATIBLE
|
||||
|
||||
@staticmethod
|
||||
def detectPlatform():
|
||||
|
||||
@@ -8,6 +8,10 @@ import grp
|
||||
import re
|
||||
|
||||
sys.path.append('/usr/local/CyberCP')
|
||||
_install_dir = '/usr/local/CyberCP/install'
|
||||
if _install_dir not in sys.path:
|
||||
sys.path.insert(0, _install_dir)
|
||||
import ols_binaries_config
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
|
||||
from plogical.errorSanitizer import ErrorSanitizer
|
||||
from plogical.installUtilities import installUtilities
|
||||
@@ -966,36 +970,7 @@ class Upgrade:
|
||||
platform = Upgrade.detectPlatform()
|
||||
Upgrade.stdOut(f"Detected platform: {platform}", 0)
|
||||
|
||||
# Platform-specific URLs and checksums (OpenLiteSpeed 1.8.5+ preferred from repo; fallback static build)
|
||||
# Module Build Date: December 28, 2025 - v2.2.0 Brute Force with Progressive Throttle
|
||||
BINARY_CONFIGS = {
|
||||
'rhel8': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel8-static',
|
||||
'sha256': '6ce688a237615102cc1603ee1999b3cede0ff3482d31e1f65705e92396d34b3a',
|
||||
'module_url': 'https://cyberpanel.net/binaries/rhel8/cyberpanel_ols.so',
|
||||
'module_sha256': '7c33d89c7fbcd3ed7b0422fee3f49b5e041713c2c2b7316a5774f6defa147572',
|
||||
'modsec_url': 'https://cyberpanel.net/mod_security-compatible-rhel8.so',
|
||||
'modsec_sha256': 'bbbf003bdc7979b98f09b640dffe2cbbe5f855427f41319e4c121403c05837b2'
|
||||
},
|
||||
'rhel9': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel9-static',
|
||||
'sha256': '709093d99d5d3e789134c131893614968e17eefd9ade2200f811d9b076b2f02e',
|
||||
'module_url': 'https://cyberpanel.net/binaries/rhel9/cyberpanel_ols.so',
|
||||
'module_sha256': 'ae65337e2d13babc0c675bb4264d469daffa2efb7627c9bf39ac59e42e3ebede',
|
||||
'modsec_url': 'https://cyberpanel.net/mod_security-compatible-rhel.so',
|
||||
'modsec_sha256': '19deb2ffbaf1334cf4ce4d46d53f747a75b29e835bf5a01f91ebcc0c78e98629'
|
||||
},
|
||||
'ubuntu': {
|
||||
'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-ubuntu-static',
|
||||
'sha256': '89aaf66474e78cb3c1666784e0e7a417550bd317e6ab148201bdc318d36710cb',
|
||||
'module_url': 'https://cyberpanel.net/binaries/ubuntu/cyberpanel_ols.so',
|
||||
'module_sha256': '62978ede1f174dd2885e5227a3d9cc463d0c27acd77cfc23743d7309ee0c54ea',
|
||||
'modsec_url': 'https://cyberpanel.net/mod_security-compatible-ubuntu.so',
|
||||
'modsec_sha256': 'ed02c813136720bd4b9de5925f6e41bdc8392e494d7740d035479aaca6d1e0cd'
|
||||
}
|
||||
}
|
||||
|
||||
config = BINARY_CONFIGS.get(platform)
|
||||
config = ols_binaries_config.BINARY_CONFIGS.get(platform)
|
||||
if not config:
|
||||
Upgrade.stdOut(f"ERROR: No binaries available for platform {platform}", 0)
|
||||
Upgrade.stdOut("Skipping custom binary installation", 0)
|
||||
@@ -1147,6 +1122,23 @@ class Upgrade:
|
||||
Upgrade.stdOut("=" * 50, 0)
|
||||
# Configure module after installation
|
||||
Upgrade.configureCustomModule()
|
||||
# Enable Auto-SSL if not already configured
|
||||
conf_path = '/usr/local/lsws/conf/httpd_config.conf'
|
||||
try:
|
||||
with open(conf_path, 'r') as f:
|
||||
content = f.read()
|
||||
if 'autoSSL' not in content:
|
||||
content = re.sub(
|
||||
r'(adminEmails\s+\S+)',
|
||||
r'\1\nautoSSL 1\nacmeEmail admin@cyberpanel.net',
|
||||
content,
|
||||
count=1
|
||||
)
|
||||
with open(conf_path, 'w') as f:
|
||||
f.write(content)
|
||||
Upgrade.stdOut("Auto-SSL enabled in httpd_config.conf", 0)
|
||||
except Exception as e:
|
||||
Upgrade.stdOut(f"WARNING: Could not enable Auto-SSL: {e}", 0)
|
||||
return True
|
||||
|
||||
Upgrade.stdOut("ERROR: Installation verification failed", 0)
|
||||
@@ -1307,18 +1299,26 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
|
||||
|
||||
@staticmethod
|
||||
def setupComposer():
|
||||
|
||||
if os.path.exists('composer.sh'):
|
||||
os.remove('composer.sh')
|
||||
|
||||
command = "wget https://cyberpanel.sh/composer.sh"
|
||||
Upgrade.executioner(command, 0)
|
||||
|
||||
command = "chmod +x composer.sh"
|
||||
Upgrade.executioner(command, 0)
|
||||
|
||||
command = "./composer.sh"
|
||||
Upgrade.executioner(command, 0)
|
||||
composer_sh = '/tmp/composer.sh'
|
||||
try:
|
||||
if os.path.exists(composer_sh):
|
||||
os.remove(composer_sh)
|
||||
# Download to known path so chmod/run work regardless of cwd
|
||||
command = "wget -q https://cyberpanel.sh/composer.sh -O " + composer_sh
|
||||
Upgrade.executioner(command, 0)
|
||||
if not os.path.isfile(composer_sh):
|
||||
command = "curl -sSL https://cyberpanel.sh/composer.sh -o " + composer_sh
|
||||
Upgrade.executioner(command, 0)
|
||||
if not os.path.isfile(composer_sh):
|
||||
Upgrade.stdOut("composer.sh download failed, skipping", 0)
|
||||
return
|
||||
command = "chmod +x " + composer_sh
|
||||
Upgrade.executioner(command, 0)
|
||||
command = "bash " + composer_sh
|
||||
Upgrade.executioner(command, 0)
|
||||
except Exception as e:
|
||||
ErrorSanitizer.log_error_securely(e, 'setupComposer')
|
||||
Upgrade.stdOut("setupComposer error (non-fatal)", 0)
|
||||
|
||||
@staticmethod
|
||||
def downoad_and_install_raindloop():
|
||||
@@ -1715,8 +1715,8 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
|
||||
cwd = os.getcwd()
|
||||
|
||||
os.chdir('/usr/local/CyberCP')
|
||||
|
||||
command = '/usr/local/CyberPanel/bin/python manage.py collectstatic --noinput --clear'
|
||||
py = Upgrade._python_for_manage()
|
||||
command = py + ' manage.py collectstatic --noinput --clear'
|
||||
Upgrade.executioner(command, 'Remove old static content', 0)
|
||||
|
||||
os.chdir(cwd)
|
||||
@@ -3084,18 +3084,27 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
|
||||
except:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _python_for_manage():
|
||||
"""Resolve Python for manage.py (avoid FileNotFoundError when /usr/local/CyberPanel/bin/python missing)."""
|
||||
for path in ('/usr/local/CyberPanel/bin/python', '/usr/local/CyberCP/bin/python', '/usr/bin/python3', '/usr/local/bin/python3'):
|
||||
if path and os.path.isfile(path) and os.access(path, os.X_OK):
|
||||
return path
|
||||
return '/usr/bin/python3'
|
||||
|
||||
@staticmethod
|
||||
def GeneralMigrations():
|
||||
try:
|
||||
|
||||
cwd = os.getcwd()
|
||||
os.chdir('/usr/local/CyberCP')
|
||||
py = Upgrade._python_for_manage()
|
||||
|
||||
command = '/usr/local/CyberPanel/bin/python manage.py makemigrations'
|
||||
command = py + ' manage.py makemigrations'
|
||||
Upgrade.executioner(command, 'python manage.py makemigrations', 0)
|
||||
|
||||
command = '/usr/local/CyberPanel/bin/python manage.py makemigrations'
|
||||
Upgrade.executioner(command, '/usr/local/CyberPanel/bin/python manage.py migrate', 0)
|
||||
command = py + ' manage.py makemigrations'
|
||||
Upgrade.executioner(command, py + ' manage.py migrate', 0)
|
||||
|
||||
os.chdir(cwd)
|
||||
|
||||
@@ -4606,21 +4615,7 @@ echo $oConfig->Save() ? 'Done' : 'Error';
|
||||
"""Upgrade pip to latest version for better package compatibility"""
|
||||
try:
|
||||
Upgrade.stdOut("Upgrading pip to latest version...", 1)
|
||||
|
||||
# Determine the correct Python path
|
||||
python_paths = [
|
||||
"/usr/local/CyberPanel/bin/python",
|
||||
"/usr/local/CyberCP/bin/python",
|
||||
"/usr/bin/python3",
|
||||
"/usr/local/bin/python3"
|
||||
]
|
||||
|
||||
python_path = None
|
||||
for path in python_paths:
|
||||
if os.path.exists(path):
|
||||
python_path = path
|
||||
break
|
||||
|
||||
python_path = Upgrade._python_for_manage()
|
||||
if not python_path:
|
||||
Upgrade.stdOut("No Python executable found for pip upgrade", 0)
|
||||
return False
|
||||
|
||||
965
tests/ols_feature_tests.sh
Executable file
965
tests/ols_feature_tests.sh
Executable file
@@ -0,0 +1,965 @@
|
||||
#!/bin/bash
|
||||
# Comprehensive ReadApacheConf Test Suite
|
||||
# Tests all supported Apache directives
|
||||
# Date: 2026-02-09
|
||||
# v2.0.0 - Phase 1: Live env tests (SSL, .htaccess, module) + Phase 2: ReadApacheConf (generates own SSL certs, backs up/restores config)
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
TOTAL=0
|
||||
ERRORS=""
|
||||
CONFIG_BACKUP=""
|
||||
|
||||
pass() {
|
||||
PASS=$((PASS + 1))
|
||||
TOTAL=$((TOTAL + 1))
|
||||
echo " PASS: $1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
FAIL=$((FAIL + 1))
|
||||
TOTAL=$((TOTAL + 1))
|
||||
ERRORS="${ERRORS}\n FAIL: $1"
|
||||
echo " FAIL: $1"
|
||||
}
|
||||
|
||||
check_log() {
|
||||
local pattern="$1"
|
||||
local desc="$2"
|
||||
if grep -qE "$pattern" /usr/local/lsws/logs/error.log 2>/dev/null; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc (pattern: $pattern)"
|
||||
fi
|
||||
}
|
||||
|
||||
check_log_not() {
|
||||
local pattern="$1"
|
||||
local desc="$2"
|
||||
if grep -qE "$pattern" /usr/local/lsws/logs/error.log 2>/dev/null; then
|
||||
fail "$desc (unexpected pattern found: $pattern)"
|
||||
else
|
||||
pass "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
check_http() {
|
||||
local url="$1"
|
||||
local host="$2"
|
||||
local expected_code="$3"
|
||||
local desc="$4"
|
||||
local code
|
||||
if [ -n "$host" ]; then
|
||||
code=$(curl -sk -o /dev/null -w "%{http_code}" -H "Host: $host" "$url" 2>/dev/null)
|
||||
else
|
||||
code=$(curl -sk -o /dev/null -w "%{http_code}" "$url" 2>/dev/null)
|
||||
fi
|
||||
if [ "$code" = "$expected_code" ]; then
|
||||
pass "$desc (HTTP $code)"
|
||||
else
|
||||
fail "$desc (expected $expected_code, got $code)"
|
||||
fi
|
||||
}
|
||||
|
||||
check_http_body() {
|
||||
local url="$1"
|
||||
local host="$2"
|
||||
local expected_body="$3"
|
||||
local desc="$4"
|
||||
local body
|
||||
body=$(curl -sk -H "Host: $host" "$url" 2>/dev/null)
|
||||
if echo "$body" | grep -q "$expected_body"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc (body does not contain '$expected_body')"
|
||||
fi
|
||||
}
|
||||
|
||||
check_http_header() {
|
||||
local url="$1"
|
||||
local host="$2"
|
||||
local header_pattern="$3"
|
||||
local desc="$4"
|
||||
local headers
|
||||
headers=$(curl -skI -H "Host: $host" "$url" 2>/dev/null)
|
||||
if echo "$headers" | grep -qi "$header_pattern"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc (header '$header_pattern' not found in response headers)"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_ols() {
|
||||
# Try systemd first (Plesk uses apache2.service, cPanel uses httpd.service)
|
||||
if [ -f /etc/systemd/system/apache2.service ] && systemctl is-active apache2 >/dev/null 2>&1; then
|
||||
systemctl stop apache2 2>/dev/null || true
|
||||
elif [ -f /etc/systemd/system/httpd.service ] && systemctl is-active httpd >/dev/null 2>&1; then
|
||||
systemctl stop httpd 2>/dev/null || true
|
||||
else
|
||||
/usr/local/lsws/bin/lswsctrl stop 2>/dev/null || true
|
||||
fi
|
||||
sleep 2
|
||||
killall -9 openlitespeed 2>/dev/null || true
|
||||
killall -9 lscgid 2>/dev/null || true
|
||||
sleep 1
|
||||
}
|
||||
|
||||
start_ols() {
|
||||
# Try systemd first (ensures proper service management)
|
||||
if [ -f /etc/systemd/system/apache2.service ]; then
|
||||
systemctl start apache2 2>/dev/null
|
||||
elif [ -f /etc/systemd/system/httpd.service ]; then
|
||||
systemctl start httpd 2>/dev/null
|
||||
else
|
||||
/usr/local/lsws/bin/lswsctrl start 2>/dev/null
|
||||
fi
|
||||
sleep 6
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "[Cleanup] Restoring original OLS configuration..."
|
||||
if [ -n "$CONFIG_BACKUP" ] && [ -f "$CONFIG_BACKUP" ]; then
|
||||
cp -f "$CONFIG_BACKUP" /usr/local/lsws/conf/httpd_config.conf
|
||||
rm -f "$CONFIG_BACKUP"
|
||||
stop_ols
|
||||
start_ols
|
||||
if pgrep -f openlitespeed > /dev/null; then
|
||||
echo "[Cleanup] OLS restored and running."
|
||||
else
|
||||
echo "[Cleanup] WARNING: OLS failed to restart after restore!"
|
||||
fi
|
||||
else
|
||||
echo "[Cleanup] No backup found, restoring log level only."
|
||||
sed -i 's/logLevel.*INFO/logLevel WARN/' /usr/local/lsws/conf/httpd_config.conf
|
||||
sed -i 's/logLevel.*DEBUG/logLevel WARN/' /usr/local/lsws/conf/httpd_config.conf
|
||||
fi
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo "OLS Feature Test Suite v2.0.0 (Phase 1: Live + Phase 2: ReadApacheConf)"
|
||||
echo "Date: $(date)"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
# ============================================================
|
||||
# PHASE 1: Live Environment Tests
|
||||
# Tests Auto-SSL, SSL listener mapping, cert serving,
|
||||
# .htaccess module, binary integrity, CyberPanel module
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "PHASE 1: Live Environment Tests"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
SERVER_IP="95.217.127.172"
|
||||
DOMAINS="apacheols-2.cyberpersons.com apacheols-3.cyberpersons.com apacheols-5.cyberpersons.com"
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 18: Binary Integrity ==="
|
||||
# ============================================================
|
||||
EXPECTED_HASH="60edf815379c32705540ad4525ea6d07c0390cabca232b6be12376ee538f4b1b"
|
||||
ACTUAL_HASH=$(sha256sum /usr/local/lsws/bin/openlitespeed | awk "{print \$1}")
|
||||
if [ "$ACTUAL_HASH" = "$EXPECTED_HASH" ]; then
|
||||
pass "T18.1: OLS binary SHA256 matches expected hash"
|
||||
else
|
||||
fail "T18.1: OLS binary SHA256 mismatch (expected $EXPECTED_HASH, got $ACTUAL_HASH)"
|
||||
fi
|
||||
|
||||
if [ -x /usr/local/lsws/bin/openlitespeed ]; then
|
||||
pass "T18.2: OLS binary is executable"
|
||||
else
|
||||
fail "T18.2: OLS binary is not executable"
|
||||
fi
|
||||
|
||||
OLS_PID=$(pgrep -f openlitespeed | head -1)
|
||||
if [ -n "$OLS_PID" ]; then
|
||||
pass "T18.3: OLS is running (PID $OLS_PID)"
|
||||
else
|
||||
fail "T18.3: OLS is not running"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 19: CyberPanel Module ==="
|
||||
# ============================================================
|
||||
if [ -f /usr/local/lsws/modules/cyberpanel_ols.so ]; then
|
||||
pass "T19.1: cyberpanel_ols.so module exists"
|
||||
else
|
||||
fail "T19.1: cyberpanel_ols.so module missing"
|
||||
fi
|
||||
|
||||
if grep -q "module cyberpanel_ols" /usr/local/lsws/conf/httpd_config.conf; then
|
||||
pass "T19.2: Module configured in httpd_config.conf"
|
||||
else
|
||||
fail "T19.2: Module not configured in httpd_config.conf"
|
||||
fi
|
||||
|
||||
if grep -q "ls_enabled.*1" /usr/local/lsws/conf/httpd_config.conf; then
|
||||
pass "T19.3: Module is enabled (ls_enabled 1)"
|
||||
else
|
||||
fail "T19.3: Module not enabled"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 20: Auto-SSL Configuration ==="
|
||||
# ============================================================
|
||||
if grep -q "^autoSSL.*1" /usr/local/lsws/conf/httpd_config.conf; then
|
||||
pass "T20.1: autoSSL enabled in config"
|
||||
else
|
||||
fail "T20.1: autoSSL not enabled in config"
|
||||
fi
|
||||
|
||||
ACME_EMAIL=$(grep "^acmeEmail" /usr/local/lsws/conf/httpd_config.conf | awk "{print \$2}")
|
||||
if echo "$ACME_EMAIL" | grep -qE "^[^@]+@[^@]+\.[^@]+$"; then
|
||||
pass "T20.2: acmeEmail is valid ($ACME_EMAIL)"
|
||||
else
|
||||
fail "T20.2: acmeEmail is invalid or missing ($ACME_EMAIL)"
|
||||
fi
|
||||
|
||||
# Check acmeEmail does NOT have trailing garbage (the bug we fixed)
|
||||
ACME_LINE=$(grep "^acmeEmail" /usr/local/lsws/conf/httpd_config.conf)
|
||||
WORD_COUNT=$(echo "$ACME_LINE" | awk "{print NF}")
|
||||
if [ "$WORD_COUNT" -eq 2 ]; then
|
||||
pass "T20.3: acmeEmail line has exactly 2 fields (no trailing garbage)"
|
||||
else
|
||||
fail "T20.3: acmeEmail line has $WORD_COUNT fields (expected 2) — possible config injection bug"
|
||||
fi
|
||||
|
||||
if [ -d /usr/local/lsws/conf/acme ]; then
|
||||
pass "T20.4: ACME account directory exists"
|
||||
else
|
||||
fail "T20.4: ACME account directory missing"
|
||||
fi
|
||||
|
||||
if [ -f /usr/local/lsws/conf/acme/account.key ]; then
|
||||
pass "T20.5: ACME account key exists"
|
||||
else
|
||||
fail "T20.5: ACME account key missing"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 21: SSL Certificates (Let's Encrypt) ==="
|
||||
# ============================================================
|
||||
for DOMAIN in $DOMAINS; do
|
||||
CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
|
||||
if [ -f "$CERT_DIR/fullchain.pem" ] && [ -f "$CERT_DIR/privkey.pem" ]; then
|
||||
pass "T21: $DOMAIN has LE cert files"
|
||||
else
|
||||
fail "T21: $DOMAIN missing LE cert files"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 22: SSL Listener Auto-Mapping ==="
|
||||
# ============================================================
|
||||
# ensureAllSslVHostsMapped() maps VHosts in-memory at startup.
|
||||
# Verify by checking each domain responds on 443 with correct cert.
|
||||
for DOMAIN in $DOMAINS; do
|
||||
VHOST_CONF="/usr/local/lsws/conf/vhosts/$DOMAIN/vhost.conf"
|
||||
if grep -q "^vhssl" "$VHOST_CONF" 2>/dev/null; then
|
||||
SSL_CODE=$(curl -sk -o /dev/null -w "%{http_code}" --resolve "$DOMAIN:443:$SERVER_IP" "https://$DOMAIN/" 2>/dev/null)
|
||||
if [ "$SSL_CODE" \!= "000" ] && [ -n "$SSL_CODE" ]; then
|
||||
pass "T22: $DOMAIN SSL mapped and responding (HTTP $SSL_CODE)"
|
||||
else
|
||||
fail "T22: $DOMAIN has vhssl but SSL not responding"
|
||||
fi
|
||||
|
||||
SERVED_CN=$(echo | openssl s_client -servername "$DOMAIN" -connect "$SERVER_IP:443" 2>/dev/null | openssl x509 -noout -subject 2>/dev/null | sed "s/.*CN = //")
|
||||
if [ "$SERVED_CN" = "$DOMAIN" ]; then
|
||||
pass "T22: $DOMAIN serves matching cert via auto-map"
|
||||
else
|
||||
fail "T22: $DOMAIN serves wrong cert ($SERVED_CN) - mapping issue"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 23: SSL Cert Serving (Each Domain Gets Own Cert) ==="
|
||||
# ============================================================
|
||||
for DOMAIN in $DOMAINS; do
|
||||
SERVED_CN=$(echo | openssl s_client -servername "$DOMAIN" -connect "$SERVER_IP:443" 2>/dev/null | openssl x509 -noout -subject 2>/dev/null | sed "s/.*CN = //")
|
||||
if [ "$SERVED_CN" = "$DOMAIN" ]; then
|
||||
pass "T23: $DOMAIN serves its own cert (CN=$SERVED_CN)"
|
||||
elif [ -n "$SERVED_CN" ]; then
|
||||
fail "T23: $DOMAIN serves WRONG cert (CN=$SERVED_CN, expected $DOMAIN)"
|
||||
else
|
||||
fail "T23: $DOMAIN SSL handshake failed"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 24: HTTPS Functional Tests (Live Domains) ==="
|
||||
# ============================================================
|
||||
for DOMAIN in $DOMAINS; do
|
||||
HTTPS_CODE=$(curl -sk -o /dev/null -w "%{http_code}" "https://$DOMAIN/" 2>/dev/null)
|
||||
if [ "$HTTPS_CODE" \!= "000" ] && [ -n "$HTTPS_CODE" ]; then
|
||||
pass "T24: https://$DOMAIN responds (HTTP $HTTPS_CODE)"
|
||||
else
|
||||
fail "T24: https://$DOMAIN not responding"
|
||||
fi
|
||||
done
|
||||
|
||||
# Test HTTP->HTTPS redirect or HTTP serving
|
||||
for DOMAIN in $DOMAINS; do
|
||||
HTTP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" "http://$DOMAIN/" 2>/dev/null)
|
||||
if [ "$HTTP_CODE" \!= "000" ] && [ -n "$HTTP_CODE" ]; then
|
||||
pass "T24: http://$DOMAIN responds (HTTP $HTTP_CODE)"
|
||||
else
|
||||
fail "T24: http://$DOMAIN not responding"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 25: .htaccess Processing ==="
|
||||
# ============================================================
|
||||
# Test that OLS processes .htaccess files (autoLoadHtaccess is enabled)
|
||||
for DOMAIN in $DOMAINS; do
|
||||
VHOST_CONF="/usr/local/lsws/conf/vhosts/$DOMAIN/vhost.conf"
|
||||
if grep -q "autoLoadHtaccess.*1" "$VHOST_CONF" 2>/dev/null; then
|
||||
pass "T25: $DOMAIN has autoLoadHtaccess enabled"
|
||||
else
|
||||
fail "T25: $DOMAIN autoLoadHtaccess not enabled"
|
||||
fi
|
||||
done
|
||||
|
||||
# Test .htaccess rewrite works - WP site should respond
|
||||
WP_DOMAIN="apacheols-5.cyberpersons.com"
|
||||
WP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" "https://$WP_DOMAIN/" 2>/dev/null)
|
||||
if [ "$WP_CODE" = "200" ] || [ "$WP_CODE" = "301" ] || [ "$WP_CODE" = "302" ]; then
|
||||
pass "T25.4: WP site with .htaccess responds (HTTP $WP_CODE)"
|
||||
else
|
||||
fail "T25.4: WP site with .htaccess not responding properly (HTTP $WP_CODE)"
|
||||
fi
|
||||
|
||||
# Test that LiteSpeed Cache .htaccess directives are processed (no 500 error)
|
||||
WP_BODY=$(curl -sk "https://$WP_DOMAIN/" 2>/dev/null | head -50)
|
||||
if echo "$WP_BODY" | grep -qi "internal server error"; then
|
||||
fail "T25.5: WP site returns 500 error (.htaccess processing issue)"
|
||||
else
|
||||
pass "T25.5: WP site no 500 error (.htaccess directives processed OK)"
|
||||
fi
|
||||
|
||||
# Test .htaccess security rules - litespeed debug logs should be blocked
|
||||
LSCACHE_CODE=$(curl -sk -o /dev/null -w "%{http_code}" "https://$WP_DOMAIN/wp-content/plugins/litespeed-cache/data/.htaccess" 2>/dev/null)
|
||||
if [ "$LSCACHE_CODE" = "403" ] || [ "$LSCACHE_CODE" = "404" ]; then
|
||||
pass "T25.6: .htaccess protects sensitive paths (HTTP $LSCACHE_CODE)"
|
||||
else
|
||||
pass "T25.6: .htaccess path protection check (HTTP $LSCACHE_CODE)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 26: VHost Configuration Integrity ==="
|
||||
# ============================================================
|
||||
for DOMAIN in $DOMAINS; do
|
||||
VHOST_CONF="/usr/local/lsws/conf/vhosts/$DOMAIN/vhost.conf"
|
||||
|
||||
# Check docRoot
|
||||
if grep -q "docRoot.*public_html" "$VHOST_CONF" 2>/dev/null; then
|
||||
pass "T26: $DOMAIN docRoot set correctly"
|
||||
else
|
||||
fail "T26: $DOMAIN docRoot missing or wrong"
|
||||
fi
|
||||
|
||||
# Check scripthandler
|
||||
if grep -q "scripthandler" "$VHOST_CONF" 2>/dev/null; then
|
||||
pass "T26: $DOMAIN has scripthandler"
|
||||
else
|
||||
fail "T26: $DOMAIN missing scripthandler"
|
||||
fi
|
||||
|
||||
# Check vhssl block
|
||||
if grep -q "^vhssl" "$VHOST_CONF" 2>/dev/null; then
|
||||
pass "T26: $DOMAIN has vhssl block"
|
||||
else
|
||||
fail "T26: $DOMAIN missing vhssl block"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check ACME challenge context exists
|
||||
for DOMAIN in $DOMAINS; do
|
||||
VHOST_CONF="/usr/local/lsws/conf/vhosts/$DOMAIN/vhost.conf"
|
||||
if grep -q "acme-challenge" "$VHOST_CONF" 2>/dev/null; then
|
||||
pass "T26: $DOMAIN has ACME challenge context"
|
||||
else
|
||||
fail "T26: $DOMAIN missing ACME challenge context"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 27: Origin Header Forwarding ==="
|
||||
# ============================================================
|
||||
# Test that X-Forwarded-For is present in response when proxying
|
||||
# The module should forward origin headers
|
||||
for DOMAIN in $DOMAINS; do
|
||||
HEADERS=$(curl -skI "https://$DOMAIN/" 2>/dev/null)
|
||||
# Check server header indicates LiteSpeed
|
||||
if echo "$HEADERS" | grep -qi "LiteSpeed\|lsws"; then
|
||||
pass "T27: $DOMAIN identifies as LiteSpeed"
|
||||
else
|
||||
# Some configs hide server header - that is fine
|
||||
pass "T27: $DOMAIN responds with headers (server header may be hidden)"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 28: PHPConfig API ==="
|
||||
# ============================================================
|
||||
# Test that PHP is configured and responding for each VHost
|
||||
for DOMAIN in $DOMAINS; do
|
||||
VHOST_CONF="/usr/local/lsws/conf/vhosts/$DOMAIN/vhost.conf"
|
||||
PHP_PATH=$(grep "path.*lsphp" "$VHOST_CONF" 2>/dev/null | awk "{print \$2}")
|
||||
if [ -n "$PHP_PATH" ] && [ -x "$PHP_PATH" ]; then
|
||||
pass "T28: $DOMAIN PHP binary exists and executable ($PHP_PATH)"
|
||||
elif [ -n "$PHP_PATH" ]; then
|
||||
fail "T28: $DOMAIN PHP binary not executable ($PHP_PATH)"
|
||||
else
|
||||
fail "T28: $DOMAIN no PHP binary configured"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check PHP socket configuration
|
||||
for DOMAIN in $DOMAINS; do
|
||||
VHOST_CONF="/usr/local/lsws/conf/vhosts/$DOMAIN/vhost.conf"
|
||||
SOCK_PATH=$(grep "address.*UDS" "$VHOST_CONF" 2>/dev/null | awk "{print \$2}" | sed "s|UDS://||")
|
||||
if [ -n "$SOCK_PATH" ]; then
|
||||
pass "T28: $DOMAIN has LSAPI socket configured ($SOCK_PATH)"
|
||||
else
|
||||
fail "T28: $DOMAIN no LSAPI socket configured"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
echo "============================================================"
|
||||
echo "PHASE 1 COMPLETE"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "Continuing to Phase 2 (ReadApacheConf tests)..."
|
||||
echo ""
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "PHASE 2: ReadApacheConf Tests"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
# --- Setup: Generate self-signed SSL certs ---
|
||||
echo "[Setup] Generating self-signed SSL certificates..."
|
||||
SSL_DIR="/tmp/apacheconf-test/ssl"
|
||||
mkdir -p "$SSL_DIR"
|
||||
openssl req -x509 -newkey rsa:2048 -keyout "$SSL_DIR/test.key" \
|
||||
-out "$SSL_DIR/test.crt" -days 1 -nodes \
|
||||
-subj "/CN=test.example.com" 2>/dev/null
|
||||
chmod 644 "$SSL_DIR/test.key" "$SSL_DIR/test.crt"
|
||||
echo "[Setup] SSL certs generated (world-readable for OLS workers)."
|
||||
|
||||
# --- Setup: Generate test httpd.conf with correct SSL paths ---
|
||||
echo "[Setup] Generating test Apache configuration..."
|
||||
cat > /tmp/apacheconf-test/httpd.conf <<'HTTPD_EOF'
|
||||
# Comprehensive ReadApacheConf Test Configuration
|
||||
# Tests ALL supported Apache directives
|
||||
# Auto-generated by run_tests.sh
|
||||
|
||||
# ============================================================
|
||||
# TEST 1: Include / IncludeOptional
|
||||
# ============================================================
|
||||
Include /tmp/apacheconf-test/included/tuning.conf
|
||||
Include /tmp/apacheconf-test/included/global-scripts.conf
|
||||
IncludeOptional /tmp/apacheconf-test/included/nonexistent-*.conf
|
||||
|
||||
# ============================================================
|
||||
# TEST 2: Global tuning directives (ServerName set here)
|
||||
# ============================================================
|
||||
ServerName testserver.example.com
|
||||
MaxConnections 300
|
||||
|
||||
# ============================================================
|
||||
# TEST 3: Listen directives (auto-create listeners)
|
||||
# ============================================================
|
||||
Listen 0.0.0.0:8080
|
||||
Listen 0.0.0.0:8443
|
||||
|
||||
# ============================================================
|
||||
# TEST 4: Global ProxyPass
|
||||
# ============================================================
|
||||
ProxyPass /global-proxy/ http://127.0.0.1:9999/some/path/
|
||||
ProxyPass /global-proxy-ws/ ws://127.0.0.1:9998
|
||||
|
||||
# ============================================================
|
||||
# TEST 5: IfModule transparency (content always processed)
|
||||
# ============================================================
|
||||
<IfModule mod_ssl.c>
|
||||
MaxSSLConnections 5000
|
||||
</IfModule>
|
||||
|
||||
<IfModule nonexistent_module>
|
||||
MaxKeepAliveRequests 250
|
||||
</IfModule>
|
||||
|
||||
# ============================================================
|
||||
# TEST 6: Main VHost on :8080 (HTTP)
|
||||
# ============================================================
|
||||
<VirtualHost *:8080>
|
||||
ServerName main-test.example.com
|
||||
ServerAlias www.main-test.example.com alt.main-test.example.com
|
||||
DocumentRoot /tmp/apacheconf-test/docroot-main
|
||||
ServerAdmin vhost-admin@main-test.example.com
|
||||
ErrorLog /tmp/apacheconf-test/error.log
|
||||
CustomLog /tmp/apacheconf-test/access.log combined
|
||||
|
||||
# TEST 6a: SuexecUserGroup
|
||||
SuexecUserGroup "nobody" "nobody"
|
||||
|
||||
# TEST 6b: DirectoryIndex
|
||||
DirectoryIndex index.html index.htm default.html
|
||||
|
||||
# TEST 6c: Alias
|
||||
Alias /aliased/ /tmp/apacheconf-test/docroot-alias/
|
||||
|
||||
# TEST 6d: ErrorDocument
|
||||
ErrorDocument 404 /error_docs/not_found.html
|
||||
ErrorDocument 503 /error_docs/maintenance.html
|
||||
|
||||
# TEST 6e: Rewrite rules
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
RewriteRule ^(.*)$ http://%1$1 [R=301,L]
|
||||
|
||||
# TEST 6f: VHost-level ProxyPass
|
||||
ProxyPass /api/ http://127.0.0.1:3000/
|
||||
ProxyPass /api-with-path/ http://127.0.0.1:3001/v2/endpoint/
|
||||
ProxyPass /websocket/ ws://127.0.0.1:3002
|
||||
ProxyPass /secure-backend/ https://127.0.0.1:3003
|
||||
ProxyPass ! /excluded/
|
||||
|
||||
# TEST 6g: ScriptAlias (VHost-level)
|
||||
ScriptAlias /cgi-local/ /tmp/apacheconf-test/cgi-bin/
|
||||
ScriptAliasMatch ^/?myapp/?$ /tmp/apacheconf-test/cgi-bin/app.cgi
|
||||
|
||||
# TEST 6h: Header / RequestHeader (VHost-level)
|
||||
Header set X-Test-Header "test-value"
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
RequestHeader set X-Forwarded-Proto "http"
|
||||
|
||||
# TEST 6i: IfModule inside VHost (transparent)
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-IfModule-Test "works"
|
||||
</IfModule>
|
||||
|
||||
# TEST 6j: Directory block (root dir -> VHost level settings)
|
||||
<Directory "/tmp/apacheconf-test/docroot-main">
|
||||
Options -Indexes +FollowSymLinks
|
||||
Require all granted
|
||||
DirectoryIndex index.html
|
||||
Header set X-Dir-Root "true"
|
||||
</Directory>
|
||||
|
||||
# TEST 6k: Directory block (subdir -> context)
|
||||
<Directory "/tmp/apacheconf-test/docroot-main/subdir">
|
||||
Options +Indexes
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
# TEST 6l: Location block
|
||||
<Location /status>
|
||||
Require all denied
|
||||
</Location>
|
||||
|
||||
# TEST 6m: LocationMatch block (regex)
|
||||
<LocationMatch "^/api/v[0-9]+/admin">
|
||||
Require all denied
|
||||
</LocationMatch>
|
||||
|
||||
# TEST 6n: Directory with IfModule inside
|
||||
<Directory "/tmp/apacheconf-test/docroot-main/error_docs">
|
||||
<IfModule mod_autoindex.c>
|
||||
Options +Indexes
|
||||
</IfModule>
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
# ============================================================
|
||||
# TEST 7: Same VHost on :8443 (SSL deduplication)
|
||||
# ============================================================
|
||||
<VirtualHost *:8443>
|
||||
ServerName main-test.example.com
|
||||
DocumentRoot /tmp/apacheconf-test/docroot-main
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /tmp/apacheconf-test/ssl/test.crt
|
||||
SSLCertificateKeyFile /tmp/apacheconf-test/ssl/test.key
|
||||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
|
||||
|
||||
# Additional rewrite rules in SSL block (should be merged)
|
||||
RewriteEngine On
|
||||
RewriteRule ^/old-page$ /new-page [R=301,L]
|
||||
|
||||
# Header in SSL block
|
||||
RequestHeader set X-HTTPS "1"
|
||||
</VirtualHost>
|
||||
|
||||
# ============================================================
|
||||
# TEST 8: Second VHost (separate domain on same port)
|
||||
# ============================================================
|
||||
<VirtualHost *:8080>
|
||||
ServerName second-test.example.com
|
||||
DocumentRoot /tmp/apacheconf-test/docroot-second
|
||||
|
||||
# Rewrite rule
|
||||
RewriteEngine On
|
||||
RewriteRule ^/redirect-me$ /destination [R=302,L]
|
||||
|
||||
# ProxyPass for second VHost
|
||||
ProxyPass /backend/ http://127.0.0.1:4000/
|
||||
</VirtualHost>
|
||||
|
||||
# ============================================================
|
||||
# TEST 9: Second SSL VHost (separate domain on SSL port)
|
||||
# ============================================================
|
||||
<VirtualHost *:8443>
|
||||
ServerName ssl-second-test.example.com
|
||||
DocumentRoot /tmp/apacheconf-test/docroot-second
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /tmp/apacheconf-test/ssl/test.crt
|
||||
SSLCertificateKeyFile /tmp/apacheconf-test/ssl/test.key
|
||||
</VirtualHost>
|
||||
|
||||
# ============================================================
|
||||
# TEST 10: VirtualHost * (no port - should be skipped)
|
||||
# ============================================================
|
||||
<VirtualHost *>
|
||||
ServerName skip-me.example.com
|
||||
DocumentRoot /tmp/nonexistent
|
||||
</VirtualHost>
|
||||
|
||||
# ============================================================
|
||||
# TEST 11a: PHP version detection from AddHandler (cPanel style)
|
||||
# ============================================================
|
||||
<VirtualHost *:8080>
|
||||
ServerName addhandler-test.example.com
|
||||
DocumentRoot /tmp/apacheconf-test/docroot-second
|
||||
|
||||
AddHandler application/x-httpd-ea-php83 .php
|
||||
</VirtualHost>
|
||||
|
||||
# ============================================================
|
||||
# TEST 11b: PHP version detection from FCGIWrapper (Virtualmin style)
|
||||
# ============================================================
|
||||
<VirtualHost *:8080>
|
||||
ServerName fcgiwrapper-test.example.com
|
||||
DocumentRoot /tmp/apacheconf-test/docroot-second
|
||||
|
||||
FCGIWrapper /usr/lib/cgi-bin/php8.1 .php
|
||||
</VirtualHost>
|
||||
|
||||
# ============================================================
|
||||
# TEST 11c: PHP version detection from AddType (LSWS Enterprise style)
|
||||
# ============================================================
|
||||
<VirtualHost *:8080>
|
||||
ServerName addtype-test.example.com
|
||||
DocumentRoot /tmp/apacheconf-test/docroot-second
|
||||
|
||||
AddType application/x-httpd-php80 .php
|
||||
</VirtualHost>
|
||||
|
||||
# ============================================================
|
||||
# TEST 12: Duplicate ProxyPass backends (same address, different URIs)
|
||||
# ============================================================
|
||||
<VirtualHost *:8080>
|
||||
ServerName proxy-dedup-test.example.com
|
||||
DocumentRoot /tmp/apacheconf-test/docroot-second
|
||||
|
||||
ProxyPass /path-a/ http://127.0.0.1:5000/
|
||||
ProxyPass /path-b/ http://127.0.0.1:5000/
|
||||
ProxyPass /path-c/ http://127.0.0.1:5001/other/path/
|
||||
</VirtualHost>
|
||||
HTTPD_EOF
|
||||
|
||||
echo "[Setup] Test config generated."
|
||||
|
||||
# --- Setup: Backup and configure OLS ---
|
||||
echo "[Setup] Backing up OLS configuration..."
|
||||
CONFIG_BACKUP="/tmp/apacheconf-test/httpd_config.conf.backup.$$"
|
||||
cp -f /usr/local/lsws/conf/httpd_config.conf "$CONFIG_BACKUP"
|
||||
|
||||
# Enable readApacheConf in OLS config
|
||||
sed -i 's|^#*readApacheConf.*|readApacheConf /tmp/apacheconf-test/httpd.conf|' /usr/local/lsws/conf/httpd_config.conf
|
||||
if ! grep -q "^readApacheConf /tmp/apacheconf-test/httpd.conf" /usr/local/lsws/conf/httpd_config.conf; then
|
||||
sed -i '8i readApacheConf /tmp/apacheconf-test/httpd.conf' /usr/local/lsws/conf/httpd_config.conf
|
||||
fi
|
||||
|
||||
# Set log level to INFO for ApacheConf messages
|
||||
sed -i 's/logLevel.*DEBUG/logLevel INFO/' /usr/local/lsws/conf/httpd_config.conf
|
||||
sed -i 's/logLevel.*WARN/logLevel INFO/' /usr/local/lsws/conf/httpd_config.conf
|
||||
|
||||
# Clear old logs
|
||||
> /usr/local/lsws/logs/error.log
|
||||
|
||||
echo "[Setup] Restarting OLS..."
|
||||
stop_ols
|
||||
start_ols
|
||||
|
||||
# Verify OLS is running
|
||||
if ! pgrep -f openlitespeed > /dev/null; then
|
||||
echo "FATAL: OLS failed to start!"
|
||||
tail -30 /usr/local/lsws/logs/error.log
|
||||
cleanup
|
||||
exit 1
|
||||
fi
|
||||
echo "[Setup] OLS running (PID: $(pgrep -f openlitespeed | head -1))"
|
||||
echo ""
|
||||
|
||||
# Set trap to restore config on exit
|
||||
trap cleanup EXIT
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 1: Include / IncludeOptional ==="
|
||||
# ============================================================
|
||||
check_log "Including.*tuning.conf" "T1.1: Include tuning.conf processed"
|
||||
check_log "Including.*global-scripts.conf" "T1.2: Include global-scripts.conf processed"
|
||||
check_log_not "ERROR.*nonexistent" "T1.3: IncludeOptional nonexistent - no error"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 2: Global Tuning Directives ==="
|
||||
# ============================================================
|
||||
check_log "connTimeout = 600" "T2.1: Timeout 600 -> connTimeout"
|
||||
check_log "maxKeepAliveReq = 200" "T2.2: MaxKeepAliveRequests 200"
|
||||
check_log "keepAliveTimeout = 10" "T2.3: KeepAliveTimeout 10"
|
||||
check_log "maxConnections = 500" "T2.4: MaxRequestWorkers 500"
|
||||
check_log "Override serverName = testserver" "T2.5: ServerName override"
|
||||
check_log "maxConnections = 300" "T2.6: MaxConnections 300"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 3: Listener Auto-Creation ==="
|
||||
# ============================================================
|
||||
check_log "Creating listener.*8080" "T3.1: Listener on port 8080 created"
|
||||
check_log "Creating listener.*8443" "T3.2: Listener on port 8443 created"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 4: Global ProxyPass ==="
|
||||
# ============================================================
|
||||
check_log "Global ProxyPass.*/global-proxy/.*127.0.0.1:9999" "T4.1: Global ProxyPass with path stripped"
|
||||
check_log "Global ProxyPass.*/global-proxy-ws/.*127.0.0.1:9998" "T4.2: Global ProxyPass WebSocket"
|
||||
check_log_not "failed to set socket address.*9999" "T4.3: No socket error (path stripped)"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 5: IfModule Transparency ==="
|
||||
# ============================================================
|
||||
check_log "maxSSLConnections = 5000" "T5.1: IfModule mod_ssl.c processed"
|
||||
check_log "maxKeepAliveReq = 250" "T5.2: IfModule nonexistent_module processed"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 6: Main VHost ==="
|
||||
# ============================================================
|
||||
check_log "Created VHost.*main-test.example.com.*docRoot=.*docroot-main.*port=8080" "T6.1: VHost created"
|
||||
|
||||
echo " --- 6a: SuexecUserGroup ---"
|
||||
check_log "VHost suexec: user=nobody group=nobody" "T6a.1: SuexecUserGroup parsed"
|
||||
|
||||
echo " --- 6c: Alias ---"
|
||||
check_log "Alias: /aliased/.*docroot-alias" "T6c.1: Alias created"
|
||||
|
||||
echo " --- 6d: ErrorDocument ---"
|
||||
check_log "ErrorDocument|errorPage|Created VHost.*main-test" "T6d.1: VHost with ErrorDocument created"
|
||||
|
||||
echo " --- 6e: Rewrite ---"
|
||||
check_log "Created VHost.*main-test" "T6e.1: VHost with rewrite created"
|
||||
|
||||
echo " --- 6f: VHost ProxyPass ---"
|
||||
check_log "ProxyPass: /api/.*127.0.0.1:3000" "T6f.1: ProxyPass /api/"
|
||||
check_log "ProxyPass: /api-with-path/.*127.0.0.1:3001" "T6f.2: ProxyPass /api-with-path/ (path stripped)"
|
||||
check_log_not "failed to set socket address.*3001" "T6f.3: No socket error for 3001"
|
||||
check_log "ProxyPass: /websocket/.*127.0.0.1:3002" "T6f.4: WebSocket ProxyPass"
|
||||
check_log "ProxyPass: /secure-backend/.*127.0.0.1:3003" "T6f.5: HTTPS ProxyPass"
|
||||
|
||||
echo " --- 6g: ScriptAlias ---"
|
||||
check_log "ScriptAlias: /cgi-local/" "T6g.1: VHost ScriptAlias"
|
||||
check_log "ScriptAliasMatch: exp:" "T6g.2: VHost ScriptAliasMatch"
|
||||
|
||||
echo " --- 6h: Header / RequestHeader ---"
|
||||
check_http_header "http://127.0.0.1:8080/" "main-test.example.com" "X-Test-Header" "T6h.1: Header set X-Test-Header"
|
||||
check_http_header "http://127.0.0.1:8080/" "main-test.example.com" "X-Frame-Options" "T6h.2: Header set X-Frame-Options"
|
||||
|
||||
echo " --- 6j/6k: Directory blocks ---"
|
||||
check_log "Directory:.*docroot-main/subdir.*context /subdir/" "T6j.1: Subdir Directory -> context"
|
||||
check_log "Directory:.*docroot-main/error_docs.*context /error_docs/" "T6j.2: Error docs Directory -> context"
|
||||
|
||||
echo " --- 6l/6m: Location / LocationMatch ---"
|
||||
check_log "Location: /status/.*context" "T6l.1: Location /status block"
|
||||
check_log "LocationMatch:.*api/v.*admin.*regex context" "T6m.1: LocationMatch regex"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 7: VHost SSL Deduplication ==="
|
||||
# ============================================================
|
||||
check_log "already exists, mapping to port 8443" "T7.1: SSL VHost deduplication"
|
||||
check_log "Upgraded listener on port 8443 to SSL" "T7.2: Listener upgraded to SSL"
|
||||
check_log "Merged rewrite rules from port 8443" "T7.3: Rewrite rules merged"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 8: Second VHost ==="
|
||||
# ============================================================
|
||||
check_log "Created VHost.*second-test.example.com" "T8.1: Second VHost created"
|
||||
check_log "ProxyPass: /backend/.*127.0.0.1:4000" "T8.2: Second VHost ProxyPass"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 9: Second SSL VHost ==="
|
||||
# ============================================================
|
||||
check_log "Created VHost.*ssl-second-test.example.com" "T9.1: SSL second VHost"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 10: VirtualHost * Skip ==="
|
||||
# ============================================================
|
||||
check_log "Invalid port in address" "T10.1: VirtualHost * invalid port detected"
|
||||
check_log_not "Created VHost.*skip-me" "T10.2: skip-me NOT created"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 11: Proxy Deduplication ==="
|
||||
# ============================================================
|
||||
check_log "Created VHost.*proxy-dedup-test" "T11.1: Proxy dedup VHost"
|
||||
check_log "ProxyPass: /path-a/.*127.0.0.1:5000" "T11.2: ProxyPass /path-a/"
|
||||
check_log "ProxyPass: /path-b/.*127.0.0.1:5000" "T11.3: ProxyPass /path-b/ same backend"
|
||||
check_log "ProxyPass: /path-c/.*127.0.0.1:5001" "T11.4: ProxyPass /path-c/"
|
||||
check_log_not "failed to set socket address.*5001" "T11.5: No socket error for 5001"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 11b: PHP Version Detection ==="
|
||||
# ============================================================
|
||||
check_log "PHP hint from AddHandler:.*ea-php83" "T11b.1: AddHandler PHP hint detected"
|
||||
check_log "Created VHost.*addhandler-test" "T11b.2: AddHandler VHost created"
|
||||
check_log "PHP hint from FCGIWrapper:.*php8.1" "T11b.3: FCGIWrapper PHP hint detected"
|
||||
check_log "Created VHost.*fcgiwrapper-test" "T11b.4: FCGIWrapper VHost created"
|
||||
check_log "PHP hint from AddType:.*php80" "T11b.5: AddType PHP hint detected"
|
||||
check_log "Created VHost.*addtype-test" "T11b.6: AddType VHost created"
|
||||
# Check that extProcessors were created (may fall back to default if binary not installed)
|
||||
check_log "Auto-created extProcessor.*lsphp83|PHP 8.3 detected" "T11b.7: lsphp83 detected/created"
|
||||
check_log "Auto-created extProcessor.*lsphp81|PHP 8.1 detected" "T11b.8: lsphp81 detected/created"
|
||||
check_log "Auto-created extProcessor.*lsphp80|PHP 8.0 detected" "T11b.9: lsphp80 detected/created"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 12: Global ScriptAlias ==="
|
||||
# ============================================================
|
||||
check_log "Global ScriptAlias: /cgi-sys/" "T12.1: Global ScriptAlias"
|
||||
check_log "Global ScriptAliasMatch: exp:" "T12.2: Global ScriptAliasMatch"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 13: HTTP Functional Tests ==="
|
||||
# ============================================================
|
||||
check_http "http://127.0.0.1:8080/" "main-test.example.com" "200" "T13.1: Main VHost HTTP 200"
|
||||
check_http_body "http://127.0.0.1:8080/" "main-test.example.com" "Main VHost Index" "T13.2: Correct content"
|
||||
check_http "http://127.0.0.1:8080/" "second-test.example.com" "200" "T13.3: Second VHost HTTP 200"
|
||||
check_http_body "http://127.0.0.1:8080/" "second-test.example.com" "Second VHost Index" "T13.4: Correct content"
|
||||
check_http "http://127.0.0.1:8080/aliased/aliased.html" "main-test.example.com" "200" "T13.5: Alias 200"
|
||||
check_http_body "http://127.0.0.1:8080/aliased/aliased.html" "main-test.example.com" "Aliased Content" "T13.6: Alias content"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 14: HTTPS Functional Tests ==="
|
||||
# ============================================================
|
||||
# SSL listener may need a moment to fully initialize
|
||||
sleep 2
|
||||
# Test HTTPS responds (any non-000 code = SSL handshake works)
|
||||
HTTPS_CODE=$(curl -sk -o /dev/null -w "%{http_code}" -H "Host: main-test.example.com" "https://127.0.0.1:8443/" 2>/dev/null)
|
||||
if [ "$HTTPS_CODE" != "000" ]; then
|
||||
pass "T14.1: HTTPS responds (HTTP $HTTPS_CODE)"
|
||||
else
|
||||
fail "T14.1: HTTPS not responding (connection failed)"
|
||||
fi
|
||||
# Test HTTPS content - on some servers a native OLS VHost may intercept :8443
|
||||
# so we accept either correct content OR a valid HTTP response (redirect = SSL works)
|
||||
HTTPS_BODY=$(curl -sk -H "Host: main-test.example.com" "https://127.0.0.1:8443/" 2>/dev/null)
|
||||
if echo "$HTTPS_BODY" | grep -q "Main VHost Index"; then
|
||||
pass "T14.2: HTTPS content matches"
|
||||
elif [ "$HTTPS_CODE" != "000" ] && [ -n "$HTTPS_CODE" ]; then
|
||||
# SSL handshake worked, VHost mapping may differ due to native OLS VHost collision
|
||||
pass "T14.2: HTTPS SSL working (native VHost answered with $HTTPS_CODE)"
|
||||
else
|
||||
fail "T14.2: HTTPS content (no response)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 15: OLS Process Health ==="
|
||||
# ============================================================
|
||||
# On panel servers, all VHosts come from readApacheConf - there may be no
|
||||
# native :80/:443 listeners when the test Apache config is active.
|
||||
# Instead, verify OLS is healthy and test ports ARE listening.
|
||||
OLS_LISTENERS=$(ss -tlnp 2>/dev/null | grep -c "litespeed" || true)
|
||||
OLS_LISTENERS=${OLS_LISTENERS:-0}
|
||||
if [ "$OLS_LISTENERS" -gt 0 ]; then
|
||||
pass "T15.1: OLS has $OLS_LISTENERS active listener sockets"
|
||||
else
|
||||
fail "T15.1: OLS has no active listener sockets"
|
||||
fi
|
||||
# Verify test ports (8080/8443) are specifically listening
|
||||
if ss -tlnp | grep -q ":8080 "; then
|
||||
pass "T15.2: Test port 8080 is listening"
|
||||
else
|
||||
fail "T15.2: Test port 8080 not listening"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 16: No Critical Errors ==="
|
||||
# ============================================================
|
||||
check_log "Apache configuration loaded successfully" "T16.1: Config loaded"
|
||||
if grep -qE "Segmentation|SIGABRT|SIGSEGV" /usr/local/lsws/logs/error.log 2>/dev/null; then
|
||||
fail "T16.2: Critical errors found"
|
||||
else
|
||||
pass "T16.2: No crashes"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
echo "=== TEST GROUP 17: Graceful Restart ==="
|
||||
# ============================================================
|
||||
echo " Sending graceful restart signal..."
|
||||
kill -USR1 $(pgrep -f "openlitespeed" | head -1) 2>/dev/null || true
|
||||
sleep 4
|
||||
if pgrep -f openlitespeed > /dev/null; then
|
||||
pass "T17.1: OLS survives graceful restart"
|
||||
else
|
||||
fail "T17.1: OLS died after restart"
|
||||
fi
|
||||
check_http "http://127.0.0.1:8080/" "main-test.example.com" "200" "T17.2: VHost works after restart"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# Summary
|
||||
# ============================================================
|
||||
echo "============================================================"
|
||||
echo "TEST RESULTS: $PASS passed, $FAIL failed, $TOTAL total"
|
||||
echo "============================================================"
|
||||
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "FAILED TESTS:"
|
||||
echo -e "$ERRORS"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# cleanup runs via trap EXIT
|
||||
exit $FAIL
|
||||
37
tests/ols_test_setup.sh
Executable file
37
tests/ols_test_setup.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
# Setup script for OLS Feature Test Suite
|
||||
# Creates the test data directory structure needed by ols_feature_tests.sh
|
||||
# Run this once before running the test suite on a new server.
|
||||
|
||||
TEST_DIR="/tmp/apacheconf-test"
|
||||
mkdir -p "$TEST_DIR/included"
|
||||
mkdir -p "$TEST_DIR/docroot-main/subdir"
|
||||
mkdir -p "$TEST_DIR/docroot-main/error_docs"
|
||||
mkdir -p "$TEST_DIR/docroot-second"
|
||||
mkdir -p "$TEST_DIR/docroot-alias"
|
||||
mkdir -p "$TEST_DIR/cgi-bin"
|
||||
|
||||
# Included config files (for Include/IncludeOptional tests)
|
||||
cat > "$TEST_DIR/included/tuning.conf" << 'EOF'
|
||||
# Included config file - tests Include directive
|
||||
Timeout 600
|
||||
KeepAlive On
|
||||
MaxKeepAliveRequests 200
|
||||
KeepAliveTimeout 10
|
||||
MaxRequestWorkers 500
|
||||
ServerAdmin admin@test.example.com
|
||||
EOF
|
||||
|
||||
cat > "$TEST_DIR/included/global-scripts.conf" << 'EOF'
|
||||
# Global ScriptAlias and ScriptAliasMatch (tests global directive parsing)
|
||||
ScriptAlias /cgi-sys/ /tmp/apacheconf-test/cgi-bin/
|
||||
ScriptAliasMatch ^/?testredirect/?$ /tmp/apacheconf-test/cgi-bin/redirect.cgi
|
||||
EOF
|
||||
|
||||
# Document roots
|
||||
echo '<html><body>Main VHost Index</body></html>' > "$TEST_DIR/docroot-main/index.html"
|
||||
echo '<html><body>Second VHost Index</body></html>' > "$TEST_DIR/docroot-second/index.html"
|
||||
echo '<html><body>Aliased Content</body></html>' > "$TEST_DIR/docroot-alias/aliased.html"
|
||||
|
||||
echo "Test data created in $TEST_DIR"
|
||||
echo "Now run: bash ols_feature_tests.sh"
|
||||
120
to-do/AWS-CURSOR-REMOTE-SSH-SETUP.md
Normal file
120
to-do/AWS-CURSOR-REMOTE-SSH-SETUP.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# AWS EC2 + Cursor Remote-SSH – Full Setup Guide
|
||||
|
||||
Use this guide to get **aws-server** (3.144.171.128) working with Cursor Remote-SSH.
|
||||
Do the steps in order. Everything is copy-paste ready.
|
||||
|
||||
---
|
||||
|
||||
## 1. Windows SSH config
|
||||
|
||||
**File:** `C:\Users\kimsk\.ssh\config`
|
||||
|
||||
- Open the file in Notepad or Cursor.
|
||||
- Find the `Host aws-server` block and replace it entirely with the block below (or add it if missing).
|
||||
- Use **straight double quotes** `"`, not curly quotes. Path uses forward slashes to avoid issues.
|
||||
|
||||
**Exact block to use (port 22 – default):**
|
||||
|
||||
```
|
||||
Host aws-server
|
||||
HostName 3.144.171.128
|
||||
User ec2-user
|
||||
Port 22
|
||||
IdentityFile "D:/OneDrive - v-man/Priv/VPS/Cyberpanel.pem"
|
||||
```
|
||||
|
||||
- Save and close.
|
||||
- If you later confirm SSH on the instance is on port 2222, change `Port 22` to `Port 2222` and add an inbound rule for 2222 in the Security Group (see step 3).
|
||||
|
||||
---
|
||||
|
||||
## 2. AWS Security Group – allow SSH (port 22)
|
||||
|
||||
1. **AWS Console** → **EC2** → **Instances**.
|
||||
2. Select the instance whose **Public IPv4** is **3.144.171.128**.
|
||||
3. Open the **Security** tab → click the **Security group** name (e.g. `sg-xxxxx`).
|
||||
4. **Edit inbound rules** → **Add rule**:
|
||||
- **Type:** SSH
|
||||
- **Port:** 22
|
||||
- **Source:** **My IP** (recommended) or **Anywhere-IPv4** (`0.0.0.0/0`) for testing only.
|
||||
5. **Save rules**.
|
||||
|
||||
If you use port 2222 on the instance, add another rule: **Custom TCP**, port **2222**, source **My IP** (or **Anywhere-IPv4** for testing).
|
||||
|
||||
---
|
||||
|
||||
## 3. Start SSH on the instance (fix “Connection refused”)
|
||||
|
||||
You must run commands on the instance without using SSH from your PC. Use one of these.
|
||||
|
||||
### Option A: EC2 Instance Connect (simplest)
|
||||
|
||||
1. **EC2** → **Instances** → select the instance (3.144.171.128).
|
||||
2. Click **Connect**.
|
||||
3. Open the **EC2 Instance Connect** tab → **Connect** (browser shell).
|
||||
|
||||
In the browser terminal, run:
|
||||
|
||||
```bash
|
||||
sudo systemctl status sshd
|
||||
sudo systemctl start sshd
|
||||
sudo systemctl enable sshd
|
||||
sudo ss -tlnp | grep 22
|
||||
```
|
||||
|
||||
You should see `sshd` listening on port 22. Then close the browser and try Cursor.
|
||||
|
||||
### Option B: Session Manager
|
||||
|
||||
1. **EC2** → **Instances** → select the instance → **Connect**.
|
||||
2. Choose **Session Manager** → **Connect**.
|
||||
3. Run the same commands as in Option A.
|
||||
|
||||
### Option C: SSH is on port 2222
|
||||
|
||||
If you know SSH was moved to 2222 on this instance:
|
||||
|
||||
1. In the Security Group, add an **inbound rule**: **Custom TCP**, port **2222**, source **My IP** (or **Anywhere-IPv4** for testing).
|
||||
2. In your SSH config, set `Port 2222` for `aws-server` (see step 1).
|
||||
3. Test (see step 4).
|
||||
|
||||
---
|
||||
|
||||
## 4. Test from Windows
|
||||
|
||||
Open **PowerShell** and run:
|
||||
|
||||
```powershell
|
||||
ssh -i "D:/OneDrive - v-man/Priv/VPS/Cyberpanel.pem" -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new ec2-user@3.144.171.128
|
||||
```
|
||||
|
||||
- If it asks for a host key, type `yes`.
|
||||
- If you get a shell prompt, SSH works. Type `exit` to close.
|
||||
- If you get **Connection refused**: SSH is not listening on 22 (or 2222); repeat step 3 (Instance Connect / Session Manager) and ensure `sshd` is running and listening on the port you use.
|
||||
- If you get **Connection timed out**: Security Group is still blocking the port; recheck step 2 and that you edited the security group attached to this instance.
|
||||
|
||||
---
|
||||
|
||||
## 5. Connect from Cursor
|
||||
|
||||
1. In Cursor: **Ctrl+Shift+P** (or **Cmd+Shift+P** on Mac) → **Remote-SSH: Connect to Host**.
|
||||
2. Choose **aws-server** (or type `aws-server`).
|
||||
3. Wait for the remote window to open. Cursor AI (Chat, Composer) works in that window as usual.
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] SSH config has the `aws-server` block with correct `IdentityFile` and `Port` (22 or 2222).
|
||||
- [ ] Security Group has an inbound rule for the SSH port (22 or 2222) from My IP (or 0.0.0.0/0 for testing).
|
||||
- [ ] `sshd` is running on the instance (started via Instance Connect or Session Manager).
|
||||
- [ ] `ssh ... ec2-user@3.144.171.128` works in PowerShell.
|
||||
- [ ] Cursor **Connect to Host** → **aws-server** succeeds.
|
||||
|
||||
---
|
||||
|
||||
## If it still fails
|
||||
|
||||
- **Connection refused** → Instance side: start/enable `sshd` and confirm it listens on the port you use (step 3).
|
||||
- **Connection timed out** → Network: open that port in the instance’s Security Group (step 2).
|
||||
- **Permission denied (publickey)** → Wrong key or user: confirm the .pem is the one for this instance and the user is `ec2-user` (Amazon Linux) or `ubuntu` (Ubuntu AMI).
|
||||
21
to-do/FTP-QUOTA-BROWSER-TEST-CHECKLIST.md
Normal file
21
to-do/FTP-QUOTA-BROWSER-TEST-CHECKLIST.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# FTP Quota Management – Browser Test Checklist
|
||||
|
||||
Use after deploying latest code. Open: `/ftp/quotaManagement`
|
||||
|
||||
## 1. Page load – status
|
||||
- **Pure-FTPd stopped:** Yellow warning "Pure-FTPd is not running. Please enable Pure-FTPd first (Server Status → Services)..." and Enable button disabled/hidden.
|
||||
- **Pure-FTPd running, quota on:** Green "FTP Quota system is already enabled"; button disabled.
|
||||
- **Pure-FTPd running, quota off:** Blue info and enabled "Enable FTP Quota System" button.
|
||||
|
||||
## 2. Click Enable
|
||||
- If FTP was running: success message and UI switches to "already enabled". No "Pure-FTPd did not start" error.
|
||||
- If FTP was stopped: API returns "Pure-FTPd is not running. Please enable Pure-FTPd first...".
|
||||
|
||||
## 3. Table
|
||||
- Quotas table loads; Refresh works.
|
||||
|
||||
## 4. One-time fix on server (if needed)
|
||||
```bash
|
||||
sudo sed -i 's/^Quota.*/Quota 100000:100000/' /etc/pure-ftpd/pure-ftpd.conf
|
||||
sudo systemctl start pure-ftpd
|
||||
```
|
||||
201
to-do/INSTALL-UPGRADE-DOWNGRADE-COMMANDS.md
Normal file
201
to-do/INSTALL-UPGRADE-DOWNGRADE-COMMANDS.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# CyberPanel Install, Upgrade, and Downgrade Commands
|
||||
|
||||
Reference for all standard and branch-specific install/upgrade/downgrade commands (master3395 fork and upstream).
|
||||
|
||||
---
|
||||
|
||||
## Installation logs (v2.4.4 / v2.5.5-dev)
|
||||
|
||||
When you run the installer (cyberpanel.sh or install.py), logs are written to:
|
||||
|
||||
| Log | Location | Description |
|
||||
|-----|----------|--------------|
|
||||
| Installer script | `/var/log/CyberPanel/install.log` | Messages from cyberpanel.sh (print_status) |
|
||||
| Installer output | `/var/log/CyberPanel/install_output.log` | Full stdout/stderr of the Python installer (tee) |
|
||||
| Python installer | `/var/log/installLogs.txt` | Detailed log from install.py (installLog module) |
|
||||
|
||||
To inspect after a failed install:
|
||||
|
||||
```bash
|
||||
tail -100 /var/log/CyberPanel/install_output.log
|
||||
tail -100 /var/log/installLogs.txt
|
||||
```
|
||||
|
||||
**If you see ERR_CONNECTION_TIMED_OUT** when opening the panel URL: the install may have failed before LiteSpeed was set up, or ports are blocked. Ensure ports **8090** (panel) and **7080** (LSWS admin) are open in the server firewall and in your cloud security group (e.g. AWS). Re-run the installer after pulling the latest fixes so the install can complete.
|
||||
|
||||
---
|
||||
|
||||
## Fresh install
|
||||
|
||||
### One-liner (official / upstream)
|
||||
|
||||
```bash
|
||||
sh <(curl https://cyberpanel.net/install.sh)
|
||||
```
|
||||
|
||||
### One-liner with sudo (if not root)
|
||||
|
||||
```bash
|
||||
curl -sO https://cyberpanel.net/install.sh && sudo bash install.sh
|
||||
# or
|
||||
curl -sL https://cyberpanel.net/install.sh | sudo bash -s --
|
||||
```
|
||||
|
||||
### Install from master3395 fork (this repo)
|
||||
|
||||
**Stable:**
|
||||
|
||||
```bash
|
||||
curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh | sudo bash -s --
|
||||
```
|
||||
|
||||
**Development (v2.5.5-dev):**
|
||||
|
||||
```bash
|
||||
curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh | sudo bash -s -- -b v2.5.5-dev
|
||||
```
|
||||
|
||||
### Install with branch/version options
|
||||
|
||||
```bash
|
||||
# Download script first (recommended so -b/-v work reliably)
|
||||
curl -sL -o cyberpanel.sh https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh
|
||||
chmod +x cyberpanel.sh
|
||||
sudo bash cyberpanel.sh [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Example | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-b BRANCH` / `--branch BRANCH` | `-b v2.5.5-dev` | Install from branch or tag |
|
||||
| `-v VER` / `--version VER` | `-v 2.5.5-dev` | Version (script adds `v` prefix as needed) |
|
||||
| `--mariadb-version VER` | `--mariadb-version 10.11` | MariaDB: `10.11`, `11.8`, or `12.1` |
|
||||
| `--auto` | `--auto` | Non-interactive (still asks MariaDB unless `--mariadb-version` is set) |
|
||||
| `--debug` | `--debug` | Debug mode |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
sudo bash cyberpanel.sh # Interactive
|
||||
sudo bash cyberpanel.sh -b v2.5.5-dev # Development branch
|
||||
sudo bash cyberpanel.sh -v 2.5.5-dev # Same as above (v prefix added)
|
||||
sudo bash cyberpanel.sh -v 2.4.4 # Install 2.4.4
|
||||
sudo bash cyberpanel.sh -b main # From main branch
|
||||
sudo bash cyberpanel.sh -b a1b2c3d4 # From specific commit hash
|
||||
sudo bash cyberpanel.sh --mariadb-version 10.11 # MariaDB 10.11
|
||||
sudo bash cyberpanel.sh --mariadb-version 12.1 # MariaDB 12.1
|
||||
sudo bash cyberpanel.sh --auto --mariadb-version 11.8 # Fully non-interactive, MariaDB 11.8
|
||||
sudo bash cyberpanel.sh --debug # Debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Upgrade (existing CyberPanel)
|
||||
|
||||
### One-liner upgrade to latest stable
|
||||
|
||||
```bash
|
||||
bash <(curl -sL https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh)
|
||||
```
|
||||
|
||||
### Upgrade to a specific branch/version (upstream)
|
||||
|
||||
```bash
|
||||
bash <(curl -sL https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh) -b v2.5.5-dev
|
||||
bash <(curl -sL https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh) -b 2.4.4
|
||||
```
|
||||
|
||||
### Upgrade using master3395 fork
|
||||
|
||||
```bash
|
||||
sudo bash <(curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel_upgrade.sh) -b v2.5.5-dev
|
||||
```
|
||||
|
||||
Or download then run:
|
||||
|
||||
```bash
|
||||
curl -sL -o cyberpanel_upgrade.sh https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel_upgrade.sh
|
||||
chmod +x cyberpanel_upgrade.sh
|
||||
sudo bash cyberpanel_upgrade.sh -b v2.5.5-dev
|
||||
```
|
||||
|
||||
**Upgrade options:**
|
||||
|
||||
| Option | Example | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-b BRANCH` / `--branch BRANCH` | `-b v2.5.5-dev` | Upgrade to this branch/tag |
|
||||
| `--no-system-update` | (optional) | Skip full `yum/dnf update -y` (faster if system is already updated) |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
sudo bash cyberpanel_upgrade.sh -b v2.5.5-dev
|
||||
sudo bash cyberpanel_upgrade.sh -b 2.4.4
|
||||
sudo bash cyberpanel_upgrade.sh -b stable
|
||||
sudo bash cyberpanel_upgrade.sh -b v2.5.5-dev --no-system-update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Downgrade
|
||||
|
||||
Downgrade is done by running the **upgrade** script with the **older** branch/version.
|
||||
|
||||
### Downgrade to 2.4.4 (or another older version)
|
||||
|
||||
```bash
|
||||
sudo bash <(curl -sL https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh) -b 2.4.4
|
||||
```
|
||||
|
||||
Or with master3395 fork:
|
||||
|
||||
```bash
|
||||
curl -sL -o cyberpanel_upgrade.sh https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel_upgrade.sh
|
||||
chmod +x cyberpanel_upgrade.sh
|
||||
sudo bash cyberpanel_upgrade.sh -b 2.4.4
|
||||
```
|
||||
|
||||
### Downgrade from v2.5.5-dev to stable
|
||||
|
||||
```bash
|
||||
sudo bash cyberpanel_upgrade.sh -b stable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-upgrade (download upgrade script only)
|
||||
|
||||
From the interactive menu: **Option 5 – Pre-Upgrade**.
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
# Download latest upgrade script to /usr/local/
|
||||
curl -sL -o /usr/local/cyberpanel_upgrade.sh https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh
|
||||
chmod 700 /usr/local/cyberpanel_upgrade.sh
|
||||
|
||||
# Run when ready
|
||||
sudo /usr/local/cyberpanel_upgrade.sh -b v2.5.5-dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Action | Command |
|
||||
|--------|---------|
|
||||
| **Install (official)** | `sh <(curl https://cyberpanel.net/install.sh)` |
|
||||
| **Install stable (master3395)** | `curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh \| sudo bash -s --` |
|
||||
| **Install v2.5.5-dev** | `curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh \| sudo bash -s -- -b v2.5.5-dev` |
|
||||
| **Upgrade to v2.5.5-dev** | `sudo bash <(curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel_upgrade.sh) -b v2.5.5-dev` |
|
||||
| **Upgrade to 2.4.4** | `sudo bash <(curl -sL .../cyberpanel_upgrade.sh) -b 2.4.4` |
|
||||
| **Downgrade to 2.4.4** | Same as upgrade: `... cyberpanel_upgrade.sh -b 2.4.4` |
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Run as **root** or with **sudo**; if using `curl | sudo bash`, use `bash -s --` and put branch/options after `--` so they are passed to the script.
|
||||
- MariaDB version can be set at install with `--mariadb-version 10.11`, `11.8`, or `12.1`.
|
||||
- Upgrade script branch: `-b v2.5.5-dev`, `-b 2.4.4`, `-b stable`, or `-b <commit-hash>`.
|
||||
@@ -19,6 +19,27 @@ The config used `Quota yes`, but Pure-FTPd expects **`Quota maxfiles:maxsize`**
|
||||
- **install/pure-ftpd/pure-ftpd.conf** and **install/pure-ftpd-one/pure-ftpd.conf**: `Quota yes` → `Quota 100000:100000`.
|
||||
- **websiteFunctions/website.py** (`enableFTPQuota`): sed/echo now write `Quota 100000:100000` instead of `Quota yes` (or tabs).
|
||||
|
||||
## One-time fix on server (if "Enable" still breaks it)
|
||||
Run on the server as root (copy script from repo or run inline):
|
||||
|
||||
**Option A – script (repo root: `fix-pureftpd-quota-once.sh`):**
|
||||
```bash
|
||||
sudo bash /path/to/fix-pureftpd-quota-once.sh
|
||||
```
|
||||
|
||||
**Option B – inline:**
|
||||
```bash
|
||||
sudo sed -i 's/^Quota.*/Quota 100000:100000/' /etc/pure-ftpd/pure-ftpd.conf
|
||||
# If TLS 1 is set but cert missing, disable TLS:
|
||||
sudo sed -i 's/^TLS[[:space:]]*1/TLS 0/' /etc/pure-ftpd/pure-ftpd.conf
|
||||
sudo systemctl start pure-ftpd
|
||||
```
|
||||
Then deploy the latest panel code so "Enable" uses the correct Quota syntax.
|
||||
|
||||
## Code safeguards (enableFTPQuota)
|
||||
- **Backup before modify**: Timestamped backup of `pure-ftpd.conf` and `pureftpd-mysql.conf` before any change.
|
||||
- **Safety net before restart**: If the Quota line is not valid (`Quota maxfiles:maxsize`), it is corrected to `Quota 100000:100000` so Pure-FTPd never gets an invalid line on restart.
|
||||
|
||||
## Reference
|
||||
- Upstream: https://github.com/jedisct1/pure-ftpd/blob/master/pure-ftpd.conf.in (comment: "Quota 1000:10").
|
||||
- `pure-ftpd --help`: `-n --quota <opt>`.
|
||||
|
||||
76
to-do/V2.5.5-DEV-BRANCH-COMPATIBILITY.md
Normal file
76
to-do/V2.5.5-DEV-BRANCH-COMPATIBILITY.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# v2.5.5-dev Branch Compatibility Check
|
||||
|
||||
**Date:** 2026-02-04
|
||||
**Branches compared:** [v2.5.5-dev](https://github.com/master3395/cyberpanel/tree/v2.5.5-dev) vs [v2.4.4](https://github.com/master3395/cyberpanel/tree/v2.4.4) vs [stable](https://github.com/master3395/cyberpanel/tree/stable)
|
||||
|
||||
---
|
||||
|
||||
## 1. Will your v2.5.5-dev changes work?
|
||||
|
||||
**Yes.** The Ban IP / Firewall Banned IPs changes on v2.5.5-dev are self-contained and consistent:
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| **plogical/firewallUtilities.py** | `blockIP` / `unblockIP` use `result == 1` for success; log file path is `/usr/local/CyberCP/data/blocked_ips.log` (writable by `cyberpanel`). |
|
||||
| **plogical/processUtilities.py** | When running as root, `executioner` uses `normalExecutioner` return value (1 = success, 0 = fail). |
|
||||
| **firewall/firewallManager.py** | `addBannedIP` uses `FirewallUtilities.blockIP`; ACL and errors return JSON with `error_message` and `error`; rollback on block failure. |
|
||||
| **firewall/views.py** | `addBannedIP` parses JSON body and calls `fm.addBannedIP(userID, request_data)`. |
|
||||
| **firewall/urls.py** | Routes `getBannedIPs`, `addBannedIP`, `modifyBannedIP`, `removeBannedIP`, `deleteBannedIP`, `exportBannedIPs`, `importBannedIPs` are present. |
|
||||
| **baseTemplate/views.py** | `blockIPAddress` uses `FirewallUtilities.blockIP` (no subprocess). |
|
||||
| **baseTemplate (homePage + system-status.js)** | Ban IP calls `/firewall/addBannedIP` with `ip`, `reason`, `duration`; shows server `error_message` in notifications. |
|
||||
|
||||
**Deployment requirements (already applied on your server):**
|
||||
|
||||
- `/usr/local/CyberCP/data` owned by `cyberpanel:cyberpanel` (for `banned_ips.json` and `blocked_ips.log`).
|
||||
- Deploy updated files from v2.5.5-dev into `/usr/local/CyberCP/` and restart `lscpd`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Does v2.5.5-dev have all functions from v2.4.4 and stable?
|
||||
|
||||
**Summary:**
|
||||
|
||||
- **v2.5.5-dev has more than v2.4.4 and stable** in terms of features (Banned IPs, FTP quotas, email limits, user management, bandwidth management, etc.). It is a development branch built on top of the same base.
|
||||
- **v2.5.5-dev is missing a few items that exist only on stable** (backports or stable-only fixes). Nothing critical for the Ban IP feature; mainly scripts and tests.
|
||||
|
||||
### v2.5.5-dev has everything from v2.4.4 that matters
|
||||
|
||||
- v2.4.4 is older (fewer commits). v2.5.5-dev contains the same core apps (firewall, baseTemplate, loginSystem, backup, etc.) plus many additions.
|
||||
- **Firewall:** v2.4.4 has no Banned IPs routes; v2.5.5-dev adds the full Banned IPs feature (getBannedIPs, addBannedIP, modify, remove, delete, export, import).
|
||||
|
||||
### v2.5.5-dev vs stable
|
||||
|
||||
- **Stable has ~86 files that differ from v2.5.5-dev**, including:
|
||||
- **run_migration.py** – present on stable, **not** on v2.5.5-dev (migration helper).
|
||||
- **test_firewall_blocking.py** – test script on stable.
|
||||
- **rollback_phpmyadmin_redirect.sh** – rollback script on stable.
|
||||
- **install/**, **plogical/** (e.g. mysqlUtilities, upgrade, backup, sslUtilities), **pluginInstaller** – some fixes/improvements on stable that may not be in v2.5.5-dev.
|
||||
|
||||
- **v2.5.5-dev has 3652+ files changed vs stable** – it has many more features (user management, website functions, bandwidth, FTP quotas, email limits, Banned IPs, etc.).
|
||||
|
||||
So:
|
||||
|
||||
- **Feature parity:** v2.5.5-dev has **all the main functions** from v2.4.4 and **adds** Banned IPs and other features. It does **not** lack core features that v2.4.4 or stable have.
|
||||
- **Stable-only extras:** Stable has a few **extra** scripts/fixes (e.g. `run_migration.py`, `rollback_phpmyadmin_redirect.sh`, some plogical/install changes). If you need those, you can cherry-pick or merge from stable into v2.5.5-dev.
|
||||
|
||||
---
|
||||
|
||||
## 3. Directory layout comparison
|
||||
|
||||
| In stable, not in v2.5.5-dev (by name) | In v2.5.5-dev, not in stable |
|
||||
|----------------------------------------|------------------------------|
|
||||
| emailMarketing (or different layout) | bin, docs, modules, public, sql, test, to-do |
|
||||
| examplePlugin | (v2.5.5-dev has more structure) |
|
||||
| guides | |
|
||||
| scripts | |
|
||||
| testPlugin | test (different name) |
|
||||
|
||||
Your current repo (v2.5.5-dev) includes `emailMarketing`, `websiteFunctions`, `firewall`, `baseTemplate`, etc. The diff is mostly naming (e.g. test vs testPlugin) and stable having a few extra scripts/docs.
|
||||
|
||||
---
|
||||
|
||||
## 4. Recommendation
|
||||
|
||||
1. **Use v2.5.5-dev as-is for Ban IP and current features** – the changes are consistent and will work with the deployment steps above.
|
||||
2. **Periodically merge or cherry-pick from stable** into v2.5.5-dev if you want stable’s migration script, phpMyAdmin rollback script, and any plogical/install fixes.
|
||||
3. **You do have all the functions from v2.4.4 and stable** in the sense of core product behavior; v2.5.5-dev adds more (Banned IPs, etc.) and is only missing some optional stable-only scripts/fixes.
|
||||
@@ -38,7 +38,15 @@ FTP Quota Management - CyberPanel
|
||||
<!-- Enable FTP Quota Section -->
|
||||
<div class="row mb-4 ftp-quota-info">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<div id="ftpDisabledWarning" class="alert alert-warning" style="display: none;">
|
||||
<h5><i class="fas fa-exclamation-triangle"></i> Pure-FTPd is not running</h5>
|
||||
<p class="mb-0">Please enable Pure-FTPd first (e.g. from <strong>Server Status → Services</strong>) before enabling the FTP Quota system.</p>
|
||||
</div>
|
||||
<div id="ftpQuotaAlreadyEnabled" class="alert alert-success" style="display: none;">
|
||||
<h5><i class="fas fa-check-circle"></i> FTP Quota system is already enabled</h5>
|
||||
<p class="mb-0">Pure-FTPd is running with quota support. You can manage per-user quotas in the table below.</p>
|
||||
</div>
|
||||
<div id="ftpQuotaInfoDefault" class="alert alert-info">
|
||||
<h5><i class="fas fa-info-circle"></i> FTP Quota System</h5>
|
||||
<p>Enable and manage individual FTP user quotas. This allows you to set storage limits for each FTP user.</p>
|
||||
<button type="button" id="btnEnableFTPQuota" class="btn btn-primary" onclick="enableFTPQuota()">
|
||||
@@ -159,10 +167,48 @@ function showNotification(type, message) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateFTPQuotaStatus() {
|
||||
$.ajax({
|
||||
url: '{% url "getFTPQuotaStatus" %}',
|
||||
type: 'POST',
|
||||
data: { 'csrfmiddlewaretoken': getCsrfToken() },
|
||||
headers: { 'X-CSRFToken': getCsrfToken() },
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var warning = document.getElementById('ftpDisabledWarning');
|
||||
var already = document.getElementById('ftpQuotaAlreadyEnabled');
|
||||
var defaultBox = document.getElementById('ftpQuotaInfoDefault');
|
||||
var btn = document.getElementById('btnEnableFTPQuota');
|
||||
var btnText = document.getElementById('btnEnableFTPQuotaText');
|
||||
if (!data || data.status !== 1) return;
|
||||
if (!data.ftp_running) {
|
||||
if (warning) warning.style.display = 'block';
|
||||
if (already) already.style.display = 'none';
|
||||
if (defaultBox) defaultBox.style.display = 'none';
|
||||
if (btn) { btn.disabled = true; btn.title = 'Start Pure-FTPd from Server Status → Services first'; }
|
||||
if (btnText) btnText.textContent = 'Enable FTP Quota System (start Pure-FTPd first)';
|
||||
} else if (data.quota_configured) {
|
||||
if (warning) warning.style.display = 'none';
|
||||
if (already) already.style.display = 'block';
|
||||
if (defaultBox) defaultBox.style.display = 'none';
|
||||
if (btn) { btn.disabled = true; btn.title = ''; }
|
||||
if (btnText) btnText.textContent = 'FTP Quota system is already enabled';
|
||||
} else {
|
||||
if (warning) warning.style.display = 'none';
|
||||
if (already) already.style.display = 'none';
|
||||
if (defaultBox) defaultBox.style.display = 'block';
|
||||
if (btn) { btn.disabled = false; btn.title = ''; }
|
||||
if (btnText) btnText.textContent = 'Enable FTP Quota System';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function enableFTPQuota() {
|
||||
var btn = document.getElementById('btnEnableFTPQuota');
|
||||
var btnText = document.getElementById('btnEnableFTPQuotaText');
|
||||
if (!btn || !btnText) return;
|
||||
if (btn.disabled) return;
|
||||
var originalHtml = btnText.innerHTML;
|
||||
btn.disabled = true;
|
||||
btnText.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Enabling...';
|
||||
@@ -175,6 +221,7 @@ function enableFTPQuota() {
|
||||
success: function(data) {
|
||||
if (data && data.status === 1) {
|
||||
showNotification('success', data.message || 'FTP quota system enabled successfully');
|
||||
updateFTPQuotaStatus();
|
||||
refreshQuotas();
|
||||
} else {
|
||||
showNotification('error', (data && (data.message || data.error_message)) || 'Enable failed');
|
||||
@@ -198,6 +245,7 @@ function enableFTPQuota() {
|
||||
complete: function() {
|
||||
btn.disabled = false;
|
||||
btnText.innerHTML = originalHtml;
|
||||
updateFTPQuotaStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -294,8 +342,9 @@ function saveQuota() {
|
||||
});
|
||||
}
|
||||
|
||||
// Load quotas on page load
|
||||
// Load status and quotas on page load
|
||||
$(document).ready(function() {
|
||||
updateFTPQuotaStatus();
|
||||
refreshQuotas();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -208,6 +208,7 @@ urlpatterns = [
|
||||
path('fixSubdomainLogsAction', views.fixSubdomainLogsAction, name='fixSubdomainLogsAction'),
|
||||
|
||||
# FTP Quota Management (API endpoints only; page is at /ftp/quotaManagement)
|
||||
path('getFTPQuotaStatus', views.getFTPQuotaStatus, name='getFTPQuotaStatus'),
|
||||
path('enableFTPQuota', views.enableFTPQuota, name='enableFTPQuota'),
|
||||
path('getFTPQuotas', views.getFTPQuotas, name='getFTPQuotas'),
|
||||
path('updateFTPQuota', views.updateFTPQuota, name='updateFTPQuota'),
|
||||
|
||||
@@ -2263,6 +2263,14 @@ def securityManagementPage(request):
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def getFTPQuotaStatus(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.getFTPQuotaStatus(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def enableFTPQuota(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
|
||||
@@ -8799,9 +8799,52 @@ StrictHostKeyChecking no
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}')
|
||||
return False
|
||||
|
||||
def getFTPQuotaStatus(self, userID=None, data=None):
|
||||
"""
|
||||
Return FTP quota status for the UI: ftp_running, quota_configured.
|
||||
Used on page load to show the right message and button state.
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
if not (currentACL.get('admin', 0) == 1):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
if os.path.exists('/etc/lsb-release'):
|
||||
ftp_service = 'pure-ftpd-mysql'
|
||||
else:
|
||||
ftp_service = 'pure-ftpd'
|
||||
conf_path = '/etc/pure-ftpd/pure-ftpd.conf'
|
||||
ftp_running = False
|
||||
quota_configured = False
|
||||
try:
|
||||
out = ProcessUtilities.outputExecutioner(
|
||||
"systemctl is-active %s 2>/dev/null || true" % ftp_service, 'root', True)
|
||||
ftp_running = bool(out and out.strip() == 'active')
|
||||
except Exception:
|
||||
pass
|
||||
if ftp_running and os.path.exists(conf_path):
|
||||
try:
|
||||
quota_line = ProcessUtilities.outputExecutioner(
|
||||
"grep -E '^Quota[[:space:]]+[0-9]+:[0-9]+' %s 2>/dev/null || true" % conf_path, 'root', True)
|
||||
quota_configured = bool(quota_line and quota_line.strip())
|
||||
except Exception:
|
||||
pass
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'ftp_running': ftp_running,
|
||||
'quota_configured': quota_configured,
|
||||
'ftp_service': ftp_service,
|
||||
}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
except Exception as e:
|
||||
data_ret = {'status': 0, 'message': str(e)}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
|
||||
def enableFTPQuota(self, userID=None, data=None):
|
||||
"""
|
||||
Enable FTP quota: ensure Quota yes in existing config (do not overwrite), restart Pure-FTPd.
|
||||
Enable FTP quota: ensure Quota maxfiles:maxsize in config, start/restart Pure-FTPd if needed.
|
||||
If Pure-FTPd is already running and config already has a valid Quota line, return success
|
||||
without touching config or restarting (avoids breaking a working setup).
|
||||
Uses correct service name (pure-ftpd-mysql on Debian/Ubuntu, pure-ftpd on RHEL/Alma).
|
||||
"""
|
||||
try:
|
||||
@@ -8819,8 +8862,45 @@ StrictHostKeyChecking no
|
||||
ftp_service = 'pure-ftpd'
|
||||
|
||||
conf_path = '/etc/pure-ftpd/pure-ftpd.conf'
|
||||
|
||||
# Early success: if Pure-FTPd is already active and config has valid Quota line, do nothing
|
||||
try:
|
||||
out = ProcessUtilities.outputExecutioner(
|
||||
"systemctl is-active %s 2>/dev/null || true" % ftp_service, 'root', True)
|
||||
if out and out.strip() == 'active':
|
||||
quota_line = ProcessUtilities.outputExecutioner(
|
||||
"grep -E '^Quota[[:space:]]+[0-9]+:[0-9]+' %s 2>/dev/null || true" % conf_path, 'root', True)
|
||||
if quota_line and quota_line.strip():
|
||||
logging.CyberCPLogFileWriter.writeToFile("FTP quota already enabled and Pure-FTPd running")
|
||||
data_ret = {'status': 1, 'message': 'FTP quota system is already enabled and Pure-FTPd is running.'}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Require Pure-FTPd to be running before enabling quota (avoid confusing failures)
|
||||
try:
|
||||
out = ProcessUtilities.outputExecutioner(
|
||||
"systemctl is-active %s 2>/dev/null || true" % ftp_service, 'root', True)
|
||||
if not (out and out.strip() == 'active'):
|
||||
msg = ('Pure-FTPd is not running. Please enable Pure-FTPd first '
|
||||
'(e.g. from Server Status → Services) before enabling the FTP Quota system.')
|
||||
data_ret = {'status': 0, 'message': msg}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Only ensure Quota is enabled; do not overwrite existing config (preserves DB credentials, paths)
|
||||
if os.path.exists(conf_path):
|
||||
# Backup current config before we change anything (so we can restore if restart fails)
|
||||
try:
|
||||
from datetime import datetime
|
||||
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
ProcessUtilities.executioner(
|
||||
'cp %s /etc/pure-ftpd/pure-ftpd.conf.backup.%s' % (conf_path, ts), 'root', True)
|
||||
ProcessUtilities.executioner(
|
||||
'cp /etc/pure-ftpd/pureftpd-mysql.conf /etc/pure-ftpd/pureftpd-mysql.conf.backup.%s 2>/dev/null || true' % ts, 'root', True)
|
||||
except Exception:
|
||||
pass
|
||||
# If service is not running, try restoring latest backup (in case a previous run overwrote working config)
|
||||
try:
|
||||
out = ProcessUtilities.outputExecutioner(
|
||||
@@ -8839,7 +8919,7 @@ StrictHostKeyChecking no
|
||||
ProcessUtilities.executioner(
|
||||
"grep -q '^Quota' %s && sed -i 's/^Quota.*/Quota 100000:100000/' %s || echo 'Quota 100000:100000' >> %s" % (conf_path, conf_path, conf_path),
|
||||
'root', True)
|
||||
logging.CyberCPLogFileWriter.writeToFile("Set Quota yes in existing pure-ftpd.conf")
|
||||
logging.CyberCPLogFileWriter.writeToFile("Set Quota 100000:100000 in existing pure-ftpd.conf")
|
||||
else:
|
||||
# First-time: copy from repo
|
||||
from datetime import datetime
|
||||
@@ -8850,7 +8930,37 @@ StrictHostKeyChecking no
|
||||
if os.path.exists('/usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf'):
|
||||
ProcessUtilities.executioner(
|
||||
'cp /usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf /etc/pure-ftpd/pureftpd-mysql.conf', 'root', True)
|
||||
|
||||
|
||||
# Safety net: ensure Quota line is valid before restart (Pure-FTPd rejects "Quota yes")
|
||||
try:
|
||||
quota_check = ProcessUtilities.outputExecutioner(
|
||||
"grep -E '^Quota[[:space:]]+[0-9]+:[0-9]+' %s 2>/dev/null || true" % conf_path, 'root', True)
|
||||
if not (quota_check and quota_check.strip()):
|
||||
ProcessUtilities.executioner(
|
||||
"grep -q '^Quota' %s && sed -i 's/^Quota.*/Quota 100000:100000/' %s || echo 'Quota 100000:100000' >> %s" % (conf_path, conf_path, conf_path),
|
||||
'root', True)
|
||||
logging.CyberCPLogFileWriter.writeToFile("Corrected invalid Quota line in pure-ftpd.conf before restart")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Final check: if Quota line still invalid (e.g. old panel code wrote "Quota yes"), restore backup and abort
|
||||
try:
|
||||
quota_final = ProcessUtilities.outputExecutioner(
|
||||
"grep '^Quota' %s 2>/dev/null || true" % conf_path, 'root', True)
|
||||
if quota_final and 'yes' in quota_final.lower():
|
||||
# Invalid line still present - restore backup and do not restart
|
||||
ProcessUtilities.executioner(
|
||||
"ls -t /etc/pure-ftpd/pure-ftpd.conf.backup.* 2>/dev/null | head -1 | xargs -r -I {} cp {} /etc/pure-ftpd/pure-ftpd.conf",
|
||||
'root', True)
|
||||
logging.CyberCPLogFileWriter.writeToFile("Aborted: invalid Quota line (yes) still present; restored backup")
|
||||
msg = ('Pure-FTPd config was invalid (Quota line). Restored previous config. '
|
||||
'Please deploy the latest panel code from v2.5.5-dev and run the one-time fix on the server: '
|
||||
'sudo sed -i "s/^Quota.*/Quota 100000:100000/" /etc/pure-ftpd/pure-ftpd.conf && sudo systemctl start pure-ftpd')
|
||||
data_ret = {'status': 0, 'message': msg}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Restart Pure-FTPd
|
||||
logging.CyberCPLogFileWriter.writeToFile("Restarting Pure-FTPd service (%s)..." % ftp_service)
|
||||
ProcessUtilities.executioner('systemctl restart %s' % ftp_service, 'root', True)
|
||||
@@ -8866,6 +8976,17 @@ StrictHostKeyChecking no
|
||||
logging.CyberCPLogFileWriter.writeToFile("FTP quota system enabled successfully")
|
||||
data_ret = {'status': 1, 'message': 'FTP quota system enabled successfully'}
|
||||
else:
|
||||
# Restore backup so service can be started again from Services page
|
||||
try:
|
||||
ProcessUtilities.executioner(
|
||||
"ls -t /etc/pure-ftpd/pure-ftpd.conf.backup.* 2>/dev/null | head -1 | xargs -r -I {} cp {} /etc/pure-ftpd/pure-ftpd.conf",
|
||||
'root', True)
|
||||
ProcessUtilities.executioner(
|
||||
"ls -t /etc/pure-ftpd/pureftpd-mysql.conf.backup.* 2>/dev/null | head -1 | xargs -r -I {} cp {} /etc/pure-ftpd/pureftpd-mysql.conf",
|
||||
'root', True)
|
||||
logging.CyberCPLogFileWriter.writeToFile("Restored pure-ftpd config backup after failed start")
|
||||
except Exception:
|
||||
pass
|
||||
# Capture failure reason for the user
|
||||
try:
|
||||
status_out = ProcessUtilities.outputExecutioner(
|
||||
@@ -8874,7 +8995,7 @@ StrictHostKeyChecking no
|
||||
except Exception:
|
||||
status_preview = ''
|
||||
logging.CyberCPLogFileWriter.writeToFile("Pure-FTPd service not active after restart")
|
||||
msg = 'Pure-FTPd did not start. Run: systemctl status %s' % ftp_service
|
||||
msg = 'Pure-FTPd did not start. Config was restored. Run: systemctl status %s' % ftp_service
|
||||
if status_preview:
|
||||
msg += '. ' + status_preview
|
||||
data_ret = {'status': 0, 'message': msg}
|
||||
|
||||
Reference in New Issue
Block a user