#!/bin/bash # ============================================================ # Let's Encrypt 证书自动续期脚本 # # 首次申请证书: # certbot certonly --webroot -w /var/www/certbot \ # -d your-domain.com -d www.your-domain.com \ # --email admin@your-domain.com --agree-tos --no-eff-email # # 配置 cron 自动续期 (每天凌晨 3 点检查): # 0 3 * * * /opt/water-management/deploy/production/nginx/certbot-renew.sh >> /var/log/certbot-renew.log 2>&1 # ============================================================ set -euo pipefail # ==================== 配置 ==================== DOMAIN="${DOMAIN:-water.example.com}" WEBROOT="/var/www/certbot" EMAIL="${CERTBOT_EMAIL:-admin@example.com}" NGINX_CONTAINER="${NGINX_CONTAINER:-wm-frontend}" RENEW_DAYS_BEFORE_EXPIRY=30 # ==================== 函数 ==================== log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } # 检查证书是否需要续期 check_cert_expiry() { local cert_path="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" if [ ! -f "$cert_path" ]; then log "⚠️ 证书文件不存在: $cert_path" return 1 fi local expiry_date expiry_date=$(openssl x509 -enddate -noout -in "$cert_path" | cut -d= -f2) local expiry_epoch expiry_epoch=$(date -d "$expiry_date" +%s) local now_epoch now_epoch=$(date +%s) local days_left=$(( (expiry_epoch - now_epoch) / 86400 )) log "📅 证书过期时间: $expiry_date (剩余 ${days_left} 天)" if [ $days_left -le $RENEW_DAYS_BEFORE_EXPIRY ]; then log "⚠️ 证书将在 ${days_left} 天内过期,需要续期" return 0 else log "✅ 证书有效,无需续期" return 1 fi } # 首次申请证书 init_cert() { log "🔐 首次申请证书..." # 确保 webroot 目录存在 mkdir -p "$WEBROOT" # 使用 webroot 方式申请(不需要停止 Nginx) certbot certonly --webroot \ -w "$WEBROOT" \ -d "$DOMAIN" \ --email "$EMAIL" \ --agree-tos \ --no-eff-email \ --non-interactive if [ $? -eq 0 ]; then log "✅ 证书申请成功" reload_nginx else log "❌ 证书申请失败" exit 1 fi } # 续期证书 renew_cert() { log "🔄 开始续期证书..." certbot renew \ --webroot \ -w "$WEBROOT" \ --non-interactive \ --quiet if [ $? -eq 0 ]; then log "✅ 证书续期成功" reload_nginx else log "❌ 证书续期失败" send_alert "证书续期失败,请手动检查!" exit 1 fi } # 重载 Nginx reload_nginx() { log "🔄 重载 Nginx 配置..." if docker exec "$NGINX_CONTAINER" nginx -s reload 2>/dev/null; then log "✅ Nginx 重载成功" else log "⚠️ Docker 方式重载失败,尝试系统方式..." systemctl reload nginx 2>/dev/null || nginx -s reload 2>/dev/null || true fi } # 发送告警(企业微信 Webhook) send_alert() { local message="$1" local webhook="${WECOM_WEBHOOK:-}" if [ -z "$webhook" ]; then log "⚠️ 未配置企业微信 Webhook,跳过告警" return 0 fi local payload payload=$(cat < /dev/null 2>&1 || true log "📤 告警已发送" } # ==================== 主逻辑 ==================== log "=========================================" log "Let's Encrypt 证书管理 - ${DOMAIN}" log "=========================================" # 检查证书是否存在 if [ ! -d "/etc/letsencrypt/live/${DOMAIN}" ]; then log "📋 证书不存在,执行首次申请" init_cert else # 检查是否需要续期 if check_cert_expiry; then renew_cert fi fi log "=========================================" log "执行完毕" log "========================================="