ARCHITECTURE: Multi-Control-Panel Design Standards
Created comprehensive architecture and quick reference documentation. NEW DOCUMENTS: 1. MULTI_CONTROL_PANEL_ARCHITECTURE.md (6500+ words) Defines MANDATORY patterns for all future development: - Core principles (never hardcode, use abstractions, conditionals) - Standard library usage (system-detect.sh, user-manager.sh) - Path mapping reference (all panels) - Standard code patterns (log discovery, docroot, domain→user) - Module classification (A/B/C/D) - Testing requirements - Code review checklist - Migration guide - Common mistakes to avoid Every developer must follow these patterns! 2. CONTROL_PANEL_QUICK_REFERENCE.md (8000+ words) Fast lookup while coding: - Panel detection methods - Complete file system path mappings - Configuration file locations - CLI tools & API commands - Database prefix patterns (CRITICAL for InterWorx!) - PHP configuration per panel - Email, FTP, security features - WordPress detection patterns - Process ownership - Code snippets for common tasks - Panel-specific quirks/gotchas - Migration implications Covers: cPanel, Plesk, InterWorx, Standalone PURPOSE: These documents establish a STANDARD ARCHITECTURE before completing InterWorx support. All modules will be refactored to follow these patterns, making it trivial to add DirectAdmin, CyberPanel, etc. KEY PATTERNS ESTABLISHED: - Never hardcode paths → use SYS_LOG_DIR, get_user_info() - Wrap API calls → check SYS_CONTROL_PANEL first - Design for extension → case statements for panels - Test on all platforms → cPanel regression required MODULE CLASSIFICATION: - Class A: Panel agnostic (no special handling) - Class B: Needs system detection (SYS_LOG_DIR) - Class C: Needs user/domain management (get_user_info) - Class D: Panel-specific (document limitations) CRITICAL GOTCHAS DOCUMENTED: - InterWorx database prefix uses DOMAIN not USERNAME! - Plesk has no shared hosting (domain-centric) - cPanel addon domains share public_html - InterWorx logs are per-domain in user home NEXT STEPS: 1. Update existing modules to follow patterns 2. Complete InterWorx support systematically 3. Expand Plesk support 4. Add DirectAdmin/CyberPanel This is the foundation for true multi-panel architecture! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,537 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user