Ficha: pool-backed, editable reregistration template (#951 phase 2) (… #495
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: Deploy develop → testes | |
| # Sync da branch `develop` pro servidor de testes via SSH + rsync. | |
| # Dispara em cada push em `develop` (após merge de PR ou push direto). | |
| # Pré-requisitos operacionais documentados em CLAUDE.md (seção | |
| # "Develop branch workflow"): SSH habilitado na hospedagem, par de | |
| # chaves gerado, 4 secrets cadastrados no GitHub. | |
| on: | |
| push: | |
| branches: [develop] | |
| workflow_dispatch: | |
| # Manual re-deploy útil quando o servidor de testes foi resetado | |
| # ou o último push falhou e o estado da develop não mudou. | |
| concurrency: | |
| # Cancelar pushes consecutivos: só o último vence. Evita corrida | |
| # de dois rsync paralelos sobre o mesmo destino. | |
| group: deploy-develop-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| deploy: | |
| name: rsync to testes | |
| runs-on: ubuntu-latest | |
| # Rede de segurança final: com BatchMode + `timeout 300` por tentativa e 3 | |
| # tentativas (2× backoff de 120s), o pior caso é ~19min; 25min cobre isso | |
| # mais o checkout/keyscan. Garante que o job nunca fique pendurado até o | |
| # cancelamento (o caso de ~15min do deploy do 35befc1). | |
| timeout-minutes: 25 | |
| # Sem environment gating — develop é território livre por design. | |
| # Se um dia esse workflow virar `deploy-prod`, aí sim entra | |
| # `environment: production` com required reviewers. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Shallow clone OK — rsync envia working tree, não histórico. | |
| fetch-depth: 1 | |
| - name: Configure SSH | |
| env: | |
| SSH_KEY: ${{ secrets.TESTES_SSH_KEY }} | |
| SSH_HOST: ${{ secrets.TESTES_SSH_HOST }} | |
| SSH_PORT: ${{ secrets.TESTES_SSH_PORT }} | |
| run: | | |
| mkdir -p ~/.ssh | |
| echo "$SSH_KEY" > ~/.ssh/deploy_key | |
| chmod 600 ~/.ssh/deploy_key | |
| # `TESTES_SSH_PORT` é opcional — VPS padrão usa 22; hospedagem | |
| # gerenciada (Hostinger, KingHost) costuma usar porta alta. | |
| PORT="${SSH_PORT:-22}" | |
| # Best-effort: keyscan pode falhar (firewall, host atrás de | |
| # CDN, etc.) sem derrubar o deploy. O step de rsync abaixo usa | |
| # `StrictHostKeyChecking=accept-new` como fallback TOFU. | |
| ssh-keyscan -p "$PORT" -H "$SSH_HOST" >> ~/.ssh/known_hosts 2>/dev/null || true | |
| - name: Rsync to testes | |
| env: | |
| SSH_HOST: ${{ secrets.TESTES_SSH_HOST }} | |
| SSH_USER: ${{ secrets.TESTES_SSH_USER }} | |
| SSH_PORT: ${{ secrets.TESTES_SSH_PORT }} | |
| REMOTE_PATH: ${{ secrets.TESTES_REMOTE_PATH }} | |
| run: | | |
| PORT="${SSH_PORT:-22}" | |
| # `--delete` remove arquivos no destino que não existem na | |
| # origem (idempotência total). Exclusões abaixo cobrem: | |
| # - VCS / CI metadata que não pertence ao runtime do plugin. | |
| # - Dependências de dev (composer require-dev only, node_modules). | |
| # - Testes e ferramentas de análise estática. | |
| # - Manifests de build/lint (composer.json, package.json e locks | |
| # não servem runtime do WordPress; analisadores externos não | |
| # precisam deles na pasta do plugin em produção). | |
| # - Docs de repositório (CONTRIBUTING.md, SECURITY.md) que vivem | |
| # no GitHub e não no runtime. `CHANGELOG.md` é mantido por | |
| # preferência — consulta histórica via SSH. | |
| # - Saídas de coverage / build intermediário. | |
| # `StrictHostKeyChecking=accept-new`: TOFU — aceita a chave do | |
| # host na primeira conexão e exige match em conexões futuras. | |
| # Mais seguro que `no` (vulnerável a MITM); funciona quando o | |
| # `ssh-keyscan` acima falhou silenciosamente. | |
| # | |
| # `ConnectTimeout` + retry: a hospedagem de testes ocasionalmente | |
| # recusa/derruba a conexão (servidor reiniciando, blip de rede). | |
| # Sem timeout explícito o connect espera ~2min no SYN e o deploy | |
| # falha num único blip — deixando o testes numa versão antiga. Aqui | |
| # cada tentativa falha rápido (30s) e o rsync (idempotente) é | |
| # repetido até 3× com backoff antes de dar o build como falho. | |
| # Delay fixo de 120s entre tentativas (janela total ~5,5min): o | |
| # backoff original de 20s/40s (~2,5min) era mais curto que um | |
| # restart típico da hospedagem — as 3 tentativas caíam dentro da | |
| # mesma indisponibilidade (run 229 e o deploy de 6.12.0 falharam | |
| # assim). Espaçar mais as mesmas 3 tentativas cobre o restart sem | |
| # alongar o caso de falha real além de ~5,5min. | |
| # | |
| # `BatchMode=yes`: nunca cair num prompt interativo. Sem isso, se a | |
| # autenticação por chave não vinga (chave com passphrase, chave | |
| # errada, ou fallback pra senha), o SSH bloqueia esperando input que | |
| # o runner nunca fornece — o job pendura até ser cancelado (o hang de | |
| # ~15min visto no deploy do 35befc1). Com BatchMode, esse caso vira | |
| # um "Permission denied (publickey)" imediato e legível. | |
| # | |
| # `timeout 300` no rsync: o ConnectTimeout só cobre o SYN inicial — | |
| # uma transferência que trava DEPOIS de conectar (stall de I/O, | |
| # ServerAlive não disparando) ainda penduraria a tentativa. O wrapper | |
| # aborta (exit 124) qualquer tentativa que passe de 5min (um deploy | |
| # real leva ~1-2min), e o `until` trata como falha e faz o retry. | |
| SSH_CMD="ssh -i ~/.ssh/deploy_key -p $PORT -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=30 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" | |
| attempt=1 | |
| max_attempts=3 | |
| until timeout 300 rsync -avz --delete \ | |
| --exclude='.git/' \ | |
| --exclude='.github/' \ | |
| --exclude='.githooks/' \ | |
| --exclude='.gitignore' \ | |
| --exclude='.gitattributes' \ | |
| --exclude='.distignore' \ | |
| --exclude='vendor/' \ | |
| --exclude='node_modules/' \ | |
| --exclude='tests/' \ | |
| --exclude='coverage-js/' \ | |
| --exclude='coverage/' \ | |
| --exclude='build/' \ | |
| --exclude='dist/' \ | |
| --exclude='.phpunit.result.cache' \ | |
| --exclude='phpunit.xml' \ | |
| --exclude='phpunit.xml.dist' \ | |
| --exclude='phpstan.neon' \ | |
| --exclude='phpstan.neon.dist' \ | |
| --exclude='phpstan-stubs.php' \ | |
| --exclude='phpcs.xml' \ | |
| --exclude='phpcs.xml.dist' \ | |
| --exclude='patchwork.json' \ | |
| --exclude='.eslintrc*' \ | |
| --exclude='eslint.config.*' \ | |
| --exclude='.stylelintrc*' \ | |
| --exclude='vitest.config.*' \ | |
| --exclude='composer.json' \ | |
| --exclude='composer.lock' \ | |
| --exclude='package.json' \ | |
| --exclude='package-lock.json' \ | |
| --exclude='CLAUDE.md' \ | |
| --exclude='CONTRIBUTING.md' \ | |
| --exclude='SECURITY.md' \ | |
| -e "$SSH_CMD" \ | |
| ./ "${SSH_USER}@${SSH_HOST}:${REMOTE_PATH}/"; do | |
| status=$? | |
| if [ "$attempt" -ge "$max_attempts" ]; then | |
| echo "::error::rsync to testes failed after ${max_attempts} attempts (last exit ${status})." | |
| exit "$status" | |
| fi | |
| backoff=120 | |
| echo "::warning::rsync attempt ${attempt} failed (exit ${status}); retrying in ${backoff}s…" | |
| sleep "$backoff" | |
| attempt=$((attempt + 1)) | |
| done | |
| - name: Cleanup SSH key | |
| if: always() | |
| run: rm -f ~/.ssh/deploy_key |