diff --git a/create_new_domain_cert.sh b/create_new_domain_cert.sh index b8ee63b..c8bd37c 100755 --- a/create_new_domain_cert.sh +++ b/create_new_domain_cert.sh @@ -226,6 +226,89 @@ backup_old_configs() { fi } +# 清理所有可能冲突的 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" + + # 检查是否有多个 server_name 指令 + 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 "${NGINX_ENABLED}/${DOMAIN}.conf" ]]; then + rm -f "${NGINX_ENABLED}/${DOMAIN}.conf" + log_info "已删除符号链接: ${NGINX_ENABLED}/${DOMAIN}.conf" + cleaned=$((cleaned + 1)) + fi + + # 3. 清理 sites-available 中的配置 + if [[ -f "${NGINX_AVAILABLE}/${DOMAIN}.conf" ]]; then + rm -f "${NGINX_AVAILABLE}/${DOMAIN}.conf" + log_info "已删除配置: ${NGINX_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" != "$HTTPS_CONF_FILE" ]] && [[ "$file" != "$HTTP_CONF_FILE" ]]; then + local backup="${file}.bak-old-$(date +%s)" + mv "$file" "$backup" + log_info "已备份旧配置: $backup" + cleaned=$((cleaned + 1)) + fi + done + + # 5. 检查其他可能的配置位置 + for dir in /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/conf.d; do + if [[ -d "$dir" ]]; then + local other_files=$(find "$dir" -name "*.conf" -type f 2>/dev/null) + echo "$other_files" | while read file; do + if grep -q "server_name.*${DOMAIN}" "$file" 2>/dev/null; then + if [[ "$file" != "$HTTPS_CONF_FILE" ]] && [[ "$file" != "$HTTP_CONF_FILE" ]]; then + local backup="${file}.bak-extra-$(date +%s)" + mv "$file" "$backup" + log_warn "发现额外配置并已备份: $backup" + cleaned=$((cleaned + 1)) + fi + fi + done + fi + done + + if [[ $cleaned -eq 0 ]]; then + log_info "未发现配置冲突" + else + log_success "已清理 $cleaned 个冲突配置" + log_warn "建议运行: systemctl restart nginx" + fi +} + # 检查证书是否已存在且未过期 check_existing_certificate() { log_step "检查现有证书..." @@ -501,6 +584,87 @@ issue_certificate() { fi } +# 带重试的证书申请函数 +issue_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 issue_certificate; then + log_success "✅ 证书申请成功!" + return 0 + else + # 申请失败,分析原因 + log_warn "⚠️ 第 $retry_count 次申请失败" + + # 检查是否是配置问题导致的失败 + if grep -q "Invalid response.*404" /var/log/provision-${DOMAIN}.log 2>/dev/null || grep -q "conflicting server name" /var/log/nginx/error.log 2>/dev/null; then + log_warn "检测到配置冲突问题,尝试清理并重试..." + + # 再次清理配置 + cleanup_nginx_conflicts + + # 重新生成 HTTP 配置 + generate_http_config + + # 完全重启 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 +} + # 安装证书到指定位置 install_certificate() { log_step "安装证书到指定目录..." @@ -796,6 +960,9 @@ main() { install_acme_sh setup_webroot backup_old_configs + + # 清理可能存在的 nginx 配置冲突 + cleanup_nginx_conflicts # 检查是否需要申请新证书 local skip_issue=false @@ -806,12 +973,16 @@ main() { fi generate_http_config + + # 在生成配置后再次清理冲突 + cleanup_nginx_conflicts + reload_nginx # 只有在需要申请新证书时才执行验证和申请流程 if [[ "$skip_issue" == "false" ]]; then if check_domain_reachability; then - issue_certificate + issue_certificate_with_retry else log_error "域名可达性检测失败,无法继续" exit 1