dga / sync_data.sh
moxiaoying's picture
Update sync_data.sh
de9ddf2 verified
#!/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 "脚本初始化完成,同步进程已在后台运行。"
# 根据你的运行环境决定脚本最后如何结束或保持运行