feat: rclone支持
这个提交包含在:
@@ -1,6 +1,6 @@
|
|||||||
# backup.sh
|
# backup.sh
|
||||||
|
|
||||||
把指定目录打包成 `tar.gz`,再通过 SMB / Samba 上传到远端服务器。同时支持 **Linux** 与 **macOS**。
|
把指定目录打包成 `tar.gz`,再上传到远端:当前支持 **SMB / Samba** 与 **rclone**(覆盖 Google Drive / OneDrive / S3 / WebDAV 等所有 rclone 支持的远端)。同时支持 **Linux** 与 **macOS**。
|
||||||
|
|
||||||
> SFTP 等其它方式已在脚本中预留入口(`run_sftp`、`SFTP_*` 配置),目前未实现。
|
> SFTP 等其它方式已在脚本中预留入口(`run_sftp`、`SFTP_*` 配置),目前未实现。
|
||||||
|
|
||||||
@@ -8,17 +8,18 @@
|
|||||||
|
|
||||||
- bash 3.2+ / tar / date / split
|
- bash 3.2+ / tar / date / split
|
||||||
- `sha256sum`(Linux 自带,属于 coreutils)或 `shasum`(macOS 自带)
|
- `sha256sum`(Linux 自带,属于 coreutils)或 `shasum`(macOS 自带)
|
||||||
- Linux:`smbclient`(包含在 `smbclient` 或 `samba-client` 包中)
|
- 上传 SMB:`smbclient`(Linux 一般在 `smbclient` 或 `samba-client` 包中;macOS 推荐 `brew install samba`,没装会回退到 `mount_smbfs`)
|
||||||
- macOS:`smbclient`(推荐,`brew install samba`),或退回到系统自带的 `mount_smbfs`
|
- 上传 rclone:`rclone`,且需提前用 `rclone config` 配好远端
|
||||||
|
|
||||||
### 安装依赖
|
### 安装依赖
|
||||||
|
|
||||||
| 系统 | 命令 |
|
| 系统 | SMB | rclone |
|
||||||
| --- | --- |
|
| --- | --- | --- |
|
||||||
| Debian / Ubuntu | `apt install -y smbclient` |
|
| Debian / Ubuntu | `apt install -y smbclient` | `apt install -y rclone` 或官方脚本 |
|
||||||
| RHEL / Rocky / Alma | `dnf install -y samba-client` |
|
| RHEL / Rocky / Alma | `dnf install -y samba-client` | `dnf install -y rclone` 或官方脚本 |
|
||||||
| Arch | `pacman -S smbclient` |
|
| Arch | `pacman -S smbclient` | `pacman -S rclone` |
|
||||||
| macOS | `brew install samba`(可选,无则使用 `mount_smbfs`) |
|
| macOS | `brew install samba`(可选) | `brew install rclone` |
|
||||||
|
| 通用 | — | `curl https://rclone.org/install.sh \| sudo bash` |
|
||||||
|
|
||||||
## 文件
|
## 文件
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ bash backup.sh <method> [options]
|
|||||||
| 方式 | 说明 |
|
| 方式 | 说明 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `smb` | 通过 SMB / Samba 上传 |
|
| `smb` | 通过 SMB / Samba 上传 |
|
||||||
|
| `rclone` | 通过 rclone 上传到网盘 / 对象存储等任意 rclone 支持的远端 |
|
||||||
| `sftp` | 已预留入口,暂未实现 |
|
| `sftp` | 已预留入口,暂未实现 |
|
||||||
|
|
||||||
最常见的用法是先编辑同目录下的 `backup.conf`,然后执行:
|
最常见的用法是先编辑同目录下的 `backup.conf`,然后执行:
|
||||||
@@ -99,6 +101,18 @@ bash backup.sh smb
|
|||||||
| `SMB_DOMAIN` | 域 / 工作组(可选) |
|
| `SMB_DOMAIN` | 域 / 工作组(可选) |
|
||||||
| `SMB_VERSION` | SMB 协议版本,如 `3.0`(可选) |
|
| `SMB_VERSION` | SMB 协议版本,如 `3.0`(可选) |
|
||||||
|
|
||||||
|
### rclone 段
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `RCLONE_EXECUTABLE` | rclone 可执行文件路径(可选,留空则从 `PATH` 中查找)。装在非标准路径时填这里 |
|
||||||
|
| `RCLONE_REMOTE` | rclone 远端名(**不带尾随冒号**),例如 `gdrive`。需提前用 `rclone config` 配好 |
|
||||||
|
| `RCLONE_PATH` | 远端目标子路径,例如 `vps-backup/web1` |
|
||||||
|
| `RCLONE_CONFIG` | 自定义 rclone.conf 路径(可选,留空则使用 `~/.config/rclone/rclone.conf`) |
|
||||||
|
| `RCLONE_FLAGS` | 透传给 `rclone` 的额外参数(可选),常用:`--bwlimit 10M --transfers 2 --tpslimit 4` |
|
||||||
|
|
||||||
|
> 网盘类远端(Google Drive、OneDrive 等)建议加 `--tpslimit` 限制每秒事务数,避免触发风控;带宽紧张的服务器加 `--bwlimit` 限速。
|
||||||
|
|
||||||
### SFTP 段(预留,暂未实现)
|
### SFTP 段(预留,暂未实现)
|
||||||
|
|
||||||
`SFTP_HOST` / `SFTP_PORT` / `SFTP_USER` / `SFTP_PASSWORD` / `SFTP_KEY` / `SFTP_PATH`
|
`SFTP_HOST` / `SFTP_PORT` / `SFTP_USER` / `SFTP_PASSWORD` / `SFTP_KEY` / `SFTP_PATH`
|
||||||
@@ -133,6 +147,16 @@ bash backup.sh smb
|
|||||||
| `--smb-domain DOMAIN` | `SMB_DOMAIN` |
|
| `--smb-domain DOMAIN` | `SMB_DOMAIN` |
|
||||||
| `--smb-version VER` | `SMB_VERSION` |
|
| `--smb-version VER` | `SMB_VERSION` |
|
||||||
|
|
||||||
|
### rclone
|
||||||
|
|
||||||
|
| 参数 | 对应配置 |
|
||||||
|
| --- | --- |
|
||||||
|
| `--rclone-executable F` | `RCLONE_EXECUTABLE` |
|
||||||
|
| `--rclone-remote NAME` | `RCLONE_REMOTE` |
|
||||||
|
| `--rclone-path PATH` | `RCLONE_PATH` |
|
||||||
|
| `--rclone-config FILE` | `RCLONE_CONFIG` |
|
||||||
|
| `--rclone-flags STR` | `RCLONE_FLAGS`(整串透传,需引号包裹) |
|
||||||
|
|
||||||
### SFTP(预留)
|
### SFTP(预留)
|
||||||
|
|
||||||
`--sftp-host` `--sftp-port` `--sftp-user` `--sftp-password` `--sftp-key` `--sftp-path`
|
`--sftp-host` `--sftp-port` `--sftp-user` `--sftp-password` `--sftp-key` `--sftp-path`
|
||||||
@@ -164,6 +188,35 @@ bash backup.sh smb \
|
|||||||
bash backup.sh smb -s "/var/lib/mysql"
|
bash backup.sh smb -s "/var/lib/mysql"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
rclone 上传到 Google Drive(先 `rclone config` 配好名为 `gdrive` 的远端):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash backup.sh rclone \
|
||||||
|
--rclone-remote gdrive \
|
||||||
|
--rclone-path vps-backup/web1 \
|
||||||
|
--rclone-flags "--bwlimit 10M --transfers 2 --tpslimit 4" \
|
||||||
|
--retention 30
|
||||||
|
```
|
||||||
|
|
||||||
|
完全依赖配置文件的 rclone 用法:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash backup.sh rclone
|
||||||
|
```
|
||||||
|
|
||||||
|
### rclone 远端的初始化
|
||||||
|
|
||||||
|
脚本本身不做交互式 OAuth,需要先在本机跑一次 `rclone config`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rclone config
|
||||||
|
# 选 n (new remote) -> 输入名字(如 gdrive)-> 选存储类型 -> 跟着提示走
|
||||||
|
rclone listremotes # 应能看到 "gdrive:"
|
||||||
|
rclone lsd gdrive: # 验证可访问
|
||||||
|
```
|
||||||
|
|
||||||
|
之后把同样的远端名填到 `backup.conf` 的 `RCLONE_REMOTE`(不带冒号)。
|
||||||
|
|
||||||
## 定时任务
|
## 定时任务
|
||||||
|
|
||||||
使用 `>`(单箭头)覆盖写入日志,避免日志文件无限增长——只保留最近一次执行的日志:
|
使用 `>`(单箭头)覆盖写入日志,避免日志文件无限增长——只保留最近一次执行的日志:
|
||||||
@@ -190,9 +243,10 @@ macOS 可用 `launchd` 或 `cron`(需要在「系统设置 → 隐私与安全
|
|||||||
|
|
||||||
## 远端目录结构
|
## 远端目录结构
|
||||||
|
|
||||||
每次备份在远端创建一个独立子目录,内含分卷文件与 SHA256 清单:
|
每次备份在远端创建一个独立子目录(SMB 与 rclone 行为一致),内含分卷文件与 SHA256 清单:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
# SMB
|
||||||
//${SMB_HOST}/${SMB_SHARE}/${SMB_PATH}/
|
//${SMB_HOST}/${SMB_SHARE}/${SMB_PATH}/
|
||||||
└── backup-20260426-031000/ # 本次备份目录
|
└── backup-20260426-031000/ # 本次备份目录
|
||||||
├── backup-20260426-031000.tar.gz.0 # 分卷 0
|
├── backup-20260426-031000.tar.gz.0 # 分卷 0
|
||||||
@@ -200,6 +254,13 @@ macOS 可用 `launchd` 或 `cron`(需要在「系统设置 → 隐私与安全
|
|||||||
├── backup-20260426-031000.tar.gz.2
|
├── backup-20260426-031000.tar.gz.2
|
||||||
├── ...
|
├── ...
|
||||||
└── backup-20260426-031000.sha256 # SHA256 清单(覆盖所有分卷)
|
└── backup-20260426-031000.sha256 # SHA256 清单(覆盖所有分卷)
|
||||||
|
|
||||||
|
# rclone
|
||||||
|
${RCLONE_REMOTE}:${RCLONE_PATH}/
|
||||||
|
└── backup-20260426-031000/
|
||||||
|
├── backup-20260426-031000.tar.gz.0
|
||||||
|
├── ...
|
||||||
|
└── backup-20260426-031000.sha256
|
||||||
```
|
```
|
||||||
|
|
||||||
未启用分卷(`COMMON_SPLIT_SIZE=""`)时目录里只有完整归档加清单:
|
未启用分卷(`COMMON_SPLIT_SIZE=""`)时目录里只有完整归档加清单:
|
||||||
@@ -285,3 +346,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` 路径需保证脚本运行用户可读。
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
# 备份方式由命令行第一个位置参数决定:
|
# 备份方式由命令行第一个位置参数决定:
|
||||||
# bash backup.sh smb -> 使用本文件 SMB 段配置
|
# bash backup.sh smb -> 使用本文件 SMB 段配置
|
||||||
|
# bash backup.sh rclone -> 使用本文件 RCLONE 段配置(上传到网盘等 rclone 支持的远端)
|
||||||
# bash backup.sh sftp -> 使用本文件 SFTP 段配置(暂未实现)
|
# bash backup.sh sftp -> 使用本文件 SFTP 段配置(暂未实现)
|
||||||
|
|
||||||
# ===== 公共配置(COMMON_*)=====
|
# ===== 公共配置(COMMON_*)=====
|
||||||
@@ -52,6 +53,27 @@ SMB_DOMAIN=""
|
|||||||
# SMB 协议版本(可选),例如 3.0
|
# SMB 协议版本(可选),例如 3.0
|
||||||
SMB_VERSION=""
|
SMB_VERSION=""
|
||||||
|
|
||||||
|
# ===== rclone 配置(仅 bash backup.sh rclone 使用)=====
|
||||||
|
# 前置条件:先在本机用 `rclone config` 配置好远端(OAuth 流程各家网盘不同,由 rclone 自己处理)。
|
||||||
|
# 配置完成后用 `rclone listremotes` 可看到远端名(带尾随冒号),填到 RCLONE_REMOTE 时不带冒号。
|
||||||
|
|
||||||
|
# rclone 可执行文件路径(可选,留空则从 PATH 中查找 `rclone`)
|
||||||
|
# 适用于 rclone 装在非标准路径(例如 /usr/local/bin/rclone、/opt/rclone/rclone)的情况
|
||||||
|
RCLONE_EXECUTABLE=""
|
||||||
|
|
||||||
|
# rclone 远端名(不带冒号),例如 gdrive / onedrive / s3-backup
|
||||||
|
RCLONE_REMOTE=""
|
||||||
|
|
||||||
|
# 远端目标子路径,例如 vps-backup/web1。最终路径 = ${RCLONE_REMOTE}:${RCLONE_PATH}/${ARCHIVE_BASENAME}/
|
||||||
|
RCLONE_PATH=""
|
||||||
|
|
||||||
|
# 自定义 rclone.conf 路径(可选,留空则使用 rclone 默认位置 ~/.config/rclone/rclone.conf)
|
||||||
|
RCLONE_CONFIG=""
|
||||||
|
|
||||||
|
# 透传给 rclone copy / purge / lsf 的额外参数(可选)
|
||||||
|
# 常用:--bwlimit 10M(限速) --transfers 2(并发数) --tpslimit 4(每秒事务上限,避免被风控)
|
||||||
|
RCLONE_FLAGS=""
|
||||||
|
|
||||||
# ===== SFTP 配置(仅 bash backup.sh sftp 使用,预留,暂未实现)=====
|
# ===== SFTP 配置(仅 bash backup.sh sftp 使用,预留,暂未实现)=====
|
||||||
SFTP_HOST=""
|
SFTP_HOST=""
|
||||||
SFTP_PORT="22"
|
SFTP_PORT="22"
|
||||||
|
|||||||
224
backup/backup.sh
224
backup/backup.sh
@@ -42,6 +42,13 @@ SMB_PASSWORD=""
|
|||||||
SMB_DOMAIN=""
|
SMB_DOMAIN=""
|
||||||
SMB_VERSION=""
|
SMB_VERSION=""
|
||||||
|
|
||||||
|
# rclone 专属
|
||||||
|
RCLONE_EXECUTABLE=""
|
||||||
|
RCLONE_REMOTE=""
|
||||||
|
RCLONE_PATH=""
|
||||||
|
RCLONE_CONFIG=""
|
||||||
|
RCLONE_FLAGS=""
|
||||||
|
|
||||||
# SFTP 专属(预留)
|
# SFTP 专属(预留)
|
||||||
SFTP_HOST=""
|
SFTP_HOST=""
|
||||||
SFTP_PORT="22"
|
SFTP_PORT="22"
|
||||||
@@ -66,6 +73,7 @@ Usage: bash backup.sh <method> [options]
|
|||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
smb 通过 SMB / Samba 协议上传
|
smb 通过 SMB / Samba 协议上传
|
||||||
|
rclone 通过 rclone 上传到网盘等远端(远端名需先用 rclone config 配置好)
|
||||||
sftp 通过 SFTP 上传(暂未实现,已预留入口)
|
sftp 通过 SFTP 上传(暂未实现,已预留入口)
|
||||||
|
|
||||||
Common options:
|
Common options:
|
||||||
@@ -88,6 +96,13 @@ SMB options:
|
|||||||
--smb-domain DOMAIN
|
--smb-domain DOMAIN
|
||||||
--smb-version VER 例如 3.0
|
--smb-version VER 例如 3.0
|
||||||
|
|
||||||
|
rclone options:
|
||||||
|
--rclone-executable F rclone 可执行文件路径(可选,默认从 PATH 查找)
|
||||||
|
--rclone-remote NAME 远端名(不带冒号),例如 gdrive
|
||||||
|
--rclone-path PATH 远端目标子路径
|
||||||
|
--rclone-config FILE 自定义 rclone.conf 路径
|
||||||
|
--rclone-flags STR 透传给 rclone 的额外参数,如 "--bwlimit 10M --transfers 2"
|
||||||
|
|
||||||
SFTP options (预留):
|
SFTP options (预留):
|
||||||
--sftp-host HOST
|
--sftp-host HOST
|
||||||
--sftp-port PORT
|
--sftp-port PORT
|
||||||
@@ -100,6 +115,8 @@ Examples:
|
|||||||
bash backup.sh smb
|
bash backup.sh smb
|
||||||
bash backup.sh smb --smb-host 192.168.1.10 --smb-share backup --smb-user u --smb-password p
|
bash backup.sh smb --smb-host 192.168.1.10 --smb-share backup --smb-user u --smb-password p
|
||||||
bash backup.sh smb -s "/etc /var/log" -C /path/to/backup.conf
|
bash backup.sh smb -s "/etc /var/log" -C /path/to/backup.conf
|
||||||
|
bash backup.sh rclone --rclone-remote gdrive --rclone-path vps-backup/web1
|
||||||
|
bash backup.sh rclone --rclone-flags "--bwlimit 10M --transfers 2"
|
||||||
bash backup.sh smb --debug
|
bash backup.sh smb --debug
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -149,6 +166,13 @@ print_effective_config() {
|
|||||||
dbg "SMB_DOMAIN=$SMB_DOMAIN"
|
dbg "SMB_DOMAIN=$SMB_DOMAIN"
|
||||||
dbg "SMB_VERSION=$SMB_VERSION"
|
dbg "SMB_VERSION=$SMB_VERSION"
|
||||||
;;
|
;;
|
||||||
|
rclone)
|
||||||
|
dbg "RCLONE_EXECUTABLE=$RCLONE_EXECUTABLE"
|
||||||
|
dbg "RCLONE_REMOTE=$RCLONE_REMOTE"
|
||||||
|
dbg "RCLONE_PATH=$RCLONE_PATH"
|
||||||
|
dbg "RCLONE_CONFIG=$RCLONE_CONFIG"
|
||||||
|
dbg "RCLONE_FLAGS=$RCLONE_FLAGS"
|
||||||
|
;;
|
||||||
sftp)
|
sftp)
|
||||||
dbg "SFTP_HOST=$SFTP_HOST"
|
dbg "SFTP_HOST=$SFTP_HOST"
|
||||||
dbg "SFTP_PORT=$SFTP_PORT"
|
dbg "SFTP_PORT=$SFTP_PORT"
|
||||||
@@ -196,7 +220,7 @@ parse_args() {
|
|||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
smb|sftp) METHOD_ARG="$1"; shift ;;
|
smb|sftp|rclone) METHOD_ARG="$1"; shift ;;
|
||||||
*) err "未知方式:$1"; usage; exit 1 ;;
|
*) err "未知方式:$1"; usage; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
dbg "parse_args: METHOD_ARG=$METHOD_ARG,剩余参数($#)=$*"
|
dbg "parse_args: METHOD_ARG=$METHOD_ARG,剩余参数($#)=$*"
|
||||||
@@ -234,6 +258,11 @@ parse_args() {
|
|||||||
--smb-password) SMB_PASSWORD="$2"; shift 2 ;;
|
--smb-password) SMB_PASSWORD="$2"; shift 2 ;;
|
||||||
--smb-domain) SMB_DOMAIN="$2"; shift 2 ;;
|
--smb-domain) SMB_DOMAIN="$2"; shift 2 ;;
|
||||||
--smb-version) SMB_VERSION="$2"; shift 2 ;;
|
--smb-version) SMB_VERSION="$2"; shift 2 ;;
|
||||||
|
--rclone-executable) RCLONE_EXECUTABLE="$2"; shift 2 ;;
|
||||||
|
--rclone-remote) RCLONE_REMOTE="$2"; shift 2 ;;
|
||||||
|
--rclone-path) RCLONE_PATH="$2"; shift 2 ;;
|
||||||
|
--rclone-config) RCLONE_CONFIG="$2"; shift 2 ;;
|
||||||
|
--rclone-flags) RCLONE_FLAGS="$2"; shift 2 ;;
|
||||||
--sftp-host) SFTP_HOST="$2"; shift 2 ;;
|
--sftp-host) SFTP_HOST="$2"; shift 2 ;;
|
||||||
--sftp-port) SFTP_PORT="$2"; shift 2 ;;
|
--sftp-port) SFTP_PORT="$2"; shift 2 ;;
|
||||||
--sftp-user) SFTP_USER="$2"; shift 2 ;;
|
--sftp-user) SFTP_USER="$2"; shift 2 ;;
|
||||||
@@ -591,6 +620,192 @@ run_smb() {
|
|||||||
cleanup_local
|
cleanup_local
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------- rclone ----------
|
||||||
|
|
||||||
|
rclone_check_deps() {
|
||||||
|
if [[ -n "$RCLONE_EXECUTABLE" ]]; then
|
||||||
|
if [[ ! -x "$RCLONE_EXECUTABLE" ]]; then
|
||||||
|
err "RCLONE_EXECUTABLE 不存在或不可执行:$RCLONE_EXECUTABLE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
dbg "rclone_check_deps: 使用自定义路径 $RCLONE_EXECUTABLE"
|
||||||
|
elif command -v rclone >/dev/null 2>&1; then
|
||||||
|
RCLONE_EXECUTABLE="$(command -v rclone)"
|
||||||
|
dbg "rclone_check_deps: 从 PATH 找到 $RCLONE_EXECUTABLE"
|
||||||
|
else
|
||||||
|
err "未安装 rclone(PATH 中找不到,且未指定 RCLONE_EXECUTABLE)。安装方式:curl https://rclone.org/install.sh | sudo bash"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
dbg "rclone_check_deps: $("$RCLONE_EXECUTABLE" version 2>/dev/null | head -1)"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rclone_validate() {
|
||||||
|
dbg "rclone_validate: REMOTE='$RCLONE_REMOTE' PATH='$RCLONE_PATH' CONFIG='$RCLONE_CONFIG'"
|
||||||
|
[[ -z "$RCLONE_REMOTE" ]] && { err "缺少 RCLONE_REMOTE(用 \`rclone listremotes\` 查看已配置的远端)"; return 1; }
|
||||||
|
[[ -z "$RCLONE_PATH" ]] && { err "缺少 RCLONE_PATH"; return 1; }
|
||||||
|
|
||||||
|
# 自定义 rclone.conf 路径必须存在
|
||||||
|
if [[ -n "$RCLONE_CONFIG" && ! -f "$RCLONE_CONFIG" ]]; then
|
||||||
|
err "RCLONE_CONFIG 指定的文件不存在:$RCLONE_CONFIG"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查远端是否在 rclone 中已注册
|
||||||
|
local listremotes
|
||||||
|
listremotes="$(rclone_cmd listremotes 2>/dev/null)" || {
|
||||||
|
err "rclone listremotes 执行失败,请检查 rclone 配置"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if ! echo "$listremotes" | grep -qx "${RCLONE_REMOTE}:"; then
|
||||||
|
err "rclone 中未找到远端 \"${RCLONE_REMOTE}:\"。已配置的远端:"
|
||||||
|
echo "$listremotes" >&2
|
||||||
|
err "请先运行:rclone config"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 包装 rclone,统一使用 RCLONE_EXECUTABLE,并带上 --config(如指定)和透传的 RCLONE_FLAGS
|
||||||
|
rclone_cmd() {
|
||||||
|
local args=()
|
||||||
|
[[ -n "$RCLONE_CONFIG" ]] && args+=( --config "$RCLONE_CONFIG" )
|
||||||
|
# RCLONE_FLAGS 是空格分隔的字符串,按词拆分透传
|
||||||
|
# shellcheck disable=SC2206
|
||||||
|
local extra=( $RCLONE_FLAGS )
|
||||||
|
"$RCLONE_EXECUTABLE" "$@" "${args[@]}" "${extra[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
rclone_upload() {
|
||||||
|
dbg "rclone_upload: 待上传=${#ARCHIVE_FILES[@]} 远端目录=${RCLONE_REMOTE}:${RCLONE_PATH%/}/${ARCHIVE_BASENAME}/"
|
||||||
|
local remote_dir="${RCLONE_REMOTE}:${RCLONE_PATH%/}/${ARCHIVE_BASENAME}"
|
||||||
|
|
||||||
|
log "通过 rclone 上传到 ${remote_dir}/"
|
||||||
|
# 让 rclone 每 5 秒打印一行进度(已传/总量/百分比/速率/ETA),写到 stdout 而不是被 INFO 级别压住。
|
||||||
|
# --stats-one-line 让进度落在单行;NOTICE 级别能在非交互(cron)下也输出。
|
||||||
|
local stats_flags=( --stats=5s --stats-one-line --stats-log-level NOTICE )
|
||||||
|
local pp pname pkb start_ts elapsed rc
|
||||||
|
for pp in "${ARCHIVE_FILES[@]}"; do
|
||||||
|
pname="$(basename "$pp")"
|
||||||
|
pkb="$(du -k "$pp" 2>/dev/null | awk '{print $1+0}')"
|
||||||
|
log "上传:$pname ($(awk -v k="$pkb" 'BEGIN{if(k>=1024*1024)printf "%.2fG", k/1024/1024; else if(k>=1024)printf "%.1fM", k/1024; else printf "%dK", k}'))"
|
||||||
|
start_ts=$(date +%s)
|
||||||
|
rclone_cmd copyto "${stats_flags[@]}" "$pp" "${remote_dir}/${pname}"
|
||||||
|
rc=$?
|
||||||
|
elapsed=$(( $(date +%s) - start_ts ))
|
||||||
|
if [[ $rc -ne 0 ]]; then
|
||||||
|
err "rclone 上传失败:$pp (耗时 ${elapsed}s)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
ok "上传完成:$pname (耗时 ${elapsed}s)"
|
||||||
|
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
|
||||||
|
dbg "rclone_upload: COMMON_RETENTION_DAYS=0,跳过远端清理"
|
||||||
|
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%/}"
|
||||||
|
local listing
|
||||||
|
listing="$(rclone_cmd lsf --dirs-only "$parent" 2>/dev/null)" || {
|
||||||
|
warn "rclone lsf 失败,跳过远端清理"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
local cutoff
|
||||||
|
if date -v-1d >/dev/null 2>&1; then
|
||||||
|
cutoff=$(date -v-"${COMMON_RETENTION_DAYS}"d +%s)
|
||||||
|
else
|
||||||
|
cutoff=$(date -d "-${COMMON_RETENTION_DAYS} days" +%s)
|
||||||
|
fi
|
||||||
|
log "清理远端早于 ${COMMON_RETENTION_DAYS} 天的备份目录"
|
||||||
|
dbg "cutoff=$cutoff parent=$parent"
|
||||||
|
|
||||||
|
# rclone lsf --dirs-only 输出形如 "name/",去掉尾随斜杠后做名字匹配
|
||||||
|
echo "$listing" | sed 's:/$::' | grep -E "^${COMMON_ARCHIVE_PREFIX}-[0-9]{8}-[0-9]{6}\$" | while read -r dir; do
|
||||||
|
local d="${dir#${COMMON_ARCHIVE_PREFIX}-}"
|
||||||
|
d="${d%%-*}" # YYYYmmdd
|
||||||
|
local t="${dir##*-}" # HHMMSS
|
||||||
|
local iso="${d:0:4}-${d:4:2}-${d:6:2} ${t:0:2}:${t:2:2}:${t:4:2}"
|
||||||
|
local ftime
|
||||||
|
if date -j -f "%Y-%m-%d %H:%M:%S" "$iso" +%s >/dev/null 2>&1; then
|
||||||
|
ftime=$(date -j -f "%Y-%m-%d %H:%M:%S" "$iso" +%s)
|
||||||
|
else
|
||||||
|
ftime=$(date -d "$iso" +%s)
|
||||||
|
fi
|
||||||
|
dbg " 远端目录 $dir -> ftime=$ftime"
|
||||||
|
if [[ "$ftime" -lt "$cutoff" ]]; then
|
||||||
|
log "删除远端旧备份目录:$dir"
|
||||||
|
if ! rclone_cmd purge "${parent}/${dir}" >/dev/null 2>&1; then
|
||||||
|
warn "purge 失败:$dir"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
run_rclone() {
|
||||||
|
dbg "run_rclone: 开始"
|
||||||
|
rclone_check_deps || return 1
|
||||||
|
rclone_validate || return 1
|
||||||
|
print_effective_config
|
||||||
|
create_archive || return 1
|
||||||
|
|
||||||
|
rclone_upload || { cleanup_local; return 1; }
|
||||||
|
ok "rclone 上传完成"
|
||||||
|
cleanup_local
|
||||||
|
}
|
||||||
|
|
||||||
# ---------- SFTP(预留)----------
|
# ---------- SFTP(预留)----------
|
||||||
|
|
||||||
run_sftp() {
|
run_sftp() {
|
||||||
@@ -614,9 +829,10 @@ main() {
|
|||||||
print_effective_config
|
print_effective_config
|
||||||
|
|
||||||
case "$METHOD" in
|
case "$METHOD" in
|
||||||
smb) run_smb ;;
|
smb) run_smb ;;
|
||||||
sftp) run_sftp ;;
|
rclone) run_rclone ;;
|
||||||
*) err "不支持的方式:$METHOD"; exit 1 ;;
|
sftp) run_sftp ;;
|
||||||
|
*) err "不支持的方式:$METHOD"; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
local rc=$?
|
local rc=$?
|
||||||
dbg "main: 退出码=$rc"
|
dbg "main: 退出码=$rc"
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户