#!/bin/bash # RAGFlow Data Migration Script # Usage: ./migration.sh [-p project_name] [backup|restore] [backup_folder] # # This script helps you backup and restore RAGFlow Docker volumes # including MySQL, MinIO, Redis, and Elasticsearch data. set -e # Exit on any error # Instead, we'll handle errors manually for better debugging experience # Default values DEFAULT_BACKUP_FOLDER="backup" DEFAULT_PROJECT_NAME="docker" VOLUME_BASES=("mysql_data" "minio_data" "redis_data" "esdata01") BACKUP_FILES=("mysql_backup.tar.gz" "minio_backup.tar.gz" "redis_backup.tar.gz" "es_backup.tar.gz") # Build volume names from project name and base names build_volume_names() { VOLUMES=() for base in "${VOLUME_BASES[@]}"; do VOLUMES+=("${PROJECT_NAME}_${base}") done } # Function to display help information show_help() { echo "RAGFlow Data Migration Tool" echo "" echo "USAGE:" echo " $0 [-p project_name] [backup_folder]" echo "" echo "OPERATIONS:" echo " backup - Create backup of all RAGFlow data volumes" echo " restore - Restore RAGFlow data volumes from backup" echo " help - Show this help message" echo "" echo "OPTIONS:" echo " -p project_name - Docker Compose project name (default: '$DEFAULT_PROJECT_NAME')" echo " Use this when you started RAGFlow with 'docker compose -p '" echo "" echo "PARAMETERS:" echo " backup_folder - Name of backup folder (default: '$DEFAULT_BACKUP_FOLDER')" echo "" echo "EXAMPLES:" echo " $0 backup # Backup with default project name 'docker'" echo " $0 backup my_backup # Backup to './my_backup' folder" echo " $0 restore # Restore from './backup' folder" echo " $0 restore my_backup # Restore from './my_backup' folder" echo " $0 -p ragflow backup # Backup volumes for project 'ragflow'" echo " $0 -p ragflow restore my_backup # Restore volumes for project 'ragflow'" echo "" echo "DOCKER VOLUMES (with default project name '$DEFAULT_PROJECT_NAME'):" echo " - ${DEFAULT_PROJECT_NAME}_mysql_data (MySQL database)" echo " - ${DEFAULT_PROJECT_NAME}_minio_data (MinIO object storage)" echo " - ${DEFAULT_PROJECT_NAME}_redis_data (Redis cache)" echo " - ${DEFAULT_PROJECT_NAME}_esdata01 (Elasticsearch indices)" echo "" echo "NOTE:" echo " If you started RAGFlow with 'docker compose -p myproject up', the volume" echo " names will be prefixed with 'myproject' instead of 'docker'. In that case," echo " use '-p myproject' with this script to match the correct volumes." } # Function to check if Docker is running check_docker() { if ! docker info >/dev/null 2>&1; then echo "❌ Error: Docker is not running or not accessible" echo "Please start Docker and try again" exit 1 fi } # Function to check if volume exists volume_exists() { local volume_name=$1 docker volume inspect "$volume_name" >/dev/null 2>&1 } # Function to check if any containers are using the target volumes check_containers_using_volumes() { echo "🔍 Checking for running containers that might be using target volumes..." # Get all running containers local running_containers=$(docker ps --format "{{.Names}}") if [ -z "$running_containers" ]; then echo "✅ No running containers found" return 0 fi # Check each running container for volume usage local containers_using_volumes=() local volume_usage_details=() for container in $running_containers; do # Get container's mount information local mounts=$(docker inspect "$container" --format '{{range .Mounts}}{{.Source}}{{"|"}}{{end}}' 2>/dev/null || echo "") # Check if any of our target volumes are used by this container for volume in "${VOLUMES[@]}"; do if echo "$mounts" | grep -q "$volume"; then containers_using_volumes+=("$container") volume_usage_details+=("$container -> $volume") break fi done done # If any containers are using our volumes, show error and exit if [ ${#containers_using_volumes[@]} -gt 0 ]; then echo "" echo "❌ ERROR: Found running containers using target volumes!" echo "" echo "📋 Running containers status:" docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" echo "" echo "🔗 Volume usage details:" for detail in "${volume_usage_details[@]}"; do echo " - $detail" done echo "" if [ "$PROJECT_NAME" = "$DEFAULT_PROJECT_NAME" ]; then echo "🛑 SOLUTION: Stop the containers before performing backup/restore operations:" echo " docker compose -f docker/.yml down" else echo "🛑 SOLUTION: Stop the containers before performing backup/restore operations:" echo " docker compose -p $PROJECT_NAME -f docker/.yml down" fi echo "" echo "💡 After backup/restore, you can restart with the corresponding 'up -d' command." echo "" exit 1 fi echo "✅ No containers are using target volumes, safe to proceed" return 0 } # Function to confirm user action confirm_action() { local message=$1 echo -n "$message (y/N): " read -r response case "$response" in [yY]|[yY][eE][sS]) return 0 ;; *) return 1 ;; esac } # Function to perform backup perform_backup() { local backup_folder=$1 echo "🚀 Starting RAGFlow data backup..." echo "📁 Backup folder: $backup_folder" echo "🏷️ Project name: $PROJECT_NAME" echo "" # Check if any containers are using the volumes check_containers_using_volumes # Create backup folder if it doesn't exist mkdir -p "$backup_folder" local total=${#VOLUMES[@]} # Backup each volume for i in "${!VOLUMES[@]}"; do local volume="${VOLUMES[$i]}" local backup_file="${BACKUP_FILES[$i]}" local step=$((i + 1)) echo "📦 Step $step/$total: Backing up $volume..." if volume_exists "$volume"; then docker run --rm \ -v "$volume":/source \ -v "$(pwd)/$backup_folder":/backup \ alpine tar czf "/backup/$backup_file" -C /source . echo "✅ Successfully backed up $volume to $backup_folder/$backup_file" else echo "⚠️ Warning: Volume $volume does not exist, skipping..." fi echo "" done echo "🎉 Backup completed successfully!" echo "📍 Backup location: $(pwd)/$backup_folder" # List backup files with sizes echo "" echo "📋 Backup files created:" for backup_file in "${BACKUP_FILES[@]}"; do if [ -f "$backup_folder/$backup_file" ]; then local size=$(ls -lh "$backup_folder/$backup_file" | awk '{print $5}') echo " - $backup_file ($size)" fi done } # Function to perform restore perform_restore() { local backup_folder=$1 echo "🔄 Starting RAGFlow data restore..." echo "📁 Backup folder: $backup_folder" echo "🏷️ Project name: $PROJECT_NAME" echo "" # Check if any containers are using the volumes check_containers_using_volumes # Check if backup folder exists if [ ! -d "$backup_folder" ]; then echo "❌ Error: Backup folder '$backup_folder' does not exist" exit 1 fi # Check if all backup files exist local missing_files=() for backup_file in "${BACKUP_FILES[@]}"; do if [ ! -f "$backup_folder/$backup_file" ]; then missing_files+=("$backup_file") fi done if [ ${#missing_files[@]} -gt 0 ]; then echo "❌ Error: Missing backup files:" for file in "${missing_files[@]}"; do echo " - $file" done echo "Please ensure all backup files are present in '$backup_folder'" exit 1 fi # Check for existing volumes and warn user local existing_volumes=() for volume in "${VOLUMES[@]}"; do if volume_exists "$volume"; then existing_volumes+=("$volume") fi done if [ ${#existing_volumes[@]} -gt 0 ]; then echo "⚠️ WARNING: The following Docker volumes already exist:" for volume in "${existing_volumes[@]}"; do echo " - $volume" done echo "" echo "🔴 IMPORTANT: Restoring will OVERWRITE existing data!" echo "💡 Recommendation: Create a backup of your current data first:" echo " $0 -p $PROJECT_NAME backup current_backup_$(date +%Y%m%d_%H%M%S)" echo "" if ! confirm_action "Do you want to continue with the restore operation?"; then echo "❌ Restore operation cancelled by user" exit 0 fi fi local total=${#VOLUMES[@]} # Create volumes and restore data for i in "${!VOLUMES[@]}"; do local volume="${VOLUMES[$i]}" local backup_file="${BACKUP_FILES[$i]}" local step=$((i + 1)) echo "🔧 Step $step/$total: Restoring $volume..." # Create volume if it doesn't exist if ! volume_exists "$volume"; then echo " 📋 Creating Docker volume: $volume" docker volume create "$volume" else echo " 📋 Using existing Docker volume: $volume" fi # Restore data echo " 📥 Restoring data from $backup_file..." docker run --rm \ -v "$volume":/target \ -v "$(pwd)/$backup_folder":/backup \ alpine tar xzf "/backup/$backup_file" -C /target echo "✅ Successfully restored $volume" echo "" done echo "🎉 Restore completed successfully!" echo "💡 You can now start your RAGFlow services" } # Main script logic main() { # Check if Docker is available check_docker # Parse -p flag PROJECT_NAME="$DEFAULT_PROJECT_NAME" while [ $# -gt 0 ]; do case "$1" in -p) if [ -z "${2:-}" ]; then echo "❌ Error: -p requires a project name argument" exit 1 fi PROJECT_NAME="$2" shift 2 ;; *) break ;; esac done # Build volume names based on project name build_volume_names # Parse remaining positional arguments local operation=${1:-} local backup_folder=${2:-$DEFAULT_BACKUP_FOLDER} # Handle help or no arguments if [ -z "$operation" ] || [ "$operation" = "help" ] || [ "$operation" = "-h" ] || [ "$operation" = "--help" ]; then show_help exit 0 fi # Validate operation case "$operation" in backup) perform_backup "$backup_folder" ;; restore) perform_restore "$backup_folder" ;; *) echo "❌ Error: Invalid operation '$operation'" echo "" show_help exit 1 ;; esac } # Run main function with all arguments main "$@"