Files
michaelschiemer/scripts/production-deploy.sh
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

447 lines
12 KiB
Bash
Executable File

#!/bin/bash
# Production Deployment Script for Custom PHP Framework
# Comprehensive deployment automation with zero-downtime strategy
#
# Usage:
# ./scripts/production-deploy.sh [initial|update|rollback]
#
# Modes:
# initial - First-time production deployment
# update - Rolling update with zero downtime
# rollback - Rollback to previous version
set -euo pipefail
# Configuration
DEPLOY_MODE="${1:-update}"
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BACKUP_DIR="${PROJECT_ROOT}/../backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_PATH="${BACKUP_DIR}/backup_${TIMESTAMP}"
# Colors
GREEN="\e[32m"
YELLOW="\e[33m"
RED="\e[31m"
BLUE="\e[34m"
RESET="\e[0m"
# Logging functions
log() {
echo -e "${BLUE}[$(date +'%H:%M:%S')]${RESET} $1"
}
success() {
echo -e "${GREEN}$1${RESET}"
}
warning() {
echo -e "${YELLOW}⚠️ $1${RESET}"
}
error() {
echo -e "${RED}$1${RESET}"
cleanup_on_error
exit 1
}
# Cleanup on error
cleanup_on_error() {
log "Cleaning up after error..."
if [[ -d "$BACKUP_PATH" ]]; then
warning "Rolling back to previous version..."
restore_backup "$BACKUP_PATH"
fi
}
# Prerequisites check
check_prerequisites() {
log "Checking prerequisites..."
# Check if running from project root
if [[ ! -f "$PROJECT_ROOT/composer.json" ]]; then
error "Must be run from project root directory"
fi
# Check Docker
if ! command -v docker &> /dev/null; then
error "Docker is not installed"
fi
# Check Docker Compose
if ! docker compose version &> /dev/null; then
error "Docker Compose is not installed"
fi
# Check .env.production exists
if [[ ! -f "$PROJECT_ROOT/.env.production" ]]; then
error ".env.production not found - copy from .env.example and configure"
fi
# Check docker-compose.production.yml exists
if [[ ! -f "$PROJECT_ROOT/docker-compose.production.yml" ]]; then
error "docker-compose.production.yml not found"
fi
# Verify VAULT_ENCRYPTION_KEY is set
if ! grep -q "VAULT_ENCRYPTION_KEY=" "$PROJECT_ROOT/.env.production" || \
grep -q "VAULT_ENCRYPTION_KEY=CHANGE_ME" "$PROJECT_ROOT/.env.production"; then
error "VAULT_ENCRYPTION_KEY not configured in .env.production"
fi
success "Prerequisites check passed"
}
# Create backup
create_backup() {
log "Creating backup..."
mkdir -p "$BACKUP_DIR"
# Backup database
if docker compose ps db | grep -q "Up"; then
log "Backing up database..."
docker compose exec -T db pg_dump -U postgres michaelschiemer_prod | \
gzip > "${BACKUP_PATH}_database.sql.gz"
success "Database backup created"
fi
# Backup .env
if [[ -f "$PROJECT_ROOT/.env" ]]; then
cp "$PROJECT_ROOT/.env" "${BACKUP_PATH}_env"
success ".env backup created"
fi
# Backup docker volumes (important directories)
if [[ -d "$PROJECT_ROOT/storage" ]]; then
tar -czf "${BACKUP_PATH}_storage.tar.gz" -C "$PROJECT_ROOT" storage
success "Storage backup created"
fi
success "Backup completed: $BACKUP_PATH"
}
# Restore from backup
restore_backup() {
local backup_path="$1"
log "Restoring from backup: $backup_path"
# Restore database
if [[ -f "${backup_path}_database.sql.gz" ]]; then
log "Restoring database..."
gunzip -c "${backup_path}_database.sql.gz" | \
docker compose exec -T db psql -U postgres michaelschiemer_prod
success "Database restored"
fi
# Restore .env
if [[ -f "${backup_path}_env" ]]; then
cp "${backup_path}_env" "$PROJECT_ROOT/.env"
success ".env restored"
fi
# Restore storage
if [[ -f "${backup_path}_storage.tar.gz" ]]; then
tar -xzf "${backup_path}_storage.tar.gz" -C "$PROJECT_ROOT"
success "Storage restored"
fi
# Restart services
docker compose -f docker-compose.yml \
-f docker-compose.production.yml \
--env-file .env.production \
restart
success "Backup restored successfully"
}
# Build Docker images
build_images() {
log "Building Docker images..."
cd "$PROJECT_ROOT"
docker compose -f docker-compose.yml \
-f docker-compose.production.yml \
--env-file .env.production \
build --no-cache
success "Docker images built"
}
# Run database migrations
run_migrations() {
log "Running database migrations..."
cd "$PROJECT_ROOT"
# Check migration status first
docker compose exec -T php php console.php db:status || true
# Run migrations
if ! docker compose exec -T php php console.php db:migrate; then
error "Database migrations failed"
fi
success "Database migrations completed"
}
# Initialize SSL certificates
init_ssl() {
log "Initializing SSL certificates..."
cd "$PROJECT_ROOT"
# Check if SSL is enabled
if grep -q "SSL_ENABLED=true" .env.production; then
log "SSL is enabled, checking certificate status..."
# Check certificate status
if docker compose exec -T php php console.php ssl:status 2>/dev/null | grep -q "Certificate is valid"; then
success "SSL certificate already exists and is valid"
else
warning "SSL certificate not found or invalid, initializing..."
if ! docker compose exec -T php php console.php ssl:init; then
error "SSL initialization failed"
fi
success "SSL certificate initialized"
fi
else
warning "SSL is disabled in .env.production"
fi
}
# Verify Vault configuration
verify_vault() {
log "Verifying Vault configuration..."
cd "$PROJECT_ROOT"
# Test Vault access
if ! docker compose exec -T php php console.php vault:list &>/dev/null; then
error "Vault not accessible - check VAULT_ENCRYPTION_KEY"
fi
success "Vault is configured correctly"
}
# Health check with retries
health_check() {
local max_retries=30
local retry_count=0
log "Running health checks..."
while [[ $retry_count -lt $max_retries ]]; do
if curl -f -s -k -H "User-Agent: Mozilla/5.0 (Deployment Health Check)" "https://localhost/health" > /dev/null 2>&1; then
success "Health check passed"
return 0
fi
retry_count=$((retry_count + 1))
log "Health check attempt $retry_count/$max_retries..."
sleep 2
done
error "Health check failed after $max_retries attempts"
}
# Initial deployment
initial_deployment() {
log "🚀 Starting initial production deployment..."
check_prerequisites
cd "$PROJECT_ROOT"
# 1. Generate Vault encryption key if not exists
if grep -q "VAULT_ENCRYPTION_KEY=CHANGE_ME" .env.production; then
log "Generating Vault encryption key..."
warning "Make sure to backup this key securely!"
# Key generation is done manually for security
error "Please generate VAULT_ENCRYPTION_KEY with: docker exec php php console.php vault:generate-key"
fi
# 2. Build images
build_images
# 3. Start services
log "Starting Docker services..."
docker compose -f docker-compose.yml \
-f docker-compose.production.yml \
--env-file .env.production \
up -d
# 4. Wait for services to be ready
log "Waiting for services to be ready..."
sleep 20
# 5. Run migrations
run_migrations
# 6. Initialize SSL
init_ssl
# 7. Verify Vault
verify_vault
# 8. Health check
health_check
# 9. Display summary
deployment_summary
success "🎉 Initial deployment completed successfully!"
}
# Update deployment (zero-downtime)
update_deployment() {
log "🔄 Starting rolling update deployment..."
check_prerequisites
create_backup
cd "$PROJECT_ROOT"
# 1. Pull latest images (if using registry)
log "Pulling latest images..."
docker compose -f docker-compose.yml \
-f docker-compose.production.yml \
--env-file .env.production \
pull || warning "Pull failed (not critical if building locally)"
# 2. Build new images
build_images
# 3. Run migrations (if any)
log "Running database migrations..."
docker compose exec -T php php console.php db:migrate || warning "No new migrations"
# 4. Rolling restart with health checks
log "Performing rolling restart..."
# Restart PHP-FPM first
docker compose -f docker-compose.yml \
-f docker-compose.production.yml \
--env-file .env.production \
up -d --no-deps --force-recreate php
sleep 10
# Restart web server
docker compose -f docker-compose.yml \
-f docker-compose.production.yml \
--env-file .env.production \
up -d --no-deps --force-recreate web
sleep 5
# Restart queue workers (graceful shutdown via stop_grace_period)
docker compose -f docker-compose.yml \
-f docker-compose.production.yml \
--env-file .env.production \
up -d --no-deps --force-recreate --scale queue-worker=2 queue-worker
# 5. Health check
health_check
# 6. Cleanup old images
log "Cleaning up old Docker images..."
docker image prune -f
# 7. Display summary
deployment_summary
success "🎉 Update deployment completed successfully!"
}
# Rollback deployment
rollback_deployment() {
log "⏪ Starting rollback..."
# Find latest backup
local latest_backup=$(find "$BACKUP_DIR" -name "backup_*_database.sql.gz" | sort -r | head -1)
if [[ -z "$latest_backup" ]]; then
error "No backup found for rollback"
fi
local backup_prefix="${latest_backup%_database.sql.gz}"
warning "Rolling back to: $backup_prefix"
read -p "Continue? (yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
log "Rollback cancelled"
exit 0
fi
restore_backup "$backup_prefix"
health_check
success "🎉 Rollback completed successfully!"
}
# Deployment summary
deployment_summary() {
echo ""
echo -e "${GREEN}========================================${RESET}"
echo -e "${GREEN} Deployment Summary${RESET}"
echo -e "${GREEN}========================================${RESET}"
echo ""
echo "📋 Mode: $DEPLOY_MODE"
echo "⏰ Timestamp: $(date)"
echo "📁 Project: $PROJECT_ROOT"
echo "💾 Backup: $BACKUP_PATH"
echo ""
echo "🐳 Docker Services:"
docker compose ps
echo ""
echo "🔒 Security Checks:"
echo " [ ] APP_ENV=production in .env.production"
echo " [ ] APP_DEBUG=false in .env.production"
echo " [ ] VAULT_ENCRYPTION_KEY configured"
echo " [ ] ADMIN_ALLOWED_IPS configured"
echo " [ ] SSL certificates valid"
echo ""
echo "📊 Health Check:"
echo " ✅ Application: https://localhost/health"
echo ""
echo "📝 Next Steps:"
echo " 1. Verify all services are running"
echo " 2. Check logs: docker compose logs -f --tail=100"
echo " 3. Test critical user flows"
echo " 4. Monitor error rates"
echo ""
echo -e "${GREEN}========================================${RESET}"
}
# Main deployment logic
main() {
case "$DEPLOY_MODE" in
initial)
initial_deployment
;;
update)
update_deployment
;;
rollback)
rollback_deployment
;;
*)
error "Invalid deployment mode: $DEPLOY_MODE. Use: initial|update|rollback"
;;
esac
}
# Trap errors
trap cleanup_on_error ERR
# Run main
main "$@"