#!/bin/bash # ============================================ # SSL 证书初始化脚本(首次部署时运行) # # 使用 Let's Encrypt (certbot) 为 3 个域名申请证书 # 流程: # 1. 启动 Nginx(仅 HTTP 80 端口,用于 ACME 验证) # 2. 用 certbot 对每个域名申请证书 # 3. 重新加载 Nginx 以启用 HTTPS # # 用法: # ./init-ssl.sh # 正式申请证书 # ./init-ssl.sh --staging # 使用 staging 环境测试(不受速率限制) # ./init-ssl.sh --dry-run # 仅测试,不真正申请 # ============================================ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$SCRIPT_DIR" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } # 加载 .env if [ ! -f .env ]; then log_error ".env 文件不存在!请先执行: cp .env.example .env 并填写配置" exit 1 fi source .env # 检查必要变量 if [ -z "$SSL_EMAIL" ]; then log_error "请在 .env 中设置 SSL_EMAIL(用于 Let's Encrypt 证书申请通知)" exit 1 fi if [ -z "$API_DOMAIN" ] || [ -z "$DLWEB_DOMAIN" ] || [ -z "$WX_DOMAIN" ]; then log_error "请在 .env 中设置 API_DOMAIN, DLWEB_DOMAIN, WX_DOMAIN" exit 1 fi DOMAINS=("$API_DOMAIN" "$DLWEB_DOMAIN" "$WX_DOMAIN") # 判断使用 docker-compose 还是 docker compose COMPOSE_CMD="docker compose" if ! docker compose version &> /dev/null 2>&1; then COMPOSE_CMD="docker-compose" fi # 解析参数 STAGING_ARG="" DRY_RUN="" for arg in "$@"; do case $arg in --staging) STAGING_ARG="--staging" log_warn "使用 Let's Encrypt Staging 环境(测试用,证书不受信任)" ;; --dry-run) DRY_RUN="--dry-run" log_warn "Dry-run 模式,不会真正申请证书" ;; esac done # ============================================ # Step 1: 生成临时自签名证书(让 Nginx 能先启动) # ============================================ log_info "Step 1: 生成临时自签名证书..." for domain in "${DOMAINS[@]}"; do CERT_DIR="./docker/nginx/dummy-certs/$domain" mkdir -p "$CERT_DIR" if [ ! -f "$CERT_DIR/fullchain.pem" ]; then openssl req -x509 -nodes -newkey rsa:2048 -days 1 \ -keyout "$CERT_DIR/privkey.pem" \ -out "$CERT_DIR/fullchain.pem" \ -subj "/CN=$domain" 2>/dev/null log_info " 已生成临时自签名证书: $domain" fi done # ============================================ # Step 2: 将临时证书复制到 certbot volume # ============================================ log_info "Step 2: 初始化证书 volume..." # 确保容器和 volume 存在 $COMPOSE_CMD up -d nginx 2>/dev/null || true # 将临时证书复制到 certbot-certs volume for domain in "${DOMAINS[@]}"; do CERT_DIR="./docker/nginx/dummy-certs/$domain" LIVE_DIR="/etc/letsencrypt/live/$domain" # 通过 nginx 容器操作 volume docker exec youle-nginx sh -c "mkdir -p $LIVE_DIR" 2>/dev/null || true docker cp "$CERT_DIR/fullchain.pem" "youle-nginx:$LIVE_DIR/fullchain.pem" docker cp "$CERT_DIR/privkey.pem" "youle-nginx:$LIVE_DIR/privkey.pem" done # 重新加载 Nginx 以使用临时证书 docker exec youle-nginx nginx -s reload 2>/dev/null || true log_info " Nginx 已使用临时证书启动" # ============================================ # Step 3: 用 certbot 申请真实证书 # ============================================ log_info "Step 3: 申请 Let's Encrypt 证书..." for domain in "${DOMAINS[@]}"; do log_info " 正在为 $domain 申请证书..." $COMPOSE_CMD run --rm certbot certonly \ --webroot \ -w /var/www/certbot \ --email "$SSL_EMAIL" \ --agree-tos \ --no-eff-email \ --force-renewal \ -d "$domain" \ $STAGING_ARG \ $DRY_RUN if [ $? -eq 0 ]; then log_info " ✓ $domain 证书申请成功" else log_error " ✗ $domain 证书申请失败!请检查:" log_error " - 域名 DNS 是否已正确解析到本服务器" log_error " - 服务器 80 端口是否对外开放" log_error " - 是否超过 Let's Encrypt 速率限制(可用 --staging 测试)" fi done # ============================================ # Step 4: 重新加载 Nginx 以使用真实证书 # ============================================ log_info "Step 4: 重新加载 Nginx..." docker exec youle-nginx nginx -s reload # ============================================ # Step 5: 清理临时文件并启动 certbot 定时续签 # ============================================ rm -rf ./docker/nginx/dummy-certs log_info "Step 5: 启动 certbot 自动续签服务..." $COMPOSE_CMD up -d certbot # ============================================ # Step 6: 安装 crontab 定时重载 Nginx(使续签生效) # ============================================ log_info "Step 6: 设置自动重载 Nginx 的 crontab..." CRON_JOB="0 */12 * * * docker exec youle-nginx nginx -s reload >/dev/null 2>&1" CRON_MARKER="# youle-nginx-ssl-reload" # 检查是否已存在 if crontab -l 2>/dev/null | grep -q "$CRON_MARKER"; then log_info " crontab 已存在,跳过" else # 追加到当前用户的 crontab (crontab -l 2>/dev/null; echo "$CRON_JOB $CRON_MARKER") | crontab - log_info " 已添加 crontab: 每 12 小时重载 Nginx(使续签的证书生效)" fi echo "" log_info "============================================" log_info "SSL 初始化完成!" log_info "============================================" echo "" echo " 证书信息:" for domain in "${DOMAINS[@]}"; do echo " https://$domain" done echo "" echo " 证书有效期: 90 天" echo " 自动续签: certbot 容器每 12 小时检查一次" echo " 自动重载: crontab 每 12 小时执行 nginx -s reload" echo "" echo " 查看证书状态:" echo " $COMPOSE_CMD run --rm certbot certificates" echo "" echo " 手动续签:" echo " ./deploy.sh ssl-renew" echo ""