#!/bin/bash # --- 配置检查 --- # 检查 WebDAV 环境变量是否设置 if [[ -z "$WEBDAV_URL" ]] || [[ -z "$WEBDAV_USERNAME" ]] || [[ -z "$WEBDAV_PASSWORD" ]]; then echo "错误:缺少必要的 WebDAV 环境变量 (WEBDAV_URL, WEBDAV_USERNAME, WEBDAV_PASSWORD)。" echo "备份功能将不可用。" # 根据你的需求,如果缺少环境变量就完全退出,或者只跳过备份相关功能 # 这里选择退出,因为脚本的主要目的是备份 exit 1 fi # --- 路径和常量定义 --- # 定义要备份的本地目录 (修改点 1:明确指定路径) LOCAL_DATA_DIR="/home/user/data" # 定义备份文件名前缀 (修改点 2:更改前缀) BACKUP_PREFIX="data_backup_" # 定义 WebDAV 上的可选子目录路径 WEBDAV_BACKUP_PATH=${WEBDAV_BACKUP_PATH:-""} # 默认为空,即 WebDAV 根目录 # 构造完整的 WebDAV URL FULL_WEBDAV_URL="${WEBDAV_URL}" if [ -n "$WEBDAV_BACKUP_PATH" ]; then # 确保 URL 拼接时斜杠正确 if [[ "${WEBDAV_URL}" != */ ]]; then WEBDAV_URL="${WEBDAV_URL}/" fi if [[ "${WEBDAV_BACKUP_PATH}" == /* ]]; then WEBDAV_BACKUP_PATH="${WEBDAV_BACKUP_PATH:1}" fi FULL_WEBDAV_URL="${WEBDAV_URL}${WEBDAV_BACKUP_PATH}" fi # 定义 Python 虚拟环境路径 (如果需要,请修改) VENV_PATH="$HOME/venv/bin/activate" # 定义同步间隔(秒),默认为 600 秒 (10 分钟) SYNC_INTERVAL=${SYNC_INTERVAL:-600} # 定义保留的备份数量 KEEP_BACKUPS=5 # --- 虚拟环境激活 --- # 检查并激活 Python 虚拟环境 (如果 Python 脚本需要) if [ -f "$VENV_PATH" ]; then echo "激活 Python 虚拟环境: $VENV_PATH" source "$VENV_PATH" else echo "警告:未找到 Python 虚拟环境 $VENV_PATH。如果 Python 脚本需要特定库,可能会失败。" fi # --- 函数定义 --- # 函数:从 WebDAV 恢复最新备份 restore_backup() { echo "开始从 WebDAV 下载最新备份..." python3 -c " import sys import os import tarfile import requests import shutil from webdav3.client import Client options = { 'webdav_hostname': '$FULL_WEBDAV_URL', 'webdav_login': '$WEBDAV_USERNAME', 'webdav_password': '$WEBDAV_PASSWORD' } local_data_dir = '$LOCAL_DATA_DIR' # 使用变量 backup_prefix = '$BACKUP_PREFIX' # 使用变量 try: client = Client(options) # 过滤文件,使用正确的前缀 (修改点 2) backups = [f for f in client.list() if f.endswith('.tar.gz') and f.startswith(backup_prefix)] if not backups: print(f'在 {options['webdav_hostname']} 没有找到前缀为 {backup_prefix} 的备份文件。') # 如果没有备份,确保目标目录存在但为空可能是期望行为,或者直接退出 os.makedirs(local_data_dir, exist_ok=True) print(f'确保目录 {local_data_dir} 存在。') sys.exit(0) # 正常退出,因为没有备份可恢复 latest_backup = sorted(backups)[-1] print(f'找到最新备份文件:{latest_backup}') remote_file_url = f'{options['webdav_hostname']}/{latest_backup}' # 假设 URL 构造正确 local_temp_file = f'/tmp/{latest_backup}' print(f'正在从 {remote_file_url} 下载...') with requests.get(remote_file_url, auth=(options['webdav_login'], options['webdav_password']), stream=True) as r: r.raise_for_status() # 检查 HTTP 错误 with open(local_temp_file, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) print(f'成功下载备份文件到 {local_temp_file}') if os.path.exists(local_temp_file): print(f'准备恢复到目录: {local_data_dir}') # 如果目录已存在,先清空它 (或者删除重建,取决于需求) if os.path.exists(local_data_dir): print(f'目录 {local_data_dir} 已存在,正在清空...') # shutil.rmtree(local_data_dir) # 删除整个目录树 # 或者清空目录内容 for item in os.listdir(local_data_dir): item_path = os.path.join(local_data_dir, item) if os.path.isfile(item_path) or os.path.islink(item_path): os.unlink(item_path) elif os.path.isdir(item_path): shutil.rmtree(item_path) else: os.makedirs(local_data_dir, exist_ok=True) print(f'正在解压 {local_temp_file} 到 {local_data_dir}...') # 解压备份文件 (修改点 1: 确保解压到正确的目录) with tarfile.open(local_temp_file, 'r:gz') as tar: tar.extractall(path=local_data_dir) print(f'成功从 {latest_backup} 恢复备份到 {local_data_dir}') os.remove(local_temp_file) # 清理临时文件 else: print(f'错误:下载的备份文件 {local_temp_file} 未找到。') except requests.exceptions.RequestException as e: print(f'下载备份时发生网络错误: {e}') sys.exit(1) except Exception as e: print(f'恢复备份时发生错误: {e}') sys.exit(1) " # 检查 Python 脚本的退出码 if [ $? -ne 0 ]; then echo "恢复备份过程中发生错误,脚本终止。" exit 1 fi } # 函数:同步数据到 WebDAV sync_data() { while true; do echo "-----------------------------------------------------" echo "开始同步过程于: $(date)" # 检查本地数据目录是否存在 if [ ! -d "$LOCAL_DATA_DIR" ]; then echo "警告:本地数据目录 $LOCAL_DATA_DIR 不存在。跳过此次备份。" sleep $SYNC_INTERVAL continue # 继续下一次循环 fi # 检查本地数据目录是否为空 if [ -z "$(ls -A $LOCAL_DATA_DIR)" ]; then echo "信息:本地数据目录 $LOCAL_DATA_DIR 为空。跳过此次备份。" sleep $SYNC_INTERVAL continue # 继续下一次循环 fi timestamp=$(date +%Y%m%d_%H%M%S) # 使用新的前缀生成备份文件名 (修改点 2) backup_file="${BACKUP_PREFIX}${timestamp}.tar.gz" local_temp_backup_path="/tmp/${backup_file}" remote_backup_path="${FULL_WEBDAV_URL}/${backup_file}" # 确保 URL 没有双斜杠 echo "正在压缩目录: $LOCAL_DATA_DIR 到 $local_temp_backup_path" # 使用 tar 压缩指定目录的内容 (修改点 1: 使用正确的源目录) # -C "$LOCAL_DATA_DIR" 表示先切换到该目录,然后压缩 '.' (当前目录的所有内容) # --warning=no-file-changed 忽略文件在压缩过程中改变的警告 tar --warning=no-file-changed -czf "$local_temp_backup_path" -C "$LOCAL_DATA_DIR" . if [ $? -ne 0 ]; then echo "错误:压缩目录 $LOCAL_DATA_DIR 失败。" rm -f "$local_temp_backup_path" # 清理可能存在的坏文件 sleep $SYNC_INTERVAL continue # 继续下一次循环 fi # 检查压缩文件大小 if [ ! -s "$local_temp_backup_path" ]; then echo "错误:压缩后的文件 $local_temp_backup_path 大小为 0 或不存在。跳过上传。" rm -f "$local_temp_backup_path" sleep $SYNC_INTERVAL continue fi echo "正在上传备份文件: $local_temp_backup_path 到 $remote_backup_path" # 使用 curl 上传文件到 WebDAV curl --fail -u "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" -T "$local_temp_backup_path" "$remote_backup_path" if [ $? -eq 0 ]; then echo "成功上传 ${backup_file} 到 WebDAV" # 上传成功后,清理本地临时文件 echo "清理本地临时文件: $local_temp_backup_path" rm -f "$local_temp_backup_path" # 清理 WebDAV 上的旧备份文件 echo "开始清理 WebDAV 上的旧备份..." python3 -c " import sys from webdav3.client import Client options = { 'webdav_hostname': '$FULL_WEBDAV_URL', 'webdav_login': '$WEBDAV_USERNAME', 'webdav_password': '$WEBDAV_PASSWORD' } backup_prefix = '$BACKUP_PREFIX' # 使用变量 keep_backups = $KEEP_BACKUPS # 使用变量 try: client = Client(options) # 过滤文件,使用正确的前缀 (修改点 2) backups = [f for f in client.list() if f.endswith('.tar.gz') and f.startswith(backup_prefix)] backups.sort() # 按名称排序(时间戳) if len(backups) > keep_backups: to_delete_count = len(backups) - keep_backups print(f'找到 {len(backups)} 个备份,超过了保留数量 {keep_backups},将删除最早的 {to_delete_count} 个。') for file_to_delete in backups[:to_delete_count]: try: # 确保路径正确,对于根目录下的文件,不需要加斜杠 delete_path = file_to_delete print(f'正在删除旧备份: {delete_path}') client.clean(delete_path) # clean 方法通常用于删除文件或目录 print(f'成功删除 {delete_path}') except Exception as delete_err: print(f'删除文件 {delete_path} 时出错: {delete_err}') else: print(f'找到 {len(backups)} 个备份,未达到保留上限 {keep_backups},无需清理。') except Exception as e: print(f'清理旧备份时发生错误: {e}') " if [ $? -ne 0 ]; then echo "警告:清理 WebDAV 旧备份时发生错误。" fi else echo "错误:上传 ${backup_file} 到 WebDAV 失败。返回码: $?" # 上传失败,也清理本地临时文件 echo "清理本地临时文件: $local_temp_backup_path" rm -f "$local_temp_backup_path" fi echo "下次同步将在 ${SYNC_INTERVAL} 秒后进行..." sleep $SYNC_INTERVAL done } # --- 主逻辑 --- # 1. 首次启动时尝试恢复最新备份 (可选,根据你的需求决定是否需要) echo "检查并恢复最新备份(如果存在)..." restore_backup # 2. 等待一段时间后启动应用程序 (如果需要) # 如果你不需要这个脚本来启动其他应用,可以注释掉下面两行 # echo "等待 30 秒后启动应用程序..." # sleep 30 # echo "启动应用程序..." # ./app server & # 你的应用程序启动命令 # 3. 启动后台同步进程 echo "启动后台数据同步进程..." sync_data & # 让主脚本保持运行(如果需要,例如在 Docker 容器中) # 如果上面的 `./app server &` 是前台运行的,或者你用其他方式保持脚本运行,就不需要这个 # 如果 `sync_data` 是唯一需要长时间运行的,它已经在后台了,主脚本可以退出 # 但如果这是容器的主进程,你可能需要一个前台等待 # 例如: wait $! # 等待最后一个后台进程 (sync_data) # 或者,如果启动了 app server: wait $(pgrep -f app server) # 等待 app server 进程 # 或者简单地无限循环: # tail -f /dev/null # 保持脚本在前台运行 echo "脚本初始化完成,同步进程已在后台运行。" # 根据你的运行环境决定脚本最后如何结束或保持运行