Files
knowledge-governance/scripts/install_backup_schedule.sh
T
姚章浩 3f603aa293 feat(backup): 系统设置里的存储与备份——定时时间与储存天数可配、下发到目标主机 cron
需求:整库逻辑备份做定时,但要在「系统设置 → 存储与备份」里设置定时时间与储存天数。

后端
- 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)。
2026-10-09 13:50:23 +08:00

119 lines
4.8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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"