| #!/bin/bash |
|
|
| |
| |
|
|
| set -e |
|
|
| |
| RED='\033[0;31m' |
| GREEN='\033[0;32m' |
| YELLOW='\033[1;33m' |
| BLUE='\033[0;34m' |
| PURPLE='\033[0;35m' |
| NC='\033[0m' |
|
|
| print_header() { |
| echo -e "${PURPLE}================================${NC}" |
| echo -e "${PURPLE}$1${NC}" |
| echo -e "${PURPLE}================================${NC}" |
| } |
|
|
| 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" |
| } |
|
|
| show_usage() { |
| echo "Usage: $0 [COMMAND]" |
| echo "" |
| echo "Commands:" |
| echo " build Build production Docker images" |
| echo " deploy Deploy to production environment" |
| echo " update Update production deployment" |
| echo " rollback Rollback to previous version" |
| echo " stop Stop production environment" |
| echo " logs Show logs from production services" |
| echo " status Show status of production services" |
| echo " health Check health of production services" |
| echo " backup Create backup of production data" |
| echo " restore Restore from backup" |
| echo " scale Scale services up/down" |
| echo " clean Clean up old images and containers" |
| echo "" |
| echo "Examples:" |
| echo " $0 build # Build production images" |
| echo " $0 deploy # Deploy to production" |
| echo " $0 scale app=3 # Scale app service to 3 replicas" |
| echo " $0 logs app # Show logs from app service" |
| } |
|
|
| check_prerequisites() { |
| print_status "Checking production prerequisites..." |
| |
| if ! command -v docker &> /dev/null; then |
| print_error "Docker is not installed." |
| exit 1 |
| fi |
| |
| if ! command -v docker-compose &> /dev/null; then |
| print_error "Docker Compose is not installed." |
| exit 1 |
| fi |
| |
| |
| if [[ ! -f .env.production ]]; then |
| print_error ".env.production file not found." |
| print_status "Please create .env.production with production configuration" |
| exit 1 |
| fi |
| |
| |
| if [[ ! -f "${SSL_CERT_PATH:-./ssl/cert.pem}" ]]; then |
| print_warning "SSL certificate not found. HTTPS will not work." |
| fi |
| |
| |
| if [[ ! -f "${GOOGLE_APPLICATION_CREDENTIALS_FILE:-./credentials.json}" ]]; then |
| print_warning "Google Cloud credentials not found." |
| fi |
| |
| print_success "Prerequisites check completed" |
| } |
|
|
| build_production_images() { |
| print_header "Building Production Docker Images" |
| |
| |
| export BUILD_TARGET=production |
| export VERSION=${VERSION:-$(date +%Y%m%d-%H%M%S)} |
| |
| |
| docker-compose -f docker-compose.prod.yml build --no-cache |
| |
| |
| docker tag misinformation-heatmap:latest misinformation-heatmap:$VERSION |
| |
| print_success "Production images built with version: $VERSION" |
| } |
|
|
| deploy_production() { |
| print_header "Deploying to Production Environment" |
| |
| |
| set -a |
| source .env.production |
| set +a |
| |
| export BUILD_TARGET=production |
| export COMPOSE_PROFILES=production,monitoring |
| |
| |
| print_status "Running pre-deployment checks..." |
| |
| |
| required_vars=("GOOGLE_CLOUD_PROJECT" "API_KEYS" "GRAFANA_PASSWORD") |
| for var in "${required_vars[@]}"; do |
| if [[ -z "${!var}" ]]; then |
| print_error "Required environment variable $var is not set" |
| exit 1 |
| fi |
| done |
| |
| |
| mkdir -p data logs ssl |
| |
| |
| print_status "Starting production services..." |
| docker-compose -f docker-compose.prod.yml up -d |
| |
| |
| print_status "Waiting for services to be healthy..." |
| sleep 30 |
| |
| |
| if check_production_health; then |
| print_success "Production deployment completed successfully" |
| print_status "Services available at:" |
| print_status " - Application: https://localhost" |
| print_status " - Monitoring: http://localhost:3001" |
| print_status " - Metrics: http://localhost:9090" |
| else |
| print_error "Production deployment failed health check" |
| print_status "Rolling back..." |
| rollback_deployment |
| exit 1 |
| fi |
| } |
|
|
| update_deployment() { |
| print_header "Updating Production Deployment" |
| |
| |
| build_production_images |
| |
| |
| print_status "Performing rolling update..." |
| |
| |
| docker-compose -f docker-compose.prod.yml up -d --no-deps app |
| |
| |
| sleep 20 |
| if check_production_health; then |
| print_success "Update completed successfully" |
| else |
| print_error "Update failed, rolling back..." |
| rollback_deployment |
| exit 1 |
| fi |
| } |
|
|
| rollback_deployment() { |
| print_header "Rolling Back Deployment" |
| |
| |
| local previous_version=$(docker images misinformation-heatmap --format "table {{.Tag}}" | grep -v latest | head -n 2 | tail -n 1) |
| |
| if [[ -n "$previous_version" ]]; then |
| print_status "Rolling back to version: $previous_version" |
| |
| |
| export VERSION=$previous_version |
| docker-compose -f docker-compose.prod.yml up -d --no-deps app |
| |
| sleep 20 |
| if check_production_health; then |
| print_success "Rollback completed successfully" |
| else |
| print_error "Rollback failed" |
| exit 1 |
| fi |
| else |
| print_error "No previous version found for rollback" |
| exit 1 |
| fi |
| } |
|
|
| stop_production() { |
| print_header "Stopping Production Environment" |
| |
| print_warning "This will stop the production environment. Are you sure? (y/N)" |
| read -r confirmation |
| |
| if [[ "$confirmation" =~ ^[Yy]$ ]]; then |
| docker-compose -f docker-compose.prod.yml down |
| print_success "Production environment stopped" |
| else |
| print_status "Operation cancelled" |
| fi |
| } |
|
|
| show_production_logs() { |
| local service=${1:-} |
| |
| if [[ -n "$service" ]]; then |
| print_header "Showing Production Logs for $service" |
| docker-compose -f docker-compose.prod.yml logs -f --tail=100 "$service" |
| else |
| print_header "Showing Production Logs for All Services" |
| docker-compose -f docker-compose.prod.yml logs -f --tail=100 |
| fi |
| } |
|
|
| show_production_status() { |
| print_header "Production Service Status" |
| |
| docker-compose -f docker-compose.prod.yml ps |
| |
| echo "" |
| print_status "Resource usage:" |
| docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}" |
| |
| echo "" |
| print_status "Disk usage:" |
| df -h |
| } |
|
|
| check_production_health() { |
| print_status "Checking production health..." |
| |
| local health_ok=true |
| |
| |
| if curl -k -s https://localhost/health > /dev/null; then |
| print_success "Application is healthy" |
| else |
| print_error "Application health check failed" |
| health_ok=false |
| fi |
| |
| |
| if docker-compose -f docker-compose.prod.yml exec -T redis redis-cli ping | grep -q PONG; then |
| print_success "Redis is healthy" |
| else |
| print_error "Redis health check failed" |
| health_ok=false |
| fi |
| |
| |
| if curl -s http://localhost:9090/-/healthy > /dev/null; then |
| print_success "Prometheus is healthy" |
| else |
| print_warning "Prometheus health check failed" |
| fi |
| |
| |
| if curl -s http://localhost:3001/api/health > /dev/null; then |
| print_success "Grafana is healthy" |
| else |
| print_warning "Grafana health check failed" |
| fi |
| |
| $health_ok |
| } |
|
|
| backup_production_data() { |
| print_header "Creating Production Data Backup" |
| |
| local backup_date=$(date +%Y%m%d_%H%M%S) |
| local backup_dir="backups/backup_$backup_date" |
| |
| mkdir -p "$backup_dir" |
| |
| |
| print_status "Backing up application data..." |
| docker run --rm -v misinformation-heatmap_app-data:/data -v $(pwd)/$backup_dir:/backup alpine tar czf /backup/app-data.tar.gz -C /data . |
| |
| |
| print_status "Backing up Redis data..." |
| docker-compose -f docker-compose.prod.yml exec -T redis redis-cli BGSAVE |
| sleep 5 |
| docker run --rm -v misinformation-heatmap_redis-prod-data:/data -v $(pwd)/$backup_dir:/backup alpine tar czf /backup/redis-data.tar.gz -C /data . |
| |
| |
| print_status "Backing up Grafana data..." |
| docker run --rm -v misinformation-heatmap_grafana-prod-data:/data -v $(pwd)/$backup_dir:/backup alpine tar czf /backup/grafana-data.tar.gz -C /data . |
| |
| |
| cat > "$backup_dir/manifest.txt" << EOF |
| Backup created: $(date) |
| Version: ${VERSION:-unknown} |
| Services backed up: |
| - Application data |
| - Redis data |
| - Grafana data |
| EOF |
| |
| print_success "Backup created: $backup_dir" |
| } |
|
|
| restore_production_data() { |
| local backup_dir=${1:-} |
| |
| if [[ -z "$backup_dir" ]]; then |
| print_error "Please specify backup directory" |
| print_status "Available backups:" |
| ls -la backups/ 2>/dev/null || print_status "No backups found" |
| exit 1 |
| fi |
| |
| if [[ ! -d "$backup_dir" ]]; then |
| print_error "Backup directory not found: $backup_dir" |
| exit 1 |
| fi |
| |
| print_header "Restoring Production Data from $backup_dir" |
| |
| print_warning "This will overwrite current production data. Are you sure? (y/N)" |
| read -r confirmation |
| |
| if [[ "$confirmation" =~ ^[Yy]$ ]]; then |
| |
| print_status "Stopping services..." |
| docker-compose -f docker-compose.prod.yml stop app redis grafana |
| |
| |
| print_status "Restoring application data..." |
| docker run --rm -v misinformation-heatmap_app-data:/data -v $(pwd)/$backup_dir:/backup alpine tar xzf /backup/app-data.tar.gz -C /data |
| |
| print_status "Restoring Redis data..." |
| docker run --rm -v misinformation-heatmap_redis-prod-data:/data -v $(pwd)/$backup_dir:/backup alpine tar xzf /backup/redis-data.tar.gz -C /data |
| |
| print_status "Restoring Grafana data..." |
| docker run --rm -v misinformation-heatmap_grafana-prod-data:/data -v $(pwd)/$backup_dir:/backup alpine tar xzf /backup/grafana-data.tar.gz -C /data |
| |
| |
| print_status "Restarting services..." |
| docker-compose -f docker-compose.prod.yml start app redis grafana |
| |
| print_success "Data restoration completed" |
| else |
| print_status "Operation cancelled" |
| fi |
| } |
|
|
| scale_services() { |
| local scale_config=${1:-} |
| |
| if [[ -z "$scale_config" ]]; then |
| print_error "Please specify scaling configuration (e.g., app=3)" |
| exit 1 |
| fi |
| |
| print_header "Scaling Production Services" |
| |
| print_status "Scaling: $scale_config" |
| docker-compose -f docker-compose.prod.yml up -d --scale "$scale_config" |
| |
| print_success "Scaling completed" |
| show_production_status |
| } |
|
|
| clean_production_resources() { |
| print_header "Cleaning Production Resources" |
| |
| print_warning "This will remove unused images and containers. Continue? (y/N)" |
| read -r confirmation |
| |
| if [[ "$confirmation" =~ ^[Yy]$ ]]; then |
| print_status "Removing unused images..." |
| docker image prune -f |
| |
| print_status "Removing old versions (keeping last 3)..." |
| docker images misinformation-heatmap --format "table {{.Tag}}" | grep -v latest | tail -n +4 | xargs -r docker rmi misinformation-heatmap: 2>/dev/null || true |
| |
| print_status "Removing unused volumes..." |
| docker volume prune -f |
| |
| print_success "Cleanup completed" |
| else |
| print_status "Operation cancelled" |
| fi |
| } |
|
|
| main() { |
| case "${1:-}" in |
| build) |
| check_prerequisites |
| build_production_images |
| ;; |
| deploy) |
| check_prerequisites |
| deploy_production |
| ;; |
| update) |
| check_prerequisites |
| update_deployment |
| ;; |
| rollback) |
| rollback_deployment |
| ;; |
| stop) |
| stop_production |
| ;; |
| logs) |
| show_production_logs "${2:-}" |
| ;; |
| status) |
| show_production_status |
| ;; |
| health) |
| check_production_health |
| ;; |
| backup) |
| backup_production_data |
| ;; |
| restore) |
| restore_production_data "${2:-}" |
| ;; |
| scale) |
| scale_services "${2:-}" |
| ;; |
| clean) |
| clean_production_resources |
| ;; |
| -h|--help|help) |
| show_usage |
| ;; |
| "") |
| print_error "No command specified" |
| show_usage |
| exit 1 |
| ;; |
| *) |
| print_error "Unknown command: $1" |
| show_usage |
| exit 1 |
| ;; |
| esac |
| } |
|
|
| main "$@" |