Upgrade to version 6.0.5 #43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Deploy Mautic | |
| on: | |
| push: | |
| branches: [Live] | |
| workflow_dispatch: {} | |
| jobs: | |
| build-deploy: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Detect NPM directory and lockfile | |
| id: npm-detection | |
| run: | | |
| if [ -f docroot/package.json ]; then | |
| echo "NPM_DIR=docroot" >> $GITHUB_ENV | |
| echo "npm_dir=docroot" >> $GITHUB_OUTPUT | |
| else | |
| echo "NPM_DIR=." >> $GITHUB_ENV | |
| echo "npm_dir=." >> $GITHUB_OUTPUT | |
| fi | |
| NPM_DIR=${NPM_DIR:-.} | |
| if [ -f "${NPM_DIR}/package-lock.json" ]; then | |
| echo "HAS_LOCK=true" >> $GITHUB_ENV | |
| echo "has_lock=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "HAS_LOCK=false" >> $GITHUB_ENV | |
| echo "has_lock=false" >> $GITHUB_OUTPUT | |
| fi | |
| echo "NPM_DIR=${NPM_DIR}, HAS_LOCK=${HAS_LOCK:-false}" | |
| - name: Setup PHP 8.3 | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.3' | |
| tools: composer:v2 | |
| extensions: intl, mbstring, curl, zip, gd, xml, dom | |
| coverage: none | |
| - name: Get Composer cache directory | |
| id: composer-cache | |
| run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT | |
| - name: Cache Composer dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.composer-cache.outputs.dir }} | |
| key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} | |
| restore-keys: ${{ runner.os }}-composer- | |
| - name: Install Composer dependencies | |
| run: composer install --no-interaction --no-progress --prefer-dist --no-dev --optimize-autoloader | |
| - name: Setup Node.js 20 (with cache) | |
| if: env.HAS_LOCK == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: ${{ env.NPM_DIR }}/package-lock.json | |
| - name: Setup Node.js 20 (without cache) | |
| if: env.HAS_LOCK != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Create .env file | |
| run: | | |
| echo "NODE_ENV=production" > ${{ env.NPM_DIR }}/.env | |
| echo "Created .env file in ${{ env.NPM_DIR }}" | |
| - name: Install NPM dependencies | |
| working-directory: ${{ env.NPM_DIR }} | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=4096 | |
| run: | | |
| if [ "${HAS_LOCK}" = "true" ]; then | |
| npm ci --no-audit --prefer-offline | |
| else | |
| npm install --no-audit | |
| fi | |
| - name: Build frontend assets | |
| working-directory: ${{ env.NPM_DIR }} | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=4096 | |
| NODE_ENV: production | |
| run: npm run build | |
| - name: Create deployment artifact | |
| run: | | |
| mkdir -p artifact | |
| # Copy application files excluding development and cache directories | |
| rsync -a \ | |
| --exclude=".git" \ | |
| --exclude=".github" \ | |
| --exclude=".gitignore" \ | |
| --exclude="node_modules" \ | |
| --exclude="docroot/node_modules" \ | |
| --exclude="var/cache" \ | |
| --exclude="var/logs" \ | |
| --exclude="docroot/var/cache" \ | |
| --exclude="docroot/var/logs" \ | |
| --exclude="config/local.php" \ | |
| --exclude="*.log" \ | |
| --exclude=".env*" \ | |
| ./ artifact/ | |
| # Include built media assets if they exist | |
| if [ -d "docroot/media" ]; then | |
| rsync -a docroot/media/ artifact/docroot/media/ || true | |
| fi | |
| # Create necessary directories | |
| mkdir -p artifact/var/{cache,logs} | |
| mkdir -p artifact/docroot/media | |
| # Set basic permissions for created directories | |
| chmod 755 artifact/var artifact/docroot/media | |
| - name: Deploy application code | |
| uses: burnett01/rsync-deployments@7.0.1 | |
| with: | |
| switches: >- | |
| -avz --delete --progress | |
| --exclude='/config/local.php' | |
| --exclude='/var/cache/**' | |
| --exclude='/var/logs/**' | |
| --exclude='/docroot/media/**' | |
| --filter='P /config/local.php' | |
| --filter='P /var/cache' | |
| --filter='P /var/logs' | |
| --filter='P /docroot/media' | |
| path: "artifact/" | |
| remote_path: "${{ secrets.REMOTE_PATH }}/" | |
| remote_host: "${{ secrets.SSH_HOST }}" | |
| remote_user: "${{ secrets.SSH_USER }}" | |
| remote_key: "${{ secrets.SSH_KEY }}" | |
| remote_port: "${{ secrets.SSH_PORT }}" | |
| - name: Deploy media assets | |
| uses: burnett01/rsync-deployments@7.0.1 | |
| with: | |
| switches: "-avz --no-perms --no-owner --no-group --ignore-existing" | |
| path: "artifact/docroot/media/" | |
| remote_path: "${{ secrets.REMOTE_PATH }}/docroot/media/" | |
| remote_host: "${{ secrets.SSH_HOST }}" | |
| remote_user: "${{ secrets.SSH_USER }}" | |
| remote_key: "${{ secrets.SSH_KEY }}" | |
| remote_port: "${{ secrets.SSH_PORT }}" | |
| - name: Configure server redirects (temporary) | |
| uses: appleboy/ssh-action@v1.2.2 | |
| with: | |
| host: ${{ secrets.SSH_HOST }} | |
| username: ${{ secrets.SSH_USER }} | |
| key: ${{ secrets.SSH_KEY }} | |
| port: ${{ secrets.SSH_PORT }} | |
| script: | | |
| set -euo pipefail | |
| BASE="${{ secrets.REMOTE_PATH }}" | |
| # Create root .htaccess for docroot redirection | |
| cat > "$BASE/.htaccess" <<'EOF' | |
| RewriteEngine On | |
| RewriteCond %{REQUEST_URI} !^/docroot/ | |
| RewriteRule ^(.*)$ docroot/$1 [L,R=302] | |
| EOF | |
| echo "Root .htaccess configured for docroot redirection" | |
| - name: Finalize deployment | |
| uses: appleboy/ssh-action@v1.2.2 | |
| with: | |
| host: ${{ secrets.SSH_HOST }} | |
| username: ${{ secrets.SSH_USER }} | |
| key: ${{ secrets.SSH_KEY }} | |
| port: ${{ secrets.SSH_PORT }} | |
| script: | | |
| set -euo pipefail | |
| umask 002 | |
| DIR="${{ secrets.REMOTE_PATH }}" | |
| OWNER="${{ secrets.CPANEL_USER }}" | |
| PHP_CLI="${{ secrets.PHP_CLI }}" | |
| PHP="${PHP_CLI:-/opt/cpanel/ea-php83/root/usr/bin/php} -d memory_limit=768M" | |
| cd "$DIR" | |
| # Ensure required directories exist | |
| mkdir -p var/{cache,logs} docroot/media | |
| # Remove filesystem attributes that might prevent operations | |
| if command -v chattr >/dev/null 2>&1; then | |
| chattr -R -i -a "$DIR/var" "$DIR/docroot/media" 2>/dev/null || true | |
| fi | |
| if command -v setfacl >/dev/null 2>&1; then | |
| setfacl -b -R "$DIR/var" "$DIR/docroot/media" 2>/dev/null || true | |
| fi | |
| # Set ownership if running as root | |
| if [ "$(id -u)" -eq 0 ] && [ -n "${OWNER:-}" ]; then | |
| chown -R "$OWNER:$OWNER" "$DIR/var" "$DIR/docroot/media" | |
| fi | |
| # Set proper permissions | |
| find var docroot/media -type d -exec chmod 775 {} + 2>/dev/null || true | |
| find var docroot/media -type f -exec chmod 664 {} + 2>/dev/null || true | |
| # Clean production cache | |
| rm -rf var/cache/prod 2>/dev/null || true | |
| mkdir -p var/cache/prod | |
| # Function to run commands as the correct user | |
| run_as_owner() { | |
| if [ "$(id -u)" -eq 0 ] && [ -n "${OWNER:-}" ]; then | |
| su -s /bin/bash - "$OWNER" -c "cd '$DIR' && $*" | |
| else | |
| bash -c "cd '$DIR' && $*" | |
| fi | |
| } | |
| # Generate Mautic assets | |
| echo "Generating Mautic assets..." | |
| run_as_owner "$PHP bin/console mautic:assets:generate --env=prod" || { | |
| echo "Warning: Asset generation failed, continuing..." | |
| } | |
| # Configure Apache security rules (after Mautic asset generation) | |
| HTACCESS_FILE="docroot/.htaccess" | |
| # Ensure DirectoryIndex is set | |
| if ! grep -q 'DirectoryIndex' "$HTACCESS_FILE" 2>/dev/null; then | |
| echo 'DirectoryIndex index.php index.html' >> "$HTACCESS_FILE" | |
| fi | |
| # Create tmp directory if it doesn't exist | |
| mkdir -p "$HOME/tmp" || mkdir -p "/home/$USER/tmp" || true | |
| # Create a delayed .htaccess configuration script in home tmp directory | |
| SCRIPT_PATH="$HOME/tmp/configure_htaccess_$(date +%s).sh" | |
| LOG_PATH="$HOME/tmp/htaccess_config.log" | |
| # Fallback paths for different cPanel setups | |
| if [ ! -d "$HOME/tmp" ]; then | |
| SCRIPT_PATH="/home/$USER/tmp/configure_htaccess_$(date +%s).sh" | |
| LOG_PATH="/home/$USER/tmp/htaccess_config.log" | |
| mkdir -p "/home/$USER/tmp" || true | |
| fi | |
| echo "Creating script at: $SCRIPT_PATH" | |
| echo "Logs will be at: $LOG_PATH" | |
| # Create the bash script | |
| cat > "$SCRIPT_PATH" << 'SCRIPT_END' | |
| #!/bin/bash | |
| set -euo pipefail | |
| # Set up logging | |
| LOG_FILE="$HOME/tmp/htaccess_config.log" | |
| if [ ! -d "$HOME/tmp" ]; then | |
| LOG_FILE="/home/$USER/tmp/htaccess_config.log" | |
| mkdir -p "/home/$USER/tmp" || true | |
| fi | |
| # Log start | |
| echo "$(date): Script started" >> "$LOG_FILE" | |
| echo "$(date): Running as user: $(whoami)" >> "$LOG_FILE" | |
| echo "$(date): Home directory: $HOME" >> "$LOG_FILE" | |
| # The project directory passed as argument | |
| PROJECT_DIR="$1" | |
| echo "$(date): Project directory: $PROJECT_DIR" >> "$LOG_FILE" | |
| cd "$PROJECT_DIR" | |
| HTACCESS_FILE="docroot/.htaccess" | |
| # Wait to ensure all Mautic processes are complete | |
| echo "$(date): Waiting 30 seconds..." >> "$LOG_FILE" | |
| sleep 30 | |
| # Check if .htaccess file exists | |
| if [ ! -f "$HTACCESS_FILE" ]; then | |
| echo "$(date): .htaccess file not found at $HTACCESS_FILE" >> "$LOG_FILE" | |
| echo "$(date): Current directory contents:" >> "$LOG_FILE" | |
| ls -la >> "$LOG_FILE" 2>&1 | |
| if [ -d "docroot" ]; then | |
| echo "$(date): docroot directory contents:" >> "$LOG_FILE" | |
| ls -la docroot/ >> "$LOG_FILE" 2>&1 | |
| fi | |
| exit 1 | |
| fi | |
| echo "$(date): Found .htaccess file at $HTACCESS_FILE" >> "$LOG_FILE" | |
| # Show current .htaccess content before modification | |
| echo "$(date): Current .htaccess content:" >> "$LOG_FILE" | |
| cat "$HTACCESS_FILE" >> "$LOG_FILE" 2>&1 | |
| # Add authz_core_module configuration if not already present | |
| if ! grep -q '<FilesMatch "^(index|upgrade/upgrade)\.php$">' "$HTACCESS_FILE" 2>/dev/null; then | |
| echo "" >> "$HTACCESS_FILE" | |
| echo "# Additional authz_core_module with FilesMatch for index and upgrade scripts" >> "$HTACCESS_FILE" | |
| echo "<IfModule authz_core_module>" >> "$HTACCESS_FILE" | |
| echo " <FilesMatch \"^(index|upgrade/upgrade)\.php$\">" >> "$HTACCESS_FILE" | |
| echo " Require all granted" >> "$HTACCESS_FILE" | |
| echo " </FilesMatch>" >> "$HTACCESS_FILE" | |
| echo "</IfModule>" >> "$HTACCESS_FILE" | |
| echo "$(date): Successfully added authz_core_module configuration with FilesMatch to .htaccess" >> "$LOG_FILE" | |
| # Show updated .htaccess content | |
| echo "$(date): Updated .htaccess content:" >> "$LOG_FILE" | |
| cat "$HTACCESS_FILE" >> "$LOG_FILE" 2>&1 | |
| else | |
| echo "$(date): authz_core_module configuration with FilesMatch already exists in .htaccess" >> "$LOG_FILE" | |
| fi | |
| # Create .env file in NPM_DIR if it doesn't exist | |
| # First, detect NPM directory | |
| if [ -f "docroot/package.json" ]; then | |
| NPM_DIR="docroot" | |
| else | |
| NPM_DIR="." | |
| fi | |
| ENV_FILE="$NPM_DIR/.env" | |
| if [ ! -f "$ENV_FILE" ]; then | |
| echo "NODE_ENV=production" > "$ENV_FILE" | |
| echo "$(date): Created .env file in $NPM_DIR with NODE_ENV=production" >> "$LOG_FILE" | |
| else | |
| # Check if NODE_ENV is already set | |
| if ! grep -q "NODE_ENV=" "$ENV_FILE" 2>/dev/null; then | |
| echo "NODE_ENV=production" >> "$ENV_FILE" | |
| echo "$(date): Added NODE_ENV=production to existing .env file in $NPM_DIR" >> "$LOG_FILE" | |
| else | |
| echo "$(date): NODE_ENV already exists in .env file in $NPM_DIR" >> "$LOG_FILE" | |
| fi | |
| fi | |
| # Remove the cron job | |
| echo "$(date): Removing cron job..." >> "$LOG_FILE" | |
| echo "$(date): Current crontab before cleanup:" >> "$LOG_FILE" | |
| crontab -l >> "$LOG_FILE" 2>&1 || echo "No crontab found" >> "$LOG_FILE" | |
| (crontab -l 2>/dev/null | grep -v "configure_htaccess" | crontab -) 2>/dev/null || true | |
| echo "$(date): Crontab after cleanup:" >> "$LOG_FILE" | |
| crontab -l >> "$LOG_FILE" 2>&1 || echo "No crontab found" >> "$LOG_FILE" | |
| # Remove this script | |
| echo "$(date): Removing script file: $0" >> "$LOG_FILE" | |
| rm -f "$0" | |
| echo "$(date): .htaccess configuration completed and cleanup done" >> "$LOG_FILE" | |
| SCRIPT_END | |
| # Make the script executable | |
| chmod +x "$SCRIPT_PATH" | |
| echo "Made script executable" | |
| # Set ownership for cPanel user | |
| if [ "$(id -u)" -eq 0 ] && [ -n "${OWNER:-}" ]; then | |
| chown "$OWNER:$OWNER" "$SCRIPT_PATH" | |
| echo "Set ownership to $OWNER" | |
| fi | |
| # Verify script was created | |
| if [ -f "$SCRIPT_PATH" ]; then | |
| echo "Script created successfully at $SCRIPT_PATH" | |
| ls -la "$SCRIPT_PATH" | |
| echo "Script content preview:" | |
| head -15 "$SCRIPT_PATH" | |
| else | |
| echo "ERROR: Script was not created!" | |
| exit 1 | |
| fi | |
| # Calculate time for cron job (current time + 1 minute) | |
| # Remove leading zeros to avoid octal interpretation | |
| CURRENT_MINUTE=$(date +%M | sed 's/^0*//') | |
| CURRENT_HOUR=$(date +%H | sed 's/^0*//') | |
| # Handle empty values (when minute/hour is 00) | |
| CURRENT_MINUTE=${CURRENT_MINUTE:-0} | |
| CURRENT_HOUR=${CURRENT_HOUR:-0} | |
| NEXT_MINUTE=$(((CURRENT_MINUTE + 1) % 60)) | |
| # If we're at minute 59, increment the hour | |
| if [ $CURRENT_MINUTE -eq 59 ]; then | |
| NEXT_HOUR=$(((CURRENT_HOUR + 1) % 24)) | |
| else | |
| NEXT_HOUR=$CURRENT_HOUR | |
| fi | |
| # Create cron job to run once at the specific time, passing project directory as argument | |
| CRON_LINE="$NEXT_MINUTE $NEXT_HOUR * * * $SCRIPT_PATH '$DIR' >/dev/null 2>&1" | |
| echo "Setting up cron job: $CRON_LINE" | |
| # Function to setup cron job for cPanel environment | |
| setup_cron() { | |
| echo "Current crontab:" | |
| crontab -l 2>/dev/null || echo "No existing crontab" | |
| echo "Adding new cron job..." | |
| (crontab -l 2>/dev/null; echo "$CRON_LINE") | crontab - | |
| echo "Updated crontab:" | |
| crontab -l 2>/dev/null || echo "Failed to read crontab" | |
| } | |
| if [ "$(id -u)" -eq 0 ] && [ -n "${OWNER:-}" ]; then | |
| # Run as the cPanel user | |
| su -s /bin/bash - "$OWNER" -c "$(declare -f setup_cron); CRON_LINE='$CRON_LINE'; setup_cron" | |
| else | |
| # Run as current user | |
| setup_cron | |
| fi | |
| echo "Created delayed .htaccess configuration script and cron job" | |
| echo "Script will run at $NEXT_HOUR:$NEXT_MINUTE" | |
| echo "Check $LOG_PATH for execution logs" | |
| echo "Script location: $SCRIPT_PATH" | |
| # Create initial log entry | |
| echo "$(date): Deployment completed, script scheduled to run at $NEXT_HOUR:$NEXT_MINUTE" >> "$LOG_PATH" | |
| # Run database migrations if database is configured | |
| echo "Checking database configuration..." | |
| if $PHP -r ' | |
| $configFile = "config/local.php"; | |
| if (!file_exists($configFile)) exit(1); | |
| $config = include $configFile; | |
| if (!is_array($config)) { | |
| $parameters = []; | |
| include $configFile; | |
| $config = $parameters ?? []; | |
| } | |
| $hasDb = !empty($config["db_name"]) && | |
| !empty($config["db_user"]) && | |
| array_key_exists("db_password", $config); | |
| exit($hasDb ? 0 : 1); | |
| '; then | |
| echo "Running database migrations..." | |
| run_as_owner "$PHP bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration" || { | |
| echo "Warning: Database migration failed, continuing..." | |
| } | |
| else | |
| echo "Database not configured, skipping migrations" | |
| fi | |
| # Clear production cache | |
| echo "Clearing production cache..." | |
| run_as_owner "$PHP bin/console cache:clear --env=prod" || { | |
| echo "Warning: Cache clear failed, continuing..." | |
| } | |
| # Final ownership fix | |
| if [ "$(id -u)" -eq 0 ] && [ -n "${OWNER:-}" ]; then | |
| chown -R "$OWNER:$OWNER" "$DIR/var" | |
| fi | |
| echo "Deployment completed successfully!" |