265 lines
7.3 KiB
Bash
Executable file
265 lines
7.3 KiB
Bash
Executable file
#!/bin/bash
|
||
|
||
set -e
|
||
|
||
# Colors for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Configuration
|
||
ENVIRONMENT=${1:-production}
|
||
COMPOSE_FILE="docker-compose.production.yml"
|
||
BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)"
|
||
ROLLBACK_DIR="./rollback-backups"
|
||
MAX_BACKUPS=10
|
||
|
||
# Function to print colored messages
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}✅ $1${NC}"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}❌ $1${NC}"
|
||
}
|
||
|
||
# Function to check prerequisites
|
||
check_prerequisites() {
|
||
print_info "Checking prerequisites..."
|
||
|
||
command -v docker >/dev/null 2>&1 || {
|
||
print_error "Docker is required but not installed. Aborting.";
|
||
exit 1;
|
||
}
|
||
|
||
command -v docker-compose >/dev/null 2>&1 || {
|
||
print_error "Docker Compose is required but not installed. Aborting.";
|
||
exit 1;
|
||
}
|
||
|
||
if ! docker info >/dev/null 2>&1; then
|
||
print_error "Docker daemon is not running. Please start Docker and try again.";
|
||
exit 1;
|
||
fi
|
||
|
||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||
print_error "$COMPOSE_FILE not found. Aborting.";
|
||
exit 1;
|
||
fi
|
||
|
||
print_success "Prerequisites check passed"
|
||
}
|
||
|
||
# Function to create backup
|
||
create_backup() {
|
||
print_info "Creating backup..."
|
||
|
||
mkdir -p "${BACKUP_DIR}"
|
||
mkdir -p "${ROLLBACK_DIR}"
|
||
|
||
# Backup database
|
||
print_info "Backing up database..."
|
||
if docker-compose -f "$COMPOSE_FILE" ps postgres | grep -q "Up"; then
|
||
docker-compose -f "$COMPOSE_FILE" exec -T postgres pg_dump -U "${POSTGRES_USER:-veza_user}" "${POSTGRES_DB:-veza_db}" > "${BACKUP_DIR}/database.sql" 2>/dev/null || {
|
||
print_warning "Database backup failed (may not be critical if database is empty)"
|
||
}
|
||
else
|
||
print_warning "PostgreSQL container is not running, skipping database backup"
|
||
fi
|
||
|
||
# Backup Redis data
|
||
print_info "Backing up Redis data..."
|
||
if docker-compose -f "$COMPOSE_FILE" ps redis | grep -q "Up"; then
|
||
docker-compose -f "$COMPOSE_FILE" exec -T redis redis-cli --rdb - > "${BACKUP_DIR}/redis.rdb" 2>/dev/null || {
|
||
print_warning "Redis backup failed (may not be critical)"
|
||
}
|
||
else
|
||
print_warning "Redis container is not running, skipping Redis backup"
|
||
fi
|
||
|
||
# Save current image tags
|
||
print_info "Saving current image tags..."
|
||
docker-compose -f "$COMPOSE_FILE" config | grep "image:" > "${BACKUP_DIR}/image-tags.txt" || true
|
||
|
||
# Copy current docker-compose file
|
||
cp "$COMPOSE_FILE" "${BACKUP_DIR}/docker-compose.production.yml"
|
||
|
||
# Create rollback backup
|
||
cp -r "${BACKUP_DIR}" "${ROLLBACK_DIR}/latest"
|
||
|
||
print_success "Backup created at ${BACKUP_DIR}"
|
||
}
|
||
|
||
# Function to cleanup old backups
|
||
cleanup_old_backups() {
|
||
print_info "Cleaning up old backups (keeping last ${MAX_BACKUPS})..."
|
||
|
||
if [ -d "./backups" ]; then
|
||
cd ./backups
|
||
ls -t | tail -n +$((MAX_BACKUPS + 1)) | xargs rm -rf 2>/dev/null || true
|
||
cd ..
|
||
fi
|
||
|
||
print_success "Old backups cleaned up"
|
||
}
|
||
|
||
# Function to pull latest images
|
||
pull_latest_images() {
|
||
print_info "Pulling latest images..."
|
||
|
||
docker-compose -f "$COMPOSE_FILE" pull || {
|
||
print_error "Failed to pull images";
|
||
exit 1;
|
||
}
|
||
|
||
print_success "Images pulled successfully"
|
||
}
|
||
|
||
# Function to deploy services
|
||
deploy_services() {
|
||
print_info "Deploying services with zero-downtime strategy..."
|
||
|
||
# Deploy services one by one to minimize downtime
|
||
SERVICES=("backend-api" "chat-server" "stream-server" "frontend")
|
||
|
||
for service in "${SERVICES[@]}"; do
|
||
if docker-compose -f "$COMPOSE_FILE" ps "$service" | grep -q "Up"; then
|
||
print_info "Updating ${service}..."
|
||
docker-compose -f "$COMPOSE_FILE" up -d --no-deps "$service" || {
|
||
print_error "Failed to deploy ${service}";
|
||
return 1;
|
||
}
|
||
sleep 5
|
||
fi
|
||
done
|
||
|
||
print_success "Services deployed successfully"
|
||
}
|
||
|
||
# Function to check health
|
||
check_health() {
|
||
local service=$1
|
||
local url=$2
|
||
local name=$3
|
||
local max_attempts=${4:-30}
|
||
local attempt=0
|
||
|
||
print_info "Checking ${name} health..."
|
||
|
||
while [ $attempt -lt $max_attempts ]; do
|
||
if curl -sf "$url" >/dev/null 2>&1; then
|
||
print_success "${name} is healthy"
|
||
return 0
|
||
fi
|
||
attempt=$((attempt + 1))
|
||
sleep 2
|
||
done
|
||
|
||
print_error "${name} health check failed after ${max_attempts} attempts"
|
||
return 1
|
||
}
|
||
|
||
# Function to verify deployment
|
||
verify_deployment() {
|
||
print_info "Verifying deployment..."
|
||
|
||
local failed=0
|
||
|
||
# Check container status
|
||
if docker-compose -f "$COMPOSE_FILE" ps | grep -q "unhealthy\|Exited\|Restarting"; then
|
||
print_error "Some containers are unhealthy or stopped"
|
||
docker-compose -f "$COMPOSE_FILE" ps
|
||
failed=1
|
||
fi
|
||
|
||
# Check health endpoints
|
||
check_health "backend-api" "http://localhost:8080/health" "Backend API" 15 || failed=1
|
||
check_health "chat-server" "http://localhost:8081/health" "Chat Server" 15 || failed=1
|
||
check_health "stream-server" "http://localhost:8082/health" "Stream Server" 15 || failed=1
|
||
check_health "frontend" "http://localhost:80/health" "Frontend" 15 || failed=1
|
||
|
||
if [ $failed -eq 1 ]; then
|
||
return 1
|
||
fi
|
||
|
||
print_success "Deployment verification passed"
|
||
return 0
|
||
}
|
||
|
||
# Function to rollback
|
||
rollback() {
|
||
print_error "Deployment failed! Initiating rollback..."
|
||
|
||
if [ ! -d "${ROLLBACK_DIR}/latest" ]; then
|
||
print_error "No rollback backup found. Manual intervention required."
|
||
exit 1
|
||
fi
|
||
|
||
print_info "Rolling back to previous version..."
|
||
|
||
# Stop current services
|
||
docker-compose -f "$COMPOSE_FILE" down || true
|
||
|
||
# Restore docker-compose file
|
||
if [ -f "${ROLLBACK_DIR}/latest/docker-compose.production.yml" ]; then
|
||
cp "${ROLLBACK_DIR}/latest/docker-compose.production.yml" "$COMPOSE_FILE"
|
||
fi
|
||
|
||
# Restore database
|
||
if [ -f "${ROLLBACK_DIR}/latest/database.sql" ]; then
|
||
print_info "Restoring database..."
|
||
docker-compose -f "$COMPOSE_FILE" up -d postgres
|
||
sleep 10
|
||
docker-compose -f "$COMPOSE_FILE" exec -T postgres psql -U "${POSTGRES_USER:-veza_user}" -d "${POSTGRES_DB:-veza_db}" < "${ROLLBACK_DIR}/latest/database.sql" || true
|
||
fi
|
||
|
||
# Start services with previous images
|
||
docker-compose -f "$COMPOSE_FILE" up -d
|
||
|
||
print_warning "Rollback completed. Please verify services manually."
|
||
exit 1
|
||
}
|
||
|
||
# Main deployment flow
|
||
main() {
|
||
print_info "Starting production deployment to ${ENVIRONMENT}..."
|
||
|
||
# Trap errors and rollback
|
||
trap rollback ERR
|
||
|
||
check_prerequisites
|
||
create_backup
|
||
cleanup_old_backups
|
||
pull_latest_images
|
||
deploy_services
|
||
|
||
# Wait for services to start
|
||
print_info "Waiting for services to start..."
|
||
sleep 30
|
||
|
||
# Verify deployment
|
||
if ! verify_deployment; then
|
||
rollback
|
||
fi
|
||
|
||
# Disable trap on success
|
||
trap - ERR
|
||
|
||
print_success "Deployment to ${ENVIRONMENT} completed successfully!"
|
||
print_info "Backup location: ${BACKUP_DIR}"
|
||
print_info "To rollback, run: ${ROLLBACK_DIR}/latest"
|
||
}
|
||
|
||
# Run main function
|
||
main
|
||
|