#!/bin/bash set -euo pipefail # Log file setup LOG_DIR="/var/log/mb-ssl" LOG_FILE="$LOG_DIR/ssl-manager.log" mkdir -p "$LOG_DIR" chmod 0755 "$LOG_DIR" exec > >(tee -a "$LOG_FILE") 2>&1 # Function to log messages log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" } # Function to send email via Postmark 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 successfully." || log "Failed to send email." else log "Email not provided. Skipping email notification." fi } # Function to validate IP address validate_ip() { local ip=$1 [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1 } # Function to validate domain 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() { 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; 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 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" log "Updating LiteSpeed configuration..." if grep -q "$DOMAIN" "$config_file"; then log "Domain $DOMAIN already exists in the configuration. Updating..." sed -i "//,/<\/listener>/s|.*|$key_file|" "$config_file" sed -i "//,/<\/listener>/s|.*|$cert_file|" "$config_file" else log "Domain $DOMAIN not found in configuration. Adding new listener..." sed -i "/<\/listenerList>/i\ \ HTTPS-$DOMAIN\
*:443
\ 1\ $key_file\ $cert_file\ 1\
" "$config_file" fi log "LiteSpeed configuration updated successfully." } # 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 } # Parse input parameters for arg in "$@"; do case $arg in --public-ip=*) PUBLIC_IP="${arg#*=}" ;; --domain=*) DOMAIN="${arg#*=}" ;; --email=*) EMAIL="${arg#*=}" ;; *) echo "Invalid argument: $arg" exit 1 ;; esac done # Input validation log "Validating inputs..." if [[ -z "${PUBLIC_IP:-}" || -z "${DOMAIN:-}" ]]; then echo "Error: --public-ip and --domain are mandatory." exit 1 fi validate_ip "$PUBLIC_IP" || { echo "Invalid public IP: $PUBLIC_IP"; exit 1; } validate_domain "$DOMAIN" || { echo "Invalid domain: $DOMAIN"; exit 1; } if [[ -n "${EMAIL:-}" ]]; then validate_email "$EMAIL" || { echo "Invalid email: $EMAIL"; exit 1; } fi # Validate the domain connection validate_domain_connection # Install Certbot log "Installing Certbot..." 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 yum install -y certbot else echo "Unsupported OS. Install Certbot manually." exit 1 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" if $CERTBOT_CMD; then log "SSL certificate issued successfully for $DOMAIN." update_litespeed_config sudo systemctl reload lsws send_email "$DOMAIN SSL Certificate Issued Successfully" "The SSL certificate for $DOMAIN has been successfully installed." setup_cron_job else log "Certbot failed." send_email "SSL Certificate Installation Failed" "An error occurred while installing the SSL certificate for $DOMAIN." exit 1 fi