Fix SSL 404 issues

main
TonyGarces 2025-03-21 02:07:43 +08:00
parent 795374738b
commit 83b158d7e0
1 changed files with 439 additions and 21 deletions

View File

@ -108,30 +108,67 @@ validate_domain_connection() {
fi
}
# Function to update LiteSpeed configuration
# 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..."
if grep -q "$DOMAIN" "$config_file"; then
log "Domain $DOMAIN already exists in the configuration. Updating..."
sed -i "/<listener>/,/<\/listener>/s|<keyFile>.*</keyFile>|<keyFile>$key_file</keyFile>|" "$config_file"
sed -i "/<listener>/,/<\/listener>/s|<certFile>.*</certFile>|<certFile>$cert_file</certFile>|" "$config_file"
# Ensure we have a backup with timestamp
cp "$config_file" "$backup_file"
log "Created backup of LiteSpeed configuration at $backup_file"
# First, clean up any redundant listeners for this domain
cleanup_redundant_listeners "$config_file" "$DOMAIN"
# After cleaning up redundant listeners but before adding domain mappings,
# create a virtual host for the domain
create_domain_virtual_host "$DOMAIN"
# Check if the required listeners exist and contain correct cert paths
local need_cert_update=false
local has_domain_listener=false
# Check if domain exists in HTTPS and HTTPS-ipv6 listeners with correct cert paths
if grep -A30 "<name>HTTPS</name>" "$config_file" | grep -q "<domain>$DOMAIN</domain>" && \
grep -A30 "<name>HTTPS-ipv6</name>" "$config_file" | grep -q "<domain>$DOMAIN</domain>"; then
# Domain exists in both listeners, check cert paths
if ! grep -A30 "<name>HTTPS</name>" "$config_file" | grep -q "<keyFile>$key_file</keyFile>" || \
! grep -A30 "<name>HTTPS-ipv6</name>" "$config_file" | grep -q "<keyFile>$key_file</keyFile>"; then
need_cert_update=true
log "Certificate paths need updating"
else
log "Domain $DOMAIN not found in configuration. Adding new listener..."
sed -i "/<\/listenerList>/i\
<listener>\
<name>HTTPS-$DOMAIN</name>\
<address>*:443</address>\
<secure>1</secure>\
<keyFile>$key_file</keyFile>\
<certFile>$cert_file</certFile>\
<certChain>1</certChain>\
</listener>" "$config_file"
log "Domain mappings and certificate paths are already correct"
fi
log "LiteSpeed configuration updated successfully."
else
log "Domain mappings need to be added"
fi
# Update certificate paths if needed
if [ "$need_cert_update" = true ]; then
log "Updating certificate paths..."
# Update keyFile and certFile for all listeners that match our domain
sed -i "/<listener>/,/<\/listener>/ s|<keyFile>.*letsencrypt/live/$DOMAIN/.*</keyFile>|<keyFile>$key_file</keyFile>|g" "$config_file"
sed -i "/<listener>/,/<\/listener>/ s|<certFile>.*letsencrypt/live/$DOMAIN/.*</certFile>|<certFile>$cert_file</certFile>|g" "$config_file"
fi
# Add domain mapping to listeners (modified function will use the correct virtual host)
add_domain_mapping "HTTPS"
add_domain_mapping "HTTPS-ipv6"
# 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 proper domain-to-virtualhost mapping."
return 0
}
# Function to set up automatic renewal
@ -163,6 +200,336 @@ setup_cron_job() {
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 if xmllint is available
if ! command -v xmllint >/dev/null 2>&1; then
log "WARNING: xmllint not available. Skipping XML validation."
return 0 # Return success and continue
fi
# Create a temporary validation copy (don't modify the original yet)
local validate_file=$(mktemp)
if [ ! -f "$validate_file" ]; then
log "Error: Failed to create temporary file for validation."
return 0 # Continue without validation rather than failing
fi
# Copy the file - don't try to fix formatting
cp "$config_file" "$validate_file"
# Try basic validation first
if xmllint --noout "$validate_file" 2>/dev/null; then
log "XML configuration validation passed."
rm -f "$validate_file"
return 0
fi
# Validation failed - attempt a simple check to see if main tags are balanced
local open_http=$(grep -c "<httpServerConfig>" "$config_file")
local close_http=$(grep -c "</httpServerConfig>" "$config_file")
local open_listeners=$(grep -c "<listener>" "$config_file")
local close_listeners=$(grep -c "</listener>" "$config_file")
if [ "$open_http" -eq "$close_http" ] && [ "$open_listeners" -eq "$close_listeners" ]; then
log "WARNING: XML syntax validation failed but basic structure seems intact. Proceeding with caution."
rm -f "$validate_file"
return 0 # Continue anyway - LiteSpeed may be more forgiving than xmllint
fi
# If we reach here, validation failed and basic structure check failed
log "ERROR: XML validation failed. Configuration file may be corrupted."
log "Found $open_http opening and $close_http closing httpServerConfig tags"
log "Found $open_listeners opening and $close_listeners closing listener tags"
rm -f "$validate_file"
if [ -f "$backup_file" ]; then
log "Restoring from backup..."
cp "$backup_file" "$config_file"
log "Backup restored. Please check your configuration manually."
fi
return 1
}
# 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
log "Error: Generated configuration is invalid. Keeping original."
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
}
# New function to create or update domain-specific virtual host
create_domain_virtual_host() {
local domain="$1"
local config_file="/var/www/conf/httpd_config.xml"
local vhost_name="${domain//./_}" # Replace dots with underscores for uniqueness
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
fi
log "Creating virtual host for $domain..."
local temp_file=$(mktemp)
# Create the virtual host definition
local vhost_config="<virtualHost>\n <name>$vhost_name</name>\n <vhRoot>/var/www/webroot/</vhRoot>\n <configFile>\$SERVER_ROOT/conf/vhconf.xml</configFile>\n <allowSymbolLink>1</allowSymbolLink>\n <enableScript>1</enableScript>\n <restrained>1</restrained>\n <setUIDMode>0</setUIDMode>\n <chrootMode>0</chrootMode>\n</virtualHost>"
# Insert the virtual host before the end of virtualHostList tag
awk -v vhost="$vhost_config" '
/<\/virtualHostList>/ { print " " vhost; }
{ print }
' "$config_file" > "$temp_file"
# Check if file looks valid
if [ -s "$temp_file" ] && grep -q "<httpServerConfig>" "$temp_file" && grep -q "</httpServerConfig>" "$temp_file"; then
cp "$temp_file" "$config_file"
log "Virtual host for $domain created successfully."
else
log "Error: Generated configuration appears invalid. Virtual host not created."
rm -f "$temp_file"
return 1
fi
rm -f "$temp_file"
return 0
}
# Add a new function to create a domain-specific HTTPS listener
create_domain_listener() {
local domain="$1"
local key_file="/etc/letsencrypt/live/$domain/privkey.pem"
local cert_file="/etc/letsencrypt/live/$domain/fullchain.pem"
local config_file="/var/www/conf/httpd_config.xml"
local vhost_name="${domain%%.*}"
log "Creating domain-specific HTTPS listener for $domain..."
# Check if listener already exists
if grep -q "<name>HTTPS-$domain</name>" "$config_file"; then
log "HTTPS listener for $domain already exists, updating certificate paths..."
sed -i "/<name>HTTPS-$domain<\/name>/,/<\/listener>/s|<keyFile>.*</keyFile>|<keyFile>$key_file</keyFile>|" "$config_file"
sed -i "/<name>HTTPS-$domain<\/name>/,/<\/listener>/s|<certFile>.*</certFile>|<certFile>$cert_file</certFile>|" "$config_file"
return 0
fi
# Create a new listener with domain-specific settings
local temp_file=$(mktemp)
# Insert the new listener before the end of listenerList
awk -v domain="$domain" -v vhost="$vhost_name" -v key="$key_file" -v cert="$cert_file" '
/<\/listenerList>/ {
print " <listener>"
print " <name>HTTPS-" domain "</name>"
print " <address>*:443</address>"
print " <secure>1</secure>"
print " <vhostMapList>"
print " <vhostMap>"
print " <vhost>" vhost "</vhost>"
print " <domain>" domain "</domain>"
print " </vhostMap>"
print " </vhostMapList>"
print " <keyFile>" key "</keyFile>"
print " <certFile>" cert "</certFile>"
print " <certChain>1</certChain>"
print " <sslProtocol>24</sslProtocol>"
print " <ciphers>ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384</ciphers>"
print " <sslSessionCache>1</sslSessionCache>"
print " <sslSessionTickets>1</sslSessionTickets>"
print " <enableSpdy>15</enableSpdy>"
print " </listener>"
}
{ print }
' "$config_file" > "$temp_file"
mv "$temp_file" "$config_file"
log "Domain-specific HTTPS listener created for $domain."
}
# Parse input parameters
for arg in "$@"; do
case $arg in
@ -203,22 +570,73 @@ if ! command -v certbot > /dev/null; then
if [[ -f /etc/debian_version ]]; then
apt-get update && apt-get install -y certbot
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 [[ -d "/etc/letsencrypt/live/$DOMAIN" ]]; then
log "Certificate for $DOMAIN already exists. Checking expiry..."
EXPIRY=$(openssl x509 -enddate -noout -in "/etc/letsencrypt/live/$DOMAIN/cert.pem" | cut -d= -f2)
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
exit 0
else
log "Certificate expires in $DAYS_LEFT days. Proceeding with renewal."
fi
fi
# Issue SSL certificate
CERTBOT_CMD="certbot certonly --webroot -w /var/www/webroot/ROOT -d $DOMAIN --agree-tos --non-interactive"
[[ -n "${EMAIL:-}" ]] && CERTBOT_CMD+=" --email $EMAIL"
# Improved LiteSpeed service handling
restart_litespeed() {
log "Restarting LiteSpeed web server..."
if systemctl is-active --quiet lsws; then
systemctl reload lsws || systemctl restart lsws
log "LiteSpeed successfully restarted."
else
systemctl start lsws
log "LiteSpeed was not running. Started the service."
fi
}
# After Certbot installation and before existing certificate check
install_xml_tools
# Replace the simple reload with the improved function
if $CERTBOT_CMD; then
log "SSL certificate issued successfully for $DOMAIN."
update_litespeed_config
sudo systemctl reload lsws
# Update LiteSpeed config with enhanced safety
if update_litespeed_config; then
restart_litespeed
send_email "$DOMAIN SSL Certificate Issued Successfully" "The SSL certificate for $DOMAIN has been successfully installed."
setup_cron_job
else
log "ERROR: Failed to update LiteSpeed configuration. Manually check your configuration."
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."