227 lines
7.0 KiB
Bash
227 lines
7.0 KiB
Bash
|
#!/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>/,/<\/listener>/s|<keyFile>.*</keyFile>|<keyFile>$key_file</keyFile>|" "$config_file"
|
||
|
sed -i "/<listener>/,/<\/listener>/s|<certFile>.*</certFile>|<certFile>$cert_file</certFile>|" "$config_file"
|
||
|
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"
|
||
|
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
|