Added xmlstarlet installation
parent
1835b91e4b
commit
8533225d00
|
@ -24,7 +24,53 @@ ERROR_LOG="${LOG_DIR}/ssl-remover-error.log"
|
|||
DEBUG_LOG="${LOG_DIR}/ssl-remover-debug.log"
|
||||
VERBOSE=0
|
||||
|
||||
# Validate configuration file exists and is readable
|
||||
if [[ ! -f "$CONF_FILE" ]]; then
|
||||
echo "❌ ERROR: Configuration file '$CONF_FILE' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -r "$CONF_FILE" ]]; then
|
||||
echo "❌ ERROR: Configuration file '$CONF_FILE' is not readable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate required tools are available
|
||||
for tool in xmlstarlet xmllint certbot; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
echo "❌ ERROR: Required tool '$tool' is not installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# === Functions ===
|
||||
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
|
||||
}
|
||||
|
||||
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; }
|
||||
|
@ -116,11 +162,15 @@ backup_config() {
|
|||
local timestamp=$(date +%Y%m%d%H%M%S)
|
||||
local backup_file="${BACKUP_DIR}/httpd_config.pre-removal-${timestamp}.xml"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
# Create backup directory with proper permissions
|
||||
sudo mkdir -p "$BACKUP_DIR" || { log_error "Failed to create backup directory '$BACKUP_DIR'"; exit 2; }
|
||||
sudo chown -R "$(whoami)":"$(id -gn)" "$BACKUP_DIR"
|
||||
sudo chmod 755 "$BACKUP_DIR"
|
||||
|
||||
if cp "$CONF_FILE" "$backup_file"; then
|
||||
log "Config backup saved to $backup_file"
|
||||
else
|
||||
log "❌ ERROR: Failed to create backup '$backup_file'. Exiting."
|
||||
log_error "Failed to create backup '$backup_file'. Exiting."
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
@ -129,21 +179,68 @@ remove_certificate() {
|
|||
local domain="$1"
|
||||
log "Checking for certificate for domain '$domain'..."
|
||||
|
||||
if certbot certificates | grep -q "Domains: $domain"; then
|
||||
log "Found certificate for '$domain'. Proceeding with removal..."
|
||||
if certbot delete --cert-name "$domain" --non-interactive; then
|
||||
# Remove all certificate files
|
||||
rm -rf "/etc/letsencrypt/live/$domain"*
|
||||
rm -rf "/etc/letsencrypt/archive/$domain"*
|
||||
log_success "Certificate successfully removed for '$domain'"
|
||||
log_verbose "Removed certificate files from /etc/letsencrypt/live/$domain and /etc/letsencrypt/archive/$domain"
|
||||
else
|
||||
log_error "Failed to remove certificate for '$domain'"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log "No certificate found for '$domain'. Skipping removal."
|
||||
# Validate domain format first
|
||||
if ! validate_domain "$domain"; then
|
||||
log_error "Invalid domain format: '$domain'"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if certbot is available and working
|
||||
if ! certbot --version >/dev/null 2>&1; then
|
||||
log_error "certbot is not available or not working properly"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check for certificate existence with better error handling
|
||||
if ! certbot certificates | grep -q "Domains: $domain"; then
|
||||
log "No certificate found for '$domain'. Skipping removal."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Found certificate for '$domain'. Proceeding with removal..."
|
||||
|
||||
# Create backup of certificate files before removal
|
||||
local cert_backup_dir="${BACKUP_DIR}/certificates/${domain}_$(date +%Y%m%d%H%M%S)"
|
||||
sudo mkdir -p "$cert_backup_dir"
|
||||
|
||||
if [[ -d "/etc/letsencrypt/live/$domain" ]]; then
|
||||
sudo cp -r "/etc/letsencrypt/live/$domain" "$cert_backup_dir/"
|
||||
fi
|
||||
if [[ -d "/etc/letsencrypt/archive/$domain" ]]; then
|
||||
sudo cp -r "/etc/letsencrypt/archive/$domain" "$cert_backup_dir/"
|
||||
fi
|
||||
|
||||
# Attempt to remove certificate
|
||||
if ! certbot delete --cert-name "$domain" --non-interactive; then
|
||||
log_error "Failed to remove certificate for '$domain'"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Verify certificate removal
|
||||
if certbot certificates | grep -q "Domains: $domain"; then
|
||||
log_error "Certificate removal verification failed for '$domain'"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Remove certificate files with verification
|
||||
local files_removed=0
|
||||
if [[ -d "/etc/letsencrypt/live/$domain" ]]; then
|
||||
sudo rm -rf "/etc/letsencrypt/live/$domain"*
|
||||
((files_removed++))
|
||||
fi
|
||||
if [[ -d "/etc/letsencrypt/archive/$domain" ]]; then
|
||||
sudo rm -rf "/etc/letsencrypt/archive/$domain"*
|
||||
((files_removed++))
|
||||
fi
|
||||
|
||||
if [[ $files_removed -eq 0 ]]; then
|
||||
log "No certificate files found to remove for '$domain'"
|
||||
else
|
||||
log_success "Certificate successfully removed for '$domain'"
|
||||
log_verbose "Removed certificate files from /etc/letsencrypt/live/$domain and /etc/letsencrypt/archive/$domain"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
cleanup_listeners() {
|
||||
|
@ -151,6 +248,17 @@ cleanup_listeners() {
|
|||
local listener_name="HTTPS-$domain"
|
||||
log "Starting cleanup for listener '$listener_name'..."
|
||||
|
||||
# Validate domain format
|
||||
if ! validate_domain "$domain"; then
|
||||
log_error "Invalid domain format: '$domain'"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Create backup directory with proper permissions
|
||||
sudo mkdir -p "$BACKUP_DIR" || { log_error "Failed to create backup directory '$BACKUP_DIR'"; return 1; }
|
||||
sudo chown -R "$(whoami)":"$(id -gn)" "$BACKUP_DIR"
|
||||
sudo chmod 755 "$BACKUP_DIR"
|
||||
|
||||
# First backup the config
|
||||
local backup_file="${BACKUP_DIR}/httpd_config.pre-removal-$(date +%Y%m%d%H%M%S).xml"
|
||||
cp "$CONF_FILE" "$backup_file" || {
|
||||
|
@ -159,15 +267,43 @@ cleanup_listeners() {
|
|||
}
|
||||
log_verbose "Created backup at: $backup_file"
|
||||
|
||||
# Remove the entire listener element with all its children
|
||||
sudo xmlstarlet ed -L \
|
||||
-d "//listenerList/listener[name='$listener_name']" \
|
||||
"$CONF_FILE" || {
|
||||
log_error "Failed to remove listener '$listener_name'"
|
||||
cp "$backup_file" "$CONF_FILE"
|
||||
return 1
|
||||
}
|
||||
log_verbose "Removed listener element: $listener_name"
|
||||
# Verify listener exists before attempting removal
|
||||
if ! awk '
|
||||
/<listener>/ { in_listener=1; buffer="" }
|
||||
in_listener { buffer = buffer $0 ORS }
|
||||
/<\/listener>/ {
|
||||
if (in_listener && buffer ~ /'"$domain"'/) {
|
||||
print buffer
|
||||
}
|
||||
in_listener=0
|
||||
}' "$CONF_FILE" >/dev/null 2>&1; then
|
||||
log "Listener for '$domain' not found in configuration. Skipping removal."
|
||||
rm -f "$backup_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Remove the listener block using xmlstarlet
|
||||
if ! sudo xmlstarlet ed --inplace -d "//listener[contains(., '$domain')]" "$CONF_FILE"; then
|
||||
log_error "Failed to remove listener for '$domain'"
|
||||
cp "$backup_file" "$CONF_FILE"
|
||||
return 1
|
||||
fi
|
||||
log_verbose "Removed listener block for domain: $domain"
|
||||
|
||||
# Verify the listener was actually removed
|
||||
if awk '
|
||||
/<listener>/ { in_listener=1; buffer="" }
|
||||
in_listener { buffer = buffer $0 ORS }
|
||||
/<\/listener>/ {
|
||||
if (in_listener && buffer ~ /'"$domain"'/) {
|
||||
print buffer
|
||||
}
|
||||
in_listener=0
|
||||
}' "$CONF_FILE" >/dev/null 2>&1; then
|
||||
log_error "Listener for '$domain' still exists after removal attempt"
|
||||
cp "$backup_file" "$CONF_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate XML after removal
|
||||
if ! xmllint --noout "$CONF_FILE" 2>/dev/null; then
|
||||
|
@ -177,36 +313,130 @@ cleanup_listeners() {
|
|||
fi
|
||||
log_verbose "XML validation passed after listener removal"
|
||||
|
||||
# Clean up any orphaned vhostMapList elements
|
||||
sudo xmlstarlet ed -L \
|
||||
-d "//vhostMapList[not(parent::listener)]" \
|
||||
"$CONF_FILE" || {
|
||||
log_error "Failed to clean up orphaned vhostMapList"
|
||||
cp "$backup_file" "$CONF_FILE"
|
||||
return 1
|
||||
}
|
||||
log_verbose "Cleaned up orphaned vhostMapList elements"
|
||||
# Set proper permissions on the config file
|
||||
sudo chown litespeed:litespeed "$CONF_FILE"
|
||||
sudo chmod 644 "$CONF_FILE"
|
||||
|
||||
# Validate XML after cleanup
|
||||
if ! xmllint --noout "$CONF_FILE" 2>/dev/null; then
|
||||
log_error "Invalid XML structure after cleanup. Restoring backup..."
|
||||
cp "$backup_file" "$CONF_FILE"
|
||||
log_success "Successfully removed listener for '$domain'"
|
||||
rm -f "$backup_file"
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_cleanup_state() {
|
||||
local domain="$1"
|
||||
local listener_name="HTTPS-$domain"
|
||||
|
||||
# Check certificate state
|
||||
local cert_exists=0
|
||||
if certbot certificates | grep -q "Domains: $domain"; then
|
||||
cert_exists=1
|
||||
fi
|
||||
|
||||
# Check listener state - improved check
|
||||
local listener_exists=0
|
||||
if xmlstarlet sel -t -c "//listenerList/listener[name='$listener_name']" "$CONF_FILE" >/dev/null 2>&1; then
|
||||
listener_exists=1
|
||||
fi
|
||||
|
||||
# Log current state
|
||||
log_verbose "Current state for domain '$domain':"
|
||||
log_verbose "Certificate exists: $cert_exists"
|
||||
log_verbose "Listener exists: $listener_exists"
|
||||
|
||||
# Return state code
|
||||
echo "$cert_exists$listener_exists"
|
||||
}
|
||||
|
||||
validate_xml_structure() {
|
||||
local file="$1"
|
||||
|
||||
# Check for basic XML structure
|
||||
if ! xmllint --noout "$file" 2>/dev/null; then
|
||||
log_error "Invalid XML structure in $file"
|
||||
return 1
|
||||
fi
|
||||
log_verbose "XML validation passed after cleanup"
|
||||
|
||||
# Check for required root elements
|
||||
if ! xmlstarlet sel -t -v "//httpServerConfig" "$file" >/dev/null 2>&1; then
|
||||
log_error "Missing httpServerConfig root element in $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check for required sections
|
||||
local required_sections=("listenerList" "virtualHostList")
|
||||
for section in "${required_sections[@]}"; do
|
||||
if ! xmlstarlet sel -t -v "//$section" "$file" >/dev/null 2>&1; then
|
||||
log_error "Missing required section '$section' in $file"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for orphaned elements
|
||||
if xmlstarlet sel -t -v "//vhostMapList[not(parent::listener)]" "$file" >/dev/null 2>&1; then
|
||||
log_error "Found orphaned vhostMapList elements in $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
log_success "Successfully removed listener '$listener_name' and cleaned up XML structure"
|
||||
rm -f "$backup_file"
|
||||
cleanup_xml() {
|
||||
local file="$1"
|
||||
local temp_file="${file}.tmp"
|
||||
|
||||
# Create temporary copy
|
||||
cp "$file" "$temp_file" || {
|
||||
log_error "Failed to create temporary file for XML cleanup"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Remove orphaned elements
|
||||
if ! xmlstarlet ed -L \
|
||||
-d "//vhostMapList[not(parent::listener)]" \
|
||||
-d "//listener[not(.//vhostMapList)]" \
|
||||
"$temp_file"; then
|
||||
log_error "Failed to clean up orphaned elements"
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate cleaned XML
|
||||
if ! validate_xml_structure "$temp_file"; then
|
||||
log_error "Invalid XML structure after cleanup"
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Move temporary file to actual config file
|
||||
if ! mv "$temp_file" "$file"; then
|
||||
log_error "Failed to update configuration file"
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Set proper permissions
|
||||
sudo chown litespeed:litespeed "$file"
|
||||
sudo chmod 644 "$file"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_xml() {
|
||||
log "Validating XML configuration..."
|
||||
if ! sudo xmllint --noout "$CONF_FILE" 2>/dev/null; then
|
||||
log "❌ ERROR: Invalid XML configuration after cleanup. Check backups."
|
||||
|
||||
# First check with xmlstarlet
|
||||
if ! sudo xmlstarlet val --well-formed "$CONF_FILE" >/dev/null 2>&1; then
|
||||
log "❌ ERROR: XML configuration is not well-formed according to xmlstarlet. Check backups."
|
||||
return 1
|
||||
fi
|
||||
log "✔ XML configuration is valid."
|
||||
|
||||
# Then check with xmllint for additional validation
|
||||
if ! sudo xmllint --noout "$CONF_FILE" 2>/dev/null; then
|
||||
log "❌ ERROR: XML configuration is invalid according to xmllint. Check backups."
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "✔ XML configuration is valid (verified by both xmlstarlet and xmllint)."
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -229,6 +459,12 @@ main() {
|
|||
setup_logging
|
||||
log "Starting SSL Removal Process"
|
||||
|
||||
# Check and install required dependencies
|
||||
log_verbose "Checking dependencies..."
|
||||
check_command "xmlstarlet" "xmlstarlet"
|
||||
check_command "xmllint" "libxml2-utils"
|
||||
check_command "certbot" "certbot"
|
||||
|
||||
# Parse parameters
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
|
@ -268,18 +504,51 @@ main() {
|
|||
continue
|
||||
fi
|
||||
|
||||
# Remove certificate first
|
||||
if ! remove_certificate "$domain"; then
|
||||
log "⚠ Warning: Failed to remove certificate for '$domain'. Continuing with cleanup..."
|
||||
fi
|
||||
|
||||
# Clean up listeners and configurations
|
||||
if ! cleanup_listeners "$domain"; then
|
||||
log_error "Failed to clean up listeners for '$domain'"
|
||||
continue
|
||||
fi
|
||||
# Check current state
|
||||
local state=$(verify_cleanup_state "$domain")
|
||||
log_verbose "Current state code: $state"
|
||||
|
||||
case "$state" in
|
||||
"00") # No certificate, no listener
|
||||
log "Domain '$domain' is already clean. No action needed."
|
||||
continue
|
||||
;;
|
||||
"01") # No certificate, listener exists
|
||||
log "Found orphaned listener for '$domain'. Removing..."
|
||||
if ! cleanup_listeners "$domain"; then
|
||||
log_error "Failed to remove orphaned listener for '$domain'"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
"10") # Certificate exists, no listener
|
||||
log "Found orphaned certificate for '$domain'. Removing..."
|
||||
if ! remove_certificate "$domain"; then
|
||||
log_error "Failed to remove orphaned certificate for '$domain'"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
"11") # Both certificate and listener exist
|
||||
log "Found both certificate and listener for '$domain'. Removing both..."
|
||||
if ! remove_certificate "$domain"; then
|
||||
log "⚠ Warning: Failed to remove certificate for '$domain'. Continuing with listener cleanup..."
|
||||
fi
|
||||
if ! cleanup_listeners "$domain"; then
|
||||
log_error "Failed to clean up listeners for '$domain'"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate and clean up XML structure
|
||||
if ! validate_xml_structure "$CONF_FILE"; then
|
||||
log "Cleaning up XML structure..."
|
||||
if ! cleanup_xml "$CONF_FILE"; then
|
||||
log_error "Failed to clean up XML structure"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate final XML configuration
|
||||
if validate_xml; then
|
||||
restart_litespeed
|
||||
|
|
|
@ -103,15 +103,28 @@ log_success() {
|
|||
|
||||
check_command() {
|
||||
local cmd="$1"
|
||||
local apt_pkg="$2"
|
||||
local 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
|
||||
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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue