Download sync_data.sh from ljx77qaq/Tavern1: direct link, hf CLI and curl.
- Browser
- Download file 8.48 kB
-
https://huggingface.co/spaces/ljx77qaq/Tavern1/resolve/main/sync_data.sh
- Command line
-
hf download hf://spaces/ljx77qaq/Tavern1/sync_data.sh
-
curl -L -o sync_data.sh https://huggingface.co/spaces/ljx77qaq/Tavern1/resolve/main/sync_data.sh
8.48 kB
| # === 日志配置 === | |
| # 将日志同时输出到标准输出和主进程日志,确保在Space/Docker日志中可见 | |
| log_info() { | |
| MSG="[WebDAV-Backup] [$(date '+%H:%M:%S')] INFO: $1" | |
| if [ -w /proc/1/fd/1 ]; then echo "$MSG" > /proc/1/fd/1; else echo "$MSG"; fi | |
| } | |
| log_error() { | |
| MSG="[WebDAV-Backup] [$(date '+%H:%M:%S')] ERROR: $1" | |
| if [ -w /proc/1/fd/1 ]; then echo "$MSG" > /proc/1/fd/1; else echo "$MSG" >&2; fi | |
| } | |
| # === 1. 检查环境变量 === | |
| if [[ -z "$WEBDAV_URL" ]] || [[ -z "$WEBDAV_USERNAME" ]] || [[ -z "$WEBDAV_PASSWORD" ]]; then | |
| log_error "未启用备份功能 - 缺少 WEBDAV_URL, WEBDAV_USERNAME 或 WEBDAV_PASSWORD" | |
| exit 0 | |
| fi | |
| # 处理 WebDAV 路径 (如果 URL 结尾没有 / 则补上) | |
| WEBDAV_URL=${WEBDAV_URL%/} | |
| # 如果设置了子路径 | |
| WEBDAV_BACKUP_PATH=${WEBDAV_BACKUP_PATH:-""} | |
| # 移除子路径前后的 / | |
| WEBDAV_BACKUP_PATH=${WEBDAV_BACKUP_PATH#/} | |
| WEBDAV_BACKUP_PATH=${WEBDAV_BACKUP_PATH%/} | |
| if [ -n "$WEBDAV_BACKUP_PATH" ]; then | |
| FULL_WEBDAV_URL="${WEBDAV_URL}/${WEBDAV_BACKUP_PATH}" | |
| else | |
| FULL_WEBDAV_URL="${WEBDAV_URL}" | |
| fi | |
| # === 2. 目录设置 === | |
| TEMP_DIR="/tmp/app_backup" | |
| DATA_DIR="/home/node/app/data" | |
| mkdir -p $TEMP_DIR | |
| chmod -R 777 $TEMP_DIR | |
| mkdir -p $DATA_DIR | |
| chmod -R 777 $DATA_DIR | |
| log_info "临时目录: $TEMP_DIR" | |
| log_info "数据目录: $DATA_DIR" | |
| log_info "WebDAV URL: $FULL_WEBDAV_URL" | |
| # === 3. 安装依赖 === | |
| if ! command -v python3 > /dev/null 2>&1; then | |
| log_info "正在安装Python..." | |
| apk add --no-cache python3 py3-pip | |
| fi | |
| # 确保安装了 webdavclient3 和 requests | |
| log_info "正在安装/更新 WebDAV 依赖..." | |
| pip3 install --no-cache-dir requests webdavclient3 | |
| if [ $? -ne 0 ]; then | |
| log_error "依赖安装失败,尝试重试..." | |
| pip3 install --no-cache-dir requests webdavclient3 | |
| fi | |
| # === 4. 测试连接 === | |
| log_info "正在测试 WebDAV 连接..." | |
| HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -u "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" -X PROPFIND "$FULL_WEBDAV_URL/") | |
| if [[ "$HTTP_CODE" == "207" ]] || [[ "$HTTP_CODE" == "200" ]]; then | |
| log_info "WebDAV 连接成功 (HTTP $HTTP_CODE)" | |
| else | |
| log_error "WebDAV 连接失败 (HTTP $HTTP_CODE),正在尝试创建目录..." | |
| # 尝试创建目录 | |
| MKCOL_CODE=$(curl -s -o /dev/null -w "%{http_code}" -u "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" -X MKCOL "$FULL_WEBDAV_URL/") | |
| if [[ "$MKCOL_CODE" == "201" ]] || [[ "$MKCOL_CODE" == "200" ]]; then | |
| log_info "远程目录创建成功" | |
| else | |
| log_error "无法连接也无法创建目录,请检查配置。HTTP: $MKCOL_CODE" | |
| # 不退出,继续尝试,也许是根目录权限问题 | |
| fi | |
| fi | |
| # === 5. 定义功能函数 === | |
| # 上传并清理旧备份 | |
| upload_backup() { | |
| file_path="$1" | |
| file_name="$2" | |
| if [ ! -f "$file_path" ]; then | |
| log_error "备份文件不存在: $file_path" | |
| return 1 | |
| fi | |
| file_size=$(du -h "$file_path" | cut -f1) | |
| log_info "开始上传: $file_name ($file_size)" | |
| # 1. 使用 CURL 上传 (比 Python 更稳定且显示进度) | |
| # 使用 -T 上传文件 | |
| HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -u "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" -T "$file_path" "$FULL_WEBDAV_URL/$file_name") | |
| if [[ "$HTTP_CODE" == "201" ]] || [[ "$HTTP_CODE" == "204" ]] || [[ "$HTTP_CODE" == "200" ]]; then | |
| log_info "上传成功 (HTTP $HTTP_CODE)" | |
| else | |
| log_error "上传失败 (HTTP $HTTP_CODE)" | |
| return 1 | |
| fi | |
| # 2. 使用 Python 清理旧文件 (保留最近5份) | |
| log_info "正在检查旧备份..." | |
| python3 -c " | |
| import sys | |
| from webdav3.client import Client | |
| options = { | |
| 'webdav_hostname': '$FULL_WEBDAV_URL', | |
| 'webdav_login': '$WEBDAV_USERNAME', | |
| 'webdav_password': '$WEBDAV_PASSWORD' | |
| } | |
| try: | |
| client = Client(options) | |
| # 获取文件列表 | |
| files = client.list() | |
| # 筛选备份文件 | |
| backups = [f for f in files if f.endswith('.tar.gz') and f.startswith('app_backup_')] | |
| backups.sort() | |
| # 保留最近 5 个 | |
| MAX_BACKUPS = 5 | |
| if len(backups) > MAX_BACKUPS: | |
| to_delete = backups[:len(backups) - MAX_BACKUPS] | |
| for file_name in to_delete: | |
| try: | |
| # webdav3 的 list 返回的可能是相对路径,clean 需要正确路径 | |
| client.clean(file_name) | |
| print(f'已删除旧备份: {file_name}') | |
| except Exception as e: | |
| print(f'删除 {file_name} 失败: {str(e)}') | |
| else: | |
| print(f'当前备份数量 ({len(backups)}) 未超过限制,无需清理') | |
| except Exception as e: | |
| print(f'清理旧备份时出错: {str(e)}') | |
| " | |
| return 0 | |
| } | |
| # 下载最新备份 | |
| download_latest_backup() { | |
| log_info "正在检查远程备份..." | |
| # 使用 Python 处理复杂的 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' | |
| } | |
| try: | |
| client = Client(options) | |
| # 1. 列出文件 | |
| files = client.list() | |
| # 筛选备份 | |
| backups = [f for f in files if f.endswith('.tar.gz') and f.startswith('app_backup_')] | |
| if not backups: | |
| print('未找到远程备份文件,跳过恢复') | |
| sys.exit(0) | |
| # 2. 找到最新的 | |
| latest_backup = sorted(backups)[-1] | |
| print(f'发现最新备份: {latest_backup}') | |
| # 3. 下载 | |
| local_path = os.path.join('$TEMP_DIR', latest_backup) | |
| download_url = f'$FULL_WEBDAV_URL/{latest_backup}' | |
| print(f'开始下载...') | |
| # 使用 requests 流式下载 | |
| with requests.get(download_url, auth=('$WEBDAV_USERNAME', '$WEBDAV_PASSWORD'), stream=True) as r: | |
| r.raise_for_status() | |
| with open(local_path, 'wb') as f: | |
| for chunk in r.iter_content(chunk_size=8192): | |
| f.write(chunk) | |
| print(f'下载完成: {local_path}') | |
| # 4. 恢复 | |
| target_dir = '$DATA_DIR' | |
| if os.path.exists(local_path): | |
| # 确保目录存在 | |
| os.makedirs(target_dir, exist_ok=True) | |
| # 检查是否需要清空现有数据 (通常首次启动是空的,但以防万一) | |
| # 这里为了安全,我们覆盖解压,而不是先删除 rm -rf | |
| print(f'正在解压到 {target_dir}...') | |
| with tarfile.open(local_path, 'r:gz') as tar: | |
| tar.extractall(target_dir) | |
| print('备份恢复成功!') | |
| # 清理下载的临时文件 | |
| os.remove(local_path) | |
| else: | |
| print('下载的文件丢失,恢复失败') | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f'恢复备份过程中出错: {str(e)}') | |
| # 不退出脚本,允许继续运行,只是恢复失败 | |
| sys.exit(1) | |
| " | |
| } | |
| # === 6. 主流程 === | |
| # 首次启动时尝试恢复 | |
| download_latest_backup | |
| # === 7. 循环同步 === | |
| sync_data() { | |
| log_info "数据同步循环服务已启动" | |
| while true; do | |
| # 默认 6 小时同步一次 (21600秒),避免 WebDAV 拥堵 | |
| SYNC_INTERVAL=${SYNC_INTERVAL:-21600} | |
| # 检查目录 | |
| if [ -d "$DATA_DIR" ]; then | |
| # 检查文件数量 | |
| file_count=$(find "$DATA_DIR" -type f | wc -l) | |
| if [ "$file_count" -eq 0 ]; then | |
| log_info "数据目录为空 ($file_count 文件),跳过备份" | |
| else | |
| log_info "开始创建备份 (文件数: $file_count)..." | |
| timestamp=$(date +%Y%m%d_%H%M%S) | |
| backup_file="app_backup_${timestamp}.tar.gz" | |
| backup_path="${TEMP_DIR}/${backup_file}" | |
| # 压缩 | |
| tar -czf "$backup_path" -C "$DATA_DIR" . | |
| if [ $? -eq 0 ]; then | |
| # 上传 | |
| upload_backup "$backup_path" "$backup_file" | |
| # 本地清理 | |
| rm -f "$backup_path" | |
| else | |
| log_error "压缩文件创建失败" | |
| fi | |
| fi | |
| else | |
| log_error "数据目录不存在: $DATA_DIR" | |
| mkdir -p "$DATA_DIR" | |
| fi | |
| log_info "下次同步将在 ${SYNC_INTERVAL} 秒后进行..." | |
| sleep $SYNC_INTERVAL | |
| done | |
| } | |
| # 启动同步循环 | |
| sync_data |