167 lines
7.5 KiB
Bash
167 lines
7.5 KiB
Bash
#!/bin/bash
|
||
|
||
# ==============================================================================
|
||
# RCLONE ENTERPRISE-GRADE SYNC SCRIPT (v4.1 - Corrected & Final)
|
||
# ==============================================================================
|
||
#
|
||
# 版本: 4.1
|
||
# 修正:
|
||
# - [关键] 恢复了 rclone sync 命令中缺失的 --backup-dir 参数,确保文件在
|
||
# 被覆盖或删除前能正确备份。
|
||
#
|
||
# 完整功能:
|
||
# - 单向同步 (rclone sync)
|
||
# - 智能备份被覆盖/删除的文件到按天组织的目录
|
||
# - 防止并发执行的锁文件机制
|
||
# - 自动清理指定天数之前的旧备份
|
||
# - 自动日志轮转,防止日志文件无限增长
|
||
# - 成功或失败时发送钉钉通知
|
||
# - 失败时,通知中包含错误日志摘要
|
||
#
|
||
# ==============================================================================
|
||
|
||
# ============================ 用户配置区域 ============================
|
||
|
||
# --- Rclone 配置 ---
|
||
SOURCE_DIR="/data2/immich/library/"
|
||
REMOTE_PATH="onedrive:immich/library/"
|
||
# 备份文件的父目录。脚本将在此目录下创建每日备份文件夹。
|
||
BACKUP_PARENT_PATH="onedrive:immich/backup/"
|
||
# 动态生成当天的完整备份路径,用于 --backup-dir 参数
|
||
TODAY_BACKUP_PATH="${BACKUP_PARENT_PATH}$(date +%Y-%m-%d)"
|
||
LOG_FILE="/data2/immich/log/rclone-immich-sync.log"
|
||
# 这个log_file也需要上传到onedrive,使用另外一个进程
|
||
# --- 锁文件配置 ---
|
||
LOCK_FILE="/data2/immich/rclone-immich-sync.lock"
|
||
|
||
# --- 日志与备份清理配置 ---
|
||
LOG_MAX_SIZE_MB=200
|
||
# 备份保留天数。所有文件修改时间早于这个天数的备份将被删除。
|
||
BACKUP_RETENTION_DAYS=180
|
||
|
||
# --- 钉钉机器人配置 ---
|
||
DINGTALK_ACCESS_TOKEN="d10822224bf76dfcadad9da452cd37fafd3439aac0a1a4bf592214f9a3768a69"
|
||
DINGTALK_SECRET="SECdb923bef42b35beca1254215d9abcfe142465628ff91e4fcaa22678bcec5c9da"
|
||
# 加签:SECdb923bef42b35beca1254215d9abcfe142465628ff91e4fcaa22678bcec5c9da
|
||
# https://oapi.dingtalk.com/robot/send?access_token=d10822224bf76dfcadad9da452cd37fafd3439aac0a1a4bf592214f9a3768a69
|
||
|
||
# --- 代理设置 ---
|
||
PROXY_URL="http://127.0.0.1:1099"
|
||
ROXY_TEST_URL="https://pac-9d7.pages.dev/access"
|
||
PROXY_KEYWORD="ACCESS"
|
||
# ============================ 初始化与检查 ============================
|
||
|
||
# --- 锁文件检查 ---
|
||
if ! mkdir "${LOCK_FILE}" 2>/dev/null; then
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') - 错误:检测到上一个同步任务仍在运行。脚本退出以防止重复执行。" | tee -a "${LOG_FILE}"
|
||
exit 1
|
||
fi
|
||
|
||
# --- 设定陷阱 (trap),确保脚本在任何情况下退出时都能删除锁文件 ---
|
||
trap 'rm -rf "${LOCK_FILE}"' EXIT
|
||
|
||
# ========================= 钉钉通知函数 (无需修改) =========================
|
||
# 此函数已包含 jq 依赖来安全处理消息内容
|
||
# 请确保已安装 jq: sudo apt-get install jq 或 sudo yum install jq
|
||
urlencode() {
|
||
string="${1}";
|
||
strlen=${#string};
|
||
encoded="";
|
||
pos c o
|
||
for (( pos=0 ; pos<strlen ; pos++ )); do c=${string:$pos:1}; case "$c" in [-_.a-zA-Z0-9] ) o="${c}" ;; * ) printf -v o '%%%02x' "'$c"; esac; encoded+="${o}"; done
|
||
echo "${encoded}"
|
||
}
|
||
send_dingtalk_notification() {
|
||
if [[ -z "${DINGTALK_ACCESS_TOKEN}" || -z "${DINGTALK_SECRET}" || "${DINGTALK_ACCESS_TOKEN}" == "YOUR_ACCESS_TOKEN_HERE" ]]; then
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') - 未配置钉钉机器人,跳过发送通知。" >> "${LOG_FILE}"; return
|
||
fi
|
||
message="$1";
|
||
timestamp=$(date +%s%N | cut -b1-13);
|
||
string_to_sign="${timestamp}\n${DINGTALK_SECRET}"
|
||
sign_base64=$(echo -ne "$string_to_sign" | openssl dgst -sha256 -hmac "$DINGTALK_SECRET" -binary | base64)
|
||
sign_encoded=$(urlencode "$sign_base64")
|
||
webhook_url="https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_ACCESS_TOKEN}&timetamp=${timestamp}&sign=${sign_encoded}"
|
||
json_payload=$(jq -n --arg content "$message" '{"msgtype": "text", "text": {"content": $content}}')
|
||
curl -s -X POST "${webhook_url}" -H 'Content-Type: application/json' -d "${json_payload}" >> "${LOG_FILE}" 2>&1
|
||
}
|
||
# ============================ 主任务:文件同步 ============================
|
||
echo "==================================================" >> "${LOG_FILE}"
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') - 开始执行 Immich 库同步任务..." >> "${LOG_FILE}"
|
||
|
||
# ============================ 日志轮转逻辑 (无需修改) ============================
|
||
if [ -f "${LOG_FILE}" ]; then
|
||
max_size_bytes=$((LOG_MAX_SIZE_MB * 1024 * 1024));
|
||
current_size_bytes=$(stat -c%s "${LOG_FILE}")
|
||
|
||
if [ ${current_size_bytes} -gt ${max_size_bytes} ]; then
|
||
archive_name="${LOG_FILE}.$(date +%Y%m%d-%H%M%S).gz"; echo "$(date '+%Y-%m-%d %H:%M:%S') - 日志文件大小超限,正在归档..." >> "${LOG_FILE}"
|
||
gzip -c "${LOG_FILE}" > "${archive_name}" && > "${LOG_FILE}"
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') - 日志归档完成。" >> "${LOG_FILE}"
|
||
fi
|
||
fi
|
||
|
||
# -------- 设置代理 --------
|
||
echo "-------- 设置代理 ----------" >> "${LOG_FILE}"
|
||
test_response=$(curl -s --proxy "$PROXY_URL" "$PROXY_TEST_URL" --connect-timeout 5)
|
||
if [ "$test_response" != "$PROXY_KEYWORD" ]; then
|
||
echo "代理测试失败 (返回值: '$response'),无代理模式连接..." >> "${LOG_FILE}"
|
||
unset https_proxy
|
||
else
|
||
export https_proxy=$PROXY_URL
|
||
echo "使用代理模式连接..." >> "${LOG_FILE}"
|
||
fi
|
||
# ==============
|
||
rclone sync "${SOURCE_DIR}" "${REMOTE_PATH}" \
|
||
--fast-list \
|
||
--checkers 32 \
|
||
--transfers 8 \
|
||
--bwlimit 1000M \
|
||
--backup-dir "${TODAY_BACKUP_PATH}" \
|
||
--retries 5 \
|
||
--retries-sleep 30s \
|
||
--log-file "${LOG_FILE}" \
|
||
--onedrive-no-version \
|
||
-v
|
||
|
||
sync_exit_code=$?
|
||
|
||
# ======================== 结果检查与通知 ========================
|
||
if [ ${sync_exit_code} -eq 0 ]; then
|
||
log_message="$(date '+%Y-%m-%d %H:%M:%S') - Immich 库同步成功完成。"
|
||
dingtalk_message="【备份成功】服务器上的 Immich 库已于 $(date '+%Y-%m-%d %H:%M:%S') 成功同步到 OneDrive。"
|
||
echo "${log_message}" >> "${LOG_FILE}"
|
||
send_dingtalk_notification "${dingtalk_message}"
|
||
|
||
# --- 子任务:清理旧备份 (仅在主任务成功后执行) ---
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') - 开始清理 ${BACKUP_RETENTION_DAYS} 天前的旧备份..." >> "${LOG_FILE}"
|
||
# 删除备份父目录中,所有修改时间早于指定天数的 文件,然后用 --rmdirs 删除因此变空的目录
|
||
rclone delete "${BACKUP_PARENT_PATH}" \
|
||
--min-age "${BACKUP_RETENTION_DAYS}d" \
|
||
--rmdirs \
|
||
--log-file "${LOG_FILE}" \
|
||
-v
|
||
|
||
cleanup_exit_code=$?
|
||
if [ ${cleanup_exit_code} -ne 0 ]; then
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') - 警告:旧备份清理失败!退出代码: ${cleanup_exit_code}" >> "${LOG_FILE}"
|
||
send_dingtalk_notification "【备份警告】主同步任务成功,但清理旧备份失败。请检查日志。"
|
||
fi
|
||
else
|
||
# --- 主任务失败 ---
|
||
log_message="$(date '+%Y-%m-%d %H:%M:%S') - 错误:Immich 库同步失败!退出代码: ${sync_exit_code}"
|
||
error_details=$(tail -n 10 "${LOG_FILE}")
|
||
dingtalk_message="【备份失败】警告!服务器上的 Immich 库同步任务失败,请立即检查!
|
||
|
||
退出代码: ${sync_exit_code}
|
||
错误摘要 (最后10行日志):
|
||
---------------------
|
||
${error_details}"
|
||
echo "${log_message}" >> "${LOG_FILE}"
|
||
send_dingtalk_notification "${dingtalk_message}"
|
||
fi
|
||
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') - 任务脚本执行完毕。" >> "${LOG_FILE}"
|
||
echo "==================================================" >> "${LOG_FILE}"
|
||
echo "" >> "${LOG_FILE}"
|
||
|
||
exit ${sync_exit_code} |