598 lines
25 KiB
Bash
598 lines
25 KiB
Bash
#!/bin/bash
|
|
# ==============================================================================
|
|
# Script Name: xmlchecker.sh
|
|
# Description: Validates and attempts to fix structural errors in LiteSpeed's
|
|
# httpd_config.xml file. Includes robust fallback mechanisms,
|
|
# semantic validation, and service restart verification.
|
|
# Version: 1.8.0 (Enterprise-Grade Optimized)
|
|
# Author: Anthony Garces (tony@mightybox.io)
|
|
# Date: 2025-03-26
|
|
# Exit Codes:
|
|
# 0: Success (original valid or minor fixes applied, or default/backup applied successfully)
|
|
# 1: General Error / Fallback to Backup completed (review recommended) / Restart failed
|
|
# 2: Success, but MINIMAL fallback config applied (CRITICAL REVIEW NEEDED)
|
|
# 3: Success, but RECOVERED config with MAJOR changes applied (CRITICAL REVIEW NEEDED)
|
|
# ==============================================================================
|
|
set -euo pipefail
|
|
|
|
# === Configuration ===
|
|
CONF_FILE="/var/www/conf/httpd_config.xml"
|
|
DEFAULT_CONF="/var/www/conf/httpd_config.default.xml" # Official default file path
|
|
SERVER_ROOT="/var/www" # Server root directory
|
|
LOG_DIR="/var/log/mb-ssl" # Log directory
|
|
MAJOR_CHANGE_THRESHOLD=20 # Lines changed to trigger "major recovery" warning
|
|
|
|
# Log files
|
|
SCRIPT_LOG="${LOG_DIR}/xmlchecker.log"
|
|
ERROR_LOG="${LOG_DIR}/xmlchecker-error.log"
|
|
DEBUG_LOG="${LOG_DIR}/xmlchecker-debug.log"
|
|
BACKUP_FILE="${LOG_DIR}/httpd_config_$(date +%Y%m%d_%H%M%S).bak" # Timestamped backup
|
|
|
|
# SSL Remover script path
|
|
SSL_REMOVER="/home/litespeed/mbmanager/ssl-manager/ssl_remover.sh"
|
|
|
|
# - Internal Variables -
|
|
TMP_FILE="/tmp/test_config_$(date +%s).xml" # Temporary copy of original/cleaned file
|
|
RECOVERY_TMP_FILE="/tmp/recovery_test_config_$(date +%s).xml" # Holds output of xmllint --recover
|
|
MINIMAL_TMP="/tmp/minimal_config_$(date +%s).xml" # Temporary minimal config file
|
|
VERBOSE=0
|
|
INITIAL_FILE_VALID=0 # Flag: 1 if CONF_FILE was valid before fixes
|
|
RECOVERY_PRODUCED_VALID_XML=0 # Flag: 1 if xmllint --recover output was valid XML
|
|
RECOVERY_WAS_MAJOR=0 # Flag: 1 if recovery resulted in significant changes
|
|
APPLIED_CONFIG_SOURCE="none" # Tracks what was finally applied
|
|
|
|
# === Cleanup Trap ===
|
|
trap 'log "Script interrupted. Cleaning up temporary files..."; sudo rm -f "$TMP_FILE" "$RECOVERY_TMP_FILE" "$MINIMAL_TMP"' EXIT
|
|
|
|
# === Functions ===
|
|
setup_logging() {
|
|
# Create log directory if it doesn't exist
|
|
sudo mkdir -p "$LOG_DIR" || { echo "❌ ERROR: Cannot create log directory '$LOG_DIR'. Check permissions."; exit 1; }
|
|
|
|
# Set proper permissions
|
|
sudo chown -R "$(whoami)":"$(id -gn)" "$LOG_DIR"
|
|
sudo chmod 755 "$LOG_DIR"
|
|
|
|
# Create log files with proper permissions
|
|
touch "$SCRIPT_LOG" "$ERROR_LOG" "$DEBUG_LOG"
|
|
chmod 644 "$SCRIPT_LOG" "$ERROR_LOG" "$DEBUG_LOG"
|
|
|
|
# Add log rotation if log files are too large (>10MB)
|
|
for log_file in "$SCRIPT_LOG" "$ERROR_LOG" "$DEBUG_LOG"; do
|
|
if [ -f "$log_file" ] && [ "$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file")" -gt 10485760 ]; then
|
|
mv "$log_file" "${log_file}.$(date +%Y%m%d)"
|
|
touch "$log_file"
|
|
chmod 644 "$log_file"
|
|
gzip "${log_file}.$(date +%Y%m%d)"
|
|
fi
|
|
done
|
|
}
|
|
|
|
log() {
|
|
local level="INFO"
|
|
local message="$1"
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
# Log to main log file
|
|
echo "[$timestamp] [$level] $message" | sudo tee -a "$SCRIPT_LOG"
|
|
|
|
# Log errors to error log file
|
|
if [[ "$message" == *"ERROR"* ]] || [[ "$message" == *"❌"* ]]; then
|
|
echo "[$timestamp] [$level] $message" | sudo tee -a "$ERROR_LOG"
|
|
fi
|
|
}
|
|
|
|
log_verbose() {
|
|
if [[ "$VERBOSE" -eq 1 ]]; then
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
echo "[$timestamp] [DEBUG] $1" | sudo tee -a "$DEBUG_LOG"
|
|
fi
|
|
}
|
|
|
|
log_error() {
|
|
local message="$1"
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
echo "[$timestamp] [ERROR] $message" | sudo tee -a "$ERROR_LOG" "$SCRIPT_LOG"
|
|
}
|
|
|
|
log_success() {
|
|
local message="$1"
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
echo "[$timestamp] [SUCCESS] $message" | sudo tee -a "$SCRIPT_LOG"
|
|
}
|
|
|
|
check_command() {
|
|
local cmd="$1"
|
|
local pkg="$2"
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
log "⚠ Required command '$cmd' not found. Attempting to install package '$pkg'..."
|
|
|
|
# Try dnf first (AlmaLinux)
|
|
if command -v dnf &>/dev/null; then
|
|
if sudo dnf install -y "$pkg"; then
|
|
log "✔ Successfully installed '$pkg' using dnf."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Fallback to yum (CentOS)
|
|
if command -v yum &>/dev/null; then
|
|
if sudo yum install -y "$pkg"; then
|
|
log "✔ Successfully installed '$pkg' using yum."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
log "❌ ERROR: Failed to install '$pkg' using either dnf or yum. Exiting."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_listener_certificates() {
|
|
local file_to_check="$1"
|
|
log "Validating listener certificates in '$file_to_check'..."
|
|
local invalid_listeners=()
|
|
|
|
# Get all HTTPS listeners
|
|
local listeners
|
|
listeners=$(sudo xmlstarlet sel -Q -t -v "//listenerList/listener[contains(name, 'HTTPS-')]/name" "$file_to_check" 2>/dev/null || echo "")
|
|
|
|
for listener in $listeners; do
|
|
local domain="${listener#HTTPS-}"
|
|
local key_file=$(sudo xmlstarlet sel -Q -t -v "//listenerList/listener[name='$listener']/keyFile" "$file_to_check" 2>/dev/null || echo "")
|
|
local cert_file=$(sudo xmlstarlet sel -Q -t -v "//listenerList/listener[name='$listener']/certFile" "$file_to_check" 2>/dev/null || echo "")
|
|
|
|
# Check if certificate files exist
|
|
if [[ ! -f "$key_file" ]] || [[ ! -f "$cert_file" ]]; then
|
|
log "⚠ Found invalid listener '$listener' with missing certificate files"
|
|
log_verbose "Key file missing: $key_file"
|
|
log_verbose "Cert file missing: $cert_file"
|
|
invalid_listeners+=("$domain")
|
|
fi
|
|
|
|
# Check if certificate is still valid
|
|
if [[ -f "$cert_file" ]]; then
|
|
local cert_expiry
|
|
cert_expiry=$(openssl x509 -enddate -noout -in "$cert_file" | cut -d= -f2)
|
|
local expiry_date
|
|
expiry_date=$(date -d "$cert_expiry" +%s)
|
|
local current_date
|
|
current_date=$(date +%s)
|
|
|
|
if [[ $expiry_date -lt $current_date ]]; then
|
|
log "⚠ Found expired certificate for listener '$listener'"
|
|
log_verbose "Certificate expired on: $cert_expiry"
|
|
invalid_listeners+=("$domain")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# If we found invalid listeners, clean them up
|
|
if [[ ${#invalid_listeners[@]} -gt 0 ]]; then
|
|
log "Found ${#invalid_listeners[@]} invalid listener(s). Cleaning up..."
|
|
for domain in "${invalid_listeners[@]}"; do
|
|
log "Cleaning up invalid listener for domain: $domain"
|
|
if [[ -f "$SSL_REMOVER" ]]; then
|
|
log "Running SSL remover for domain: $domain"
|
|
sudo bash "$SSL_REMOVER" --domains="$domain" --verbose || {
|
|
log_error "Failed to clean up invalid listener for domain: $domain"
|
|
}
|
|
else
|
|
log_error "SSL remover script not found at: $SSL_REMOVER"
|
|
# Fallback to manual cleanup
|
|
sudo xmlstarlet ed -L \
|
|
-d "//listenerList/listener[name='HTTPS-$domain']" \
|
|
"$file_to_check" || {
|
|
log_error "Failed to remove invalid listener for domain: $domain"
|
|
}
|
|
fi
|
|
done
|
|
fi
|
|
|
|
return ${#invalid_listeners[@]}
|
|
}
|
|
|
|
perform_semantic_checks() {
|
|
local file_to_check="$1"
|
|
log_verbose "Performing semantic checks on '$file_to_check'..."
|
|
local semantic_errors=0
|
|
|
|
# Check for critical elements
|
|
if ! sudo xmlstarlet sel -t -c "//virtualHostList" "$file_to_check" &>/dev/null; then
|
|
log_error "Missing <virtualHostList> in '$file_to_check'"
|
|
semantic_errors=$((semantic_errors + 1))
|
|
fi
|
|
if ! sudo xmlstarlet sel -t -c "//listenerList" "$file_to_check" &>/dev/null; then
|
|
log_error "Missing <listenerList> in '$file_to_check'"
|
|
semantic_errors=$((semantic_errors + 1))
|
|
fi
|
|
|
|
# Validate listener certificates
|
|
if ! validate_listener_certificates "$file_to_check"; then
|
|
semantic_errors=$((semantic_errors + 1))
|
|
fi
|
|
|
|
# Return result
|
|
if [[ "$semantic_errors" -eq 0 ]]; then
|
|
log_success "Semantic checks passed for '$file_to_check'"
|
|
return 0
|
|
else
|
|
log_error "Found $semantic_errors semantic issue(s) in '$file_to_check'"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
clean_invalid_entries() {
|
|
local file_to_clean="$1"
|
|
log "Attempting to clean invalid entries from '$file_to_clean'..."
|
|
|
|
# Remove empty or redundant nodes
|
|
sudo xmlstarlet ed -L -d '//virtualHost[not(node()[not(self::comment() or (self::text() and normalize-space()=""))])]' "$file_to_clean" || log "Warning: Failed to clean empty virtualHost nodes."
|
|
sudo xmlstarlet ed -L -d '//listener[not(node()[not(self::comment() or (self::text() and normalize-space()=""))])]' "$file_to_clean" || log "Warning: Failed to clean empty listener nodes."
|
|
sudo xmlstarlet ed -L -d '//virtualHostList[not(node()[not(self::comment() or (self::text() and normalize-space()=""))])]' "$file_to_clean" || log "Warning: Failed to clean empty virtualHostList."
|
|
sudo xmlstarlet ed -L -d '//listenerList[not(node()[not(self::comment() or (self::text() and normalize-space()=""))])]' "$file_to_clean" || log "Warning: Failed to clean empty listenerList."
|
|
|
|
log "Cleanup attempts completed for '$file_to_clean'."
|
|
}
|
|
|
|
restart_litespeed() {
|
|
log "Attempting to restart LiteSpeed server..."
|
|
if sudo systemctl restart lsws; then
|
|
log "Info: Restart command issued for LiteSpeed (lsws). Waiting 3 seconds to verify status..."
|
|
sleep 3
|
|
if sudo systemctl is-active --quiet lsws; then
|
|
log "✔ LiteSpeed (lsws) is active after restart."
|
|
return 0
|
|
else
|
|
log "❌ FATAL: LiteSpeed (lsws) failed to become active after restart command."
|
|
log " Check service status: 'sudo systemctl status lsws'"
|
|
log " Check LiteSpeed error log: '${SERVER_ROOT:-/var/www}/logs/error.log'"
|
|
log " Consider manually reverting to backup: sudo cp '$BACKUP_FILE' '$CONF_FILE' && sudo systemctl restart lsws"
|
|
return 1
|
|
fi
|
|
else
|
|
log "❌ FATAL: Failed to execute restart command for LiteSpeed (lsws)."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
apply_and_restart() {
|
|
local source_file="$1"
|
|
local source_desc="$2" # e.g., "original", "recovered", "default", "backup", "minimal"
|
|
|
|
APPLIED_CONFIG_SOURCE="$source_desc" # Update global tracker
|
|
|
|
log "Applying changes from $source_desc file ('$source_file') to '$CONF_FILE'..."
|
|
|
|
# Check if source and destination are the same file
|
|
if [[ "$source_file" == "$CONF_FILE" ]]; then
|
|
log "Info: Source file ('$source_file') and destination file ('$CONF_FILE') are the same. Skipping copy."
|
|
else
|
|
log "Preview of changes (diff with backup '$BACKUP_FILE'):"
|
|
if ! sudo diff -u "$BACKUP_FILE" "$source_file" | sudo tee -a "$SCRIPT_LOG"; then
|
|
log "Info: No differences found or diff command failed."
|
|
fi
|
|
|
|
# Apply the configuration using sudo cp
|
|
if sudo cp -a "$source_file" "$CONF_FILE"; then
|
|
log "✔ Configuration updated successfully from $source_desc file."
|
|
else
|
|
log "❌ FATAL: Failed to copy $source_desc file '$source_file' to '$CONF_FILE'. Configuration NOT updated."
|
|
return 1 # Signal failure
|
|
fi
|
|
fi
|
|
|
|
# Restart LiteSpeed using the hardcoded command
|
|
log "Attempting to restart LiteSpeed server..."
|
|
if sudo systemctl restart lsws; then
|
|
log "Info: Restart command issued for LiteSpeed (lsws). Waiting 3 seconds to verify status..."
|
|
sleep 3
|
|
if sudo systemctl is-active --quiet lsws; then
|
|
log "✔ LiteSpeed (lsws) is active after restart."
|
|
return 0 # Success
|
|
else
|
|
log "❌ FATAL: LiteSpeed (lsws) failed to become active after restart command."
|
|
log " Check service status: 'sudo systemctl status lsws'"
|
|
log " Check LiteSpeed error log: '${SERVER_ROOT:-/var/www}/logs/error.log'"
|
|
log " Consider manually reverting to backup: sudo cp '$BACKUP_FILE' '$CONF_FILE' && sudo systemctl restart lsws"
|
|
return 1 # Failure
|
|
fi
|
|
else
|
|
log "❌ FATAL: Failed to execute restart command for LiteSpeed (lsws)."
|
|
return 1 # Failure
|
|
fi
|
|
}
|
|
|
|
validate_xml_structure() {
|
|
local file_to_check="$1"
|
|
log_verbose "Performing comprehensive XML validation on '$file_to_check'..."
|
|
local validation_errors=0
|
|
|
|
# 1. Check if file is well-formed with detailed error reporting
|
|
if ! xmlstarlet val --err --well-formed "$file_to_check" 2> >(while IFS= read -r line; do
|
|
log_error "XML Validation Error: $line"
|
|
validation_errors=$((validation_errors + 1))
|
|
done); then
|
|
log_error "File '$file_to_check' is not well-formed XML"
|
|
validation_errors=$((validation_errors + 1))
|
|
fi
|
|
|
|
# 2. Check for required root elements
|
|
if ! xmlstarlet sel -Q -t -c "//httpServerConfig" "$file_to_check" 2>/dev/null; then
|
|
log_error "Missing required root element <httpServerConfig>"
|
|
validation_errors=$((validation_errors + 1))
|
|
fi
|
|
|
|
# 3. Check for required sections with detailed error reporting
|
|
local required_sections=("listenerList" "virtualHostList" "extProcessorList")
|
|
for section in "${required_sections[@]}"; do
|
|
if ! xmlstarlet sel -Q -t -c "//$section" "$file_to_check" 2>/dev/null; then
|
|
log_error "Missing required section <$section>"
|
|
# Additional check for malformed section
|
|
if xmlstarlet sel -Q -t -c "//*[contains(name(), '$section')]" "$file_to_check" 2>/dev/null; then
|
|
log_error "Found malformed <$section> element (possibly with incorrect case or attributes)"
|
|
fi
|
|
validation_errors=$((validation_errors + 1))
|
|
fi
|
|
done
|
|
|
|
# 4. Check for orphaned elements with detailed reporting
|
|
if xmlstarlet sel -Q -t -c "//listener[not(parent::listenerList)]" "$file_to_check" 2>/dev/null; then
|
|
log_error "Found orphaned <listener> elements"
|
|
# Report the orphaned listener names
|
|
xmlstarlet sel -Q -t -m "//listener[not(parent::listenerList)]" -v "concat('Orphaned Listener: ', name)" -n "$file_to_check" 2>/dev/null | while read -r line; do
|
|
log_error "$line"
|
|
done
|
|
validation_errors=$((validation_errors + 1))
|
|
fi
|
|
|
|
if xmlstarlet sel -Q -t -c "//virtualHost[not(parent::virtualHostList)]" "$file_to_check" 2>/dev/null; then
|
|
log_error "Found orphaned <virtualHost> elements"
|
|
# Report the orphaned virtualHost names
|
|
xmlstarlet sel -Q -t -m "//virtualHost[not(parent::virtualHostList)]" -v "concat('Orphaned VirtualHost: ', name)" -n "$file_to_check" 2>/dev/null | while read -r line; do
|
|
log_error "$line"
|
|
done
|
|
validation_errors=$((validation_errors + 1))
|
|
fi
|
|
|
|
# 5. Validate listener structure with detailed reporting
|
|
xmlstarlet sel -Q -t -m "//listener" -v "concat('Checking listener: ', name)" -n "$file_to_check" 2>/dev/null | while read -r listener; do
|
|
if [[ -n "$listener" ]]; then
|
|
log_verbose "$listener"
|
|
# Check if listener has required attributes
|
|
if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/address" "$file_to_check" 2>/dev/null; then
|
|
log_error "Listener '${listener#Checking listener: }' is missing required <address> element"
|
|
validation_errors=$((validation_errors + 1))
|
|
fi
|
|
# Additional check for SSL listeners
|
|
if [[ "${listener#Checking listener: }" == HTTPS-* ]]; then
|
|
if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/certFile" "$file_to_check" 2>/dev/null; then
|
|
log_error "HTTPS Listener '${listener#Checking listener: }' is missing required <certFile> element"
|
|
validation_errors=$((validation_errors + 1))
|
|
fi
|
|
if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/keyFile" "$file_to_check" 2>/dev/null; then
|
|
log_error "HTTPS Listener '${listener#Checking listener: }' is missing required <keyFile> element"
|
|
validation_errors=$((validation_errors + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Return result
|
|
if [[ "$validation_errors" -eq 0 ]]; then
|
|
log_success "XML structure validation passed for '$file_to_check'"
|
|
return 0
|
|
else
|
|
log_error "Found $validation_errors XML structure issue(s) in '$file_to_check'"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
cleanup_xml() {
|
|
local file_to_clean="$1"
|
|
log "Performing XML cleanup on '$file_to_clean'..."
|
|
|
|
# 1. Remove empty nodes
|
|
xmlstarlet ed -L -d '//*[not(node())]' "$file_to_clean" || log "Warning: Failed to remove empty nodes"
|
|
|
|
# 2. Remove orphaned elements
|
|
xmlstarlet ed -L -d '//listener[not(parent::listenerList)]' "$file_to_clean" || log "Warning: Failed to remove orphaned listeners"
|
|
xmlstarlet ed -L -d '//virtualHost[not(parent::virtualHostList)]' "$file_to_clean" || log "Warning: Failed to remove orphaned virtualHosts"
|
|
|
|
# 3. Clean up whitespace
|
|
xmlstarlet ed -L -d '//text()[normalize-space()=""]' "$file_to_clean" || log "Warning: Failed to clean whitespace"
|
|
|
|
# 4. Validate after cleanup
|
|
if ! validate_xml_structure "$file_to_clean"; then
|
|
log_error "XML validation failed after cleanup"
|
|
return 1
|
|
fi
|
|
|
|
log "XML cleanup completed for '$file_to_clean'"
|
|
return 0
|
|
}
|
|
|
|
# === Main Script Logic ===
|
|
main() {
|
|
# Setup logging first
|
|
setup_logging
|
|
log "Starting XML config check/fix V1.8.0 for $CONF_FILE"
|
|
|
|
# Argument Parsing
|
|
if [[ "${1:-}" == "--verbose" ]]; then
|
|
VERBOSE=1
|
|
log "Verbose mode enabled"
|
|
fi
|
|
|
|
# Dependency Checks
|
|
log_verbose "Checking dependencies..."
|
|
check_command "xmllint" "libxml2-utils"
|
|
check_command "xmlstarlet" "xmlstarlet"
|
|
|
|
# Backup Original File
|
|
log "Backing up '$CONF_FILE' to '$BACKUP_FILE'..."
|
|
sudo cp -a "$CONF_FILE" "$BACKUP_FILE"
|
|
|
|
# Initial Validation using xmlstarlet
|
|
log_verbose "Running initial xmlstarlet validation on '$CONF_FILE'..."
|
|
if xmlstarlet val --well-formed "$CONF_FILE" 2>/dev/null; then
|
|
log_success "Initial Check: '$CONF_FILE' is well-formed XML"
|
|
if validate_xml_structure "$CONF_FILE"; then
|
|
log_success "Initial Check: '$CONF_FILE' has valid structure"
|
|
INITIAL_FILE_VALID=1
|
|
else
|
|
log_error "Initial Check: '$CONF_FILE' has structural issues"
|
|
fi
|
|
else
|
|
log_error "Initial Check: '$CONF_FILE' is not well-formed XML"
|
|
fi
|
|
|
|
# Recovery Attempt
|
|
if [[ "$INITIAL_FILE_VALID" -eq 0 ]]; then
|
|
log "Attempting XML cleanup..."
|
|
if cleanup_xml "$CONF_FILE"; then
|
|
log_success "XML cleanup completed successfully"
|
|
INITIAL_FILE_VALID=1
|
|
else
|
|
log_error "XML cleanup failed"
|
|
fi
|
|
fi
|
|
|
|
# Final Validation and Decision
|
|
FINAL_CONFIG_SOURCE_FILE=""
|
|
FINAL_CONFIG_SOURCE_DESC=""
|
|
FINAL_EXIT_CODE=0
|
|
log_verbose "Running final validation and decision logic..."
|
|
|
|
# Check 1: Is the original (potentially cleaned) temp file valid now?
|
|
if [[ "$INITIAL_FILE_VALID" -eq 1 ]]; then
|
|
FINAL_CONFIG_SOURCE_FILE="$CONF_FILE"
|
|
FINAL_CONFIG_SOURCE_DESC="original"
|
|
else
|
|
log_error "Neither original nor cleaned file is valid. Falling back to default or minimal config"
|
|
FINAL_EXIT_CODE=2
|
|
fi
|
|
|
|
# Apply and Restart
|
|
if [[ -n "$FINAL_CONFIG_SOURCE_FILE" ]]; then
|
|
if apply_and_restart "$FINAL_CONFIG_SOURCE_FILE" "$FINAL_CONFIG_SOURCE_DESC"; then
|
|
log_success "Script completed successfully. Applied: $FINAL_CONFIG_SOURCE_DESC"
|
|
exit "$FINAL_EXIT_CODE"
|
|
else
|
|
log_error "Failed to apply and restart using $FINAL_CONFIG_SOURCE_DESC config"
|
|
exit 1
|
|
fi
|
|
else
|
|
log_error "No valid configuration file found. Exiting"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# === Entry Point ===
|
|
main "$@"
|
|
|
|
# === Fallback Logic ===
|
|
log "Attempting fallback sequence..."
|
|
FALLBACK_APPLIED="" # Track which fallback succeeded
|
|
|
|
# Priority 1: Official Default Config (with include checks)
|
|
if [[ -z "$FALLBACK_APPLIED" && -f "$DEFAULT_CONF" ]]; then
|
|
log_verbose "Fallback Priority 1: Checking official default config '$DEFAULT_CONF'..."
|
|
if sudo xmllint --noout "$DEFAULT_CONF" 2>/dev/null; then
|
|
# Validate semantic correctness of the default config
|
|
if perform_semantic_checks "$DEFAULT_CONF"; then
|
|
log "Info: Default config is valid. Applying default configuration."
|
|
|
|
# Log the exact content of the default config for debugging
|
|
log_verbose "Exact content of default config ('$DEFAULT_CONF'):"
|
|
sudo cat "$DEFAULT_CONF" | sudo tee -a "$SCRIPT_LOG" >/dev/null
|
|
|
|
# Apply the default configuration
|
|
if apply_and_restart "$DEFAULT_CONF" "default"; then
|
|
log "✅ Script completed successfully. Applied: default configuration."
|
|
|
|
# Verify the copied file matches the default config exactly
|
|
if ! sudo diff -q "$DEFAULT_CONF" "$CONF_FILE" &>/dev/null; then
|
|
log "❌ FATAL: Copied file does NOT match the default configuration exactly!"
|
|
log " Differences detected between '$DEFAULT_CONF' and '$CONF_FILE'."
|
|
exit 1
|
|
else
|
|
log "✔ Verified: Copied file matches the default configuration exactly."
|
|
fi
|
|
|
|
FALLBACK_APPLIED="default"
|
|
exit 0 # Exit code 0 for success with default config
|
|
else
|
|
log "❌ FATAL: Failed to apply/restart using default configuration!"
|
|
fi
|
|
else
|
|
log "⚠ Semantic Check: Default config failed semantic validation. Skipping fallback to default."
|
|
fi
|
|
else
|
|
log "⚠ Structural Check: Default config failed XML validation. Skipping fallback to default."
|
|
fi
|
|
fi
|
|
|
|
# Priority 2: Minimal Fallback Configuration
|
|
if [[ -z "$FALLBACK_APPLIED" ]]; then
|
|
log "Fallback Priority 2: Generating minimal fallback configuration..."
|
|
cat <<EOF | sudo tee "$MINIMAL_TMP" >/dev/null
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<httpServerConfig>
|
|
<serverName>LiteSpeed</serverName>
|
|
<listenerList>
|
|
<listener>
|
|
<name>HTTP</name>
|
|
<address>*:80</address>
|
|
</listener>
|
|
</listenerList>
|
|
<virtualHostList>
|
|
<virtualHost>
|
|
<name>default</name>
|
|
<vhRoot>/var/www/webroot/</vhRoot>
|
|
<configFile>\$SERVER_ROOT/conf/vhconf.xml</configFile>
|
|
<allowSymbolLink>1</allowSymbolLink>
|
|
<enableScript>1</enableScript>
|
|
<restrained>1</restrained>
|
|
<setUIDMode>0</setUIDMode>
|
|
<chrootMode>0</chrootMode>
|
|
</virtualHost>
|
|
</virtualHostList>
|
|
<extProcessorList>
|
|
<extProcessor>
|
|
<type>lsapi</type>
|
|
<name>lsphp</name>
|
|
<address>uds://tmp/lshttpd/lsphp.sock</address>
|
|
<maxConns>35</maxConns>
|
|
<env>PHP_LSAPI_MAX_REQUESTS=500</env>
|
|
<initTimeout>60</initTimeout>
|
|
<retryTimeout>0</retryTimeout>
|
|
<persistConn>1</persistConn>
|
|
</extProcessor>
|
|
</extProcessorList>
|
|
</httpServerConfig>
|
|
EOF
|
|
|
|
# Validate minimal config
|
|
if sudo xmllint --noout "$MINIMAL_TMP" 2>/dev/null; then
|
|
if perform_semantic_checks "$MINIMAL_TMP"; then
|
|
log "Info: Minimal fallback config generated successfully and passed validation."
|
|
if apply_and_restart "$MINIMAL_TMP" "minimal"; then
|
|
log "🚨 CRITICAL WARNING: Server running on MINIMAL config. Manual reconfiguration required! 🚨"
|
|
log "✅ Script completed. Applied: minimal configuration."
|
|
FALLBACK_APPLIED="minimal"
|
|
exit 2 # Exit code 2 for minimal config
|
|
else
|
|
log "❌ FATAL: Failed to apply/restart even the minimal config!"
|
|
fi
|
|
else
|
|
log "❌ FATAL: Generated minimal config failed semantic validation! This is a script bug."
|
|
fi
|
|
else
|
|
log "❌ FATAL: Generated minimal config is invalid XML! This is a script bug."
|
|
fi
|
|
fi
|
|
|
|
# If we reach here, all fallbacks failed
|
|
log "❌ FATAL: All fallback mechanisms failed. Server configuration remains unchanged."
|
|
exit 1 |