Files
acme_renew/acme_test.sh
jeremygan2021 5d12be811e first commit
2026-03-03 15:01:07 +08:00

251 lines
8.0 KiB
Bash
Raw Permalink 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
#
# 脚本名称: acme_test.sh (Enhanced)
# 功能: 全面诊断 ACME 挑战环境(权限、配置、网络)
# 日期: 2026-03-03
# ================= 配置区域 =================
# 默认配置,可被交互式输入覆盖
DEFAULT_WEBROOT="/var/www/letsencrypt"
NGINX_CONF_DIR="/etc/nginx/conf.d"
# 彩色输出
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} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "\n${BLUE}[STEP]${NC} $1"; }
log_substep() { echo -e " ${CYAN}${NC} $1"; }
# 检查 root 权限
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "请使用 root 权限运行此脚本 (sudo)"
exit 1
fi
}
# 获取域名和 Webroot
get_input() {
log_step "配置检查参数"
# 域名输入
if [[ -z "$1" ]]; then
read -p "请输入要测试的域名 (例如: example.com): " DOMAIN
else
DOMAIN="$1"
fi
if [[ -z "$DOMAIN" ]]; then
log_error "域名不能为空"
exit 1
fi
log_info "目标域名: $DOMAIN"
# Webroot 输入
if [[ -z "$2" ]]; then
read -p "请输入 Webroot 路径 [默认: $DEFAULT_WEBROOT]: " INPUT_WEBROOT
WEBROOT="${INPUT_WEBROOT:-$DEFAULT_WEBROOT}"
else
WEBROOT="$2"
fi
log_info "Webroot: $WEBROOT"
}
# 检查 DNS 解析
check_dns() {
log_step "DNS 解析检查"
# 获取本机公网 IP (尝试多个源)
LOCAL_IP=$(curl -s4 ifconfig.me || curl -s4 icanhazip.com)
log_substep "本机公网 IP: ${LOCAL_IP:-无法获取}"
# 获取域名解析 IP
# 优先使用 dig如果没有则尝试 ping
if command -v dig &> /dev/null; then
DOMAIN_IP=$(dig +short "$DOMAIN" | head -n1)
else
DOMAIN_IP=$(ping -c 1 "$DOMAIN" 2>/dev/null | head -n1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
fi
log_substep "域名解析 IP: ${DOMAIN_IP:-无法获取}"
if [[ -n "$LOCAL_IP" && -n "$DOMAIN_IP" ]]; then
if [[ "$LOCAL_IP" == "$DOMAIN_IP" ]]; then
log_info "✅ DNS 解析正确指向本机"
else
log_warn "⚠️ DNS 解析 IP ($DOMAIN_IP) 与本机 IP ($LOCAL_IP) 不一致"
log_warn " (如果是 CDN/负载均衡环境,请忽略此警告)"
fi
elif [[ -z "$DOMAIN_IP" ]]; then
log_warn "⚠️ 无法解析域名 $DOMAIN,请检查 DNS 设置"
fi
}
# 检查 Webroot 权限与写入
check_webroot_access() {
log_step "Webroot 写入测试"
CHALLENGE_DIR="${WEBROOT}/.well-known/acme-challenge"
# 检查 Webroot 是否存在
if [[ ! -d "$WEBROOT" ]]; then
log_warn "Webroot 目录不存在: $WEBROOT"
read -p "是否创建该目录? [y/N] " create_choice
if [[ "$create_choice" =~ ^[Yy]$ ]]; then
mkdir -p "$WEBROOT"
log_info "✅ 已创建目录: $WEBROOT"
else
log_error "无法继续测试,目录不存在"
exit 1
fi
fi
# 创建挑战目录
if [[ ! -d "$CHALLENGE_DIR" ]]; then
mkdir -p "$CHALLENGE_DIR"
log_substep "创建挑战目录: $CHALLENGE_DIR"
fi
# 权限检查 (简单版)
TEST_FILE="${CHALLENGE_DIR}/test_token_$(date +%s)"
echo "acme_test_content" > "$TEST_FILE"
if [[ -f "$TEST_FILE" ]]; then
log_info "✅ Root 用户写入成功: $TEST_FILE"
else
log_error "❌ Root 用户写入失败,请检查磁盘空间或文件系统只读状态"
return 1
fi
# 修正权限 (确保 Nginx 可读)
chown -R www-data:www-data "$WEBROOT"
chmod -R 755 "$WEBROOT"
log_info "✅ 已修正 Webroot 权限 (owner: www-data, mod: 755)"
return 0
}
# Nginx 配置与端口检查
check_nginx() {
log_step "Nginx 配置与服务检查"
# 检查端口
if ss -tlnp | grep -q ':80\b'; then
log_info "✅ 端口 80 正在监听"
else
log_error "❌ 端口 80 未被监听Nginx 可能未启动"
log_info "尝试查看 Nginx 状态:"
systemctl status nginx --no-pager | head -n 5
fi
# 检查配置中是否有 location 规则
CONF_FILE="${NGINX_CONF_DIR}/${DOMAIN}.conf"
# 如果找不到标准命名的 conf尝试模糊搜索
if [[ ! -f "$CONF_FILE" ]]; then
CONF_FILE=$(grep -l "server_name.*$DOMAIN" ${NGINX_CONF_DIR}/*.conf 2>/dev/null | head -n1)
fi
if [[ -f "$CONF_FILE" ]]; then
log_substep "检查配置文件: $CONF_FILE"
if grep -q "location.*\.well-known/acme-challenge" "$CONF_FILE"; then
log_info "✅ 发现 ACME challenge location 规则"
else
log_warn "⚠️ 未在配置文件中发现显式的 .well-known/acme-challenge 规则"
log_warn " (如果使用了 include 或通用配置,可能也是正常的)"
fi
else
log_warn "⚠️ 未找到域名 $DOMAIN 的专属配置文件 (在 $NGINX_CONF_DIR)"
fi
}
# 模拟 HTTP 请求
perform_http_test() {
log_step "HTTP 访问测试 (模拟 ACME 验证)"
TEST_FILENAME=$(basename "$TEST_FILE")
# 构造 URL
# 本地测试 URL (绕过 DNS)
TEST_URL="http://127.0.0.1/.well-known/acme-challenge/$TEST_FILENAME"
# 外部访问 URL
DOMAIN_URL="http://${DOMAIN}/.well-known/acme-challenge/$TEST_FILENAME"
log_substep "1. 本地回环测试 (127.0.0.1)"
# 使用 Host 头强制指定域名
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: $DOMAIN" "$TEST_URL")
if [[ "$HTTP_CODE" == "200" ]]; then
log_info "✅ 本地访问成功 (HTTP 200)"
CONTENT=$(curl -s -H "Host: $DOMAIN" "$TEST_URL")
if [[ "$CONTENT" == "acme_test_content" ]]; then
log_info "✅ 内容匹配成功"
else
log_error "❌ 内容不匹配: 获取到了 '$CONTENT'"
fi
else
log_error "❌ 本地访问失败 (HTTP $HTTP_CODE)"
log_warn " 可能原因: Nginx root/alias 配置错误,或 location 匹配错误"
log_info " 尝试获取详细 Header:"
curl -I -H "Host: $DOMAIN" "$TEST_URL" 2>&1 | head -n 10
fi
log_substep "2. 域名解析访问测试 (公共网络模拟)"
echo " 尝试直接访问: $DOMAIN_URL"
EXTERNAL_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 "$DOMAIN_URL")
if [[ "$EXTERNAL_CODE" == "200" ]]; then
log_info "✅ 域名访问成功 (HTTP 200)"
elif [[ "$EXTERNAL_CODE" == "301" ]] || [[ "$EXTERNAL_CODE" == "302" ]]; then
log_warn "⚠️ 检测到重定向 (HTTP $EXTERNAL_CODE)"
REDIRECT_URL=$(curl -s -o /dev/null -w "%{redirect_url}" "$DOMAIN_URL")
log_info " 重定向目标: $REDIRECT_URL"
log_info " ACME 协议通常支持重定向,但请确保最终目标可达 (HTTPS 配置是否正确?)"
elif [[ "$EXTERNAL_CODE" == "000" ]]; then
log_error "❌ 无法连接到服务器 (Timeout/Connection Refused)"
log_warn " 请检查防火墙 (ufw/iptables) 和安全组设置 (端口 80)"
else
log_error "❌ 域名访问失败 (HTTP $EXTERNAL_CODE)"
fi
}
# 清理
cleanup() {
log_step "清理测试文件"
if [[ -f "$TEST_FILE" ]]; then
rm -f "$TEST_FILE"
log_info "✅ 已删除测试文件: $TEST_FILE"
else
log_info "无文件需要清理"
fi
}
# 主流程
main() {
# 注册清理钩子,脚本退出时自动清理
trap cleanup EXIT
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 🔍 ACME 验证环境诊断工具 ${NC}"
echo -e "${BLUE}========================================${NC}"
check_root
get_input "$1" "$2"
check_dns
check_webroot_access
check_nginx
perform_http_test
echo -e "\n${GREEN}=== 诊断结束 ===${NC}"
}
main "$@"