diff --git a/migrate.sh b/migrate.sh index 93a9473..215fc05 100644 --- a/migrate.sh +++ b/migrate.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Usage: curl -sL https://url/migrate.sh | bash -s -- [SSH_KEY_PATH] +# Usage: curl -sL https://url/migrate.sh | bash -s -- [SSH_KEY_PATH] LOCAL_SEARCH="$1" DEST_CONN="$2" @@ -11,102 +11,96 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' -# --- 1. CONFIGURE SSH --- -# Base options to avoid "Are you sure?" prompts and timeouts +# --- 1. SSH SETUP --- 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 [Key_Path]${NC}" - exit 1 +# --- 2. DETECT MODE (Container vs Volume) --- +MODE="container" +# Check if input is a valid volume +if docker volume inspect "$LOCAL_SEARCH" >/dev/null 2>&1; then + MODE="volume" + echo -e "${YELLOW}Info: Detected input as Volume Name, not Container.${NC}" fi -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 +# --- 3. EXECUTE MIGRATION --- -# --- 3. IDENTIFY CONTAINERS --- -echo -e "\nšŸ” Identifying Containers..." +if [ "$MODE" == "volume" ]; then + # === VOLUME MODE === + L_VOL="$LOCAL_SEARCH" + R_VOL="$REMOTE_SEARCH" + + echo -e " Source Volume: ${GREEN}$L_VOL${NC}" + echo -e " Target Volume: ${GREEN}$R_VOL${NC}" -# 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_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" ]; 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)" - -# --- 4. STOP CONTAINERS --- -echo -e "\nšŸ›‘ ${RED}Stopping containers to freeze state...${NC}" -docker stop "$L_ID" -$SSH_CMD "$DEST_CONN" "docker stop $R_ID" - -# --- 5. MIGRATE VOLUMES --- -echo -e "\nšŸ“¦ Mapping & Syncing Volumes..." - -# Get Mounts: Type|SourcePath|InternalPath -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 system mounts - if [[ "$INTERNAL_TARGET" == *".sock" ]] || [[ "$INTERNAL_TARGET" == *".conf" ]]; then continue; fi - - echo -e "\n šŸ”„ Volume: ${YELLOW}$INTERNAL_TARGET${NC}" - - # 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}Skipping: Target has no matching volume.${NC}" - continue + # Get Paths + REAL_L_PATH=$(docker volume inspect --format '{{.Mountpoint}}' "$L_VOL") + REAL_R_PATH=$($SSH_CMD "$DEST_CONN" "docker volume inspect --format '{{.Mountpoint}}' $R_VOL") + + if [ -z "$REAL_R_PATH" ]; then + echo -e "${RED}Error: Remote volume '$R_VOL' not found on Server B.${NC}" + exit 1 fi - # Resolve Real Paths - if [ "$TYPE" == "volume" ]; then - REAL_L_PATH=$(docker volume inspect --format '{{.Mountpoint}}' "$L_PATH") - 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 + # Attempt to stop any containers using these volumes (Safety) + echo -e "\nšŸ›‘ Stopping any containers using these volumes..." + # Local + L_CONTAINER=$(docker ps -q --filter volume="$L_VOL") + [ -n "$L_CONTAINER" ] && docker stop $L_CONTAINER + # Remote + R_CONTAINER=$($SSH_CMD "$DEST_CONN" "docker ps -q --filter volume='$R_VOL'") + [ -n "$R_CONTAINER" ] && $SSH_CMD "$DEST_CONN" "docker stop $R_CONTAINER" - # RSYNC EXECUTION - echo " šŸš€ Transferring data..." + # Sync + echo -e "šŸš€ Transferring data..." rsync -az --info=progress2 -e "$RSYNC_SH" "${REAL_L_PATH}/" "$DEST_CONN:${REAL_R_PATH}/" -done + + echo -e "\nāœ… Volume Transfer Complete!" + [ -n "$L_CONTAINER" ] && docker start $L_CONTAINER + [ -n "$R_CONTAINER" ] && $SSH_CMD "$DEST_CONN" "docker start $R_CONTAINER" -# --- 6. RESTART --- -echo -e "\nāœ… Transfer Complete." -echo -e " Restarting containers..." -$SSH_CMD "$DEST_CONN" "docker start $R_ID" -docker start "$L_ID" +else + # === CONTAINER MODE (Original Logic) === + L_ID=$(docker ps -aq --filter "name=${LOCAL_SEARCH}" | head -n 1) + R_ID=$($SSH_CMD "$DEST_CONN" "docker ps -aq --filter 'name=${REMOTE_SEARCH}' | head -n 1") + + if [ -z "$L_ID" ]; then echo -e "${RED}Error: Container/Volume '$LOCAL_SEARCH' not found.${NC}"; exit 1; fi + if [ -z "$R_ID" ]; then echo -e "${RED}Error: Remote container '$REMOTE_SEARCH' not found.${NC}"; exit 1; fi -echo -e "\nšŸŽ‰ ${GREEN}Success!${NC}" + echo -e "šŸ›‘ Stopping containers..." + docker stop "$L_ID" + $SSH_CMD "$DEST_CONN" "docker stop $R_ID" + + echo -e "šŸ“¦ Syncing volumes..." + 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) + TARGET=$(echo "$MOUNT" | cut -d'|' -f3) + + if [[ "$TARGET" == *".sock" ]] || [[ "$TARGET" == *".conf" ]]; then continue; fi + + R_PATH_RAW=$($SSH_CMD "$DEST_CONN" "docker inspect -f '{{range .Mounts}}{{if eq .Destination \"$TARGET\"}}{{.Source}}{{end}}{{end}}' $R_ID") + + if [ -n "$R_PATH_RAW" ]; then + if [ "$TYPE" == "volume" ]; then + REAL_L=$(docker volume inspect --format '{{.Mountpoint}}' "$L_PATH") + REAL_R=$($SSH_CMD "$DEST_CONN" "docker volume inspect --format '{{.Mountpoint}}' $R_PATH_RAW" 2>/dev/null || echo $R_PATH_RAW) + else + REAL_L="$L_PATH"; REAL_R="$R_PATH_RAW" + fi + rsync -az --info=progress2 -e "$RSYNC_SH" "${REAL_L}/" "$DEST_CONN:${REAL_R}/" + fi + done + + echo -e "āœ… Container Transfer Complete!" + docker start "$L_ID" + $SSH_CMD "$DEST_CONN" "docker start $R_ID" +fi