Skip to content

Ficha: pool-backed, editable reregistration template (#951 phase 2) (… #495

Ficha: pool-backed, editable reregistration template (#951 phase 2) (…

Ficha: pool-backed, editable reregistration template (#951 phase 2) (… #495

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