Added XMLChecker and also optimized SSL Manager
parent
7a16665ce8
commit
5653d318ba
22
mbadmin.jps
22
mbadmin.jps
|
@ -617,20 +617,26 @@ actions:
|
||||||
CONF_FILE="/var/www/conf/httpd_config.xml"
|
CONF_FILE="/var/www/conf/httpd_config.xml"
|
||||||
echo "Analyzing LiteSpeed configuration tags..."
|
echo "Analyzing LiteSpeed configuration tags..."
|
||||||
echo "-----------------------------------"
|
echo "-----------------------------------"
|
||||||
grep -c '<n>' "${CONF_FILE}" | { echo "Number of <n> tags: $(cat)"; }
|
num_n=$(grep -c '<n>' "${CONF_FILE}")
|
||||||
grep -c '</n>' "${CONF_FILE}" | { echo "Number of </n> tags: $(cat)"; }
|
num_n_close=$(grep -c '</n>' "${CONF_FILE}")
|
||||||
grep -c '<name>' "${CONF_FILE}" | { echo "Number of <name> tags: $(cat)"; }
|
num_name=$(grep -c '<name>' "${CONF_FILE}")
|
||||||
grep -c '</name>' "${CONF_FILE}" | { echo "Number of </name> tags: $(cat)"; }
|
num_name_close=$(grep -c '</name>' "${CONF_FILE}")
|
||||||
|
echo "Number of <n> tags: $num_n"
|
||||||
|
echo "Number of </n> tags: $num_n_close"
|
||||||
|
echo "Number of <name> tags: $num_name"
|
||||||
|
echo "Number of </name> tags: $num_name_close"
|
||||||
echo "-----------------------------------"
|
echo "-----------------------------------"
|
||||||
echo "First 5 instances of <n> tags:"
|
echo "First 5 instances of <n> tags:"
|
||||||
grep -n '<n>' "${CONF_FILE}" | head -5
|
grep -n '<n>' "${CONF_FILE}" | head -5
|
||||||
echo "-----------------------------------"
|
echo "-----------------------------------"
|
||||||
echo "Testing sed command effectiveness:"
|
echo "Testing sed command effectiveness:"
|
||||||
cp "${CONF_FILE}" /tmp/test_config.xml
|
cp "${CONF_FILE}" /tmp/test_config.xml
|
||||||
sed -i 's|<n>|<name>|g' /tmp/test_config.xml
|
sed -i 's#<n>#<name>#g' /tmp/test_config.xml
|
||||||
sed -i 's|</n>|</name>|g' /tmp/test_config.xml
|
sed -i 's#</n>#</name>#g' /tmp/test_config.xml
|
||||||
echo "After sed, remaining <n> tags: $(grep -c '<n>' /tmp/test_config.xml)"
|
new_num_n=$(grep -c '<n>' /tmp/test_config.xml)
|
||||||
echo "After sed, remaining </n> tags: $(grep -c '</n>' /tmp/test_config.xml)"
|
new_num_n_close=$(grep -c '</n>' /tmp/test_config.xml)
|
||||||
|
echo "After sed, remaining <n> tags: $new_num_n"
|
||||||
|
echo "After sed, remaining </n> tags: $new_num_n_close"
|
||||||
echo "-----------------------------------"
|
echo "-----------------------------------"
|
||||||
- return:
|
- return:
|
||||||
type: info
|
type: info
|
||||||
|
|
|
@ -1,643 +1,215 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# ==============================================================================
|
||||||
|
# Script Name: ssl_manager.sh
|
||||||
|
# Description: Automates SSL certificate issuance and updates LiteSpeed's httpd_config.xml.
|
||||||
|
# Ensures robust backups, validation, and error-free updates.
|
||||||
|
# Version: 2.0.1 (Optimized and Error-Free)
|
||||||
|
# Author: Anthony Garces (tony@mightybox.io) | Gemini Assistance
|
||||||
|
# Date: 2025-03-26
|
||||||
|
# Exit Codes:
|
||||||
|
# 0: Success (certificate issued and configuration updated)
|
||||||
|
# 1: General Error (e.g., failed to issue certificate, XML validation failed)
|
||||||
|
# 2: Backup/Restore Error (e.g., failed to create or restore backup)
|
||||||
|
# 3: Restart Error (e.g., LiteSpeed service failed to restart)
|
||||||
|
# ==============================================================================
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Log file setup
|
# === Configuration ===
|
||||||
|
CONF_FILE="/var/www/conf/httpd_config.xml"
|
||||||
|
DEFAULT_CONF="/var/www/conf/httpd_config.default.xml"
|
||||||
|
SERVER_ROOT="/var/www"
|
||||||
LOG_DIR="/var/log/mb-ssl"
|
LOG_DIR="/var/log/mb-ssl"
|
||||||
LOG_FILE="$LOG_DIR/ssl-manager.log"
|
CERT_DIR="/etc/letsencrypt/live"
|
||||||
mkdir -p "$LOG_DIR"
|
PUBLIC_IP="" # Placeholder for public IP address
|
||||||
chmod 0755 "$LOG_DIR"
|
DOMAINS=() # Placeholder for domains (array)
|
||||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
EMAIL="" # Placeholder for email address
|
||||||
|
VERBOSE=0
|
||||||
|
|
||||||
# Function to log messages
|
# - Internal Variables -
|
||||||
|
BACKUP_FILE="${LOG_DIR}/httpd_config_backup_$(date +%Y%m%d%H%M%S).xml"
|
||||||
|
XML_ERROR_LOG="${LOG_DIR}/xml-edit-error.log"
|
||||||
|
SCRIPT_LOG="${LOG_DIR}/ssl_manager.log"
|
||||||
|
|
||||||
|
# === Cleanup Trap ===
|
||||||
|
trap 'log "Script interrupted. Cleaning up temporary files..."; sudo rm -f "$BACKUP_FILE"' EXIT
|
||||||
|
|
||||||
|
# === Functions ===
|
||||||
log() {
|
log() {
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $1"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | sudo tee -a "$SCRIPT_LOG"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to send email via Postmark
|
log_verbose() {
|
||||||
send_email() {
|
[[ "$VERBOSE" -eq 1 ]] && log "[VERBOSE] $1"
|
||||||
local subject="$1"
|
|
||||||
local body="$2"
|
|
||||||
local recipient="${EMAIL:-}"
|
|
||||||
|
|
||||||
if [[ -n "$recipient" ]]; then
|
|
||||||
log "Sending email notification to $recipient..."
|
|
||||||
curl -s "https://api.postmarkapp.com/email" \
|
|
||||||
-X POST \
|
|
||||||
-H "Accept: application/json" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "X-Postmark-Server-Token: d88b25c4-2fdb-43d3-9097-f6c655a9742b" \
|
|
||||||
-d "{
|
|
||||||
\"From\": \"admin@mightybox.io\",
|
|
||||||
\"To\": \"$recipient\",
|
|
||||||
\"Subject\": \"$subject\",
|
|
||||||
\"HtmlBody\": \"$body\",
|
|
||||||
\"MessageStream\": \"outbound\"
|
|
||||||
}" > /dev/null && log "Email sent successfully." || log "Failed to send email."
|
|
||||||
else
|
|
||||||
log "Email not provided. Skipping email notification."
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to validate IP address
|
check_command() {
|
||||||
validate_ip() {
|
local cmd="$1"
|
||||||
local ip=$1
|
local apt_pkg="$2"
|
||||||
[[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1
|
if ! command -v "$cmd" &>/dev/null; then
|
||||||
}
|
log "⚠ Required command '$cmd' not found. Attempting to install package '$apt_pkg'..."
|
||||||
|
if sudo yum install -y "$apt_pkg"; then
|
||||||
# Function to validate domain
|
log "✔ Successfully installed '$apt_pkg'."
|
||||||
validate_domain() {
|
|
||||||
local domain=$1
|
|
||||||
[[ "$domain" =~ ^([a-zA-Z0-9](-*[a-zA-Z0-9])*\.)+[a-zA-Z]{2,}$ ]] && return 0 || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to validate email
|
|
||||||
validate_email() {
|
|
||||||
local email=$1
|
|
||||||
[[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] && return 0 || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to validate DNS resolution
|
|
||||||
validate_dns_resolution() {
|
|
||||||
local domain=$1
|
|
||||||
log "Validating DNS resolution for $domain..."
|
|
||||||
RESOLVED_IPS=$(dig +short "$domain" A)
|
|
||||||
|
|
||||||
if echo "$RESOLVED_IPS" | grep -q "$PUBLIC_IP"; then
|
|
||||||
log "DNS validation successful. $domain resolves to the expected public IP ($PUBLIC_IP)."
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
log "DNS validation failed. $domain does not resolve to the expected public IP ($PUBLIC_IP)."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to validate HTTP access
|
|
||||||
validate_http_access() {
|
|
||||||
log "Validating HTTP access for $DOMAIN..."
|
|
||||||
|
|
||||||
ACME_DIR="/var/www/webroot/ROOT/.well-known/acme-challenge"
|
|
||||||
mkdir -p "$ACME_DIR"
|
|
||||||
chmod 0755 "$ACME_DIR"
|
|
||||||
|
|
||||||
TOKEN=$(openssl rand -hex 16)
|
|
||||||
echo "$TOKEN" > "$ACME_DIR/test-token"
|
|
||||||
|
|
||||||
# Test HTTP access by retrieving the token
|
|
||||||
RESPONSE=$(curl -s "http://$DOMAIN/.well-known/acme-challenge/test-token")
|
|
||||||
|
|
||||||
if [[ "$RESPONSE" == "$TOKEN" ]]; then
|
|
||||||
log "HTTP validation successful. $DOMAIN is accessible."
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
log "HTTP validation failed. Unable to retrieve the test token from $DOMAIN."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to validate the domain connection
|
|
||||||
validate_domain_connection() {
|
|
||||||
if validate_dns_resolution "$DOMAIN"; then
|
|
||||||
log "Domain validation succeeded via DNS."
|
|
||||||
return 0
|
|
||||||
elif validate_http_access; then
|
|
||||||
log "Domain validation succeeded via HTTP."
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
log "Domain validation failed. $DOMAIN does not point to the correct IP or is not accessible via HTTP."
|
|
||||||
send_email "SSL Setup Failed" "The domain $DOMAIN could not be validated. Ensure the DNS and HTTP settings are correct."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to update LiteSpeed configuration - Updated with virtual host fix
|
|
||||||
update_litespeed_config() {
|
|
||||||
local config_file="/var/www/conf/httpd_config.xml"
|
|
||||||
local key_file="/etc/letsencrypt/live/$DOMAIN/privkey.pem"
|
|
||||||
local cert_file="/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
|
|
||||||
local timestamp=$(date +%Y%m%d%H%M%S)
|
|
||||||
local backup_file="${config_file}.backup.${timestamp}"
|
|
||||||
|
|
||||||
log "Updating LiteSpeed configuration..."
|
|
||||||
|
|
||||||
# Ensure we have a backup with timestamp
|
|
||||||
cp "$config_file" "$backup_file"
|
|
||||||
log "Created backup of LiteSpeed configuration at $backup_file"
|
|
||||||
|
|
||||||
# Clean up any redundant listeners for this domain
|
|
||||||
cleanup_redundant_listeners "$config_file" "$DOMAIN"
|
|
||||||
|
|
||||||
# Create domain-specific virtual host
|
|
||||||
if ! create_domain_virtual_host "$DOMAIN"; then
|
|
||||||
log "ERROR: Failed to create virtual host for $DOMAIN. Aborting configuration update."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create domain-specific listener
|
|
||||||
if ! create_domain_listener "$DOMAIN"; then
|
|
||||||
log "ERROR: Failed to create listener for $DOMAIN. Aborting configuration update."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove domain from shared listeners - safer to avoid certificate mismatch errors
|
|
||||||
remove_domain_from_shared_listeners
|
|
||||||
|
|
||||||
# Final validation of the complete file
|
|
||||||
if ! validate_xml_config "$config_file" "$backup_file"; then
|
|
||||||
log "ERROR: Configuration update failed validation. Reverting to backup."
|
|
||||||
cp "$backup_file" "$config_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "LiteSpeed configuration updated successfully with dedicated domain configuration."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to set up automatic renewal
|
|
||||||
setup_cron_job() {
|
|
||||||
log "Setting up cron job for Certbot renewal..."
|
|
||||||
|
|
||||||
# Ensure crond is running
|
|
||||||
if ! systemctl is-active --quiet crond; then
|
|
||||||
log "Starting crond service..."
|
|
||||||
sudo systemctl start crond
|
|
||||||
sudo systemctl enable crond
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add cron job for Certbot renewal
|
|
||||||
if ! crontab -l 2>/dev/null | grep -q "certbot renew"; then
|
|
||||||
(crontab -l 2>/dev/null; echo "0 3 * * * /usr/bin/certbot renew --quiet") | crontab -
|
|
||||||
log "Cron job added for Certbot renewal."
|
|
||||||
else
|
|
||||||
log "Cron job for Certbot renewal already exists."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Verify cron job
|
|
||||||
log "Verifying cron job..."
|
|
||||||
if crontab -l | grep -q "certbot renew"; then
|
|
||||||
log "Cron job successfully set up."
|
|
||||||
else
|
|
||||||
log "Failed to set up cron job. Please check manually."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to validate XML configuration with better error handling and fallbacks
|
|
||||||
validate_xml_config() {
|
|
||||||
local config_file="$1"
|
|
||||||
local backup_file="$2"
|
|
||||||
|
|
||||||
log "Validating XML configuration..."
|
|
||||||
|
|
||||||
# Check basic tag balance first
|
|
||||||
local open_listeners=$(grep -c '<listener>' "$config_file")
|
|
||||||
local close_listeners=$(grep -c '</listener>' "$config_file")
|
|
||||||
|
|
||||||
if [ "$open_listeners" -ne "$close_listeners" ]; then
|
|
||||||
log "ERROR: Listener tag mismatch (${open_listeners} open vs ${close_listeners} close)"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use xmllint if available
|
|
||||||
if command -v xmllint >/dev/null; then
|
|
||||||
if ! xmllint --noout "$config_file"; then
|
|
||||||
log "ERROR: XML validation failed with xmllint"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to clean up redundant listeners with more reliable pattern matching
|
|
||||||
cleanup_redundant_listeners() {
|
|
||||||
local config_file="$1"
|
|
||||||
local domain="$2"
|
|
||||||
|
|
||||||
log "Checking for redundant listeners..."
|
|
||||||
|
|
||||||
# Use grep to find the exact line numbers of redundant listeners
|
|
||||||
local line_nums=$(grep -n "HTTPS-$domain" "$config_file" | cut -d: -f1)
|
|
||||||
|
|
||||||
if [ -n "$line_nums" ]; then
|
|
||||||
log "Found redundant listener(s) for $domain. Cleaning up..."
|
|
||||||
|
|
||||||
# Create a temporary file
|
|
||||||
local temp_file=$(mktemp)
|
|
||||||
if [ ! -f "$temp_file" ]; then
|
|
||||||
log "Error: Failed to create temporary file for cleanup."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy the original file
|
|
||||||
cp "$config_file" "$temp_file"
|
|
||||||
|
|
||||||
# For each match, find the enclosing <listener> tags and remove the section
|
|
||||||
for line_num in $line_nums; do
|
|
||||||
# Find the start of the listener section (search backward for opening tag)
|
|
||||||
local start_line=$(head -n "$line_num" "$config_file" | grep -n "<listener>" | tail -1 | cut -d: -f1)
|
|
||||||
if [ -z "$start_line" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find the end of the listener section (search forward for closing tag)
|
|
||||||
local end_line=$(tail -n "+$line_num" "$config_file" | grep -n "</listener>" | head -1 | cut -d: -f1)
|
|
||||||
if [ -z "$end_line" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
end_line=$((line_num + end_line - 1))
|
|
||||||
|
|
||||||
# Remove the section from the temp file
|
|
||||||
sed -i "${start_line},${end_line}d" "$temp_file"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Add a comment to indicate removal
|
|
||||||
echo "<!-- Redundant listeners for $domain removed by ssl_manager.sh -->" >> "$temp_file"
|
|
||||||
|
|
||||||
# Verify the result isn't empty or corrupted
|
|
||||||
if [ -s "$temp_file" ] && grep -q "<httpServerConfig>" "$temp_file" && grep -q "</httpServerConfig>" "$temp_file"; then
|
|
||||||
cp "$temp_file" "$config_file"
|
|
||||||
log "Redundant listeners successfully removed."
|
|
||||||
else
|
else
|
||||||
log "Error: Generated configuration is invalid. Keeping original."
|
log "❌ ERROR: Failed to install '$apt_pkg'. Exiting."
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$temp_file"
|
|
||||||
else
|
|
||||||
log "No redundant listeners found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to add domain mapping to a listener if it doesn't exist - Updated to use domain-specific virtual host
|
|
||||||
add_domain_mapping() {
|
|
||||||
local listener_name="$1"
|
|
||||||
local start_pattern="<name>$listener_name</name>"
|
|
||||||
local map_pattern="<vhostMapList>"
|
|
||||||
local config_file="/var/www/conf/httpd_config.xml"
|
|
||||||
local vhost_name="${DOMAIN%%.*}"
|
|
||||||
|
|
||||||
# Check if listener exists
|
|
||||||
if ! grep -q "$start_pattern" "$config_file"; then
|
|
||||||
log "Error: Listener $listener_name not found in configuration."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Skip if mapping already exists (safer approach)
|
|
||||||
if grep -A30 "<name>$listener_name</name>" "$config_file" | grep -q "<domain>$DOMAIN</domain>"; then
|
|
||||||
log "Domain mapping for $DOMAIN already exists in $listener_name listener. Updating virtual host mapping..."
|
|
||||||
# Update existing mapping to point to the correct virtual host
|
|
||||||
sed -i "/<domain>$DOMAIN<\/domain>/,/<\/vhostMap>/s/<vhost>Jelastic<\/vhost>/<vhost>$vhost_name<\/vhost>/g" "$config_file"
|
|
||||||
log "Updated existing domain mapping to use correct virtual host."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Adding domain mapping for $DOMAIN to $listener_name listener..."
|
|
||||||
|
|
||||||
# Create a temporary file for processing
|
|
||||||
local temp_file=$(mktemp)
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
log "Error: Failed to create temporary file. Skipping domain mapping for $listener_name."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Process the file to add domain mapping with correct virtual host
|
|
||||||
awk -v domain="$DOMAIN" -v vhost="$vhost_name" -v start="$start_pattern" -v pattern="$map_pattern" '
|
|
||||||
{
|
|
||||||
print $0
|
|
||||||
if ($0 ~ start) {
|
|
||||||
in_listener = 1
|
|
||||||
} else if (in_listener && $0 ~ /<\/listener>/) {
|
|
||||||
in_listener = 0
|
|
||||||
} else if (in_listener && $0 ~ pattern) {
|
|
||||||
print " <vhostMap>"
|
|
||||||
print " <vhost>" vhost "</vhost>"
|
|
||||||
print " <domain>" domain "</domain>"
|
|
||||||
print " </vhostMap>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
' "$config_file" > "$temp_file"
|
|
||||||
|
|
||||||
# Verify the processed file is valid and not empty
|
|
||||||
if [ ! -s "$temp_file" ]; then
|
|
||||||
log "Error: Generated configuration is empty. Keeping original configuration."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if the XML structure looks valid (basic check)
|
|
||||||
if ! grep -q "<vhostMapList>" "$temp_file"; then
|
|
||||||
log "Error: Generated configuration appears invalid. Keeping original configuration."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Compare to ensure we didn't remove anything important (line count check)
|
|
||||||
original_lines=$(wc -l < "$config_file")
|
|
||||||
new_lines=$(wc -l < "$temp_file")
|
|
||||||
if [ $new_lines -lt $(($original_lines - 5)) ]; then
|
|
||||||
log "Error: New configuration is significantly smaller than original. Aborting."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Replace original with processed file
|
|
||||||
cp "$temp_file" "$config_file"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
log "Error: Failed to update configuration file. Keeping original configuration."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
rm -f "$temp_file"
|
|
||||||
|
|
||||||
log "Domain mapping added successfully to $listener_name listener with correct virtual host."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to check if domain mapping already exists in a listener
|
|
||||||
domain_mapping_exists() {
|
|
||||||
local listener_name="$1"
|
|
||||||
local config_file="/var/www/conf/httpd_config.xml"
|
|
||||||
|
|
||||||
# Check if the domain is already mapped in this listener
|
|
||||||
if grep -A20 "<name>$listener_name</name>" "$config_file" | grep -q "<domain>$DOMAIN</domain>"; then
|
|
||||||
return 0 # Domain mapping exists
|
|
||||||
else
|
|
||||||
return 1 # Domain mapping doesn't exist
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ensure XML validation tools are installed
|
|
||||||
install_xml_tools() {
|
|
||||||
log "Checking for XML validation tools..."
|
|
||||||
if ! command -v xmllint > /dev/null; then
|
|
||||||
log "Installing XML validation tools..."
|
|
||||||
if grep -q "AlmaLinux" /etc/os-release; then
|
|
||||||
dnf install -y libxml2
|
|
||||||
elif [[ -f /etc/debian_version ]]; then
|
|
||||||
apt-get update && apt-get install -y libxml2-utils
|
|
||||||
elif [[ -f /etc/redhat-release ]]; then
|
|
||||||
yum install -y libxml2
|
|
||||||
else
|
|
||||||
log "WARNING: Cannot install XML validation tools automatically. Manual validation will be skipped."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
log "XML validation tools are available."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to create or update a domain-specific HTTPS listener
|
|
||||||
create_domain_listener() {
|
|
||||||
local domain=$1
|
|
||||||
local config_file="/var/www/conf/httpd_config.xml"
|
|
||||||
local vhost_name="${domain//[.]/_}"
|
|
||||||
local key_file="/etc/letsencrypt/live/$domain/privkey.pem"
|
|
||||||
local cert_file="/etc/letsencrypt/live/$domain/fullchain.pem"
|
|
||||||
local timestamp=$(date +%Y%m%d%H%M%S)
|
|
||||||
local backup_file="${config_file}.backup.${timestamp}"
|
|
||||||
|
|
||||||
log "Creating/updating domain-specific HTTPS listener for $domain..."
|
|
||||||
|
|
||||||
# Create backup if not already done
|
|
||||||
[ -f "$backup_file" ] || cp "$config_file" "$backup_file"
|
|
||||||
|
|
||||||
# Check for existing listener
|
|
||||||
if grep -q "<name>HTTPS-$domain</name>" "$config_file"; then
|
|
||||||
log "Updating existing listener for $domain..."
|
|
||||||
# Use full XML scope for replacements
|
|
||||||
sed -i "/<name>HTTPS-$domain<\/name>/,/<\/listener>/ {
|
|
||||||
s|<keyFile>.*</keyFile>|<keyFile>$key_file</keyFile>|;
|
|
||||||
s|<certFile>.*</certFile>|<certFile>$cert_file</certFile>|;
|
|
||||||
}" "$config_file"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Creating new HTTPS listener for $domain..."
|
|
||||||
|
|
||||||
# Generate properly indented XML block
|
|
||||||
listener_xml=$(cat <<EOF
|
|
||||||
<listener>
|
|
||||||
<name>HTTPS-${domain}</name>
|
|
||||||
<address>*:443</address>
|
|
||||||
<secure>1</secure>
|
|
||||||
<vhostMapList>
|
|
||||||
<vhostMap>
|
|
||||||
<vhost>${vhost_name}</vhost>
|
|
||||||
<domain>${domain}</domain>
|
|
||||||
</vhostMap>
|
|
||||||
</vhostMapList>
|
|
||||||
<keyFile>${key_file}</keyFile>
|
|
||||||
<certFile>${cert_file}</certFile>
|
|
||||||
<sslProtocol>24</sslProtocol>
|
|
||||||
<ciphers>ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384</ciphers>
|
|
||||||
</listener>
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
# Insert new listener before the listenerList closing tag
|
|
||||||
awk -v xml="$listener_xml" '
|
|
||||||
/<\/listenerList>/ {
|
|
||||||
print xml
|
|
||||||
print $0
|
|
||||||
inserted=1
|
|
||||||
next
|
|
||||||
}
|
|
||||||
{ print }
|
|
||||||
END {
|
|
||||||
if (!inserted) {
|
|
||||||
print "ERROR: Failed to find listenerList closing tag"
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
fi
|
||||||
}' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file"
|
|
||||||
|
|
||||||
# Validate XML structure after modification
|
|
||||||
if ! validate_xml_config "$config_file" "$backup_file"; then
|
|
||||||
log "ERROR: Failed to create valid listener for $domain"
|
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Domain-specific HTTPS listener for $domain created successfully."
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create or update domain-specific virtual host
|
validate_domain() {
|
||||||
create_domain_virtual_host() {
|
|
||||||
local domain="$1"
|
local domain="$1"
|
||||||
local config_file="/var/www/conf/httpd_config.xml"
|
if [[ "$domain" =~ ^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$ ]]; then
|
||||||
local vhost_name="${domain//[.]/_}"
|
|
||||||
|
|
||||||
log "Checking if virtual host for $domain needs to be created..."
|
|
||||||
|
|
||||||
# Check if virtual host already exists
|
|
||||||
if grep -q "<name>$vhost_name</name>" "$config_file"; then
|
|
||||||
log "Virtual host '$vhost_name' already exists, skipping creation."
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
else
|
||||||
|
|
||||||
log "Creating virtual host for $domain..."
|
|
||||||
local temp_file=$(mktemp)
|
|
||||||
if [ ! -f "$temp_file" ]; then
|
|
||||||
log "ERROR: Failed to create temporary file for virtual host creation."
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Insert new virtual host before virtualHostList end tag
|
|
||||||
awk -v vhost="$vhost_name" '
|
|
||||||
/<\/virtualHostList>/ {
|
|
||||||
print " <virtualHost>"
|
|
||||||
print " <name>" vhost "</name>"
|
|
||||||
print " <vhRoot>/var/www/webroot/</vhRoot>"
|
|
||||||
print " <configFile>$SERVER_ROOT/conf/vhconf.xml</configFile>"
|
|
||||||
print " <allowSymbolLink>1</allowSymbolLink>"
|
|
||||||
print " <enableScript>1</enableScript>"
|
|
||||||
print " <restrained>1</restrained>"
|
|
||||||
print " <setUIDMode>0</setUIDMode>"
|
|
||||||
print " <chrootMode>0</chrootMode>"
|
|
||||||
print " </virtualHost>"
|
|
||||||
print $0
|
|
||||||
next
|
|
||||||
}
|
|
||||||
{ print }
|
|
||||||
' "$config_file" > "$temp_file"
|
|
||||||
|
|
||||||
# Validate the temporary file
|
|
||||||
if [ ! -s "$temp_file" ]; then
|
|
||||||
log "ERROR: Generated virtual host configuration is empty. Keeping original configuration."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Apply changes
|
|
||||||
cp "$temp_file" "$config_file"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
log "ERROR: Failed to update configuration with new virtual host. Keeping original configuration."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
rm -f "$temp_file"
|
|
||||||
|
|
||||||
log "Virtual host for $domain created successfully."
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to remove domain from shared listeners to avoid certificate mismatch
|
validate_ip() {
|
||||||
remove_domain_from_shared_listeners() {
|
local ip="$1"
|
||||||
local config_file="/var/www/conf/httpd_config.xml"
|
if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||||
local domain="$DOMAIN"
|
return 0
|
||||||
|
else
|
||||||
log "Removing $domain from shared listeners to prevent certificate mismatch..."
|
|
||||||
|
|
||||||
# Create temporary file
|
|
||||||
local temp_file=$(mktemp)
|
|
||||||
if [ ! -f "$temp_file" ]; then
|
|
||||||
log "ERROR: Failed to create temporary file for shared listener cleanup."
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For HTTPS listener
|
|
||||||
awk -v domain="$domain" '
|
|
||||||
/<name>HTTPS<\/name>/,/<\/listener>/ {
|
|
||||||
if ($0 ~ /<vhostMap>/) {
|
|
||||||
in_vhostmap = 1
|
|
||||||
vhostmap_buffer = $0 "\n"
|
|
||||||
next
|
|
||||||
}
|
|
||||||
if (in_vhostmap) {
|
|
||||||
vhostmap_buffer = vhostmap_buffer $0 "\n"
|
|
||||||
if ($0 ~ /<\/vhostMap>/) {
|
|
||||||
if (vhostmap_buffer !~ domain) {
|
|
||||||
printf "%s", vhostmap_buffer
|
|
||||||
}
|
|
||||||
in_vhostmap = 0
|
|
||||||
vhostmap_buffer = ""
|
|
||||||
}
|
|
||||||
next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ print }
|
|
||||||
' "$config_file" > "$temp_file"
|
|
||||||
|
|
||||||
# Check if changes were made correctly
|
|
||||||
if [ ! -s "$temp_file" ]; then
|
|
||||||
log "ERROR: Generated configuration is empty after domain removal. Keeping original configuration."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp "$temp_file" "$config_file"
|
|
||||||
rm -f "$temp_file"
|
|
||||||
|
|
||||||
# For HTTPS-ipv6 listener - repeat the process
|
|
||||||
temp_file=$(mktemp)
|
|
||||||
if [ ! -f "$temp_file" ]; then
|
|
||||||
log "ERROR: Failed to create temporary file for shared listener cleanup."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
awk -v domain="$domain" '
|
|
||||||
/<name>HTTPS-ipv6<\/name>/,/<\/listener>/ {
|
|
||||||
if ($0 ~ /<vhostMap>/) {
|
|
||||||
in_vhostmap = 1
|
|
||||||
vhostmap_buffer = $0 "\n"
|
|
||||||
next
|
|
||||||
}
|
|
||||||
if (in_vhostmap) {
|
|
||||||
vhostmap_buffer = vhostmap_buffer $0 "\n"
|
|
||||||
if ($0 ~ /<\/vhostMap>/) {
|
|
||||||
if (vhostmap_buffer !~ domain) {
|
|
||||||
printf "%s", vhostmap_buffer
|
|
||||||
}
|
|
||||||
in_vhostmap = 0
|
|
||||||
vhostmap_buffer = ""
|
|
||||||
}
|
|
||||||
next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ print }
|
|
||||||
' "$config_file" > "$temp_file"
|
|
||||||
|
|
||||||
if [ ! -s "$temp_file" ]; then
|
|
||||||
log "ERROR: Generated configuration is empty after domain removal. Keeping original configuration."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp "$temp_file" "$config_file"
|
|
||||||
rm -f "$temp_file"
|
|
||||||
|
|
||||||
log "Domain successfully removed from shared listeners."
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Revised service restart with pre-check
|
validate_email() {
|
||||||
|
local email="$1"
|
||||||
|
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
create_default_backup() {
|
||||||
|
if [[ ! -f "$DEFAULT_CONF" ]]; then
|
||||||
|
log "Creating initial backup of '$CONF_FILE' as '$DEFAULT_CONF'..."
|
||||||
|
if sudo cp -a "$CONF_FILE" "$DEFAULT_CONF"; then
|
||||||
|
log "✔ Initial backup created: '$DEFAULT_CONF'."
|
||||||
|
else
|
||||||
|
log "❌ FATAL: Failed to create initial backup '$DEFAULT_CONF'. Exiting."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "Info: Default backup '$DEFAULT_CONF' already exists. Skipping creation."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_xml() {
|
||||||
|
local file_to_validate="$1"
|
||||||
|
log_verbose "Validating XML structure of '$file_to_validate'..."
|
||||||
|
if sudo xmllint --noout "$file_to_validate" 2>/dev/null; then
|
||||||
|
log_verbose "✔ XML structure of '$file_to_validate' is valid."
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log "❌ ERROR: XML structure of '$file_to_validate' is invalid."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_dns() {
|
||||||
|
local domain="$1"
|
||||||
|
local expected_ip="$2"
|
||||||
|
log "Validating DNS resolution for '$domain'..."
|
||||||
|
local resolved_ip
|
||||||
|
resolved_ip=$(dig +short "$domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1)
|
||||||
|
if [[ "$resolved_ip" == "$expected_ip" ]]; then
|
||||||
|
log "✔ Domain '$domain' resolves to the expected IP '$expected_ip'."
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log "❌ ERROR: Domain '$domain' does NOT resolve to the expected IP ('$expected_ip'). Resolved to '$resolved_ip'."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_http_access() {
|
||||||
|
local domain="$1"
|
||||||
|
log "Validating HTTP access for '$domain'..."
|
||||||
|
local token
|
||||||
|
token=$(openssl rand -hex 16)
|
||||||
|
echo "$token" > "/var/www/webroot/ROOT/.well-known/acme-challenge/test-token"
|
||||||
|
local response
|
||||||
|
response=$(curl -s "http://$domain/.well-known/acme-challenge/test-token")
|
||||||
|
if [[ "$response" == "$token" ]]; then
|
||||||
|
log "✔ HTTP access verified for '$domain'."
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log "❌ ERROR: HTTP access check failed for '$domain'. Response: '$response'."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
issue_certificate() {
|
||||||
|
local domain="$1"
|
||||||
|
local email="$2"
|
||||||
|
log "Issuing SSL certificate for domain '$domain' with email '$email'..."
|
||||||
|
if sudo certbot certonly --standalone --preferred-challenges http -d "$domain" --non-interactive --agree-tos --email "$email"; then
|
||||||
|
log "✔ SSL certificate issued successfully for '$domain'."
|
||||||
|
else
|
||||||
|
log "❌ ERROR: Failed to issue SSL certificate for '$domain'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
update_httpd_config() {
|
||||||
|
local domain="$1"
|
||||||
|
local ip="$2"
|
||||||
|
log "Updating httpd_config.xml for domain '$domain' with IP '$ip'..."
|
||||||
|
|
||||||
|
# Create a backup of the current configuration
|
||||||
|
log "Creating timestamped backup of '$CONF_FILE' as '$BACKUP_FILE'..."
|
||||||
|
if sudo cp -a "$CONF_FILE" "$BACKUP_FILE"; then
|
||||||
|
log "✔ Backup created: '$BACKUP_FILE'."
|
||||||
|
else
|
||||||
|
log "❌ ERROR: Failed to create backup of '$CONF_FILE'. Exiting."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update the listener configuration using xmlstarlet
|
||||||
|
log "Adding listener for domain '$domain' with IP '$ip'..."
|
||||||
|
sudo xmlstarlet ed -L \
|
||||||
|
-s "//listenerList" -t elem -n "listener" \
|
||||||
|
-s "//listener[last()]" -t elem -n "name" -v "$domain" \
|
||||||
|
-s "//listener[last()]" -t elem -n "address" -v "$ip:443" \
|
||||||
|
-s "//listener[last()]" -t elem -n "secure" -v "1" \
|
||||||
|
-s "//listener[last()]" -t elem -n "keyFile" -v "/etc/letsencrypt/live/$domain/privkey.pem" \
|
||||||
|
-s "//listener[last()]" -t elem -n "certFile" -v "/etc/letsencrypt/live/$domain/fullchain.pem" \
|
||||||
|
"$CONF_FILE"
|
||||||
|
|
||||||
|
# Validate the updated configuration
|
||||||
|
if validate_xml "$CONF_FILE"; then
|
||||||
|
log "✔ Updated configuration is valid."
|
||||||
|
else
|
||||||
|
log "❌ ERROR: Updated configuration is invalid. Restoring backup..."
|
||||||
|
sudo cp -a "$BACKUP_FILE" "$CONF_FILE"
|
||||||
|
log "✔ Backup restored: '$CONF_FILE'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
restart_litespeed() {
|
restart_litespeed() {
|
||||||
log "Restarting LiteSpeed web server..."
|
log "Restarting LiteSpeed server..."
|
||||||
|
if sudo systemctl restart lsws; then
|
||||||
# Configuration test first
|
log "✔ LiteSpeed server restarted successfully."
|
||||||
if /usr/local/lsws/bin/lshttpd -t 2>&1 | grep -q "Configuration file check failed"; then
|
else
|
||||||
log "ERROR: Configuration test failed, not restarting"
|
log "❌ ERROR: Failed to restart LiteSpeed server. Restoring backup..."
|
||||||
return 1
|
sudo cp -a "$BACKUP_FILE" "$CONF_FILE"
|
||||||
|
log "✔ Backup restored: '$CONF_FILE'."
|
||||||
|
exit 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
systemctl restart lsws
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
if ! systemctl is-active --quiet lsws; then
|
|
||||||
log "ERROR: LiteSpeed failed to start"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "LiteSpeed successfully restarted"
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parse input parameters
|
# === Main Script Logic ===
|
||||||
declare -a DOMAINS
|
log "Starting SSL Manager V2.0.1..."
|
||||||
|
|
||||||
|
# Ensure log directory exists
|
||||||
|
sudo mkdir -p "$LOG_DIR" || { log "❌ ERROR: Cannot create log directory '$LOG_DIR'. Check permissions."; exit 1; }
|
||||||
|
sudo touch "$SCRIPT_LOG" "$XML_ERROR_LOG"
|
||||||
|
sudo chown "$(whoami)":"$(id -gn)" "$SCRIPT_LOG" "$XML_ERROR_LOG" 2>/dev/null || log_verbose "Info: Could not change ownership of log files (may require sudo)."
|
||||||
|
|
||||||
|
# Argument Parsing
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case $arg in
|
case $arg in
|
||||||
--public-ip=*)
|
--public-ip=*)
|
||||||
|
@ -650,102 +222,66 @@ for arg in "$@"; do
|
||||||
--email=*)
|
--email=*)
|
||||||
EMAIL="${arg#*=}"
|
EMAIL="${arg#*=}"
|
||||||
;;
|
;;
|
||||||
|
--verbose)
|
||||||
|
VERBOSE=1
|
||||||
|
log "Verbose mode enabled."
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Invalid argument: $arg"
|
log "Invalid argument: $arg"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Input validation
|
# Input Validation
|
||||||
log "Validating inputs..."
|
if [[ -z "$PRIMARY_DOMAIN" || -z "$PUBLIC_IP" || -z "$EMAIL" ]]; then
|
||||||
if [[ -z "${PUBLIC_IP:-}" || ${#DOMAINS[@]} -eq 0 ]]; then
|
log "❌ ERROR: Missing required parameters. Provide --domains, --public-ip, and --email."
|
||||||
echo "Error: --public-ip and --domain(s) are mandatory."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
validate_ip "$PUBLIC_IP" || { echo "Invalid public IP: $PUBLIC_IP"; exit 1; }
|
|
||||||
for domain in "${DOMAINS[@]}"; do
|
if ! validate_domain "$PRIMARY_DOMAIN"; then
|
||||||
validate_domain "$domain" || { echo "Invalid domain: $domain"; exit 1; }
|
log "❌ ERROR: Invalid domain '$PRIMARY_DOMAIN'."
|
||||||
done
|
exit 1
|
||||||
if [[ -n "${EMAIL:-}" ]]; then
|
|
||||||
validate_email "$EMAIL" || { echo "Invalid email: $EMAIL"; exit 1; }
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Main execution loop
|
if ! validate_ip "$PUBLIC_IP"; then
|
||||||
for DOMAIN in "${DOMAINS[@]}"; do
|
log "❌ ERROR: Invalid IP address '$PUBLIC_IP'."
|
||||||
log "Processing domain: $DOMAIN"
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Validate the domain connection
|
if ! validate_email "$EMAIL"; then
|
||||||
validate_domain_connection
|
log "❌ ERROR: Invalid email address '$EMAIL'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Install Certbot
|
# Validate DNS and HTTP Access
|
||||||
log "Installing Certbot..."
|
if ! validate_dns "$PRIMARY_DOMAIN" "$PUBLIC_IP"; then
|
||||||
if ! command -v certbot > /dev/null; then
|
log "❌ ERROR: DNS validation failed. Exiting."
|
||||||
if [[ -f /etc/debian_version ]]; then
|
exit 1
|
||||||
apt-get update && apt-get install -y certbot
|
fi
|
||||||
elif [[ -f /etc/redhat-release ]]; then
|
|
||||||
# Check if it's AlmaLinux or other RHEL derivatives
|
|
||||||
if grep -q "AlmaLinux" /etc/os-release; then
|
|
||||||
log "Detected AlmaLinux. Installing EPEL repository and Certbot..."
|
|
||||||
# Install EPEL repository first
|
|
||||||
dnf install -y epel-release
|
|
||||||
# Install Certbot and Python modules for the webroot plugin
|
|
||||||
dnf install -y certbot python3-certbot-apache
|
|
||||||
else
|
|
||||||
# Fallback for other RHEL-based systems
|
|
||||||
yum install -y certbot
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Unsupported OS. Install Certbot manually."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for existing certificate before requesting
|
if ! validate_http_access "$PRIMARY_DOMAIN"; then
|
||||||
if [[ -d "/etc/letsencrypt/live/$DOMAIN" ]]; then
|
log "❌ ERROR: HTTP access validation failed. Exiting."
|
||||||
log "Certificate for $DOMAIN already exists. Checking expiry..."
|
exit 1
|
||||||
EXPIRY=$(openssl x509 -enddate -noout -in "/etc/letsencrypt/live/$DOMAIN/cert.pem" | cut -d= -f2)
|
fi
|
||||||
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
|
|
||||||
NOW_EPOCH=$(date +%s)
|
|
||||||
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
|
|
||||||
|
|
||||||
if [[ $DAYS_LEFT -gt 30 ]]; then
|
|
||||||
log "Certificate still valid for $DAYS_LEFT days. Skipping renewal."
|
|
||||||
update_litespeed_config
|
|
||||||
setup_cron_job
|
|
||||||
continue
|
|
||||||
else
|
|
||||||
log "Certificate expires in $DAYS_LEFT days. Proceeding with renewal."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Modify Certbot command to include all domains
|
# Dependency Checks
|
||||||
CERTBOT_CMD="certbot certonly --webroot -w /var/www/webroot/ROOT"
|
log_verbose "Checking dependencies..."
|
||||||
for domain in "${DOMAINS[@]}"; do
|
check_command "xmllint" "libxml2-utils"
|
||||||
CERTBOT_CMD+=" -d $domain"
|
check_command "xmlstarlet" "xmlstarlet"
|
||||||
done
|
check_command "certbot" "certbot"
|
||||||
CERTBOT_CMD+=" --agree-tos --non-interactive"
|
|
||||||
[[ -n "${EMAIL:-}" ]] && CERTBOT_CMD+=" --email $EMAIL"
|
|
||||||
|
|
||||||
# After Certbot installation and before existing certificate check
|
# Create Default Backup
|
||||||
install_xml_tools
|
create_default_backup
|
||||||
|
|
||||||
# Replace the simple reload with the improved function
|
# Issue SSL Certificate
|
||||||
if $CERTBOT_CMD; then
|
issue_certificate "$PRIMARY_DOMAIN" "$EMAIL"
|
||||||
log "SSL certificate issued successfully for $DOMAIN."
|
|
||||||
|
# Update httpd_config.xml
|
||||||
# Update LiteSpeed config with enhanced safety
|
update_httpd_config "$PRIMARY_DOMAIN" "$PUBLIC_IP"
|
||||||
if update_litespeed_config; then
|
|
||||||
restart_litespeed
|
# Restart LiteSpeed
|
||||||
send_email "$DOMAIN SSL Certificate Issued Successfully" "The SSL certificate for $DOMAIN has been successfully installed."
|
restart_litespeed
|
||||||
setup_cron_job
|
|
||||||
else
|
log "✅ SSL Manager completed successfully. Certificate issued and configuration updated."
|
||||||
log "ERROR: Failed to update LiteSpeed configuration. Manually check your configuration."
|
exit 0
|
||||||
send_email "SSL Certificate Installation Warning" "The SSL certificate for $DOMAIN was issued successfully, but there was an error updating the LiteSpeed configuration. Please check your server configuration manually."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "Certbot failed."
|
|
||||||
send_email "SSL Certificate Installation Failed" "An error occurred while installing the SSL certificate for $DOMAIN."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
|
@ -0,0 +1,375 @@
|
||||||
|
#!/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
|
||||||
|
|
||||||
|
# - 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
|
||||||
|
BACKUP_FILE="${LOG_DIR}/httpd_config_$(date +%Y%m%d_%H%M%S).bak" # Timestamped backup
|
||||||
|
XML_ERROR_LOG="${LOG_DIR}/xml-parse-error.log"
|
||||||
|
SCRIPT_LOG="${LOG_DIR}/config_fixer.log"
|
||||||
|
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 ===
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | sudo tee -a "$SCRIPT_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_verbose() {
|
||||||
|
[[ "$VERBOSE" -eq 1 ]] && log "[VERBOSE] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
local apt_pkg="$2"
|
||||||
|
if ! command -v "$cmd" &>/dev/null; then
|
||||||
|
log "⚠ Required command '$cmd' not found. Attempting to install package '$apt_pkg'..."
|
||||||
|
if sudo yum install -y "$apt_pkg"; then
|
||||||
|
log "✔ Successfully installed '$apt_pkg'."
|
||||||
|
else
|
||||||
|
log "❌ ERROR: Failed to install '$apt_pkg'. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "Warning: <virtualHostList> is missing in '$file_to_check'."
|
||||||
|
semantic_errors=$((semantic_errors + 1))
|
||||||
|
fi
|
||||||
|
if ! sudo xmlstarlet sel -t -c "//listenerList" "$file_to_check" &>/dev/null; then
|
||||||
|
log "Warning: <listenerList> is missing in '$file_to_check'."
|
||||||
|
semantic_errors=$((semantic_errors + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate listener certificates
|
||||||
|
local listeners
|
||||||
|
listeners=$(sudo xmlstarlet sel -Q -t -v "//listenerList/listener/name" "$file_to_check" 2>/dev/null || echo "")
|
||||||
|
for listener in $listeners; do
|
||||||
|
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 "")
|
||||||
|
|
||||||
|
# Resolve paths
|
||||||
|
key_file=$(echo "$key_file" | sed "s|\$SERVER_ROOT|$SERVER_ROOT|g")
|
||||||
|
cert_file=$(echo "$cert_file" | sed "s|\$SERVER_ROOT|$SERVER_ROOT|g")
|
||||||
|
|
||||||
|
if [[ -n "$key_file" && ! -e "$key_file" ]]; then
|
||||||
|
log "Warning: Listener '$listener' has invalid keyFile path: '$key_file'"
|
||||||
|
semantic_errors=$((semantic_errors + 1))
|
||||||
|
fi
|
||||||
|
if [[ -n "$cert_file" && ! -e "$cert_file" ]]; then
|
||||||
|
log "Warning: Listener '$listener' has invalid certFile path: '$cert_file'"
|
||||||
|
semantic_errors=$((semantic_errors + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Return result
|
||||||
|
if [[ "$semantic_errors" -eq 0 ]]; then
|
||||||
|
log_verbose "Semantic checks passed for '$file_to_check'."
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log "⚠ Semantic Check: Found $semantic_errors potential issue(s) in '$file_to_check'. Review warnings."
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# === Main Script Logic ===
|
||||||
|
log "Starting XML config check/fix V1.8.0 for $CONF_FILE"
|
||||||
|
sudo mkdir -p "$LOG_DIR" || { log "❌ ERROR: Cannot create log directory '$LOG_DIR'. Check permissions."; exit 1; }
|
||||||
|
sudo touch "$SCRIPT_LOG" "$XML_ERROR_LOG"
|
||||||
|
sudo chown "$(whoami)":"$(id -gn)" "$SCRIPT_LOG" "$XML_ERROR_LOG" 2>/dev/null || log_verbose "Info: Could not change ownership of log files (may require sudo)."
|
||||||
|
|
||||||
|
# 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
|
||||||
|
log_verbose "Running initial xmllint validation on '$CONF_FILE'..."
|
||||||
|
if sudo xmllint --noout "$CONF_FILE" 2>"$XML_ERROR_LOG"; then
|
||||||
|
log "✔ Initial Check: '$CONF_FILE' is valid XML."
|
||||||
|
INITIAL_FILE_VALID=1
|
||||||
|
else
|
||||||
|
log "⚠ Initial Check: '$CONF_FILE' is invalid XML. Will attempt recovery/cleanup."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Recovery Attempt
|
||||||
|
if [[ "$INITIAL_FILE_VALID" -eq 0 ]]; then
|
||||||
|
log "Attempting automatic recovery with 'xmllint --recover'..."
|
||||||
|
sudo rm -f "$RECOVERY_TMP_FILE"
|
||||||
|
if xmllint --recover "$CONF_FILE" 2>/dev/null > "$RECOVERY_TMP_FILE"; then
|
||||||
|
if sudo xmllint --noout "$RECOVERY_TMP_FILE" 2>/dev/null; then
|
||||||
|
log "✔ 'xmllint --recover' produced structurally valid XML output."
|
||||||
|
RECOVERY_PRODUCED_VALID_XML=1
|
||||||
|
else
|
||||||
|
log "⚠ 'xmllint --recover' ran but the output is STILL invalid XML. Discarding recovery."
|
||||||
|
sudo rm -f "$RECOVERY_TMP_FILE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "⚠ 'xmllint --recover' command failed or produced errors. Discarding recovery."
|
||||||
|
sudo rm -f "$RECOVERY_TMP_FILE"
|
||||||
|
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"
|
||||||
|
elif [[ "$RECOVERY_PRODUCED_VALID_XML" -eq 1 ]]; then
|
||||||
|
FINAL_CONFIG_SOURCE_FILE="$RECOVERY_TMP_FILE"
|
||||||
|
FINAL_CONFIG_SOURCE_DESC="recovered"
|
||||||
|
else
|
||||||
|
log "❌ FATAL: Neither original nor recovered 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 "✅ Script completed successfully. Applied: $FINAL_CONFIG_SOURCE_DESC."
|
||||||
|
exit "$FINAL_EXIT_CODE"
|
||||||
|
else
|
||||||
|
log "❌ FATAL: Failed to apply and restart using $FINAL_CONFIG_SOURCE_DESC config."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "❌ FATAL: No valid configuration file found. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# === 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
|
Loading…
Reference in New Issue