Spaces:
Sleeping
Sleeping
| # --- 配置检查 --- | |
| # 检查 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 "脚本初始化完成,同步进程已在后台运行。" | |
| # 根据你的运行环境决定脚本最后如何结束或保持运行 |