From 02a90074113bd7bae0a123f2579f704ab5455100 Mon Sep 17 00:00:00 2001 From: kqjy Date: Fri, 21 Nov 2025 03:50:09 +0000 Subject: [PATCH] Update migrate.sh --- migrate.sh | 110 +++++++++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/migrate.sh b/migrate.sh index 69473de..93a9473 100644 --- a/migrate.sh +++ b/migrate.sh @@ -1,58 +1,74 @@ #!/bin/bash -# Usage: curl -sL https://your-cdn/migrate.sh | bash -s -- +# Usage: curl -sL https://url/migrate.sh | bash -s -- [SSH_KEY_PATH] LOCAL_SEARCH="$1" DEST_CONN="$2" REMOTE_SEARCH="$3" +SSH_KEY_PATH="$4" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' -# --- 1. PRE-FLIGHT & SSH CHECK --- +# --- 1. CONFIGURE SSH --- +# Base options to avoid "Are you sure?" prompts and timeouts +SSH_BASE_OPTS="-o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=no" + +if [ -n "$SSH_KEY_PATH" ]; then + echo -e "šŸ”‘ Using Identity File: ${YELLOW}$SSH_KEY_PATH${NC}" + if [ ! -f "$SSH_KEY_PATH" ]; then + echo -e "${RED}Error: Key file not found at $SSH_KEY_PATH${NC}" + exit 1 + fi + # Create the SSH command with the key + SSH_CMD="ssh -i $SSH_KEY_PATH $SSH_BASE_OPTS" + # Create the RSYNC generic shell command + RSYNC_SH="ssh -i $SSH_KEY_PATH $SSH_BASE_OPTS" +else + SSH_CMD="ssh $SSH_BASE_OPTS" + RSYNC_SH="ssh $SSH_BASE_OPTS" +fi + +# --- 2. PRE-FLIGHT CHECKS --- if [ -z "$REMOTE_SEARCH" ]; then - echo -e "${RED}Usage: $0 ${NC}" - echo -e "Example: $0 postgres-old root@10.0.0.5 postgres-new" + echo -e "${RED}Usage: $0 [Key_Path]${NC}" exit 1 fi -echo -e "āš ļø ${YELLOW}REMINDER:${NC} Ensure SSH keys are set up: ${GREEN}ssh-copy-id $DEST_CONN${NC}" -if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$DEST_CONN" echo "SSH OK" >/dev/null 2>&1; then - echo -e "${RED}Error: SSH connection to $DEST_CONN failed.${NC}" +echo -e "šŸ“” Connecting to $DEST_CONN..." +if ! $SSH_CMD "$DEST_CONN" echo "SSH Connection OK" >/dev/null 2>&1; then + echo -e "${RED}Error: Cannot connect to $DEST_CONN.${NC}" + echo -e " Check your IP, User, or SSH Key permissions." exit 1 fi -# --- 2. IDENTIFY CONTAINERS --- -echo -e "\nšŸ” Finding containers..." +# --- 3. IDENTIFY CONTAINERS --- +echo -e "\nšŸ” Identifying Containers..." # Local L_ID=$(docker ps -aq --filter "name=${LOCAL_SEARCH}" | head -n 1) L_NAME=$(docker ps -a --filter "name=${LOCAL_SEARCH}" --format "{{.Names}}" | head -n 1) # Remote (via SSH) -R_ID=$(ssh "$DEST_CONN" "docker ps -aq --filter 'name=${REMOTE_SEARCH}' | head -n 1") -R_NAME=$(ssh "$DEST_CONN" "docker ps -a --filter 'name=${REMOTE_SEARCH}' --format '{{.Names}}' | head -n 1") +R_ID=$($SSH_CMD "$DEST_CONN" "docker ps -aq --filter 'name=${REMOTE_SEARCH}' | head -n 1") +R_NAME=$($SSH_CMD "$DEST_CONN" "docker ps -a --filter 'name=${REMOTE_SEARCH}' --format '{{.Names}}' | head -n 1") -if [ -z "$L_ID" ] || [ -z "$R_ID" ]; then - echo -e "${RED}Error: Could not find containers.${NC}" - echo "Local Found: ${L_ID:-None} | Remote Found: ${R_ID:-None}" - exit 1 -fi +if [ -z "$L_ID" ]; then echo -e "${RED}Local container '${LOCAL_SEARCH}' not found.${NC}"; exit 1; fi +if [ -z "$R_ID" ]; then echo -e "${RED}Remote container '${REMOTE_SEARCH}' not found on Server B.${NC}"; exit 1; fi echo -e " Source: ${GREEN}$L_NAME${NC} ($L_ID)" -echo -e " Target: ${GREEN}$R_NAME${NC} ($R_ID) on $DEST_CONN" +echo -e " Target: ${GREEN}$R_NAME${NC} ($R_ID)" -# --- 3. STOP CONTAINERS (SAFE MODE) --- -echo -e "\nšŸ›‘ ${RED}Stopping BOTH containers to ensure safe data transfer...${NC}" +# --- 4. STOP CONTAINERS --- +echo -e "\nšŸ›‘ ${RED}Stopping containers to freeze state...${NC}" docker stop "$L_ID" -ssh "$DEST_CONN" "docker stop $R_ID" +$SSH_CMD "$DEST_CONN" "docker stop $R_ID" -# --- 4. MAP & SYNC VOLUMES --- -echo -e "\nšŸ“¦ Analyzing Volume Maps..." +# --- 5. MIGRATE VOLUMES --- +echo -e "\nšŸ“¦ Mapping & Syncing Volumes..." -# Get list of internal mount targets from Source (Where data lives inside the container) -# Format: Type|SourcePath|DestinationPath +# Get Mounts: Type|SourcePath|InternalPath L_MOUNTS=$(docker inspect -f '{{range .Mounts}}{{.Type}}|{{.Source}}|{{.Destination}} {{end}}' "$L_ID") for MOUNT in $L_MOUNTS; do @@ -60,51 +76,37 @@ for MOUNT in $L_MOUNTS; do L_PATH=$(echo "$MOUNT" | cut -d'|' -f2) INTERNAL_TARGET=$(echo "$MOUNT" | cut -d'|' -f3) - # skip bind mounts that are likely system files (docker.sock, etc) - if [[ "$INTERNAL_TARGET" == *".sock" ]] || [[ "$INTERNAL_TARGET" == *".conf" ]]; then - echo " Skipping config/system file: $INTERNAL_TARGET" - continue - fi + # Skip system mounts + if [[ "$INTERNAL_TARGET" == *".sock" ]] || [[ "$INTERNAL_TARGET" == *".conf" ]]; then continue; fi - echo -e "\n šŸ”„ Processing mount at ${YELLOW}$INTERNAL_TARGET${NC}" + echo -e "\n šŸ”„ Volume: ${YELLOW}$INTERNAL_TARGET${NC}" - # Find the corresponding volume/path on the REMOTE container - # We look for the mount that shares the same INTERNAL destination - R_PATH_RAW=$(ssh "$DEST_CONN" "docker inspect -f '{{range .Mounts}}{{if eq .Destination \"$INTERNAL_TARGET\"}}{{.Source}}{{end}}{{end}}' $R_ID") + # Find corresponding path on Remote + R_PATH_RAW=$($SSH_CMD "$DEST_CONN" "docker inspect -f '{{range .Mounts}}{{if eq .Destination \"$INTERNAL_TARGET\"}}{{.Source}}{{end}}{{end}}' $R_ID") if [ -z "$R_PATH_RAW" ]; then - echo -e " ${RED}Warning: Target container has no matching volume for $INTERNAL_TARGET. Skipping.${NC}" + echo -e " ${RED}Skipping: Target has no matching volume.${NC}" continue fi - # If it's a Docker volume, we need the real filesystem path (Mountpoint) + # Resolve Real Paths if [ "$TYPE" == "volume" ]; then - # L_PATH is already the volume name, we need the path REAL_L_PATH=$(docker volume inspect --format '{{.Mountpoint}}' "$L_PATH") - # Remote might be a volume too, get its path - R_VOL_NAME=$(basename "$R_PATH_RAW") # Assuming standard docker volume path structure or just name - # Safest way: inspect the remote volume name if it looks like a volume - REAL_R_PATH=$(ssh "$DEST_CONN" "docker volume inspect --format '{{.Mountpoint}}' $R_PATH_RAW 2>/dev/null || echo $R_PATH_RAW") + REAL_R_PATH=$($SSH_CMD "$DEST_CONN" "docker volume inspect --format '{{.Mountpoint}}' $R_PATH_RAW 2>/dev/null || echo $R_PATH_RAW") else REAL_L_PATH="$L_PATH" REAL_R_PATH="$R_PATH_RAW" fi - echo " Source (A): $REAL_L_PATH" - echo " Target (B): $REAL_R_PATH" - - # RSYNC - echo " šŸš€ Syncing..." - # Ensure trailing slashes for rsync to copy CONTENTS, not the folder itself - rsync -az --info=progress2 -e ssh "${REAL_L_PATH}/" "$DEST_CONN:${REAL_R_PATH}/" + # RSYNC EXECUTION + echo " šŸš€ Transferring data..." + rsync -az --info=progress2 -e "$RSYNC_SH" "${REAL_L_PATH}/" "$DEST_CONN:${REAL_R_PATH}/" done -# --- 5. RESTART --- -echo -e "\nāœ… Migration Complete." -echo -e " Starting Remote Container ($R_NAME)..." -ssh "$DEST_CONN" "docker start $R_ID" - -echo -e " Starting Local Container ($L_NAME)..." +# --- 6. RESTART --- +echo -e "\nāœ… Transfer Complete." +echo -e " Restarting containers..." +$SSH_CMD "$DEST_CONN" "docker start $R_ID" docker start "$L_ID" -echo -e "\nšŸŽ‰ ${GREEN}Done! Check your new server.${NC}" +echo -e "\nšŸŽ‰ ${GREEN}Success!${NC}"