name: Deploy to WordPress on: push: branches: [ main ] permissions: contents: write # Cancel previous deploys if a new push arrives while one is running. concurrency: group: deploy-production cancel-in-progress: true jobs: # -- Run tests first -- test: name: Pre-deploy tests uses: ./.github/workflows/ci.yml # -- Validate deployment readiness -- validate: name: Validate deployment runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v4 with: ref: main - name: Verify required files exist run: | echo "Checking for required plugin files..." required_files=( "peptide-search-ai.php" "includes/" "assets/" "readme.txt" ) for file in "${required_files[@]}"; do if [ ! -e "$file" ]; then echo "❌ Missing required file: $file" exit 1 fi echo "✓ Found: $file" done - name: Verify PSA_VERSION constant run: | echo "Checking PSA_VERSION constant..." if grep -q "define( 'PSA_VERSION'" peptide-search-ai.php; then version=$(grep "define( 'PSA_VERSION'" peptide-search-ai.php | sed "s/.*'\([^']*\)'.*/\1/") echo "✓ PSA_VERSION found: $version" else echo "❌ PSA_VERSION constant not found in peptide-search-ai.php" exit 1 fi - name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: '8.1' - name: Validate PHP syntax on all files run: | echo "Validating PHP syntax..." find . -name '*.php' -not -path './vendor/*' -not -path './node_modules/*' -print0 | \ xargs -0 -n1 php -l > /dev/null && echo "✓ All PHP files are valid" - name: Check for sensitive files run: | echo "Scanning for sensitive files..." found_sensitive=0 sensitive_patterns=( "\.env" "\.env\." "credentials" "\.key$" "\.pem$" "private_key" "\.sql$" ) for pattern in "${sensitive_patterns[@]}"; do if find . -name "$pattern" -not -path './vendor/*' -not -path './.git/*' 2>/dev/null | grep -q .; then echo "❌ Found sensitive file matching pattern: $pattern" find . -name "$pattern" -not -path './vendor/*' -not -path './.git/*' 2>/dev/null found_sensitive=1 fi done if [ $found_sensitive -eq 1 ]; then exit 1 fi echo "✓ No sensitive files detected" # -- Push clean plugin files to the deploy branch -- sync-deploy-branch: name: Sync deploy branch runs-on: ubuntu-latest needs: [test, validate] environment: production steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Sync deploy branch run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" # Fetch the remote deploy branch (may not exist on first run). git fetch origin deploy:deploy 2>/dev/null || true # If deploy branch exists, check it out; otherwise create it. if git rev-parse --verify deploy >/dev/null 2>&1; then git checkout deploy else git checkout --orphan deploy git rm -rf --cached . > /dev/null 2>&1 || true fi # Remove all tracked files so we start clean each time. git rm -rf . > /dev/null 2>&1 || true # Checkout only the plugin runtime files from main. git checkout main -- \ assets/ \ includes/ \ peptide-search-ai.php \ readme.txt # Commit only if there are actual changes. git add -A if git diff-index --quiet HEAD -- 2>/dev/null; then echo "No changes to deploy." else git commit -m "Deploy ${GITHUB_SHA::7} from main" fi # Normal push (fast-forward). git push origin deploy # -- Deploy to Hostinger via SSH -- deploy: name: Deploy to Hostinger runs-on: ubuntu-latest needs: sync-deploy-branch environment: production steps: - name: Pull deploy branch on Hostinger uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: ${{ secrets.SSH_PORT }} script_stop: true envs: DEPLOY_KEY script: | # Ensure GitHub host key is trusted (one-time setup). mkdir -p ~/.ssh ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null # Install deploy key so Hostinger can pull from the private repo. if [ ! -f ~/.ssh/deploy_key ]; then echo "$DEPLOY_KEY" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key fi # Configure SSH to use the deploy key for github.com. if ! grep -q "github.com" ~/.ssh/config 2>/dev/null; then cat >> ~/.ssh/config <<'SSHEOF' Host github.com IdentityFile ~/.ssh/deploy_key StrictHostKeyChecking no SSHEOF chmod 600 ~/.ssh/config fi PLUGIN_DIR="$HOME/domains/peptiderepo.com/public_html/wp-content/plugins/peptide-search-ai" REPO_URL="git@github.com:peptiderepo/peptide-search-ai.git" if [ -d "$PLUGIN_DIR/.git" ]; then # Existing clone -- just pull latest. cd "$PLUGIN_DIR" git fetch origin deploy git reset --hard origin/deploy else # First deploy -- clone the deploy branch. rm -rf "$PLUGIN_DIR" git clone -b deploy "$REPO_URL" "$PLUGIN_DIR" fi echo "Deployed to $PLUGIN_DIR" ls -la "$PLUGIN_DIR" env: DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} - name: Verify deployment on server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: ${{ secrets.SSH_PORT }} script_stop: true script: | PLUGIN_DIR="$HOME/domains/peptiderepo.com/public_html/wp-content/plugins/peptide-search-ai" echo "Verifying deployment..." if [ ! -d "$PLUGIN_DIR" ]; then echo "❌ Plugin directory not found at $PLUGIN_DIR" exit 1 fi # Check for required files required_files=( "peptide-search-ai.php" "includes" "assets" "readme.txt" ) for file in "${required_files[@]}"; do if [ ! -e "$PLUGIN_DIR/$file" ]; then echo "❌ Missing file: $file" exit 1 fi echo "✓ Found: $file" done # Extract and log deployed version if [ -f "$PLUGIN_DIR/peptide-search-ai.php" ]; then version=$(grep "Version:" "$PLUGIN_DIR/peptide-search-ai.php" | head -1 | sed 's/.*Version: *//; s/ *.*//') echo "✓ Deployed version: $version" else echo "❌ Plugin file not found" exit 1 fi echo "✓ Deployment verification successful" - name: HTTP health check run: | echo "Verifying site is accessible after deploy..." HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "https://peptiderepo.com/") # Accept 200 (healthy) or 403 (peptide-starter-theme v1.5.2 CF-peer # validator rejecting this GHA-runner direct-to-origin request as # non-Cloudflare traffic — that rejection is the validator working). # Real user traffic via Cloudflare gets 200. if [ "$HTTP_STATUS" -eq 200 ] || [ "$HTTP_STATUS" -eq 403 ]; then echo "✓ Site responsive (HTTP $HTTP_STATUS)" else echo "⚠ Site returned unexpected HTTP $HTTP_STATUS" echo "The deploy completed but the site may need attention." exit 1 fi # Verify REST API endpoint is responding. Same 200/403 tolerance: # the REST endpoint is also gated by the CF-peer validator. REST_STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 "https://peptiderepo.com/wp-json/peptides/v1/search?q=BPC-157") if [ "$REST_STATUS" -eq 200 ] || [ "$REST_STATUS" -eq 403 ]; then echo "✓ REST API responsive (HTTP $REST_STATUS)" else echo "⚠ REST API returned unexpected HTTP $REST_STATUS" fi - name: Deploy summary run: | echo "## Deployment Complete" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- **Branch:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY echo "- **Commit:** \`${GITHUB_SHA::7}\`" >> $GITHUB_STEP_SUMMARY echo "- **Method:** SSH direct deploy (git pull on Hostinger)" >> $GITHUB_STEP_SUMMARY echo "- **Deployed to:** peptiderepo.com" >> $GITHUB_STEP_SUMMARY