#!/usr/bin/env bash set -euo pipefail COMPOSE=./docker-compose.yaml SERVICE=postgres DATA_DIR=./database ROLLBACK_TIMESTAMP=$(date +%Y%m%d_%H%M%S) echo "๐Ÿงช Validating docker-compose config..." docker compose -f "$COMPOSE" config > /dev/null || { echo "โŒ docker-compose config failed. Restore aborted." exit 1 } # Extract current Postgres image CURRENT_IMG=$(docker compose -f "$COMPOSE" config | grep "image:" | grep "$SERVICE" | awk '{print $2}' || true) if [[ -z "$CURRENT_IMG" ]]; then echo "โŒ Could not detect current image for service '$SERVICE'." exit 1 fi CURRENT_TAG=$(basename "$CURRENT_IMG") CURRENT_VERSION=$(echo "$CURRENT_TAG" | cut -d'-' -f1) # e.g., 17.5 # Detect appropriate backup folder BACKUP_CANDIDATES=($(ls -td ./database_backup_* 2>/dev/null || true)) if [[ ${#BACKUP_CANDIDATES[@]} -eq 0 ]]; then echo "โŒ No backup directory found. Cannot determine previous version." echo "โ„น๏ธ Available folders:" ls -1d ./database_backup_* || true exit 1 elif [[ ${#BACKUP_CANDIDATES[@]} -eq 1 ]]; then SELECTED_BACKUP="${BACKUP_CANDIDATES[0]}" echo "โ„น๏ธ Only one backup found. Using: ${SELECTED_BACKUP}" else SELECTED_BACKUP="${BACKUP_CANDIDATES[1]}" echo "โ„น๏ธ Multiple backups found. Using second latest: ${SELECTED_BACKUP}" fi # Extract version from selected backup folder OLD_TAG=$(basename "$SELECTED_BACKUP" | sed -E 's/database_backup_(([^_]+)-alpine).*/\1/') OLD_IMG="postgres:${OLD_TAG}" DELETED_UPGRADE_DIR=./database_upgraded_${CURRENT_VERSION}_${ROLLBACK_TIMESTAMP} echo "โช Initiating rollback from Postgres ${CURRENT_TAG} to ${OLD_IMG}..." # Step 1: Confirm backup exists if [ ! -d "$SELECTED_BACKUP" ]; then echo "โŒ Backup folder '${SELECTED_BACKUP}' not found. Aborting." exit 1 fi # Step 2: Stop services echo "๐Ÿ›‘ Stopping running services..." docker compose -f "$COMPOSE" down # Step 3: Archive current (possibly broken) database echo "๐Ÿ“ฆ Archiving current database directory as '${DELETED_UPGRADE_DIR}'..." mv "$DATA_DIR" "$DELETED_UPGRADE_DIR" # Step 4: Restore previous version echo "โ™ป๏ธ Restoring from backup folder '${SELECTED_BACKUP}'..." cp -a "$SELECTED_BACKUP" "$DATA_DIR" # Step 5: Restore image tag in docker-compose.yaml echo "๐Ÿ” Reverting docker-compose image tag to Postgres ${OLD_IMG}..." update_image_tag() { local svc="$1" local file="$2" local target_tag="$3" echo "๐Ÿ” Reverting docker-compose image tag for service '$svc' to Postgres: ${target_tag}..." # Use awk to scope updates within the service definition only awk -v service="$svc" -v new_tag="$target_tag" ' BEGIN { in_service = 0 } /^[ ]{2}[a-zA-Z0-9_-]+:/ { in_service = ($1 == service ":") ? 1 : 0 } in_service && /^\s*image:/ { sub(/postgres:[^"'"'"']+/, "postgres:" new_tag) } { print } ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" } update_image_tag "$SERVICE" "$COMPOSE" "$OLD_TAG" # Step 6: Restart Postgres echo "๐Ÿš€ Starting Postgres service with restored image..." docker compose -f "$COMPOSE" up -d "$SERVICE" # Step 7: Final messages echo "โœ… Rollback complete!" echo "๐Ÿ—ƒ๏ธ PostgreSQL downgraded to '${OLD_IMG}' and data restored from '${SELECTED_BACKUP}'." echo "๐Ÿ“ฆ The faulty upgrade has been archived in '${DELETED_UPGRADE_DIR}'." echo " - To clean: rm -rf ${DELETED_UPGRADE_DIR}" echo " - To verify: docker compose logs -f $SERVICE" # Step 8: Restart full application echo "๐Ÿ”„ Pulling latest images..." if ! docker compose pull; then echo "โŒ Failed to pull images. Aborting." exit 1 fi echo "๐Ÿ”„ Starting full application stack..." if ! docker compose up -d --force-recreate; then echo "โŒ Failed to start application stack. Please check logs." exit 1 fi echo "โœ… Deployment completed successfully."