diff --git a/migrate.sh b/migrate.sh new file mode 100644 index 0000000..69473de --- /dev/null +++ b/migrate.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# Usage: curl -sL https://your-cdn/migrate.sh | bash -s -- + +LOCAL_SEARCH="$1" +DEST_CONN="$2" +REMOTE_SEARCH="$3" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# --- 1. PRE-FLIGHT & SSH CHECK --- +if [ -z "$REMOTE_SEARCH" ]; then + echo -e "${RED}Usage: $0 ${NC}" + echo -e "Example: $0 postgres-old root@10.0.0.5 postgres-new" + 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}" + exit 1 +fi + +# --- 2. IDENTIFY CONTAINERS --- +echo -e "\nšŸ” Finding 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") + +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 + +echo -e " Source: ${GREEN}$L_NAME${NC} ($L_ID)" +echo -e " Target: ${GREEN}$R_NAME${NC} ($R_ID) on $DEST_CONN" + +# --- 3. STOP CONTAINERS (SAFE MODE) --- +echo -e "\nšŸ›‘ ${RED}Stopping BOTH containers to ensure safe data transfer...${NC}" +docker stop "$L_ID" +ssh "$DEST_CONN" "docker stop $R_ID" + +# --- 4. MAP & SYNC VOLUMES --- +echo -e "\nšŸ“¦ Analyzing Volume Maps..." + +# Get list of internal mount targets from Source (Where data lives inside the container) +# Format: Type|SourcePath|DestinationPath +L_MOUNTS=$(docker inspect -f '{{range .Mounts}}{{.Type}}|{{.Source}}|{{.Destination}} {{end}}' "$L_ID") + +for MOUNT in $L_MOUNTS; do + TYPE=$(echo "$MOUNT" | cut -d'|' -f1) + 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 + + echo -e "\n šŸ”„ Processing mount at ${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") + + if [ -z "$R_PATH_RAW" ]; then + echo -e " ${RED}Warning: Target container has no matching volume for $INTERNAL_TARGET. Skipping.${NC}" + continue + fi + + # If it's a Docker volume, we need the real filesystem path (Mountpoint) + 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") + 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}/" +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)..." +docker start "$L_ID" + +echo -e "\nšŸŽ‰ ${GREEN}Done! Check your new server.${NC}"