migrate-compose.sh 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. #!/usr/bin/env bash
  2. # Move a Docker Compose project and its named volumes to another host.
  3. # Source host: this machine
  4. # Destination host: 192.168.8.80
  5. # Project directory on both hosts: /home/tas_councils
  6. set -euo pipefail
  7. # ------------------------- CONFIG -------------------------
  8. DEST_HOST="192.168.8.80"
  9. DEST_USER="${USER}" # change if the username differs on the new server
  10. PROJECT_DIR="/home/tas_councils"
  11. COMPOSE_FILE="${PROJECT_DIR}/docker-compose.yml"
  12. SSH_OPTS="-o StrictHostKeyChecking=accept-new -o ConnectTimeout=5"
  13. # ----------------------------------------------------------
  14. say() { printf "\n%s\n" "$*"; }
  15. need() {
  16. command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1"; exit 1; }
  17. }
  18. # Prefer 'docker compose', fall back to 'docker-compose'
  19. dc() {
  20. if docker compose version >/dev/null 2>&1; then
  21. docker compose "$@"
  22. else
  23. docker-compose "$@"
  24. fi
  25. }
  26. # Remote 'docker compose' wrapper
  27. remote_dc() {
  28. local cmd="$*"
  29. ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "bash -lc '
  30. if docker compose version >/dev/null 2>&1; then dcc=\"docker compose\"; else dcc=\"docker-compose\"; fi
  31. cd \"${PROJECT_DIR}\" && \$dcc ${cmd}
  32. '"
  33. }
  34. # ----------------------- PRECHECKS ------------------------
  35. need docker
  36. need rsync
  37. need ssh
  38. [[ -d "${PROJECT_DIR}" ]] || { echo "Project dir not found: ${PROJECT_DIR}"; exit 1; }
  39. [[ -f "${COMPOSE_FILE}" ]] || { echo "Compose file not found: ${COMPOSE_FILE}"; exit 1; }
  40. say "Checking SSH access to ${DEST_USER}@${DEST_HOST}..."
  41. ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" 'echo "OK from $(hostname)"' >/dev/null
  42. say "Checking Docker on destination..."
  43. ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" 'docker version >/dev/null 2>&1' \
  44. || { echo "Docker not available on destination"; exit 1; }
  45. say "Creating project directory on destination..."
  46. ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "mkdir -p '${PROJECT_DIR}'"
  47. # Determine project name
  48. PROJECT_NAME="${PROJECT_NAME:-$(basename "${PROJECT_DIR}")}"
  49. if [[ -f "${PROJECT_DIR}/.env" ]]; then
  50. # If .env sets COMPOSE_PROJECT_NAME, use it
  51. ENV_NAME="$(grep -E '^\s*COMPOSE_PROJECT_NAME\s*=' "${PROJECT_DIR}/.env" | tail -n1 | cut -d= -f2- | tr -d '[:space:]' || true)"
  52. if [[ -n "${ENV_NAME}" ]]; then
  53. PROJECT_NAME="${ENV_NAME}"
  54. fi
  55. fi
  56. say "Project name detected: ${PROJECT_NAME}"
  57. # --------------------- STOP SOURCE STACK ------------------
  58. say "Stopping stack on source host..."
  59. dc -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" down
  60. # -------------------- SYNC PROJECT DIR --------------------
  61. say "Syncing project directory to destination..."
  62. rsync -a --delete -e "ssh ${SSH_OPTS}" "${PROJECT_DIR}/" "${DEST_USER}@${DEST_HOST}:${PROJECT_DIR}/"
  63. # -------------------- COPY NAMED VOLUMES ------------------
  64. say "Finding named volumes for the project..."
  65. mapfile -t VOLUMES < <(docker volume ls --format '{{.Name}}' | grep -E "^${PROJECT_NAME}_" || true)
  66. if [[ ${#VOLUMES[@]} -eq 0 ]]; then
  67. say "No named volumes found with prefix ${PROJECT_NAME}_"
  68. else
  69. say "Will copy ${#VOLUMES[@]} volume(s):"
  70. printf ' - %s\n' "${VOLUMES[@]}"
  71. for VOL in "${VOLUMES[@]}"; do
  72. say "Copying volume: ${VOL}"
  73. # Create volume on destination if it does not exist
  74. ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "docker volume inspect '${VOL}' >/dev/null 2>&1 || docker volume create '${VOL}' >/dev/null"
  75. # Stream tar from source volume into destination volume
  76. docker run --rm -v "${VOL}:/src:ro" busybox sh -c "cd /src && tar -cpf - ." \
  77. | ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "docker run --rm -i -v '${VOL}:/dest' busybox sh -c 'cd /dest && tar -xpf -'"
  78. done
  79. fi
  80. # ------------------ START DESTINATION STACK ----------------
  81. say "Starting stack on destination host..."
  82. # Pull images first. If the destination has no Internet, comment out the pull line.
  83. ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "bash -lc '
  84. cd \"${PROJECT_DIR}\"
  85. if docker compose version >/dev/null 2>&1; then dcc=\"docker compose\"; else dcc=\"docker-compose\"; fi
  86. \$dcc pull || true
  87. \$dcc up -d
  88. '"
  89. say "Destination stack status:"
  90. ssh ${SSH_OPTS} "${DEST_USER}@${DEST_HOST}" "bash -lc '
  91. cd \"${PROJECT_DIR}\"
  92. if docker compose version >/dev/null 2>&1; then dcc=\"docker compose\"; else dcc=\"docker-compose\"; fi
  93. \$dcc ps
  94. '"
  95. say "Done. Your Compose project and its named volumes have been moved to ${DEST_HOST}."
  96. say "If you used bind mounts outside ${PROJECT_DIR}, review those paths and copy them as needed."