Skip to content

Upgrade to version 6.0.5 #43

Upgrade to version 6.0.5

Upgrade to version 6.0.5 #43

Workflow file for this run

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!"