mb-admin/scripts/ssl-manager/ssl_remover.sh

296 lines
9.4 KiB
Bash

#!/bin/bash
# ==============================================================================
# Script Name: ssl_remover.sh
# Description: Removes SSL certificates and cleans up LiteSpeed configurations.
# Ensures safe removal of listeners, virtual hosts, and certificates.
# Version: 2.0.0 (Professional-Grade Optimized)
# Author: Anthony Garces (tony@mightybox.io)
# Date: 2025-03-26
# Exit Codes:
# 0: Success (SSL removed and configuration cleaned up)
# 1: General Error (e.g., invalid parameters, XML validation failed)
# 2: Backup/Restore Error (e.g., failed to create backup)
# 3: Restart Error (e.g., LiteSpeed service failed to restart)
# ==============================================================================
set -euo pipefail
# === Configuration ===
CONF_FILE="/var/www/conf/httpd_config.xml"
BACKUP_DIR="/var/www/conf/backups"
LOG_DIR="/var/log/mb-ssl"
CERT_DIR="/etc/letsencrypt/live"
SCRIPT_LOG="${LOG_DIR}/ssl-remover.log"
ERROR_LOG="${LOG_DIR}/ssl-remover-error.log"
DEBUG_LOG="${LOG_DIR}/ssl-remover-debug.log"
VERBOSE=0
# === Functions ===
setup_logging() {
# Create log directory if it doesn't exist
sudo mkdir -p "$LOG_DIR" || { echo "❌ ERROR: Cannot create log directory '$LOG_DIR'. Check permissions."; exit 1; }
# Set proper permissions
sudo chown -R "$(whoami)":"$(id -gn)" "$LOG_DIR"
sudo chmod 755 "$LOG_DIR"
# Create log files with proper permissions
touch "$SCRIPT_LOG" "$ERROR_LOG" "$DEBUG_LOG"
chmod 644 "$SCRIPT_LOG" "$ERROR_LOG" "$DEBUG_LOG"
# Add log rotation if log files are too large (>10MB)
for log_file in "$SCRIPT_LOG" "$ERROR_LOG" "$DEBUG_LOG"; do
if [ -f "$log_file" ] && [ "$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file")" -gt 10485760 ]; then
mv "$log_file" "${log_file}.$(date +%Y%m%d)"
touch "$log_file"
chmod 644 "$log_file"
gzip "${log_file}.$(date +%Y%m%d)"
fi
done
}
log() {
local level="INFO"
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Log to main log file
echo "[$timestamp] [$level] $message" | tee -a "$SCRIPT_LOG"
# Log errors to error log file
if [[ "$message" == *"ERROR"* ]] || [[ "$message" == *"❌"* ]]; then
echo "[$timestamp] [$level] $message" >> "$ERROR_LOG"
fi
}
log_verbose() {
if [[ "$VERBOSE" -eq 1 ]]; then
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [DEBUG] $1" | tee -a "$DEBUG_LOG"
fi
}
log_error() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [ERROR] $message" | tee -a "$ERROR_LOG" "$SCRIPT_LOG"
}
log_success() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [SUCCESS] $message" | tee -a "$SCRIPT_LOG"
}
send_email() {
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." || log "Email failed."
fi
}
validate_domain() {
local domain="$1"
if [[ "$domain" =~ ^([a-zA-Z0-9](-*[a-zA-Z0-9])*\.)+[a-zA-Z]{2,}$ ]]; then
return 0
else
return 1
fi
}
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"
if cp "$CONF_FILE" "$backup_file"; then
log "Config backup saved to $backup_file"
else
log "❌ ERROR: Failed to create backup '$backup_file'. Exiting."
exit 2
fi
}
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."
fi
}
cleanup_listeners() {
local domain="$1"
local listener_name="HTTPS-$domain"
log "Starting cleanup for listener '$listener_name'..."
# 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" || {
log_error "Failed to create backup before cleanup"
return 1
}
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"
# Validate XML after removal
if ! xmllint --noout "$CONF_FILE" 2>/dev/null; then
log_error "Invalid XML structure after listener removal. Restoring backup..."
cp "$backup_file" "$CONF_FILE"
return 1
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"
# 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"
return 1
fi
log_verbose "XML validation passed after cleanup"
log_success "Successfully removed listener '$listener_name' and cleaned up XML structure"
rm -f "$backup_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."
return 1
fi
log "✔ XML configuration is valid."
return 0
}
restart_litespeed() {
log "Restarting LiteSpeed server..."
if sudo systemctl restart lsws; then
log "✔ LiteSpeed server restarted successfully."
else
log "❌ ERROR: Failed to restart LiteSpeed server."
return 3
fi
}
# === Main Script Logic ===
main() {
declare -a DOMAINS
EMAIL=""
# Setup logging first
setup_logging
log "Starting SSL Removal Process"
# Parse parameters
while [[ $# -gt 0 ]]; do
case "$1" in
--domains=*)
IFS=',' read -ra DOMAINS <<< "${1#*=}"
log_verbose "Parsed domains: ${DOMAINS[*]}"
;;
--email=*)
EMAIL="${1#*=}"
log_verbose "Set email notification to: $EMAIL"
;;
--verbose)
VERBOSE=1
log "Verbose mode enabled"
;;
*)
log_error "Invalid parameter: $1"
exit 1
;;
esac
shift
done
# Validate input
if [[ ${#DOMAINS[@]} -eq 0 ]]; then
log_error "--domains parameter is required"
exit 1
fi
# Process each domain
for domain in "${DOMAINS[@]}"; do
log "Processing domain: $domain"
# Validate domain format
if ! validate_domain "$domain"; then
log_error "Invalid domain '$domain'. Skipping."
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
done
# Validate final XML configuration
if validate_xml; then
restart_litespeed
log_success "SSL Removal completed successfully for domains: ${DOMAINS[*]}"
send_email "SSL Removal Complete" "Successfully removed SSL for domains: ${DOMAINS[*]}"
else
log_error "SSL removed but configuration validation failed for domains: ${DOMAINS[*]}"
send_email "SSL Removal Warning" "SSL removed but configuration validation failed for domains: ${DOMAINS[*]}"
exit 1
fi
}
# === Entry Point ===
main "$@"