3f603aa293
需求:整库逻辑备份做定时,但要在「系统设置 → 存储与备份」里设置定时时间与储存天数。 后端 - migration 000045 backup_schedule(单行:enabled/schedule_time/retention_days/retention_months/ last_applied_*),store + 领域校验(HH:MM、1~365 天、0~24 月,非法值 400 且不落库不下发) - internal/backuphost:通过 ssh 在目标主机上读回/下发计划并立即备份(未接线时 Available=false, 页面降级提示;主机侧错误带输出回传)。最近一次只认有 MANIFEST.json 的完整目录,跳 *.incomplete - 接口 GET/PUT /api/v1/backup-schedule、GET …/host、POST …/apply、POST …/run; 写操作经 classifyRole 限管理员;run 为异步 202 + 在途单飞(http 写超时 30s,备份约 35s) - openapi.yaml 补齐 5 条路由(契约测试要求每条都写 400/500) 主机侧 - scripts/install_backup_schedule.sh(幂等):写 /etc/knowledge-governance/backup-schedule.env 与 /etc/cron.d/kb-logical-backup,参数校验,--show/--disable,最后一行 SCHEDULE_APPLIED 供平台核对 - logical-backup.sh 从该文件读保留策略;失败或被中断把目录改名 *.incomplete,不留'像成功'的半成品 - install --show 只回显计划行(原先把 SHELL=/PATH= 当计划显示) 前端 - web/src/backupSchedule.ts(严格解析:缺计划字段即报错,不把默认值伪装成设置) - BackupScheduleSection:容器 + 纯视图(管理员表单/只读、已一致/待下发、在途轮询) 运行前提(已落实):运行镜像加 openssh-client,且容器 uid 1000 在 /etc/passwd 有名字 (否则 ssh 报 No user exists for uid 1000);凭据只读挂载 secrets/backup-host(git 忽略) 实测:主机 cron 每天 03:30 / 保留 7 天 / 每月 3 份;立即备份一次 34 秒 / 742.7 MiB / 失败 0; Go 与前端测试全绿(170 vitest)。
119 lines
4.8 KiB
Bash
119 lines
4.8 KiB
Bash
#!/usr/bin/env bash
|
||
# 在"目标库所在主机"上安装/更新**整库逻辑备份**的定时计划(幂等)。
|
||
# 平台的「系统设置 → 存储与备份」通过 SSH 下发时会调用它;也可在主机上手工执行。
|
||
#
|
||
# 用法(在目标主机上,需要 root):
|
||
# bash install_backup_schedule.sh --show # 只打印当前计划
|
||
# bash install_backup_schedule.sh --time 03:30 --days 7 --months 3
|
||
# bash install_backup_schedule.sh --disable # 关闭定时(保留已生成的备份)
|
||
#
|
||
# 落盘:
|
||
# /etc/knowledge-governance/backup-schedule.env 计划参数(0600)
|
||
# /etc/cron.d/kb-logical-backup cron 条目(0644,root:root)
|
||
# $KB_BACKUP_ROOT/logs/logical-backup.log 运行日志
|
||
#
|
||
# 输出最后一行固定为 SCHEDULE_APPLIED …,供平台解析核对。
|
||
set -euo pipefail
|
||
|
||
if [ -f /etc/knowledge-governance/backup.env ]; then
|
||
# shellcheck disable=SC1091
|
||
. /etc/knowledge-governance/backup.env
|
||
fi
|
||
KB_BACKUP_ROOT="${KB_BACKUP_ROOT:-/srv/kb-backups}"
|
||
BACKUP_SCRIPT="${BACKUP_SCRIPT:-/opt/weknora-test/logical-backup.sh}"
|
||
SCHEDULE_ENV="/etc/knowledge-governance/backup-schedule.env"
|
||
CRON_FILE="/etc/cron.d/kb-logical-backup"
|
||
LOG_FILE="$KB_BACKUP_ROOT/logs/logical-backup.log"
|
||
|
||
TIME=""; DAYS=""; MONTHS=""; MODE="apply"
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--time) TIME="${2:-}"; shift 2 ;;
|
||
--days) DAYS="${2:-}"; shift 2 ;;
|
||
--months) MONTHS="${2:-}"; shift 2 ;;
|
||
--disable) MODE="disable"; shift ;;
|
||
--show) MODE="show"; shift ;;
|
||
*) echo "未知参数: $1" >&2; exit 2 ;;
|
||
esac
|
||
done
|
||
|
||
show_state() {
|
||
echo "备份脚本: $BACKUP_SCRIPT $([ -x "$BACKUP_SCRIPT" ] || [ -f "$BACKUP_SCRIPT" ] && echo '(存在)' || echo '(缺失)')"
|
||
if [ -f "$SCHEDULE_ENV" ]; then
|
||
echo "计划参数: $(grep -v '^#' "$SCHEDULE_ENV" | paste -sd' ' -)"
|
||
else
|
||
echo "计划参数: 未配置"
|
||
fi
|
||
if [ -f "$CRON_FILE" ]; then
|
||
echo "cron 条目:"
|
||
# 只列计划行(去掉 SHELL=/PATH= 这类环境行),否则平台会把环境变量当计划回显
|
||
grep -E '^[0-9*@]' "$CRON_FILE" | sed 's/^/ /'
|
||
else
|
||
echo "cron 条目: 未安装(定时关闭)"
|
||
fi
|
||
if command -v systemctl >/dev/null 2>&1; then
|
||
echo "cron 服务: $(systemctl is-active cron 2>/dev/null || systemctl is-active crond 2>/dev/null || echo unknown)"
|
||
fi
|
||
echo "备份根目录: $KB_BACKUP_ROOT($(du -sh "$KB_BACKUP_ROOT/rds-logical" 2>/dev/null | cut -f1 || echo 0) 已用)"
|
||
}
|
||
|
||
if [ "$MODE" = "show" ]; then
|
||
show_state
|
||
exit 0
|
||
fi
|
||
|
||
if [ "$MODE" = "disable" ]; then
|
||
rm -f "$CRON_FILE"
|
||
if [ -f "$SCHEDULE_ENV" ]; then
|
||
sed -i 's/^KB_BACKUP_SCHEDULE_ENABLED=.*/KB_BACKUP_SCHEDULE_ENABLED=0/' "$SCHEDULE_ENV" || true
|
||
fi
|
||
echo "已关闭定时备份(已生成的备份保留)。"
|
||
echo "SCHEDULE_APPLIED enabled=0"
|
||
exit 0
|
||
fi
|
||
|
||
# ---- 参数校验(错就拒绝,不落半成品)----
|
||
case "$TIME" in
|
||
[0-2][0-9]:[0-5][0-9]) ;;
|
||
*) echo "--time 必须是 HH:MM(00:00~23:59),收到 '$TIME'" >&2; exit 2 ;;
|
||
esac
|
||
case "$DAYS" in ''|*[!0-9]*) echo "--days 必须是整数" >&2; exit 2 ;; esac
|
||
case "$MONTHS" in ''|*[!0-9]*) echo "--months 必须是整数" >&2; exit 2 ;; esac
|
||
[ "$DAYS" -ge 1 ] && [ "$DAYS" -le 365 ] || { echo "--days 需在 1~365" >&2; exit 2; }
|
||
[ "$MONTHS" -ge 0 ] && [ "$MONTHS" -le 24 ] || { echo "--months 需在 0~24" >&2; exit 2; }
|
||
[ -f "$BACKUP_SCRIPT" ] || { echo "备份脚本不存在: $BACKUP_SCRIPT" >&2; exit 2; }
|
||
|
||
HOUR="${TIME%%:*}"; MINUTE="${TIME##*:}"
|
||
# cron 里不能出现未转义的 %;这里全程不使用 % 以免歧义。
|
||
CRON_LINE="$MINUTE $HOUR * * * root /bin/bash $BACKUP_SCRIPT >> $LOG_FILE 2>&1"
|
||
|
||
mkdir -p "$KB_BACKUP_ROOT/logs" "$(dirname "$SCHEDULE_ENV")"
|
||
cat >"$SCHEDULE_ENV" <<EOF
|
||
# 由 install_backup_schedule.sh 写入(平台「系统设置 → 存储与备份」下发)
|
||
KB_BACKUP_SCHEDULE_ENABLED=1
|
||
KB_BACKUP_SCHEDULE_TIME=$TIME
|
||
KB_BACKUP_KEEP_DAYS=$DAYS
|
||
KB_BACKUP_KEEP_MONTHS=$MONTHS
|
||
KB_BACKUP_SCRIPT=$BACKUP_SCRIPT
|
||
KB_BACKUP_LOG=$LOG_FILE
|
||
EOF
|
||
chmod 600 "$SCHEDULE_ENV"
|
||
|
||
cat >"$CRON_FILE" <<EOF
|
||
# 整库逻辑备份(目标库所在主机本地执行;参数来自 $SCHEDULE_ENV)
|
||
SHELL=/bin/bash
|
||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||
$CRON_LINE
|
||
EOF
|
||
chmod 644 "$CRON_FILE"
|
||
chown root:root "$CRON_FILE" 2>/dev/null || true
|
||
|
||
NEXT_RUN="$(date -d "today $TIME" '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null || echo unknown)"
|
||
if [ "$NEXT_RUN" != "unknown" ] && [ "$(date -d "today $TIME" +%s)" -le "$(date +%s)" ]; then
|
||
NEXT_RUN="$(date -d "tomorrow $TIME" '+%Y-%m-%dT%H:%M:%S%z')"
|
||
fi
|
||
|
||
echo "计划已下发:每天 $TIME(保留 $DAYS 天,另按自然月保留 $MONTHS 份)"
|
||
echo "cron 文件:$CRON_FILE"
|
||
echo "下次执行:$NEXT_RUN"
|
||
echo "SCHEDULE_APPLIED enabled=1 time=$TIME days=$DAYS months=$MONTHS next=$NEXT_RUN cron=$CRON_FILE" |