feat: 添加调试日志
这个提交包含在:
@@ -104,6 +104,7 @@ bash backup.sh smb
|
|||||||
| `-p, --prefix NAME` | `ARCHIVE_PREFIX` |
|
| `-p, --prefix NAME` | `ARCHIVE_PREFIX` |
|
||||||
| `--keep-local` | 等价于 `CLEAN_LOCAL=false` |
|
| `--keep-local` | 等价于 `CLEAN_LOCAL=false` |
|
||||||
| `--retention DAYS` | `RETENTION_DAYS` |
|
| `--retention DAYS` | `RETENTION_DAYS` |
|
||||||
|
| `--debug` | 打印详细调试日志(也可用 `DEBUG=true` 环境变量) |
|
||||||
| `-h, --help` | 显示帮助 |
|
| `-h, --help` | 显示帮助 |
|
||||||
|
|
||||||
### SMB
|
### SMB
|
||||||
|
|||||||
186
backup/backup.sh
186
backup/backup.sh
@@ -10,12 +10,16 @@ RED='\033[0;31m'
|
|||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
CYAN='\033[0;36m'
|
CYAN='\033[0;36m'
|
||||||
|
MAGENTA='\033[0;35m'
|
||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
|
|
||||||
|
DEBUG="${DEBUG:-false}"
|
||||||
|
|
||||||
log() { printf "${CYAN}[%s]${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
log() { printf "${CYAN}[%s]${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
||||||
warn() { printf "${YELLOW}[%s] WARN:${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
warn() { printf "${YELLOW}[%s] WARN:${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
||||||
err() { printf "${RED}[%s] ERROR:${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
|
err() { printf "${RED}[%s] ERROR:${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
|
||||||
ok() { printf "${GREEN}[%s]${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
ok() { printf "${GREEN}[%s]${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
||||||
|
dbg() { [[ "$DEBUG" == "true" ]] && printf "${MAGENTA}[%s] DEBUG:${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2 || true; }
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
CONF_FILE="${SCRIPT_DIR}/backup.conf"
|
CONF_FILE="${SCRIPT_DIR}/backup.conf"
|
||||||
@@ -44,6 +48,9 @@ SFTP_KEY=""
|
|||||||
SFTP_PATH=""
|
SFTP_PATH=""
|
||||||
|
|
||||||
OS_NAME="$(uname -s)"
|
OS_NAME="$(uname -s)"
|
||||||
|
ARCHIVE_FILE=""
|
||||||
|
ARCHIVE_NAME=""
|
||||||
|
SMB_TOOL=""
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -60,6 +67,7 @@ Common options:
|
|||||||
-p, --prefix NAME 归档文件名前缀(默认:backup)
|
-p, --prefix NAME 归档文件名前缀(默认:backup)
|
||||||
--keep-local 上传后保留本地归档
|
--keep-local 上传后保留本地归档
|
||||||
--retention DAYS 远端保留天数,0 表示不清理
|
--retention DAYS 远端保留天数,0 表示不清理
|
||||||
|
--debug 打印详细调试日志(也可用环境变量 DEBUG=true)
|
||||||
-h, --help 显示帮助
|
-h, --help 显示帮助
|
||||||
|
|
||||||
SMB options:
|
SMB options:
|
||||||
@@ -83,44 +91,111 @@ 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 smb --debug
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
load_conf() {
|
mask() {
|
||||||
if [[ -f "$CONF_FILE" ]]; then
|
# 给敏感值打码:保留前 1 位,其余替换成 *
|
||||||
# shellcheck disable=SC1090
|
local s="$1"
|
||||||
source "$CONF_FILE"
|
[[ -z "$s" ]] && { echo "(empty)"; return; }
|
||||||
log "已加载配置文件:$CONF_FILE"
|
local len=${#s}
|
||||||
|
if [[ $len -le 2 ]]; then
|
||||||
|
printf '%s\n' "**"
|
||||||
else
|
else
|
||||||
warn "配置文件不存在:$CONF_FILE(仅使用命令行参数)"
|
printf '%s%s\n' "${s:0:1}" "$(printf '%*s' $((len-1)) '' | tr ' ' '*')"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print_runtime_env() {
|
||||||
|
dbg "===== 运行时环境 ====="
|
||||||
|
dbg "OS_NAME=$OS_NAME"
|
||||||
|
dbg "uname -a: $(uname -a 2>/dev/null)"
|
||||||
|
dbg "BASH_VERSION=$BASH_VERSION"
|
||||||
|
dbg "PWD=$(pwd)"
|
||||||
|
dbg "SCRIPT_DIR=$SCRIPT_DIR"
|
||||||
|
dbg "脚本路径=${BASH_SOURCE[0]}"
|
||||||
|
dbg "EUID=$EUID USER=${USER:-?}"
|
||||||
|
dbg "PATH=$PATH"
|
||||||
|
dbg "原始参数($#): $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_effective_config() {
|
||||||
|
dbg "===== 生效配置 ====="
|
||||||
|
dbg "CONF_FILE=$CONF_FILE"
|
||||||
|
dbg "METHOD=$METHOD"
|
||||||
|
dbg "SOURCE_PATHS=$SOURCE_PATHS"
|
||||||
|
dbg "TMP_DIR=$TMP_DIR"
|
||||||
|
dbg "ARCHIVE_PREFIX=$ARCHIVE_PREFIX"
|
||||||
|
dbg "CLEAN_LOCAL=$CLEAN_LOCAL"
|
||||||
|
dbg "RETENTION_DAYS=$RETENTION_DAYS"
|
||||||
|
dbg "SMB_HOST=$SMB_HOST"
|
||||||
|
dbg "SMB_SHARE=$SMB_SHARE"
|
||||||
|
dbg "SMB_PATH=$SMB_PATH"
|
||||||
|
dbg "SMB_USER=$SMB_USER"
|
||||||
|
dbg "SMB_PASSWORD=$(mask "$SMB_PASSWORD")"
|
||||||
|
dbg "SMB_DOMAIN=$SMB_DOMAIN"
|
||||||
|
dbg "SMB_VERSION=$SMB_VERSION"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_conf() {
|
||||||
|
dbg "load_conf: 读取 $CONF_FILE"
|
||||||
|
if [[ -f "$CONF_FILE" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$CONF_FILE"
|
||||||
|
local rc=$?
|
||||||
|
if [[ $rc -ne 0 ]]; then
|
||||||
|
err "配置文件加载失败(rc=$rc):$CONF_FILE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log "已加载配置文件:$CONF_FILE"
|
||||||
|
dbg "load_conf: source 成功,rc=$rc"
|
||||||
|
else
|
||||||
|
warn "配置文件不存在:$CONF_FILE(仅使用命令行参数)"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
parse_args() {
|
parse_args() {
|
||||||
|
dbg "parse_args: 入参 ($#) = $*"
|
||||||
|
|
||||||
if [[ $# -lt 1 ]]; then
|
if [[ $# -lt 1 ]]; then
|
||||||
usage; exit 1
|
usage; exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 第 0 遍:扫描 --debug,让后续日志能打印
|
||||||
|
local a
|
||||||
|
for a in "$@"; do
|
||||||
|
if [[ "$a" == "--debug" ]]; then
|
||||||
|
DEBUG="true"
|
||||||
|
dbg "已启用 DEBUG 模式"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
smb|sftp) METHOD_ARG="$1"; shift ;;
|
smb|sftp) METHOD_ARG="$1"; shift ;;
|
||||||
*) err "未知方式:$1"; usage; exit 1 ;;
|
*) err "未知方式:$1"; usage; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
dbg "parse_args: METHOD_ARG=$METHOD_ARG,剩余参数($#)=$*"
|
||||||
|
|
||||||
# 第一遍只取 --config / -C,使后续参数能覆盖配置
|
# 第 1 遍只取 --config / -C,使后续参数能覆盖配置
|
||||||
# 使用 for 循环 + prev 变量,兼容 bash 3.2(避免数组索引在 set -u 下出问题)
|
|
||||||
local prev=""
|
local prev=""
|
||||||
local arg
|
local arg
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
if [[ "$prev" == "-C" || "$prev" == "--config" ]]; then
|
if [[ "$prev" == "-C" || "$prev" == "--config" ]]; then
|
||||||
CONF_FILE="$arg"
|
CONF_FILE="$arg"
|
||||||
|
dbg "parse_args: 命令行覆盖 CONF_FILE=$CONF_FILE"
|
||||||
fi
|
fi
|
||||||
prev="$arg"
|
prev="$arg"
|
||||||
done
|
done
|
||||||
|
|
||||||
load_conf
|
load_conf || return 1
|
||||||
METHOD="$METHOD_ARG"
|
METHOD="$METHOD_ARG"
|
||||||
|
dbg "parse_args: 进入第 2 遍参数解析"
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
|
dbg " parse: 当前=$1"
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-C|--config) shift 2 ;; # 已处理
|
-C|--config) shift 2 ;; # 已处理
|
||||||
-s|--source) SOURCE_PATHS="$2"; shift 2 ;;
|
-s|--source) SOURCE_PATHS="$2"; shift 2 ;;
|
||||||
@@ -128,6 +203,7 @@ parse_args() {
|
|||||||
-p|--prefix) ARCHIVE_PREFIX="$2"; shift 2 ;;
|
-p|--prefix) ARCHIVE_PREFIX="$2"; shift 2 ;;
|
||||||
--keep-local) CLEAN_LOCAL="false"; shift ;;
|
--keep-local) CLEAN_LOCAL="false"; shift ;;
|
||||||
--retention) RETENTION_DAYS="$2"; shift 2 ;;
|
--retention) RETENTION_DAYS="$2"; shift 2 ;;
|
||||||
|
--debug) DEBUG="true"; shift ;;
|
||||||
--smb-host) SMB_HOST="$2"; shift 2 ;;
|
--smb-host) SMB_HOST="$2"; shift 2 ;;
|
||||||
--smb-share) SMB_SHARE="$2"; shift 2 ;;
|
--smb-share) SMB_SHARE="$2"; shift 2 ;;
|
||||||
--smb-path) SMB_PATH="$2"; shift 2 ;;
|
--smb-path) SMB_PATH="$2"; shift 2 ;;
|
||||||
@@ -145,68 +221,89 @@ parse_args() {
|
|||||||
*) err "未知参数:$1"; usage; exit 1 ;;
|
*) err "未知参数:$1"; usage; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
dbg "parse_args: 解析完成"
|
||||||
|
return 0
|
||||||
require_cmd() {
|
|
||||||
for c in "$@"; do
|
|
||||||
if ! command -v "$c" >/dev/null 2>&1; then
|
|
||||||
err "缺少依赖命令:$c"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create_archive() {
|
create_archive() {
|
||||||
|
dbg "create_archive: 入口 SOURCE_PATHS='$SOURCE_PATHS' TMP_DIR='$TMP_DIR'"
|
||||||
[[ -z "$SOURCE_PATHS" ]] && { err "未配置 SOURCE_PATHS"; return 1; }
|
[[ -z "$SOURCE_PATHS" ]] && { err "未配置 SOURCE_PATHS"; return 1; }
|
||||||
|
|
||||||
mkdir -p "$TMP_DIR" || { err "无法创建临时目录:$TMP_DIR"; return 1; }
|
if ! mkdir -p "$TMP_DIR"; then
|
||||||
|
err "无法创建临时目录:$TMP_DIR"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
dbg "create_archive: 临时目录就绪 $TMP_DIR (空间: $(df -h "$TMP_DIR" 2>/dev/null | tail -1))"
|
||||||
|
|
||||||
local ts archive
|
local ts archive
|
||||||
ts="$(date '+%Y%m%d-%H%M%S')"
|
ts="$(date '+%Y%m%d-%H%M%S')"
|
||||||
archive="${TMP_DIR}/${ARCHIVE_PREFIX}-${ts}.tar.gz"
|
archive="${TMP_DIR}/${ARCHIVE_PREFIX}-${ts}.tar.gz"
|
||||||
|
dbg "create_archive: 目标归档=$archive"
|
||||||
|
|
||||||
# shellcheck disable=SC2206
|
# shellcheck disable=SC2206
|
||||||
local paths=($SOURCE_PATHS)
|
local paths=($SOURCE_PATHS)
|
||||||
|
dbg "create_archive: 解析后路径数=${#paths[@]}"
|
||||||
|
local valid=0
|
||||||
for p in "${paths[@]}"; do
|
for p in "${paths[@]}"; do
|
||||||
if [[ ! -e "$p" ]]; then
|
if [[ ! -e "$p" ]]; then
|
||||||
warn "源路径不存在,跳过:$p"
|
warn "源路径不存在,跳过:$p"
|
||||||
|
else
|
||||||
|
dbg " 路径就绪:$p"
|
||||||
|
valid=$((valid+1))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
if [[ $valid -eq 0 ]]; then
|
||||||
|
err "没有任何有效的源路径"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
log "开始打包:$archive"
|
log "开始打包:$archive"
|
||||||
if ! tar -czf "$archive" "${paths[@]}" 2>/dev/null; then
|
local tar_err
|
||||||
err "打包失败"
|
tar_err="$(tar -czf "$archive" "${paths[@]}" 2>&1)"
|
||||||
|
local rc=$?
|
||||||
|
if [[ $rc -ne 0 ]]; then
|
||||||
|
err "打包失败 (rc=$rc): $tar_err"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "$tar_err" ]]; then
|
||||||
|
dbg "tar 输出: $tar_err"
|
||||||
|
fi
|
||||||
ARCHIVE_FILE="$archive"
|
ARCHIVE_FILE="$archive"
|
||||||
ARCHIVE_NAME="$(basename "$archive")"
|
ARCHIVE_NAME="$(basename "$archive")"
|
||||||
ok "打包完成:$archive ($(du -h "$archive" | awk '{print $1}'))"
|
ok "打包完成:$archive ($(du -h "$archive" | awk '{print $1}'))"
|
||||||
|
dbg "create_archive: ARCHIVE_FILE=$ARCHIVE_FILE ARCHIVE_NAME=$ARCHIVE_NAME"
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_local() {
|
cleanup_local() {
|
||||||
|
dbg "cleanup_local: CLEAN_LOCAL=$CLEAN_LOCAL ARCHIVE_FILE=$ARCHIVE_FILE"
|
||||||
if [[ "$CLEAN_LOCAL" == "true" && -n "$ARCHIVE_FILE" && -f "$ARCHIVE_FILE" ]]; then
|
if [[ "$CLEAN_LOCAL" == "true" && -n "$ARCHIVE_FILE" && -f "$ARCHIVE_FILE" ]]; then
|
||||||
rm -f "$ARCHIVE_FILE" && log "已删除本地归档:$ARCHIVE_FILE"
|
rm -f "$ARCHIVE_FILE" && log "已删除本地归档:$ARCHIVE_FILE"
|
||||||
|
else
|
||||||
|
dbg "cleanup_local: 跳过删除"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- SMB ----------
|
# ---------- SMB ----------
|
||||||
|
|
||||||
smb_check_deps() {
|
smb_check_deps() {
|
||||||
|
dbg "smb_check_deps: OS=$OS_NAME"
|
||||||
case "$OS_NAME" in
|
case "$OS_NAME" in
|
||||||
Linux)
|
Linux)
|
||||||
if command -v smbclient >/dev/null 2>&1; then
|
if command -v smbclient >/dev/null 2>&1; then
|
||||||
SMB_TOOL="smbclient"
|
SMB_TOOL="smbclient"
|
||||||
|
dbg "smb_check_deps: 找到 smbclient => $(command -v smbclient)"
|
||||||
else
|
else
|
||||||
err "未安装 smbclient。Debian/Ubuntu: apt install smbclient;RHEL/Alma: dnf install samba-client"
|
err "未安装 smbclient。Debian/Ubuntu: apt install smbclient;RHEL/Alma: dnf install samba-client"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
Darwin)
|
Darwin)
|
||||||
# macOS 自带 smbutil,但上传需要挂载;优先使用 smbclient(brew install samba)
|
|
||||||
if command -v smbclient >/dev/null 2>&1; then
|
if command -v smbclient >/dev/null 2>&1; then
|
||||||
SMB_TOOL="smbclient"
|
SMB_TOOL="smbclient"
|
||||||
|
dbg "smb_check_deps: 找到 smbclient => $(command -v smbclient)"
|
||||||
elif command -v mount_smbfs >/dev/null 2>&1; then
|
elif command -v mount_smbfs >/dev/null 2>&1; then
|
||||||
SMB_TOOL="mount_smbfs"
|
SMB_TOOL="mount_smbfs"
|
||||||
|
dbg "smb_check_deps: 未找到 smbclient,回退 mount_smbfs => $(command -v mount_smbfs)"
|
||||||
else
|
else
|
||||||
err "未找到 smbclient(brew install samba)或 mount_smbfs"
|
err "未找到 smbclient(brew install samba)或 mount_smbfs"
|
||||||
return 1
|
return 1
|
||||||
@@ -214,20 +311,25 @@ smb_check_deps() {
|
|||||||
;;
|
;;
|
||||||
*) err "不支持的系统:$OS_NAME"; return 1 ;;
|
*) err "不支持的系统:$OS_NAME"; return 1 ;;
|
||||||
esac
|
esac
|
||||||
|
log "SMB 工具:$SMB_TOOL"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
smb_validate() {
|
smb_validate() {
|
||||||
|
dbg "smb_validate: HOST='$SMB_HOST' SHARE='$SMB_SHARE' USER='$SMB_USER' PATH='$SMB_PATH'"
|
||||||
[[ -z "$SMB_HOST" ]] && { err "缺少 SMB_HOST"; return 1; }
|
[[ -z "$SMB_HOST" ]] && { err "缺少 SMB_HOST"; return 1; }
|
||||||
[[ -z "$SMB_SHARE" ]] && { err "缺少 SMB_SHARE"; return 1; }
|
[[ -z "$SMB_SHARE" ]] && { err "缺少 SMB_SHARE"; return 1; }
|
||||||
[[ -z "$SMB_USER" ]] && { err "缺少 SMB_USER"; return 1; }
|
[[ -z "$SMB_USER" ]] && { err "缺少 SMB_USER"; return 1; }
|
||||||
|
[[ -z "$SMB_PASSWORD" ]] && warn "SMB_PASSWORD 为空(如服务器允许匿名/空密码可忽略)"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
smb_upload_smbclient() {
|
smb_upload_smbclient() {
|
||||||
|
dbg "smb_upload_smbclient: 进入"
|
||||||
local remote_dir="${SMB_PATH%/}"
|
local remote_dir="${SMB_PATH%/}"
|
||||||
local commands=""
|
local commands=""
|
||||||
|
|
||||||
if [[ -n "$remote_dir" ]]; then
|
if [[ -n "$remote_dir" ]]; then
|
||||||
# 逐级 mkdir,避免目录不存在
|
|
||||||
local IFS=/
|
local IFS=/
|
||||||
# shellcheck disable=SC2206
|
# shellcheck disable=SC2206
|
||||||
local parts=($remote_dir)
|
local parts=($remote_dir)
|
||||||
@@ -241,43 +343,54 @@ smb_upload_smbclient() {
|
|||||||
commands+="cd \"${remote_dir}\";"
|
commands+="cd \"${remote_dir}\";"
|
||||||
fi
|
fi
|
||||||
commands+="put \"${ARCHIVE_FILE}\" \"${ARCHIVE_NAME}\";"
|
commands+="put \"${ARCHIVE_FILE}\" \"${ARCHIVE_NAME}\";"
|
||||||
|
dbg "smb_upload_smbclient: smbclient 命令串=$commands"
|
||||||
|
|
||||||
local args=( "//${SMB_HOST}/${SMB_SHARE}" "-U" "${SMB_USER}%${SMB_PASSWORD}" )
|
local args=( "//${SMB_HOST}/${SMB_SHARE}" "-U" "${SMB_USER}%${SMB_PASSWORD}" )
|
||||||
[[ -n "$SMB_DOMAIN" ]] && args+=( "-W" "$SMB_DOMAIN" )
|
[[ -n "$SMB_DOMAIN" ]] && args+=( "-W" "$SMB_DOMAIN" )
|
||||||
[[ -n "$SMB_VERSION" ]] && args+=( "-m" "SMB${SMB_VERSION//./}" )
|
[[ -n "$SMB_VERSION" ]] && args+=( "-m" "SMB${SMB_VERSION//./}" )
|
||||||
|
# debug 模式给 smbclient 也加上 -d 1
|
||||||
|
local sm_debug=()
|
||||||
|
[[ "$DEBUG" == "true" ]] && sm_debug=( "-d" "1" )
|
||||||
|
|
||||||
log "通过 smbclient 上传到 //${SMB_HOST}/${SMB_SHARE}/${SMB_PATH}"
|
log "通过 smbclient 上传到 //${SMB_HOST}/${SMB_SHARE}/${SMB_PATH}"
|
||||||
if ! smbclient "${args[@]}" -c "$commands"; then
|
dbg "smbclient 参数(脱敏)://${SMB_HOST}/${SMB_SHARE} -U ${SMB_USER}%$(mask "$SMB_PASSWORD") ${SMB_DOMAIN:+-W $SMB_DOMAIN} ${SMB_VERSION:+-m SMB${SMB_VERSION//./}}"
|
||||||
|
if ! smbclient "${args[@]}" "${sm_debug[@]}" -c "$commands"; then
|
||||||
err "smbclient 上传失败"
|
err "smbclient 上传失败"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
dbg "smb_upload_smbclient: 上传 OK"
|
||||||
|
|
||||||
if [[ "$RETENTION_DAYS" -gt 0 ]]; then
|
if [[ "$RETENTION_DAYS" -gt 0 ]]; then
|
||||||
smb_retention_smbclient "${args[@]}"
|
smb_retention_smbclient "${args[@]}"
|
||||||
|
else
|
||||||
|
dbg "smb_upload_smbclient: RETENTION_DAYS=0,跳过远端清理"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
smb_retention_smbclient() {
|
smb_retention_smbclient() {
|
||||||
local args=( "$@" )
|
local args=( "$@" )
|
||||||
|
dbg "smb_retention_smbclient: RETENTION_DAYS=$RETENTION_DAYS"
|
||||||
local listing
|
local listing
|
||||||
listing="$(smbclient "${args[@]}" -D "${SMB_PATH:-/}" -c "ls" 2>/dev/null)" || return 0
|
listing="$(smbclient "${args[@]}" -D "${SMB_PATH:-/}" -c "ls" 2>/dev/null)" || return 0
|
||||||
local cutoff
|
local cutoff
|
||||||
if date -v-1d >/dev/null 2>&1; then
|
if date -v-1d >/dev/null 2>&1; then
|
||||||
cutoff=$(date -v-"${RETENTION_DAYS}"d +%s) # macOS BSD date
|
cutoff=$(date -v-"${RETENTION_DAYS}"d +%s)
|
||||||
else
|
else
|
||||||
cutoff=$(date -d "-${RETENTION_DAYS} days" +%s)
|
cutoff=$(date -d "-${RETENTION_DAYS} days" +%s)
|
||||||
fi
|
fi
|
||||||
log "清理远端早于 ${RETENTION_DAYS} 天的归档"
|
log "清理远端早于 ${RETENTION_DAYS} 天的归档"
|
||||||
|
dbg "cutoff=$cutoff ($(date -r "$cutoff" 2>/dev/null || date -d "@$cutoff" 2>/dev/null))"
|
||||||
echo "$listing" | awk '{print $1}' | grep -E "^${ARCHIVE_PREFIX}-[0-9]{8}-[0-9]{6}\\.tar\\.gz\$" | while read -r f; do
|
echo "$listing" | awk '{print $1}' | grep -E "^${ARCHIVE_PREFIX}-[0-9]{8}-[0-9]{6}\\.tar\\.gz\$" | while read -r f; do
|
||||||
local fts ftime
|
local fts ftime
|
||||||
fts="$(echo "$f" | sed -E "s/^${ARCHIVE_PREFIX}-([0-9]{8})-([0-9]{6})\\.tar\\.gz\$/\\1 \\2/")"
|
fts="$(echo "$f" | sed -E "s/^${ARCHIVE_PREFIX}-([0-9]{8})-([0-9]{6})\\.tar\\.gz\$/\\1 \\2/")"
|
||||||
local d="${fts% *}" t="${fts#* }"
|
local d="${fts% *}" t="${fts#* }"
|
||||||
local iso="${d:0:4}-${d:4:2}-${d:6:2} ${t:0:2}:${t:2:2}:${t:4:2}"
|
local iso="${d:0:4}-${d:4:2}-${d:6:2} ${t:0:2}:${t:2:2}:${t:4:2}"
|
||||||
if date -j -f "%Y-%m-%d %H:%M:%S" "$iso" +%s >/dev/null 2>&1; then
|
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) # macOS
|
ftime=$(date -j -f "%Y-%m-%d %H:%M:%S" "$iso" +%s)
|
||||||
else
|
else
|
||||||
ftime=$(date -d "$iso" +%s)
|
ftime=$(date -d "$iso" +%s)
|
||||||
fi
|
fi
|
||||||
|
dbg " 远端文件 $f -> ftime=$ftime"
|
||||||
if [[ "$ftime" -lt "$cutoff" ]]; then
|
if [[ "$ftime" -lt "$cutoff" ]]; then
|
||||||
log "删除远端旧归档:$f"
|
log "删除远端旧归档:$f"
|
||||||
smbclient "${args[@]}" -D "${SMB_PATH:-/}" -c "del \"$f\"" >/dev/null 2>&1 || warn "删除失败:$f"
|
smbclient "${args[@]}" -D "${SMB_PATH:-/}" -c "del \"$f\"" >/dev/null 2>&1 || warn "删除失败:$f"
|
||||||
@@ -286,11 +399,12 @@ smb_retention_smbclient() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
smb_upload_mount() {
|
smb_upload_mount() {
|
||||||
# macOS 兜底:通过挂载点拷贝
|
dbg "smb_upload_mount: 使用 mount_smbfs 兜底"
|
||||||
local mnt
|
local mnt
|
||||||
mnt="$(mktemp -d /tmp/smbmnt.XXXXXX)"
|
mnt="$(mktemp -d /tmp/smbmnt.XXXXXX)"
|
||||||
local url="//${SMB_USER}:${SMB_PASSWORD}@${SMB_HOST}/${SMB_SHARE}"
|
local url="//${SMB_USER}:${SMB_PASSWORD}@${SMB_HOST}/${SMB_SHARE}"
|
||||||
log "挂载 SMB:${mnt}"
|
log "挂载 SMB:${mnt}"
|
||||||
|
dbg "mount_smbfs URL(脱敏)://${SMB_USER}:$(mask "$SMB_PASSWORD")@${SMB_HOST}/${SMB_SHARE}"
|
||||||
if ! mount_smbfs "$url" "$mnt"; then
|
if ! mount_smbfs "$url" "$mnt"; then
|
||||||
err "挂载 SMB 失败"; rmdir "$mnt"; return 1
|
err "挂载 SMB 失败"; rmdir "$mnt"; return 1
|
||||||
fi
|
fi
|
||||||
@@ -299,6 +413,7 @@ smb_upload_mount() {
|
|||||||
dest="$mnt/$SMB_PATH"
|
dest="$mnt/$SMB_PATH"
|
||||||
mkdir -p "$dest" || true
|
mkdir -p "$dest" || true
|
||||||
fi
|
fi
|
||||||
|
dbg "smb_upload_mount: 目标 $dest"
|
||||||
if cp "$ARCHIVE_FILE" "$dest/"; then
|
if cp "$ARCHIVE_FILE" "$dest/"; then
|
||||||
ok "已复制到 $dest/"
|
ok "已复制到 $dest/"
|
||||||
else
|
else
|
||||||
@@ -312,8 +427,10 @@ smb_upload_mount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run_smb() {
|
run_smb() {
|
||||||
|
dbg "run_smb: 开始"
|
||||||
smb_check_deps || return 1
|
smb_check_deps || return 1
|
||||||
smb_validate || return 1
|
smb_validate || return 1
|
||||||
|
print_effective_config
|
||||||
create_archive || return 1
|
create_archive || return 1
|
||||||
|
|
||||||
if [[ "$SMB_TOOL" == "smbclient" ]]; then
|
if [[ "$SMB_TOOL" == "smbclient" ]]; then
|
||||||
@@ -335,13 +452,26 @@ run_sftp() {
|
|||||||
# ---------- 入口 ----------
|
# ---------- 入口 ----------
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
parse_args "$@"
|
# 提前处理 --debug,便于尽早开启日志
|
||||||
|
local a
|
||||||
|
for a in "$@"; do
|
||||||
|
[[ "$a" == "--debug" ]] && DEBUG="true"
|
||||||
|
done
|
||||||
|
|
||||||
|
print_runtime_env "$@"
|
||||||
|
|
||||||
|
parse_args "$@" || exit 1
|
||||||
|
|
||||||
|
print_effective_config
|
||||||
|
|
||||||
case "$METHOD" in
|
case "$METHOD" in
|
||||||
smb) run_smb ;;
|
smb) run_smb ;;
|
||||||
sftp) run_sftp ;;
|
sftp) run_sftp ;;
|
||||||
*) err "不支持的方式:$METHOD"; exit 1 ;;
|
*) err "不支持的方式:$METHOD"; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
local rc=$?
|
||||||
|
dbg "main: 退出码=$rc"
|
||||||
|
exit $rc
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户