From 57271d1067e67101008f024a98d6b7f0a55d42c4 Mon Sep 17 00:00:00 2001 From: peptiderepo Date: Tue, 9 Jun 2026 07:03:17 +0000 Subject: [PATCH] ci: add shared reusable deploy workflow (deploy-app.yml) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single staging→smoke→production pipeline callable by every plugin/theme repo via workflow_call. Replaces six copy-pasted per-repo deploy.yml files so deploy logic is fixed once. Includes the pre/post active-plugin-count guard, LiteSpeed purge, and health checks. Callers pass target_path + secrets: inherit. Agent-Session: cto reusable-deploy --- .github/workflows/deploy-app.yml | 126 +++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 .github/workflows/deploy-app.yml diff --git a/.github/workflows/deploy-app.yml b/.github/workflows/deploy-app.yml new file mode 100644 index 0000000..38f9009 --- /dev/null +++ b/.github/workflows/deploy-app.yml @@ -0,0 +1,126 @@ +name: Reusable Deploy (staging → smoke → production) + +# Single shared deploy pipeline for every peptiderepo plugin and the theme. +# Callers pass `target_path` (the wp-content subpath) and `secrets: inherit`, +# and the deploy logic lives HERE — fix it once instead of in six copy-pasted +# per-repo deploy.yml files (the drift that broke every deploy in May 2026). +# +# Staging deploy → smoke (informational) → production deploy (env-gated) with a +# pre/post active-plugin-count guard (the 2026-04-23 WP-core-clobber tripwire), +# LiteSpeed purge, and a health check. + +on: + workflow_call: + inputs: + target_path: + description: "wp-content subpath, e.g. wp-content/plugins/prautoblogger or wp-content/themes/peptide-starter" + required: true + type: string + run_smoke: + description: "Run the staging smoke suite (informational; never gates prod)" + required: false + default: true + type: boolean + secrets: + SSH_PRIVATE_KEY: + required: true + SSH_HOST: + required: true + SSH_PORT: + required: true + SSH_USERNAME: + required: true + STAGING_WP_ADMIN_USER: + required: false + STAGING_WP_ADMIN_PASSWORD: + required: false + +permissions: + contents: read + +jobs: + deploy-staging: + name: Deploy to Staging + runs-on: ubuntu-latest + environment: staging + steps: + - uses: actions/checkout@v4 + - name: Deploy via rsync → staging + run: | + 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 + BASE="/home/${{ secrets.SSH_USERNAME }}/domains/peptiderepo.com/public_html" + rsync -avz --delete \ + --exclude '.git' --exclude '.github' --exclude 'tests' --exclude 'node_modules' \ + --exclude 'vendor' --exclude 'composer.json' --exclude 'composer.lock' \ + --exclude 'phpunit.xml' --exclude 'phpunit.xml.dist' --exclude 'phpcs.xml.dist' \ + --exclude '.phpcs.xml.dist' --exclude 'ARCHITECTURE.md' --exclude 'CONVENTIONS.md' \ + --exclude 'CHANGELOG.md' --exclude 'qa-reviews' \ + -e "ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=30 -o BatchMode=yes" \ + ./ "${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:$BASE/staging/${{ inputs.target_path }}/" + - name: Staging health check + run: | + sleep 5 + echo "Staging responded: HTTP $(curl -s -o /dev/null -w '%{http_code}' https://staging.peptiderepo.com/ || echo 000)" + - name: Cleanup + if: always() + run: rm -f ~/.ssh/deploy_key + + smoke: + name: Smoke tests (staging) + needs: deploy-staging + if: ${{ inputs.run_smoke }} + uses: peptiderepo/peptide-e2e/.github/workflows/smoke.yml@main + secrets: + STAGING_WP_ADMIN_USER: ${{ secrets.STAGING_WP_ADMIN_USER }} + STAGING_WP_ADMIN_PASSWORD: ${{ secrets.STAGING_WP_ADMIN_PASSWORD }} + + deploy-production: + name: Deploy to Production + needs: [deploy-staging] # smoke is informational; not a prod gate + runs-on: ubuntu-latest + environment: production + steps: + - uses: actions/checkout@v4 + - name: Deploy via rsync → production (plugin-inventory guard) + run: | + 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 + BASE="/home/${{ secrets.SSH_USERNAME }}/domains/peptiderepo.com/public_html" + SSHC="ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=30 -o BatchMode=yes ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}" + PRE=$($SSHC "wp --path=$BASE plugin list --status=active --field=name 2>/dev/null | wc -l" || echo 0) + echo "Pre-deploy active plugins: $PRE" + rsync -avz --delete \ + --exclude '.git' --exclude '.github' --exclude 'tests' --exclude 'node_modules' \ + --exclude 'vendor' --exclude 'composer.json' --exclude 'composer.lock' \ + --exclude 'phpunit.xml' --exclude 'phpunit.xml.dist' --exclude 'phpcs.xml.dist' \ + --exclude '.phpcs.xml.dist' --exclude 'ARCHITECTURE.md' --exclude 'CONVENTIONS.md' \ + --exclude 'CHANGELOG.md' --exclude 'qa-reviews' \ + -e "ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=30 -o BatchMode=yes" \ + ./ "${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:$BASE/${{ inputs.target_path }}/" + POST=$($SSHC "wp --path=$BASE plugin list --status=active --field=name 2>/dev/null | wc -l" || echo 0) + echo "Post-deploy active plugins: $POST" + if [ "${PRE:-0}" -gt 0 ] && [ "${POST:-0}" -lt "${PRE:-0}" ]; then + echo "ERROR: active plugin count dropped ${PRE} -> ${POST} after deploy — aborting as failed." + exit 1 + fi + $SSHC "cd $BASE && find wp-content/litespeed/ -type f -delete 2>/dev/null; touch .lscache_purge 2>/dev/null || true" + echo "LiteSpeed cache purged." + - name: Production health check + run: | + sleep 5 + S=$(curl -s -o /dev/null -w '%{http_code}' https://peptiderepo.com) + if [ "$S" != "200" ] && [ "$S" != "403" ]; then echo "ERROR: site returned HTTP $S"; exit 1; fi + echo "Production healthy (HTTP $S)" + - name: Cleanup + if: always() + run: rm -f ~/.ssh/deploy_key + - name: Deploy summary + run: | + echo "## Deployment complete" >> "$GITHUB_STEP_SUMMARY" + echo "- **Target:** ${{ inputs.target_path }}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Commit:** \`${GITHUB_SHA::7}\`" >> "$GITHUB_STEP_SUMMARY" -- 2.49.1