Files
acme_renew/update_acme_cert.sh
jeremygan2021 beb3273a08 power
2026-03-23 00:11:03 +08:00

722 lines
25 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
#
# 脚本名称: update_acme_cert.sh
# 功能: 自动检测并更新 ACME SSL 证书(增强版)
# 作者: Assistant
# 日期: 2026-03-23
# 用法:
# sudo ./update_acme_cert.sh
#
# 交互式输入:
# - 域名 (例如: example.com)
#
# 新增增强功能:
# 1. 自动安装/升级 acme.sh如未安装
# 2. 自动备份旧证书配置
# 3. nginx 配置冲突智能清理
# 4. 域名可达性自动检测
# 5. 证书更新失败自动重试最多3次指数退避
# 6. 多维度最终验证HTTPS访问、HTTP跳转、SSL证书
# 7. 详细的诊断建议和排查步骤
#
# 前提条件:
# - 需要 root 权限运行
# - 需要安装 nginx
# - 需要开放 80 和 443 端口
# - 域名需正确解析到本服务器
#
# sudo chmod +x update_acme_cert.sh
# ================= 配置区域 =================
ACME_SH="/root/.acme.sh/acme.sh"
NGINX_CONF_DIR="/etc/nginx/conf.d"
WEBROOT="/var/www/letsencrypt"
TLS_BASE_DIR="/etc/nginx/tls"
LOG_FILE="/var/log/acme_update.log"
EMAIL="admin@$(hostname -f)"
# ===========================================
# 彩色输出(方便阅读)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 日志函数 - 中文输出
log_info() { echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"; }
log_warn() { echo -e "${YELLOW}[警告]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"; }
log_error() { echo -e "${RED}[错误]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"; }
log_step() { echo -e "${BLUE}[步骤]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"; }
log_success() { echo -e "${CYAN}[完成]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"; }
# 检查是否 root 权限
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "请使用 root 权限运行此脚本"
exit 1
fi
}
# 检查并安装 acme.sh
install_acme_sh() {
log_step "检查 acme.sh 安装状态..."
if [[ -f "$ACME_SH" ]]; then
log_success "acme.sh 已安装: $ACME_SH"
local version=$("$ACME_SH" --version 2>/dev/null | head -n1)
log_info "当前版本: $version"
echo ""
read -p "$(echo -e "${YELLOW}[确认]${NC} 是否升级 acme.sh 到最新版本? [y/N]: ")" upgrade
if [[ "$upgrade" =~ ^[Yy]$ ]]; then
log_info "正在升级 acme.sh..."
if "$ACME_SH" --upgrade --auto-upgrade; then
log_success "acme.sh 升级成功"
else
log_warn "升级失败,继续使用当前版本"
fi
fi
else
log_warn "未检测到 acme.sh开始自动安装..."
log_info "执行安装命令: curl https://get.acme.sh | sh -s email=$EMAIL"
if curl https://get.acme.sh | sh -s email=$EMAIL; then
log_success "acme.sh 安装成功"
if [[ -f "$ACME_SH" ]]; then
log_info "验证安装: $ACME_SH"
else
log_error "acme.sh 安装验证失败"
exit 1
fi
else
log_error "acme.sh 安装失败,请检查网络连接"
exit 1
fi
fi
log_info "设置默认 CA 为 Let's Encrypt..."
"$ACME_SH" --set-default-ca --server letsencrypt >/dev/null 2>&1 || true
}
# 备份旧配置(幂等性支持)
backup_old_configs() {
log_step "检查并备份旧配置..."
local backup_count=0
local timestamp=$(date +%s)
# 备份证书目录
if [[ -d "$CERT_DIR" ]] && [[ -n "$(ls -A $CERT_DIR 2>/dev/null)" ]]; then
local backup_dir="${CERT_DIR}.bak-${timestamp}"
cp -r "$CERT_DIR" "$backup_dir"
log_info "已备份证书目录: $backup_dir"
backup_count=$((backup_count + 1))
fi
if [[ $backup_count -eq 0 ]]; then
log_info "未发现旧配置,无需备份"
else
log_success "共备份 $backup_count 个旧配置"
fi
}
# 输入域名并验证
input_domain() {
echo ""
read -p "$(echo -e "${BLUE}[输入]${NC} 请输入要更新的域名例如example.com: ")" DOMAIN
if [[ -z "$DOMAIN" ]]; then
log_error "域名不能为空"
exit 1
fi
log_info "目标域名: $DOMAIN"
}
# 检查 nginx 配置文件是否存在
check_nginx_conf() {
CONF_FILE="${NGINX_CONF_DIR}/${DOMAIN}.conf"
if [[ ! -f "$CONF_FILE" ]]; then
log_warn "未找到配置文件: $CONF_FILE"
# 尝试模糊匹配
CONF_FILE=$(grep -l "server_name.*$DOMAIN" ${NGINX_CONF_DIR}/*.conf 2>/dev/null | head -n1)
if [[ -z "$CONF_FILE" ]]; then
log_error "$NGINX_CONF_DIR 中未找到包含域名 $DOMAIN 的 nginx 配置"
exit 1
fi
log_info "通过模糊匹配找到配置文件: $CONF_FILE"
else
log_info "找到配置文件: $CONF_FILE"
fi
}
# 从 nginx 配置中提取证书路径
extract_cert_path() {
log_step "解析配置文件,提取证书路径..."
# 提取 ssl_certificate 路径
CERT_PATH=$(grep -E "^\s*ssl_certificate\s+" "$CONF_FILE" | head -n1 | awk '{print $2}' | tr -d ';')
KEY_PATH=$(grep -E "^\s*ssl_certificate_key\s+" "$CONF_FILE" | head -n1 | awk '{print $2}' | tr -d ';')
if [[ -z "$CERT_PATH" || -z "$KEY_PATH" ]]; then
log_warn "未在配置中找到 ssl_certificate 或 ssl_certificate_key 指令"
# 使用默认路径
CERT_DIR="${TLS_BASE_DIR}/${DOMAIN}"
CERT_FILE="${CERT_DIR}/cert.pem"
KEY_FILE="${CERT_DIR}/key.pem"
log_info "使用默认证书目录: $CERT_DIR"
else
CERT_DIR=$(dirname "$CERT_PATH")
CERT_FILE="$CERT_PATH"
KEY_FILE="$KEY_PATH"
log_info "证书文件: $CERT_FILE"
log_info "密钥文件: $KEY_FILE"
fi
# 确保目录存在
mkdir -p "$CERT_DIR"
}
# 清理 nginx 配置冲突
cleanup_nginx_conflicts() {
log_step "清理 nginx 配置冲突..."
local cleaned=0
# 1. 清理所有包含此域名的配置文件
log_info "搜索包含 ${DOMAIN} 的配置文件..."
local conflict_files=$(grep -rl "server_name.*${DOMAIN}" /etc/nginx/ 2>/dev/null | grep -v ".bak-" | grep -v ".bak/")
if [[ -n "$conflict_files" ]]; then
echo "$conflict_files" | while read file; do
log_warn "发现冲突配置: $file"
local count=$(grep -c "server_name" "$file" 2>/dev/null || echo 0)
if [[ $count -gt 1 ]]; then
log_warn "文件包含多个域名,仅移除此域名的配置"
log_error "需要手动处理: $file"
elif [[ $count -eq 1 ]]; then
local backup="${file}.bak-conflict-$(date +%s)"
mv "$file" "$backup"
log_info "已备份并移除冲突配置: $backup"
cleaned=$((cleaned + 1))
fi
done
fi
# 2. 清理 sites-enabled 中的符号链接
if [[ -L "/etc/nginx/sites-enabled/${DOMAIN}.conf" ]]; then
rm -f "/etc/nginx/sites-enabled/${DOMAIN}.conf"
log_info "已删除符号链接: /etc/nginx/sites-enabled/${DOMAIN}.conf"
cleaned=$((cleaned + 1))
fi
# 3. 清理 sites-available 中的配置
if [[ -f "/etc/nginx/sites-available/${DOMAIN}.conf" ]]; then
rm -f "/etc/nginx/sites-available/${DOMAIN}.conf"
log_info "已删除配置: /etc/nginx/sites-available/${DOMAIN}.conf"
cleaned=$((cleaned + 1))
fi
# 4. 清理 conf.d 中所有相关配置
local confd_files=$(find /etc/nginx/conf.d/ -name "*${DOMAIN}*.conf" -type f 2>/dev/null)
echo "$confd_files" | while read file; do
if [[ "$file" != "$CONF_FILE" ]]; then
local backup="${file}.bak-old-$(date +%s)"
mv "$file" "$backup"
log_info "已备份旧配置: $backup"
cleaned=$((cleaned + 1))
fi
done
if [[ $cleaned -eq 0 ]]; then
log_info "未发现配置冲突"
else
log_success "已清理 $cleaned 个冲突配置"
log_warn "建议运行: systemctl restart nginx"
fi
}
# 检测域名 80 端口可达性
check_domain_reachability() {
log_step "检测域名 80 端口可达性..."
local test_url="http://${DOMAIN}/.well-known/acme-challenge/test"
log_info "测试 URL: $test_url"
local response
response=$(curl -I -s --connect-timeout 10 "$test_url" 2>&1)
local http_code=$(echo "$response" | grep -i "HTTP/" | head -n1 | awk '{print $2}')
local location=$(echo "$response" | grep -i "^Location:" | head -n1 | awk '{print $2}' | tr -d '\r')
log_info "HTTP 响应码: ${http_code:-无响应}"
case "$http_code" in
200)
log_success "✅ 域名 80 端口可达性检测通过 (HTTP 200)"
log_info "ACME 挑战路径正常响应"
return 0
;;
404)
log_success "✅ 域名 80 端口可达性检测通过 (HTTP 404)"
log_warn "ACME 挑战路径返回 404但 nginx 已响应"
log_info "nginx 会自动创建挑战文件,检测通过"
return 0
;;
301|302)
log_warn "检测到 HTTP 跳转 (HTTP $http_code)"
if [[ -n "$location" ]]; then
log_info "跳转目标: $location"
if [[ "$location" =~ ^https:// ]]; then
log_warn "⚠️ HTTPS 跳转已配置生效!"
log_info "这说明之前已经成功配置过 HTTPS"
echo ""
echo -e "${YELLOW}检测到域名 $DOMAIN 已配置 HTTPS 跳转${NC}"
echo "这可能是之前运行的配置残留"
echo ""
if [[ "$location" == "https://" ]] || [[ "$location" == "https://$DOMAIN" ]] || [[ "$location" =~ ^https://.*$ ]]; then
log_info "HTTPS 跳转配置正常,域名可达"
log_info "可以继续证书更新流程"
return 0
else
log_error "跳转目标异常: $location"
return 1
fi
else
log_warn "跳转到非 HTTPS 地址: $location"
return 1
fi
else
log_warn "收到 HTTP $http_code 但无 Location 头"
return 1
fi
;;
000)
log_error "❌ 连接失败 (HTTP $http_code)"
log_error "域名可能未解析或防火墙阻止"
return 1
;;
*)
log_error "❌ 意外的 HTTP 响应码: $http_code"
return 1
;;
esac
}
# 检查证书是否过期(返回 0=已过期1=未过期)
check_cert_expired() {
local cert_file="$1"
if [[ ! -f "$cert_file" ]]; then
log_warn "证书文件不存在: $cert_file,视为需要更新"
return 0
fi
# 获取过期时间
EXPIRE_DATE=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d= -f2)
if [[ -z "$EXPIRE_DATE" ]]; then
log_warn "无法读取证书过期时间,视为需要更新"
return 0
fi
EXPIRE_TIMESTAMP=$(date -d "$EXPIRE_DATE" +%s 2>/dev/null)
NOW_TIMESTAMP=$(date +%s)
DAYS_LEFT=$(( (EXPIRE_TIMESTAMP - NOW_TIMESTAMP) / 86400 ))
log_info "证书过期时间: $EXPIRE_DATE"
log_info "距离过期还有: ${DAYS_LEFT}"
# 提前 30 天预警更新
if [[ $DAYS_LEFT -lt 30 ]]; then
log_warn "证书即将过期(<30天需要更新"
return 0
else
log_info "证书仍在有效期内,无需更新"
return 1
fi
}
# 使用 acme.sh 颁发/更新证书
renew_certificate() {
log_step "开始更新证书: $DOMAIN"
# 方式1: 设置默认 CA可选
log_info "【方式1】设置默认 CA 为 Let's Encrypt..."
if ! "$ACME_SH" --set-default-ca --server letsencrypt >/dev/null 2>&1; then
log_warn "设置 CA 失败,继续尝试颁发证书..."
fi
# 方式2: 颁发证书webroot 模式)
log_info "【方式2】使用 webroot 模式颁发证书..."
log_info "Webroot 路径: $WEBROOT"
local cmd="$ACME_SH --issue -d $DOMAIN --webroot $WEBROOT"
if [[ "$FORCE_RENEW" == "true" ]]; then
cmd="$cmd --force"
log_info "⚠️ 已启用强制更新模式"
fi
# 捕获输出用于分析,同时也显示在终端
local tmp_out=$(mktemp)
$cmd 2>&1 | tee "$tmp_out"
local ret=${PIPESTATUS[0]}
# 分析结果
if [[ $ret -eq 0 ]]; then
log_info "✅ 证书颁发/更新成功!"
rm -f "$tmp_out"
return 0
else
# 检查是否是因为已经更新而跳过
if grep -qE "Domains not changed|Skipping|already issued" "$tmp_out"; then
log_warn "⚠️ ACME 提示证书无需更新或已存在,视为成功"
rm -f "$tmp_out"
return 0
else
log_error "❌ 证书颁发失败,请检查域名解析和 webroot 权限"
rm -f "$tmp_out"
return 1
fi
fi
}
# 带重试的证书更新函数
renew_certificate_with_retry() {
local max_retries=3
local retry_count=0
local wait_seconds=5
while [[ $retry_count -lt $max_retries ]]; do
retry_count=$((retry_count + 1))
echo ""
log_step "尝试更新证书 (第 $retry_count/$max_retries 次)..."
# 调用基本的证书更新函数
if renew_certificate; then
log_success "✅ 证书更新成功!"
return 0
else
# 更新失败,分析原因
log_warn "⚠️ 第 $retry_count 次更新失败"
# 检查是否是配置问题导致的失败
if grep -q "Invalid response.*404" /var/log/acme_update.log 2>/dev/null || grep -q "conflicting server name" /var/log/nginx/error.log 2>/dev/null; then
log_warn "检测到配置冲突问题,尝试清理并重试..."
# 再次清理配置
cleanup_nginx_conflicts
# 完全重启 nginx
log_info "重启 nginx 服务..."
if systemctl restart nginx >/dev/null 2>&1 || systemctl reload nginx >/dev/null 2>&1 || service nginx restart >/dev/null 2>&1; then
log_success "nginx 重启成功"
else
log_error "nginx 重启失败,请手动检查"
fi
# 等待一下让 nginx 完全启动
sleep $wait_seconds
fi
# 如果不是最后一次尝试,等待后重试
if [[ $retry_count -lt $max_retries ]]; then
echo ""
log_info "等待 ${wait_seconds} 秒后重试..."
sleep $wait_seconds
# 增加等待时间(指数退避)
wait_seconds=$((wait_seconds * 2))
fi
fi
done
# 所有重试都失败
log_error "❌ 经过 $max_retries 次尝试,证书更新仍然失败"
echo ""
log_error "可能的原因:"
echo " 1. 域名未正确解析到本服务器"
echo " 2. 防火墙阻止了 80 端口"
echo " 3. nginx 配置存在冲突"
echo " 4. webroot 目录权限不足"
echo ""
echo "建议的排查步骤:"
echo " # 1. 检查域名解析"
echo " dig ${DOMAIN}"
echo ""
echo " # 2. 测试 80 端口"
echo " curl -I http://${DOMAIN}/.well-known/acme-challenge/test"
echo ""
echo " # 3. 检查 nginx 配置"
echo " grep -r 'server_name.*${DOMAIN}' /etc/nginx/"
echo ""
echo " # 4. 检查 webroot 权限"
echo " ls -la ${WEBROOT}"
echo " sudo chown -R www-data:www-data ${WEBROOT}"
echo ""
return 1
}
# 验证证书内容(匹配用户提供的特征)
verify_cert_success() {
local cert_file="$1"
if [[ ! -f "$cert_file" ]]; then
log_warn "验证失败: 证书文件不存在 -> $cert_file"
return 1
fi
# 检查是否包含证书结束标记
if grep -q "END CERTIFICATE" "$cert_file"; then
log_info "✅ 证书内容验证通过"
return 0
fi
log_warn "证书内容验证未通过 - 未找到结束标记"
return 1
}
# 安装证书到指定位置
install_certificate() {
log_step "安装证书到生产环境..."
INSTALL_CMD="$ACME_SH --install-cert -d $DOMAIN \
--key-file ${KEY_FILE} \
--fullchain-file ${CERT_FILE} \
--reloadcmd \"service nginx force-reload\""
log_info "执行安装命令: $INSTALL_CMD"
if eval "$INSTALL_CMD"; then
log_info "✅ 证书安装成功"
return 0
else
log_error "❌ 证书安装失败"
return 1
fi
}
# 设置证书文件权限
set_cert_permissions() {
log_step "设置证书文件权限..."
# 确保使用实际目录
CERT_DIR=$(dirname "$CERT_FILE")
if sudo chown www-data:www-data "${CERT_DIR}"/*.pem 2>/dev/null; then
log_info "✅ 文件所有者已设置为 www-data:www-data"
else
log_warn "⚠️ chown 执行失败,请手动检查权限"
fi
if sudo chmod 600 "${CERT_DIR}"/*.pem 2>/dev/null; then
log_info "✅ 文件权限已设置为 600仅所有者可读写"
else
log_warn "⚠️ chmod 执行失败,请手动检查权限"
fi
# 确保 nginx 可读www-data 是 nginx 运行用户)
log_info "✅ 权限设置完成nginx 可正常读取证书"
}
# 重载 nginx 使配置生效
reload_nginx() {
log_step "重载 nginx 配置..."
if systemctl restart nginx >/dev/null 2>&1; then
log_success "nginx 重启成功"
elif systemctl reload nginx >/dev/null 2>&1; then
log_success "nginx 重载成功"
elif service nginx restart >/dev/null 2>&1; then
log_success "nginx 重启成功 (service 命令)"
elif service nginx reload >/dev/null 2>&1; then
log_success "nginx 重载成功 (service 命令)"
elif service nginx force-reload >/dev/null 2>&1; then
log_success "nginx 重载成功 (force-reload)"
elif nginx -s reload >/dev/null 2>&1; then
log_success "nginx 重载成功 (nginx -s)"
else
log_error "nginx 重启/重载失败,请手动执行: systemctl restart nginx"
exit 1
fi
}
# 增强的最终验证
enhanced_final_verification() {
log_step "开始最终验证流程..."
# 测试 HTTPS 访问
echo ""
echo -e "${BLUE}测试 1: HTTPS 访问${NC}"
local https_response=$(curl -I -s --connect-timeout 10 "https://${DOMAIN}" 2>&1)
local https_code=$(echo "$https_response" | grep -i "HTTP/" | head -n1 | awk '{print $2}')
if [[ "$https_code" == "200" ]] || [[ "$https_code" == "301" ]] || [[ "$https_code" == "302" ]]; then
log_success "✅ HTTPS 访问成功 (HTTP $https_code)"
else
log_warn "⚠️ HTTPS 响应异常: ${https_code:-无响应}"
fi
# 测试 HTTP 跳转
echo ""
echo -e "${BLUE}测试 2: HTTP 跳转测试${NC}"
local http_response=$(curl -I -s --connect-timeout 10 "http://${DOMAIN}" 2>&1)
local http_code=$(echo "$http_response" | grep -i "HTTP/" | head -n1 | awk '{print $2}')
local location=$(echo "$http_response" | grep -i "^Location:" | head -n1 | sed 's/Location: *//i' | tr -d '\r')
if [[ "$http_code" == "301" ]] || [[ "$http_code" == "302" ]]; then
log_success "✅ HTTP 跳转正常 (HTTP $http_code)"
if [[ -n "$location" ]]; then
log_info "跳转目标: $location"
# 验证跳转目标是否正确
if [[ "$location" =~ ^https://${DOMAIN} ]] || [[ "$location" =~ ^https://${DOMAIN}/ ]]; then
log_success "✅ 跳转目标正确: $location"
else
log_warn "⚠️ 跳转目标可能不正确: $location"
log_info "期望格式: https://${DOMAIN}..."
fi
else
log_warn "⚠️ 收到 HTTP $http_code 但没有 Location 头"
log_error "这可能导致浏览器无法自动跳转"
fi
else
log_warn "⚠️ HTTP 跳转异常: ${http_code:-无响应}"
echo ""
log_error "完整响应头:"
echo "$http_response" | head -n 10
echo ""
log_error "如果显示 nginx 默认页面,可能原因:"
echo " 1. nginx 使用了旧的配置文件"
echo " 2. 80 端口有多个 server 块冲突"
echo " 3. 需要完全重启 nginx 而不是重载"
fi
# 验证 SSL 证书
echo ""
echo -e "${BLUE}测试 3: SSL 证书验证${NC}"
local ssl_verify=$(echo | openssl s_client -connect "${DOMAIN}:443" -servername "${DOMAIN}" 2>/dev/null | openssl x509 -noout -subject -dates 2>/dev/null)
if [[ -n "$ssl_verify" ]]; then
log_success "✅ SSL 证书有效"
echo "$ssl_verify" | while read line; do
log_info " $line"
done
else
log_warn "⚠️ SSL 证书验证失败"
fi
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} ✅ SSL 证书更新成功! ${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e "${BLUE}📋 配置信息:${NC}"
echo " 域名: $DOMAIN"
echo " 证书文件: $CERT_FILE"
echo " 密钥文件: $KEY_FILE"
echo " Nginx 配置: $CONF_FILE"
echo " 日志文件: $LOG_FILE"
echo ""
echo -e "${BLUE}🧪 验证命令:${NC}"
echo " 1. 测试 HTTPS 访问:"
echo -e " ${GREEN}curl -I https://${DOMAIN}${NC}"
echo ""
echo " 2. 查看证书详情:"
echo -e " ${GREEN}openssl x509 -in $CERT_FILE -noout -text | head -20${NC}"
echo ""
echo " 3. 在线验证 SSL 配置:"
echo -e " ${GREEN}https://www.ssllabs.com/ssltest/analyze.html?d=${DOMAIN}${NC}"
echo ""
echo -e "${YELLOW}⚠️ 提示:${NC}"
echo " - HTTP 请求会自动 301 跳转到 HTTPS"
echo " - 证书将在 60 天后自动续期"
echo " - 如需手动续期: $ACME_SH --renew -d $DOMAIN"
echo ""
}
# 主流程
main() {
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 🔄 ACME SSL 证书自动更新脚本 (Robust) ${NC}"
echo -e "${BLUE} 日志文件: $LOG_FILE ${NC}"
echo -e "${BLUE}========================================${NC}"
check_root
install_acme_sh
input_domain
check_nginx_conf
extract_cert_path
echo ""
log_info "开始执行证书更新流程..."
echo ""
backup_old_configs
# 清理可能存在的 nginx 配置冲突
cleanup_nginx_conflicts
local need_renew=false
local do_install=false
# 检查证书是否需要更新
if check_cert_expired "$CERT_FILE"; then
need_renew=true
else
echo ""
echo -e "${YELLOW}当前证书仍在有效期内,请选择操作:${NC}"
echo "1) 强制更新证书 (Force Renew)"
echo "2) 跳过更新,仅执行安装和重载 (Install Only)"
echo "3) 退出脚本 (Exit)"
read -p "请输入选项 [1-3]: " choice
case "$choice" in
1) need_renew=true; FORCE_RENEW=true ;;
2) need_renew=false; do_install=true ;;
*) log_info "用户选择退出"; exit 0 ;;
esac
fi
# 只有在需要更新证书时才执行验证和更新流程
if [[ "$need_renew" == "true" ]]; then
if check_domain_reachability; then
if renew_certificate_with_retry; then
# 更新成功(或跳过)后,必须执行安装
do_install=true
else
log_error "❌ 证书更新失败,流程终止"
exit 1
fi
else
log_error "域名可达性检测失败,无法继续"
exit 1
fi
fi
if [[ "$do_install" == "true" ]]; then
if install_certificate; then
set_cert_permissions
reload_nginx
# 使用增强的最终验证
enhanced_final_verification
log_success "🎉 域名 [$DOMAIN] 证书更新/安装全流程完成!"
else
log_error "❌ 证书安装失败"
exit 1
fi
else
log_info "✨ 域名 [$DOMAIN] 证书无需更新,任务结束"
fi
echo ""
log_success "🎉 域名 [$DOMAIN] SSL 证书更新完成!"
echo ""
}
# 执行主函数
main "$@"