nbugs commited on
Commit
51af98b
·
verified ·
1 Parent(s): d1a7f47

Update sync_data.sh

Browse files
Files changed (1) hide show
  1. sync_data.sh +138 -313
sync_data.sh CHANGED
@@ -1,335 +1,160 @@
1
  #!/bin/bash
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- # 此脚本仅创建同步脚本,不在构建阶段运行同步进程
4
-
5
- # 创建数据目录
6
- mkdir -p ./data
7
 
8
- # 定义哈希计算函数
 
9
  get_file_hash() {
10
  local file_path="$1"
11
  if [ -f "$file_path" ]; then
12
  md5sum "$file_path" | awk '{print $1}'
13
  else
14
- echo "文件不存在"
15
  fi
16
  }
17
 
18
- # 创建 Hugging Face 同步脚本,优化内存使用
19
- cat > /tmp/hf_sync.py << 'EOL'
20
- from huggingface_hub import HfApi
21
- import sys
22
- import os
23
- import gc
24
- import time
25
-
26
- def manage_backups(api, repo_id, max_files=10):
27
- """管理备份文件,保留最新的max_files个文件,内存优化版"""
28
- try:
29
- files = api.list_repo_files(repo_id=repo_id, repo_type="dataset")
30
- backup_files = [f for f in files if f.startswith('webui_backup_') and f.endswith('.db')]
31
-
32
- # 按日期分组文件(从文件名中提取日期)
33
- backup_by_date = {}
34
- for file in backup_files:
35
- try:
36
- date_part = file.split('_')[2].split('.')[0]
37
- backup_by_date[date_part] = file
38
- except:
39
- continue
40
-
41
- # 保留最新的max_files个文件
42
- sorted_dates = sorted(backup_by_date.keys(), reverse=True)
43
- if len(sorted_dates) > max_files:
44
- files_to_delete = [backup_by_date[date] for date in sorted_dates[max_files:]]
45
-
46
- # 分批删除文件以减少内存使用
47
- batch_size = 3
48
- for i in range(0, len(files_to_delete), batch_size):
49
- batch = files_to_delete[i:i+batch_size]
50
- for file in batch:
51
- api.delete_file(path_in_repo=file, repo_id=repo_id, repo_type="dataset")
52
- print(f"已删除旧备份: {file}")
53
- # 强制垃圾回收
54
- gc.collect()
55
- # 批次间暂停
56
- if i + batch_size < len(files_to_delete):
57
- time.sleep(2)
58
- except Exception as e:
59
- print(f"管理备份错误: {str(e)}")
60
- finally:
61
- # 确保垃圾回收
62
- gc.collect()
63
-
64
- def upload_backup(file_path, file_name, token, repo_id):
65
- """上传备份文件到Hugging Face,内存优化版"""
66
- api = HfApi(token=token)
67
- try:
68
- # 检查文件是否存在
69
- try:
70
- files = api.list_repo_files(repo_id=repo_id, repo_type="dataset")
71
- if file_name in files:
72
- api.delete_file(path_in_repo=file_name, repo_id=repo_id, repo_type="dataset")
73
- print(f"已删除同名文件: {file_name}")
74
- except Exception as e:
75
- print(f"检查文件存在错误: {str(e)}")
76
- gc.collect()
77
-
78
- # 上传新文件
79
- api.upload_file(
80
- path_or_fileobj=file_path,
81
- path_in_repo=file_name,
82
- repo_id=repo_id,
83
- repo_type="dataset"
84
- )
85
- print(f"成功上传: {file_name}")
86
-
87
- # 管理备份,可选执行
88
- if os.environ.get("MANAGE_BACKUPS", "true").lower() == "true":
89
- manage_backups(api, repo_id)
90
- except Exception as e:
91
- print(f"上传失败: {str(e)}")
92
- finally:
93
- gc.collect()
94
-
95
- def download_latest_backup(token, repo_id):
96
- """从Hugging Face下载最新备份,内存优化版"""
97
- api = HfApi(token=token)
98
- try:
99
- files = api.list_repo_files(repo_id=repo_id, repo_type="dataset")
100
- backup_files = [f for f in files if f.startswith('webui_backup_') and f.endswith('.db')]
101
-
102
- if not backup_files:
103
- return False
104
-
105
- # 找到最新的文件(按日期排序)
106
- latest_file = max(backup_files, key=lambda x: x.split('_')[2].split('.')[0])
107
- file_path = api.hf_hub_download(
108
- repo_id=repo_id,
109
- filename=latest_file,
110
- repo_type="dataset"
111
- )
112
-
113
- if file_path and os.path.exists(file_path):
114
- os.makedirs('./data', exist_ok=True)
115
- os.system(f'cp "{file_path}" ./data/webui.db')
116
- print(f"成功从Hugging Face恢复: {latest_file}")
117
- return True
118
- else:
119
- return False
120
- except Exception as e:
121
- print(f"下载失败: {str(e)}")
122
- return False
123
- finally:
124
- gc.collect()
125
-
126
- if __name__ == "__main__":
127
- try:
128
- action = sys.argv[1]
129
- token = sys.argv[2]
130
- repo_id = sys.argv[3]
131
-
132
- if action == "upload":
133
- file_path = sys.argv[4]
134
- file_name = sys.argv[5]
135
- upload_backup(file_path, file_name, token, repo_id)
136
- elif action == "download":
137
- download_latest_backup(token, repo_id)
138
- except Exception as e:
139
- print(f"脚本执行错误: {str(e)}")
140
- finally:
141
- # 最终垃圾回收
142
- gc.collect()
143
- EOL
144
-
145
- # 创建同步服务启动脚本(不在构建时执行)
146
- cat > /tmp/start_sync.sh << 'EOL'
147
- #!/bin/bash
148
-
149
- # 检查必要的环境变量
150
- if [ -z "$WEBDAV_URL" ] || [ -z "$WEBDAV_USERNAME" ] || [ -z "$WEBDAV_PASSWORD" ]; then
151
- echo "缺少必要的环境变量: WEBDAV_URL、WEBDAV_USERNAME 或 WEBDAV_PASSWORD"
152
- export WEBDAV_ENABLED="false"
153
  else
154
- export WEBDAV_ENABLED="true"
155
  fi
156
 
157
- if [ -z "$HF_TOKEN" ] || [ -z "$DATASET_ID" ]; then
158
- echo "缺少必要的环境变量: HF_TOKEN DATASET_ID"
159
- export HF_ENABLED="false"
160
- else
161
- export HF_ENABLED="true"
 
 
 
 
 
162
  fi
163
 
164
- # 初始化数据恢复策略
165
- echo "初始化数据恢复..."
166
-
167
- # 尝试恢复数据
168
- restore_data() {
169
- # 首先尝试从 WebDAV 恢复
170
- if [ "$WEBDAV_ENABLED" = "true" ]; then
171
- echo "尝试从 WebDAV 获取文件列表..."
172
- webdav_files=$(curl -s -X PROPFIND --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" -H "Depth: 1" "$WEBDAV_URL/openwebui/" | grep '<d:href>' | grep 'webui_[0-9]\{8\}.db' | sed 's|</?d:href>||g')
173
-
174
- if [ -n "$webdav_files" ]; then
175
- latest_file=$(echo "$webdav_files" | sort -r | head -n 1)
176
- download_url="$WEBDAV_URL/openwebui/$latest_file"
177
- curl -L -o "./data/webui.db" --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$download_url" && {
178
- echo "成功从 WebDAV 下载最新数据库: $latest_file"
179
- return 0
180
- }
181
- fi
182
- echo "WebDAV 恢复失败"
183
- fi
184
 
185
- # 如果 WebDAV 失败,尝试从 Hugging Face 恢复
186
- if [ "$HF_ENABLED" = "true" ]; then
187
- echo "尝试从 Hugging Face 恢复..."
188
- python /tmp/hf_sync.py download "$HF_TOKEN" "$DATASET_ID" && {
189
- echo "成功从 Hugging Face 恢复"
190
- return 0
191
- }
192
- fi
193
 
194
- # 所有恢复方法都失败
195
- echo "所有恢复失败,创建空数据库..."
196
- touch ./data/webui.db
197
- return 1
198
- }
199
 
200
- # WebDAV 同步函数(使用 cron 风格的调度)
201
  webdav_sync() {
202
- if [ "$WEBDAV_ENABLED" != "true" ]; then
203
- echo "WebDAV 同步已禁用"
204
- return
205
- fi
206
 
207
- echo "执行 WebDAV 同步: $(date)"
208
-
209
- if [ ! -f "./data/webui.db" ]; then
210
- echo "未找到 webui.db,跳过同步"
211
- return
212
- fi
213
-
214
- # 生成文件名(包含年月日)
215
- current_date=$(date +'%Y%m%d')
216
- file_name="webui_${current_date}.db"
217
- upload_url="$WEBDAV_URL/openwebui/${file_name}"
218
-
219
- # 计算本地文件哈希
220
- local_hash=$(get_file_hash "./data/webui.db")
221
-
222
- # 获取远程文件哈希(通过临时下载)
223
- remote_temp="/tmp/webui_remote.db"
224
- curl -s -o "$remote_temp" --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$upload_url" > /dev/null 2>&1
225
- remote_hash=$(get_file_hash "$remote_temp")
226
- rm -f "$remote_temp"
227
-
228
- if [ "$local_hash" = "$remote_hash" ]; then
229
- echo "文件未变化,跳过 WebDAV 上传"
230
- return
231
- fi
232
-
233
- echo "检测到文件变化,开始上传到 WebDAV..."
234
- curl -L -T "./data/webui.db" --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$upload_url" && {
235
- echo "WebDAV 上传成功: $file_name"
236
-
237
- # 更新主文件(覆盖 webui.db)
238
- main_url="$WEBDAV_URL/openwebui/webui.db"
239
- curl -L -T "./data/webui.db" --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$main_url" && {
240
- echo "主文件更新成功"
241
- } || {
242
- echo "主文件更新失败"
243
- }
244
- } || {
245
- echo "WebDAV 上传失败"
246
- }
247
-
248
- # 清理过期 WebDAV 文件(保留最近 7 天)
249
- echo "清理过期 WebDAV 文件..."
250
- webdav_files=$(curl -s -X PROPFIND --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" -H "Depth: 1" "$WEBDAV_URL/openwebui/" | grep '<d:href>' | grep 'webui_[0-9]\{8\}.db' | sed 's|</?d:href>||g')
251
- cleanup_days=7
252
- cutoff_date=$(date -d "-${cleanup_days} days" +%Y%m%d)
253
-
254
- for file in $webdav_files; do
255
- file_date=$(echo "$file" | grep -oE '[0-9]{8}')
256
- if [ -n "$file_date" ] && [ "$file_date" -lt "$cutoff_date" ]; then
257
- delete_url="$WEBDAV_URL/openwebui/$file"
258
- curl -X DELETE --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$delete_url" && echo "删除过期文件: $file"
259
- fi
260
- done
261
- }
262
-
263
- # Hugging Face 同步函数
264
- hf_sync() {
265
- if [ "$HF_ENABLED" != "true" ]; then
266
- echo "Hugging Face 同步已禁用"
267
- return
268
- fi
269
-
270
- echo "执行 Hugging Face 同步: $(date)"
271
-
272
- if [ ! -f "./data/webui.db" ]; then
273
- echo "未找到数据库文件,跳过 Hugging Face 同步"
274
- return
275
- fi
276
-
277
- current_date=$(date +'%Y%m%d')
278
- backup_file="webui_backup_${current_date}.db"
279
- temp_path="/tmp/${backup_file}"
280
- cp "./data/webui.db" "$temp_path"
281
-
282
- echo "正在上传到 Hugging Face..."
283
- python /tmp/hf_sync.py upload "$HF_TOKEN" "$DATASET_ID" "$temp_path" "$backup_file"
284
- rm -f "$temp_path"
285
- }
286
-
287
- # 主函数
288
- main() {
289
- # 恢复数据
290
- restore_data
291
-
292
- # 设置同步间隔(默认2小时)
293
- SYNC_INTERVAL=${SYNC_INTERVAL:-7200}
294
- echo "同步间隔设置为: ${SYNC_INTERVAL} 秒"
295
-
296
- # 循环执行同步,但使用更高效的方式
297
  while true; do
298
- # 每次同步前先休眠,避免启动时立即同步
299
- sleep $SYNC_INTERVAL
300
-
301
- # 执行WebDAV同步
302
- if [ "$WEBDAV_ENABLED" = "true" ]; then
303
- webdav_sync
304
- fi
305
-
306
- # 清理内存
307
- sync
308
- echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
309
-
310
- # 执行Hugging Face同步
311
- if [ "$HF_ENABLED" = "true" ]; then
312
- hf_sync
313
- fi
314
-
315
- # 清理内存
316
- sync
317
- echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
318
- done
319
- }
320
-
321
- # 以非阻塞方式启动主函数
322
- main &
323
- EOL
324
-
325
- # 确保脚本可执行
326
- chmod +x /tmp/start_sync.sh
327
-
328
- # 修改启动脚本以包含同步功能,但在容器启动时启动而不是构建时
329
- cat > /tmp/sync_starter.sh << 'EOL'
330
- # 在容器启动后启动同步服务
331
- /bin/bash /tmp/start_sync.sh &
332
- EOL
333
-
334
- # 注意:此处只是准备脚本,不执行它们
335
- echo "同步脚本已准备就绪,将在容器启动时执行"
 
1
  #!/bin/bash
2
+ # sync_data.sh - 负责数据恢复和启动后台同步任务
3
+
4
+ # 遇到错误时立即退出(在恢复阶段)
5
+ set -e
6
+
7
+ # --- 配置 ---
8
+ # WebDAV 相关环境变量由 Hugging Face Secrets 提供:
9
+ # WEBDAV_URL, WEBDAV_USERNAME, WEBDAV_PASSWORD
10
+ # Hugging Face 相关环境变量由 Hugging Face Secrets 提供:
11
+ # HF_TOKEN, DATASET_ID
12
+ WEBDAV_BASE_PATH="openwebui" # WebDAV上的基础目录名
13
+ LOCAL_DATA_DIR="./data" # 本地数据存储目录
14
+ DB_FILENAME="webui.db" # 数据库文件名
15
+ WEBDAV_SYNC_INTERVAL=${WEBDAV_SYNC_INTERVAL:-7200} # WebDAV同步间隔(秒),默认2小时
16
+ HF_SYNC_INTERVAL=${HF_SYNC_INTERVAL:-7200} # Hugging Face同步间隔(秒),默认2小时
17
+ WEBDAV_CLEANUP_DAYS=7 # WebDAV保留最近多少天的备份
18
+ HF_MAX_BACKUPS=50 # Hugging Face保留的最大备份数量 (通过hf_sync.py控制)
19
+
20
+ # --- 检查环境变量 ---
21
+ if [ -z "$WEBDAV_URL" ] || [ -z "$WEBDAV_USERNAME" ] || [ -z "$WEBDAV_PASSWORD" ]; then
22
+ echo "错误:缺少必要的WebDAV环境变量 (WEBDAV_URL, WEBDAV_USERNAME, WEBDAV_PASSWORD)。"
23
+ exit 1
24
+ fi
25
+ if [ -z "$HF_TOKEN" ] || [ -z "$DATASET_ID" ]; then
26
+ echo "错误:缺少必要的Hugging Face环境变量 (HF_TOKEN, DATASET_ID)。"
27
+ exit 1
28
+ fi
29
 
30
+ # 确保WebDAV URL末尾没有斜杠,方便拼接
31
+ WEBDAV_URL=$(echo "$WEBDAV_URL" | sed 's:/*$::')
32
+ WEBDAV_FULL_PATH="$WEBDAV_URL/$WEBDAV_BASE_PATH"
 
33
 
34
+ # --- 函数定义 ---
35
+ # 计算文件MD5哈希
36
  get_file_hash() {
37
  local file_path="$1"
38
  if [ -f "$file_path" ]; then
39
  md5sum "$file_path" | awk '{print $1}'
40
  else
41
+ echo "not_found" # 返回特殊值表示文件不存在
42
  fi
43
  }
44
 
45
+ # --- 初始化和恢复 ---
46
+ echo "[恢复] 开始初始化数据恢复流程..."
47
+ mkdir -p "$LOCAL_DATA_DIR"
48
+ LOCAL_DB_PATH="$LOCAL_DATA_DIR/$DB_FILENAME"
49
+ recovered=false
50
+
51
+ # 1. 尝试从 WebDAV 恢复最新的日期文件
52
+ echo "[恢复] 尝试从 WebDAV 恢复 ($WEBDAV_FULL_PATH)..."
53
+ # 使用 curl 获取目录列表 (PROPFIND)。注意:解析 XML 可能不稳定
54
+ # curl -s -f: 静默模式,遇到HTTP错误时失败退出
55
+ webdav_list_output=$(curl -s -f --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" -X PROPFIND -H "Depth: 1" "$WEBDAV_FULL_PATH/" || echo "WebDAV PROPFIND failed")
56
+
57
+ if [[ "$webdav_list_output" != "WebDAV PROPFIND failed" ]]; then
58
+ # 提取日期格式的文件名 (webui_YYYYMMDD.db)
59
+ webdav_files=$(echo "$webdav_list_output" | grep '<d:href>' | sed 's#<d:href>[^<]*/$[^<]*$</d:href>#\1#' | grep -E '^webui_[0-9]{8}\.db$')
60
+ if [ -n "$webdav_files" ]; then
61
+ latest_webdav_file=$(echo "$webdav_files" | sort -r | head -n 1)
62
+ echo "[恢复] 在WebDAV找到最新的日期文件: $latest_webdav_file"
63
+ download_url="$WEBDAV_FULL_PATH/$latest_webdav_file"
64
+ temp_db_path="${LOCAL_DB_PATH}.webdav.tmp"
65
+ echo "[恢复] 尝试下载: $download_url"
66
+ if curl -L -f -s -o "$temp_db_path" --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$download_url"; then
67
+ # 验证下载的文件是否有效(至少非空)
68
+ if [ -s "$temp_db_path" ]; then
69
+ mv "$temp_db_path" "$LOCAL_DB_PATH"
70
+ echo "[恢复] 成功从 WebDAV ($latest_webdav_file) 恢复数据库。"
71
+ recovered=true
72
+ else
73
+ echo "[恢复] WebDAV下载的文件为空或无效,删除临时文件。"
74
+ rm -f "$temp_db_path"
75
+ fi
76
+ else
77
+ echo "[恢复] WebDAV 下载 $latest_webdav_file 失败。"
78
+ rm -f "$temp_db_path"
79
+ fi
80
+ else
81
+ echo "[恢复] 在WebDAV路径 ($WEBDAV_FULL_PATH/) 未找到符合 'webui_YYYYMMDD.db' 格式的文件。"
82
+ # 可选:尝试恢复主文件 webui.db (如果存在)
83
+ main_db_url="$WEBDAV_FULL_PATH/$DB_FILENAME"
84
+ echo "[恢复] 尝试从WebDAV恢复主文件: $main_db_url"
85
+ temp_db_path="${LOCAL_DB_PATH}.webdav_main.tmp"
86
+ if curl -L -f -s -o "$temp_db_path" --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$main_db_url"; then
87
+ if [ -s "$temp_db_path" ]; then
88
+ mv "$temp_db_path" "$LOCAL_DB_PATH"
89
+ echo "[恢复] 成功从 WebDAV (主文件 $DB_FILENAME) 恢复数据库。"
90
+ recovered=true
91
+ else
92
+ echo "[恢复] WebDAV下载的主文件为空或无效,删除临时文件。"
93
+ rm -f "$temp_db_path"
94
+ fi
95
+ else
96
+ echo "[恢复] 从WebDAV下载主文件 $DB_FILENAME 失败。"
97
+ rm -f "$temp_db_path"
98
+ fi
99
+ fi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  else
101
+ echo "[恢复] 无法访问WebDAV目录 ($WEBDAV_FULL_PATH/)。"
102
  fi
103
 
104
+ # 2. 如果WebDAV恢复失败,尝试从 Hugging Face 恢复
105
+ if [ "$recovered" != true ]; then
106
+ echo "[恢复] WebDAV恢复失败或未找到文件,尝试从 Hugging Face ($DATASET_ID) 恢复..."
107
+ # 调用 Python 脚本进行下载,传递目标目录
108
+ if python3 /app/hf_sync.py download "$HF_TOKEN" "$DATASET_ID" "$LOCAL_DATA_DIR"; then
109
+ echo "[恢复] 成功从 Hugging Face 恢复。"
110
+ recovered=true
111
+ else
112
+ echo "[恢复] 从 Hugging Face 恢复也失败了。"
113
+ fi
114
  fi
115
 
116
+ # 3. 如果所有恢复都失败,并且本地数据库文件不存在,则创建空文件
117
+ if [ "$recovered" != true ] && [ ! -f "$LOCAL_DB_PATH" ]; then
118
+ echo "[恢复] 所有恢复方式均失败,创建空的数据库文件: $LOCAL_DB_PATH"
119
+ touch "$LOCAL_DB_PATH"
120
+ fi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ echo "[恢复] 数据恢复流程结束。"
 
 
 
 
 
 
 
123
 
124
+ # --- 后台同步任务 ---
 
 
 
 
125
 
126
+ # WebDAV 同步函数
127
  webdav_sync() {
128
+ echo "[WebDAV Sync] 后台任务启动,间隔: ${WEBDAV_SYNC_INTERVAL} "
129
+ local last_uploaded_hash=""
 
 
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  while true; do
132
+ timestamp=$(date '+%Y-%m-%d %H:%M:%S')
133
+ echo "[WebDAV Sync @ $timestamp] 开始检查..."
134
+
135
+ if [ -f "$LOCAL_DB_PATH" ]; then
136
+ current_hash=$(get_file_hash "$LOCAL_DB_PATH")
137
+ echo "[WebDAV Sync @ $timestamp] 本地文件哈希: $current_hash"
138
+
139
+ if [ "$current_hash" == "$last_uploaded_hash" ]; then
140
+ echo "[WebDAV Sync @ $timestamp] 文件未变化,跳过上传。"
141
+ else
142
+ echo "[WebDAV Sync @ $timestamp] 检测到文件变化或首次运行,准备上传到 WebDAV..."
143
+ current_date=$(date +'%Y%m%d')
144
+ dated_filename="webui_${current_date}.db"
145
+ upload_dated_url="$WEBDAV_FULL_PATH/$dated_filename"
146
+ upload_main_url="$WEBDAV_FULL_PATH/$DB_FILENAME"
147
+
148
+ # 上传带日期的文件
149
+ echo "[WebDAV Sync @ $timestamp] 上传日期文件: $upload_dated_url"
150
+ if curl -L -f -T "$LOCAL_DB_PATH" --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$upload_dated_url"; then
151
+ echo "[WebDAV Sync @ $timestamp] 日期文件上传成功: $dated_filename"
152
+
153
+ # 上传主文件 (覆盖)
154
+ echo "[WebDAV Sync @ $timestamp] 更新主文件: $upload_main_url"
155
+ if curl -L -f -T "$LOCAL_DB_PATH" --user "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" "$upload_main_url"; then
156
+ echo "[WebDAV Sync @ $timestamp] 主文件更新成功。"
157
+ last_uploaded_hash=$current_hash # 只有主文件也成功才更新哈希
158
+ else
159
+ echo "[WebDAV Sync @ $timestamp] 错误:主文件更新失败!"
160
+ # 主文件失败,不更新哈希,下次会