|
|
#!/bin/bash |
|
|
|
|
|
|
|
|
if [[ -z "$HF_TOKEN" ]] || [[ -z "$DATASET_ID" ]]; then |
|
|
echo "Starting Cloudreve without backup/restore functionality - missing HF_TOKEN or DATASET_ID" |
|
|
|
|
|
echo "Starting Cloudreve directly..." |
|
|
exec /opt/cloudreve/cloudreve -c /opt/cloudreve/config.ini |
|
|
exit 0 |
|
|
fi |
|
|
|
|
|
|
|
|
echo "Activating Python venv..." |
|
|
source /opt/venv/bin/activate |
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
" |
|
|
} |
|
|
|
|
|
|
|
|
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_data() { |
|
|
echo "Background Sync Process Started" |
|
|
while true; do |
|
|
|
|
|
|
|
|
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)" |
|
|
|
|
|
|
|
|
timestamp=$(date +%Y%m%d_%H%M%S) |
|
|
backup_file="${BACKUP_PREFIX}_${timestamp}.tar.gz" |
|
|
backup_path="/tmp/${backup_file}" |
|
|
|
|
|
echo "Compressing Cloudreve data (executable, db, config) to: $backup_path" |
|
|
|
|
|
|
|
|
tar -czf "$backup_path" -C "$CLOUDREVE_DIR" \ |
|
|
$(basename "$EXECUTABLE_PATH") \ |
|
|
$(basename "$DB_FILE_PATH") \ |
|
|
$(basename "$CONFIG_FILE_PATH") |
|
|
|
|
|
|
|
|
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}" |
|
|
|
|
|
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" |
|
|
fi |
|
|
|
|
|
|
|
|
SYNC_INTERVAL=${SYNC_INTERVAL:-3600} |
|
|
echo "Next sync in ${SYNC_INTERVAL} seconds..." |
|
|
sleep $SYNC_INTERVAL |
|
|
done |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
echo "Attempting to restore latest backup from HuggingFace..." |
|
|
download_latest_backup |
|
|
|
|
|
|
|
|
if [ $? -ne 0 ]; then |
|
|
echo "CRITICAL: Backup restoration failed. Exiting." |
|
|
exit 1 |
|
|
fi |
|
|
echo "Backup restore process finished." |
|
|
|
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
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." |
|
|
|
|
|
fi |
|
|
fi |
|
|
|
|
|
|
|
|
|
|
|
echo "Starting background data sync..." |
|
|
sync_data & |
|
|
sync_pid=$! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
echo "Starting Cloudreve application as the main process..." |
|
|
exec /opt/cloudreve/cloudreve -c "$CONFIG_FILE_PATH" |
|
|
|
|
|
|
|
|
exec_failed_code=$? |
|
|
echo "CRITICAL: Failed to execute Cloudreve. Exit code: $exec_failed_code" |
|
|
|
|
|
kill $sync_pid 2>/dev/null |
|
|
exit $exec_failed_code |