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 |