| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- #!/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 <<EOF
- {
- "msgtype": "text",
- "text": {
- "content": "🚨 证书告警 [${DOMAIN}]\n${message}\n时间: $(date '+%Y-%m-%d %H:%M:%S')"
- }
- }
- EOF
- )
-
- curl -s -X POST "$webhook" \
- -H "Content-Type: application/json" \
- -d "$payload" > /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 "========================================="
|