feat: 修改逻辑

这个提交包含在:
HA
2026-04-26 23:21:40 +08:00
父节点 abeb6f2e80
当前提交 2db4f4b530
共有 3 个文件被更改,包括 32 次插入58 次删除

查看文件

@@ -84,7 +84,8 @@ bash backup.sh smb
| 字段 | 说明 | | 字段 | 说明 |
| --- | --- | | --- | --- |
| `COMMON_SOURCE_PATHS` | 要备份的源路径,多个用空格分隔,需引号包裹 | | `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_ARCHIVE_PREFIX` | 归档/远端目录命名前缀,最终形如 `prefix-YYYYmmdd-HHMMSS/` |
| `COMMON_CLEAN_LOCAL` | 上传后是否删除本地归档(`true` / `false` | | `COMMON_CLEAN_LOCAL` | 上传后是否删除本地归档(`true` / `false` |
| `COMMON_RETENTION_DAYS` | 远端保留天数,`0` 表示不清理;按目录整体清理 | | `COMMON_RETENTION_DAYS` | 远端保留天数,`0` 表示不清理;按目录整体清理 |
@@ -346,5 +347,5 @@ tar -xzf backup-20260426-031000.tar.gz
- 命令行密码会出现在进程列表中,安全敏感场景请优先使用 `backup.conf`,并把权限收紧:`chmod 600 backup.conf` - 命令行密码会出现在进程列表中,安全敏感场景请优先使用 `backup.conf`,并把权限收紧:`chmod 600 backup.conf`
- macOS 上若未安装 `smbclient`,会回退到挂载方式(`mount_smbfs`),此时不支持 `--retention` 远端清理,仅完成上传。 - macOS 上若未安装 `smbclient`,会回退到挂载方式(`mount_smbfs`),此时不支持 `--retention` 远端清理,仅完成上传。
- 远端清理仅清理符合 `${COMMON_ARCHIVE_PREFIX}-YYYYmmdd-HHMMSS` 命名规范的目录,避免误删其它内容。 - 远端清理仅清理符合 `${COMMON_ARCHIVE_PREFIX}-YYYYmmdd-HHMMSS` 命名规范的目录,避免误删其它内容。
- rclone 上传完成后会自动用 `rclone hashsum SHA256` 拉取远端 hash 与本地 `.sha256` 清单逐项比对,校验失败会以非 0 状态码退出。少数远端(个别 WebDAV不支持 SHA256 hash,遇到这种情况脚本会报错退出,可改成手动下载后用 `sha256sum -c` 校验。
- rclone 远端必须先在本机用 `rclone config` 配好;自定义 `RCLONE_CONFIG` 路径需保证脚本运行用户可读。 - rclone 远端必须先在本机用 `rclone config` 配好;自定义 `RCLONE_CONFIG` 路径需保证脚本运行用户可读。
- 脚本不在上传后做远端 SHA256 校验(不同后端对 hash 的支持差异太大)。如需校验,恢复时进入备份目录用 `sha256sum -c` 对照同目录下的 `.sha256` 清单即可。

查看文件

@@ -11,8 +11,10 @@
# 要备份的源目录或文件(多个用空格分隔,需引号包裹) # 要备份的源目录或文件(多个用空格分隔,需引号包裹)
COMMON_SOURCE_PATHS="/opt" COMMON_SOURCE_PATHS="/opt"
# 本地临时打包目录(用于存放归档/分卷/sha 文件,备份完成后会清理 # 本地临时打包目录(用于存放归档/分卷/sha 文件)
COMMON_TMP_DIR="/tmp/backup" # 注意:当 COMMON_CLEAN_LOCAL=true 时,会清理目录下所有以 COMMON_ARCHIVE_PREFIX 开头
# 的文件/目录(包含上次失败遗留的中间产物),其它无关文件不受影响。
COMMON_TMP_DIR="/tmp/backup_script"
# 归档/远端目录命名前缀,最终形如 ${COMMON_ARCHIVE_PREFIX}-YYYYmmdd-HHMMSS/ # 归档/远端目录命名前缀,最终形如 ${COMMON_ARCHIVE_PREFIX}-YYYYmmdd-HHMMSS/
COMMON_ARCHIVE_PREFIX="backup" COMMON_ARCHIVE_PREFIX="backup"

查看文件

@@ -27,7 +27,7 @@ CONF_FILE="${SCRIPT_DIR}/backup.conf"
# ---- 默认值 ---- # ---- 默认值 ----
# 公共配置(统一以 COMMON_ 前缀,与 backup.conf 一致) # 公共配置(统一以 COMMON_ 前缀,与 backup.conf 一致)
COMMON_SOURCE_PATHS="" COMMON_SOURCE_PATHS=""
COMMON_TMP_DIR="/tmp/backup" COMMON_TMP_DIR="/tmp/backup_script"
COMMON_ARCHIVE_PREFIX="backup" COMMON_ARCHIVE_PREFIX="backup"
COMMON_CLEAN_LOCAL="true" COMMON_CLEAN_LOCAL="true"
COMMON_RETENTION_DAYS=0 COMMON_RETENTION_DAYS=0
@@ -79,7 +79,7 @@ Methods:
Common options: Common options:
-C, --config FILE 指定配置文件路径(默认:脚本所在目录下 backup.conf -C, --config FILE 指定配置文件路径(默认:脚本所在目录下 backup.conf
-s, --source PATHS 要备份的源路径,多个用空格分隔,需引号包裹 -s, --source PATHS 要备份的源路径,多个用空格分隔,需引号包裹
-t, --tmp-dir DIR 本地临时目录(默认:/tmp/backup -t, --tmp-dir DIR 本地临时目录(默认:/tmp/backup_script
-p, --prefix NAME 归档文件名前缀默认backup -p, --prefix NAME 归档文件名前缀默认backup
--keep-local 上传后保留本地归档 --keep-local 上传后保留本地归档
--retention DAYS 远端保留天数,0 表示不清理 --retention DAYS 远端保留天数,0 表示不清理
@@ -424,17 +424,34 @@ create_archive() {
} }
cleanup_local() { 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 if [[ "$COMMON_CLEAN_LOCAL" != "true" ]]; then
dbg "cleanup_local: 跳过删除" dbg "cleanup_local: 跳过删除"
return 0 return 0
fi fi
local p # 安全栅栏:必备字段不能为空,避免出现 rm -rf /* 这类灾难
for p in "${ARCHIVE_FILES[@]}"; do if [[ -z "$COMMON_TMP_DIR" || -z "$COMMON_ARCHIVE_PREFIX" ]]; then
if [[ -f "$p" ]]; then warn "COMMON_TMP_DIR 或 COMMON_ARCHIVE_PREFIX 为空,拒绝清理"
rm -f "$p" && log "已删除本地:$p" return 0
fi 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 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 ---------- # ---------- SMB ----------
@@ -701,9 +718,6 @@ rclone_upload() {
done done
ok "已上传全部 ${#ARCHIVE_FILES[@]} 个文件到 ${remote_dir}/" ok "已上传全部 ${#ARCHIVE_FILES[@]} 个文件到 ${remote_dir}/"
# 远端 SHA256 校验:拉取远端 hash,与本地 .sha256 清单比对
rclone_verify_remote "$remote_dir" || return 1
if [[ "$COMMON_RETENTION_DAYS" -gt 0 ]]; then if [[ "$COMMON_RETENTION_DAYS" -gt 0 ]]; then
rclone_retention rclone_retention
else else
@@ -711,49 +725,6 @@ rclone_upload() {
fi 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() { rclone_retention() {
dbg "rclone_retention: COMMON_RETENTION_DAYS=$COMMON_RETENTION_DAYS" dbg "rclone_retention: COMMON_RETENTION_DAYS=$COMMON_RETENTION_DAYS"
local parent="${RCLONE_REMOTE}:${RCLONE_PATH%/}" local parent="${RCLONE_REMOTE}:${RCLONE_PATH%/}"