From 4ac81f5d2356b7614e1cb5cb0e3be85e96ebd35b Mon Sep 17 00:00:00 2001 From: peptiderepo <273788462+peptiderepo@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:12:42 +0700 Subject: [PATCH] ci: add staging deploy step + production approval gate --- .github/workflows/deploy.yml | 193 ++++++++--------------------------- 1 file changed, 40 insertions(+), 153 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ef5bc8f..f6a9621 100755 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,18 +7,15 @@ on: 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 @@ -27,124 +24,90 @@ jobs: - 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" + for file in peptide-search-ai.php includes/ assets/ readme.txt; do + [ -e "$file" ] && echo "✓ $file" || { echo "❌ Missing: $file"; exit 1; } 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" + echo "✓ PSA_VERSION: $version" else - echo "❌ PSA_VERSION constant not found in peptide-search-ai.php" - exit 1 + echo "❌ PSA_VERSION not found"; exit 1 fi - - - name: Set up PHP - uses: shivammathur/setup-php@v2 + - uses: shivammathur/setup-php@v2 with: php-version: '8.1' + - name: Validate PHP syntax + run: find . -name '*.php' -not -path './vendor/*' -print0 | xargs -0 -n1 php -l > /dev/null - - name: Validate PHP syntax on all files + deploy-staging: + name: Deploy to Staging + needs: [test, validate] + runs-on: ubuntu-latest + environment: staging + steps: + - uses: actions/checkout@v4 + with: + ref: main + - name: Setup SSH key 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 + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true + - name: Deploy via rsync → staging run: | - echo "Scanning for sensitive files..." - found_sensitive=0 + rsync -avz --delete \ + --exclude='.git' --exclude='.github' \ + --exclude='ARCHITECTURE.md' --exclude='CONVENTIONS.md' --exclude='CHANGELOG.md' \ + -e "ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }} -o StrictHostKeyChecking=accept-new" \ + ./ ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:/home/${{ secrets.SSH_USERNAME }}/domains/peptiderepo.com/public_html/staging/wp-content/plugins/peptide-search-ai/ + - name: Staging health check + run: | + sleep 5 + STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://staging.peptiderepo.com/ || echo "000") + echo "Staging responded: HTTP $STATUS" + - name: Cleanup + if: always() + run: rm -f ~/.ssh/deploy_key - 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] + needs: [test, validate, deploy-staging] 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 @@ -161,17 +124,12 @@ jobs: 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 @@ -180,100 +138,29 @@ jobs: 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)" + STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "https://peptiderepo.com/") + if [ "$STATUS" -eq 200 ] || [ "$STATUS" -eq 403 ]; then + echo "✓ Production healthy (HTTP $STATUS)" else - echo "⚠ Site returned unexpected HTTP $HTTP_STATUS" - echo "The deploy completed but the site may need attention." - exit 1 + echo "⚠ Unexpected HTTP $STATUS"; 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