280 lines
9.5 KiB
Bash
Executable File
280 lines
9.5 KiB
Bash
Executable File
#!/bin/bash
|
||
# ============================================================
|
||
# NAC Knowledge Engine - MongoDB每日备份脚本 v2
|
||
# 新增:备份失败时自动发送Webhook告警(企业微信/钉钉/飞书/通用)
|
||
# 数据库:nac_knowledge_engine(多语言合规知识库)
|
||
# 备份目录:/opt/nac/backups/mongodb/
|
||
# 保留策略:保留最近30天的备份,自动清理旧备份
|
||
# 日志:/var/log/nac_mongo_backup.log
|
||
# ============================================================
|
||
set -euo pipefail
|
||
|
||
# ─── 配置 ────────────────────────────────────────────────────
|
||
BACKUP_BASE="/opt/nac/backups/mongodb"
|
||
LOG_FILE="/var/log/nac_mongo_backup.log"
|
||
RETENTION_DAYS=30
|
||
DB_NAME="nac_knowledge_engine"
|
||
DATE=$(date +%Y%m%d_%H%M%S)
|
||
BACKUP_DIR="${BACKUP_BASE}/${DATE}"
|
||
ENV_FILE="/opt/nac/services/nac-admin/.env"
|
||
|
||
# ─── 日志函数 ─────────────────────────────────────────────────
|
||
log() {
|
||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||
}
|
||
log_error() {
|
||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" | tee -a "$LOG_FILE" >&2
|
||
}
|
||
|
||
# ─── Webhook告警函数 ──────────────────────────────────────────
|
||
# 从.env文件读取Webhook配置并发送告警
|
||
send_webhook_alert() {
|
||
local TITLE="$1"
|
||
local CONTENT="$2"
|
||
local LEVEL="${3:-error}" # error | warning | info
|
||
|
||
# 从.env读取Webhook URL
|
||
local WECOM_URL DINGTALK_URL FEISHU_URL GENERIC_URL
|
||
WECOM_URL=$(grep "^NAC_NOTIFY_WECOM_WEBHOOK=" "${ENV_FILE}" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'" | tr -d '[:space:]')
|
||
DINGTALK_URL=$(grep "^NAC_NOTIFY_DINGTALK_WEBHOOK=" "${ENV_FILE}" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'" | tr -d '[:space:]')
|
||
FEISHU_URL=$(grep "^NAC_NOTIFY_FEISHU_WEBHOOK=" "${ENV_FILE}" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'" | tr -d '[:space:]')
|
||
GENERIC_URL=$(grep "^NAC_NOTIFY_WEBHOOK_URL=" "${ENV_FILE}" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'" | tr -d '[:space:]')
|
||
|
||
local SENT=0
|
||
local TIMESTAMP
|
||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||
|
||
# 企业微信
|
||
if [ -n "${WECOM_URL}" ]; then
|
||
local WECOM_PAYLOAD
|
||
WECOM_PAYLOAD=$(cat <<EOF
|
||
{
|
||
"msgtype": "markdown",
|
||
"markdown": {
|
||
"content": "## 🚨 ${TITLE}\n\n${CONTENT}\n\n> 时间:${TIMESTAMP}\n> 服务:NAC MongoDB备份\n> 级别:${LEVEL}"
|
||
}
|
||
}
|
||
EOF
|
||
)
|
||
if curl -s -X POST "${WECOM_URL}" \
|
||
-H "Content-Type: application/json" \
|
||
-d "${WECOM_PAYLOAD}" \
|
||
--connect-timeout 10 \
|
||
--max-time 15 \
|
||
-o /dev/null 2>>"${LOG_FILE}"; then
|
||
log "[Webhook] 企业微信告警发送成功"
|
||
SENT=$((SENT + 1))
|
||
else
|
||
log_error "[Webhook] 企业微信告警发送失败"
|
||
fi
|
||
fi
|
||
|
||
# 钉钉
|
||
if [ -n "${DINGTALK_URL}" ]; then
|
||
local DINGTALK_PAYLOAD
|
||
DINGTALK_PAYLOAD=$(cat <<EOF
|
||
{
|
||
"msgtype": "markdown",
|
||
"markdown": {
|
||
"title": "${TITLE}",
|
||
"text": "## 🚨 ${TITLE}\n\n${CONTENT}\n\n> 时间:${TIMESTAMP}\n> 服务:NAC MongoDB备份\n> 级别:${LEVEL}"
|
||
},
|
||
"at": { "isAtAll": true }
|
||
}
|
||
EOF
|
||
)
|
||
if curl -s -X POST "${DINGTALK_URL}" \
|
||
-H "Content-Type: application/json" \
|
||
-d "${DINGTALK_PAYLOAD}" \
|
||
--connect-timeout 10 \
|
||
--max-time 15 \
|
||
-o /dev/null 2>>"${LOG_FILE}"; then
|
||
log "[Webhook] 钉钉告警发送成功"
|
||
SENT=$((SENT + 1))
|
||
else
|
||
log_error "[Webhook] 钉钉告警发送失败"
|
||
fi
|
||
fi
|
||
|
||
# 飞书
|
||
if [ -n "${FEISHU_URL}" ]; then
|
||
local COLOR
|
||
case "${LEVEL}" in
|
||
error) COLOR="red" ;;
|
||
warning) COLOR="orange" ;;
|
||
*) COLOR="blue" ;;
|
||
esac
|
||
local FEISHU_PAYLOAD
|
||
FEISHU_PAYLOAD=$(cat <<EOF
|
||
{
|
||
"msg_type": "interactive",
|
||
"card": {
|
||
"header": {
|
||
"title": { "tag": "plain_text", "content": "🚨 ${TITLE}" },
|
||
"template": "${COLOR}"
|
||
},
|
||
"elements": [
|
||
{
|
||
"tag": "div",
|
||
"text": { "tag": "lark_md", "content": "${CONTENT}\n\n**时间:** ${TIMESTAMP}\n**服务:** NAC MongoDB备份\n**级别:** ${LEVEL}" }
|
||
}
|
||
]
|
||
}
|
||
}
|
||
EOF
|
||
)
|
||
if curl -s -X POST "${FEISHU_URL}" \
|
||
-H "Content-Type: application/json" \
|
||
-d "${FEISHU_PAYLOAD}" \
|
||
--connect-timeout 10 \
|
||
--max-time 15 \
|
||
-o /dev/null 2>>"${LOG_FILE}"; then
|
||
log "[Webhook] 飞书告警发送成功"
|
||
SENT=$((SENT + 1))
|
||
else
|
||
log_error "[Webhook] 飞书告警发送失败"
|
||
fi
|
||
fi
|
||
|
||
# 通用Webhook
|
||
if [ -n "${GENERIC_URL}" ]; then
|
||
local GENERIC_PAYLOAD
|
||
GENERIC_PAYLOAD=$(cat <<EOF
|
||
{
|
||
"title": "${TITLE}",
|
||
"content": "${CONTENT}",
|
||
"level": "${LEVEL}",
|
||
"service": "nac_mongo_backup",
|
||
"timestamp": "${TIMESTAMP}"
|
||
}
|
||
EOF
|
||
)
|
||
if curl -s -X POST "${GENERIC_URL}" \
|
||
-H "Content-Type: application/json" \
|
||
-d "${GENERIC_PAYLOAD}" \
|
||
--connect-timeout 10 \
|
||
--max-time 15 \
|
||
-o /dev/null 2>>"${LOG_FILE}"; then
|
||
log "[Webhook] 通用Webhook告警发送成功"
|
||
SENT=$((SENT + 1))
|
||
else
|
||
log_error "[Webhook] 通用Webhook告警发送失败"
|
||
fi
|
||
fi
|
||
|
||
if [ "${SENT}" -eq 0 ]; then
|
||
log "[Webhook] 未配置任何Webhook,告警仅记录到日志"
|
||
fi
|
||
}
|
||
|
||
# ─── 错误处理:捕获脚本异常退出 ──────────────────────────────
|
||
BACKUP_FAILED=0
|
||
trap 'if [ $? -ne 0 ] && [ "${BACKUP_FAILED}" -eq 0 ]; then
|
||
BACKUP_FAILED=1
|
||
log_error "备份脚本异常退出(非预期错误)"
|
||
send_webhook_alert \
|
||
"NAC MongoDB备份异常退出" \
|
||
"备份脚本在执行过程中发生未预期的错误,请立即检查服务器状态。\n\n**日志文件:** ${LOG_FILE}\n**备份目录:** ${BACKUP_BASE}" \
|
||
"error"
|
||
fi' EXIT
|
||
|
||
# ─── 主备份流程 ───────────────────────────────────────────────
|
||
log "======================================================"
|
||
log "NAC MongoDB备份开始 v2 - 数据库: ${DB_NAME}"
|
||
log "备份目录: ${BACKUP_DIR}"
|
||
|
||
# 创建备份目录
|
||
mkdir -p "${BACKUP_DIR}"
|
||
|
||
# 读取MongoDB连接URL
|
||
MONGO_URL=""
|
||
MONGO_URL_FILE="/opt/nac/services/nac-admin/mongo_url.conf"
|
||
if [ -f "${MONGO_URL_FILE}" ]; then
|
||
MONGO_URL=$(cat "${MONGO_URL_FILE}" | tr -d '[:space:]')
|
||
else
|
||
MONGO_URL=$(grep "^NAC_MONGO_URL=" "${ENV_FILE}" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'")
|
||
fi
|
||
|
||
if [ -z "${MONGO_URL}" ]; then
|
||
log_error "无法读取MongoDB连接URL,备份失败"
|
||
BACKUP_FAILED=1
|
||
send_webhook_alert \
|
||
"NAC MongoDB备份失败 - 无法读取连接URL" \
|
||
"备份脚本无法从配置文件中读取MongoDB连接URL。\n\n**检查文件:** ${ENV_FILE}\n**所需变量:** NAC_MONGO_URL" \
|
||
"error"
|
||
exit 1
|
||
fi
|
||
|
||
# 执行mongodump备份
|
||
log "开始执行 mongodump..."
|
||
MONGODUMP_LOG="${BACKUP_DIR}/mongodump.log"
|
||
|
||
if mongodump \
|
||
--uri="${MONGO_URL}" \
|
||
--db="${DB_NAME}" \
|
||
--out="${BACKUP_DIR}" \
|
||
--gzip \
|
||
--quiet 2>"${MONGODUMP_LOG}"; then
|
||
|
||
# 计算备份大小
|
||
BACKUP_SIZE=$(du -sh "${BACKUP_DIR}" | cut -f1)
|
||
log "备份成功 - 大小: ${BACKUP_SIZE}"
|
||
|
||
# 创建备份清单文件
|
||
cat > "${BACKUP_DIR}/BACKUP_MANIFEST.txt" << EOF
|
||
NAC Knowledge Engine MongoDB备份清单 v2
|
||
========================================
|
||
备份时间: $(date '+%Y-%m-%d %H:%M:%S')
|
||
数据库名: ${DB_NAME}
|
||
备份大小: ${BACKUP_SIZE}
|
||
备份工具: mongodump $(mongodump --version 2>&1 | head -1 | awk '{print $3}' || echo "unknown")
|
||
备份内容:
|
||
$(ls -la "${BACKUP_DIR}/${DB_NAME}/" 2>/dev/null || echo " (无集合文件)")
|
||
EOF
|
||
|
||
log "备份清单已创建: ${BACKUP_DIR}/BACKUP_MANIFEST.txt"
|
||
|
||
# 备份成功时发送成功通知(可选,避免频繁通知可注释此段)
|
||
# send_webhook_alert "NAC MongoDB备份成功" "数据库 ${DB_NAME} 备份完成,大小: ${BACKUP_SIZE}" "info"
|
||
|
||
else
|
||
MONGODUMP_ERROR=$(cat "${MONGODUMP_LOG}" 2>/dev/null | head -5 || echo "无错误详情")
|
||
log_error "mongodump执行失败"
|
||
log_error "错误详情: ${MONGODUMP_ERROR}"
|
||
rm -rf "${BACKUP_DIR}"
|
||
BACKUP_FAILED=1
|
||
|
||
# 发送告警
|
||
send_webhook_alert \
|
||
"NAC MongoDB备份失败 - mongodump执行失败" \
|
||
"mongodump命令执行失败,数据库 **${DB_NAME}** 备份未完成。\n\n**错误信息:** ${MONGODUMP_ERROR}\n\n**日志文件:** ${LOG_FILE}\n\n请立即检查MongoDB服务状态和网络连接。" \
|
||
"error"
|
||
exit 1
|
||
fi
|
||
|
||
# ─── 清理旧备份(保留最近30天)────────────────────────────────
|
||
log "清理${RETENTION_DAYS}天前的旧备份..."
|
||
DELETED_COUNT=0
|
||
while IFS= read -r -d '' old_dir; do
|
||
log " 删除旧备份: $(basename "${old_dir}")"
|
||
rm -rf "${old_dir}"
|
||
DELETED_COUNT=$((DELETED_COUNT + 1))
|
||
done < <(find "${BACKUP_BASE}" -maxdepth 1 -type d -mtime +${RETENTION_DAYS} -print0 2>/dev/null)
|
||
|
||
if [ "${DELETED_COUNT}" -gt 0 ]; then
|
||
log "已清理 ${DELETED_COUNT} 个旧备份"
|
||
else
|
||
log "无需清理旧备份"
|
||
fi
|
||
|
||
# ─── 备份统计 ─────────────────────────────────────────────────
|
||
TOTAL_BACKUPS=$(find "${BACKUP_BASE}" -maxdepth 1 -type d | wc -l)
|
||
TOTAL_SIZE=$(du -sh "${BACKUP_BASE}" 2>/dev/null | cut -f1)
|
||
log "当前备份总数: $((TOTAL_BACKUPS - 1)) 个,总占用: ${TOTAL_SIZE}"
|
||
log "NAC MongoDB备份完成 v2 ✓"
|
||
log "======================================================"
|
||
|
||
# 正常完成,取消错误trap
|
||
BACKUP_FAILED=1 # 防止EXIT trap误触发
|
||
exit 0
|