353 lines
9.0 KiB
Bash
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
|