Initial commit: Complete NodeJS-native setup
- Migrated from Python pre-commit to NodeJS-native solution - Reorganized documentation structure - Set up Husky + lint-staged for efficient pre-commit hooks - Fixed Dockerfile healthcheck issue - Added comprehensive documentation index
This commit is contained in:
Executable
+221
@@ -0,0 +1,221 @@
|
||||
#!/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..."
|
||||
docker stop meds-validation-test 2>/dev/null || true
|
||||
docker rm meds-validation-test 2>/dev/null || true
|
||||
docker compose -f docker/docker-compose.yaml -p meds-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..."
|
||||
./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..."
|
||||
|
||||
# Build the image with buildx for multiple platforms
|
||||
docker buildx build --no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--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 meds-validation \
|
||||
--load \
|
||||
.
|
||||
|
||||
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 meds-validation-test \
|
||||
meds-validation
|
||||
|
||||
# Wait for container to start
|
||||
sleep 5
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q meds-validation-test; then
|
||||
print_error "Container failed to start"
|
||||
docker logs meds-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 meds-validation up -d --build
|
||||
|
||||
# Wait for services to start
|
||||
sleep 10
|
||||
|
||||
# Check service health
|
||||
if docker compose -f docker/docker-compose.yaml -p meds-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 meds-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 meds-validation --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 meds-validation 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 meds-validation 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
|
||||
Executable
+274
@@ -0,0 +1,274 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Kubernetes deployment script with environment variable substitution
|
||||
# This script processes template files and applies them to Kubernetes
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Script directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
K8S_DIR="$SCRIPT_DIR/k8s"
|
||||
TEMP_DIR="/tmp/meds-k8s-deploy"
|
||||
|
||||
# Function to print colored output
|
||||
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 load environment variables
|
||||
load_env() {
|
||||
local env_file="$1"
|
||||
if [[ -f "$env_file" ]]; then
|
||||
print_info "Loading environment from $env_file"
|
||||
# Export variables from .env file
|
||||
set -a
|
||||
source "$env_file"
|
||||
set +a
|
||||
else
|
||||
print_warning "Environment file $env_file not found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to substitute environment variables in template files
|
||||
substitute_templates() {
|
||||
print_info "Processing template files..."
|
||||
|
||||
# Create temporary directory
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
# Process each template file
|
||||
for template_file in "$K8S_DIR"/*.template; do
|
||||
if [[ -f "$template_file" ]]; then
|
||||
local filename=$(basename "$template_file" .template)
|
||||
local output_file="$TEMP_DIR/$filename"
|
||||
|
||||
print_info "Processing template: $filename"
|
||||
|
||||
# Substitute environment variables
|
||||
envsubst < "$template_file" > "$output_file"
|
||||
|
||||
print_success "Generated: $output_file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Function to validate required environment variables
|
||||
validate_env() {
|
||||
local required_vars=("INGRESS_HOST")
|
||||
local missing_vars=()
|
||||
|
||||
for var in "${required_vars[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
missing_vars+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_vars[@]} -gt 0 ]]; then
|
||||
print_error "Missing required environment variables:"
|
||||
for var in "${missing_vars[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
echo ""
|
||||
echo "Please set these variables in your .env file or environment."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to apply Kubernetes manifests
|
||||
apply_manifests() {
|
||||
local manifest_dir="$1"
|
||||
|
||||
print_info "Applying Kubernetes manifests from $manifest_dir"
|
||||
|
||||
# Apply non-template files first
|
||||
for manifest_file in "$K8S_DIR"/*.yaml; do
|
||||
if [[ -f "$manifest_file" && ! "$manifest_file" =~ \.template$ ]]; then
|
||||
print_info "Applying: $(basename "$manifest_file")"
|
||||
kubectl apply -f "$manifest_file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Apply processed template files
|
||||
if [[ -d "$TEMP_DIR" ]]; then
|
||||
for manifest_file in "$TEMP_DIR"/*.yaml; do
|
||||
if [[ -f "$manifest_file" ]]; then
|
||||
print_info "Applying: $(basename "$manifest_file")"
|
||||
kubectl apply -f "$manifest_file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to cleanup temporary files
|
||||
cleanup() {
|
||||
if [[ -d "$TEMP_DIR" ]]; then
|
||||
print_info "Cleaning up temporary files..."
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to show deployment status
|
||||
show_status() {
|
||||
print_info "Deployment Status:"
|
||||
echo ""
|
||||
|
||||
print_info "Pods:"
|
||||
kubectl get pods -l app=rxminder
|
||||
echo ""
|
||||
|
||||
print_info "Services:"
|
||||
kubectl get services -l app=rxminder
|
||||
echo ""
|
||||
|
||||
print_info "Ingress:"
|
||||
kubectl get ingress -l app=rxminder
|
||||
echo ""
|
||||
|
||||
if [[ -n "${INGRESS_HOST:-}" ]]; then
|
||||
print_success "Application should be available at: http://$INGRESS_HOST"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to show usage
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -e, --env FILE Environment file to load (default: .env)"
|
||||
echo " -d, --dry-run Show what would be applied without applying"
|
||||
echo " -s, --status Show deployment status only"
|
||||
echo " -c, --cleanup Cleanup temporary files and exit"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 Deploy with default .env file"
|
||||
echo " $0 -e .env.prod Deploy with production environment"
|
||||
echo " $0 --dry-run Preview what would be deployed"
|
||||
echo " $0 --status Check deployment status"
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
local env_file=".env"
|
||||
local dry_run=false
|
||||
local status_only=false
|
||||
local cleanup_only=false
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-e|--env)
|
||||
env_file="$2"
|
||||
shift 2
|
||||
;;
|
||||
-d|--dry-run)
|
||||
dry_run=true
|
||||
shift
|
||||
;;
|
||||
-s|--status)
|
||||
status_only=true
|
||||
shift
|
||||
;;
|
||||
-c|--cleanup)
|
||||
cleanup_only=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Handle cleanup only
|
||||
if [[ "$cleanup_only" == true ]]; then
|
||||
cleanup
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Handle status only
|
||||
if [[ "$status_only" == true ]]; then
|
||||
show_status
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if kubectl is available
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
print_error "kubectl is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we can connect to Kubernetes cluster
|
||||
if ! kubectl cluster-info &> /dev/null; then
|
||||
print_error "Cannot connect to Kubernetes cluster"
|
||||
print_info "Make sure your kubectl is configured correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "🚀 Deploying Medication Reminder App to Kubernetes"
|
||||
echo ""
|
||||
|
||||
# Load environment variables
|
||||
load_env "$env_file"
|
||||
|
||||
# Validate required environment variables
|
||||
validate_env
|
||||
|
||||
# Process templates
|
||||
substitute_templates
|
||||
|
||||
if [[ "$dry_run" == true ]]; then
|
||||
print_info "Dry run mode - showing generated manifests:"
|
||||
echo ""
|
||||
|
||||
for manifest_file in "$TEMP_DIR"/*.yaml; do
|
||||
if [[ -f "$manifest_file" ]]; then
|
||||
echo "=== $(basename "$manifest_file") ==="
|
||||
cat "$manifest_file"
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
else
|
||||
# Apply manifests
|
||||
apply_manifests "$K8S_DIR"
|
||||
|
||||
print_success "Deployment completed!"
|
||||
echo ""
|
||||
|
||||
# Show status
|
||||
show_status
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
cleanup
|
||||
}
|
||||
|
||||
# Trap to ensure cleanup happens
|
||||
trap cleanup EXIT
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
Executable
+221
@@ -0,0 +1,221 @@
|
||||
#!/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..."
|
||||
docker stop meds-validation-test 2>/dev/null || true
|
||||
docker rm meds-validation-test 2>/dev/null || true
|
||||
docker compose -f docker/docker-compose.yaml -p meds-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..."
|
||||
./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..."
|
||||
|
||||
# Build the image with buildx for multiple platforms
|
||||
docker buildx build --no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--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 meds-validation \
|
||||
--load \
|
||||
.
|
||||
|
||||
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 meds-validation-test \
|
||||
meds-validation
|
||||
|
||||
# Wait for container to start
|
||||
sleep 5
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q meds-validation-test; then
|
||||
print_error "Container failed to start"
|
||||
docker logs meds-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 meds-validation up -d --build
|
||||
|
||||
# Wait for services to start
|
||||
sleep 10
|
||||
|
||||
# Check service health
|
||||
if docker compose -f docker/docker-compose.yaml -p meds-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 meds-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 meds-validation --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 meds-validation 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 meds-validation 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
|
||||
Executable
+382
@@ -0,0 +1,382 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🧪 Deployment Validation Script
|
||||
# Validates complete deployment with all environment variables and health checks
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting deploymif docker compose -f docker/docker-compose.yaml -p rxminder-validation ps | grep -q "Up"; then
|
||||
print_success "Docker Compose setup completed successfully!"
|
||||
else
|
||||
print_error "Docker Compose services failed to start"
|
||||
docker compose -f docker/docker-compose.yaml -p rxminder-validation logsalidation..."
|
||||
|
||||
# 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..."
|
||||
docker stop rxminder-validation-test 2>/dev/null || true
|
||||
docker rm rxminder-validation-test 2>/dev/null || true
|
||||
docker compose -f docker/docker-compose.yaml -p rxminder-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..."
|
||||
./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 "rxminder-builder"; then
|
||||
print_status "Creating new buildx builder instance..."
|
||||
docker buildx create --name rxminder-builder --driver docker-container --bootstrap
|
||||
fi
|
||||
|
||||
# Use the builder
|
||||
docker buildx use rxminder-builder
|
||||
|
||||
print_status "4. Building multi-platform Docker image with buildx..."
|
||||
|
||||
# Build the image with buildx for multiple platforms
|
||||
docker buildx build --no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--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 rxminder-validation \
|
||||
--load \
|
||||
.
|
||||
|
||||
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 rxminder-validation-test \
|
||||
rxminder-validation
|
||||
|
||||
# Wait for container to start
|
||||
sleep 5
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q rxminder-validation-test; then
|
||||
print_error "Container failed to start"
|
||||
docker logs rxminder-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 rxminder-validation up -d --build
|
||||
|
||||
# Wait for services to start
|
||||
sleep 10
|
||||
|
||||
# Check service health
|
||||
if docker compose -f docker/docker-compose.yaml -p meds-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 meds-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 rxminder-validation --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 rxminder-validation 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 rxminder-validation 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
|
||||
|
||||
# Load Gitea-specific environment variables
|
||||
REGISTRY=${GITEA_SERVER_URL#https://}
|
||||
IMAGE_NAME=${GITEA_REPOSITORY}
|
||||
IMAGE_TAG=${GITEA_SHA:0:8}
|
||||
|
||||
print_status "Registry: $REGISTRY"
|
||||
print_status "Image: $IMAGE_NAME:$IMAGE_TAG"
|
||||
fi
|
||||
|
||||
# Check if .env file exists
|
||||
if [ ! -f ".env" ]; then
|
||||
print_warning ".env file not found, using defaults"
|
||||
|
||||
# Create minimal .env for Gitea deployment
|
||||
cat > .env << EOF
|
||||
COUCHDB_USER=admin
|
||||
COUCHDB_PASSWORD=change-this-secure-password
|
||||
VITE_COUCHDB_URL=http://couchdb:5984
|
||||
VITE_COUCHDB_USER=admin
|
||||
VITE_COUCHDB_PASSWORD=change-this-secure-password
|
||||
APP_BASE_URL=http://localhost:8080
|
||||
NODE_ENV=production
|
||||
EOF
|
||||
|
||||
print_warning "Created default .env file - please update with your credentials"
|
||||
fi
|
||||
|
||||
# Load environment variables
|
||||
print_status "Loading environment variables from .env file..."
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
|
||||
# Validate required environment variables
|
||||
REQUIRED_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "VITE_COUCHDB_URL")
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if [ -z "${!var}" ]; then
|
||||
print_error "Required environment variable $var is not set"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
print_success "Environment variables validated"
|
||||
|
||||
# Function to deploy via Docker Compose
|
||||
deploy_compose() {
|
||||
print_status "Deploying with Docker Compose..."
|
||||
|
||||
# Export image variables for compose
|
||||
export IMAGE_TAG
|
||||
export REGISTRY
|
||||
export IMAGE_NAME
|
||||
|
||||
# Use the built image from registry if available
|
||||
if [ "$GITEA_ACTIONS" = "true" ]; then
|
||||
# Override the image in docker-compose
|
||||
export FRONTEND_IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG"
|
||||
print_status "Using Gitea Actions built image: $FRONTEND_IMAGE"
|
||||
fi
|
||||
|
||||
# Pull the latest images
|
||||
print_status "Pulling latest images..."
|
||||
docker-compose -f docker/docker-compose.yaml pull || print_warning "Failed to pull some images"
|
||||
|
||||
# Start services
|
||||
print_status "Starting services..."
|
||||
docker-compose -f docker/docker-compose.yaml up -d
|
||||
|
||||
# Wait for services
|
||||
print_status "Waiting for services to be ready..."
|
||||
sleep 10
|
||||
|
||||
# Health check
|
||||
print_status "Checking service health..."
|
||||
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
|
||||
print_success "Frontend service is healthy"
|
||||
else
|
||||
print_warning "Frontend health check failed, checking logs..."
|
||||
docker-compose -f docker/docker-compose.yaml logs frontend
|
||||
fi
|
||||
|
||||
if curl -f http://localhost:5984/_up > /dev/null 2>&1; then
|
||||
print_success "CouchDB service is healthy"
|
||||
else
|
||||
print_warning "CouchDB health check failed"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to deploy via Kubernetes
|
||||
deploy_k8s() {
|
||||
print_status "Deploying to Kubernetes..."
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
print_error "kubectl is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update image in k8s manifests
|
||||
if [ "$GITEA_ACTIONS" = "true" ]; then
|
||||
print_status "Updating Kubernetes manifests with new image..."
|
||||
sed -i "s|image:.*rxminder.*|image: $REGISTRY/$IMAGE_NAME:$IMAGE_TAG|g" k8s/frontend-deployment.yaml
|
||||
fi
|
||||
|
||||
# Apply manifests
|
||||
print_status "Applying Kubernetes manifests..."
|
||||
kubectl apply -f k8s/
|
||||
|
||||
# Wait for rollout
|
||||
print_status "Waiting for deployment rollout..."
|
||||
kubectl rollout status deployment/frontend-deployment
|
||||
|
||||
print_success "Kubernetes deployment completed"
|
||||
}
|
||||
|
||||
# Main deployment logic
|
||||
case "$ENVIRONMENT" in
|
||||
"production"|"prod")
|
||||
print_status "Deploying to production environment"
|
||||
deploy_compose
|
||||
;;
|
||||
"kubernetes"|"k8s")
|
||||
print_status "Deploying to Kubernetes environment"
|
||||
deploy_k8s
|
||||
;;
|
||||
"staging")
|
||||
print_status "Deploying to staging environment"
|
||||
# Use staging-specific configurations
|
||||
export APP_BASE_URL="http://staging.localhost:8080"
|
||||
deploy_compose
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown environment: $ENVIRONMENT"
|
||||
echo "Available environments: production, kubernetes, staging"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
print_success "Deployment to $ENVIRONMENT completed successfully! 🎉"
|
||||
|
||||
# Post-deployment tasks
|
||||
print_status "Running post-deployment tasks..."
|
||||
|
||||
# Cleanup old images (optional)
|
||||
if [ "$CLEANUP_OLD_IMAGES" = "true" ]; then
|
||||
print_status "Cleaning up old Docker images..."
|
||||
docker image prune -f --filter "until=72h" || print_warning "Image cleanup failed"
|
||||
fi
|
||||
|
||||
# Send notification (if configured)
|
||||
if [ -n "$DEPLOYMENT_WEBHOOK_URL" ]; then
|
||||
print_status "Sending deployment notification..."
|
||||
curl -X POST "$DEPLOYMENT_WEBHOOK_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"text\":\"✅ RxMinder deployed to $ENVIRONMENT\", \"environment\":\"$ENVIRONMENT\", \"image\":\"$REGISTRY/$IMAGE_NAME:$IMAGE_TAG\"}" \
|
||||
|| print_warning "Failed to send notification"
|
||||
fi
|
||||
|
||||
print_success "All tasks completed! 🚀"
|
||||
Executable
+518
@@ -0,0 +1,518 @@
|
||||
#!/bin/bash
|
||||
|
||||
# gitea-deploy.sh - Gitea-specific deployment script
|
||||
# Usage: ./gitea-deploy.sh [environment] [image-tag]
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Load environment variables from .env file if it exists
|
||||
if [ -f "$PROJECT_DIR/.env" ]; then
|
||||
export $(cat "$PROJECT_DIR/.env" | grep -v '^#' | grep -E '^[A-Z_]+=.*' | xargs)
|
||||
fi
|
||||
|
||||
ENVIRONMENT=${1:-production}
|
||||
IMAGE_TAG=${2:-latest}
|
||||
REGISTRY=${GITEA_REGISTRY:-${CONTAINER_REGISTRY:-"ghcr.io"}}
|
||||
IMAGE_NAME=${GITEA_REPOSITORY:-${CONTAINER_REPOSITORY:-"rxminder"}}
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_status() {
|
||||
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}"
|
||||
}
|
||||
|
||||
echo "🚀 Deploying RxMinder from Gitea to $ENVIRONMENT environment..."
|
||||
|
||||
# Check if running in Gitea Actions
|
||||
if [ "$GITEA_ACTIONS" = "true" ]; then
|
||||
print_status "Running in Gitea Actions environment"
|
||||
|
||||
# Load Gitea-specific environment variables
|
||||
REGISTRY=${GITEA_SERVER_URL#https://}
|
||||
IMAGE_NAME=${GITEA_REPOSITORY}
|
||||
IMAGE_TAG=${GITEA_SHA:0:8}
|
||||
|
||||
print_status "Registry: $REGISTRY"
|
||||
print_status "Image: $IMAGE_NAME:$IMAGE_TAG"
|
||||
fi
|
||||
|
||||
# Check if .env file exists
|
||||
if [ ! -f ".env" ]; then
|
||||
print_warning ".env file not found, using defaults"
|
||||
|
||||
# Create minimal .env for Gitea deployment
|
||||
cat > .env << EOF
|
||||
COUCHDB_USER=admin
|
||||
COUCHDB_PASSWORD=change-this-secure-password
|
||||
VITE_COUCHDB_URL=http://couchdb:5984
|
||||
VITE_COUCHDB_USER=admin
|
||||
VITE_COUCHDB_PASSWORD=change-this-secure-password
|
||||
APP_BASE_URL=http://localhost:8080
|
||||
NODE_ENV=production
|
||||
EOF
|
||||
|
||||
print_warning "Created default .env file - please update with your credentials"
|
||||
fi
|
||||
|
||||
# Load environment variables
|
||||
print_status "Loading environment variables from .env file..."
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
|
||||
# Validate required environment variables
|
||||
REQUIRED_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "VITE_COUCHDB_URL")
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if [ -z "${!var}" ]; then
|
||||
print_error "Required environment variable $var is not set"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
print_success "Environment variables validated"
|
||||
|
||||
# Function to deploy via Docker Compose
|
||||
deploy_compose() {
|
||||
print_status "Deploying with Docker Compose..."
|
||||
|
||||
# Export image variables for compose
|
||||
export IMAGE_TAG
|
||||
export REGISTRY
|
||||
export IMAGE_NAME
|
||||
|
||||
# Use the built image from registry if available
|
||||
if [ "$GITEA_ACTIONS" = "true" ]; then
|
||||
# Override the image in docker-compose
|
||||
export FRONTEND_IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG"
|
||||
print_status "Using Gitea Actions built image: $FRONTEND_IMAGE"
|
||||
fi
|
||||
|
||||
# Pull the latest images
|
||||
print_status "Pulling latest images..."
|
||||
docker-compose -f docker/docker-compose.yaml pull || print_warning "Failed to pull some images"
|
||||
|
||||
# Start services
|
||||
print_status "Starting services..."
|
||||
docker-compose -f docker/docker-compose.yaml up -d
|
||||
|
||||
# Wait for services
|
||||
print_status "Waiting for services to be ready..."
|
||||
sleep 10
|
||||
|
||||
# Health check
|
||||
print_status "Checking service health..."
|
||||
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
|
||||
print_success "Frontend service is healthy"
|
||||
else
|
||||
print_warning "Frontend health check failed, checking logs..."
|
||||
docker-compose -f docker/docker-compose.yaml logs frontend
|
||||
fi
|
||||
|
||||
if curl -f http://localhost:5984/_up > /dev/null 2>&1; then
|
||||
print_success "CouchDB service is healthy"
|
||||
else
|
||||
print_warning "CouchDB health check failed"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to deploy via Kubernetes
|
||||
deploy_k8s() {
|
||||
print_status "Deploying to Kubernetes..."
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
print_error "kubectl is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update image in k8s manifests
|
||||
if [ "$GITEA_ACTIONS" = "true" ]; then
|
||||
print_status "Updating Kubernetes manifests with new image..."
|
||||
sed -i "s|image:.*rxminder.*|image: $REGISTRY/$IMAGE_NAME:$IMAGE_TAG|g" k8s/frontend-deployment.yaml
|
||||
fi
|
||||
|
||||
# Apply manifests
|
||||
print_status "Applying Kubernetes manifests..."
|
||||
kubectl apply -f k8s/
|
||||
|
||||
# Wait for rollout
|
||||
print_status "Waiting for deployment rollout..."
|
||||
kubectl rollout status deployment/frontend-deployment
|
||||
|
||||
print_success "Kubernetes deployment completed"
|
||||
}
|
||||
|
||||
# Main deployment logic
|
||||
case "$ENVIRONMENT" in
|
||||
"production"|"prod")
|
||||
print_status "Deploying to production environment"
|
||||
deploy_compose
|
||||
;;
|
||||
"kubernetes"|"k8s")
|
||||
print_status "Deploying to Kubernetes environment"
|
||||
deploy_k8s
|
||||
;;
|
||||
"staging")
|
||||
print_status "Deploying to staging environment"
|
||||
# Use staging-specific configurations
|
||||
export APP_BASE_URL="http://staging.localhost:8080"
|
||||
deploy_compose
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown environment: $ENVIRONMENT"
|
||||
echo "Available environments: production, kubernetes, staging"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
print_success "Deployment to $ENVIRONMENT completed successfully! 🎉"
|
||||
|
||||
# Post-deployment tasks
|
||||
print_status "Running post-deployment tasks..."
|
||||
|
||||
# Cleanup old images (optional)
|
||||
if [ "$CLEANUP_OLD_IMAGES" = "true" ]; then
|
||||
print_status "Cleaning up old Docker images..."
|
||||
docker image prune -f --filter "until=72h" || print_warning "Image cleanup failed"
|
||||
fi
|
||||
|
||||
# Send notification (if configured)
|
||||
if [ -n "$DEPLOYMENT_WEBHOOK_URL" ]; then
|
||||
print_status "Sending deployment notification..."
|
||||
curl -X POST "$DEPLOYMENT_WEBHOOK_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"text\":\"✅ RxMinder deployed to $ENVIRONMENT\", \"environment\":\"$ENVIRONMENT\", \"image\":\"$REGISTRY/$IMAGE_NAME:$IMAGE_TAG\"}" \
|
||||
|| print_warning "Failed to send notification"
|
||||
fi
|
||||
|
||||
print_success "All tasks completed! 🚀"
|
||||
print_error "Docker is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Docker Buildx
|
||||
if ! docker buildx version >/dev/null 2>&1; then
|
||||
print_error "Docker Buildx is not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if in Gitea Actions environment
|
||||
if [ "$GITEA_ACTIONS" = "true" ]; then
|
||||
print_status "Running in Gitea Actions environment"
|
||||
GITEA_REGISTRY=${GITEA_SERVER_URL#https://}
|
||||
GITEA_REPOSITORY=${GITEA_REPOSITORY}
|
||||
fi
|
||||
|
||||
print_success "All requirements met"
|
||||
}
|
||||
|
||||
setup_buildx() {
|
||||
print_status "Setting up Docker Buildx for Gitea..."
|
||||
|
||||
# Create builder if it doesn't exist
|
||||
if ! docker buildx ls | grep -q "gitea-builder"; then
|
||||
print_status "Creating Gitea buildx builder..."
|
||||
docker buildx create \
|
||||
--name gitea-builder \
|
||||
--driver docker-container \
|
||||
--bootstrap \
|
||||
--use
|
||||
print_success "Gitea builder created"
|
||||
else
|
||||
docker buildx use gitea-builder
|
||||
print_success "Using existing Gitea builder"
|
||||
fi
|
||||
}
|
||||
|
||||
login_registry() {
|
||||
print_status "Logging into Gitea registry..."
|
||||
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
print_error "GITEA_TOKEN environment variable is required"
|
||||
print_status "Set it with: export GITEA_TOKEN=your_token"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Login to Gitea registry
|
||||
echo "$GITEA_TOKEN" | docker login "$GITEA_REGISTRY" -u "$GITEA_ACTOR" --password-stdin
|
||||
print_success "Logged into Gitea registry"
|
||||
}
|
||||
|
||||
build_local() {
|
||||
print_status "Building for local development..."
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Load environment variables
|
||||
if [ -f ".env" ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
# Build with Gitea bake file
|
||||
docker buildx bake \
|
||||
-f .gitea/gitea-bake.hcl \
|
||||
--set="*.platform=linux/amd64" \
|
||||
--load \
|
||||
dev
|
||||
|
||||
print_success "Local build completed"
|
||||
}
|
||||
|
||||
build_multiplatform() {
|
||||
local tag=${1:-$DEFAULT_TAG}
|
||||
print_status "Building multi-platform image with tag: $tag..."
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Load environment variables
|
||||
if [ -f ".env" ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
# Export variables for bake
|
||||
export TAG="$tag"
|
||||
export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "dev")}
|
||||
|
||||
# Build with Gitea bake file
|
||||
docker buildx bake \
|
||||
-f .gitea/gitea-bake.hcl \
|
||||
app-ci
|
||||
|
||||
print_success "Multi-platform build completed"
|
||||
}
|
||||
|
||||
build_staging() {
|
||||
print_status "Building staging image..."
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Load environment variables
|
||||
if [ -f ".env.staging" ]; then
|
||||
export $(cat .env.staging | grep -v '^#' | xargs)
|
||||
elif [ -f ".env" ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
# Export variables for bake
|
||||
export TAG="staging-$(date +%Y%m%d-%H%M%S)"
|
||||
export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "staging")}
|
||||
|
||||
# Build staging target
|
||||
docker buildx bake \
|
||||
-f .gitea/gitea-bake.hcl \
|
||||
staging
|
||||
|
||||
print_success "Staging build completed"
|
||||
}
|
||||
|
||||
build_production() {
|
||||
local tag=${1:-$DEFAULT_TAG}
|
||||
print_status "Building production image with tag: $tag..."
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Load production environment variables
|
||||
if [ -f ".env.production" ]; then
|
||||
export $(cat .env.production | grep -v '^#' | xargs)
|
||||
elif [ -f ".env" ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
# Export variables for bake
|
||||
export TAG="$tag"
|
||||
export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "prod")}
|
||||
|
||||
# Build production target with full attestations
|
||||
docker buildx bake \
|
||||
-f .gitea/gitea-bake.hcl \
|
||||
prod
|
||||
|
||||
print_success "Production build completed"
|
||||
}
|
||||
|
||||
test_local() {
|
||||
print_status "Running tests locally..."
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Install dependencies if needed
|
||||
if [ ! -d "node_modules" ]; then
|
||||
print_status "Installing dependencies..."
|
||||
bun install --frozen-lockfile
|
||||
fi
|
||||
|
||||
# Run linting
|
||||
print_status "Running linter..."
|
||||
bun run lint
|
||||
|
||||
# Run type checking
|
||||
print_status "Running type checker..."
|
||||
bun run type-check
|
||||
|
||||
# Run tests
|
||||
print_status "Running tests..."
|
||||
bun run test
|
||||
|
||||
print_success "All tests passed"
|
||||
}
|
||||
|
||||
deploy() {
|
||||
local environment=${1:-production}
|
||||
local tag=${2:-latest}
|
||||
|
||||
print_status "Deploying to $environment with tag $tag..."
|
||||
|
||||
# Use the gitea-deploy script
|
||||
"$SCRIPT_DIR/gitea-deploy.sh" "$environment" "$tag"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
print_status "Cleaning up Gitea builder and images..."
|
||||
|
||||
# Remove builder
|
||||
if docker buildx ls | grep -q "gitea-builder"; then
|
||||
docker buildx rm gitea-builder
|
||||
print_success "Gitea builder removed"
|
||||
fi
|
||||
|
||||
# Clean up old images (keep last 3 tags)
|
||||
print_status "Cleaning up old images..."
|
||||
docker image prune -f --filter "until=72h" || print_warning "Image cleanup failed"
|
||||
|
||||
print_success "Cleanup completed"
|
||||
}
|
||||
|
||||
show_status() {
|
||||
print_status "Gitea CI/CD Status"
|
||||
echo
|
||||
|
||||
# Check environment
|
||||
if [ "$GITEA_ACTIONS" = "true" ]; then
|
||||
echo "🏃 Running in Gitea Actions"
|
||||
echo "📦 Registry: $GITEA_REGISTRY"
|
||||
echo "📋 Repository: $GITEA_REPOSITORY"
|
||||
echo "🏷️ SHA: ${GITEA_SHA:-unknown}"
|
||||
echo "🌿 Branch: ${GITEA_REF_NAME:-unknown}"
|
||||
else
|
||||
echo "💻 Running locally"
|
||||
echo "📦 Registry: $GITEA_REGISTRY"
|
||||
echo "📋 Repository: $GITEA_REPOSITORY"
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# Check Docker and buildx
|
||||
echo "🐳 Docker version: $(docker --version)"
|
||||
echo "🔧 Buildx version: $(docker buildx version)"
|
||||
|
||||
# Check builders
|
||||
echo
|
||||
echo "🏗️ Available builders:"
|
||||
docker buildx ls
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Gitea CI/CD Helper for RxMinder
|
||||
|
||||
Usage: $0 [command] [options]
|
||||
|
||||
Commands:
|
||||
setup - Setup buildx builder for Gitea
|
||||
login - Login to Gitea registry
|
||||
build-local - Build for local development
|
||||
build-multi [tag] - Build multi-platform image
|
||||
build-staging - Build staging image
|
||||
build-prod [tag] - Build production image
|
||||
test - Run tests locally
|
||||
deploy [env] [tag] - Deploy to environment
|
||||
cleanup - Cleanup builders and images
|
||||
status - Show CI/CD status
|
||||
help - Show this help
|
||||
|
||||
Examples:
|
||||
$0 setup
|
||||
$0 build-local
|
||||
$0 build-multi v1.2.3
|
||||
$0 build-staging
|
||||
$0 build-prod v1.2.3
|
||||
$0 test
|
||||
$0 deploy production v1.2.3
|
||||
$0 deploy staging
|
||||
$0 status
|
||||
|
||||
Environment Variables:
|
||||
GITEA_REGISTRY - Gitea registry URL (default: gitea.example.com)
|
||||
GITEA_REPOSITORY - Repository name (default: user/rxminder)
|
||||
GITEA_TOKEN - Gitea access token (required for registry)
|
||||
GITEA_ACTOR - Gitea username (for registry login)
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main command handling
|
||||
case "${1:-help}" in
|
||||
"setup")
|
||||
check_requirements
|
||||
setup_buildx
|
||||
;;
|
||||
"login")
|
||||
check_requirements
|
||||
login_registry
|
||||
;;
|
||||
"build-local")
|
||||
check_requirements
|
||||
setup_buildx
|
||||
build_local
|
||||
;;
|
||||
"build-multi")
|
||||
check_requirements
|
||||
setup_buildx
|
||||
login_registry
|
||||
build_multiplatform "$2"
|
||||
;;
|
||||
"build-staging")
|
||||
check_requirements
|
||||
setup_buildx
|
||||
login_registry
|
||||
build_staging
|
||||
;;
|
||||
"build-prod")
|
||||
check_requirements
|
||||
setup_buildx
|
||||
login_registry
|
||||
build_production "$2"
|
||||
;;
|
||||
"test")
|
||||
test_local
|
||||
;;
|
||||
"deploy")
|
||||
deploy "$2" "$3"
|
||||
;;
|
||||
"cleanup")
|
||||
cleanup
|
||||
;;
|
||||
"status")
|
||||
show_status
|
||||
;;
|
||||
"help"|*)
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
Executable
+330
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 🚀 RxMinder Template-based Kubernetes Deployment Script
|
||||
# This script processes template files and applies them to Kubernetes
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Script configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
K8S_DIR="$(dirname "$SCRIPT_DIR")/k8s"
|
||||
ENV_FILE="$(dirname "$SCRIPT_DIR")/.env"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_status() {
|
||||
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 load environment variables
|
||||
load_env() {
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
print_status "Loading environment variables from .env..."
|
||||
# Export variables from .env file
|
||||
set -a
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
print_success "Environment variables loaded"
|
||||
else
|
||||
print_warning ".env file not found at $ENV_FILE"
|
||||
print_warning "Using default values. Copy .env.example to .env and customize."
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to substitute environment variables in templates
|
||||
substitute_template() {
|
||||
local template_file="$1"
|
||||
local output_file="$2"
|
||||
|
||||
print_status "Processing template: $template_file"
|
||||
|
||||
# Use envsubst to substitute environment variables
|
||||
if command -v envsubst >/dev/null 2>&1; then
|
||||
envsubst < "$template_file" > "$output_file"
|
||||
else
|
||||
print_error "envsubst not found. Please install gettext package."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Generated: $output_file"
|
||||
}
|
||||
|
||||
# Function to apply Kubernetes resources
|
||||
apply_k8s_resource() {
|
||||
local resource_file="$1"
|
||||
|
||||
if [[ -f "$resource_file" ]]; then
|
||||
print_status "Applying Kubernetes resource: $resource_file"
|
||||
if kubectl apply -f "$resource_file"; then
|
||||
print_success "Applied: $resource_file"
|
||||
else
|
||||
print_error "Failed to apply: $resource_file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
print_warning "Resource file not found: $resource_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to validate required environment variables
|
||||
validate_env() {
|
||||
local required_vars=(
|
||||
"APP_NAME"
|
||||
"DOCKER_IMAGE"
|
||||
"COUCHDB_USER"
|
||||
"COUCHDB_PASSWORD"
|
||||
"INGRESS_HOST"
|
||||
"STORAGE_CLASS"
|
||||
"STORAGE_SIZE"
|
||||
)
|
||||
|
||||
local missing_vars=()
|
||||
|
||||
for var in "${required_vars[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
missing_vars+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_vars[@]} -gt 0 ]]; then
|
||||
print_error "Missing required environment variables:"
|
||||
for var in "${missing_vars[@]}"; do
|
||||
echo -e " ${RED}- $var${NC}"
|
||||
done
|
||||
print_warning "Please update your .env file with these variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "All required environment variables are set"
|
||||
}
|
||||
|
||||
# Function to process all templates
|
||||
process_templates() {
|
||||
local temp_dir="/tmp/rxminder-k8s-$$"
|
||||
mkdir -p "$temp_dir"
|
||||
|
||||
print_status "Processing Kubernetes templates..."
|
||||
|
||||
# Find all template files
|
||||
local template_files=(
|
||||
"$K8S_DIR/couchdb-secret.yaml.template"
|
||||
"$K8S_DIR/ingress.yaml.template"
|
||||
)
|
||||
|
||||
# Add any additional template files
|
||||
for template_file in "$K8S_DIR"/*.template; do
|
||||
if [[ -f "$template_file" ]]; then
|
||||
template_files+=("$template_file")
|
||||
fi
|
||||
done
|
||||
|
||||
# Process each template
|
||||
for template_file in "${template_files[@]}"; do
|
||||
if [[ -f "$template_file" ]]; then
|
||||
local base_name
|
||||
base_name="$(basename "$template_file" .template)"
|
||||
local output_file="$temp_dir/$base_name"
|
||||
substitute_template "$template_file" "$output_file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$temp_dir"
|
||||
}
|
||||
|
||||
# Function to deploy resources in correct order
|
||||
deploy_resources() {
|
||||
local resource_dir="$1"
|
||||
|
||||
print_status "Deploying Kubernetes resources..."
|
||||
|
||||
# Deploy in specific order for dependencies
|
||||
local deployment_order=(
|
||||
"couchdb-secret.yaml"
|
||||
"couchdb-pvc.yaml"
|
||||
"couchdb-service.yaml"
|
||||
"couchdb-statefulset.yaml"
|
||||
"configmap.yaml"
|
||||
"frontend-deployment.yaml"
|
||||
"frontend-service.yaml"
|
||||
"ingress.yaml"
|
||||
"$K8S_DIR/network-policy.yaml"
|
||||
"$K8S_DIR/hpa.yaml"
|
||||
)
|
||||
|
||||
for resource in "${deployment_order[@]}"; do
|
||||
if [[ "$resource" == *.yaml ]]; then
|
||||
# Check if it's a template-generated file
|
||||
if [[ -f "$resource_dir/$(basename "$resource")" ]]; then
|
||||
apply_k8s_resource "$resource_dir/$(basename "$resource")"
|
||||
else
|
||||
# Apply directly from k8s directory
|
||||
apply_k8s_resource "$resource"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Function to run database seeding job
|
||||
run_db_seed() {
|
||||
print_status "Running database seed job..."
|
||||
|
||||
# Apply the db-seed-job (which uses environment variables from secret)
|
||||
if kubectl apply -f "$K8S_DIR/db-seed-job.yaml"; then
|
||||
print_success "Database seed job submitted"
|
||||
|
||||
# Wait for job completion
|
||||
print_status "Waiting for database seed job to complete..."
|
||||
if kubectl wait --for=condition=complete --timeout=300s job/db-seed-job; then
|
||||
print_success "Database seeding completed successfully"
|
||||
else
|
||||
print_warning "Database seed job may have failed. Check logs:"
|
||||
echo "kubectl logs job/db-seed-job"
|
||||
fi
|
||||
else
|
||||
print_error "Failed to apply database seed job"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to display deployment status
|
||||
show_status() {
|
||||
print_status "Deployment Status:"
|
||||
echo
|
||||
|
||||
print_status "Pods:"
|
||||
kubectl get pods -l app="${APP_NAME:-rxminder}"
|
||||
echo
|
||||
|
||||
print_status "Services:"
|
||||
kubectl get services -l app="${APP_NAME:-rxminder}"
|
||||
echo
|
||||
|
||||
print_status "Ingress:"
|
||||
kubectl get ingress
|
||||
echo
|
||||
|
||||
if [[ -n "${INGRESS_HOST:-}" ]]; then
|
||||
print_success "Application should be available at: http://${INGRESS_HOST}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to cleanup temporary files
|
||||
cleanup() {
|
||||
if [[ -n "${temp_dir:-}" && -d "$temp_dir" ]]; then
|
||||
rm -rf "$temp_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main deployment function
|
||||
main() {
|
||||
local command="${1:-deploy}"
|
||||
|
||||
case "$command" in
|
||||
"deploy"|"apply")
|
||||
print_status "🚀 Starting RxMinder Kubernetes deployment..."
|
||||
echo
|
||||
|
||||
# Set default values for required variables
|
||||
export APP_NAME="${APP_NAME:-rxminder}"
|
||||
export DOCKER_IMAGE="${DOCKER_IMAGE:-gitea-http.taildb3494.ts.net/will/meds:latest}"
|
||||
export COUCHDB_USER="${COUCHDB_USER:-admin}"
|
||||
export COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}"
|
||||
export INGRESS_HOST="${INGRESS_HOST:-rxminder.local}"
|
||||
export STORAGE_CLASS="${STORAGE_CLASS:-longhorn}"
|
||||
export STORAGE_SIZE="${STORAGE_SIZE:-5Gi}"
|
||||
|
||||
load_env
|
||||
validate_env
|
||||
|
||||
# Process templates
|
||||
temp_dir=$(process_templates)
|
||||
trap cleanup EXIT
|
||||
|
||||
# Deploy resources
|
||||
deploy_resources "$temp_dir"
|
||||
|
||||
# Run database seeding
|
||||
run_db_seed
|
||||
|
||||
# Show status
|
||||
echo
|
||||
show_status
|
||||
|
||||
print_success "🎉 RxMinder deployment completed!"
|
||||
;;
|
||||
|
||||
"status")
|
||||
load_env
|
||||
show_status
|
||||
;;
|
||||
|
||||
"delete"|"cleanup")
|
||||
print_status "🗑️ Cleaning up RxMinder deployment..."
|
||||
kubectl delete all,pvc,secret,configmap,ingress -l app="${APP_NAME:-rxminder}" || true
|
||||
kubectl delete job db-seed-job || true
|
||||
print_success "Cleanup completed"
|
||||
;;
|
||||
|
||||
"help"|"-h"|"--help")
|
||||
echo "RxMinder Kubernetes Deployment Script"
|
||||
echo
|
||||
echo "Usage: $0 [command]"
|
||||
echo
|
||||
echo "Commands:"
|
||||
echo " deploy Deploy RxMinder to Kubernetes (default)"
|
||||
echo " status Show deployment status"
|
||||
echo " delete Delete all RxMinder resources"
|
||||
echo " help Show this help message"
|
||||
echo
|
||||
echo "Environment variables (set in .env):"
|
||||
echo " APP_NAME Application name (default: rxminder)"
|
||||
echo " DOCKER_IMAGE Container image to deploy"
|
||||
echo " COUCHDB_USER Database username (default: admin)"
|
||||
echo " COUCHDB_PASSWORD Database password (required)"
|
||||
echo " INGRESS_HOST Ingress hostname (required)"
|
||||
echo " STORAGE_CLASS Storage class for PVCs (default: longhorn)"
|
||||
echo " STORAGE_SIZE Storage size for database (default: 5Gi)"
|
||||
;;
|
||||
|
||||
*)
|
||||
print_error "Unknown command: $command"
|
||||
echo "Use '$0 help' for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check if kubectl is available
|
||||
if ! command -v kubectl >/dev/null 2>&1; then
|
||||
print_error "kubectl not found. Please install kubectl and configure it to connect to your cluster."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if envsubst is available
|
||||
if ! command -v envsubst >/dev/null 2>&1; then
|
||||
print_error "envsubst not found. Please install the gettext package:"
|
||||
echo " Ubuntu/Debian: sudo apt-get install gettext"
|
||||
echo " macOS: brew install gettext"
|
||||
echo " RHEL/CentOS: sudo yum install gettext"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Production database seeder script
|
||||
// This script seeds the production CouchDB with default admin user
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const projectDir = resolve(__dirname, '..');
|
||||
|
||||
console.log('🌱 Starting production database seeding...');
|
||||
|
||||
// Load environment variables from .env file if it exists
|
||||
try {
|
||||
const envFile = resolve(projectDir, '.env');
|
||||
const envContent = readFileSync(envFile, 'utf8');
|
||||
|
||||
console.log('📄 Loading environment variables from .env file...');
|
||||
|
||||
envContent.split('\n').forEach(line => {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {
|
||||
const [key, ...valueParts] = trimmed.split('=');
|
||||
const value = valueParts.join('=');
|
||||
if (!process.env[key]) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(
|
||||
'ℹ️ No .env file found, using environment variables or defaults'
|
||||
);
|
||||
}
|
||||
|
||||
// Use environment variables with fallbacks
|
||||
const COUCHDB_URL =
|
||||
process.env.VITE_COUCHDB_URL ||
|
||||
process.env.COUCHDB_URL ||
|
||||
'http://localhost:5984';
|
||||
const COUCHDB_USER =
|
||||
process.env.VITE_COUCHDB_USER || process.env.COUCHDB_USER || 'admin';
|
||||
const COUCHDB_PASSWORD =
|
||||
process.env.VITE_COUCHDB_PASSWORD ||
|
||||
process.env.COUCHDB_PASSWORD ||
|
||||
'change-this-secure-password';
|
||||
|
||||
// Set environment variables for the seeder to use
|
||||
process.env.VITE_COUCHDB_URL = COUCHDB_URL;
|
||||
process.env.VITE_COUCHDB_USER = COUCHDB_USER;
|
||||
process.env.VITE_COUCHDB_PASSWORD = COUCHDB_PASSWORD;
|
||||
|
||||
console.log('🔗 CouchDB Configuration:');
|
||||
console.log(` URL: ${COUCHDB_URL}`);
|
||||
console.log(` User: ${COUCHDB_USER}`);
|
||||
console.log(` Password: ${'*'.repeat(COUCHDB_PASSWORD.length)}`);
|
||||
|
||||
// Validate required environment variables
|
||||
if (!COUCHDB_URL || !COUCHDB_USER || !COUCHDB_PASSWORD) {
|
||||
console.error('❌ Missing required environment variables:');
|
||||
console.error(' VITE_COUCHDB_URL or COUCHDB_URL');
|
||||
console.error(' VITE_COUCHDB_USER or COUCHDB_USER');
|
||||
console.error(' VITE_COUCHDB_PASSWORD or COUCHDB_PASSWORD');
|
||||
console.error('');
|
||||
console.error('💡 Set these in your .env file or as environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function seedDatabase() {
|
||||
try {
|
||||
// Import the seeder (this will use the production CouchDB due to env vars)
|
||||
const { DatabaseSeeder } = await import('../services/database.seeder.ts');
|
||||
|
||||
// Wait a bit for databases to be initialized
|
||||
console.log('⏳ Waiting for databases to initialize...');
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
const seeder = new DatabaseSeeder();
|
||||
|
||||
console.log('📊 Seeding admin user...');
|
||||
await seeder.seedDefaultAdmin();
|
||||
|
||||
console.log('🎉 Production database seeding completed successfully!');
|
||||
console.log('🔐 You can now login with:');
|
||||
console.log(' Email: admin@localhost');
|
||||
console.log(' Password: change-this-secure-password');
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Seeding failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
seedDatabase();
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🎭 Playwright E2E Test Setup Script
|
||||
|
||||
echo "🎭 Setting up Playwright for E2E testing..."
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [[ ! -f "package.json" ]]; then
|
||||
echo "❌ Error: Must be run from project root directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install Playwright if not already installed
|
||||
echo "📦 Installing Playwright..."
|
||||
if command -v bun &> /dev/null; then
|
||||
bun add -D @playwright/test
|
||||
else
|
||||
npm install -D @playwright/test
|
||||
fi
|
||||
|
||||
# Install browser binaries
|
||||
echo "🌐 Installing browser binaries..."
|
||||
npx playwright install
|
||||
|
||||
# Install system dependencies (Linux)
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
echo "🐧 Installing system dependencies for Linux..."
|
||||
npx playwright install-deps
|
||||
fi
|
||||
|
||||
# Create .gitignore entries for Playwright
|
||||
echo "📝 Updating .gitignore for Playwright artifacts..."
|
||||
if ! grep -q "test-results" .gitignore; then
|
||||
echo "" >> .gitignore
|
||||
echo "# Playwright artifacts" >> .gitignore
|
||||
echo "test-results/" >> .gitignore
|
||||
echo "playwright-report/" >> .gitignore
|
||||
echo "playwright/.cache/" >> .gitignore
|
||||
fi
|
||||
|
||||
# Verify installation
|
||||
echo "✅ Verifying Playwright installation..."
|
||||
npx playwright --version
|
||||
|
||||
echo ""
|
||||
echo "🎉 Playwright setup complete!"
|
||||
echo ""
|
||||
echo "📋 Quick start commands:"
|
||||
echo " bun run test:e2e # Run all E2E tests"
|
||||
echo " bun run test:e2e:ui # Run tests in UI mode"
|
||||
echo " bun run test:e2e:debug # Debug tests"
|
||||
echo " bun run test:e2e:report # View test report"
|
||||
echo ""
|
||||
echo "📚 Documentation: tests/e2e/README.md"
|
||||
echo "⚙️ Configuration: playwright.config.ts"
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
# Run lint-staged for file-specific checks
|
||||
bunx lint-staged
|
||||
|
||||
# Run type checking (doesn't need file filtering)
|
||||
bun run type-check
|
||||
|
||||
# Check for large files (similar to pre-commit check-added-large-files)
|
||||
git diff --cached --name-only | while IFS= read -r file; do
|
||||
if [ -f "$file" ]; then
|
||||
size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
|
||||
if [ "$size" -gt 500000 ]; then # 500KB limit
|
||||
echo "Error: Large file detected: $file ($(echo $size | awk '{print int($1/1024)}')KB)"
|
||||
echo "Consider using Git LFS for large files."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for merge conflict markers
|
||||
if git diff --cached | grep -E "^[<>=]{7}" >/dev/null; then
|
||||
echo "Error: Merge conflict markers detected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for private keys (basic check)
|
||||
if git diff --cached --name-only | xargs grep -l "BEGIN.*PRIVATE KEY" 2>/dev/null; then
|
||||
echo "Error: Private key detected in staged files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Pre-commit checks passed!"
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}Setting up NodeJS-native pre-commit hooks and code formatters...${NC}"
|
||||
|
||||
# Check if we're in a git repository
|
||||
if [ ! -d ".git" ]; then
|
||||
echo -e "${RED}Error: Not a git repository. Please run this script from the project root.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install dependencies
|
||||
echo -e "${YELLOW}Installing dependencies...${NC}"
|
||||
if command -v bun &> /dev/null; then
|
||||
bun install
|
||||
elif command -v npm &> /dev/null; then
|
||||
npm install
|
||||
else
|
||||
echo -e "${RED}Error: Neither bun nor npm found. Please install one of them first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Initialize Husky (NodeJS-native git hooks)
|
||||
echo -e "${YELLOW}Setting up Husky git hooks...${NC}"
|
||||
if command -v bun &> /dev/null; then
|
||||
bunx husky init
|
||||
elif command -v npm &> /dev/null; then
|
||||
npx husky init
|
||||
fi
|
||||
|
||||
# Make pre-commit hook executable
|
||||
chmod +x .husky/pre-commit
|
||||
|
||||
# Run initial formatting
|
||||
echo -e "${YELLOW}Running initial code formatting...${NC}"
|
||||
if command -v bun &> /dev/null; then
|
||||
bun run format
|
||||
elif command -v npm &> /dev/null; then
|
||||
npm run format
|
||||
fi
|
||||
|
||||
# Run initial linting
|
||||
echo -e "${YELLOW}Running initial linting...${NC}"
|
||||
if command -v bun &> /dev/null; then
|
||||
bun run lint:fix
|
||||
elif command -v npm &> /dev/null; then
|
||||
npm run lint:fix
|
||||
fi
|
||||
|
||||
# Run initial markdown linting
|
||||
echo -e "${YELLOW}Running initial markdown linting...${NC}"
|
||||
if command -v bun &> /dev/null; then
|
||||
bun run lint:markdown:fix || echo "Markdown linting completed with warnings"
|
||||
elif command -v npm &> /dev/null; then
|
||||
npm run lint:markdown:fix || echo "Markdown linting completed with warnings"
|
||||
fi
|
||||
|
||||
# Fix EditorConfig issues
|
||||
echo -e "${YELLOW}Fixing EditorConfig issues...${NC}"
|
||||
if command -v bun &> /dev/null; then
|
||||
bun run fix:editorconfig || echo "EditorConfig fixes completed"
|
||||
elif command -v npm &> /dev/null; then
|
||||
npm run fix:editorconfig || echo "EditorConfig fixes completed"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ NodeJS-native pre-commit hooks and code formatters have been set up successfully!${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}What was configured:${NC}"
|
||||
echo " ✓ Husky git hooks (.husky/pre-commit)"
|
||||
echo " ✓ Prettier code formatter (.prettierrc)"
|
||||
echo " ✓ ESLint configuration (eslint.config.cjs)"
|
||||
echo " ✓ EditorConfig (.editorconfig)"
|
||||
echo " ✓ Markdownlint configuration (.markdownlint.json)"
|
||||
echo " ✓ Secretlint configuration (.secretlintrc.json)"
|
||||
echo " ✓ Lint-staged for efficient pre-commit formatting"
|
||||
echo " ✓ Dockerfilelint for Docker file validation"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Available commands:${NC}"
|
||||
echo " • bun run format - Format all files with Prettier"
|
||||
echo " • bun run lint - Run ESLint on TypeScript/JavaScript files"
|
||||
echo " • bun run lint:fix - Run ESLint with auto-fix"
|
||||
echo " • bun run lint:markdown - Check Markdown files"
|
||||
echo " • bun run lint:markdown:fix - Fix Markdown files"
|
||||
echo " • bun run lint:docker - Check Dockerfile"
|
||||
echo " • bun run check:secrets - Check for secrets in files"
|
||||
echo " • bun run check:editorconfig - Check EditorConfig compliance"
|
||||
echo " • bun run fix:editorconfig - Fix EditorConfig issues"
|
||||
echo " • bun run type-check - Run TypeScript type checking"
|
||||
echo ""
|
||||
echo -e "${YELLOW}What happens on commit:${NC}"
|
||||
echo " 1. Lint-staged runs on changed files:"
|
||||
echo " • ESLint auto-fix + Prettier formatting for JS/TS files"
|
||||
echo " • Prettier formatting for JSON/YAML/MD/CSS files"
|
||||
echo " • Markdownlint auto-fix for Markdown files"
|
||||
echo " • Dockerfilelint for Dockerfile"
|
||||
echo " • EditorConfig fixes for all files"
|
||||
echo " 2. TypeScript type checking on entire project"
|
||||
echo " 3. Large file detection (>500KB)"
|
||||
echo " 4. Merge conflict marker detection"
|
||||
echo " 5. Basic private key detection"
|
||||
echo ""
|
||||
echo -e "${GREEN}All commits will now be automatically checked and formatted! 🎉${NC}"
|
||||
echo -e "${GREEN}This setup is 100% NodeJS-native - no Python dependencies required! 🚀${NC}"
|
||||
Executable
+225
@@ -0,0 +1,225 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🧪 Deployment Validation Script
|
||||
# Validates complete deployment with all environment variables and health checks
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting deploymif docker compose -f docker/docker-compose.yaml -p rxminder-validation ps | grep -q "Up"; then
|
||||
print_success "Docker Compose setup completed successfully!"
|
||||
else
|
||||
print_error "Docker Compose services failed to start"
|
||||
docker compose -f docker/docker-compose.yaml -p rxminder-validation logsalidation..."
|
||||
|
||||
# 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..."
|
||||
docker stop rxminder-validation-test 2>/dev/null || true
|
||||
docker rm rxminder-validation-test 2>/dev/null || true
|
||||
docker compose -f docker/docker-compose.yaml -p rxminder-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..."
|
||||
./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 "rxminder-builder"; then
|
||||
print_status "Creating new buildx builder instance..."
|
||||
docker buildx create --name rxminder-builder --driver docker-container --bootstrap
|
||||
fi
|
||||
|
||||
# Use the builder
|
||||
docker buildx use rxminder-builder
|
||||
|
||||
print_status "4. Building multi-platform Docker image with buildx..."
|
||||
|
||||
# Build the image with buildx for multiple platforms
|
||||
docker buildx build --no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--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 rxminder-validation \
|
||||
--load \
|
||||
.
|
||||
|
||||
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 rxminder-validation-test \
|
||||
rxminder-validation
|
||||
|
||||
# Wait for container to start
|
||||
sleep 5
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q rxminder-validation-test; then
|
||||
print_error "Container failed to start"
|
||||
docker logs rxminder-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 rxminder-validation up -d --build
|
||||
|
||||
# Wait for services to start
|
||||
sleep 10
|
||||
|
||||
# Check service health
|
||||
if docker compose -f docker/docker-compose.yaml -p meds-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 meds-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 rxminder-validation --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 rxminder-validation 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 rxminder-validation 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
|
||||
Executable
+221
@@ -0,0 +1,221 @@
|
||||
#!/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..."
|
||||
docker stop meds-validation-test 2>/dev/null || true
|
||||
docker rm meds-validation-test 2>/dev/null || true
|
||||
docker compose -f docker/docker-compose.yaml -p meds-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..."
|
||||
./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..."
|
||||
|
||||
# Build the image with buildx for multiple platforms
|
||||
docker buildx build --no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--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 meds-validation \
|
||||
--load \
|
||||
.
|
||||
|
||||
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 meds-validation-test \
|
||||
meds-validation
|
||||
|
||||
# Wait for container to start
|
||||
sleep 5
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q meds-validation-test; then
|
||||
print_error "Container failed to start"
|
||||
docker logs meds-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 meds-validation up -d --build
|
||||
|
||||
# Wait for services to start
|
||||
sleep 10
|
||||
|
||||
# Check service health
|
||||
if docker compose -f docker/docker-compose.yaml -p meds-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 meds-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 meds-validation --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 meds-validation 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 meds-validation 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
|
||||
Executable
+274
@@ -0,0 +1,274 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Environment validation script
|
||||
# Validates all .env files for consistency and completeness
|
||||
|
||||
set -e
|
||||
|
||||
print_header() {
|
||||
echo "🔍 Environment Configuration Validation"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
}
|
||||
|
||||
print_section() {
|
||||
echo ""
|
||||
echo "📋 $1"
|
||||
echo "$(printf '%*s' ${#1} '' | tr ' ' '-')"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo "✅ $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo "⚠️ $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo "❌ $1"
|
||||
}
|
||||
|
||||
# Required variables for each environment
|
||||
CORE_VARS=(
|
||||
"COUCHDB_USER"
|
||||
"COUCHDB_PASSWORD"
|
||||
"VITE_COUCHDB_URL"
|
||||
"VITE_COUCHDB_USER"
|
||||
"VITE_COUCHDB_PASSWORD"
|
||||
"APP_BASE_URL"
|
||||
"MAILGUN_API_KEY"
|
||||
"MAILGUN_DOMAIN"
|
||||
"MAILGUN_FROM_EMAIL"
|
||||
)
|
||||
|
||||
K8S_VARS=(
|
||||
"INGRESS_HOST"
|
||||
)
|
||||
|
||||
OPTIONAL_VARS=(
|
||||
"NODE_ENV"
|
||||
"VITE_GOOGLE_CLIENT_ID"
|
||||
"VITE_GITHUB_CLIENT_ID"
|
||||
)
|
||||
|
||||
validate_file() {
|
||||
local file="$1"
|
||||
local file_type="$2"
|
||||
|
||||
print_section "Validating $file ($file_type)"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
print_error "File not found: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local missing_vars=()
|
||||
local found_vars=()
|
||||
|
||||
# Check core variables
|
||||
for var in "${CORE_VARS[@]}"; do
|
||||
if grep -q "^${var}=" "$file" || grep -q "^#.*${var}=" "$file"; then
|
||||
found_vars+=("$var")
|
||||
else
|
||||
missing_vars+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
# Check K8s variables for relevant files
|
||||
if [[ "$file_type" != "template" ]]; then
|
||||
for var in "${K8S_VARS[@]}"; do
|
||||
if grep -q "^${var}=" "$file" || grep -q "^#.*${var}=" "$file"; then
|
||||
found_vars+=("$var")
|
||||
else
|
||||
missing_vars+=("$var")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Report results
|
||||
print_success "Found ${#found_vars[@]} variables"
|
||||
|
||||
if [[ ${#missing_vars[@]} -gt 0 ]]; then
|
||||
print_warning "Missing variables:"
|
||||
for var in "${missing_vars[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
fi
|
||||
|
||||
# Check for old VITE_MAILGUN variables
|
||||
if grep -q "VITE_MAILGUN" "$file"; then
|
||||
print_error "Found deprecated VITE_MAILGUN variables (should be MAILGUN_*)"
|
||||
fi
|
||||
|
||||
# Check variable format
|
||||
local malformed_vars=()
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^[A-Z_]+=.* ]]; then
|
||||
local var_name="${line%%=*}"
|
||||
if [[ ! "$var_name" =~ ^[A-Z_][A-Z0-9_]*$ ]]; then
|
||||
malformed_vars+=("$var_name")
|
||||
fi
|
||||
fi
|
||||
done < "$file"
|
||||
|
||||
if [[ ${#malformed_vars[@]} -gt 0 ]]; then
|
||||
print_warning "Malformed variable names:"
|
||||
for var in "${malformed_vars[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
validate_consistency() {
|
||||
print_section "Cross-file Consistency Check"
|
||||
|
||||
# Extract variable names from each file
|
||||
local example_vars=()
|
||||
local env_vars=()
|
||||
local prod_vars=()
|
||||
|
||||
if [[ -f ".env.example" ]]; then
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^[A-Z_]+=.* ]]; then
|
||||
example_vars+=("${line%%=*}")
|
||||
fi
|
||||
done < ".env.example"
|
||||
fi
|
||||
|
||||
if [[ -f ".env" ]]; then
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^[A-Z_]+=.* ]]; then
|
||||
env_vars+=("${line%%=*}")
|
||||
fi
|
||||
done < ".env"
|
||||
fi
|
||||
|
||||
if [[ -f ".env.production" ]]; then
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^[A-Z_]+=.* ]]; then
|
||||
prod_vars+=("${line%%=*}")
|
||||
fi
|
||||
done < ".env.production"
|
||||
fi
|
||||
|
||||
# Check if .env and .env.production have all variables from .env.example
|
||||
local missing_in_env=()
|
||||
local missing_in_prod=()
|
||||
|
||||
for var in "${example_vars[@]}"; do
|
||||
if [[ ! " ${env_vars[@]} " =~ " ${var} " ]]; then
|
||||
missing_in_env+=("$var")
|
||||
fi
|
||||
if [[ ! " ${prod_vars[@]} " =~ " ${var} " ]]; then
|
||||
missing_in_prod+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_in_env[@]} -eq 0 ]]; then
|
||||
print_success ".env has all variables from .env.example"
|
||||
else
|
||||
print_warning ".env missing variables from .env.example:"
|
||||
for var in "${missing_in_env[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ ${#missing_in_prod[@]} -eq 0 ]]; then
|
||||
print_success ".env.production has all variables from .env.example"
|
||||
else
|
||||
print_warning ".env.production missing variables from .env.example:"
|
||||
for var in "${missing_in_prod[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
validate_k8s_template() {
|
||||
print_section "Kubernetes Template Validation"
|
||||
|
||||
local template_file="k8s/ingress.yaml.template"
|
||||
|
||||
if [[ ! -f "$template_file" ]]; then
|
||||
print_error "Template file not found: $template_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check for template variables
|
||||
local template_vars=()
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ \$\{([A-Z_][A-Z0-9_]*)\} ]]; then
|
||||
local var_name="${BASH_REMATCH[1]}"
|
||||
if [[ ! " ${template_vars[@]} " =~ " ${var_name} " ]]; then
|
||||
template_vars+=("$var_name")
|
||||
fi
|
||||
fi
|
||||
done < "$template_file"
|
||||
|
||||
print_success "Found ${#template_vars[@]} template variables:"
|
||||
for var in "${template_vars[@]}"; do
|
||||
echo " - \${$var}"
|
||||
done
|
||||
|
||||
# Check if template variables are defined in env files
|
||||
for var in "${template_vars[@]}"; do
|
||||
local found_in_files=()
|
||||
|
||||
if grep -q "^${var}=" ".env.example" 2>/dev/null; then
|
||||
found_in_files+=(".env.example")
|
||||
fi
|
||||
if grep -q "^${var}=" ".env" 2>/dev/null; then
|
||||
found_in_files+=(".env")
|
||||
fi
|
||||
if grep -q "^${var}=" ".env.production" 2>/dev/null; then
|
||||
found_in_files+=(".env.production")
|
||||
fi
|
||||
|
||||
if [[ ${#found_in_files[@]} -gt 0 ]]; then
|
||||
print_success "$var defined in: ${found_in_files[*]}"
|
||||
else
|
||||
print_error "$var not defined in any environment file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
main() {
|
||||
print_header
|
||||
|
||||
# Validate individual files
|
||||
if [[ -f ".env.example" ]]; then
|
||||
validate_file ".env.example" "template"
|
||||
fi
|
||||
|
||||
if [[ -f ".env" ]]; then
|
||||
validate_file ".env" "development"
|
||||
fi
|
||||
|
||||
if [[ -f ".env.production" ]]; then
|
||||
validate_file ".env.production" "production"
|
||||
fi
|
||||
|
||||
# Cross-file validation
|
||||
validate_consistency
|
||||
|
||||
# Kubernetes template validation
|
||||
validate_k8s_template
|
||||
|
||||
print_section "Summary"
|
||||
print_success "Environment validation complete!"
|
||||
echo ""
|
||||
echo "💡 Tips:"
|
||||
echo " - Copy .env.example to .env for local development"
|
||||
echo " - Use .env.production for production deployments"
|
||||
echo " - Run './deploy-k8s.sh --dry-run' to test Kubernetes deployment"
|
||||
echo " - All Mailgun variables use server-side naming (no VITE_ prefix)"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user