369 lines
11 KiB
Bash
369 lines
11 KiB
Bash
#!/bin/bash
|
|
|
|
# Deploy script for Release environment with multi-architecture support
|
|
# Usage: ./deploy-release.sh <IMAGE_TAG>
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
readonly DEPLOY_DIR="/opt/bcards-staging"
|
|
readonly DOCKER_COMPOSE_FILE="docker-compose.staging.yml"
|
|
readonly CONTAINER_NAME="bcards-staging"
|
|
readonly HEALTH_CHECK_URL="http://localhost:8090/health"
|
|
readonly MAX_HEALTH_CHECK_ATTEMPTS=10
|
|
readonly HEALTH_CHECK_INTERVAL=10
|
|
readonly ROLLBACK_TIMEOUT=300
|
|
|
|
# Colors for output
|
|
readonly RED='\033[0;31m'
|
|
readonly GREEN='\033[0;32m'
|
|
readonly YELLOW='\033[1;33m'
|
|
readonly BLUE='\033[0;34m'
|
|
readonly NC='\033[0m' # No Color
|
|
|
|
# Logging functions
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
# Cleanup function
|
|
cleanup() {
|
|
local exit_code=$?
|
|
if [ $exit_code -ne 0 ]; then
|
|
log_error "Deployment failed with exit code $exit_code"
|
|
rollback_deployment
|
|
fi
|
|
exit $exit_code
|
|
}
|
|
|
|
# Set trap for cleanup on exit
|
|
trap cleanup EXIT
|
|
|
|
# Validate input parameters
|
|
validate_input() {
|
|
if [ $# -ne 1 ]; then
|
|
log_error "Usage: $0 <IMAGE_TAG>"
|
|
exit 1
|
|
fi
|
|
|
|
local image_tag="$1"
|
|
if [[ ! "$image_tag" =~ ^[a-zA-Z0-9._-]+$ ]]; then
|
|
log_error "Invalid image tag format: $image_tag"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Check prerequisites
|
|
check_prerequisites() {
|
|
log_info "Checking prerequisites..."
|
|
|
|
# Check if Docker is running
|
|
if ! docker info >/dev/null 2>&1; then
|
|
log_error "Docker is not running or not accessible"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if docker-compose is available
|
|
if ! command -v docker-compose >/dev/null 2>&1; then
|
|
log_error "docker-compose is not installed"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if deployment directory exists
|
|
if [ ! -d "$DEPLOY_DIR" ]; then
|
|
log_info "Creating deployment directory: $DEPLOY_DIR"
|
|
mkdir -p "$DEPLOY_DIR"
|
|
fi
|
|
|
|
log_success "Prerequisites check passed"
|
|
}
|
|
|
|
# Backup current deployment
|
|
backup_current_deployment() {
|
|
log_info "Backing up current deployment..."
|
|
|
|
local timestamp=$(date +%Y%m%d_%H%M%S)
|
|
local backup_dir="$DEPLOY_DIR/backups/$timestamp"
|
|
|
|
mkdir -p "$backup_dir"
|
|
|
|
# Backup environment file if exists
|
|
if [ -f "$DEPLOY_DIR/.env" ]; then
|
|
cp "$DEPLOY_DIR/.env" "$backup_dir/.env.backup"
|
|
cp "$DEPLOY_DIR/.env" "$DEPLOY_DIR/.env.backup"
|
|
log_info "Environment file backed up"
|
|
fi
|
|
|
|
# Backup docker-compose file if exists
|
|
if [ -f "$DEPLOY_DIR/$DOCKER_COMPOSE_FILE" ]; then
|
|
cp "$DEPLOY_DIR/$DOCKER_COMPOSE_FILE" "$backup_dir/${DOCKER_COMPOSE_FILE}.backup"
|
|
log_info "Docker compose file backed up"
|
|
fi
|
|
|
|
# Get current container image for potential rollback
|
|
if docker ps --format "table {{.Names}}\t{{.Image}}" | grep -q "$CONTAINER_NAME"; then
|
|
local current_image=$(docker inspect --format='{{.Config.Image}}' "$CONTAINER_NAME" 2>/dev/null || echo "")
|
|
if [ -n "$current_image" ]; then
|
|
echo "$current_image" > "$DEPLOY_DIR/.previous_image"
|
|
log_info "Current image backed up: $current_image"
|
|
fi
|
|
fi
|
|
|
|
log_success "Backup completed: $backup_dir"
|
|
}
|
|
|
|
# Test MongoDB connectivity
|
|
test_mongodb_connection() {
|
|
log_info "Testing MongoDB connectivity..."
|
|
|
|
local mongodb_host="192.168.0.100"
|
|
local mongodb_port="27017"
|
|
|
|
# Test basic connectivity
|
|
if timeout 10 bash -c "</dev/tcp/$mongodb_host/$mongodb_port" 2>/dev/null; then
|
|
log_success "MongoDB connection test passed"
|
|
else
|
|
log_error "Cannot connect to MongoDB at $mongodb_host:$mongodb_port"
|
|
return 1
|
|
fi
|
|
|
|
# Run detailed MongoDB test script if available
|
|
if [ -f "$SCRIPT_DIR/test-mongodb-connection.sh" ]; then
|
|
log_info "Running detailed MongoDB connection tests..."
|
|
bash "$SCRIPT_DIR/test-mongodb-connection.sh"
|
|
fi
|
|
}
|
|
|
|
# Pull new Docker image
|
|
pull_docker_image() {
|
|
local image_tag="$1"
|
|
local full_image="registry.redecarneir.us/bcards:$image_tag"
|
|
|
|
log_info "Pulling Docker image: $full_image"
|
|
|
|
# Pull the multi-arch image
|
|
if docker pull "$full_image"; then
|
|
log_success "Image pulled successfully"
|
|
else
|
|
log_error "Failed to pull image: $full_image"
|
|
return 1
|
|
fi
|
|
|
|
# Verify image architecture
|
|
local image_arch=$(docker inspect --format='{{.Architecture}}' "$full_image" 2>/dev/null || echo "unknown")
|
|
local system_arch=$(uname -m)
|
|
|
|
log_info "Image architecture: $image_arch"
|
|
log_info "System architecture: $system_arch"
|
|
|
|
# Convert system arch format to Docker format for comparison
|
|
case "$system_arch" in
|
|
x86_64) system_arch="amd64" ;;
|
|
aarch64) system_arch="arm64" ;;
|
|
esac
|
|
|
|
if [ "$image_arch" = "$system_arch" ] || [ "$image_arch" = "unknown" ]; then
|
|
log_success "Image architecture is compatible"
|
|
else
|
|
log_warning "Image architecture ($image_arch) may not match system ($system_arch), but multi-arch support should handle this"
|
|
fi
|
|
}
|
|
|
|
# Deploy new version
|
|
deploy_new_version() {
|
|
local image_tag="$1"
|
|
|
|
log_info "Deploying new version with tag: $image_tag"
|
|
|
|
# Copy docker-compose file to deployment directory
|
|
cp "$PROJECT_ROOT/$DOCKER_COMPOSE_FILE" "$DEPLOY_DIR/"
|
|
|
|
# Create/update environment file
|
|
cat > "$DEPLOY_DIR/.env" << EOF
|
|
IMAGE_TAG=$image_tag
|
|
REGISTRY=registry.redecarneir.us
|
|
MONGODB_CONNECTION_STRING=mongodb://192.168.0.100:27017/BCardsDB
|
|
ASPNETCORE_ENVIRONMENT=Release
|
|
CERT_PASSWORD=
|
|
EOF
|
|
|
|
# Stop existing containers
|
|
cd "$DEPLOY_DIR"
|
|
if docker-compose -f "$DOCKER_COMPOSE_FILE" ps -q | grep -q .; then
|
|
log_info "Stopping existing containers..."
|
|
docker-compose -f "$DOCKER_COMPOSE_FILE" down --remove-orphans
|
|
fi
|
|
|
|
# Start new containers
|
|
log_info "Starting new containers..."
|
|
docker-compose -f "$DOCKER_COMPOSE_FILE" up -d
|
|
|
|
# Wait for containers to start
|
|
sleep 15
|
|
|
|
log_success "New version deployed"
|
|
}
|
|
|
|
# Health check
|
|
perform_health_check() {
|
|
log_info "Performing health check..."
|
|
|
|
local attempt=1
|
|
while [ $attempt -le $MAX_HEALTH_CHECK_ATTEMPTS ]; do
|
|
log_info "Health check attempt $attempt/$MAX_HEALTH_CHECK_ATTEMPTS"
|
|
|
|
# Check if container is running
|
|
if ! docker ps --format "table {{.Names}}" | grep -q "$CONTAINER_NAME"; then
|
|
log_warning "Container $CONTAINER_NAME is not running"
|
|
else
|
|
# Check application health endpoint
|
|
if curl -f -s "$HEALTH_CHECK_URL" >/dev/null 2>&1; then
|
|
log_success "Health check passed"
|
|
return 0
|
|
fi
|
|
|
|
# Check if the application is responding on port 80
|
|
if curl -f -s "http://localhost:8090/" >/dev/null 2>&1; then
|
|
log_success "Application is responding (health endpoint may not be configured)"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [ $attempt -eq $MAX_HEALTH_CHECK_ATTEMPTS ]; then
|
|
log_error "Health check failed after $MAX_HEALTH_CHECK_ATTEMPTS attempts"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Waiting $HEALTH_CHECK_INTERVAL seconds before next attempt..."
|
|
sleep $HEALTH_CHECK_INTERVAL
|
|
((attempt++))
|
|
done
|
|
}
|
|
|
|
# Rollback deployment
|
|
rollback_deployment() {
|
|
log_warning "Initiating rollback..."
|
|
|
|
cd "$DEPLOY_DIR"
|
|
|
|
# Stop current containers
|
|
if [ -f "$DOCKER_COMPOSE_FILE" ]; then
|
|
docker-compose -f "$DOCKER_COMPOSE_FILE" down --remove-orphans
|
|
fi
|
|
|
|
# Restore previous environment if backup exists
|
|
if [ -f ".env.backup" ]; then
|
|
mv ".env.backup" ".env"
|
|
log_info "Previous environment restored"
|
|
fi
|
|
|
|
# Try to start previous version if image is available
|
|
if [ -f ".previous_image" ]; then
|
|
local previous_image=$(cat ".previous_image")
|
|
log_info "Attempting to restore previous image: $previous_image"
|
|
|
|
# Update .env with previous image tag
|
|
local previous_tag=$(echo "$previous_image" | cut -d':' -f2)
|
|
sed -i "s/IMAGE_TAG=.*/IMAGE_TAG=$previous_tag/" .env 2>/dev/null || true
|
|
|
|
# Try to start previous version
|
|
if docker-compose -f "$DOCKER_COMPOSE_FILE" up -d; then
|
|
log_success "Rollback completed successfully"
|
|
else
|
|
log_error "Rollback failed - manual intervention required"
|
|
fi
|
|
else
|
|
log_warning "No previous version found for rollback"
|
|
fi
|
|
}
|
|
|
|
# Cleanup old images and containers
|
|
cleanup_old_resources() {
|
|
log_info "Cleaning up old Docker resources..."
|
|
|
|
# Remove dangling images
|
|
if docker images -f "dangling=true" -q | head -1 | grep -q .; then
|
|
docker rmi $(docker images -f "dangling=true" -q) || true
|
|
log_info "Dangling images removed"
|
|
fi
|
|
|
|
# Remove old backups (keep last 5)
|
|
if [ -d "$DEPLOY_DIR/backups" ]; then
|
|
find "$DEPLOY_DIR/backups" -maxdepth 1 -type d -name "20*" | sort -r | tail -n +6 | xargs rm -rf || true
|
|
log_info "Old backups cleaned up"
|
|
fi
|
|
|
|
log_success "Cleanup completed"
|
|
}
|
|
|
|
# Display deployment summary
|
|
display_summary() {
|
|
local image_tag="$1"
|
|
|
|
log_success "Deployment Summary:"
|
|
echo "=================================="
|
|
echo "🚀 Image Tag: $image_tag"
|
|
echo "🌐 Environment: Release (Staging)"
|
|
echo "🔗 Application URL: http://localhost:8090"
|
|
echo "🔗 Health Check: $HEALTH_CHECK_URL"
|
|
echo "🗄️ MongoDB: 192.168.0.100:27017"
|
|
echo "📁 Deploy Directory: $DEPLOY_DIR"
|
|
echo "🐳 Container: $CONTAINER_NAME"
|
|
|
|
# Show container status
|
|
echo ""
|
|
echo "Container Status:"
|
|
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(NAMES|$CONTAINER_NAME)" || true
|
|
|
|
# Show image info
|
|
echo ""
|
|
echo "Image Information:"
|
|
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.CreatedAt}}\t{{.Size}}" | grep -E "(REPOSITORY|bcards)" | head -5 || true
|
|
|
|
echo "=================================="
|
|
}
|
|
|
|
# Main deployment function
|
|
main() {
|
|
local image_tag="$1"
|
|
|
|
log_info "Starting deployment process for BCards Release environment"
|
|
log_info "Target image tag: $image_tag"
|
|
log_info "Target architecture: $(uname -m)"
|
|
log_info "Deploy directory: $DEPLOY_DIR"
|
|
|
|
# Execute deployment steps
|
|
validate_input "$@"
|
|
check_prerequisites
|
|
test_mongodb_connection
|
|
backup_current_deployment
|
|
pull_docker_image "$image_tag"
|
|
deploy_new_version "$image_tag"
|
|
|
|
# Perform health check (rollback handled by trap if this fails)
|
|
if perform_health_check; then
|
|
cleanup_old_resources
|
|
display_summary "$image_tag"
|
|
log_success "Deployment completed successfully!"
|
|
else
|
|
log_error "Health check failed - rollback will be triggered"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Run main function with all arguments
|
|
main "$@" |