first commit

This commit is contained in:
jeremygan2021
2026-03-03 15:01:07 +08:00
commit 5d12be811e
5 changed files with 1347 additions and 0 deletions

250
acme_test.sh Normal file
View File

@@ -0,0 +1,250 @@
#!/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 "$@"