mirror of
https://github.com/acedanger/shell.git
synced 2025-12-05 22:50:18 -08:00
feat: Enhance restoration script with disk space validation and improved PostgreSQL readiness checks
This commit is contained in:
@@ -208,6 +208,20 @@ validate_backup_files() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check available disk space for database restoration
|
||||
local db_size_bytes=$(stat -c%s "$DB_BACKUP" 2>/dev/null || echo "0")
|
||||
local available_space=$(df /tmp | tail -1 | awk '{print $4}')
|
||||
available_space=$((available_space * 1024)) # Convert to bytes
|
||||
|
||||
# Estimate uncompressed size (conservative: 5x compressed size)
|
||||
local estimated_uncompressed=$((db_size_bytes * 5))
|
||||
|
||||
if [ $estimated_uncompressed -gt $available_space ]; then
|
||||
log_message "Error: Insufficient disk space for database restoration"
|
||||
log_message "Estimated space needed: $(( estimated_uncompressed / 1024 / 1024 ))MB, Available: $(( available_space / 1024 / 1024 ))MB"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local db_size=$(du -sh "$DB_BACKUP" 2>/dev/null | cut -f1 || echo "unknown")
|
||||
log_message "✓ Database backup validated: $DB_BACKUP ($db_size)"
|
||||
fi
|
||||
@@ -224,6 +238,20 @@ validate_backup_files() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check available disk space for uploads restoration
|
||||
local uploads_size_bytes=$(stat -c%s "$UPLOADS_BACKUP" 2>/dev/null || echo "0")
|
||||
local upload_dir_space=$(df "$(dirname "$UPLOAD_LOCATION")" | tail -1 | awk '{print $4}')
|
||||
upload_dir_space=$((upload_dir_space * 1024)) # Convert to bytes
|
||||
|
||||
# Estimate uncompressed size (conservative: 2x compressed size for media files)
|
||||
local estimated_uploads_uncompressed=$((uploads_size_bytes * 2))
|
||||
|
||||
if [ $estimated_uploads_uncompressed -gt $upload_dir_space ]; then
|
||||
log_message "Error: Insufficient disk space for uploads restoration"
|
||||
log_message "Estimated space needed: $(( estimated_uploads_uncompressed / 1024 / 1024 ))MB, Available: $(( upload_dir_space / 1024 / 1024 ))MB"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local uploads_size=$(du -sh "$UPLOADS_BACKUP" 2>/dev/null | cut -f1 || echo "unknown")
|
||||
log_message "✓ Uploads backup validated: $UPLOADS_BACKUP ($uploads_size)"
|
||||
fi
|
||||
@@ -272,7 +300,21 @@ manage_containers() {
|
||||
|
||||
# Wait for postgres to be ready
|
||||
log_message "Waiting for PostgreSQL to be ready..."
|
||||
sleep 10
|
||||
local max_attempts=15
|
||||
local attempt=0
|
||||
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
if docker exec immich_postgres pg_isready -U "$DB_USERNAME" >/dev/null 2>&1; then
|
||||
log_message "✓ PostgreSQL is ready"
|
||||
break
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
log_message "Warning: PostgreSQL may not be fully ready"
|
||||
fi
|
||||
|
||||
# Start immich_server
|
||||
docker start immich_server || {
|
||||
@@ -327,24 +369,32 @@ restore_database() {
|
||||
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
if docker exec immich_postgres pg_isready -U "$DB_USERNAME" >/dev/null 2>&1; then
|
||||
log_message "✓ PostgreSQL is ready (attempt $((attempt + 1)))"
|
||||
break
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
log_message "Waiting for PostgreSQL... (attempt $attempt/$max_attempts)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
log_message "Error: PostgreSQL did not become ready within timeout"
|
||||
log_message "Error: PostgreSQL did not become ready within timeout (${max_attempts} attempts)"
|
||||
rm -rf "$temp_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_message "✓ PostgreSQL is ready"
|
||||
|
||||
# Drop existing database and recreate (if it exists)
|
||||
log_message "Preparing database for restoration..."
|
||||
docker exec immich_postgres psql -U "$DB_USERNAME" -c "DROP DATABASE IF EXISTS $DB_DATABASE_NAME;" 2>/dev/null || true
|
||||
docker exec immich_postgres psql -U "$DB_USERNAME" -c "CREATE DATABASE $DB_DATABASE_NAME;" || {
|
||||
|
||||
# Validate database and username names to prevent injection
|
||||
if [[ ! "$DB_DATABASE_NAME" =~ ^[a-zA-Z0-9_]+$ ]] || [[ ! "$DB_USERNAME" =~ ^[a-zA-Z0-9_]+$ ]]; then
|
||||
log_message "Error: Invalid characters in database name or username"
|
||||
rm -rf "$temp_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec immich_postgres psql -U "$DB_USERNAME" -c "DROP DATABASE IF EXISTS \"$DB_DATABASE_NAME\";" 2>/dev/null || true
|
||||
docker exec immich_postgres psql -U "$DB_USERNAME" -c "CREATE DATABASE \"$DB_DATABASE_NAME\";" || {
|
||||
log_message "Error: Failed to create database $DB_DATABASE_NAME"
|
||||
rm -rf "$temp_dir"
|
||||
exit 1
|
||||
@@ -401,18 +451,51 @@ restore_uploads() {
|
||||
mkdir -p "$UPLOAD_LOCATION"
|
||||
fi
|
||||
|
||||
# Extract uploads backup
|
||||
# Extract uploads backup with security safeguards
|
||||
log_message "Extracting uploads backup..."
|
||||
log_message "This may take a while depending on the size of your media library..."
|
||||
|
||||
# Extract to parent directory, then move content to correct location
|
||||
# Extract to parent directory with security options
|
||||
local parent_dir=$(dirname "$UPLOAD_LOCATION")
|
||||
local upload_dirname=$(basename "$UPLOAD_LOCATION")
|
||||
|
||||
if tar -xzf "$UPLOADS_BACKUP" -C "$parent_dir"; then
|
||||
# Create a secure temporary extraction directory
|
||||
local temp_extract_dir=$(mktemp -d)
|
||||
|
||||
# Extract with safety options to prevent path traversal
|
||||
if tar --no-absolute-names --strip-components=0 -xzf "$UPLOADS_BACKUP" -C "$temp_extract_dir"; then
|
||||
# Move extracted content to final location
|
||||
if [ -d "$UPLOAD_LOCATION" ]; then
|
||||
log_message "Backup existing upload directory..."
|
||||
mv "$UPLOAD_LOCATION" "${UPLOAD_LOCATION}.backup.$(date +%s)" || {
|
||||
log_message "Error: Failed to backup existing upload directory"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Move extracted content to final location
|
||||
mv "$temp_extract_dir"/* "$UPLOAD_LOCATION" 2>/dev/null || {
|
||||
# Handle case where extraction created nested directories
|
||||
local extracted_dir=$(find "$temp_extract_dir" -mindepth 1 -maxdepth 1 -type d | head -1)
|
||||
if [ -n "$extracted_dir" ]; then
|
||||
mv "$extracted_dir" "$UPLOAD_LOCATION" || {
|
||||
log_message "Error: Failed to move extracted files to upload location"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
log_message "Error: No content found in uploads backup"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
rm -rf "$temp_extract_dir"
|
||||
log_message "✅ Uploads restoration completed successfully"
|
||||
else
|
||||
log_message "Error: Failed to extract uploads backup"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -490,11 +573,15 @@ verify_restoration() {
|
||||
|
||||
# Initialize variables
|
||||
CONTAINERS_STOPPED=false
|
||||
RESTORE_START_TIME=$(date +%s)
|
||||
|
||||
# Main restoration logic
|
||||
log_message "=== IMMICH RESTORATION STARTED ==="
|
||||
send_notification "🔄 Immich Restore Started" "Beginning restoration from backup files" "info"
|
||||
|
||||
# Validate backup files first (even in dry-run mode for safety)
|
||||
validate_backup_files
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_message "=== DRY RUN MODE - NO ACTUAL RESTORATION WILL BE PERFORMED ==="
|
||||
log_message ""
|
||||
@@ -515,9 +602,6 @@ if [ "$DRY_RUN" = true ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Validate backup files
|
||||
validate_backup_files
|
||||
|
||||
# Stop containers
|
||||
manage_containers stop
|
||||
|
||||
@@ -530,7 +614,20 @@ verify_restoration
|
||||
|
||||
# Calculate restoration time and send success notification
|
||||
RESTORE_END_TIME=$(date +%s)
|
||||
TOTAL_RESTORE_TIME=$((RESTORE_END_TIME - $(date +%s)))
|
||||
TOTAL_RESTORE_TIME=$((RESTORE_END_TIME - RESTORE_START_TIME))
|
||||
RESTORE_TIME_FORMATTED=""
|
||||
|
||||
if [ $TOTAL_RESTORE_TIME -gt 3600 ]; then
|
||||
HOURS=$((TOTAL_RESTORE_TIME / 3600))
|
||||
MINUTES=$(((TOTAL_RESTORE_TIME % 3600) / 60))
|
||||
RESTORE_TIME_FORMATTED="${HOURS}h ${MINUTES}m"
|
||||
elif [ $TOTAL_RESTORE_TIME -gt 60 ]; then
|
||||
MINUTES=$((TOTAL_RESTORE_TIME / 60))
|
||||
SECONDS=$((TOTAL_RESTORE_TIME % 60))
|
||||
RESTORE_TIME_FORMATTED="${MINUTES}m ${SECONDS}s"
|
||||
else
|
||||
RESTORE_TIME_FORMATTED="${TOTAL_RESTORE_TIME}s"
|
||||
fi
|
||||
|
||||
# Format file information for notification
|
||||
NOTIFICATION_MESSAGE=""
|
||||
@@ -550,7 +647,7 @@ if [ "$SKIP_UPLOADS" = false ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
NOTIFICATION_MESSAGE="${NOTIFICATION_MESSAGE}\n✅ Restoration completed successfully"
|
||||
NOTIFICATION_MESSAGE="${NOTIFICATION_MESSAGE}\n✅ Restoration completed successfully in ${RESTORE_TIME_FORMATTED}"
|
||||
NOTIFICATION_MESSAGE="${NOTIFICATION_MESSAGE}\n🏠 Restored to: $(hostname)"
|
||||
|
||||
send_notification "✅ Immich Restore Completed" "$NOTIFICATION_MESSAGE" "success"
|
||||
|
||||
Reference in New Issue
Block a user