Files
gh-atournayre-claude-market…/skills/symfony-skill/scripts/deploy.sh
2025-11-29 17:59:04 +08:00

353 lines
9.0 KiB
Bash

#!/bin/bash
# Symfony Deployment Script
# Usage: ./deploy.sh [environment] [branch]
# Example: ./deploy.sh production main
set -e
# Configuration
ENVIRONMENT=${1:-production}
BRANCH=${2:-main}
PROJECT_PATH="/var/www/symfony-app"
BACKUP_PATH="/var/backups/symfony-app"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
check_requirements() {
log_info "Checking requirements..."
# Check if PHP is installed
if ! command -v php &> /dev/null; then
log_error "PHP is not installed"
exit 1
fi
# Check if Composer is installed
if ! command -v composer &> /dev/null; then
log_error "Composer is not installed"
exit 1
fi
# Check if Git is installed
if ! command -v git &> /dev/null; then
log_error "Git is not installed"
exit 1
fi
# Check PHP version
PHP_VERSION=$(php -r "echo PHP_VERSION;")
MIN_PHP_VERSION="8.1.0"
if [ "$(printf '%s\n' "$MIN_PHP_VERSION" "$PHP_VERSION" | sort -V | head -n1)" != "$MIN_PHP_VERSION" ]; then
log_error "PHP version must be at least $MIN_PHP_VERSION (current: $PHP_VERSION)"
exit 1
fi
log_info "All requirements met"
}
create_backup() {
log_info "Creating backup..."
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_PATH"
# Backup database
if [ -f "$PROJECT_PATH/.env.local" ]; then
source "$PROJECT_PATH/.env.local"
if [ ! -z "$DATABASE_URL" ]; then
# Parse database URL
DB_USER=$(echo $DATABASE_URL | sed -E 's/.*:\/\/([^:]+):.*/\1/')
DB_PASS=$(echo $DATABASE_URL | sed -E 's/.*:\/\/[^:]+:([^@]+)@.*/\1/')
DB_HOST=$(echo $DATABASE_URL | sed -E 's/.*@([^:\/]+).*/\1/')
DB_NAME=$(echo $DATABASE_URL | sed -E 's/.*\///')
mysqldump -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BACKUP_PATH/db_backup_$TIMESTAMP.sql"
log_info "Database backup created: db_backup_$TIMESTAMP.sql"
fi
fi
# Backup files
tar -czf "$BACKUP_PATH/files_backup_$TIMESTAMP.tar.gz" \
-C "$PROJECT_PATH" \
--exclude=var/cache \
--exclude=var/log \
--exclude=vendor \
--exclude=node_modules \
.
log_info "Files backup created: files_backup_$TIMESTAMP.tar.gz"
# Keep only last 5 backups
ls -1dt "$BACKUP_PATH"/* | tail -n +11 | xargs -r rm -f
}
pull_latest_code() {
log_info "Pulling latest code from $BRANCH branch..."
cd "$PROJECT_PATH"
# Stash any local changes
git stash
# Fetch latest changes
git fetch origin
# Checkout and pull the specified branch
git checkout "$BRANCH"
git pull origin "$BRANCH"
log_info "Code updated to latest version"
}
install_dependencies() {
log_info "Installing dependencies..."
cd "$PROJECT_PATH"
# Install Composer dependencies
if [ "$ENVIRONMENT" = "production" ]; then
composer install --no-dev --optimize-autoloader --no-interaction
else
composer install --optimize-autoloader --no-interaction
fi
# Install NPM dependencies if package.json exists
if [ -f "package.json" ]; then
npm ci
# Build assets
if [ "$ENVIRONMENT" = "production" ]; then
npm run build
else
npm run dev
fi
fi
log_info "Dependencies installed"
}
run_migrations() {
log_info "Running database migrations..."
cd "$PROJECT_PATH"
# Check if there are pending migrations
PENDING=$(php bin/console doctrine:migrations:status --show-versions | grep "not migrated" | wc -l)
if [ "$PENDING" -gt 0 ]; then
log_info "Found $PENDING pending migration(s)"
# Run migrations
php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
log_info "Migrations completed"
else
log_info "No pending migrations"
fi
}
clear_cache() {
log_info "Clearing cache..."
cd "$PROJECT_PATH"
# Clear Symfony cache
php bin/console cache:clear --env="$ENVIRONMENT" --no-warmup
php bin/console cache:warmup --env="$ENVIRONMENT"
# Clear OPcache if available
if command -v cachetool &> /dev/null; then
cachetool opcache:reset
log_info "OPcache cleared"
fi
log_info "Cache cleared and warmed up"
}
update_permissions() {
log_info "Updating file permissions..."
cd "$PROJECT_PATH"
# Set proper permissions for var directory
chmod -R 775 var/
# Set proper ownership (adjust user:group as needed)
if [ ! -z "$WEB_USER" ]; then
chown -R "$WEB_USER":"$WEB_USER" var/
fi
log_info "Permissions updated"
}
run_tests() {
log_info "Running tests..."
cd "$PROJECT_PATH"
# Run PHPUnit tests if they exist
if [ -f "bin/phpunit" ] || [ -f "vendor/bin/phpunit" ]; then
if [ "$ENVIRONMENT" != "production" ]; then
php bin/phpunit --testdox || {
log_error "Tests failed! Deployment aborted."
exit 1
}
log_info "All tests passed"
else
log_warning "Skipping tests in production environment"
fi
else
log_warning "PHPUnit not found, skipping tests"
fi
}
restart_services() {
log_info "Restarting services..."
# Restart PHP-FPM
if systemctl is-active --quiet php8.1-fpm; then
systemctl reload php8.1-fpm
log_info "PHP-FPM reloaded"
fi
# Restart web server
if systemctl is-active --quiet nginx; then
systemctl reload nginx
log_info "Nginx reloaded"
elif systemctl is-active --quiet apache2; then
systemctl reload apache2
log_info "Apache reloaded"
fi
# Restart queue workers if Messenger is used
if systemctl is-active --quiet symfony-messenger; then
systemctl restart symfony-messenger
log_info "Messenger workers restarted"
fi
}
health_check() {
log_info "Performing health check..."
cd "$PROJECT_PATH"
# Check if the application responds
if [ ! -z "$APP_URL" ]; then
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$APP_URL/health-check")
if [ "$HTTP_STATUS" -eq 200 ]; then
log_info "Health check passed (HTTP $HTTP_STATUS)"
else
log_error "Health check failed (HTTP $HTTP_STATUS)"
exit 1
fi
fi
# Check database connection
php bin/console doctrine:query:sql "SELECT 1" > /dev/null 2>&1 || {
log_error "Database connection failed"
exit 1
}
log_info "All health checks passed"
}
notify_deployment() {
MESSAGE="$1"
# Send notification (configure your notification method)
# Example: Slack webhook
if [ ! -z "$SLACK_WEBHOOK" ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"Deployment: $MESSAGE\"}" \
"$SLACK_WEBHOOK"
fi
# Log to deployment log
echo "[$(date)] $MESSAGE" >> "$PROJECT_PATH/var/log/deployments.log"
}
rollback() {
log_error "Deployment failed! Rolling back..."
# Restore database from backup
if [ -f "$BACKUP_PATH/db_backup_$TIMESTAMP.sql" ]; then
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < "$BACKUP_PATH/db_backup_$TIMESTAMP.sql"
log_info "Database restored from backup"
fi
# Restore files from backup
if [ -f "$BACKUP_PATH/files_backup_$TIMESTAMP.tar.gz" ]; then
rm -rf "$PROJECT_PATH"/*
tar -xzf "$BACKUP_PATH/files_backup_$TIMESTAMP.tar.gz" -C "$PROJECT_PATH"
log_info "Files restored from backup"
fi
clear_cache
restart_services
notify_deployment "❌ Deployment failed and rolled back on $ENVIRONMENT"
exit 1
}
# Main deployment process
main() {
log_info "========================================="
log_info "Starting Symfony deployment"
log_info "Environment: $ENVIRONMENT"
log_info "Branch: $BRANCH"
log_info "Timestamp: $TIMESTAMP"
log_info "========================================="
# Set error trap
trap rollback ERR
# Execute deployment steps
check_requirements
create_backup
pull_latest_code
install_dependencies
run_migrations
clear_cache
update_permissions
run_tests
restart_services
health_check
# Remove error trap after successful deployment
trap - ERR
log_info "========================================="
log_info "Deployment completed successfully!"
log_info "========================================="
notify_deployment "✅ Successfully deployed to $ENVIRONMENT from $BRANCH"
}
# Run main function
main
# Exit successfully
exit 0