Raj718's picture
feat: Complete Task 9 - Security Hardening & Production Deployment with 67 tests passing
16cf46c
#!/bin/bash
# LeaseGuard Production Deployment Script
# Implements secure deployment practices with health checks and rollback capabilities
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
APP_NAME="leaseguard"
DEPLOYMENT_ENV=${1:-production}
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/${APP_NAME}"
LOG_FILE="/var/log/${APP_NAME}/deploy_${TIMESTAMP}.log"
# Logging function
log() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"
exit 1
}
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE"
}
warning() {
echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE"
}
# Pre-deployment checks
pre_deployment_checks() {
log "Starting pre-deployment security checks..."
# Check if running as root
if [[ $EUID -eq 0 ]]; then
error "This script should not be run as root"
fi
# Check required environment variables
required_vars=(
"REDIS_PASSWORD"
"GEMINI_API_KEY"
"SUPABASE_URL"
"SUPABASE_ANON_KEY"
"SESSION_SECRET"
"ALLOWED_ORIGINS"
)
for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
error "Required environment variable $var is not set"
fi
done
# Check Docker and Docker Compose
if ! command -v docker &> /dev/null; then
error "Docker is not installed"
fi
if ! command -v docker-compose &> /dev/null; then
error "Docker Compose is not installed"
fi
# Check SSL certificates
if [[ ! -f "./ssl/cert.pem" ]] || [[ ! -f "./ssl/key.pem" ]]; then
error "SSL certificates not found in ./ssl/ directory"
fi
# Check disk space
available_space=$(df / | awk 'NR==2 {print $4}')
if [[ $available_space -lt 1048576 ]]; then # Less than 1GB
warning "Low disk space available: ${available_space}KB"
fi
success "Pre-deployment checks passed"
}
# Security scan
security_scan() {
log "Running security scan..."
# Check for known vulnerabilities in dependencies
if command -v npm &> /dev/null; then
log "Running npm audit..."
if npm audit --audit-level=high; then
success "No high-severity vulnerabilities found"
else
warning "High-severity vulnerabilities found - review before deployment"
fi
fi
# Check Docker image for vulnerabilities (if trivy is available)
if command -v trivy &> /dev/null; then
log "Running Trivy security scan..."
trivy image --severity HIGH,CRITICAL node:18-alpine || warning "Trivy scan found vulnerabilities"
fi
# Check for secrets in code
log "Checking for potential secrets in code..."
if grep -r "password\|secret\|key\|token" . --exclude-dir=node_modules --exclude-dir=.git | grep -v "process.env" | grep -v "REDIS_PASSWORD\|GEMINI_API_KEY\|SUPABASE_ANON_KEY\|SESSION_SECRET"; then
warning "Potential secrets found in code - review before deployment"
fi
}
# Backup current deployment
backup_current() {
log "Creating backup of current deployment..."
mkdir -p "$BACKUP_DIR"
# Backup Docker volumes
if docker volume ls | grep -q "${APP_NAME}_redis_data"; then
docker run --rm -v "${APP_NAME}_redis_data:/data" -v "$BACKUP_DIR:/backup" alpine tar czf "/backup/redis_backup_${TIMESTAMP}.tar.gz" -C /data .
success "Redis data backed up"
fi
# Backup environment file
if [[ -f ".env" ]]; then
cp .env "$BACKUP_DIR/env_backup_${TIMESTAMP}"
success "Environment file backed up"
fi
# Backup Docker Compose file
cp docker-compose.prod.yml "$BACKUP_DIR/compose_backup_${TIMESTAMP}.yml"
success "Docker Compose file backed up"
}
# Deploy application
deploy_application() {
log "Deploying LeaseGuard application..."
# Stop existing containers
log "Stopping existing containers..."
docker-compose -f docker-compose.prod.yml down --timeout 30 || warning "Failed to stop some containers"
# Pull latest images
log "Pulling latest images..."
docker-compose -f docker-compose.prod.yml pull
# Build and start services
log "Building and starting services..."
docker-compose -f docker-compose.prod.yml up -d --build
# Wait for services to be healthy
log "Waiting for services to be healthy..."
timeout=300 # 5 minutes
elapsed=0
while [[ $elapsed -lt $timeout ]]; do
if docker-compose -f docker-compose.prod.yml ps | grep -q "healthy"; then
success "All services are healthy"
break
fi
sleep 10
elapsed=$((elapsed + 10))
log "Waiting for healthy services... ($elapsed/$timeout seconds)"
done
if [[ $elapsed -ge $timeout ]]; then
error "Services failed to become healthy within timeout"
fi
}
# Health checks
health_checks() {
log "Running post-deployment health checks..."
# Check application health
local max_retries=10
local retry_count=0
while [[ $retry_count -lt $max_retries ]]; do
if curl -f -s http://localhost:3000/api/health > /dev/null; then
success "Application health check passed"
break
fi
retry_count=$((retry_count + 1))
log "Health check attempt $retry_count/$max_retries failed, retrying in 10 seconds..."
sleep 10
done
if [[ $retry_count -ge $max_retries ]]; then
error "Application health check failed after $max_retries attempts"
fi
# Check Redis connection
if docker exec leaseguard-redis redis-cli --raw incr ping > /dev/null 2>&1; then
success "Redis health check passed"
else
error "Redis health check failed"
fi
# Check SSL certificate
if openssl s_client -connect localhost:443 -servername localhost < /dev/null 2>/dev/null | grep -q "Verify return code: 0"; then
success "SSL certificate validation passed"
else
warning "SSL certificate validation failed"
fi
# Performance test
log "Running performance test..."
response_time=$(curl -w "%{time_total}" -o /dev/null -s http://localhost:3000/api/health)
if (( $(echo "$response_time < 2.0" | bc -l) )); then
success "Performance test passed: ${response_time}s response time"
else
warning "Performance test warning: ${response_time}s response time"
fi
}
# Rollback function
rollback() {
log "Rolling back deployment..."
# Stop current deployment
docker-compose -f docker-compose.prod.yml down
# Restore from backup
if [[ -f "$BACKUP_DIR/compose_backup_${TIMESTAMP}.yml" ]]; then
cp "$BACKUP_DIR/compose_backup_${TIMESTAMP}.yml" docker-compose.prod.yml
docker-compose -f docker-compose.prod.yml up -d
success "Rollback completed"
else
error "No backup found for rollback"
fi
}
# Cleanup old backups
cleanup_backups() {
log "Cleaning up old backups..."
# Keep only last 5 backups
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete 2>/dev/null || true
find "$BACKUP_DIR" -name "env_backup_*" -mtime +7 -delete 2>/dev/null || true
find "$BACKUP_DIR" -name "compose_backup_*" -mtime +7 -delete 2>/dev/null || true
success "Cleanup completed"
}
# Main deployment function
main() {
log "Starting LeaseGuard deployment to $DEPLOYMENT_ENV environment"
# Create log directory
mkdir -p "$(dirname "$LOG_FILE")"
# Set up error handling
trap 'error "Deployment failed at line $LINENO"' ERR
trap 'rollback' EXIT
# Run deployment steps
pre_deployment_checks
security_scan
backup_current
deploy_application
health_checks
# Remove rollback trap on success
trap - EXIT
cleanup_backups
success "Deployment completed successfully!"
log "Deployment log saved to: $LOG_FILE"
}
# Run main function
main "$@"