#!/bin/bash set -euo pipefail # ShieldAI Docker Compose Rollback Script # Usage: ./rollback-compose.sh [--env prod|dev] # # Rolls back all services to a previous tagged image using docker-compose.prod.yml # # Examples: # ./rollback-compose.sh v1.2.3 # Rollback to v1.2.3 # ./rollback-compose.sh v1.2.3 --env prod # Explicit production compose PREVIOUS_TAG="${1:-}" ENV_MODE="${2:-prod}" # ─── Configuration ─────────────────────────────────────────────── SERVICES="api darkwatch spamshield voiceprint" COMPOSE_FILE="docker-compose.prod.yml" REGISTRY_OWNER="${GITHUB_REPOSITORY_OWNER:-shieldai}" # ─── Helpers ───────────────────────────────────────────────────── log() { local level="$1" shift echo "[$(date -u '+%H:%M:%S')] [$level] $*" } log_info() { log "INFO" "$@"; } log_warn() { log "WARN" "$@"; } log_error() { log "ERROR" "$@"; } # ─── Validation ────────────────────────────────────────────────── if [[ -z "$PREVIOUS_TAG" ]]; then log_error "Usage: $0 [--env prod|dev]" log_error "Example: $0 v1.2.3" exit 1 fi if ! command -v docker &>/dev/null; then log_error "Docker not found in PATH" exit 1 fi # ─── Rollback Logic ────────────────────────────────────────────── main() { log_info "=== Docker Compose Rollback ===" log_info "Target tag: $PREVIOUS_TAG" log_info "Compose file: $COMPOSE_FILE" log_info "Registry: ghcr.io/$REGISTRY_OWNER" # 1. Pull previous images log_info "Pulling previous images..." local pull_failed=0 for svc in $SERVICES; do local image="ghcr.io/${REGISTRY_OWNER}/shieldai-${svc}:${PREVIOUS_TAG}" log_info "Pulling $image..." if docker pull "$image" 2>/dev/null; then log_info "Pulled: $image" else log_warn "Pull failed: $image (may not exist)" pull_failed=1 fi done if [[ $pull_failed -eq 1 ]]; then log_warn "Some images may not exist at tag $PREVIOUS_TAG" log_info "Continuing with available images..." fi # 2. Stop current services gracefully log_info "Stopping current services..." DOCKER_TAG="$PREVIOUS_TAG" docker compose -f "$COMPOSE_FILE" down --timeout 30 2>/dev/null || true # 3. Start with previous tag log_info "Starting services with tag $PREVIOUS_TAG..." DOCKER_TAG="$PREVIOUS_TAG" docker compose -f "$COMPOSE_FILE" up -d # 4. Wait for services to be healthy log_info "Waiting for services to become healthy..." sleep 10 # 5. Verify health local passed=0 local failed=0 for svc in $SERVICES; do local port port=$(case "$svc" in api) echo 3000 ;; darkwatch) echo 3001 ;; spamshield) echo 3002 ;; voiceprint) echo 3003 ;; esac) local http_code http_code=$(curl -s -o /dev/null -w "%{http_code}" \ --connect-timeout 10 --max-time 30 \ "http://localhost:${port}/health" 2>/dev/null || echo "000") if [[ "$http_code" == "200" ]]; then log_info "Health OK: $svc (port $port, HTTP $http_code)" ((passed++)) else log_warn "Health FAIL: $svc (port $port, HTTP $http_code)" ((failed++)) fi done log_info "=== Rollback Complete ===" log_info "Passed: $passed, Failed: $failed" if [[ $failed -gt 0 ]]; then log_warn "Some services failed health check. Check logs: docker compose -f $COMPOSE_FILE logs" exit 1 fi log_info "All services healthy after rollback" exit 0 } main "$@"