#!/bin/bash # ๐Ÿงช Deployment Validation Script # Validates complete deployment with all environment variables and health checks set -e echo "๐Ÿš€ Starting deployment validation..." # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Cleanup function cleanup() { print_status "Cleaning up test containers..." APP_NAME_LOWER=$(echo "${APP_NAME:-meds}" | tr '[:upper:]' '[:lower:]') docker stop ${APP_NAME_LOWER}-validation-test 2>/dev/null || true docker rm ${APP_NAME_LOWER}-validation-test 2>/dev/null || true docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation down 2>/dev/null || true } # Set trap for cleanup trap cleanup EXIT print_status "1. Validating environment files..." # Check if required environment files exist if [[ ! -f .env ]]; then print_error ".env file not found. Run 'cp .env.example .env' and configure it." exit 1 fi if [[ ! -f .env.example ]]; then print_error ".env.example file not found." exit 1 fi print_success "Environment files exist" # Validate environment consistency print_status "2. Checking environment variable consistency..." ./scripts/validate-env.sh print_status "3. Setting up Docker Buildx..." # Ensure buildx is available if ! docker buildx version >/dev/null 2>&1; then print_error "Docker Buildx is not available. Please update Docker to a version that supports Buildx." exit 1 fi # Create a new builder instance if it doesn't exist if ! docker buildx ls | grep -q "meds-builder"; then print_status "Creating new buildx builder instance..." docker buildx create --name meds-builder --driver docker-container --bootstrap fi # Use the builder docker buildx use meds-builder print_status "4. Building multi-platform Docker image with buildx..." # Convert APP_NAME to lowercase for Docker compatibility APP_NAME_LOWER=$(echo "${APP_NAME:-meds}" | tr '[:upper:]' '[:lower:]') # Determine host platform HOST_ARCH="$(uname -m)" case "$HOST_ARCH" in x86_64) HOST_PLATFORM="linux/amd64" ;; aarch64|arm64) HOST_PLATFORM="linux/arm64" ;; *) HOST_PLATFORM="linux/amd64" ;; esac # Decide build strategy: # Enable multi-platform (push) if MULTI_PLATFORM=1 or CONTAINER_REGISTRY is set if [[ "${MULTI_PLATFORM:-0}" == "1" || -n "${CONTAINER_REGISTRY:-}" ]]; then BUILD_PLATFORMS="linux/amd64,linux/arm64" EXPORT_MODE="--push" # If CONTAINER_REGISTRY provided, ensure trailing slash if [[ -n "${CONTAINER_REGISTRY:-}" && "${CONTAINER_REGISTRY}" != */ ]]; then CONTAINER_REGISTRY="${CONTAINER_REGISTRY}/" fi IMAGE_TAG="${IMAGE_TAG:-${CONTAINER_REGISTRY:-}${APP_NAME_LOWER}-validation:latest}" print_status "Multi-platform build enabled (platforms: $BUILD_PLATFORMS) -> push $IMAGE_TAG" else BUILD_PLATFORMS="$HOST_PLATFORM" EXPORT_MODE="--load" IMAGE_TAG="${IMAGE_TAG:-${APP_NAME_LOWER}-validation}" print_status "Single-platform build ($BUILD_PLATFORMS) -> load locally as $IMAGE_TAG" fi # Perform build docker buildx build --no-cache \ --platform "$BUILD_PLATFORMS" \ --build-arg APP_NAME="${APP_NAME:-RxMinder}" \ --build-arg COUCHDB_USER="${COUCHDB_USER:-admin}" \ --build-arg COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}" \ --build-arg VITE_COUCHDB_URL="${VITE_COUCHDB_URL:-http://localhost:5984}" \ --build-arg VITE_COUCHDB_USER="${VITE_COUCHDB_USER:-admin}" \ --build-arg VITE_COUCHDB_PASSWORD="${VITE_COUCHDB_PASSWORD:-change-this-secure-password}" \ --build-arg APP_BASE_URL="${APP_BASE_URL:-http://localhost:8080}" \ --build-arg VITE_GOOGLE_CLIENT_ID="${VITE_GOOGLE_CLIENT_ID:-}" \ --build-arg VITE_GITHUB_CLIENT_ID="${VITE_GITHUB_CLIENT_ID:-}" \ --build-arg MAILGUN_API_KEY="${MAILGUN_API_KEY:-}" \ --build-arg MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-}" \ --build-arg MAILGUN_FROM_EMAIL="${MAILGUN_FROM_EMAIL:-}" \ --build-arg NODE_ENV="${NODE_ENV:-production}" \ -t "$IMAGE_TAG" \ $EXPORT_MODE \ . # If we pushed (multi-platform), pull the host-specific image for local tests if [[ "$EXPORT_MODE" == "--push" ]]; then print_status "Pulling image $IMAGE_TAG for local validation..." docker pull "$IMAGE_TAG" fi print_success "Docker image built successfully" print_status "5. Testing container startup and health..." # Run container in background docker run --rm -d \ -p 8083:80 \ --name ${APP_NAME_LOWER}-validation-test \ "$IMAGE_TAG" # Wait for container to start sleep 5 # Check if container is running if ! docker ps | grep -q ${APP_NAME_LOWER}-validation-test; then print_error "Container failed to start" docker logs ${APP_NAME_LOWER}-validation-test exit 1 fi print_success "Container started successfully" # Test health endpoint print_status "5. Testing health endpoint..." for i in {1..10}; do if curl -s -f http://localhost:8083/health > /dev/null; then print_success "Health endpoint responding" break elif [[ $i -eq 10 ]]; then print_error "Health endpoint not responding after 10 attempts" exit 1 else print_warning "Health endpoint not ready, retrying... ($i/10)" sleep 2 fi done # Test main application print_status "6. Testing main application..." HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8083) if [[ $HTTP_CODE -eq 200 ]]; then print_success "Main application responding (HTTP $HTTP_CODE)" else print_error "Main application not responding properly (HTTP $HTTP_CODE)" exit 1 fi # Test docker-compose build print_status "7. Testing Docker Compose build..." docker compose -f docker/docker-compose.yaml build frontend --no-cache print_success "Docker Compose build successful" # Test docker-compose with validation project name print_status "8. Testing Docker Compose deployment..." docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation up -d --build # Wait for services to start sleep 10 # Check service health if docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation ps | grep -q "Up"; then print_success "Docker Compose services started successfully" else print_error "Docker Compose services failed to start" docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation logs exit 1 fi # Test health of compose deployment if curl -s -f http://localhost:8080/health > /dev/null; then print_success "Docker Compose health endpoint responding" else print_warning "Docker Compose health endpoint not responding (may need CouchDB)" fi print_status "9. Checking image size..." IMAGE_SIZE=$(docker image inspect "$IMAGE_TAG" --format='{{.Size}}' | numfmt --to=iec) print_success "Image size: $IMAGE_SIZE" print_status "10. Validating security configuration..." # Check if image runs as non-root USER_INFO=$(docker run --rm "$IMAGE_TAG" whoami) if [[ "$USER_INFO" != "root" ]]; then print_success "Container runs as non-root user: $USER_INFO" else print_warning "Container runs as root user (security consideration)" fi # Check nginx configuration if docker run --rm "$IMAGE_TAG" nginx -t 2>/dev/null; then print_success "Nginx configuration is valid" else print_error "Nginx configuration has issues" exit 1 fi print_status "11. Final validation complete!" echo echo "๐ŸŽ‰ Deployment validation successful!" echo echo "Summary:" echo "โœ… Environment files validated" echo "โœ… Docker image builds successfully" echo "โœ… Container starts and runs healthy" echo "โœ… Health endpoints respond correctly" echo "โœ… Docker Compose deployment works" echo "โœ… Security configuration validated" echo "โœ… Image size optimized ($IMAGE_SIZE)" echo echo "Your deployment is ready for production! ๐Ÿš€" echo echo "Next steps:" echo "1. Configure production environment variables in .env" echo "2. Run './deploy.sh production' for production deployment" echo "3. Set up monitoring and backups" echo "4. Configure SSL/TLS certificates" echo