#!/usr/bin/env bash # Move a Docker Compose project and its named volumes to another host. # Source host: this machine # Destination host: 192.168.8.80 # Project directory on both hosts: /home/tas_councils set -euo pipefail # ------------------------- CONFIG ------------------------- DEST_HOST="192.168.8.80" DEST_USER="${USER}" # change if the username differs on the new server PROJECT_DIR="/home/tas_councils" COMPOSE_FILE="${PROJECT_DIR}/docker-compose.yml" SSH_OPTS="-o StrictHostKeyChecking=accept-new -o ConnectTimeout=5" # ---------------------------------------------------------- say() { printf "\n%s\n" "$*"; } need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1"; exit 1; } } # Prefer 'docker compose', fall back to 'docker-compose' dc() { if docker compose version >/dev/null 2>&1; then docker compose "$@" else docker-compose "$@" fi } # Remote 'docker compose' wrapper remote_dc() { local cmd="$*" ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "bash -lc ' if docker compose version >/dev/null 2>&1; then dcc=\"docker compose\"; else dcc=\"docker-compose\"; fi cd \"${PROJECT_DIR}\" && \$dcc ${cmd} '" } # ----------------------- PRECHECKS ------------------------ need docker need rsync need ssh [[ -d "${PROJECT_DIR}" ]] || { echo "Project dir not found: ${PROJECT_DIR}"; exit 1; } [[ -f "${COMPOSE_FILE}" ]] || { echo "Compose file not found: ${COMPOSE_FILE}"; exit 1; } say "Checking SSH access to ${DEST_USER}@${DEST_HOST}..." ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" 'echo "OK from $(hostname)"' >/dev/null say "Checking Docker on destination..." ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" 'docker version >/dev/null 2>&1' \ || { echo "Docker not available on destination"; exit 1; } say "Creating project directory on destination..." ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "mkdir -p '${PROJECT_DIR}'" # Determine project name PROJECT_NAME="${PROJECT_NAME:-$(basename "${PROJECT_DIR}")}" if [[ -f "${PROJECT_DIR}/.env" ]]; then # If .env sets COMPOSE_PROJECT_NAME, use it ENV_NAME="$(grep -E '^\s*COMPOSE_PROJECT_NAME\s*=' "${PROJECT_DIR}/.env" | tail -n1 | cut -d= -f2- | tr -d '[:space:]' || true)" if [[ -n "${ENV_NAME}" ]]; then PROJECT_NAME="${ENV_NAME}" fi fi say "Project name detected: ${PROJECT_NAME}" # --------------------- STOP SOURCE STACK ------------------ say "Stopping stack on source host..." dc -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" down # -------------------- SYNC PROJECT DIR -------------------- say "Syncing project directory to destination..." rsync -a --delete -e "ssh ${SSH_OPTS}" "${PROJECT_DIR}/" "${DEST_USER}@${DEST_HOST}:${PROJECT_DIR}/" # -------------------- COPY NAMED VOLUMES ------------------ say "Finding named volumes for the project..." mapfile -t VOLUMES < <(docker volume ls --format '{{.Name}}' | grep -E "^${PROJECT_NAME}_" || true) if [[ ${#VOLUMES[@]} -eq 0 ]]; then say "No named volumes found with prefix ${PROJECT_NAME}_" else say "Will copy ${#VOLUMES[@]} volume(s):" printf ' - %s\n' "${VOLUMES[@]}" for VOL in "${VOLUMES[@]}"; do say "Copying volume: ${VOL}" # Create volume on destination if it does not exist ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "docker volume inspect '${VOL}' >/dev/null 2>&1 || docker volume create '${VOL}' >/dev/null" # Stream tar from source volume into destination volume docker run --rm -v "${VOL}:/src:ro" busybox sh -c "cd /src && tar -cpf - ." \ | ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "docker run --rm -i -v '${VOL}:/dest' busybox sh -c 'cd /dest && tar -xpf -'" done fi # ------------------ START DESTINATION STACK ---------------- say "Starting stack on destination host..." # Pull images first. If the destination has no Internet, comment out the pull line. ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "bash -lc ' cd \"${PROJECT_DIR}\" if docker compose version >/dev/null 2>&1; then dcc=\"docker compose\"; else dcc=\"docker-compose\"; fi \$dcc pull || true \$dcc up -d '" say "Destination stack status:" ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "bash -lc ' cd \"${PROJECT_DIR}\" if docker compose version >/dev/null 2>&1; then dcc=\"docker compose\"; else dcc=\"docker-compose\"; fi \$dcc ps '" say "Done. Your Compose project and its named volumes have been moved to ${DEST_HOST}." say "If you used bind mounts outside ${PROJECT_DIR}, review those paths and copy them as needed."