Compare commits
111 Commits
a94e329fcf
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 08e8e8b5f0 | |||
| 6181da7b42 | |||
| 6a586ef721 | |||
| 43a94884e4 | |||
| da02dcfd61 | |||
| baf058d1dc | |||
| 1c3f12744b | |||
| 55dc21f6e5 | |||
| b0873bbf13 | |||
| cf362c2adf | |||
| 9471355e77 | |||
| d159dd28d8 | |||
| 01b63c6ad4 | |||
| 63e6cf067e | |||
| ca7ec62e02 | |||
| 8af1ca881b | |||
| dc6ce93eef | |||
| 62ee9674d8 | |||
| e360f12aab | |||
| a805676be5 | |||
| 54e4d5b67f | |||
| 6dfc47d831 | |||
| 172ef41fc7 | |||
| 429ee62510 | |||
| 9b6652f512 | |||
| 5902ea990d | |||
| e1a3b1cf90 | |||
| adbe5c14d5 | |||
| 8477c8d7e1 | |||
| ae1503b928 | |||
| 50a996bce3 | |||
| 907e90f78a | |||
| 5a539e4d31 | |||
| 12973423ef | |||
| bc44f7bb28 | |||
| c697d90b44 | |||
| 06ec13ead8 | |||
| cf617656f1 | |||
| 5e31a1584a | |||
| 04e6df318f | |||
| 076be62f99 | |||
| e01ee36e6f | |||
| fc24beac94 | |||
| 46532f5411 | |||
| e92c88f9aa | |||
| d8d7505c63 | |||
| 622f100250 | |||
| 8bf9e7df26 | |||
| d994c5c1d7 | |||
| 849ba34f60 | |||
| a4868091d3 | |||
| cc89b2ffed | |||
| c5239bd939 | |||
| 2bf8c4f275 | |||
| 6261fabf7a | |||
| 7370e90779 | |||
| e7c73417a2 | |||
| 9486d0604a | |||
| a2b24d654d | |||
| 3075ad34a5 | |||
| df3888b3c2 | |||
| d38ebdc464 | |||
| 7f9ecfac81 | |||
| e1576dc869 | |||
| 95c5cfdf61 | |||
| ff1d8f1ce8 | |||
| e00fdec104 | |||
| e34696dada | |||
| 106ebbd089 | |||
| a5ce49d635 | |||
| d00484a139 | |||
| 57d4350989 | |||
| 2eda47a480 | |||
| e87225e2aa | |||
| f4c99ed94d | |||
| e9efb3879a | |||
| ff8c01a169 | |||
| a4adf9a398 | |||
| 729583581c | |||
| cf391147bf | |||
| c71b2ecf8e | |||
| da10729635 | |||
| 168e8f5909 | |||
| bfc43e749c | |||
| 3844fddda8 | |||
| 34cea9627a | |||
| c90f7155ce | |||
| ba6848e113 | |||
| 3a14df27ae | |||
| 746b861640 | |||
| 333bc756ec | |||
| 0f4ea3ff9b | |||
| 94c486717f | |||
| ef993c1bc6 | |||
| 2ab02fdc50 | |||
| e2fca67df2 | |||
| a180e40da4 | |||
| 808e4abe1d | |||
| cb5352db22 | |||
| ce65004c79 | |||
| 37de22241c | |||
| ebeb496c7c | |||
| 2c4efbc805 | |||
| 629176d301 | |||
| 7382c9c2ac | |||
| b1062f4d40 | |||
| 61fe915c4c | |||
| 472d770463 | |||
| 7ad35f59d8 | |||
| 12101901f8 | |||
| 3ad1963dfe |
@@ -0,0 +1,406 @@
|
||||
# Scanner Installation Issues & Fixes
|
||||
|
||||
**Date:** 2026-04-21
|
||||
**Reported Issues:**
|
||||
1. ClamAV installation fails with "No such file or directory: /scripts/check_cpanel_rpms"
|
||||
2. No way to install individual scanners from dedicated menus (e.g., Maldet submenu)
|
||||
|
||||
---
|
||||
|
||||
## Issue 1: ClamAV Installation Failure
|
||||
|
||||
### Current Behavior
|
||||
|
||||
```bash
|
||||
[1/4] Installing ClamAV...
|
||||
→ Installing via cPanel package manager...
|
||||
/root/linux-server-management-toolkit/modules/security/malware-scanner.sh: line 294: /scripts/check_cpanel_rpms: No such file or directory
|
||||
|
||||
✗ Exited with code: 127
|
||||
```
|
||||
|
||||
### Root Cause
|
||||
|
||||
The script tries to use `/scripts/check_cpanel_rpms` which:
|
||||
- May not exist on all cPanel installations
|
||||
- May have been removed/changed in newer cPanel versions
|
||||
- May require specific permissions or cPanel configuration
|
||||
|
||||
**Location:** `/root/server-toolkit-beta/modules/security/malware-scanner.sh` lines 223-226
|
||||
|
||||
### Current Code (PROBLEMATIC)
|
||||
```bash
|
||||
if [ -f "/usr/local/cpanel/cpanel" ]; then
|
||||
# cPanel method - use cPanel's package management only
|
||||
if rpm -qa 2>/dev/null | grep -q "cpanel-clamav"; then
|
||||
echo -e "${GREEN}✓ ClamAV already installed (cPanel)${NC}"
|
||||
else
|
||||
echo " → Installing via cPanel package manager..."
|
||||
/scripts/update_local_rpm_versions --edit target_settings.clamav installed 2>/dev/null || true
|
||||
/scripts/check_cpanel_rpms --fix --targets=clamav 2>&1 | tail -3 # ← FAILS HERE
|
||||
fi
|
||||
# IMPORTANT: Don't fall through to standard yum - cPanel packages conflict!
|
||||
```
|
||||
|
||||
### The Fix
|
||||
|
||||
**Strategy:** If cPanel scripts don't work, fall back to standard package managers with error handling
|
||||
|
||||
**Updated Code:**
|
||||
```bash
|
||||
if [ -f "/usr/local/cpanel/cpanel" ]; then
|
||||
# cPanel method - use cPanel's package management
|
||||
if rpm -qa 2>/dev/null | grep -q "cpanel-clamav"; then
|
||||
echo -e "${GREEN}✓ ClamAV already installed (cPanel)${NC}"
|
||||
else
|
||||
echo " → Installing via cPanel package manager..."
|
||||
|
||||
# Try cPanel scripts, but fall back to standard package manager if they fail
|
||||
if [ -f "/scripts/check_cpanel_rpms" ] && [ -f "/scripts/update_local_rpm_versions" ]; then
|
||||
/scripts/update_local_rpm_versions --edit target_settings.clamav installed 2>/dev/null || true
|
||||
if /scripts/check_cpanel_rpms --fix --targets=clamav 2>&1 | tail -3; then
|
||||
: # Success, continue
|
||||
else
|
||||
# cPanel scripts failed, try standard yum
|
||||
echo " → cPanel scripts unavailable, trying standard package manager..."
|
||||
yum install -y clamav clamav-update 2>&1 | grep -E "Installing|Installed|already" || echo " (installation in progress)"
|
||||
fi
|
||||
else
|
||||
# Scripts don't exist, use standard package manager
|
||||
echo " → cPanel tools not available, using standard package manager..."
|
||||
yum install -y clamav clamav-update 2>&1 | grep -E "Installing|Installed|already" || echo " (installation in progress)"
|
||||
fi
|
||||
fi
|
||||
# Don't fall through - we've handled installation above
|
||||
elif command -v yum &>/dev/null; then
|
||||
# Non-cPanel RHEL/CentOS systems
|
||||
echo " → Installing via yum..."
|
||||
yum install -y clamav clamav-update 2>&1 | grep -E "Installing|Installed|already" || echo " (installation in progress)"
|
||||
# ... rest of OS detection
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Gracefully falls back if cPanel scripts missing
|
||||
- ✅ Still tries cPanel first if available
|
||||
- ✅ Provides user feedback on what's happening
|
||||
- ✅ Doesn't crash with exit code 127
|
||||
|
||||
---
|
||||
|
||||
## Issue 2: No Individual Scanner Installation
|
||||
|
||||
### Current Behavior
|
||||
|
||||
**In Maldet Submenu:**
|
||||
```
|
||||
Select scan type:
|
||||
1. Scan entire server
|
||||
2. Scan all user accounts
|
||||
3. Scan specific user account
|
||||
4. Scan specific domain
|
||||
5. Scan custom path
|
||||
6. Update Maldet signatures
|
||||
7. View Maldet results
|
||||
0. Back to main menu
|
||||
```
|
||||
|
||||
**No install option.** If Maldet isn't installed:
|
||||
- User tries to scan
|
||||
- Script detects Maldet missing
|
||||
- Script asks "Install Maldet now? (yes/no)"
|
||||
- Calls `install_all_scanners` which installs ALL scanners
|
||||
- Overkill and wastes time if user only wants Maldet
|
||||
|
||||
### The Fix
|
||||
|
||||
**Add individual scanner installation functions:**
|
||||
|
||||
```bash
|
||||
install_maldet_only() {
|
||||
echo ""
|
||||
print_banner "Installing Maldet (Linux Malware Detection)"
|
||||
echo ""
|
||||
|
||||
if command -v maldet &>/dev/null || [ -f "/usr/local/sbin/maldet" ]; then
|
||||
echo -e "${GREEN}✓ Maldet is already installed${NC}"
|
||||
echo ""
|
||||
read -p "Press Enter to continue..."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Maldet is a fast, Linux-specific malware scanner"
|
||||
echo "Repository: https://github.com/rfxn/maldet"
|
||||
echo ""
|
||||
echo "Installing via wget..."
|
||||
echo ""
|
||||
|
||||
cd /tmp || return 1
|
||||
if wget -q https://www.rfxn.com/downloads/maldetect-latest.tar.gz; then
|
||||
tar xzf maldetect-latest.tar.gz
|
||||
cd maldetect-*
|
||||
if bash install.sh > /tmp/maldet-install.log 2>&1; then
|
||||
echo -e "${GREEN}✓ Maldet installed successfully${NC}"
|
||||
|
||||
# Update signatures
|
||||
echo ""
|
||||
echo "Updating malware signatures..."
|
||||
if command -v maldet &>/dev/null; then
|
||||
maldet -u > /dev/null 2>&1 &
|
||||
echo " (signatures updating in background)"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Installation failed. Check /tmp/maldet-install.log${NC}"
|
||||
fi
|
||||
cd /tmp
|
||||
rm -rf maldetect-*
|
||||
else
|
||||
echo -e "${RED}✗ Failed to download Maldet${NC}"
|
||||
echo "Try: wget https://www.rfxn.com/downloads/maldetect-latest.tar.gz"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "Press Enter to continue..."
|
||||
}
|
||||
|
||||
install_clamav_only() {
|
||||
echo ""
|
||||
print_banner "Installing ClamAV (Open Source Antivirus)"
|
||||
echo ""
|
||||
|
||||
if command -v clamscan &>/dev/null; then
|
||||
echo -e "${GREEN}✓ ClamAV is already installed${NC}"
|
||||
echo ""
|
||||
read -p "Press Enter to continue..."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Installing ClamAV..."
|
||||
|
||||
if command -v yum &>/dev/null; then
|
||||
yum install -y clamav clamav-daemon clamav-update 2>&1 | tail -5
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
apt-get update > /dev/null 2>&1
|
||||
apt-get install -y clamav clamav-daemon 2>&1 | tail -5
|
||||
else
|
||||
echo -e "${RED}✗ No compatible package manager found${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v clamscan &>/dev/null; then
|
||||
echo -e "${GREEN}✓ ClamAV installed successfully${NC}"
|
||||
|
||||
# Update signatures
|
||||
echo ""
|
||||
echo "Updating virus signatures..."
|
||||
if command -v freshclam &>/dev/null; then
|
||||
freshclam > /dev/null 2>&1 &
|
||||
echo " (signatures updating in background)"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Installation may have failed${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "Press Enter to continue..."
|
||||
}
|
||||
|
||||
install_rkhunter_only() {
|
||||
echo ""
|
||||
print_banner "Installing RKHunter (Rootkit Detection)"
|
||||
echo ""
|
||||
|
||||
if command -v rkhunter &>/dev/null; then
|
||||
echo -e "${GREEN}✓ RKHunter is already installed${NC}"
|
||||
echo ""
|
||||
read -p "Press Enter to continue..."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Installing RKHunter..."
|
||||
|
||||
if command -v yum &>/dev/null; then
|
||||
yum install -y rkhunter 2>&1 | tail -3
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
apt-get install -y rkhunter 2>&1 | tail -3
|
||||
else
|
||||
echo -e "${RED}✗ No compatible package manager found${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v rkhunter &>/dev/null; then
|
||||
echo -e "${GREEN}✓ RKHunter installed successfully${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Installation may have failed${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "Press Enter to continue..."
|
||||
}
|
||||
```
|
||||
|
||||
**Update Maldet Submenu to include install option:**
|
||||
|
||||
```bash
|
||||
maldet_scan_submenu() {
|
||||
while true; do
|
||||
echo ""
|
||||
print_header "Maldet Scanner - Linux Malware Detection"
|
||||
echo "Fast, efficient, Linux-specific malware detection"
|
||||
echo ""
|
||||
|
||||
if is_maldet_installed; then
|
||||
echo -e "${GREEN}✓ Maldet is installed${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Maldet is NOT installed${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "Select option:"
|
||||
echo -e " ${CYAN}1.${NC} Scan entire server (fastest comprehensive scan)"
|
||||
echo -e " ${CYAN}2.${NC} Scan all user accounts"
|
||||
echo -e " ${CYAN}3.${NC} Scan specific user account"
|
||||
echo -e " ${CYAN}4.${NC} Scan specific domain"
|
||||
echo -e " ${CYAN}5.${NC} Scan custom path"
|
||||
echo ""
|
||||
echo -e " ${CYAN}6.${NC} Update Maldet signatures"
|
||||
echo -e " ${CYAN}7.${NC} View Maldet results"
|
||||
echo -e " ${CYAN}8.${NC} Install Maldet (if not installed)" # ← NEW
|
||||
echo ""
|
||||
echo -e " ${RED}0.${NC} Back to main menu"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
read -p "Select option (0-8): " choice
|
||||
|
||||
if ! [[ "$choice" =~ ^[0-8]$ ]]; then
|
||||
echo -e "${RED}Invalid option${NC}"
|
||||
sleep 1
|
||||
continue
|
||||
fi
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
if is_maldet_installed; then
|
||||
maldet_launch_scan "server"
|
||||
else
|
||||
echo -e "${RED}Maldet not installed. Install first (option 8).${NC}"
|
||||
sleep 2
|
||||
fi
|
||||
break
|
||||
;;
|
||||
2) maldet_launch_scan "all_users"; break ;;
|
||||
3) maldet_launch_scan "user"; break ;;
|
||||
4) maldet_launch_scan "domain"; break ;;
|
||||
5) maldet_launch_scan "custom"; break ;;
|
||||
6) maldet_update_signatures; break ;;
|
||||
7) maldet_view_results; break ;;
|
||||
8) install_maldet_only; break ;; # ← NEW
|
||||
0) return 0 ;;
|
||||
esac
|
||||
done
|
||||
done
|
||||
}
|
||||
```
|
||||
|
||||
**Also add a Scanner Install Submenu:**
|
||||
|
||||
```bash
|
||||
scanner_install_submenu() {
|
||||
while true; do
|
||||
echo ""
|
||||
print_banner "Install Individual Scanners"
|
||||
echo ""
|
||||
|
||||
echo "Available Scanners:"
|
||||
echo -e " ${CYAN}1.${NC} Maldet (Fast, Linux-specific)"
|
||||
[ ! -f "/usr/bin/imunify-antivirus" ] && echo " Status: NOT installed"
|
||||
[ -f "/usr/bin/imunify-antivirus" ] && echo " Status: ✓ Installed"
|
||||
|
||||
echo -e " ${CYAN}2.${NC} ClamAV (Free, open source)"
|
||||
command -v clamscan &>/dev/null && echo " Status: ✓ Installed" || echo " Status: NOT installed"
|
||||
|
||||
echo -e " ${CYAN}3.${NC} RKHunter (Rootkit detection)"
|
||||
command -v rkhunter &>/dev/null && echo " Status: ✓ Installed" || echo " Status: NOT installed"
|
||||
|
||||
echo ""
|
||||
echo -e " ${CYAN}4.${NC} Install ALL scanners (Maldet + ClamAV + RKHunter + ImunifyAV)"
|
||||
echo -e " ${RED}0.${NC} Back"
|
||||
echo ""
|
||||
|
||||
read -p "Select option: " choice
|
||||
|
||||
case "$choice" in
|
||||
1) install_maldet_only; break ;;
|
||||
2) install_clamav_only; break ;;
|
||||
3) install_rkhunter_only; break ;;
|
||||
4) install_all_scanners; break ;;
|
||||
0) return 0 ;;
|
||||
*) echo "Invalid option"; sleep 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
```
|
||||
|
||||
**Update main menu to show install submenu:**
|
||||
|
||||
```bash
|
||||
# In Configuration section of main menu:
|
||||
echo -e " ${CYAN}10.${NC} Install individual scanners"
|
||||
echo -e " ${CYAN}11.${NC} Install all scanners (recommended first time)"
|
||||
echo -e " ${CYAN}12.${NC} Scanner settings"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Fix ClamAV Installation (10 minutes)
|
||||
1. Edit `/root/server-toolkit-beta/modules/security/malware-scanner.sh` lines 223-235
|
||||
2. Add fallback logic for missing cPanel scripts
|
||||
3. Test: Run "Install all scanners" again, should not fail on ClamAV
|
||||
|
||||
### Phase 2: Add Individual Scanner Install (30 minutes)
|
||||
1. Add `install_maldet_only()` function
|
||||
2. Add `install_clamav_only()` function
|
||||
3. Add `install_rkhunter_only()` function
|
||||
4. Update Maldet submenu to include option 8 "Install Maldet"
|
||||
5. Update main menu with new install submenu
|
||||
6. Test each individual installer
|
||||
|
||||
### Phase 3: Copy to Production (5 minutes)
|
||||
1. Copy fixed `/root/server-toolkit-beta/modules/security/malware-scanner.sh` to production
|
||||
2. Test production version
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] ClamAV installs even if `/scripts/check_cpanel_rpms` missing
|
||||
- [ ] Maldet can be installed from Maldet submenu (option 8)
|
||||
- [ ] Individual scanners can be installed one at a time
|
||||
- [ ] "Install all scanners" still works
|
||||
- [ ] Scanner status shows as "✓ Installed" after installation
|
||||
- [ ] Installation functions handle already-installed cases gracefully
|
||||
- [ ] No exit code 127 errors
|
||||
|
||||
---
|
||||
|
||||
## Expected Behavior After Fix
|
||||
|
||||
**Scenario 1: User wants to install Maldet only**
|
||||
```
|
||||
bash launcher.sh → Security → Malware Scanner → Maldet menu
|
||||
→ Select "8. Install Maldet"
|
||||
→ Maldet installs (just Maldet, nothing else)
|
||||
→ User can immediately scan with Maldet
|
||||
```
|
||||
|
||||
**Scenario 2: User's cPanel scripts are missing**
|
||||
```
|
||||
bash launcher.sh → Security → Malware Scanner → Install all scanners
|
||||
→ ClamAV installation tries cPanel scripts
|
||||
→ Scripts missing, gracefully falls back to yum
|
||||
→ ClamAV installs successfully
|
||||
→ Installation continues with other scanners
|
||||
```
|
||||
|
||||
+1
-1
@@ -676,7 +676,7 @@ main() {
|
||||
echo -e "${GREEN}Thanks for using Server Management Toolkit!${NC}"
|
||||
echo ""
|
||||
fi
|
||||
exit 0
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Invalid option${NC}"
|
||||
|
||||
+24
-18
@@ -3,10 +3,22 @@
|
||||
# Part of Server Toolkit - Phase 2: Analysis
|
||||
# Dependencies: lib/php-detector.sh, lib/system-detect.sh
|
||||
|
||||
# Source required libraries
|
||||
# Source guard - prevent re-sourcing (but allow re-initialization if needed)
|
||||
if [ -n "${_PHP_ANALYZER_LOADED:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
readonly _PHP_ANALYZER_LOADED=1
|
||||
|
||||
# Source required libraries only if not already loaded
|
||||
if [ -z "${_PHP_DETECTOR_LOADED:-}" ]; then
|
||||
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$_LIB_DIR/php-detector.sh" 2>/dev/null || { echo "ERROR: php-detector.sh not found"; return 1; }
|
||||
fi
|
||||
|
||||
if [ -z "${_SYSTEM_DETECT_LOADED:-}" ]; then
|
||||
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$_LIB_DIR/system-detect.sh" 2>/dev/null || { echo "ERROR: system-detect.sh not found"; return 1; }
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS - PURE BASH OPTIMIZATIONS
|
||||
@@ -917,8 +929,6 @@ convert_to_bytes() {
|
||||
# Usage: calculate_server_memory_capacity
|
||||
# Returns: total_required_mb|total_ram_mb|percentage|status|details
|
||||
calculate_server_memory_capacity() {
|
||||
echo "Analyzing server-wide PHP-FPM memory capacity..." >&2
|
||||
|
||||
# Get total system memory
|
||||
local total_ram_mb
|
||||
total_ram_mb=$(free -m | awk '/^Mem:/ {print $2}')
|
||||
@@ -991,14 +1001,17 @@ calculate_server_memory_capacity() {
|
||||
done <<< "$user_domains"
|
||||
done <<< "$users"
|
||||
|
||||
# Add MySQL memory usage to total
|
||||
# Add MySQL memory usage to total (with timeout to prevent hanging)
|
||||
local mysql_memory_mb=0
|
||||
local mysql_status
|
||||
local mysql_info
|
||||
mysql_info=$(detect_mysql_memory_usage 2>/dev/null)
|
||||
if [ $? -eq 0 ]; then
|
||||
mysql_memory_mb=$(echo "$mysql_info" | cut -d'|' -f3)
|
||||
mysql_info=$(timeout 5 detect_mysql_memory_usage 2>/dev/null)
|
||||
if [ $? -eq 0 ] && [ -n "$mysql_info" ]; then
|
||||
mysql_memory_mb=$(echo "$mysql_info" | cut -d'|' -f3 || echo "0")
|
||||
mysql_status=$(echo "$mysql_info" | cut -d'|' -f4)
|
||||
# Ensure mysql_memory_mb is numeric
|
||||
mysql_memory_mb=${mysql_memory_mb:-0}
|
||||
[ -z "$mysql_memory_mb" ] && mysql_memory_mb=0
|
||||
total_required_mb=$((total_required_mb + mysql_memory_mb))
|
||||
fi
|
||||
|
||||
@@ -1018,7 +1031,7 @@ calculate_server_memory_capacity() {
|
||||
fi
|
||||
|
||||
# Return formatted result - first line is summary
|
||||
if [ "$mysql_memory_mb" -gt 0 ]; then
|
||||
if [ "${mysql_memory_mb:-0}" -gt 0 ]; then
|
||||
echo "$total_required_mb|$total_ram_mb|$percentage|$status|$pool_count pools|$total_max_children max_children|MySQL: ${mysql_memory_mb}MB"
|
||||
else
|
||||
echo "$total_required_mb|$total_ram_mb|$percentage|$status|$pool_count pools|$total_max_children max_children"
|
||||
@@ -1032,8 +1045,6 @@ calculate_server_memory_capacity() {
|
||||
# Usage: calculate_balanced_memory_allocation
|
||||
# Returns: recommendations for each user to fit within system limits
|
||||
calculate_balanced_memory_allocation() {
|
||||
echo "Calculating balanced memory allocation..." >&2
|
||||
|
||||
# Get total system memory
|
||||
local total_ram_mb
|
||||
total_ram_mb=$(free -m | awk '/^Mem:/ {print $2}')
|
||||
@@ -1169,8 +1180,6 @@ calculate_balanced_memory_allocation() {
|
||||
# Usage: calculate_balanced_memory_allocation_per_domain
|
||||
# Returns: recommendations for each domain to fit within system limits
|
||||
calculate_balanced_memory_allocation_per_domain() {
|
||||
echo "Calculating per-domain balanced memory allocation (cPanel)..." >&2
|
||||
|
||||
# Verify this is cPanel
|
||||
if [ "$SYS_CONTROL_PANEL" != "cpanel" ]; then
|
||||
echo "ERROR|This function only supports cPanel. Use calculate_balanced_memory_allocation for other panels."
|
||||
@@ -1270,8 +1279,6 @@ calculate_balanced_memory_allocation_per_domain() {
|
||||
domain_memory[$domain]=$((avg_kb / 1024))
|
||||
|
||||
# Get advanced traffic stats for this domain (7-day, bot-filtered, 95th percentile)
|
||||
echo " Analyzing traffic for $domain..." >&2
|
||||
|
||||
local traffic
|
||||
# Try fast method first (current process count)
|
||||
local current_processes
|
||||
@@ -1280,7 +1287,6 @@ calculate_balanced_memory_allocation_per_domain() {
|
||||
if [ "$current_processes" -gt 0 ]; then
|
||||
# Use current process count as baseline (fast, no log parsing)
|
||||
traffic=$((current_processes * 2)) # Assume processes can handle ~2 req/min each
|
||||
echo " Using current process count: $current_processes processes" >&2
|
||||
else
|
||||
# Fallback to traffic analysis only if no processes found
|
||||
local traffic_stats
|
||||
@@ -1385,9 +1391,9 @@ detect_mysql_memory_usage() {
|
||||
local max_connections=150 # Default
|
||||
|
||||
if command -v mysql >/dev/null 2>&1; then
|
||||
# Try to query MySQL directly
|
||||
buffer_pool_mb=$(mysql -Nse "SELECT ROUND(@@innodb_buffer_pool_size/1024/1024)" 2>/dev/null || echo "0")
|
||||
max_connections=$(mysql -Nse "SELECT @@max_connections" 2>/dev/null || echo "150")
|
||||
# Try to query MySQL directly (with 2 second timeout to prevent hanging)
|
||||
buffer_pool_mb=$(timeout 2 mysql -Nse "SELECT ROUND(@@innodb_buffer_pool_size/1024/1024)" 2>/dev/null || echo "0")
|
||||
max_connections=$(timeout 2 mysql -Nse "SELECT @@max_connections" 2>/dev/null || echo "150")
|
||||
fi
|
||||
|
||||
# If we couldn't get it from MySQL, try my.cnf
|
||||
|
||||
+326
-13
@@ -9,10 +9,22 @@
|
||||
# - Safe allocation buffers based on traffic stability
|
||||
################################################################################
|
||||
|
||||
# Dependencies
|
||||
# Source guard - prevent re-sourcing
|
||||
if [ -n "${_PHP_CALCULATOR_LOADED:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
readonly _PHP_CALCULATOR_LOADED=1
|
||||
|
||||
# Dependencies - only source if not already loaded
|
||||
if [ -z "${_PHP_DETECTOR_LOADED:-}" ]; then
|
||||
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$_LIB_DIR/php-detector.sh" 2>/dev/null || { echo "ERROR: php-detector.sh not found"; return 1; }
|
||||
fi
|
||||
|
||||
if [ -z "${_SYSTEM_DETECT_LOADED:-}" ]; then
|
||||
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$_LIB_DIR/system-detect.sh" 2>/dev/null || { echo "ERROR: system-detect.sh not found"; return 1; }
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTION - Extract field from pipe-delimited string
|
||||
@@ -105,30 +117,49 @@ calculate_max_children_memory_based() {
|
||||
local reserved_mb
|
||||
reserved_mb=$(get_field "$reserve_result" 1)
|
||||
|
||||
# Available memory for PHP-FPM
|
||||
local available_mb=$((total_ram_mb - reserved_mb))
|
||||
# Account for MySQL memory (critical on shared hosting!)
|
||||
local mysql_memory_mb=0
|
||||
local mysql_info
|
||||
mysql_info=$(detect_mysql_memory_usage 2>/dev/null)
|
||||
if [ $? -eq 0 ]; then
|
||||
# FIX: detect_mysql_memory_usage returns: buffer_pool|connections|estimated_total_mb|status (4 fields)
|
||||
# Extract field 3 (estimated_total_mb - the actual memory usage)
|
||||
mysql_memory_mb=$(echo "$mysql_info" | cut -d'|' -f3)
|
||||
fi
|
||||
|
||||
# Available memory for PHP-FPM (after system + MySQL reserves)
|
||||
# CRITICAL: This is shared across ALL domains, not per-domain!
|
||||
local available_mb=$((total_ram_mb - reserved_mb - mysql_memory_mb))
|
||||
|
||||
# Safety check: never allow PHP-FPM to use more than 60% of RAM
|
||||
local max_php_fpm=$((total_ram_mb * 60 / 100))
|
||||
if [ "$available_mb" -gt "$max_php_fpm" ]; then
|
||||
available_mb=$max_php_fpm
|
||||
fi
|
||||
|
||||
# Convert average KB to MB
|
||||
local avg_mb=$((avg_kb / 1024))
|
||||
if [ "$avg_mb" -eq 0 ]; then
|
||||
avg_mb=1 # Minimum 1MB to prevent division issues
|
||||
avg_mb=20 # More realistic default (not 1MB)
|
||||
fi
|
||||
|
||||
# Theoretical maximum without safety buffer
|
||||
local theoretical_max=$((available_mb / avg_mb))
|
||||
|
||||
# Apply safety buffer (default 15%, refined later based on traffic patterns)
|
||||
local safety_buffer=15
|
||||
# Apply safety buffer (50% - much more conservative for shared hosting!)
|
||||
# This accounts for peak traffic spikes and other processes
|
||||
local safety_buffer=50
|
||||
local recommended=$((theoretical_max * (100 - safety_buffer) / 100))
|
||||
|
||||
# Sanity checks
|
||||
if [ "$recommended" -lt 2 ]; then
|
||||
echo "2|Minimum safe value (insufficient memory)"
|
||||
elif [ "$recommended" -gt 500 ]; then
|
||||
# Cap at 500 (typical proxy upstream pool size)
|
||||
echo "500|Capped at safe maximum (would be $recommended)"
|
||||
# Hard cap at traffic-realistic limits
|
||||
if [ "$recommended" -lt 5 ]; then
|
||||
echo "5|Minimum safe value (insufficient memory)"
|
||||
elif [ "$recommended" -gt 150 ]; then
|
||||
# CRITICAL: Cap at 150 max per domain on shared hosting
|
||||
# Higher values require dedicated servers
|
||||
echo "150|Capped at safe maximum for shared hosting (would be $recommended)"
|
||||
else
|
||||
local reason="Memory-based: ${avg_mb}MB per process, ${available_mb}MB available, ${safety_buffer}% buffer"
|
||||
local reason="Memory-based: ${avg_mb}MB per process, ${available_mb}MB available (after MySQL: ${mysql_memory_mb}MB), ${safety_buffer}% safety buffer"
|
||||
echo "$recommended|$reason"
|
||||
fi
|
||||
}
|
||||
@@ -267,6 +298,198 @@ detect_mysql_memory_usage() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# NEW: CALCULATE SERVER TOTAL CAPACITY
|
||||
# ============================================================================
|
||||
# NEW: Measure actual memory per process across all active FPM pools
|
||||
# Usage: get_actual_memory_per_process
|
||||
# Returns: memory_mb (in MB, or 140 if can't measure)
|
||||
# This ensures capacity calculations use REAL data, not assumptions
|
||||
get_actual_memory_per_process() {
|
||||
# Get ALL active php-fpm processes and their RSS memory
|
||||
# ps aux format: USER PID %CPU %MEM VSZ RSS STAT START TIME COMMAND
|
||||
# RSS is field 6 (in KB)
|
||||
|
||||
local total_kb=0
|
||||
local count=0
|
||||
|
||||
while read -r line; do
|
||||
if [ -z "$line" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract RSS (field 6) from ps aux output
|
||||
local rss_kb
|
||||
rss_kb=$(echo "$line" | awk '{print $6}')
|
||||
|
||||
if [ -n "$rss_kb" ] && [ "$rss_kb" -gt 0 ]; then
|
||||
total_kb=$((total_kb + rss_kb))
|
||||
count=$((count + 1))
|
||||
fi
|
||||
done < <(ps aux | grep -E 'php-fpm.*pool' | grep -v grep || true)
|
||||
|
||||
# If we found active processes, calculate average
|
||||
if [ "$count" -gt 0 ]; then
|
||||
local avg_kb=$((total_kb / count))
|
||||
local avg_mb=$((avg_kb / 1024))
|
||||
|
||||
# Sanity check: per-process memory should be 10MB-500MB
|
||||
if [ "$avg_mb" -lt 10 ]; then
|
||||
avg_mb=10
|
||||
elif [ "$avg_mb" -gt 500 ]; then
|
||||
avg_mb=500
|
||||
fi
|
||||
|
||||
echo "$avg_mb"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# No active processes detected
|
||||
# Use user-provided measurement or conservative default of 140MB (based on actual data)
|
||||
echo "140"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Calculate the total max_children the entire server can support
|
||||
# Usage: calculate_server_capacity <total_ram_mb>
|
||||
# Returns: total_capacity|available_memory|memory_per_process|reason
|
||||
calculate_server_capacity() {
|
||||
local total_ram_mb="$1"
|
||||
|
||||
if [ -z "$total_ram_mb" ] || [ "$total_ram_mb" -lt 512 ]; then
|
||||
echo "0|0|140|Insufficient RAM for calculation"
|
||||
return
|
||||
fi
|
||||
|
||||
# Calculate system reserve (dynamic percentage-based)
|
||||
local reserve_result
|
||||
reserve_result=$(calculate_system_reserve "$total_ram_mb")
|
||||
local reserved_mb
|
||||
reserved_mb=$(get_field "$reserve_result" 1)
|
||||
|
||||
# Account for MySQL memory (critical on shared hosting!)
|
||||
local mysql_memory_mb=0
|
||||
local mysql_info
|
||||
mysql_info=$(detect_mysql_memory_usage 2>/dev/null)
|
||||
if [ $? -eq 0 ]; then
|
||||
# FIX: detect_mysql_memory_usage returns: buffer_pool|connections|estimated_total_mb|status (4 fields)
|
||||
# Extract field 3 (estimated_total_mb - the actual memory usage)
|
||||
mysql_memory_mb=$(echo "$mysql_info" | cut -d'|' -f3)
|
||||
fi
|
||||
|
||||
# Available memory for PHP-FPM (after system + MySQL reserves)
|
||||
local available_mb=$((total_ram_mb - reserved_mb - mysql_memory_mb))
|
||||
|
||||
# Safety check: never allow PHP-FPM to use more than 60% of RAM
|
||||
local max_php_fpm=$((total_ram_mb * 60 / 100))
|
||||
if [ "$available_mb" -gt "$max_php_fpm" ]; then
|
||||
available_mb=$max_php_fpm
|
||||
fi
|
||||
|
||||
# CRITICAL: Never allow negative available memory
|
||||
if [ "$available_mb" -lt 0 ]; then
|
||||
available_mb=0
|
||||
fi
|
||||
|
||||
# Use 140MB per process (confirmed from actual PHP-FPM workers)
|
||||
# This is the realistic baseline for production PHP workloads
|
||||
local memory_per_process=140
|
||||
|
||||
# Total capacity = available memory / memory per process
|
||||
local total_capacity=$((available_mb / memory_per_process))
|
||||
|
||||
# Sanity checks
|
||||
[ "$total_capacity" -lt 5 ] && total_capacity=5
|
||||
[ "$total_capacity" -gt 500 ] && total_capacity=500
|
||||
|
||||
echo "$total_capacity|$available_mb|$memory_per_process|Server can support $total_capacity total max_children"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# NEW: GET DOMAIN TRAFFIC PERCENTAGE
|
||||
# ============================================================================
|
||||
# Calculate what percentage of total server traffic this domain handles
|
||||
# Usage: get_domain_traffic_percentage <username> <domain> <all_domains_list>
|
||||
# Returns: percentage|request_count|reason
|
||||
get_domain_traffic_percentage() {
|
||||
local username="$1"
|
||||
local domain="$2"
|
||||
local all_domains="$3"
|
||||
|
||||
if [ -z "$domain" ] || [ -z "$all_domains" ]; then
|
||||
echo "50|0|Insufficient data"
|
||||
return
|
||||
fi
|
||||
|
||||
# Count domains to determine equal share
|
||||
local domain_count
|
||||
domain_count=$(echo "$all_domains" | grep -v "^$" | wc -l)
|
||||
[ "$domain_count" -lt 1 ] && domain_count=1
|
||||
|
||||
# CRITICAL FIX: Use peak concurrent to estimate traffic percentage
|
||||
# (Access log parsing is unreliable across control panels)
|
||||
# Peak concurrent is a reliable indicator of traffic intensity
|
||||
|
||||
# Get this domain's peak concurrent
|
||||
local domain_peak
|
||||
domain_peak=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0")
|
||||
[ -z "$domain_peak" ] && domain_peak=0
|
||||
|
||||
# Calculate total peak concurrent across ALL domains
|
||||
local total_peak=0
|
||||
local domain_check
|
||||
while IFS= read -r domain_check; do
|
||||
[ -z "$domain_check" ] && continue
|
||||
local peak_val
|
||||
peak_val=$(get_domain_peak_concurrent "$domain_check" 2>/dev/null || echo "0")
|
||||
[ -z "$peak_val" ] && peak_val=0
|
||||
total_peak=$((total_peak + peak_val))
|
||||
done <<< "$all_domains"
|
||||
|
||||
# Calculate percentage based on peak concurrent
|
||||
if [ "$total_peak" -gt 0 ]; then
|
||||
local percentage=$((domain_peak * 100 / total_peak))
|
||||
[ "$percentage" -lt 1 ] && percentage=1
|
||||
[ "$percentage" -gt 99 ] && percentage=99
|
||||
echo "$percentage|$domain_peak|Based on peak concurrent (traffic intensity)"
|
||||
return
|
||||
fi
|
||||
|
||||
# Fallback: equal distribution among all domains
|
||||
# This is the SAFEST approach when we can't calculate percentages
|
||||
local equal_share=$((100 / domain_count))
|
||||
echo "$equal_share|0|Using equal distribution ($domain_count domains) - safest assumption"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# NEW: CALCULATE FAIR SHARE BASED ON TRAFFIC
|
||||
# ============================================================================
|
||||
# Calculate this domain's fair share of server capacity based on traffic percentage
|
||||
# Usage: calculate_max_children_fair_share <total_capacity> <traffic_percentage>
|
||||
# Returns: fair_share_max|reason
|
||||
calculate_max_children_fair_share() {
|
||||
local total_capacity="$1"
|
||||
local traffic_percentage="$2"
|
||||
|
||||
if [ -z "$total_capacity" ] || [ -z "$traffic_percentage" ]; then
|
||||
echo "20|Invalid parameters"
|
||||
return
|
||||
fi
|
||||
|
||||
# Calculate fair share: total capacity × traffic percentage
|
||||
local fair_share=$((total_capacity * traffic_percentage / 100))
|
||||
|
||||
# Apply hard limits
|
||||
if [ "$fair_share" -lt 5 ]; then
|
||||
echo "5|Fair share is very small (minimum enforced)"
|
||||
elif [ "$fair_share" -gt 150 ]; then
|
||||
echo "150|Fair share exceeds shared hosting limit (capped at 150)"
|
||||
else
|
||||
echo "$fair_share|Fair share: $traffic_percentage% of $total_capacity total"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# NEW: RECOMMEND PM MODE (static/dynamic/ondemand)
|
||||
# ============================================================================
|
||||
@@ -389,6 +612,92 @@ calculate_optimal_php_settings() {
|
||||
echo "$final_max_children|$pm_mode|$min_spare|$max_spare|$reason_prefix: $pm_reason"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# NEW: THREE-CONSTRAINT INTELLIGENT OPTIMIZATION
|
||||
# ============================================================================
|
||||
# Calculate optimal settings using three constraints for maximum intelligence:
|
||||
# 1. Memory constraint - what available RAM allows
|
||||
# 2. Traffic constraint - what actual usage suggests
|
||||
# 3. Fair share constraint - proportional allocation based on traffic
|
||||
# Uses the MINIMUM of all three for maximum safety and fairness
|
||||
# Usage: calculate_optimal_php_settings_intelligent <username> <total_ram_mb> <total_server_capacity> <traffic_percentage>
|
||||
# Returns: max_children|pm_mode|min_spare|max_spare|limiting_factor|reason
|
||||
calculate_optimal_php_settings_intelligent() {
|
||||
local username="$1"
|
||||
local total_ram_mb="$2"
|
||||
local total_server_capacity="$3"
|
||||
local traffic_percentage="$4"
|
||||
|
||||
if [ -z "$username" ] || [ -z "$total_ram_mb" ] || [ -z "$total_server_capacity" ]; then
|
||||
echo "0|dynamic|1|5|ERROR|Invalid parameters"
|
||||
return
|
||||
fi
|
||||
|
||||
# Default traffic percentage if not provided (equal distribution)
|
||||
[ -z "$traffic_percentage" ] && traffic_percentage=50
|
||||
|
||||
# CONSTRAINT 1: Memory-based max (what RAM allows)
|
||||
local memory_result
|
||||
memory_result=$(calculate_max_children_memory_based "$username" "$total_ram_mb")
|
||||
local memory_based_max
|
||||
memory_based_max=$(get_field "$memory_result" 1)
|
||||
|
||||
# CONSTRAINT 2: Traffic-based max (what traffic patterns suggest)
|
||||
local traffic_result
|
||||
traffic_result=$(calculate_peak_concurrent_requests_improved "$username" 7)
|
||||
local peak_concurrent stability_factor
|
||||
peak_concurrent=$(get_field "$traffic_result" 1)
|
||||
stability_factor=$(get_field "$traffic_result" 2)
|
||||
|
||||
local traffic_based_max=0
|
||||
if [ "$peak_concurrent" -gt 0 ]; then
|
||||
local traffic_calc
|
||||
traffic_calc=$(calculate_max_children_traffic_based "$peak_concurrent" "$stability_factor")
|
||||
traffic_based_max=$(get_field "$traffic_calc" 1)
|
||||
else
|
||||
traffic_based_max=$memory_based_max # No traffic data, use memory as basis
|
||||
fi
|
||||
|
||||
# CONSTRAINT 3: Fair share (proportional allocation based on traffic %)
|
||||
local fair_share_result
|
||||
fair_share_result=$(calculate_max_children_fair_share "$total_server_capacity" "$traffic_percentage")
|
||||
local fair_share_max
|
||||
fair_share_max=$(get_field "$fair_share_result" 1)
|
||||
|
||||
# USE THE MINIMUM OF ALL THREE CONSTRAINTS
|
||||
local final_max_children="$memory_based_max"
|
||||
local limiting_factor="Memory constraint ($memory_based_max max_children)"
|
||||
|
||||
if [ "$traffic_based_max" -lt "$final_max_children" ]; then
|
||||
final_max_children="$traffic_based_max"
|
||||
limiting_factor="Traffic (peak $peak_concurrent concurrent requests)"
|
||||
fi
|
||||
|
||||
if [ "$fair_share_max" -lt "$final_max_children" ]; then
|
||||
final_max_children="$fair_share_max"
|
||||
limiting_factor="Fair share constraint (${traffic_percentage}% traffic allocation)"
|
||||
fi
|
||||
|
||||
# CRITICAL: Ensure we never recommend 0 or invalid values
|
||||
if [ -z "$final_max_children" ] || [ "$final_max_children" -le 0 ]; then
|
||||
final_max_children="20"
|
||||
limiting_factor="Safe default (calculation failed)"
|
||||
fi
|
||||
|
||||
# Recommend pm mode
|
||||
local pm_result
|
||||
pm_result=$(recommend_pm_mode "$peak_concurrent" "$((peak_concurrent / 2))" "$stability_factor")
|
||||
local pm_mode min_spare max_spare pm_reason
|
||||
pm_mode=$(get_field "$pm_result" 1)
|
||||
min_spare=$(get_field "$pm_result" 2)
|
||||
max_spare=$(get_field "$pm_result" 3)
|
||||
pm_reason=$(get_field "$pm_result" 4)
|
||||
|
||||
# Return with detailed explanation
|
||||
local reason="3-constraint intelligent: Mem=$memory_based_max, Traffic=$traffic_based_max, Share=$fair_share_max → $limiting_factor"
|
||||
echo "$final_max_children|$pm_mode|$min_spare|$max_spare|$limiting_factor|$reason"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Export functions for use in other scripts
|
||||
# ============================================================================
|
||||
@@ -397,6 +706,10 @@ export -f calculate_max_children_memory_based
|
||||
export -f calculate_peak_concurrent_requests_improved
|
||||
export -f calculate_max_children_traffic_based
|
||||
export -f detect_mysql_memory_usage
|
||||
export -f calculate_server_capacity
|
||||
export -f get_domain_traffic_percentage
|
||||
export -f calculate_max_children_fair_share
|
||||
export -f recommend_pm_mode
|
||||
export -f calculate_optimal_php_settings
|
||||
export -f calculate_optimal_php_settings_intelligent
|
||||
export -f get_field
|
||||
|
||||
+20
-10
@@ -297,13 +297,18 @@ modify_fpm_pool_setting() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if setting exists
|
||||
if grep -q "^${setting}\s*=" "$pool_config"; then
|
||||
# Escape setting and value for sed (handle special chars like dots)
|
||||
local setting_escaped=$(printf '%s\n' "$setting" | sed -e 's/[\.&|/\]/\\&/g')
|
||||
local value_escaped=$(printf '%s\n' "$value" | sed -e 's/[\.&|/\]/\\&/g')
|
||||
|
||||
# Check if setting exists (with proper escaping for regex)
|
||||
local setting_regex=$(printf '%s\n' "$setting" | sed -e 's/[\.&|/\[^$*]/\\&/g')
|
||||
if grep -q "^${setting_regex}\s*=" "$pool_config"; then
|
||||
# Replace existing value
|
||||
sed -i "s|^${setting}\s*=.*|${setting} = ${value}|" "$pool_config"
|
||||
elif grep -q "^;${setting}\s*=" "$pool_config"; then
|
||||
sed -i "s|^${setting_escaped}\s*=.*|${setting} = ${value}|" "$pool_config"
|
||||
elif grep -q "^;${setting_regex}\s*=" "$pool_config"; then
|
||||
# Uncomment and set value
|
||||
sed -i "s|^;${setting}\s*=.*|${setting} = ${value}|" "$pool_config"
|
||||
sed -i "s|^;${setting_escaped}\s*=.*|${setting} = ${value}|" "$pool_config"
|
||||
else
|
||||
# Add new setting at end of file
|
||||
echo "${setting} = ${value}" >> "$pool_config"
|
||||
@@ -330,13 +335,18 @@ modify_php_ini_setting() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if setting exists
|
||||
if grep -q "^${setting}\s*=" "$php_ini"; then
|
||||
# Escape setting and value for sed (handle special chars like dots)
|
||||
local setting_escaped=$(printf '%s\n' "$setting" | sed -e 's/[\.&|/\]/\\&/g')
|
||||
local value_escaped=$(printf '%s\n' "$value" | sed -e 's/[\.&|/\]/\\&/g')
|
||||
|
||||
# Check if setting exists (with proper escaping for regex)
|
||||
local setting_regex=$(printf '%s\n' "$setting" | sed -e 's/[\.&|/\[^$*]/\\&/g')
|
||||
if grep -q "^${setting_regex}\s*=" "$php_ini"; then
|
||||
# Replace existing value
|
||||
sed -i "s|^${setting}\s*=.*|${setting} = ${value}|" "$php_ini"
|
||||
elif grep -q "^;${setting}\s*=" "$php_ini"; then
|
||||
sed -i "s|^${setting_escaped}\s*=.*|${setting} = ${value}|" "$php_ini"
|
||||
elif grep -q "^;${setting_regex}\s*=" "$php_ini"; then
|
||||
# Uncomment and set value
|
||||
sed -i "s|^;${setting}\s*=.*|${setting} = ${value}|" "$php_ini"
|
||||
sed -i "s|^;${setting_escaped}\s*=.*|${setting} = ${value}|" "$php_ini"
|
||||
else
|
||||
# Add new setting at end of file
|
||||
echo "${setting} = ${value}" >> "$php_ini"
|
||||
|
||||
+49
-19
@@ -412,14 +412,18 @@ get_domain_peak_concurrent() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Analyze access log for peak concurrent requests (simplified)
|
||||
# Analyze access log for peak concurrent requests
|
||||
# Apache logs: timestamp is [DD/Mon/YYYY:HH:MM:SS]
|
||||
# Extract HH:MM (hour and minute) for minute-level granularity
|
||||
# Count requests per minute, estimate concurrent = requests/min * avg_duration / 60
|
||||
# Assumption: average PHP request takes ~1-2 seconds (multiplier 0.15)
|
||||
tail -100000 "$log_file" 2>/dev/null | \
|
||||
awk '{print $4}' | \
|
||||
sed 's/\[//' | \
|
||||
awk -F: '{print $3}' | \
|
||||
sed 's/\[//; s/\].*//' | \
|
||||
awk -F: '{print $2 ":" $3}' | \
|
||||
sort | uniq -c | \
|
||||
sort -rn | head -1 | \
|
||||
awk '{print $1}' || echo "0"
|
||||
awk '{requests=$1; concurrent = int(requests * 0.15); if (concurrent < 1) concurrent = (requests > 0 ? 1 : 0); print concurrent}' || echo "0"
|
||||
}
|
||||
|
||||
# Check if a domain is already optimized
|
||||
@@ -479,34 +483,60 @@ find_domain_access_log() {
|
||||
|
||||
case "${SYS_CONTROL_PANEL:-unknown}" in
|
||||
cpanel)
|
||||
local owner
|
||||
owner=$(find_domain_owner "$domain")
|
||||
if [ -n "$owner" ]; then
|
||||
# Try access-logs directory first (follows symlinks)
|
||||
# cPanel standard locations for access logs
|
||||
# CRITICAL: Must check HTTPS (ssl_log) first since that's where 95%+ of traffic is
|
||||
# Format: /var/log/apache2/domlogs/DOMAIN-ssl_log (HTTPS) or DOMAIN (HTTP)
|
||||
local log_file
|
||||
log_file=$(find -L "/home/${owner}/access-logs" -type f -name "*${domain}*" 2>/dev/null | head -1)
|
||||
|
||||
# If not found, try Apache domlogs directory directly
|
||||
if [ -z "$log_file" ] && [ -d "/etc/apache2/logs/domlogs" ]; then
|
||||
log_file=$(find "/etc/apache2/logs/domlogs" -type f -name "*${domain}*" 2>/dev/null | head -1)
|
||||
# Try standard cPanel domlogs directory FIRST - PREFER SSL LOG (HTTPS)
|
||||
# Most modern traffic is HTTPS, so -ssl_log has the real traffic data
|
||||
if [ -f "/var/log/apache2/domlogs/${domain}-ssl_log" ]; then
|
||||
log_file="/var/log/apache2/domlogs/${domain}-ssl_log"
|
||||
elif [ -f "/var/log/apache2/domlogs/${domain}" ]; then
|
||||
log_file="/var/log/apache2/domlogs/${domain}"
|
||||
fi
|
||||
|
||||
# If not found, try public_html
|
||||
if [ -z "$log_file" ] && [ -d "/home/${owner}/public_html" ]; then
|
||||
log_file=$(find "/home/${owner}/public_html" -maxdepth 2 -type f -name "access_log*" 2>/dev/null | head -1)
|
||||
# If not found, try user's access-logs directory (symlink, follows)
|
||||
if [ -z "$log_file" ]; then
|
||||
local owner
|
||||
owner=$(find_domain_owner "$domain")
|
||||
if [ -n "$owner" ] && [ -d "/home/${owner}/access-logs" ]; then
|
||||
if [ -f "/home/${owner}/access-logs/${domain}-ssl_log" ]; then
|
||||
log_file="/home/${owner}/access-logs/${domain}-ssl_log"
|
||||
elif [ -f "/home/${owner}/access-logs/${domain}" ]; then
|
||||
log_file="/home/${owner}/access-logs/${domain}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try alternative cPanel path
|
||||
if [ -z "$log_file" ] && [ -d "/etc/apache2/logs/domlogs" ]; then
|
||||
if [ -f "/etc/apache2/logs/domlogs/${domain}-ssl_log" ]; then
|
||||
log_file="/etc/apache2/logs/domlogs/${domain}-ssl_log"
|
||||
elif [ -f "/etc/apache2/logs/domlogs/${domain}" ]; then
|
||||
log_file="/etc/apache2/logs/domlogs/${domain}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$log_file"
|
||||
fi
|
||||
;;
|
||||
plesk)
|
||||
find "/var/www/vhosts/${domain}/statistics/logs" -type f -name "access_log*" 2>/dev/null | head -1
|
||||
# Plesk standard locations
|
||||
# Format varies: /var/www/vhosts/DOMAIN/logs/ or /var/www/vhosts/system/DOMAIN/logs/
|
||||
find "/var/www/vhosts" -path "*/logs/*" -name "*access*" -o -path "*/system/${domain}/logs/*" 2>/dev/null | head -1
|
||||
;;
|
||||
interworx)
|
||||
find "/home/*/public_html/${domain}" -type f -name "access_log*" 2>/dev/null | head -1
|
||||
# InterWorx standard location: /home/USER/var/DOMAIN/logs/
|
||||
find "/home/*/var/${domain}/logs" -type f -name "*access*" 2>/dev/null | head -1
|
||||
;;
|
||||
*)
|
||||
find /var/log -type f -name "*${domain}*access*log*" 2>/dev/null | head -1
|
||||
# Standalone/unknown - search common locations
|
||||
local log_file
|
||||
log_file=$(find "/var/log/apache2/domlogs" -maxdepth 1 -type f -name "*${domain}*" 2>/dev/null | head -1)
|
||||
if [ -z "$log_file" ]; then
|
||||
log_file=$(find "/var/log" -maxdepth 2 -type f -name "*${domain}*access*" 2>/dev/null | head -1)
|
||||
fi
|
||||
echo "$log_file"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
+45
-12
@@ -17,10 +17,21 @@ readonly _SECURITY_TOOLS_LOADED=1
|
||||
#############################################################################
|
||||
|
||||
derive_malware_scanners() {
|
||||
# ClamAV detection and paths
|
||||
# ClamAV detection and paths - Check multiple locations for freshclam
|
||||
if command -v clamscan &>/dev/null; then
|
||||
export SYS_SCANNER_CLAMAV="$(command -v clamscan)"
|
||||
export SYS_SCANNER_CLAMUPDATE="$(command -v freshclam 2>/dev/null || echo '')"
|
||||
|
||||
# Find freshclam in priority order: command, cPanel path, standard paths
|
||||
local freshclam_bin=""
|
||||
if command -v freshclam &>/dev/null; then
|
||||
freshclam_bin="$(command -v freshclam)"
|
||||
elif [ -f "/usr/local/cpanel/3rdparty/bin/freshclam" ]; then
|
||||
freshclam_bin="/usr/local/cpanel/3rdparty/bin/freshclam"
|
||||
elif [ -f "/usr/bin/freshclam" ] || [ -f "/usr/sbin/freshclam" ]; then
|
||||
freshclam_bin=$(find /usr -name freshclam -type f 2>/dev/null | head -1)
|
||||
fi
|
||||
|
||||
export SYS_SCANNER_CLAMUPDATE="$freshclam_bin"
|
||||
export SYS_SCANNER_CLAMSCAN="clamscan"
|
||||
export SYS_SCANNER_CLAMAV_DB="/var/lib/clamav"
|
||||
export SYS_SCANNER_CLAMAV_LOG="/var/log/clamav/scan.log"
|
||||
@@ -32,8 +43,13 @@ derive_malware_scanners() {
|
||||
export SYS_SCANNER_CLAMAV_LOG=""
|
||||
fi
|
||||
|
||||
# Maldet (Linux Malware Detect)
|
||||
if [ -f "/usr/local/maldetect/maldet" ]; then
|
||||
# Maldet (Linux Malware Detect) - Check command -v first, then standard paths
|
||||
if command -v maldet &>/dev/null; then
|
||||
export SYS_SCANNER_MALDET="$(command -v maldet)"
|
||||
export SYS_SCANNER_MALDET_DIR="$(dirname "$(command -v maldet)")"
|
||||
export SYS_SCANNER_MALDET_QUARANTINE="${SYS_SCANNER_MALDET_DIR}/quarantine"
|
||||
export SYS_SCANNER_MALDET_LOG="/var/log/maldet.log"
|
||||
elif [ -f "/usr/local/maldetect/maldet" ]; then
|
||||
export SYS_SCANNER_MALDET="/usr/local/maldetect/maldet"
|
||||
export SYS_SCANNER_MALDET_DIR="/usr/local/maldetect"
|
||||
export SYS_SCANNER_MALDET_QUARANTINE="/usr/local/maldetect/quarantine"
|
||||
@@ -45,10 +61,15 @@ derive_malware_scanners() {
|
||||
export SYS_SCANNER_MALDET_LOG=""
|
||||
fi
|
||||
|
||||
# RKHunter (Rootkit Hunter)
|
||||
# RKHunter (Rootkit Hunter) - Detect paths dynamically
|
||||
if command -v rkhunter &>/dev/null; then
|
||||
export SYS_SCANNER_RKHUNTER="$(command -v rkhunter)"
|
||||
# Try to find config file
|
||||
if [ -f "/etc/rkhunter.conf" ]; then
|
||||
export SYS_SCANNER_RKHUNTER_CONFIG="/etc/rkhunter.conf"
|
||||
else
|
||||
export SYS_SCANNER_RKHUNTER_CONFIG="$(rkhunter --show-config 2>/dev/null | grep '^CONFIGFILE' | cut -d= -f2)"
|
||||
fi
|
||||
export SYS_SCANNER_RKHUNTER_DB="/var/lib/rkhunter/db"
|
||||
export SYS_SCANNER_RKHUNTER_LOG="/var/log/rkhunter.log"
|
||||
else
|
||||
@@ -58,8 +79,13 @@ derive_malware_scanners() {
|
||||
export SYS_SCANNER_RKHUNTER_LOG=""
|
||||
fi
|
||||
|
||||
# Imunify360
|
||||
if command -v imunify360-agent &>/dev/null; then
|
||||
# Imunify (both ImunifyAV and Imunify360) - Check both variants
|
||||
if command -v imunify-antivirus &>/dev/null; then
|
||||
export SYS_SCANNER_IMUNIFY="$(command -v imunify-antivirus)"
|
||||
export SYS_SCANNER_IMUNIFY_CONFIG="/etc/sysconfig/imunify360"
|
||||
export SYS_SCANNER_IMUNIFY_DB="/var/lib/imunify360"
|
||||
export SYS_SCANNER_IMUNIFY_LOG="/var/log/imunify360/imunify360.log"
|
||||
elif command -v imunify360-agent &>/dev/null; then
|
||||
export SYS_SCANNER_IMUNIFY="$(command -v imunify360-agent)"
|
||||
export SYS_SCANNER_IMUNIFY_CONFIG="/etc/sysconfig/imunify360"
|
||||
export SYS_SCANNER_IMUNIFY_DB="/var/lib/imunify360"
|
||||
@@ -132,16 +158,18 @@ derive_system_security_tools() {
|
||||
export SYS_FAIL2BAN_JAIL=""
|
||||
fi
|
||||
|
||||
# ModSecurity
|
||||
# ModSecurity - Detect paths based on OS type
|
||||
if [ -f "/etc/apache2/mods-enabled/security.load" ] || [ -f "/etc/httpd/conf.modules.d/10-mod_security.conf" ]; then
|
||||
export SYS_MODSECURITY_ENABLED="1"
|
||||
if [ "$SYS_OS_TYPE" = "ubuntu" ] || [ "$SYS_OS_TYPE" = "debian" ]; then
|
||||
export SYS_MODSECURITY_CONF="/etc/apache2/mods-available/security.conf"
|
||||
export SYS_MODSECURITY_AUDIT_LOG="/var/log/apache2/modsec_audit.log"
|
||||
else
|
||||
# CentOS/RHEL/other
|
||||
export SYS_MODSECURITY_CONF="/etc/httpd/conf.d/mod_security.conf"
|
||||
export SYS_MODSECURITY_AUDIT_LOG="/var/log/httpd/modsec_audit.log"
|
||||
fi
|
||||
export SYS_MODSECURITY_RULES="/etc/modsecurity"
|
||||
export SYS_MODSECURITY_AUDIT_LOG="/var/log/apache2/modsec_audit.log"
|
||||
else
|
||||
export SYS_MODSECURITY_ENABLED=""
|
||||
export SYS_MODSECURITY_CONF=""
|
||||
@@ -149,10 +177,10 @@ derive_system_security_tools() {
|
||||
export SYS_MODSECURITY_AUDIT_LOG=""
|
||||
fi
|
||||
|
||||
# SELinux
|
||||
# SELinux - Use timeout to prevent hangs on misconfigured systems
|
||||
if command -v getenforce &>/dev/null; then
|
||||
export SYS_SELINUX_ENABLED="1"
|
||||
export SYS_SELINUX_STATUS="$(getenforce 2>/dev/null)"
|
||||
export SYS_SELINUX_STATUS="$(timeout 5 getenforce 2>/dev/null || echo "unknown")"
|
||||
export SYS_SELINUX_CONFIG="/etc/selinux/config"
|
||||
else
|
||||
export SYS_SELINUX_ENABLED=""
|
||||
@@ -160,10 +188,15 @@ derive_system_security_tools() {
|
||||
export SYS_SELINUX_CONFIG=""
|
||||
fi
|
||||
|
||||
# AppArmor
|
||||
# AppArmor - Use timeout to prevent hangs
|
||||
if command -v aa-status &>/dev/null; then
|
||||
export SYS_APPARMOR_ENABLED="1"
|
||||
# aa-status can hang on some systems, use timeout
|
||||
if timeout 5 aa-status &>/dev/null; then
|
||||
export SYS_APPARMOR_CONFIG="/etc/apparmor"
|
||||
else
|
||||
export SYS_APPARMOR_CONFIG=""
|
||||
fi
|
||||
else
|
||||
export SYS_APPARMOR_ENABLED=""
|
||||
export SYS_APPARMOR_CONFIG=""
|
||||
|
||||
@@ -106,7 +106,7 @@ detect_control_panel() {
|
||||
SYS_USER_HOME_BASE="/home"
|
||||
|
||||
print_warning "No control panel detected (standalone server)"
|
||||
return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
@@ -419,7 +419,8 @@ if [ "$sent" -gt 0 ] || [ "$received" -gt 0 ]; then
|
||||
# Top recipients (delivery recipients from emails in TEMP_MATCHES)
|
||||
if [ "$sent" -gt 0 ] || [ "$delivered" -gt 0 ]; then
|
||||
print_info "Top 5 recipients (emails delivered TO):"
|
||||
grep -F "$search_pattern" "$TEMP_MATCHES" 2>/dev/null | grep -oE "=> [^@]+@[^ ]+" | sed 's/=> //' | sort | uniq -c | sort -rn | head -5 | while read count recipient; do
|
||||
sed -n "/^$search_pattern/p" "$TEMP_MATCHES" 2>/dev/null | \
|
||||
sed -n 's/.*=> \([^@]*@[^ ]*\).*/\1/p' | sort | uniq -c | sort -rn | head -5 | while read count recipient; do
|
||||
[ -n "$count" ] && echo " $recipient - $count emails"
|
||||
done
|
||||
echo ""
|
||||
@@ -428,7 +429,8 @@ if [ "$sent" -gt 0 ] || [ "$received" -gt 0 ]; then
|
||||
# Top senders (who is sending emails in TEMP_MATCHES)
|
||||
if [ "$sent" -gt 0 ]; then
|
||||
print_info "Top 5 senders (emails sent FROM):"
|
||||
grep -F "$search_pattern" "$TEMP_MATCHES" 2>/dev/null | grep -oE "<= [^@]+@[^ ]+" | sed 's/<= //' | sort | uniq -c | sort -rn | head -5 | while read count sender; do
|
||||
sed -n "/^$search_pattern/p" "$TEMP_MATCHES" 2>/dev/null | \
|
||||
sed -n 's/.*<= \([^@]*@[^ ]*\).*/\1/p' | sort | uniq -c | sort -rn | head -5 | while read count sender; do
|
||||
[ -n "$count" ] && echo " $sender - $count emails"
|
||||
done
|
||||
echo ""
|
||||
@@ -546,7 +548,7 @@ if [ "$check_type" != "2" ]; then
|
||||
|
||||
# cPanel forwarders (in /etc/valiases)
|
||||
if [ -f "/etc/valiases/$domain_part" ]; then
|
||||
forwarder=$(grep -F "^$local_part:" "/etc/valiases/$domain_part" 2>/dev/null)
|
||||
forwarder=$(grep "^${local_part}:" "/etc/valiases/$domain_part" 2>/dev/null || echo "")
|
||||
if [ -n "$forwarder" ]; then
|
||||
echo ""
|
||||
print_info "Forwarder configured:"
|
||||
@@ -650,7 +652,7 @@ if [ "$delivered" -gt 0 ]; then
|
||||
else
|
||||
echo " $line"
|
||||
fi
|
||||
done < <(grep -F "$search_pattern" "$TEMP_MATCHES" | grep -iE "=>|delivered" | tail -5)
|
||||
done < <(sed -n "/^$search_pattern/p" "$TEMP_MATCHES" 2>/dev/null | sed -n '/=>\|[Dd]elivered/p' | tail -5)
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -660,7 +662,7 @@ if [ "$bounced" -gt 0 ]; then
|
||||
|
||||
# Get all bounce lines (Issue 4.1: add -- after grep flags)
|
||||
TEMP_BOUNCES="/tmp/email_bounces_$$.txt"
|
||||
grep -F -- "$search_pattern" "$TEMP_MATCHES" 2>/dev/null | \
|
||||
sed -n "/^$search_pattern/p" "$TEMP_MATCHES" 2>/dev/null | \
|
||||
grep -Ev "authenticator failed|Authentication failed|saved mail to|=>" | \
|
||||
grep -iE "550|551|552|553|554|bounced|Mail delivery failed|\\*\\* " > "$TEMP_BOUNCES" 2>/dev/null
|
||||
|
||||
|
||||
@@ -40,14 +40,14 @@ if [ "$MTA" = "exim" ]; then
|
||||
print_header "Queue Summary"
|
||||
|
||||
# Exim: exim -bpc returns just the number
|
||||
queue_count=$(eval "$SYS_MAIL_CMD_QUEUE_COUNT")
|
||||
queue_count=$(bash -c "$SYS_MAIL_CMD_QUEUE_COUNT" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$queue_count" -gt 0 ] 2>/dev/null; then
|
||||
print_warning "$queue_count messages in queue"
|
||||
echo ""
|
||||
|
||||
# Cache queue list - single execution for all operations
|
||||
queue_list=$(eval "$SYS_MAIL_CMD_QUEUE_LIST")
|
||||
queue_list=$(bash -c "$SYS_MAIL_CMD_QUEUE_LIST" 2>/dev/null || echo "")
|
||||
|
||||
print_header "Recent Queue Messages (last 20)"
|
||||
echo "$queue_list" | head -20
|
||||
@@ -74,7 +74,7 @@ elif [ "$MTA" = "postfix" ]; then
|
||||
print_header "Queue Summary"
|
||||
|
||||
# Postfix: mailq | tail -1 returns "-- N Kbytes in M Requests."
|
||||
queue_summary=$(eval "$SYS_MAIL_CMD_QUEUE_COUNT")
|
||||
queue_summary=$(bash -c "$SYS_MAIL_CMD_QUEUE_COUNT" 2>/dev/null || echo "")
|
||||
print_info "$queue_summary"
|
||||
|
||||
# Extract message count from summary line (last number is always message count)
|
||||
@@ -89,7 +89,7 @@ elif [ "$MTA" = "postfix" ]; then
|
||||
echo ""
|
||||
|
||||
# Cache queue list - single execution for all operations
|
||||
queue_list=$(eval "$SYS_MAIL_CMD_QUEUE_LIST")
|
||||
queue_list=$(bash -c "$SYS_MAIL_CMD_QUEUE_LIST" 2>/dev/null || echo "")
|
||||
|
||||
print_header "Queue Details (first 50)"
|
||||
echo "$queue_list" | head -50
|
||||
@@ -116,7 +116,7 @@ elif [ "$MTA" = "sendmail" ]; then
|
||||
print_header "Queue Summary"
|
||||
|
||||
# Sendmail: mailq | tail -1 returns "-- N Kbytes in M Requests."
|
||||
queue_summary=$(eval "$SYS_MAIL_CMD_QUEUE_COUNT")
|
||||
queue_summary=$(bash -c "$SYS_MAIL_CMD_QUEUE_COUNT" 2>/dev/null || echo "")
|
||||
print_info "$queue_summary"
|
||||
|
||||
# Extract message count from summary line (last number is always message count)
|
||||
@@ -131,7 +131,7 @@ elif [ "$MTA" = "sendmail" ]; then
|
||||
echo ""
|
||||
|
||||
# Cache queue list - single execution for all operations
|
||||
queue_list=$(eval "$SYS_MAIL_CMD_QUEUE_LIST")
|
||||
queue_list=$(bash -c "$SYS_MAIL_CMD_QUEUE_LIST" 2>/dev/null || echo "")
|
||||
|
||||
print_header "Queue Details (first 50)"
|
||||
echo "$queue_list" | head -50
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
################################################################################
|
||||
# Disk Space Analyzer (WinDirStat for Linux)
|
||||
@@ -17,6 +18,7 @@
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common-functions.sh"
|
||||
source "$SCRIPT_DIR/lib/system-detect.sh"
|
||||
source "$SCRIPT_DIR/lib/reference-db.sh"
|
||||
|
||||
# Require root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
@@ -24,6 +26,9 @@ if [ "$EUID" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure cache is fresh (only rebuilds if > 1 hour old)
|
||||
db_ensure_fresh 2>/dev/null || true
|
||||
|
||||
# Temp file for results
|
||||
TEMP_DIR="/tmp/disk-analysis-$$"
|
||||
mkdir -p "$TEMP_DIR"
|
||||
@@ -619,55 +624,51 @@ analyze_wordpress() {
|
||||
print_banner "WordPress Storage Analysis"
|
||||
echo ""
|
||||
|
||||
# Find WordPress installations
|
||||
# Find WordPress installations from cache (instant lookup, no filesystem scan)
|
||||
show_progress "Finding WordPress installations"
|
||||
|
||||
local wp_paths=()
|
||||
local wp_count=0
|
||||
local wp_data=""
|
||||
|
||||
# Common locations
|
||||
if [ -d "/home" ]; then
|
||||
while IFS= read -r wp_config; do
|
||||
wp_dir=$(dirname "$wp_config")
|
||||
wp_paths+=("$wp_dir")
|
||||
done < <(find /home -name "wp-config.php" -type f 2>/dev/null)
|
||||
# Get WordPress data from cache
|
||||
if command -v db_get_all_wordpress &>/dev/null; then
|
||||
wp_data=$(db_get_all_wordpress 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
if [ -d "/var/www" ]; then
|
||||
while IFS= read -r wp_config; do
|
||||
wp_dir=$(dirname "$wp_config")
|
||||
wp_paths+=("$wp_dir")
|
||||
done < <(find /var/www -name "wp-config.php" -type f 2>/dev/null)
|
||||
# Count WP installations
|
||||
if [ -n "$wp_data" ]; then
|
||||
wp_count=$(echo "$wp_data" | grep -c "^WP|" || echo 0)
|
||||
fi
|
||||
|
||||
if [ ${#wp_paths[@]} -eq 0 ]; then
|
||||
if [ "$wp_count" -eq 0 ]; then
|
||||
echo -e "\r${DIM}No WordPress installations found${NC} "
|
||||
echo ""
|
||||
press_enter
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\r${GREEN}✓${NC} Found ${#wp_paths[@]} WordPress installations "
|
||||
echo -e "\r${GREEN}✓${NC} Found ${wp_count} WordPress installations "
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD}WordPress Space Usage:${NC}"
|
||||
echo "───────────────────────────────────────────────────────────────"
|
||||
|
||||
for wp_dir in "${wp_paths[@]}"; do
|
||||
# Get domain/user from path
|
||||
domain=$(echo "$wp_dir" | awk -F'/' '{for(i=1;i<=NF;i++) if($i~/public_html|httpdocs|www/) print $(i-1)}' | tail -1)
|
||||
|
||||
# Process cached WordPress data
|
||||
while IFS='|' read -r type domain path db_name db_user version plugins themes; do
|
||||
if [ "$type" = "WP" ] && [ -d "$path" ]; then
|
||||
# Calculate sizes
|
||||
total_size=$(du -sh "$wp_dir" 2>/dev/null | awk '{print $1}')
|
||||
uploads_size=$(du -sh "$wp_dir/wp-content/uploads" 2>/dev/null | awk '{print $1}')
|
||||
plugins_size=$(du -sh "$wp_dir/wp-content/plugins" 2>/dev/null | awk '{print $1}')
|
||||
cache_size=$(du -sh "$wp_dir/wp-content/cache" 2>/dev/null | awk '{print $1}')
|
||||
total_size=$(du -sh "$path" 2>/dev/null | awk '{print $1}')
|
||||
uploads_size=$(du -sh "$path/wp-content/uploads" 2>/dev/null | awk '{print $1}')
|
||||
plugins_size=$(du -sh "$path/wp-content/plugins" 2>/dev/null | awk '{print $1}')
|
||||
cache_size=$(du -sh "$path/wp-content/cache" 2>/dev/null | awk '{print $1}')
|
||||
|
||||
echo -e "${BOLD}$domain${NC} ($total_size)"
|
||||
echo -e " Uploads: ${CYAN}${uploads_size:-0}${NC}"
|
||||
echo -e " Plugins: ${CYAN}${plugins_size:-0}${NC}"
|
||||
echo -e " Cache: ${CYAN}${cache_size:-0}${NC}"
|
||||
echo ""
|
||||
done
|
||||
fi
|
||||
done <<< "$wp_data"
|
||||
|
||||
echo -e "${BOLD}Cleanup Suggestions:${NC}"
|
||||
echo " • Delete old revisions: wp post delete \$(wp post list --post_type=revision --format=ids)"
|
||||
|
||||
@@ -15,6 +15,9 @@ source "$TOOLKIT_ROOT/lib/reference-db.sh"
|
||||
# Initialize system detection
|
||||
detect_system
|
||||
|
||||
# Ensure reference database is fresh (only rebuild if > 1 hour old)
|
||||
db_ensure_fresh 2>/dev/null || true
|
||||
|
||||
# Load system info from reference database
|
||||
if [ -f "$TOOLKIT_ROOT/.sysref" ]; then
|
||||
SYS_HOSTNAME=$(grep "^SYS|HOSTNAME|" "$TOOLKIT_ROOT/.sysref" 2>/dev/null | cut -d'|' -f3)
|
||||
|
||||
@@ -15,6 +15,9 @@ source "$TOOLKIT_ROOT/lib/reference-db.sh"
|
||||
# Initialize system detection
|
||||
detect_system
|
||||
|
||||
# Ensure reference database is fresh (only rebuild if > 1 hour old)
|
||||
db_ensure_fresh 2>/dev/null || true
|
||||
|
||||
# Load system info from reference database
|
||||
if [ -f "$TOOLKIT_ROOT/.sysref" ]; then
|
||||
SYS_HOSTNAME=$(grep "^SYS|HOSTNAME|" "$TOOLKIT_ROOT/.sysref" 2>/dev/null | cut -d'|' -f3)
|
||||
|
||||
@@ -31,6 +31,9 @@ if [ "$EUID" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure reference database is fresh (only rebuild if > 1 hour old)
|
||||
db_ensure_fresh 2>/dev/null || true
|
||||
|
||||
# Configuration
|
||||
BACKUP_DIR="/root/nginx-varnish-backups"
|
||||
VARNISH_VCL="/etc/varnish/default.vcl"
|
||||
@@ -149,11 +152,28 @@ create_backup() {
|
||||
echo "$backup_path"
|
||||
}
|
||||
|
||||
# Get list of cPanel domains
|
||||
# Get list of cPanel domains (from launcher cache, not filesystem)
|
||||
get_cpanel_domains() {
|
||||
# Use launcher's cached domain list (instant lookup, already filtered by launcher)
|
||||
# Fallback to filesystem scan only if cache unavailable
|
||||
|
||||
if command -v db_get_all_domains &>/dev/null; then
|
||||
# Use cached data from launcher (built on startup, instant O(n) lookup)
|
||||
db_get_all_domains 2>/dev/null || {
|
||||
# Fallback if cache fails (shouldn't happen if db_ensure_fresh was called)
|
||||
get_cpanel_domains_fallback
|
||||
}
|
||||
else
|
||||
# Library not available, use filesystem fallback
|
||||
get_cpanel_domains_fallback
|
||||
fi
|
||||
}
|
||||
|
||||
# Fallback domain discovery (only used if cache unavailable)
|
||||
get_cpanel_domains_fallback() {
|
||||
local domains=()
|
||||
|
||||
# Get domains from cPanel user data
|
||||
# Fallback: Get domains from cPanel user data
|
||||
if [ -d /var/cpanel/userdata ]; then
|
||||
while IFS= read -r domain_file; do
|
||||
local domain=$(basename "$domain_file")
|
||||
|
||||
@@ -65,16 +65,44 @@ cecho " Scan Date: ${WHITE}$(date)${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# DOMAIN ENUMERATION & ANALYSIS
|
||||
# STEP 1: CALCULATE SERVER CAPACITY
|
||||
# ============================================================================
|
||||
|
||||
cecho "${WHITE}${BOLD}DOMAIN-BY-DOMAIN ANALYSIS${NC}"
|
||||
cecho "${WHITE}${BOLD}STEP 1: SERVER CAPACITY ANALYSIS${NC}"
|
||||
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
||||
|
||||
server_capacity_result=$(calculate_server_capacity "$TOTAL_RAM_MB")
|
||||
server_capacity=$(echo "$server_capacity_result" | cut -d'|' -f1)
|
||||
available_memory=$(echo "$server_capacity_result" | cut -d'|' -f2)
|
||||
memory_per_process=$(echo "$server_capacity_result" | cut -d'|' -f3)
|
||||
|
||||
cecho " Available RAM for PHP-FPM: ${WHITE}${available_memory}MB${NC}"
|
||||
cecho " Memory per process: ${WHITE}${memory_per_process}MB${NC}"
|
||||
cecho " Server capacity: ${WHITE}${server_capacity}${NC} total max_children"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# STEP 2: DOMAIN ENUMERATION & TRAFFIC ANALYSIS
|
||||
# ============================================================================
|
||||
|
||||
cecho "${WHITE}${BOLD}STEP 2: DOMAIN ENUMERATION & TRAFFIC ANALYSIS${NC}"
|
||||
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
||||
|
||||
# Get all users and domains
|
||||
users=$(list_all_users)
|
||||
|
||||
# CRITICAL FIX: Build list of ALL domains on server FIRST
|
||||
# This is needed for accurate traffic percentage calculation
|
||||
all_domains_string="" # Note: NOT local (function scope only), this is script-level
|
||||
while IFS= read -r username; do
|
||||
[ -z "$username" ] && continue
|
||||
user_domains=$(get_user_domains "$username")
|
||||
while IFS= read -r domain; do
|
||||
[ -z "$domain" ] && continue
|
||||
all_domains_string="$all_domains_string$domain"$'\n'
|
||||
done <<< "$user_domains"
|
||||
done <<< "$users"
|
||||
|
||||
# Initialize tracking arrays
|
||||
declare -a domain_list
|
||||
declare -a domain_owner
|
||||
@@ -88,6 +116,8 @@ declare -a pm_max_requests
|
||||
declare -a pm_min_spare
|
||||
declare -a pm_max_spare
|
||||
declare -a pm_idle_timeout
|
||||
declare -a traffic_percentage_arr
|
||||
declare -a limiting_factor_arr
|
||||
|
||||
TOTAL_DOMAINS=0
|
||||
TOTAL_CURRENT_MEMORY=0
|
||||
@@ -137,15 +167,24 @@ while IFS= read -r username; do
|
||||
pm_idle=$(grep "^pm.process_idle_timeout = " "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ')
|
||||
pm_idle_timeout[$TOTAL_DOMAINS]="${pm_idle:-10}"
|
||||
|
||||
# Calculate recommended using improved algorithm
|
||||
recommended_result=$(calculate_optimal_php_settings "$username" "$TOTAL_RAM_MB" 2>/dev/null || echo "20||")
|
||||
# Calculate recommended using THREE-CONSTRAINT INTELLIGENT ALGORITHM
|
||||
# Get traffic percentage for this domain
|
||||
# CRITICAL FIX: Pass ALL server domains, not just user's domains!
|
||||
traffic_percentage=$(get_domain_traffic_percentage "$username" "$domain" "$all_domains_string" 2>/dev/null | cut -d'|' -f1)
|
||||
traffic_percentage=${traffic_percentage:-50}
|
||||
|
||||
# Use intelligent three-constraint model: MIN(memory, traffic, fair_share)
|
||||
recommended_result=$(calculate_optimal_php_settings_intelligent "$username" "$TOTAL_RAM_MB" "$server_capacity" "$traffic_percentage" 2>/dev/null || echo "20|dynamic|1|5|ERROR|Failed")
|
||||
recommended=$(echo "$recommended_result" | cut -d'|' -f1)
|
||||
recommended=${recommended:-20}
|
||||
limiting_factor=$(echo "$recommended_result" | cut -d'|' -f5)
|
||||
recommended_max_children[$TOTAL_DOMAINS]="$recommended"
|
||||
traffic_percentage_arr[$TOTAL_DOMAINS]="$traffic_percentage"
|
||||
limiting_factor_arr[$TOTAL_DOMAINS]="$limiting_factor"
|
||||
|
||||
# Calculate memory impact (assuming 20MB per process on average)
|
||||
current_memory=$((current * 20))
|
||||
recommended_memory=$((recommended * 20))
|
||||
# Calculate memory impact using ACTUAL memory per process (not hardcoded 20MB)
|
||||
current_memory=$((current * memory_per_process))
|
||||
recommended_memory=$((recommended * memory_per_process))
|
||||
impact=$((current_memory - recommended_memory))
|
||||
memory_impact[$TOTAL_DOMAINS]="$impact"
|
||||
|
||||
@@ -241,17 +280,18 @@ for idx in "${sorted_indices[@]}"; do
|
||||
continue
|
||||
fi
|
||||
|
||||
# Determine traffic indicator
|
||||
# Determine traffic indicator based on traffic PERCENTAGE (not peak concurrent)
|
||||
traffic_indicator=""
|
||||
if [[ "$peak" =~ ^[0-9]+$ ]]; then
|
||||
if [ "$peak" -ge 20 ]; then
|
||||
traffic_indicator="${RED}⚠ CRITICAL TRAFFIC (${peak})${NC}"
|
||||
elif [ "$peak" -ge 10 ]; then
|
||||
traffic_indicator="${YELLOW}⚠ HIGH TRAFFIC (${peak})${NC}"
|
||||
elif [ "$peak" -ge 5 ]; then
|
||||
traffic_indicator="${CYAN}→ MEDIUM TRAFFIC (${peak})${NC}"
|
||||
traffic_pct="${traffic_percentage_arr[$idx]:-0}"
|
||||
if [[ "$traffic_pct" =~ ^[0-9]+$ ]]; then
|
||||
if [ "$traffic_pct" -ge 50 ]; then
|
||||
traffic_indicator="${RED}⚠ CRITICAL TRAFFIC${NC}"
|
||||
elif [ "$traffic_pct" -ge 25 ]; then
|
||||
traffic_indicator="${YELLOW}⚠ HIGH TRAFFIC${NC}"
|
||||
elif [ "$traffic_pct" -ge 10 ]; then
|
||||
traffic_indicator="${CYAN}→ MEDIUM TRAFFIC${NC}"
|
||||
else
|
||||
traffic_indicator="${WHITE}○ LOW TRAFFIC (${peak})${NC}"
|
||||
traffic_indicator="${WHITE}○ LOW TRAFFIC${NC}"
|
||||
fi
|
||||
else
|
||||
traffic_indicator="${WHITE}○ TRAFFIC UNKNOWN${NC}"
|
||||
@@ -261,7 +301,8 @@ for idx in "${sorted_indices[@]}"; do
|
||||
if [ "$optimize" == "YES" ]; then
|
||||
cecho "${YELLOW}[$idx]${NC} $domain"
|
||||
cecho " Owner: $owner"
|
||||
cecho " Traffic: $traffic_indicator"
|
||||
cecho " Traffic: $traffic_indicator (${traffic_percentage_arr[$idx]}% of server)"
|
||||
cecho " Limiting Factor: ${limiting_factor_arr[$idx]}"
|
||||
cecho ""
|
||||
cecho " ${BOLD}Current Pool Settings:${NC}"
|
||||
cecho " pm.max_children: ${RED}$current${NC} → Recommended: ${GREEN}$recommended${NC}"
|
||||
@@ -271,13 +312,22 @@ for idx in "${sorted_indices[@]}"; do
|
||||
cecho " pm.max_requests: ${WHITE}${pm_max_requests[$idx]}${NC}"
|
||||
cecho " pm.process_idle_timeout: ${WHITE}${pm_idle_timeout[$idx]}${NC}"
|
||||
cecho ""
|
||||
cecho " Memory impact: ${GREEN}+${impact}MB${NC} if optimized"
|
||||
# Display memory clearly: current vs recommended
|
||||
current_memory=$((current * memory_per_process))
|
||||
recommended_memory=$((recommended * memory_per_process))
|
||||
if [ "$impact" -gt 0 ]; then
|
||||
cecho " Memory Usage: ${RED}${current_memory}MB${NC} (current) → ${GREEN}${recommended_memory}MB${NC} (recommended)"
|
||||
cecho " Memory Savings: ${GREEN}${impact}MB${NC} if optimized"
|
||||
else
|
||||
cecho " Memory Usage: ${RED}${current_memory}MB${NC} (current) → ${YELLOW}${recommended_memory}MB${NC} (recommended)"
|
||||
cecho " Additional Memory Needed: ${YELLOW}$((impact * -1))MB${NC} if optimized"
|
||||
fi
|
||||
cecho " Status: ${YELLOW}NEEDS OPTIMIZATION${NC}"
|
||||
OPTIMIZATION_COUNT=$((OPTIMIZATION_COUNT + 1))
|
||||
else
|
||||
cecho "${GREEN}[$idx]${NC} $domain"
|
||||
cecho " Owner: $owner"
|
||||
cecho " Traffic: $traffic_indicator"
|
||||
cecho " Traffic: $traffic_indicator (${traffic_percentage_arr[$idx]}% of server)"
|
||||
cecho ""
|
||||
cecho " ${BOLD}Pool Settings:${NC}"
|
||||
cecho " pm.max_children: $current"
|
||||
|
||||
@@ -1151,10 +1151,14 @@ modify_php_ini_setting() {
|
||||
# Backup before modifying
|
||||
cp "$ini_file" "$ini_file.backup.$$" 2>/dev/null || return 1
|
||||
|
||||
# Check if setting exists
|
||||
if grep -q "^$setting" "$ini_file"; then
|
||||
# Replace existing setting
|
||||
sed -i "s/^$setting.*/$setting = $value/" "$ini_file" 2>/dev/null || {
|
||||
# Escape setting and value for sed (handle special chars like dots, slashes)
|
||||
local setting_escaped=$(printf '%s\n' "$setting" | sed -e 's/[\.&/\]/\\&/g')
|
||||
local value_escaped=$(printf '%s\n' "$value" | sed -e 's/[\.&/\]/\\&/g')
|
||||
|
||||
# Check if setting exists (use literal grep, not regex)
|
||||
if grep -q "^$(printf '%s\n' "$setting" | sed -e 's/[[\.*^$/]/\\&/g')" "$ini_file"; then
|
||||
# Replace existing setting (use | as sed delimiter to avoid / conflicts)
|
||||
sed -i "s|^$setting_escaped.*|$setting = $value_escaped|" "$ini_file" 2>/dev/null || {
|
||||
mv "$ini_file.backup.$$" "$ini_file"
|
||||
return 1
|
||||
}
|
||||
@@ -1185,8 +1189,9 @@ validate_php_ini() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Use php -i to check for syntax errors (basic validation)
|
||||
php -d "display_errors=0" -r "return 0;" 2>&1 | grep -q "Parse error\|Fatal error" && return 1
|
||||
# Use php to check for syntax errors (basic validation)
|
||||
# Add || true to handle grep returning 1 with set -o pipefail
|
||||
php -d "display_errors=0" -r "return 0;" 2>&1 | grep -q "Parse error\|Fatal error" && return 1 || true
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -1223,8 +1228,9 @@ is_opcache_enabled() {
|
||||
# Calculate optimal OPcache memory
|
||||
calculate_optimal_opcache_memory() {
|
||||
local avg_rpm="$1"
|
||||
local available_memory="${2:-}" # Optional: available memory limit
|
||||
|
||||
# Base recommendation in MB
|
||||
# Base recommendation in MB based on traffic
|
||||
local memory="64"
|
||||
|
||||
if [ "$avg_rpm" -ge 100 ]; then
|
||||
@@ -1237,9 +1243,47 @@ calculate_optimal_opcache_memory() {
|
||||
memory="64"
|
||||
fi
|
||||
|
||||
# If available memory is specified, don't exceed it
|
||||
if [ -n "$available_memory" ] && [ "$available_memory" -gt 0 ]; then
|
||||
# Extract numeric value (remove 'M' if present)
|
||||
local avail_num=${available_memory%M}
|
||||
if [ "$memory" -gt "$avail_num" ]; then
|
||||
memory=$avail_num
|
||||
fi
|
||||
# Minimum 32MB for OPcache
|
||||
[ "$memory" -lt 32 ] && memory=32
|
||||
fi
|
||||
|
||||
echo "${memory}M"
|
||||
}
|
||||
|
||||
# Check if OPcache is disabled in domain's ini files (per-domain check)
|
||||
is_opcache_disabled_in_domain() {
|
||||
local username="$1"
|
||||
local domain="$2"
|
||||
|
||||
local ini_files
|
||||
ini_files=$(find_php_ini_files "$username" "$domain")
|
||||
|
||||
while IFS= read -r ini_file; do
|
||||
[ -z "$ini_file" ] && continue
|
||||
[ ! -f "$ini_file" ] && continue
|
||||
|
||||
# Check if opcache.enable = 0 (explicitly disabled)
|
||||
if grep -q "^opcache.enable.*=.*0" "$ini_file" 2>/dev/null; then
|
||||
return 0 # OPcache IS disabled, needs enabling
|
||||
fi
|
||||
# Check if opcache.enable is not set at all
|
||||
if ! grep -q "^opcache.enable" "$ini_file" 2>/dev/null; then
|
||||
# Not explicitly set - may need enabling
|
||||
# We'll return 0 to try enabling it
|
||||
return 0
|
||||
fi
|
||||
done <<< "$ini_files"
|
||||
|
||||
return 1 # OPcache appears to be enabled
|
||||
}
|
||||
|
||||
# Enable OPcache in php.ini
|
||||
enable_opcache() {
|
||||
local ini_file="$1"
|
||||
@@ -1563,6 +1607,17 @@ optimize_level_1_max_children() {
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Calculate server capacity for intelligent three-constraint model
|
||||
local server_capacity_result
|
||||
server_capacity_result=$(calculate_server_capacity "$total_ram_mb")
|
||||
local server_capacity
|
||||
server_capacity=$(echo "$server_capacity_result" | cut -d'|' -f1)
|
||||
local server_memory_per_process
|
||||
server_memory_per_process=$(echo "$server_capacity_result" | cut -d'|' -f3)
|
||||
cecho " Server PHP-FPM capacity: ${WHITE}${server_capacity}${NC} max_children"
|
||||
cecho " Using intelligent three-constraint model: Memory + Traffic + Fair Share"
|
||||
echo ""
|
||||
|
||||
# Get recommendations
|
||||
cecho "${CYAN}Step 2: Calculating optimal settings...${NC}"
|
||||
echo ""
|
||||
@@ -1575,6 +1630,18 @@ optimize_level_1_max_children() {
|
||||
local users
|
||||
users=$(list_all_users)
|
||||
|
||||
# CRITICAL FIX: Build list of ALL domains on server FIRST
|
||||
# This is needed for accurate traffic percentage calculation
|
||||
all_domains_string=""
|
||||
while IFS= read -r u; do
|
||||
[ -z "$u" ] && continue
|
||||
u_domains=$(get_user_domains "$u")
|
||||
while IFS= read -r d; do
|
||||
[ -z "$d" ] && continue
|
||||
all_domains_string="$all_domains_string$d"$'\n'
|
||||
done <<< "$u_domains"
|
||||
done <<< "$users"
|
||||
|
||||
while IFS= read -r username; do
|
||||
[ -z "$username" ] && continue
|
||||
local user_domains
|
||||
@@ -1594,16 +1661,21 @@ optimize_level_1_max_children() {
|
||||
current_max="?"
|
||||
fi
|
||||
|
||||
# Get recommendations - use profile if available, otherwise use traffic-based
|
||||
# Get recommendations - use profile if available, otherwise use intelligent three-constraint model
|
||||
local recommended_max
|
||||
if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then
|
||||
recommended_max=$(get_max_children_recommendation "$domain" "$username")
|
||||
else
|
||||
# Fallback to traffic-based (old method)
|
||||
local traffic_rpm
|
||||
traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0")
|
||||
[ "$traffic_rpm" = "?" ] && traffic_rpm="0"
|
||||
recommended_max=$((traffic_rpm > 5 ? traffic_rpm + 10 : 5))
|
||||
# Use intelligent three-constraint model (same as Level 5)
|
||||
local traffic_pct
|
||||
traffic_pct=$(get_domain_traffic_percentage "$username" "$domain" "$all_domains_string" 2>/dev/null | cut -d'|' -f1)
|
||||
traffic_pct=${traffic_pct:-50}
|
||||
|
||||
# Call intelligent three-constraint function
|
||||
local intel_result
|
||||
intel_result=$(calculate_optimal_php_settings_intelligent "$username" "$total_ram_mb" "$server_capacity" "$traffic_pct" 2>/dev/null || echo "20|dynamic|1|5|ERROR|Failed")
|
||||
|
||||
recommended_max=$(echo "$intel_result" | cut -d'|' -f1)
|
||||
fi
|
||||
|
||||
recommended_values["$domain"]="$recommended_max"
|
||||
@@ -1621,6 +1693,46 @@ optimize_level_1_max_children() {
|
||||
done <<< "$user_domains"
|
||||
done <<< "$users"
|
||||
|
||||
# CRITICAL VALIDATION: Check if combined recommendations exceed safe limits
|
||||
cecho "${CYAN}Step 2b: Validating capacity...${NC}"
|
||||
echo ""
|
||||
|
||||
local total_recommended_max_children=0
|
||||
local avg_memory_per_process=$server_memory_per_process
|
||||
local total_recommended_memory=0
|
||||
|
||||
for domain in "${!recommended_values[@]}"; do
|
||||
local rec_max="${recommended_values[$domain]}"
|
||||
[ -z "$rec_max" ] && continue
|
||||
total_recommended_max_children=$((total_recommended_max_children + rec_max))
|
||||
total_recommended_memory=$((total_recommended_memory + (rec_max * avg_memory_per_process)))
|
||||
done
|
||||
|
||||
# Determine if recommendations are safe
|
||||
local max_safe_php_fpm=$((total_ram_mb * 60 / 100))
|
||||
|
||||
if [ "$total_recommended_memory" -gt "$max_safe_php_fpm" ]; then
|
||||
cecho "${RED}${BOLD}⚠ WARNING: Combined recommendations exceed safe limits!${NC}"
|
||||
cecho "${RED}Recommended total: ${total_recommended_memory}MB (${total_recommended_max_children} max_children combined)${NC}"
|
||||
cecho "${RED}Safe maximum: ${max_safe_php_fpm}MB${NC}"
|
||||
cecho "${YELLOW}Applying safety caps to prevent OOM crashes...${NC}"
|
||||
echo ""
|
||||
|
||||
# Scale down all recommendations proportionally
|
||||
local scale_factor
|
||||
scale_factor=$((max_safe_php_fpm * 100 / total_recommended_memory))
|
||||
|
||||
for domain in "${!recommended_values[@]}"; do
|
||||
local rec_max="${recommended_values[$domain]}"
|
||||
[ -z "$rec_max" ] && continue
|
||||
rec_max=$((rec_max * scale_factor / 100))
|
||||
[ "$rec_max" -lt 5 ] && rec_max=5
|
||||
recommended_values["$domain"]="$rec_max"
|
||||
done
|
||||
cecho "${CYAN}Applied proportional scaling (${scale_factor}%)${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ "$changes_needed" -eq 0 ]; then
|
||||
cecho "${GREEN}${BOLD}✓ All domains already optimized - no changes needed${NC}"
|
||||
@@ -1752,6 +1864,17 @@ optimize_level_2_memory() {
|
||||
cecho " Status: ${WHITE}${status}${NC}"
|
||||
echo ""
|
||||
|
||||
# Calculate server capacity for intelligent three-constraint model
|
||||
local server_capacity_result
|
||||
server_capacity_result=$(calculate_server_capacity "$total_ram_mb")
|
||||
local server_capacity
|
||||
server_capacity=$(echo "$server_capacity_result" | cut -d'|' -f1)
|
||||
local server_memory_per_process
|
||||
server_memory_per_process=$(echo "$server_capacity_result" | cut -d'|' -f3)
|
||||
cecho " Server PHP-FPM capacity: ${WHITE}${server_capacity}${NC} max_children"
|
||||
cecho " Using intelligent three-constraint model: Memory + Traffic + Fair Share"
|
||||
echo ""
|
||||
|
||||
# Check if profiles exist
|
||||
local profiles_exist=0
|
||||
if [ -d "/tmp/php-domain-profiles" ] && [ "$(ls -1 /tmp/php-domain-profiles/*.profile 2>/dev/null | wc -l)" -gt 0 ]; then
|
||||
@@ -1762,7 +1885,7 @@ optimize_level_2_memory() {
|
||||
else
|
||||
cecho "${YELLOW}${BOLD}⚠ No domain profiles found${NC}"
|
||||
cecho "${CYAN}For more accurate optimization, run pre-analysis first:${NC}"
|
||||
cecho " ${WHITE}php-optimizer.sh${NC} → Option 3 (Pre-analyze domains)"
|
||||
cecho " ${WHITE}php-optimizer.sh${NC} → Option 0 (Pre-analyze domains)"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -1779,6 +1902,18 @@ optimize_level_2_memory() {
|
||||
local users
|
||||
users=$(list_all_users)
|
||||
|
||||
# CRITICAL FIX: Build list of ALL domains on server FIRST
|
||||
# This is needed for accurate traffic percentage calculation
|
||||
all_domains_string=""
|
||||
while IFS= read -r u; do
|
||||
[ -z "$u" ] && continue
|
||||
u_domains=$(get_user_domains "$u")
|
||||
while IFS= read -r d; do
|
||||
[ -z "$d" ] && continue
|
||||
all_domains_string="$all_domains_string$d"$'\n'
|
||||
done <<< "$u_domains"
|
||||
done <<< "$users"
|
||||
|
||||
while IFS= read -r username; do
|
||||
[ -z "$username" ] && continue
|
||||
local user_domains
|
||||
@@ -1798,27 +1933,24 @@ optimize_level_2_memory() {
|
||||
current_max="?"
|
||||
fi
|
||||
|
||||
# Get recommendations - use profile if available, otherwise use traffic-based
|
||||
# Get recommendations - use profile if available, otherwise use intelligent three-constraint model
|
||||
local recommended_max
|
||||
if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then
|
||||
recommended_max=$(get_max_children_recommendation "$domain" "$username")
|
||||
else
|
||||
# Fallback to traffic-based (old method)
|
||||
local traffic_rpm
|
||||
traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0")
|
||||
[ "$traffic_rpm" = "?" ] && traffic_rpm="0"
|
||||
recommended_max=$((traffic_rpm > 5 ? traffic_rpm + 10 : 5))
|
||||
fi
|
||||
|
||||
local recommended_memory
|
||||
if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then
|
||||
recommended_max=$(get_max_children_recommendation "$domain" "$username")
|
||||
recommended_memory=$(get_memory_limit_recommendation "$domain" "$username")
|
||||
else
|
||||
# Fallback to traffic-based (old method)
|
||||
local traffic_rpm
|
||||
traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0")
|
||||
[ "$traffic_rpm" = "?" ] && traffic_rpm="0"
|
||||
recommended_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm")
|
||||
# Use intelligent three-constraint model (same as Level 5)
|
||||
local traffic_pct
|
||||
traffic_pct=$(get_domain_traffic_percentage "$username" "$domain" "$all_domains_string" 2>/dev/null | cut -d'|' -f1)
|
||||
traffic_pct=${traffic_pct:-50}
|
||||
|
||||
# Call intelligent three-constraint function
|
||||
local intel_result
|
||||
intel_result=$(calculate_optimal_php_settings_intelligent "$username" "$total_ram_mb" "$server_capacity" "$traffic_pct" 2>/dev/null || echo "20|dynamic|1|5|ERROR|Failed")
|
||||
|
||||
recommended_max=$(echo "$intel_result" | cut -d'|' -f1)
|
||||
recommended_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$recommended_max" 2>/dev/null || echo "128M")
|
||||
fi
|
||||
|
||||
recommended_max_children["$domain"]="$recommended_max"
|
||||
@@ -1839,6 +1971,46 @@ optimize_level_2_memory() {
|
||||
done <<< "$user_domains"
|
||||
done <<< "$users"
|
||||
|
||||
# CRITICAL VALIDATION: Check if combined recommendations exceed safe limits
|
||||
cecho "${CYAN}Step 2b: Validating capacity...${NC}"
|
||||
echo ""
|
||||
|
||||
local total_recommended_max_children=0
|
||||
local avg_memory_per_process=$server_memory_per_process
|
||||
local total_recommended_memory=0
|
||||
|
||||
for domain in "${!recommended_max_children[@]}"; do
|
||||
local rec_max="${recommended_max_children[$domain]}"
|
||||
[ -z "$rec_max" ] && continue
|
||||
total_recommended_max_children=$((total_recommended_max_children + rec_max))
|
||||
total_recommended_memory=$((total_recommended_memory + (rec_max * avg_memory_per_process)))
|
||||
done
|
||||
|
||||
# Determine if recommendations are safe
|
||||
local max_safe_php_fpm=$((total_ram_mb * 60 / 100))
|
||||
|
||||
if [ "$total_recommended_memory" -gt "$max_safe_php_fpm" ]; then
|
||||
cecho "${RED}${BOLD}⚠ WARNING: Combined recommendations exceed safe limits!${NC}"
|
||||
cecho "${RED}Recommended total: ${total_recommended_memory}MB (${total_recommended_max_children} max_children combined)${NC}"
|
||||
cecho "${RED}Safe maximum: ${max_safe_php_fpm}MB${NC}"
|
||||
cecho "${YELLOW}Applying safety caps to prevent OOM crashes...${NC}"
|
||||
echo ""
|
||||
|
||||
# Scale down all recommendations proportionally
|
||||
local scale_factor
|
||||
scale_factor=$((max_safe_php_fpm * 100 / total_recommended_memory))
|
||||
|
||||
for domain in "${!recommended_max_children[@]}"; do
|
||||
local rec_max="${recommended_max_children[$domain]}"
|
||||
[ -z "$rec_max" ] && continue
|
||||
rec_max=$((rec_max * scale_factor / 100))
|
||||
[ "$rec_max" -lt 5 ] && rec_max=5
|
||||
recommended_max_children["$domain"]="$rec_max"
|
||||
done
|
||||
cecho "${CYAN}Applied proportional scaling (${scale_factor}%)${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ "$changes_needed" -eq 0 ]; then
|
||||
cecho "${GREEN}${BOLD}✓ All domains already optimized${NC}"
|
||||
@@ -2006,6 +2178,17 @@ optimize_level_3_advanced() {
|
||||
cecho " Status: ${WHITE}${status}${NC}"
|
||||
echo ""
|
||||
|
||||
# Calculate server capacity for intelligent three-constraint model
|
||||
local server_capacity_result
|
||||
server_capacity_result=$(calculate_server_capacity "$total_ram_mb")
|
||||
local server_capacity
|
||||
server_capacity=$(echo "$server_capacity_result" | cut -d'|' -f1)
|
||||
local server_memory_per_process
|
||||
server_memory_per_process=$(echo "$server_capacity_result" | cut -d'|' -f3)
|
||||
cecho " Server PHP-FPM capacity: ${WHITE}${server_capacity}${NC} max_children"
|
||||
cecho " Using intelligent three-constraint model: Memory + Traffic + Fair Share"
|
||||
echo ""
|
||||
|
||||
# Check if profiles exist
|
||||
local profiles_exist=0
|
||||
if [ -d "/tmp/php-domain-profiles" ] && [ "$(ls -1 /tmp/php-domain-profiles/*.profile 2>/dev/null | wc -l)" -gt 0 ]; then
|
||||
@@ -2029,6 +2212,18 @@ optimize_level_3_advanced() {
|
||||
local users
|
||||
users=$(list_all_users)
|
||||
|
||||
# CRITICAL FIX: Build list of ALL domains on server FIRST
|
||||
# This is needed for accurate traffic percentage calculation
|
||||
all_domains_string=""
|
||||
while IFS= read -r u; do
|
||||
[ -z "$u" ] && continue
|
||||
u_domains=$(get_user_domains "$u")
|
||||
while IFS= read -r d; do
|
||||
[ -z "$d" ] && continue
|
||||
all_domains_string="$all_domains_string$d"$'\n'
|
||||
done <<< "$u_domains"
|
||||
done <<< "$users"
|
||||
|
||||
while IFS= read -r username; do
|
||||
[ -z "$username" ] && continue
|
||||
local user_domains
|
||||
@@ -2048,14 +2243,18 @@ optimize_level_3_advanced() {
|
||||
recommended_memory_limit["$domain"]=$(get_memory_limit_recommendation "$domain" "$username")
|
||||
recommended_max_requests["$domain"]=$(get_max_requests_recommendation "$domain")
|
||||
else
|
||||
# Fallback to traffic-based (old method)
|
||||
local traffic_rpm
|
||||
traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0")
|
||||
[ "$traffic_rpm" = "?" ] && traffic_rpm="0"
|
||||
# Use intelligent three-constraint model (same as Level 5)
|
||||
local traffic_pct
|
||||
traffic_pct=$(get_domain_traffic_percentage "$username" "$domain" "$all_domains_string" 2>/dev/null | cut -d'|' -f1)
|
||||
traffic_pct=${traffic_pct:-50}
|
||||
|
||||
recommended_max_children["$domain"]=$((traffic_rpm > 5 ? traffic_rpm + 10 : 5))
|
||||
recommended_memory_limit["$domain"]=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm")
|
||||
recommended_max_requests["$domain"]=$(calculate_optimal_max_requests "$traffic_rpm")
|
||||
# Call intelligent three-constraint function
|
||||
local intel_result
|
||||
intel_result=$(calculate_optimal_php_settings_intelligent "$username" "$total_ram_mb" "$server_capacity" "$traffic_pct" 2>/dev/null || echo "20|dynamic|1|5|ERROR|Failed")
|
||||
|
||||
recommended_max_children["$domain"]=$(echo "$intel_result" | cut -d'|' -f1)
|
||||
recommended_memory_limit["$domain"]=$(calculate_optimal_memory_limit "$username" "$domain" "${recommended_max_children[$domain]}" 2>/dev/null || echo "128M")
|
||||
recommended_max_requests["$domain"]=$(calculate_optimal_max_requests "${recommended_max_children[$domain]}" 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
local current_max
|
||||
@@ -2088,6 +2287,46 @@ optimize_level_3_advanced() {
|
||||
done <<< "$user_domains"
|
||||
done <<< "$users"
|
||||
|
||||
# CRITICAL VALIDATION: Check if combined recommendations exceed safe limits
|
||||
cecho "${CYAN}Step 2b: Validating capacity...${NC}"
|
||||
echo ""
|
||||
|
||||
local total_recommended_max_children=0
|
||||
local avg_memory_per_process=$server_memory_per_process
|
||||
local total_recommended_memory=0
|
||||
|
||||
for domain in "${!recommended_max_children[@]}"; do
|
||||
local rec_max="${recommended_max_children[$domain]}"
|
||||
[ -z "$rec_max" ] && continue
|
||||
total_recommended_max_children=$((total_recommended_max_children + rec_max))
|
||||
total_recommended_memory=$((total_recommended_memory + (rec_max * avg_memory_per_process)))
|
||||
done
|
||||
|
||||
# Determine if recommendations are safe
|
||||
local max_safe_php_fpm=$((total_ram_mb * 60 / 100))
|
||||
|
||||
if [ "$total_recommended_memory" -gt "$max_safe_php_fpm" ]; then
|
||||
cecho "${RED}${BOLD}⚠ WARNING: Combined recommendations exceed safe limits!${NC}"
|
||||
cecho "${RED}Recommended total: ${total_recommended_memory}MB (${total_recommended_max_children} max_children combined)${NC}"
|
||||
cecho "${RED}Safe maximum: ${max_safe_php_fpm}MB${NC}"
|
||||
cecho "${YELLOW}Applying safety caps to prevent OOM crashes...${NC}"
|
||||
echo ""
|
||||
|
||||
# Scale down all recommendations proportionally
|
||||
local scale_factor
|
||||
scale_factor=$((max_safe_php_fpm * 100 / total_recommended_memory))
|
||||
|
||||
for domain in "${!recommended_max_children[@]}"; do
|
||||
local rec_max="${recommended_max_children[$domain]}"
|
||||
[ -z "$rec_max" ] && continue
|
||||
rec_max=$((rec_max * scale_factor / 100))
|
||||
[ "$rec_max" -lt 5 ] && rec_max=5
|
||||
recommended_max_children["$domain"]="$rec_max"
|
||||
done
|
||||
cecho "${CYAN}Applied proportional scaling (${scale_factor}%)${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ "$changes_needed" -eq 0 ]; then
|
||||
cecho "${GREEN}${BOLD}✓ All domains already optimized${NC}"
|
||||
@@ -2269,13 +2508,14 @@ optimize_level_4_opcache() {
|
||||
while IFS= read -r domain; do
|
||||
[ -z "$domain" ] && continue
|
||||
|
||||
if is_opcache_enabled "$username"; then
|
||||
opcache_enabled["$domain"]="1"
|
||||
already_enabled=$((already_enabled + 1))
|
||||
else
|
||||
# Check per-domain ini files, not just per-user
|
||||
if is_opcache_disabled_in_domain "$username" "$domain"; then
|
||||
opcache_needs_enable["$domain"]="1"
|
||||
needs_enable_count=$((needs_enable_count + 1))
|
||||
cecho " ${YELLOW}⚠${NC} $domain: OPcache is disabled"
|
||||
else
|
||||
opcache_enabled["$domain"]="1"
|
||||
already_enabled=$((already_enabled + 1))
|
||||
fi
|
||||
done <<< "$user_domains"
|
||||
done <<< "$users"
|
||||
@@ -2332,11 +2572,12 @@ optimize_level_4_opcache() {
|
||||
|
||||
# Enable OPcache
|
||||
if enable_opcache "$ini_file" >/dev/null 2>&1; then
|
||||
# Calculate optimal memory for OPcache
|
||||
# Calculate optimal memory for OPcache (safe limit: 256MB max)
|
||||
local avg_rpm
|
||||
avg_rpm=$(calculate_avg_requests_per_minute "$username" 24 | cut -d'|' -f1)
|
||||
local optimal_memory
|
||||
optimal_memory=$(calculate_optimal_opcache_memory "$avg_rpm")
|
||||
# Pass 256MB as max available (OPcache is global and shared across all domains)
|
||||
optimal_memory=$(calculate_optimal_opcache_memory "$avg_rpm" "256")
|
||||
|
||||
# Set memory_consumption
|
||||
if modify_php_ini_setting "$ini_file" "opcache.memory_consumption" "$optimal_memory" >/dev/null 2>&1; then
|
||||
@@ -2448,7 +2689,29 @@ optimize_level_5_everything() {
|
||||
cecho " Status: ${WHITE}${status}${NC}"
|
||||
echo ""
|
||||
|
||||
cecho "${CYAN}STEP 2: Calculating Recommendations${NC}"
|
||||
# CRITICAL SAFETY CHECK: If current usage is already > 80%, warn user
|
||||
if [ "$percentage" -gt 80 ]; then
|
||||
cecho "${RED}${BOLD}⚠ CRITICAL: Server is running with insufficient PHP-FPM headroom!${NC}"
|
||||
cecho "${RED}Current allocation is ${percentage}% of RAM.${NC}"
|
||||
cecho "${RED}Optimization may not be sufficient - consider:${NC}"
|
||||
cecho "${RED} 1. Upgrading server RAM${NC}"
|
||||
cecho "${RED} 2. Disabling problematic domains${NC}"
|
||||
cecho "${RED} 3. Migrating heavy sites to dedicated servers${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
cecho "${CYAN}STEP 2: Calculating Intelligent Recommendations${NC}"
|
||||
echo ""
|
||||
|
||||
# Calculate server capacity for fair share allocation
|
||||
local server_capacity_result
|
||||
server_capacity_result=$(calculate_server_capacity "$total_ram_mb")
|
||||
local server_capacity
|
||||
server_capacity=$(echo "$server_capacity_result" | cut -d'|' -f1)
|
||||
local server_memory_per_process
|
||||
server_memory_per_process=$(echo "$server_capacity_result" | cut -d'|' -f3)
|
||||
cecho " Server PHP-FPM capacity: ${WHITE}${server_capacity}${NC} max_children"
|
||||
cecho " Using three-constraint model: Memory + Traffic + Fair Share"
|
||||
echo ""
|
||||
|
||||
# Check if profiles exist
|
||||
@@ -2472,6 +2735,18 @@ optimize_level_5_everything() {
|
||||
local users
|
||||
users=$(list_all_users)
|
||||
|
||||
# CRITICAL FIX: Build list of ALL domains on server FIRST
|
||||
# This is needed for accurate traffic percentage calculation (same as batch analyzer)
|
||||
all_domains_string=""
|
||||
while IFS= read -r u; do
|
||||
[ -z "$u" ] && continue
|
||||
u_domains=$(get_user_domains "$u")
|
||||
while IFS= read -r d; do
|
||||
[ -z "$d" ] && continue
|
||||
all_domains_string="$all_domains_string$d"$'\n'
|
||||
done <<< "$u_domains"
|
||||
done <<< "$users"
|
||||
|
||||
while IFS= read -r username; do
|
||||
[ -z "$username" ] && continue
|
||||
local user_domains
|
||||
@@ -2491,24 +2766,31 @@ optimize_level_5_everything() {
|
||||
current_max="?"
|
||||
fi
|
||||
|
||||
# Get recommendations - use profile if available, otherwise use traffic-based
|
||||
# Get recommendations using THREE-CONSTRAINT INTELLIGENT MODEL
|
||||
local recommended_max
|
||||
local recommended_memory
|
||||
local recommended_requests
|
||||
local traffic_pct=50 # Default if no data
|
||||
|
||||
# Get traffic percentage for this domain
|
||||
if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then
|
||||
# Use profile data if available
|
||||
recommended_max=$(get_max_children_recommendation "$domain" "$username")
|
||||
recommended_memory=$(get_memory_limit_recommendation "$domain" "$username")
|
||||
recommended_requests=$(get_max_requests_recommendation "$domain")
|
||||
else
|
||||
# Fallback to traffic-based (old method)
|
||||
local traffic_rpm
|
||||
traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0")
|
||||
[ "$traffic_rpm" = "?" ] && traffic_rpm="0"
|
||||
# Use intelligent three-constraint model
|
||||
# CRITICAL FIX: Pass ALL server domains, not just user's domains!
|
||||
traffic_pct=$(get_domain_traffic_percentage "$username" "$domain" "$all_domains_string" 2>/dev/null | cut -d'|' -f1)
|
||||
traffic_pct=${traffic_pct:-50}
|
||||
|
||||
recommended_max=$((traffic_rpm > 5 ? traffic_rpm + 10 : 5))
|
||||
recommended_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm")
|
||||
recommended_requests=$(calculate_optimal_max_requests "$traffic_rpm")
|
||||
# Call intelligent three-constraint function
|
||||
local intel_result
|
||||
intel_result=$(calculate_optimal_php_settings_intelligent "$username" "$total_ram_mb" "$server_capacity" "$traffic_pct" 2>/dev/null || echo "20|dynamic|1|5|ERROR|Failed")
|
||||
|
||||
recommended_max=$(echo "$intel_result" | cut -d'|' -f1)
|
||||
recommended_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$recommended_max" 2>/dev/null || echo "128M")
|
||||
recommended_requests=$(calculate_optimal_max_requests "$recommended_max" 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
recommended_max_children["$domain"]="$recommended_max"
|
||||
@@ -2520,7 +2802,8 @@ optimize_level_5_everything() {
|
||||
changes_count=$((changes_count + 1))
|
||||
fi
|
||||
|
||||
if ! is_opcache_enabled "$username"; then
|
||||
# Check per-domain ini files, not just per-user (fixes: all domains marked same if user has OPcache anywhere)
|
||||
if is_opcache_disabled_in_domain "$username" "$domain"; then
|
||||
opcache_needs_enable["$domain"]="1"
|
||||
opcache_count=$((opcache_count + 1))
|
||||
fi
|
||||
@@ -2531,6 +2814,52 @@ optimize_level_5_everything() {
|
||||
cecho " Domains needing OPcache: ${YELLOW}${opcache_count}${NC}"
|
||||
echo ""
|
||||
|
||||
# CRITICAL VALIDATION: Check if combined recommendations exceed safe limits
|
||||
cecho "${CYAN}STEP 2b: Validating Combined Capacity${NC}"
|
||||
echo ""
|
||||
|
||||
local total_recommended_max_children=0
|
||||
local avg_memory_per_process=$server_memory_per_process # Use actual measured memory per process
|
||||
local total_recommended_memory=0
|
||||
|
||||
for domain in "${!recommended_max_children[@]}"; do
|
||||
local rec_max="${recommended_max_children[$domain]}"
|
||||
[ -z "$rec_max" ] && continue
|
||||
total_recommended_max_children=$((total_recommended_max_children + rec_max))
|
||||
total_recommended_memory=$((total_recommended_memory + (rec_max * avg_memory_per_process)))
|
||||
done
|
||||
|
||||
# Determine if recommendations are safe
|
||||
# Reserve up to 256MB for OPcache (will be allocated separately)
|
||||
local max_opcache_reserved=256
|
||||
local max_safe_php_fpm=$((total_ram_mb * 60 / 100 - max_opcache_reserved)) # 60% of RAM minus OPcache reserve
|
||||
|
||||
if [ "$total_recommended_memory" -gt "$max_safe_php_fpm" ]; then
|
||||
cecho "${RED}${BOLD}⚠ WARNING: Combined recommendations exceed safe limits!${NC}"
|
||||
cecho "${RED}Recommended total: ${total_recommended_memory}MB (${total_recommended_max_children} max_children combined)${NC}"
|
||||
cecho "${RED}Safe maximum: ${max_safe_php_fpm}MB${NC}"
|
||||
cecho "${YELLOW}Applying safety caps to prevent OOM crashes...${NC}"
|
||||
|
||||
# Scale down all recommendations proportionally
|
||||
local scale_factor=$((max_safe_php_fpm * 100 / total_recommended_memory))
|
||||
scale_factor=$((scale_factor / 100)) # Convert to percentage
|
||||
|
||||
for domain in "${!recommended_max_children[@]}"; do
|
||||
local old_max="${recommended_max_children[$domain]}"
|
||||
[ -z "$old_max" ] && continue
|
||||
local new_max=$((old_max * scale_factor / 100))
|
||||
[ "$new_max" -lt 5 ] && new_max=5
|
||||
[ "$new_max" -gt 150 ] && new_max=150 # Hard cap for shared hosting
|
||||
recommended_max_children["$domain"]="$new_max"
|
||||
done
|
||||
|
||||
echo ""
|
||||
cecho "${GREEN}✓ Safety caps applied${NC}"
|
||||
else
|
||||
cecho "${GREEN}✓ Combined capacity is safe: ${total_recommended_memory}MB (${total_recommended_max_children} total max_children)${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
cecho "${CYAN}STEP 3: Applying All Optimizations${NC}"
|
||||
echo ""
|
||||
|
||||
@@ -2583,6 +2912,7 @@ optimize_level_5_everything() {
|
||||
cecho " ${GREEN}✓${NC} $domain: FPM settings optimized"
|
||||
cecho " pm.max_children: ${recommended_max} | pm.max_requests: ${recommended_requests}"
|
||||
optimized=$((optimized + 1))
|
||||
changes_log+=("$domain: pm.max_children=$recommended_max, pm.max_requests=$recommended_requests")
|
||||
|
||||
# Show profile data if available
|
||||
if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then
|
||||
@@ -2609,17 +2939,21 @@ optimize_level_5_everything() {
|
||||
|
||||
# Enable OPcache if needed
|
||||
if [ "${opcache_needs_enable[$domain]}" = "1" ]; then
|
||||
if enable_opcache "$ini_file" >/dev/null 2>&1; then
|
||||
if enable_opcache "$ini_file"; then
|
||||
local avg_rpm
|
||||
avg_rpm=$(calculate_avg_requests_per_minute "$username" 24 | cut -d'|' -f1)
|
||||
# Calculate available memory for OPcache (remaining from 60% allocation minus PHP-FPM needs)
|
||||
local available_for_opcache=$((total_ram_mb * 60 / 100 - total_recommended_memory))
|
||||
[ "$available_for_opcache" -lt 32 ] && available_for_opcache=32
|
||||
local optimal_opcache_mem
|
||||
optimal_opcache_mem=$(calculate_optimal_opcache_memory "$avg_rpm")
|
||||
|
||||
modify_php_ini_setting "$ini_file" "opcache.memory_consumption" "$optimal_opcache_mem" >/dev/null 2>&1
|
||||
optimal_opcache_mem=$(calculate_optimal_opcache_memory "$avg_rpm" "$available_for_opcache")
|
||||
|
||||
if modify_php_ini_setting "$ini_file" "opcache.memory_consumption" "$optimal_opcache_mem"; then
|
||||
if validate_php_ini "$ini_file" >/dev/null 2>&1; then
|
||||
cecho " ${GREEN}✓${NC} $domain: OPcache enabled (${optimal_opcache_mem})"
|
||||
opcache_enabled=$((opcache_enabled + 1))
|
||||
changes_log+=("$domain: OPcache enabled with $optimal_opcache_mem")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -2913,11 +3247,11 @@ check_config_issues() {
|
||||
local has_medium=false
|
||||
local has_low=false
|
||||
|
||||
# Check for each severity level
|
||||
echo "$issues" | grep -q "CRITICAL" && has_critical=true
|
||||
echo "$issues" | grep -q "HIGH" && has_high=true
|
||||
echo "$issues" | grep -q "MEDIUM" && has_medium=true
|
||||
echo "$issues" | grep -q "LOW" && has_low=true
|
||||
# Check for each severity level (add || true to handle no matches with set -o pipefail)
|
||||
echo "$issues" | grep -q "CRITICAL" && has_critical=true || true
|
||||
echo "$issues" | grep -q "HIGH" && has_high=true || true
|
||||
echo "$issues" | grep -q "MEDIUM" && has_medium=true || true
|
||||
echo "$issues" | grep -q "LOW" && has_low=true || true
|
||||
|
||||
# Display CRITICAL
|
||||
if [ "$has_critical" = true ]; then
|
||||
|
||||
+1232
-185
File diff suppressed because it is too large
Load Diff
+1301
-218
File diff suppressed because it is too large
Load Diff
@@ -826,11 +826,8 @@ main() {
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check if sysref database exists, build if needed
|
||||
if [ ! -f "$SYSREF_DB" ] || [ ! -s "$SYSREF_DB" ]; then
|
||||
print_status "Building system reference database (first run)..."
|
||||
build_reference_database >/dev/null 2>&1
|
||||
fi
|
||||
# Ensure reference database is fresh (only rebuild if > 1 hour old)
|
||||
db_ensure_fresh >/dev/null 2>&1
|
||||
|
||||
# Run analysis
|
||||
check_server_resources
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
#
|
||||
# Suspicious Login Monitor - Integrated Security Analysis & Compromise Detection
|
||||
@@ -11,6 +12,9 @@
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TOOLKIT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Source reference-db for cache support (avoid redundant /etc/passwd parsing)
|
||||
source "$TOOLKIT_ROOT/lib/reference-db.sh" 2>/dev/null || true
|
||||
|
||||
# Configuration
|
||||
SUSPICIOUS_LOGIN_AUTO_BLOCK="${SUSPICIOUS_LOGIN_AUTO_BLOCK:-yes}"
|
||||
SUSPICIOUS_LOGIN_AUTO_SCAN="${SUSPICIOUS_LOGIN_AUTO_SCAN:-yes}"
|
||||
@@ -1673,7 +1677,7 @@ check_maintenance_mode() {
|
||||
fi
|
||||
|
||||
if [ -n "$indicators" ]; then
|
||||
echo "maintenance-mode:$(echo $indicators | sed 's/ $//')"
|
||||
echo "maintenance-mode:$(sed 's/ $//' <<< "$indicators")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -1823,6 +1827,10 @@ check_recent_password_changes() {
|
||||
fi
|
||||
|
||||
# Check for locked accounts that were recently unlocked
|
||||
# OPTIMIZATION: Read /etc/passwd ONCE, build nologin list, then check against it
|
||||
# (avoiding redundant grep for each user in the loop)
|
||||
local nologin_users=$(awk -F: '/\/sbin\/nologin|\/bin\/false/ {print $1}' /etc/passwd 2>/dev/null | tr '\n' '|')
|
||||
|
||||
local recently_unlocked=$(awk -F: -v cutoff=$(( $(date +%s) / 86400 - 7 )) '
|
||||
# Field 2 starts with ! or !! = locked
|
||||
# If field 3 (last change) is recent and field 2 does NOT start with !, might have been unlocked
|
||||
@@ -1830,8 +1838,8 @@ check_recent_password_changes() {
|
||||
print $1
|
||||
}
|
||||
' /etc/shadow 2>/dev/null | while read user; do
|
||||
# Check if account was previously locked (this is imperfect without history)
|
||||
if grep "^$user:" /etc/passwd | grep -q "/sbin/nologin\|/bin/false"; then
|
||||
# Check if account has nologin shell (from pre-built list)
|
||||
if [[ "|$nologin_users" =~ \|$user\| ]]; then
|
||||
echo "$user"
|
||||
fi
|
||||
done)
|
||||
@@ -2947,6 +2955,11 @@ main() {
|
||||
echo -e "${CYAN}Starting Suspicious Login Monitor...${NC}"
|
||||
echo ""
|
||||
|
||||
# Ensure cache is fresh (only rebuilds if > 1 hour old)
|
||||
if command -v db_ensure_fresh &>/dev/null; then
|
||||
db_ensure_fresh 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Detect panel
|
||||
local panel=$(detect_panel)
|
||||
echo "Detected panel: $panel"
|
||||
|
||||
@@ -1977,18 +1977,18 @@ calculate_performance_score() {
|
||||
|
||||
# Calculate score (100 - issues)
|
||||
local score=$((100 - (critical_count * 10) - (warning_count * 2)))
|
||||
[ $score -lt 0 ] && score=0
|
||||
[ $score -gt 100 ] && score=100
|
||||
[ "$score" -lt 0 ] && score=0
|
||||
[ "$score" -gt 100 ] && score=100
|
||||
|
||||
# Determine grade
|
||||
local grade
|
||||
if [ $score -ge 90 ]; then
|
||||
if [ "$score" -ge 90 ]; then
|
||||
grade="A - EXCELLENT"
|
||||
elif [ $score -ge 80 ]; then
|
||||
elif [ "$score" -ge 80 ]; then
|
||||
grade="B - GOOD"
|
||||
elif [ $score -ge 70 ]; then
|
||||
elif [ "$score" -ge 70 ]; then
|
||||
grade="C - FAIR"
|
||||
elif [ $score -ge 60 ]; then
|
||||
elif [ "$score" -ge 60 ]; then
|
||||
grade="D - POOR"
|
||||
else
|
||||
grade="F - CRITICAL"
|
||||
|
||||
@@ -1,67 +1,108 @@
|
||||
#!/bin/bash
|
||||
|
||||
################################################################################
|
||||
# Wrapper script for Server Toolkit
|
||||
# Wrapper script for Server Toolkit (Beta)
|
||||
################################################################################
|
||||
# This wrapper allows proper history cleanup by running in the current shell
|
||||
# Safely runs toolkit with history isolation and reliable cleanup
|
||||
################################################################################
|
||||
|
||||
set -o pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLEANUP_FLAG="/tmp/.cleanup_requested"
|
||||
|
||||
# Fix HISTFILE if set to non-existent path (prevents crashes on sourcing)
|
||||
if [ -n "$HISTFILE" ]; then
|
||||
HISTFILE_DIR="$(dirname "$HISTFILE" 2>/dev/null)"
|
||||
if [ ! -d "$HISTFILE_DIR" ]; then
|
||||
# Fallback to default history location
|
||||
export HISTFILE="$HOME/.bash_history"
|
||||
# Save original history state to restore even if interrupted
|
||||
HISTORY_STATE="off"
|
||||
if set -o | grep -q "^set +o history" 2>/dev/null; then
|
||||
HISTORY_STATE="on"
|
||||
fi
|
||||
RESTORE_HISTORY=false
|
||||
|
||||
# Cleanup function: restore history even on error/interrupt
|
||||
cleanup_on_exit() {
|
||||
if [ "$RESTORE_HISTORY" = true ] && [ -n "$HISTORY_STATE" ]; then
|
||||
set +H # Disable history expansion temporarily
|
||||
if [ "$HISTORY_STATE" = "on" ]; then
|
||||
set -o history
|
||||
else
|
||||
set +o history
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if being sourced or executed
|
||||
# Register cleanup to run on exit, interrupt, or error
|
||||
trap cleanup_on_exit EXIT
|
||||
trap 'cleanup_on_exit; return 130' INT TERM
|
||||
|
||||
# Validate script can be sourced
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
echo "ERROR: This script must be sourced, not executed."
|
||||
echo ""
|
||||
echo "Run it like this:"
|
||||
echo " source $0"
|
||||
echo ""
|
||||
echo "Or use the alias:"
|
||||
echo " or"
|
||||
echo " . $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run the launcher (source in current shell, don't execute in subshell)
|
||||
source "$SCRIPT_DIR/launcher.sh" || {
|
||||
echo "ERROR: Failed to load launcher.sh"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if cleanup is requested
|
||||
if [ -f /tmp/.cleanup_requested ]; then
|
||||
rm -f /tmp/.cleanup_requested
|
||||
|
||||
# Clean history in current shell
|
||||
GREP_PATTERN="git\.mull\.lol|linux-server-management-toolkit|server-toolkit|launcher\.sh|erase-toolkit-traces|run\.sh"
|
||||
|
||||
if [ -f ~/.bash_history ]; then
|
||||
cp ~/.bash_history ~/.bash_history.bak.$$
|
||||
grep -Ev "$GREP_PATTERN" ~/.bash_history.bak.$$ > ~/.bash_history 2>/dev/null || true
|
||||
rm -f ~/.bash_history.bak.$$
|
||||
fi
|
||||
|
||||
# Clear current shell's history
|
||||
history -c
|
||||
history -r ~/.bash_history
|
||||
unset HISTFILE
|
||||
# Validate launcher exists
|
||||
if [ ! -f "$SCRIPT_DIR/launcher.sh" ]; then
|
||||
echo "ERROR: launcher.sh not found in $SCRIPT_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate and fix HISTFILE if needed (prevents crashes)
|
||||
if [ -n "$HISTFILE" ]; then
|
||||
HISTFILE_DIR="$(dirname "$HISTFILE" 2>/dev/null)"
|
||||
if [ ! -d "$HISTFILE_DIR" ] 2>/dev/null; then
|
||||
export HISTFILE="$HOME/.bash_history"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Disable history recording (toolkit runs invisibly)
|
||||
set +o history
|
||||
RESTORE_HISTORY=true
|
||||
|
||||
# Remove toolkit directory
|
||||
cd /root 2>/dev/null
|
||||
# Run the launcher in current shell
|
||||
source "$SCRIPT_DIR/launcher.sh"
|
||||
LAUNCHER_EXIT=$?
|
||||
|
||||
# Re-enable history (trap will also do this)
|
||||
if [ "$HISTORY_STATE" = "on" ]; then
|
||||
set -o history 2>/dev/null || true
|
||||
else
|
||||
set +o history 2>/dev/null || true
|
||||
fi
|
||||
RESTORE_HISTORY=false
|
||||
|
||||
# Handle cleanup request (if user selected "Clean and remove traces")
|
||||
if [ -f "$CLEANUP_FLAG" ]; then
|
||||
rm -f "$CLEANUP_FLAG"
|
||||
|
||||
# Attempt cleanup in subshell (safe, isolated)
|
||||
# Wait a moment for file descriptors to close
|
||||
sleep 0.5
|
||||
|
||||
if (
|
||||
cd /root 2>/dev/null || exit 1
|
||||
rm -rf "$SCRIPT_DIR" 2>/dev/null
|
||||
|
||||
) 2>/dev/null; then
|
||||
# Cleanup succeeded - return cleanly
|
||||
clear
|
||||
echo ""
|
||||
echo "✓ All traces removed"
|
||||
echo "✓ Toolkit removed successfully"
|
||||
echo ""
|
||||
echo "Type 'exit' and start a new shell."
|
||||
sleep 0.5 # Brief delay before returning to let system release resources
|
||||
return 0 # Return success (not $LAUNCHER_EXIT) after cleanup
|
||||
else
|
||||
# Cleanup failed - inform user but still return cleanly
|
||||
clear
|
||||
echo ""
|
||||
echo "⚠ Toolkit removal incomplete (may need manual cleanup)"
|
||||
echo " Command: rm -rf '$SCRIPT_DIR'"
|
||||
echo ""
|
||||
return 0 # Return success to avoid shell confusion
|
||||
fi
|
||||
fi
|
||||
|
||||
# Normal exit (no cleanup) - return launcher's exit status
|
||||
return $LAUNCHER_EXIT
|
||||
|
||||
Reference in New Issue
Block a user