Update migrate.sh

This commit is contained in:
2025-11-21 03:50:09 +00:00
parent a8177a3958
commit 02a9007411

View File

@@ -1,58 +1,74 @@
#!/bin/bash #!/bin/bash
# Usage: curl -sL https://your-cdn/migrate.sh | bash -s -- <LOCAL_ID> <USER@REMOTE_IP> <REMOTE_ID> # Usage: curl -sL https://url/migrate.sh | bash -s -- <LOCAL_ID> <USER@REMOTE_IP> <REMOTE_ID> [SSH_KEY_PATH]
LOCAL_SEARCH="$1" LOCAL_SEARCH="$1"
DEST_CONN="$2" DEST_CONN="$2"
REMOTE_SEARCH="$3" REMOTE_SEARCH="$3"
SSH_KEY_PATH="$4"
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
NC='\033[0m' 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 if [ -z "$REMOTE_SEARCH" ]; then
echo -e "${RED}Usage: $0 <Local_Container_Name> <User@Remote_IP> <Remote_Container_Name>${NC}" echo -e "${RED}Usage: $0 <Local_ID> <User@Remote_IP> <Remote_ID> [Key_Path]${NC}"
echo -e "Example: $0 postgres-old root@10.0.0.5 postgres-new"
exit 1 exit 1
fi fi
echo -e "⚠️ ${YELLOW}REMINDER:${NC} Ensure SSH keys are set up: ${GREEN}ssh-copy-id $DEST_CONN${NC}" echo -e "📡 Connecting to $DEST_CONN..."
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$DEST_CONN" echo "SSH OK" >/dev/null 2>&1; then if ! $SSH_CMD "$DEST_CONN" echo "SSH Connection OK" >/dev/null 2>&1; then
echo -e "${RED}Error: SSH connection to $DEST_CONN failed.${NC}" echo -e "${RED}Error: Cannot connect to $DEST_CONN.${NC}"
echo -e " Check your IP, User, or SSH Key permissions."
exit 1 exit 1
fi fi
# --- 2. IDENTIFY CONTAINERS --- # --- 3. IDENTIFY CONTAINERS ---
echo -e "\n🔍 Finding containers..." echo -e "\n🔍 Identifying Containers..."
# Local # Local
L_ID=$(docker ps -aq --filter "name=${LOCAL_SEARCH}" | head -n 1) 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) L_NAME=$(docker ps -a --filter "name=${LOCAL_SEARCH}" --format "{{.Names}}" | head -n 1)
# Remote (via SSH) # Remote (via SSH)
R_ID=$(ssh "$DEST_CONN" "docker ps -aq --filter 'name=${REMOTE_SEARCH}' | head -n 1") R_ID=$($SSH_CMD "$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_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 if [ -z "$L_ID" ]; then echo -e "${RED}Local container '${LOCAL_SEARCH}' not found.${NC}"; exit 1; fi
echo -e "${RED}Error: Could not find containers.${NC}" if [ -z "$R_ID" ]; then echo -e "${RED}Remote container '${REMOTE_SEARCH}' not found on Server B.${NC}"; exit 1; fi
echo "Local Found: ${L_ID:-None} | Remote Found: ${R_ID:-None}"
exit 1
fi
echo -e " Source: ${GREEN}$L_NAME${NC} ($L_ID)" 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) --- # --- 4. STOP CONTAINERS ---
echo -e "\n🛑 ${RED}Stopping BOTH containers to ensure safe data transfer...${NC}" echo -e "\n🛑 ${RED}Stopping containers to freeze state...${NC}"
docker stop "$L_ID" docker stop "$L_ID"
ssh "$DEST_CONN" "docker stop $R_ID" $SSH_CMD "$DEST_CONN" "docker stop $R_ID"
# --- 4. MAP & SYNC VOLUMES --- # --- 5. MIGRATE VOLUMES ---
echo -e "\n📦 Analyzing Volume Maps..." echo -e "\n📦 Mapping & Syncing Volumes..."
# Get list of internal mount targets from Source (Where data lives inside the container) # Get Mounts: Type|SourcePath|InternalPath
# Format: Type|SourcePath|DestinationPath
L_MOUNTS=$(docker inspect -f '{{range .Mounts}}{{.Type}}|{{.Source}}|{{.Destination}} {{end}}' "$L_ID") L_MOUNTS=$(docker inspect -f '{{range .Mounts}}{{.Type}}|{{.Source}}|{{.Destination}} {{end}}' "$L_ID")
for MOUNT in $L_MOUNTS; do for MOUNT in $L_MOUNTS; do
@@ -60,51 +76,37 @@ for MOUNT in $L_MOUNTS; do
L_PATH=$(echo "$MOUNT" | cut -d'|' -f2) L_PATH=$(echo "$MOUNT" | cut -d'|' -f2)
INTERNAL_TARGET=$(echo "$MOUNT" | cut -d'|' -f3) INTERNAL_TARGET=$(echo "$MOUNT" | cut -d'|' -f3)
# skip bind mounts that are likely system files (docker.sock, etc) # Skip system mounts
if [[ "$INTERNAL_TARGET" == *".sock" ]] || [[ "$INTERNAL_TARGET" == *".conf" ]]; then if [[ "$INTERNAL_TARGET" == *".sock" ]] || [[ "$INTERNAL_TARGET" == *".conf" ]]; then continue; fi
echo " Skipping config/system file: $INTERNAL_TARGET"
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 # Find corresponding path on Remote
# We look for the mount that shares the same INTERNAL destination R_PATH_RAW=$($SSH_CMD "$DEST_CONN" "docker inspect -f '{{range .Mounts}}{{if eq .Destination \"$INTERNAL_TARGET\"}}{{.Source}}{{end}}{{end}}' $R_ID")
R_PATH_RAW=$(ssh "$DEST_CONN" "docker inspect -f '{{range .Mounts}}{{if eq .Destination \"$INTERNAL_TARGET\"}}{{.Source}}{{end}}{{end}}' $R_ID")
if [ -z "$R_PATH_RAW" ]; then 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 continue
fi fi
# If it's a Docker volume, we need the real filesystem path (Mountpoint) # Resolve Real Paths
if [ "$TYPE" == "volume" ]; then 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") REAL_L_PATH=$(docker volume inspect --format '{{.Mountpoint}}' "$L_PATH")
# Remote might be a volume too, get its path REAL_R_PATH=$($SSH_CMD "$DEST_CONN" "docker volume inspect --format '{{.Mountpoint}}' $R_PATH_RAW 2>/dev/null || echo $R_PATH_RAW")
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")
else else
REAL_L_PATH="$L_PATH" REAL_L_PATH="$L_PATH"
REAL_R_PATH="$R_PATH_RAW" REAL_R_PATH="$R_PATH_RAW"
fi fi
echo " Source (A): $REAL_L_PATH" # RSYNC EXECUTION
echo " Target (B): $REAL_R_PATH" echo " 🚀 Transferring data..."
rsync -az --info=progress2 -e "$RSYNC_SH" "${REAL_L_PATH}/" "$DEST_CONN:${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}/"
done done
# --- 5. RESTART --- # --- 6. RESTART ---
echo -e "\n✅ Migration Complete." echo -e "\n✅ Transfer Complete."
echo -e " Starting Remote Container ($R_NAME)..." echo -e " Restarting containers..."
ssh "$DEST_CONN" "docker start $R_ID" $SSH_CMD "$DEST_CONN" "docker start $R_ID"
echo -e " Starting Local Container ($L_NAME)..."
docker start "$L_ID" docker start "$L_ID"
echo -e "\n🎉 ${GREEN}Done! Check your new server.${NC}" echo -e "\n🎉 ${GREEN}Success!${NC}"