File size: 13,660 Bytes
c2e15e4
c4f3936
e28913e
7d404ab
c61036b
 
 
c2e15e4
c61036b
c4f3936
 
c61036b
 
afb63c7
 
c61036b
e28913e
 
c61036b
 
 
e28913e
c61036b
 
 
e28913e
c4f3936
afb63c7
 
 
 
e28913e
 
17a9d42
c4f3936
 
 
 
e28913e
 
 
 
 
 
c4f3936
 
e28913e
c4f3936
 
 
e28913e
 
 
c4f3936
 
e28913e
 
afb63c7
 
e28913e
c61036b
 
e28913e
afb63c7
e28913e
 
 
afb63c7
 
e28913e
 
afb63c7
 
c61036b
afb63c7
 
 
e28913e
afb63c7
e28913e
 
17a9d42
e28913e
 
 
c61036b
afb63c7
 
 
 
c61036b
 
 
e28913e
 
c61036b
afb63c7
 
e28913e
c61036b
 
 
e28913e
 
 
c61036b
afb63c7
c61036b
 
 
afb63c7
e28913e
c61036b
afb63c7
c61036b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e28913e
 
c61036b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f4fe845
c61036b
 
e28913e
c61036b
 
 
 
 
 
afb63c7
c61036b
 
 
 
 
afb63c7
 
 
 
c61036b
afb63c7
c61036b
afb63c7
c61036b
 
 
 
 
 
afb63c7
c61036b
e28913e
c61036b
 
 
 
e28913e
c61036b
 
 
 
 
 
 
6139425
c61036b
 
 
6139425
e28913e
c61036b
 
 
 
 
 
 
6139425
c61036b
 
6139425
 
c61036b
 
6139425
 
 
 
 
c61036b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e28913e
c61036b
 
 
 
 
6139425
c61036b
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#!/bin/bash

# 检查 Hugging Face Token 和 Dataset ID 环境变量
if [[ -z "$HF_TOKEN" ]] || [[ -z "$DATASET_ID" ]]; then
    echo "Starting Cloudreve without backup/restore functionality - missing HF_TOKEN or DATASET_ID"
    # 直接启动 Cloudreve 作为主进程
    echo "Starting Cloudreve directly..."
    exec /opt/cloudreve/cloudreve -c /opt/cloudreve/config.ini
    exit 0 # exec 通常不会返回,但加上 exit 0 以防万一
fi

# 激活 Python 虚拟环境
echo "Activating Python venv..."
source /opt/venv/bin/activate

# 定义 Cloudreve 主程序目录 和 备份文件前缀
CLOUDREVE_DIR="/opt/cloudreve"
BACKUP_PREFIX="cloudreve_backup"
CONFIG_FILE_PATH="/opt/cloudreve/config.ini"
DB_FILE_PATH="/opt/cloudreve/cloudreve.db"
EXECUTABLE_PATH="/opt/cloudreve/cloudreve"

# --- Python 函数定义 ---
# (Python 函数 upload_backup 和 download_latest_backup 保持不变,这里省略以减少篇幅)
# --- 请将你原始脚本中的 Python 函数 upload_backup 和 download_latest_backup 复制到这里 ---
# Python 函数: 上传备份
upload_backup() {
    file_path="$1"
    file_name="$2"
    token="$HF_TOKEN"
    repo_id="$DATASET_ID"

    echo "Preparing to upload backup file: $file_path as $file_name to Dataset: $repo_id"

    python3 -c "
from huggingface_hub import HfApi
import sys
import os
print(f'HF_TOKEN is set: {os.environ.get(\"HF_TOKEN\") is not None}')
print(f'DATASET_ID is set: {os.environ.get(\"DATASET_ID\") is not None}')
def manage_backups(api, repo_id_val, max_files=5):
    print('Managing old backups...')
    files = api.list_repo_files(repo_id=repo_id_val, repo_type='dataset')
    backup_files = [f for f in files if f.startswith('$BACKUP_PREFIX') and f.endswith('.tar.gz')]
    backup_files.sort()
    if len(backup_files) >= max_files:
        print(f'Found {len(backup_files)} backup files, maximum allowed is {max_files}.')
        files_to_delete = backup_files[:(len(backup_files) - max_files + 1)]
        for file_to_delete in files_to_delete:
            try:
                print(f'Deleting old backup: {file_to_delete}')
                api.delete_file(path_in_repo=file_to_delete, repo_id=repo_id_val, repo_type='dataset')
                print(f'Successfully deleted: {file_to_delete}')
            except Exception as e:
                print(f'Error deleting {file_to_delete}: {str(e)}')
    else:
        print('Number of backup files is within the limit.')
api = HfApi(token='$token')
try:
    repo_id_val = os.environ.get('DATASET_ID') # 从环境变量中获取 repo_id
    if not repo_id_val:
        raise ValueError('DATASET_ID environment variable is not set.')
    print(f'Uploading file: $file_path to {repo_id_val} as $file_name')
    api.upload_file(
        path_or_fileobj='$file_path',
        path_in_repo='$file_name',
        repo_id=repo_id_val,
        repo_type='dataset'
    )
    print(f'Successfully uploaded $file_name')
    manage_backups(api, repo_id_val)
except Exception as e:
    print(f'Error uploading file: {str(e)}')
    sys.exit(1) # Exit if upload fails
"
}

# Python 函数: 下载最新备份
download_latest_backup() {
  token="$HF_TOKEN"
  repo_id="$DATASET_ID"

  echo "Preparing to download the latest backup from Dataset: $repo_id"

  python3 -c "
from huggingface_hub import HfApi, hf_hub_download
import sys
import os
import tarfile
import tempfile
import shutil
import subprocess

print(f'HF_TOKEN is set: {os.environ.get(\"HF_TOKEN\") is not None}')
print(f'DATASET_ID is set: {os.environ.get(\"DATASET_ID\") is not None}')

api = HfApi(token='$token')
try:
    repo_id_val = os.environ.get('DATASET_ID') # 从环境变量中获取 repo_id
    if not repo_id_val:
        raise ValueError('DATASET_ID environment variable is not set.')

    print(f'Listing files in Dataset: {repo_id_val}')
    files = api.list_repo_files(repo_id=repo_id_val, repo_type='dataset')
    backup_files = [f for f in files if f.startswith('$BACKUP_PREFIX') and f.endswith('.tar.gz')]

    if not backup_files:
        print('No backup files found in the Dataset. Skipping restore.')
        sys.exit(0) # Exit successfully if no backups to restore

    latest_backup = sorted(backup_files)[-1]
    print(f'Latest backup file found: {latest_backup}')

    with tempfile.TemporaryDirectory() as temp_dir:
        print(f'Downloading {latest_backup} to temporary directory {temp_dir}...')
        try:
             filepath = hf_hub_download(
                repo_id=repo_id_val,
                filename=latest_backup,
                repo_type='dataset',
                local_dir=temp_dir,
                token=os.environ.get('HF_TOKEN') # Pass token explicitly if needed
            )
        except Exception as download_error:
             print(f'Error during hf_hub_download: {download_error}')
             # Attempt to list files again for debugging
             try:
                 print('Attempting to list repo files again for debugging...')
                 files_debug = api.list_repo_files(repo_id=repo_id_val, repo_type='dataset')
                 print(f'Files found (debug): {files_debug}')
             except Exception as list_error:
                 print(f'Error listing files during debug: {list_error}')
             sys.exit(1)


        if filepath and os.path.exists(filepath):
            print(f'Successfully downloaded backup to temporary directory: {filepath}')

            # Files/Dirs to restore (relative paths within CLOUDREVE_DIR)
            items_to_restore = ['cloudreve', 'cloudreve.db', 'config.ini']

            # Ensure target directory exists
            os.makedirs(\"$CLOUDREVE_DIR\", exist_ok=True)

            print('Listing contents before restore:')
            subprocess.run(['ls', '-lA', \"$CLOUDREVE_DIR\"], check=False) # Use -A to show hidden files

            # --- Safer Restore Logic ---
            # 1. Extract backup to a temporary location first
            extract_temp_dir = os.path.join(temp_dir, 'extracted_backup')
            os.makedirs(extract_temp_dir, exist_ok=True)
            print(f'Extracting backup archive: {filepath} to {extract_temp_dir}')
            try:
                with tarfile.open(filepath, 'r:gz') as tar:
                    tar.extractall(extract_temp_dir)
                print('Extraction complete.')
            except tarfile.ReadError as tar_err:
                print(f'Error reading tar file: {tar_err}')
                sys.exit(1)
            except Exception as extract_err:
                 print(f'Error during extraction: {extract_err}')
                 sys.exit(1)


            # 2. Check if essential files exist in the extracted backup
            essential_files_present = True
            for item in items_to_restore:
                 extracted_item_path = os.path.join(extract_temp_dir, item)
                 if not os.path.exists(extracted_item_path):
                      print(f'Error: Essential item "{item}" not found in extracted backup at {extracted_item_path}. Aborting restore.')
                      essential_files_present = False
                      break # Stop checking

            if not essential_files_present:
                 sys.exit(1) # Abort if essential files are missing

            # 3. Delete existing items in the target directory
            print(f'Deleting existing items in $CLOUDREVE_DIR before restoring...')
            for item in items_to_restore:
                target_path = os.path.join(\"$CLOUDREVE_DIR\", item)
                if os.path.exists(target_path):
                    try:
                        if os.path.isdir(target_path) and not os.path.islink(target_path):
                            print(f'Deleting directory: {target_path}')
                            shutil.rmtree(target_path)
                        else:
                            print(f'Deleting file/link: {target_path}')
                            os.remove(target_path)
                    except OSError as e:
                        print(f'Error deleting {target_path}: {e}. Continuing...')


            # 4. Move extracted items to the target directory
            print(f'Moving extracted items from {extract_temp_dir} to $CLOUDREVE_DIR...')
            for item in items_to_restore:
                 source_path = os.path.join(extract_temp_dir, item)
                 target_path = os.path.join(\"$CLOUDREVE_DIR\", item)
                 try:
                      print(f'Moving {source_path} to {target_path}')
                      shutil.move(source_path, target_path)
                 except Exception as move_err:
                      print(f'Error moving {item}: {move_err}')
                      # Decide if this is critical, maybe exit? For now, print and continue.


            print(f'Successfully restored backup from {latest_backup}')
            print('Listing contents after restore:')
            subprocess.run(['ls', '-lA', \"$CLOUDREVE_DIR\"], check=False) # Use -A
        else:
            print(f'Error: Downloaded file path "{filepath}" does not exist or download failed.')
            sys.exit(1) # Exit if download path invalid

except ValueError as ve:
    print(f'Configuration Error: {ve}')
    sys.exit(1)
except Exception as e:
    print(f'Error during backup download/restore: {str(e)}')
    # Print traceback for more details
    import traceback
    traceback.print_exc()
    sys.exit(1) # Exit on error
"
}


# --- Sync Function ---
sync_data() {
    echo "Background Sync Process Started"
    while true; do
        # Wait for initial Cloudreve setup potentially creating db/config if first run
        # Also wait if essential files are missing before attempting backup
        while [ ! -f "$CONFIG_FILE_PATH" ] || [ ! -f "$DB_FILE_PATH" ] || [ ! -f "$EXECUTABLE_PATH" ]; do
             echo "Waiting for essential Cloudreve files (config.ini, cloudreve.db, cloudreve) to exist before backup attempt..."
             sleep 15
        done

        echo "Starting sync cycle at $(date)"

        # Define backup path and name
        timestamp=$(date +%Y%m%d_%H%M%S)
        backup_file="${BACKUP_PREFIX}_${timestamp}.tar.gz"
        backup_path="/tmp/${backup_file}" # Use /tmp for temporary files

        echo "Compressing Cloudreve data (executable, db, config) to: $backup_path"
        # Use -C to change directory, ensuring archive paths are relative
        # Only include the executable, db, and config file
        tar -czf "$backup_path" -C "$CLOUDREVE_DIR" \
            $(basename "$EXECUTABLE_PATH") \
            $(basename "$DB_FILE_PATH") \
            $(basename "$CONFIG_FILE_PATH")

        # Check if compression was successful (file exists and is not empty)
        if [ -s "$backup_path" ]; then
            echo "Compression complete. File size: $(ls -lh "$backup_path" | awk '{print $5}')"
            echo "Uploading backup to HuggingFace..."
            upload_backup "$backup_path" "${backup_file}"
            # Check exit status of upload_backup? The python script should exit non-zero on failure.
            if [ $? -ne 0 ]; then
                echo "Backup upload failed. Keeping local archive: $backup_path"
            else
                echo "Upload successful. Removing local archive."
                rm -f "$backup_path"
            fi
        else
            echo "Compression failed or created an empty file. Skipping upload."
            rm -f "$backup_path" # Remove potentially empty/corrupt file
        fi

        # Define sync interval (use environment variable or default to 3600 seconds = 1 hour)
        SYNC_INTERVAL=${SYNC_INTERVAL:-3600}
        echo "Next sync in ${SYNC_INTERVAL} seconds..."
        sleep $SYNC_INTERVAL
    done
}

# --- Main Execution ---

# 1. Attempt to restore from the latest backup on startup
echo "Attempting to restore latest backup from HuggingFace..."
download_latest_backup
# Check exit code? If restore fails critically, maybe don't start?
# The python script now exits non-zero on critical errors.
if [ $? -ne 0 ]; then
    echo "CRITICAL: Backup restoration failed. Exiting."
    exit 1
fi
echo "Backup restore process finished."


# 2. Check if config file exists after potential restore. If not, Cloudreve needs to run once to create it.
if [ ! -f "$CONFIG_FILE_PATH" ]; then
    echo "Config file ($CONFIG_FILE_PATH) not found. Running Cloudreve once to generate initial config."
    /opt/cloudreve/cloudreve -c "$CONFIG_FILE_PATH"
    # Cloudreve will print initial password and exit (or wait for setup if web setup enabled)
    # Need to check if it actually created the config...
    if [ ! -f "$CONFIG_FILE_PATH" ]; then
        echo "CRITICAL: Cloudreve failed to create initial config file. Exiting."
        exit 1
    else
         echo "Initial config file created. Please check logs for admin credentials if needed."
         # Consider stopping here or adding a pause? For automated deployment, continue.
    fi
fi


# 3. Start the background sync process
echo "Starting background data sync..."
sync_data & # Run sync_data function in the background
sync_pid=$! # Get PID of background sync process

# 4. Start Cloudreve in the foreground using exec
# 'exec' replaces the current shell process with the Cloudreve process.
# This makes Cloudreve the main process of the container.
echo "Starting Cloudreve application as the main process..."
exec /opt/cloudreve/cloudreve -c "$CONFIG_FILE_PATH"

# If exec fails, the script continues here.
exec_failed_code=$?
echo "CRITICAL: Failed to execute Cloudreve. Exit code: $exec_failed_code"
# Attempt to kill the background sync process if exec failed
kill $sync_pid 2>/dev/null
exit $exec_failed_code