feat: 修改逻辑
这个提交包含在:
@@ -84,7 +84,8 @@ bash backup.sh smb
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `COMMON_SOURCE_PATHS` | 要备份的源路径,多个用空格分隔,需引号包裹 |
|
||||
| `COMMON_TMP_DIR` | 本地临时打包目录,默认 `/tmp/backup` |
|
||||
| `COMMON_TMP_DIR` | 本地临时打包目录,默认 `/tmp/backup_script` |
|
||||
| `COMMON_CLEAN_LOCAL` 配套行为 | 上传完成或失败后,会清理 `${COMMON_TMP_DIR}/${COMMON_ARCHIVE_PREFIX}*` 匹配到的文件/目录(含上次失败遗留的同前缀产物),同目录下其它无关文件不受影响 |
|
||||
| `COMMON_ARCHIVE_PREFIX` | 归档/远端目录命名前缀,最终形如 `prefix-YYYYmmdd-HHMMSS/` |
|
||||
| `COMMON_CLEAN_LOCAL` | 上传后是否删除本地归档(`true` / `false`) |
|
||||
| `COMMON_RETENTION_DAYS` | 远端保留天数,`0` 表示不清理;按目录整体清理 |
|
||||
@@ -346,5 +347,5 @@ tar -xzf backup-20260426-031000.tar.gz
|
||||
- 命令行密码会出现在进程列表中,安全敏感场景请优先使用 `backup.conf`,并把权限收紧:`chmod 600 backup.conf`。
|
||||
- macOS 上若未安装 `smbclient`,会回退到挂载方式(`mount_smbfs`),此时不支持 `--retention` 远端清理,仅完成上传。
|
||||
- 远端清理仅清理符合 `${COMMON_ARCHIVE_PREFIX}-YYYYmmdd-HHMMSS` 命名规范的目录,避免误删其它内容。
|
||||
- rclone 上传完成后会自动用 `rclone hashsum SHA256` 拉取远端 hash 与本地 `.sha256` 清单逐项比对,校验失败会以非 0 状态码退出。少数远端(个别 WebDAV)不支持 SHA256 hash,遇到这种情况脚本会报错退出,可改成手动下载后用 `sha256sum -c` 校验。
|
||||
- rclone 远端必须先在本机用 `rclone config` 配好;自定义 `RCLONE_CONFIG` 路径需保证脚本运行用户可读。
|
||||
- 脚本不在上传后做远端 SHA256 校验(不同后端对 hash 的支持差异太大)。如需校验,恢复时进入备份目录用 `sha256sum -c` 对照同目录下的 `.sha256` 清单即可。
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
# 要备份的源目录或文件(多个用空格分隔,需引号包裹)
|
||||
COMMON_SOURCE_PATHS="/opt"
|
||||
|
||||
# 本地临时打包目录(用于存放归档/分卷/sha 文件,备份完成后会清理)
|
||||
COMMON_TMP_DIR="/tmp/backup"
|
||||
# 本地临时打包目录(用于存放归档/分卷/sha 文件)
|
||||
# 注意:当 COMMON_CLEAN_LOCAL=true 时,会清理目录下所有以 COMMON_ARCHIVE_PREFIX 开头
|
||||
# 的文件/目录(包含上次失败遗留的中间产物),其它无关文件不受影响。
|
||||
COMMON_TMP_DIR="/tmp/backup_script"
|
||||
|
||||
# 归档/远端目录命名前缀,最终形如 ${COMMON_ARCHIVE_PREFIX}-YYYYmmdd-HHMMSS/
|
||||
COMMON_ARCHIVE_PREFIX="backup"
|
||||
|
||||
@@ -27,7 +27,7 @@ CONF_FILE="${SCRIPT_DIR}/backup.conf"
|
||||
# ---- 默认值 ----
|
||||
# 公共配置(统一以 COMMON_ 前缀,与 backup.conf 一致)
|
||||
COMMON_SOURCE_PATHS=""
|
||||
COMMON_TMP_DIR="/tmp/backup"
|
||||
COMMON_TMP_DIR="/tmp/backup_script"
|
||||
COMMON_ARCHIVE_PREFIX="backup"
|
||||
COMMON_CLEAN_LOCAL="true"
|
||||
COMMON_RETENTION_DAYS=0
|
||||
@@ -79,7 +79,7 @@ Methods:
|
||||
Common options:
|
||||
-C, --config FILE 指定配置文件路径(默认:脚本所在目录下 backup.conf)
|
||||
-s, --source PATHS 要备份的源路径,多个用空格分隔,需引号包裹
|
||||
-t, --tmp-dir DIR 本地临时目录(默认:/tmp/backup)
|
||||
-t, --tmp-dir DIR 本地临时目录(默认:/tmp/backup_script)
|
||||
-p, --prefix NAME 归档文件名前缀(默认:backup)
|
||||
--keep-local 上传后保留本地归档
|
||||
--retention DAYS 远端保留天数,0 表示不清理
|
||||
@@ -424,17 +424,34 @@ create_archive() {
|
||||
}
|
||||
|
||||
cleanup_local() {
|
||||
dbg "cleanup_local: COMMON_CLEAN_LOCAL=$COMMON_CLEAN_LOCAL 文件数=${#ARCHIVE_FILES[@]}"
|
||||
dbg "cleanup_local: COMMON_CLEAN_LOCAL=$COMMON_CLEAN_LOCAL COMMON_TMP_DIR=$COMMON_TMP_DIR PREFIX=$COMMON_ARCHIVE_PREFIX"
|
||||
if [[ "$COMMON_CLEAN_LOCAL" != "true" ]]; then
|
||||
dbg "cleanup_local: 跳过删除"
|
||||
return 0
|
||||
fi
|
||||
local p
|
||||
for p in "${ARCHIVE_FILES[@]}"; do
|
||||
if [[ -f "$p" ]]; then
|
||||
rm -f "$p" && log "已删除本地:$p"
|
||||
# 安全栅栏:必备字段不能为空,避免出现 rm -rf /* 这类灾难
|
||||
if [[ -z "$COMMON_TMP_DIR" || -z "$COMMON_ARCHIVE_PREFIX" ]]; then
|
||||
warn "COMMON_TMP_DIR 或 COMMON_ARCHIVE_PREFIX 为空,拒绝清理"
|
||||
return 0
|
||||
fi
|
||||
if [[ ! -d "$COMMON_TMP_DIR" ]]; then
|
||||
dbg "cleanup_local: 目录不存在,跳过"
|
||||
return 0
|
||||
fi
|
||||
# 仅清理所有以 COMMON_ARCHIVE_PREFIX 开头的文件/目录。
|
||||
# 这样既能带走本次的归档/分卷/sha,也能扫掉上次失败遗留(同样以该前缀开头)的
|
||||
# 中间产物(如 *.tar.gz、*.tar.gz.split-tmp.* 等),同时不会误伤同目录下的其它文件。
|
||||
local count=0 p
|
||||
shopt -s nullglob
|
||||
for p in "${COMMON_TMP_DIR%/}/${COMMON_ARCHIVE_PREFIX}"*; do
|
||||
rm -rf -- "$p" && count=$((count+1))
|
||||
done
|
||||
shopt -u nullglob
|
||||
if [[ $count -gt 0 ]]; then
|
||||
log "已清理本地以 \"${COMMON_ARCHIVE_PREFIX}\" 开头的文件/目录 ${count} 项(位于 ${COMMON_TMP_DIR})"
|
||||
else
|
||||
dbg "cleanup_local: 没有匹配 ${COMMON_ARCHIVE_PREFIX}* 的文件"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------- SMB ----------
|
||||
@@ -701,9 +718,6 @@ rclone_upload() {
|
||||
done
|
||||
ok "已上传全部 ${#ARCHIVE_FILES[@]} 个文件到 ${remote_dir}/"
|
||||
|
||||
# 远端 SHA256 校验:拉取远端 hash,与本地 .sha256 清单比对
|
||||
rclone_verify_remote "$remote_dir" || return 1
|
||||
|
||||
if [[ "$COMMON_RETENTION_DAYS" -gt 0 ]]; then
|
||||
rclone_retention
|
||||
else
|
||||
@@ -711,49 +725,6 @@ rclone_upload() {
|
||||
fi
|
||||
}
|
||||
|
||||
rclone_verify_remote() {
|
||||
local remote_dir="$1"
|
||||
local sha_local
|
||||
# 在 ARCHIVE_FILES 中找到 .sha256 清单(cleanup 之前)
|
||||
for f in "${ARCHIVE_FILES[@]}"; do
|
||||
if [[ "$f" == *.sha256 ]]; then sha_local="$f"; break; fi
|
||||
done
|
||||
if [[ -z "$sha_local" || ! -f "$sha_local" ]]; then
|
||||
warn "未找到本地 SHA256 清单,跳过远端校验"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "远端 SHA256 校验:${remote_dir}/"
|
||||
local remote_sha
|
||||
remote_sha="$(rclone_cmd hashsum SHA256 "$remote_dir" 2>/dev/null)" || {
|
||||
err "rclone hashsum 执行失败(远端可能不支持 SHA256,可手动下载后用 sha256sum -c 校验)"
|
||||
return 1
|
||||
}
|
||||
dbg "远端 hash 输出: $remote_sha"
|
||||
|
||||
# 本地清单格式: "<hash> <filename>";远端 rclone hashsum 输出同样格式但顺序可能不同
|
||||
# 逐行比对:以文件名为 key,hash 必须一致
|
||||
local fname expect actual
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
expect="${line%% *}"
|
||||
fname="${line##* }"
|
||||
# 跳过清单文件自身(不会出现在 ARCHIVE 主体里,但保险)
|
||||
[[ "$fname" == "$(basename "$sha_local")" ]] && continue
|
||||
actual="$(echo "$remote_sha" | awk -v f="$fname" '$2==f{print $1; exit}')"
|
||||
if [[ -z "$actual" ]]; then
|
||||
err "远端缺少文件:$fname"
|
||||
return 1
|
||||
fi
|
||||
if [[ "$expect" != "$actual" ]]; then
|
||||
err "远端 SHA256 不匹配:$fname (本地=$expect 远端=$actual)"
|
||||
return 1
|
||||
fi
|
||||
dbg " 校验通过:$fname"
|
||||
done < "$sha_local"
|
||||
ok "远端 SHA256 校验全部通过"
|
||||
}
|
||||
|
||||
rclone_retention() {
|
||||
dbg "rclone_retention: COMMON_RETENTION_DAYS=$COMMON_RETENTION_DAYS"
|
||||
local parent="${RCLONE_REMOTE}:${RCLONE_PATH%/}"
|
||||
|
||||
在新工单中引用
屏蔽一个用户