# Multi-Control-Panel Architecture **Version:** 1.0 **Date:** 2025-11-19 **Status:** ACTIVE STANDARD ## Executive Summary This document defines the **standard architecture** for building control panel-agnostic tools in the Server Management Toolkit. All new code and refactored modules MUST follow these patterns to ensure compatibility with cPanel, Plesk, InterWorx, and future control panels. --- ## Supported Control Panels ### Current Support Levels: - **cPanel** - βœ… Full support (primary platform) - **Plesk** - ⚠️ Partial support (needs expansion) - **InterWorx** - 🚧 In progress (Phases 1-3 complete) - **Standalone** - βœ… Basic support (no control panel) ### Future Targets: - DirectAdmin - CyberPanel - Webmin/Virtualmin --- ## Core Principles ### 1. **Never Hardcode Paths** ```bash # ❌ BAD - Hardcoded cPanel path LOG_DIR="/var/log/apache2/domlogs" docroot="/home/$user/public_html" # βœ… GOOD - Use system detection LOG_DIR="${SYS_LOG_DIR}" # From system-detect.sh eval $(get_user_info "$username") # Returns $HOME_DIR, $PRIMARY_DOMAIN docroot="${HOME_DIR}/${PRIMARY_DOMAIN}/html" # Constructed dynamically ``` ### 2. **Use Abstraction Libraries** ```bash # ❌ BAD - Direct control panel-specific code user=$(grep -l "DNS.*$domain" /var/cpanel/users/* 2>/dev/null | ...) # βœ… GOOD - Use user-manager.sh abstraction eval $(get_user_info "$username") # Now you have: $USER_EXISTS, $USERNAME, $PRIMARY_DOMAIN, $ALL_DOMAINS, etc. ``` ### 3. **Conditional Panel-Specific Features** ```bash # ❌ BAD - Assumes cPanel exists whmapi1 cphulkd_list_blocks # βœ… GOOD - Conditional with graceful fallback if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then whmapi1 cphulkd_list_blocks else echo "CPHulk is cPanel-specific, skipping..." fi ``` ### 4. **Design for Extension** ```bash # βœ… GOOD - Easy to add new panels case "$SYS_CONTROL_PANEL" in cpanel) # cPanel-specific logic ;; plesk) # Plesk-specific logic ;; interworx) # InterWorx-specific logic ;; directadmin) # Future: DirectAdmin logic ;; *) echo "Unsupported control panel: $SYS_CONTROL_PANEL" return 1 ;; esac ``` --- ## Standard Library Usage ### system-detect.sh **Purpose:** Runtime detection of control panel, OS, paths, and system resources **Exports:** - `SYS_CONTROL_PANEL` - Control panel type (cpanel, plesk, interworx, none) - `SYS_LOG_DIR` - Apache/web server log directory - `SYS_USER_HOME_BASE` - Base directory for user homes - `SYS_WEB_SERVER` - Web server type (apache, nginx, litespeed) - `SYS_FIREWALL` - Firewall type (csf, firewalld, iptables, ufw) - `SYS_CSF_ACTIVE` - CSF availability flag **Usage:** ```bash # Automatically sourced when loading common-functions.sh source "$SCRIPT_DIR/lib/common-functions.sh" source "$SCRIPT_DIR/lib/system-detect.sh" # Variables are now available if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then # cPanel-specific code fi ``` ### user-manager.sh **Purpose:** Control panel-agnostic user/domain/database management **Functions:** #### get_user_info() ```bash eval $(get_user_info "username") # Returns (via eval): # - USER_EXISTS=yes/no # - USERNAME=username # - PRIMARY_DOMAIN=example.com # - ALL_DOMAINS=space-separated list # - EMAIL=user@example.com # - HOME_DIR=/home/username # - DISK_USED=1024M ``` #### get_user_domains() ```bash domains=$(get_user_domains "username") # Returns newline-separated list of domains ``` #### get_user_databases() ```bash databases=$(get_user_databases "username") # Returns newline-separated list of databases # Note: InterWorx uses domain prefix, not username! ``` **Critical Note:** Database prefixes differ! - cPanel: `username_dbname` - InterWorx: `first8charsOfDomain_dbname` - Plesk: `dbname` (user-scoped) --- ## Path Mapping Reference | Resource | cPanel | Plesk | InterWorx | Standalone | |----------|--------|-------|-----------|------------| | **User Home** | `/home/user` | `/var/www/vhosts/domain` | `/home/user` | `/home/user` | | **Document Root** | `/home/user/public_html` | `/var/www/vhosts/domain/httpdocs` | `/home/user/domain.com/html` | `/var/www/html` | | **Access Logs** | `/var/log/apache2/domlogs/domain` | `/var/www/vhosts/system/domain/logs` | `/home/user/var/domain/logs/access_log` | `/var/log/httpd/access_log` | | **Error Logs** | `/var/log/apache2/domlogs/domain-error_log` | `/var/www/vhosts/system/domain/logs/error_log` | `/home/user/var/domain/logs/error_log` | `/var/log/httpd/error_log` | | **PHP Error Logs** | `/home/user/public_html/error_log` | `/var/www/vhosts/domain/httpdocs/error_log` | `/home/user/domain/html/error_log` | N/A | | **User Config** | `/var/cpanel/users/user` | Plesk DB | listaccounts.pex or vhost configs | N/A | | **Domain Config** | `/var/cpanel/userdata/user/domain` | Plesk DB | `/etc/httpd/conf.d/vhost_domain.conf` | N/A | | **Mail Dir** | `/home/user/mail` | `/var/qmail/mailnames/domain` | `/home/user/var/domain/mail` | `/var/mail` | | **Cron Jobs** | `/var/spool/cron/user` | `/var/spool/cron/user` | `/var/spool/cron/user` | `/var/spool/cron/user` | | **SSL Certs** | `/etc/letsencrypt/live/domain` | `/etc/letsencrypt/live/domain` | `/etc/letsencrypt/live/domain` | `/etc/letsencrypt/live/domain` | | **Database Prefix** | `username_` | No prefix | `first8chars_` | N/A | --- ## Standard Patterns ### Pattern 1: Log File Discovery ```bash discover_logs() { local log_files=() if [ "$SYS_CONTROL_PANEL" = "interworx" ]; then # InterWorx: Per-domain logs in user home while IFS= read -r logfile; do [ -f "$logfile" ] && log_files+=("$logfile") done < <(find /home/*/var/*/logs -type f -name "access_log" 2>/dev/null) elif [ "$SYS_CONTROL_PANEL" = "plesk" ]; then # Plesk: Centralized per-domain logs while IFS= read -r logfile; do [ -f "$logfile" ] && log_files+=("$logfile") done < <(find /var/www/vhosts/system/*/logs -type f -name "access_log" 2>/dev/null) elif [ "$SYS_CONTROL_PANEL" = "cpanel" ] && [ -n "$SYS_LOG_DIR" ]; then # cPanel: Centralized domlogs while IFS= read -r logfile; do [ -f "$logfile" ] && log_files+=("$logfile") done < <(find "$SYS_LOG_DIR" -type f ! -name "*-bytes_log" ! -name "*error_log" 2>/dev/null) else # Standalone: Main access log only [ -f "/var/log/httpd/access_log" ] && log_files+=("/var/log/httpd/access_log") [ -f "/var/log/apache2/access.log" ] && log_files+=("/var/log/apache2/access.log") fi echo "${log_files[@]}" } ``` ### Pattern 2: Document Root Discovery ```bash get_docroot() { local domain="$1" local username="$2" # Optional, helps with InterWorx case "$SYS_CONTROL_PANEL" in cpanel) # cPanel: public_html echo "/home/${username}/public_html" ;; plesk) # Plesk: httpdocs echo "/var/www/vhosts/${domain}/httpdocs" ;; interworx) # InterWorx: domain.com/html echo "/home/${username}/${domain}/html" ;; *) # Standalone: Assume standard echo "/var/www/html" ;; esac } ``` ### Pattern 3: Domainβ†’User Mapping ```bash get_user_for_domain() { local domain="$1" case "$SYS_CONTROL_PANEL" in cpanel) # cPanel: /etc/userdatadomains grep "^${domain}:" /etc/userdatadomains 2>/dev/null | cut -d: -f2 | awk -F'==' '{print $1}' ;; plesk) # Plesk: Use CLI plesk bin subscription --info "$domain" 2>/dev/null | grep "Owner" | awk '{print $2}' ;; interworx) # InterWorx: Parse vhost configs grep -l "ServerName ${domain}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | head -1 | \ xargs grep "SuexecUserGroup" 2>/dev/null | awk '{print $2}' ;; *) echo "Unsupported" return 1 ;; esac } ``` ### Pattern 4: Control Panel API Calls ```bash # Always wrap API calls in control panel checks ban_ip_via_panel() { local ip="$1" case "$SYS_CONTROL_PANEL" in cpanel) if command -v whmapi1 &>/dev/null; then whmapi1 cphulkd_blacklist ip="$ip" else echo "cPanel WHM API not available" return 1 fi ;; plesk) if command -v plesk &>/dev/null; then plesk bin ip --ban "$ip" else echo "Plesk CLI not available" return 1 fi ;; interworx) # InterWorx has no built-in IP ban API # Fall back to firewall echo "InterWorx has no native IP ban, using firewall" return 2 ;; *) echo "No control panel API available" return 1 ;; esac } ``` --- ## Testing Requirements ### Every Module Must Be Tested On: 1. βœ… **cPanel** - Regression testing (ensure no breakage) 2. βœ… **Standalone** - No control panel environment 3. ⚠️ **Plesk** - If Plesk support claimed 4. ⚠️ **InterWorx** - If InterWorx support claimed ### Test Checklist: - [ ] Module loads without errors on all platforms - [ ] Paths are correctly detected - [ ] User/domain functions work correctly - [ ] Graceful degradation when panel-specific features unavailable - [ ] Error messages are helpful and mention control panel - [ ] No hardcoded paths remain - [ ] API calls are wrapped in panel checks --- ## Code Review Checklist Before committing any module changes, verify: - [ ] No hardcoded paths (`/var/cpanel`, `/usr/local/cpanel`, `/var/log/apache2/domlogs`, `public_html`) - [ ] Uses `SYS_LOG_DIR` from system-detect.sh - [ ] Uses `get_user_info()` / `get_user_domains()` from user-manager.sh - [ ] API calls wrapped in `if [ "$SYS_CONTROL_PANEL" = "cpanel" ]` - [ ] Document root constructed dynamically - [ ] Error messages include control panel info for debugging - [ ] Tested with `bash -n script.sh` (syntax check) - [ ] Added to INTERWORX_COMPATIBILITY_AUDIT.md if applicable --- ## Module Classification ### Class A: Control Panel Agnostic **Can work on any system** - hardware-health-check.sh - tail-secure-log.sh - tail-mail-log.sh - firewall-activity-monitor.sh - ssh-attack-monitor.sh **Requirements:** None special ### Class B: Requires System Detection **Needs paths but no user/domain management** - network-bandwidth-analyzer.sh - tail-apache-access.sh - tail-apache-error.sh - web-traffic-monitor.sh **Requirements:** - Source system-detect.sh - Use `SYS_LOG_DIR` ### Class C: Requires User/Domain Management **Needs to map users/domains** - bot-analyzer.sh - website-error-analyzer.sh - 500-error-tracker.sh - malware-scanner.sh - wordpress-cron-manager.sh **Requirements:** - Source system-detect.sh - Source user-manager.sh - Use `get_user_info()` / `get_user_domains()` ### Class D: Control Panel-Specific **Only works on specific panels** - enable-cphulk.sh (cPanel only) - acronis-* (cPanel specific) **Requirements:** - Check `SYS_CONTROL_PANEL` at startup - Exit gracefully if wrong panel - Document panel requirement in help text --- ## Migration Guide ### Converting Existing Module to Multi-Panel: 1. **Add Library Imports** ```bash # At top of script, after shebang 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/user-manager.sh" # If Class C ``` 2. **Replace Hardcoded Paths** ```bash # Find all hardcoded paths grep -n "/var/cpanel\|/usr/local/cpanel\|public_html\|domlogs" yourscript.sh # Replace with system variables or functions ``` 3. **Wrap API Calls** ```bash # Find all API calls grep -n "whmapi\|uapi\|cpapi" yourscript.sh # Wrap in conditional checks ``` 4. **Add Control Panel Cases** ```bash # For panel-specific logic, use case statements case "$SYS_CONTROL_PANEL" in cpanel) ... ;; plesk) ... ;; interworx) ... ;; *) ... ;; esac ``` 5. **Test Syntax** ```bash bash -n yourscript.sh ``` 6. **Update Documentation** - Add to INTERWORX_COMPATIBILITY_AUDIT.md - Note supported panels in script header - Update help text --- ## Common Mistakes to Avoid ### ❌ Mistake #1: Using Command Existence as Panel Detection ```bash # BAD - Command might exist on other panels if command -v whmapi1 &>/dev/null; then # Assume cPanel fi ``` **Fix:** Always check `SYS_CONTROL_PANEL` ```bash # GOOD if [ "$SYS_CONTROL_PANEL" = "cpanel" ] && command -v whmapi1 &>/dev/null; then # Definitely cPanel and API available fi ``` ### ❌ Mistake #2: Assuming File Locations ```bash # BAD - Only works on cPanel domains=$(grep "^DNS" /var/cpanel/users/$user | awk '{print $2}') ``` **Fix:** Use abstraction ```bash # GOOD domains=$(get_user_domains "$user") ``` ### ❌ Mistake #3: Hardcoding Document Root Structure ```bash # BAD - Assumes cPanel's public_html find /home/*/public_html -name "wp-config.php" ``` **Fix:** Use dynamic paths ```bash # GOOD case "$SYS_CONTROL_PANEL" in cpanel) find /home/*/public_html -name "wp-config.php" ;; interworx) find /home/*/*/html -name "wp-config.php" ;; plesk) find /var/www/vhosts/*/httpdocs -name "wp-config.php" ;; esac ``` ### ❌ Mistake #4: Silent Failures ```bash # BAD - Fails silently on non-cPanel whmapi1 some_command ``` **Fix:** Check and report ```bash # GOOD if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then whmapi1 some_command else print_warning "This feature requires cPanel (current: $SYS_CONTROL_PANEL)" return 1 fi ``` --- ## Future Enhancements ### Planned Improvements: 1. **Panel-specific feature matrix** - Document what features work on which panels 2. **Automated testing framework** - Docker containers for each panel 3. **Panel capability detection** - Runtime feature detection 4. **Plugin architecture** - Easy addition of new panel support ### Control Panel Support Roadmap: - **Phase 1-3:** InterWorx (IN PROGRESS) - **Phase 4:** Plesk expansion (fill gaps) - **Phase 5:** DirectAdmin - **Phase 6:** CyberPanel --- ## Questions and Support **Q: What if a feature is impossible on a specific panel?** A: Document it, detect it, and fail gracefully with a clear message. **Q: Should I support all panels in every module?** A: No. Class D modules can be panel-specific. Just document it. **Q: What about backward compatibility?** A: Always maintain cPanel compatibility. It's the primary platform. **Q: How do I test without access to all panels?** A: At minimum: syntax check + cPanel regression. InterWorx/Plesk testing is nice-to-have. --- ## Version History - **1.0** (2025-11-19) - Initial standard established - Defined core principles - Created pattern library - Established testing requirements - Documented common mistakes