Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions docs/backups/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Backups Supabase Self-Hosted — Documentação

Última atualização: 2026-05-12 — testado e validado.

## Estrutura no container `supabase-backup_backup`

```
/backups/
├── supabase_selfhosted_YYYYMMDD_HHMMSS.dump # backup oficial automático
├── supabase_selfhosted_YYYYMMDD_HHMMSS.dump.sha256
├── archive/ # históricos pré-migração (não tocar)
├── manual/ # snapshots manuais pré-mudança
├── restore-test.sh # ← este script (deploy de docs/backups/)
├── restore-test.log
└── last_error.log
```
Comment on lines +7 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Bloco de estrutura sem linguagem no fence (MD040).

Adicionar linguagem evita warning de lint.

Diff sugerido
-```
+```text
 /backups/
 ├── supabase_selfhosted_YYYYMMDD_HHMMSS.dump       # backup oficial automático
 ├── supabase_selfhosted_YYYYMMDD_HHMMSS.dump.sha256
 ├── archive/                                        # históricos pré-migração (não tocar)
 ├── manual/                                         # snapshots manuais pré-mudança
 ├── restore-test.sh                                 # ← este script (deploy de docs/backups/)
 ├── restore-test.log
 └── last_error.log
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/backups/README.md` around lines 7 - 16, The fenced code block showing
the /backups/ directory structure is missing a language tag which triggers
MD040; update the opening fence from ``` to ```text in the README's directory
tree block so the linter recognizes it as plain text (the block containing the
lines starting with "/backups/" and entries like "restore-test.sh",
"restore-test.log", "last_error.log").


## Configuração atual do backup automático

| Variável | Valor | Significado |
|---|---|---|
| PGHOST | supabase_db | Host alvo (container interno) |
| PGUSER | supabase_admin | Usuário com acesso a tudo |
| PGDATABASE | postgres | BD principal |
| RETENTION_DAYS | 14 | Manter 14 dias |
| MIN_SIZE_BYTES | 104857600 (100 MB) | Backup menor = REJEITADO |
| Formato | --format=custom | Permite restore parcial |
| Compressão | --compress=6 | Bom tradeoff tamanho/CPU |
| Frequência | 24h (sleep 86400) | Diário |

## Disaster Recovery — procedure VALIDADO em 2026-05-12

IMPORTANTE: o dump contém a extensão pg_cron que SÓ roda no BD chamado 'postgres'.
Filtrar antes de restaurar em BD com outro nome.

```bash
# 1. Acessar o container de backup
docker exec -it $(docker ps -qf name=supabase-backup_backup) sh

Comment on lines +38 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Comandos de docker ps podem selecionar container errado.

Nos Lines [38], [87] e [89], o filtro sem âncora pode retornar múltiplos IDs e quebrar docker exec/cp no procedimento de DR/deploy.

Diff sugerido
-docker exec -it $(docker ps -qf name=supabase-backup_backup) sh
+docker exec -it $(docker ps -q --filter "name=^/supabase-backup_backup$") sh
@@
-docker cp docs/backups/restore-test.sh \
-  $(docker ps -qf name=supabase-backup_backup):/backups/restore-test.sh
+docker cp docs/backups/restore-test.sh \
+  $(docker ps -q --filter "name=^/supabase-backup_backup$"):/backups/restore-test.sh
@@
-docker exec $(docker ps -qf name=supabase-backup_backup) \
+docker exec $(docker ps -q --filter "name=^/supabase-backup_backup$") \
   chmod +x /backups/restore-test.sh

Also applies to: 86-90

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/backups/README.md` around lines 38 - 39, Os comandos que usam docker ps
-qf name=supabase-backup_backup podem devolver múltiplos containers e quebrar
docker exec/cp; altere cada ocorrência (por exemplo onde aparece docker exec -it
$(docker ps -qf name=supabase-backup_backup) sh e comandos semelhantes) para
selecionar um único container de forma determinística: use um filtro ancorado
com regex (--filter "name=^supabase-backup_backup$") ou use --format '{{.ID}}'
piped a head -n1 (e.g. CONTAINER=$(docker ps -qf "name=^supabase-backup_backup$"
--format '{{.ID}}' | head -n1) && docker exec -it $CONTAINER sh) para garantir
que docker exec/cp opere no container correto.

# 2. Validar checksum
cd /backups
sha256sum -c supabase_selfhosted_YYYYMMDD_HHMMSS.dump.sha256

# 3. Criar BD destino
psql -c "CREATE DATABASE restore_test;"

# 4. Gerar TOC e filtrar pg_cron
pg_restore -l /backups/supabase_selfhosted_YYYYMMDD_HHMMSS.dump > /tmp/toc.list
grep -Ev '(pg_cron|cron\.)' /tmp/toc.list > /tmp/toc.filtered

# 5. Restore (tempo médio: 2 minutos)
pg_restore -d restore_test --no-owner --no-acl \
--use-list=/tmp/toc.filtered --jobs=4 \
/backups/supabase_selfhosted_YYYYMMDD_HHMMSS.dump

# 6. Validar contagens (esperado: 600+ tables, 17k+ contacts, 1.8M+ messages)
psql -d restore_test -c "SELECT count(*) FROM information_schema.tables WHERE table_schema='public';"
psql -d restore_test -c "SELECT count(*) FROM public.evolution_contacts;"
psql -d restore_test -c "SELECT count(*) FROM public.evolution_messages;"
```

### Resultado do dry-run validado em 2026-05-12 19:54 UTC

| Métrica | Valor |
|---|---|
| Tempo de restore | 121s |
| Warnings ignorados | 23 (todos pg_cron-related, não-bloqueantes) |
| Tamanho restaurado | 1921 MB |
| Tabelas restauradas | 600 |
| evolution_contacts | 17.340 (match com produção) |
| evolution_messages | 1.838.351 (match) |
| profiles | 6 (match) |

## Scripts neste diretório

- **`restore-test.sh`** — Script de teste de restore automatizado. Deve ser deployado
em `/backups/restore-test.sh` dentro do container `supabase-backup_backup`.
- **`install-restore-test-cron.sh`** — Helper que adiciona um cron mensal no HOST da
VPS para executar o restore-test via `docker exec`. Roda dia 1 de cada mês às
04:00 UTC.

## Deploy

```bash
# 1. Copiar scripts para o container
docker cp docs/backups/restore-test.sh \
$(docker ps -qf name=supabase-backup_backup):/backups/restore-test.sh

docker exec $(docker ps -qf name=supabase-backup_backup) \
chmod +x /backups/restore-test.sh

# 2. Instalar cron no HOST (não no container)
bash docs/backups/install-restore-test-cron.sh
```

## Pendências conhecidas

- [ ] **CRÍTICO**: Off-site backup (R2 Cloudflare). Atualmente SPOF — apenas local na VPS.
- Worker `backup-upload-r2.adm01.workers.dev` existe mas retorna 401 (secret incorreto).
- Script `/workspace/scripts/backup-to-r2.sh` no host falhando desde 04-mai-2026.
- Próximo passo: regenerar secret no Cloudflare Dashboard OU criar R2 API Token S3-compat.
- [ ] **ALTO**: Alerta de falha (webhook Slack/n8n se BACKUP_FAILED_* aparecer).
- [ ] **MÉDIO**: Validação periódica de checksum (cron `sha256sum -c`).
- [x] **MÉDIO**: Restore test agendado mensal — script pronto, cron documentado.

## Histórico

- 2026-04-30 a 2026-05-04: backups FATOR X cloud (5 arquivos arquivados em `/backups/archive/`).
- 2026-05-05 a 2026-05-11: 7 backups quebrados (20 bytes cada, PGHOST apontando para
FATOR X que caiu). DELETADOS em 2026-05-12.
- 2026-05-12 10:32: primeiro backup self-hosted válido (606 MB).
- 2026-05-12 19:54: dry-run de restore validado em produção com sucesso (121s).
44 changes: 44 additions & 0 deletions docs/backups/install-restore-test-cron.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash
# Instala cron pra rodar restore-test mensalmente
# Executa dentro do container supabase-backup_backup via docker exec
# Roda no HOST da VPS (não dentro do container)

set -e

BACKUP_CONTAINER="supabase-backup_backup"

# Pré-flight: o container precisa existir (e ser único) no momento da instalação.
# `docker ps -qf name=...` pode retornar múltiplos IDs com prefixo igual ou
# zero IDs — qualquer caso faria o cron falhar silenciosamente em produção.
RUNNING_IDS=$(docker ps -q --filter "name=^/${BACKUP_CONTAINER}$" || true)
if [ -z "$RUNNING_IDS" ]; then
echo "❌ Container ${BACKUP_CONTAINER} não está rodando."
echo " Inicie o Supabase Self-Hosted antes de instalar o cron."
exit 1
fi
if [ "$(printf '%s\n' "$RUNNING_IDS" | wc -l)" -gt 1 ]; then
echo "❌ Mais de um container ${BACKUP_CONTAINER} rodando — abortando."
echo "$RUNNING_IDS"
exit 1
fi

# Comando a executar: usa o NOME exato do container (não filter por ID),
# para que o cron continue funcionando mesmo se o container reiniciar e
# obtiver outro ID. Falha clara se o container não existir naquele momento.
CRON_LINE="0 4 1 * * /usr/bin/docker exec ${BACKUP_CONTAINER} /backups/restore-test.sh >> /var/log/restore-test-cron.log 2>&1"

Comment on lines +8 to +29
# Verifica se já existe
if crontab -l 2>/dev/null | grep -qF 'restore-test.sh'; then
echo "✅ Cron já instalado:"
crontab -l | grep restore-test
exit 0
fi
Comment on lines +30 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validação de cron está ampla demais.

No Line [31], buscar só por restore-test.sh pode detectar entrada errada e abortar instalação válida. Compare com a linha completa esperada.

Diff sugerido
-if crontab -l 2>/dev/null | grep -qF 'restore-test.sh'; then
+if crontab -l 2>/dev/null | grep -qF "$CRON_LINE"; then
   echo "✅ Cron já instalado:"
-  crontab -l | grep restore-test
+  crontab -l | grep -F "$CRON_LINE"
   exit 0
 fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/backups/install-restore-test-cron.sh` around lines 30 - 35, The current
cron presence check uses a too-broad grep for 'restore-test.sh' (if crontab -l
2>/dev/null | grep -qF 'restore-test.sh') which can produce false positives;
update the check to match the full expected cron line exactly (e.g., use grep
-Fx or compare against the complete cron entry string) so the if block that
echoes and exits only runs when the exact cron entry exists; adjust the
subsequent crontab display (crontab -l | grep restore-test) to show the exact
matched line as well.


# Adiciona ao crontab
(crontab -l 2>/dev/null; echo "$CRON_LINE") | crontab -
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Append cron line even when no crontab exists

With set -e enabled, the append command fails on first-time installs: when the user has no crontab yet, crontab -l returns non-zero and the subshell exits before echo "$CRON_LINE" runs, so nothing is piped into crontab -. The script can then report success while leaving the restore job unscheduled.

Useful? React with 👍 / 👎.

echo "✅ Cron instalado: roda dia 1 de cada mês às 04:00 UTC"
echo "$CRON_LINE"
echo ""
echo "Logs em /var/log/restore-test-cron.log (host) + /backups/restore-test.log (container)"
echo "Pra rodar agora manualmente:"
echo " docker exec ${BACKUP_CONTAINER} /backups/restore-test.sh"
126 changes: 126 additions & 0 deletions docs/backups/restore-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/bin/sh
# Restore test automatizado
# Roda mensalmente via cron (cron 0 4 1 * *)
# Pega o backup mais recente, restaura em BD temporário,
# valida contagens, dropa, e loga resultado
#
# DEPLOY: este script deve ser copiado para /backups/restore-test.sh
# dentro do container supabase-backup_backup
#
# Validado em produção em 2026-05-12: PASSOU em 121s
# - 600 tables restauradas
# - 17.340 evolution_contacts (match)
# - 1.838.351 evolution_messages (match)
# - 1921 MB

set -e

LOG=/backups/restore-test.log

# Timestamp avaliado no momento de cada log line (não no startup),
# para que restores longos tenham logs com horário real de cada evento.
log() {
echo "[$(date -Iseconds)] $*" | tee -a "$LOG"
}

log "==================== RESTORE TEST INICIADO ===================="

# 1. Identificar backup mais recente.
# `ls` com `set -e`: append `|| true` na substituição para que ausência
# de match não derrube o script antes do check `[ -z "$LATEST" ]`.
LATEST=$(ls -t /backups/supabase_selfhosted_*.dump 2>/dev/null | head -1 || true)

if [ -z "$LATEST" ]; then
log "ERRO: nenhum backup encontrado em /backups/supabase_selfhosted_*.dump"
touch "/backups/RESTORE_TEST_FAILED_$(date +%Y%m%d_%H%M%S)"
exit 1
fi
Comment on lines +16 to +37

log "Backup alvo: $LATEST ($(du -h "$LATEST" | cut -f1))"

# 2. Validar SHA256 — subshell + if direto evita que `set -e` interrompa
# antes do bloco de erro (caso `cd` falhe ou checksum não bater).
if [ -f "${LATEST}.sha256" ]; then
if (cd /backups && sha256sum -c "$(basename "$LATEST").sha256" >/dev/null 2>&1); then
log "OK: SHA256 válido"
else
log "ERRO: SHA256 FALHOU - backup corrompido!"
touch "/backups/RESTORE_TEST_FAILED_$(date +%Y%m%d_%H%M%S)"
exit 2
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
else
log "AVISO: sem arquivo .sha256 para validar"
fi

# 3. Criar BD temporário (identificador é seguro: composto só por timestamp).
DBNAME="restore_test_$(date +%Y%m%d_%H%M%S)"
log "Criando BD temporário: $DBNAME"

Comment on lines +56 to +58
# DROP com FORCE (PG13+) termina conexões pendentes em vez de falhar.
# Fallback para variante sem FORCE em PG<13: termina backends manualmente.
psql -c "DROP DATABASE IF EXISTS \"$DBNAME\" WITH (FORCE);" >/dev/null 2>&1 \
|| {
psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DBNAME' AND pid <> pg_backend_pid();" >/dev/null 2>&1 || true
psql -c "DROP DATABASE IF EXISTS \"$DBNAME\";" >/dev/null 2>&1 || true
}
psql -c "CREATE DATABASE \"$DBNAME\";" >/dev/null 2>&1

# 4. Gerar TOC filtrada (sem pg_cron que requer BD chamado postgres).
# `grep -E` com alternação `|` é portável; o antigo `grep -v 'pg_cron\|cron\.'`
# depende de extensão GNU e silenciosamente passa pg_cron em outros greps.
pg_restore -l "$LATEST" > "/tmp/toc-${DBNAME}.list" 2>/dev/null
grep -Ev '(pg_cron|cron\.)' "/tmp/toc-${DBNAME}.list" > "/tmp/toc-${DBNAME}.filtered"
Comment on lines +71 to +72
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle TOC generation failures before exiting with set -e

After creating the temporary database, pg_restore -l and the TOC filter run without error handling under set -e; if either command fails (e.g., damaged dump or parse error), the script exits immediately before creating a failure marker and before dropping the temp database. This leaves orphan restore_test_* databases and loses the script’s intended failure signaling path for this error class.

Useful? React with 👍 / 👎.


Comment on lines +55 to +73
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Falta cleanup garantido em falhas intermediárias com set -e.

Se falhar antes da etapa final, o banco temporário e TOCs podem ficar órfãos. Coloque cleanup em trap EXIT para garantir idempotência.

Diff sugerido
 set -e
 
 LOG=/backups/restore-test.log
+DBNAME=""
+TOC_LIST=""
+TOC_FILTERED=""
+
+cleanup() {
+  if [ -n "$DBNAME" ]; then
+    psql -c "DROP DATABASE IF EXISTS \"$DBNAME\" WITH (FORCE);" >/dev/null 2>&1 \
+      || psql -c "DROP DATABASE IF EXISTS \"$DBNAME\";" >/dev/null 2>&1 \
+      || true
+  fi
+  [ -n "$TOC_LIST" ] && rm -f "$TOC_LIST" "$TOC_FILTERED"
+}
+trap cleanup EXIT
@@
 DBNAME="restore_test_$(date +%Y%m%d_%H%M%S)"
@@
-pg_restore -l "$LATEST" > "/tmp/toc-${DBNAME}.list" 2>/dev/null
-grep -Ev '(pg_cron|cron\.)' "/tmp/toc-${DBNAME}.list" > "/tmp/toc-${DBNAME}.filtered"
+TOC_LIST="/tmp/toc-${DBNAME}.list"
+TOC_FILTERED="/tmp/toc-${DBNAME}.filtered"
+pg_restore -l "$LATEST" > "$TOC_LIST" 2>/dev/null
+grep -Ev '(pg_cron|cron\.)' "$TOC_LIST" > "$TOC_FILTERED"
@@
-# 8. Cleanup (idempotente — DROP IF EXISTS WITH FORCE limpa mesmo se algo
-# ficar pendente no BD temporário).
-log "Dropping BD temporário"
-psql -c "DROP DATABASE IF EXISTS \"$DBNAME\" WITH (FORCE);" >/dev/null 2>&1 \
-  || psql -c "DROP DATABASE IF EXISTS \"$DBNAME\";" >/dev/null 2>&1 \
-  || true
-rm -f "/tmp/toc-${DBNAME}.list" "/tmp/toc-${DBNAME}.filtered"
+# 8. Cleanup automático via trap EXIT.
+log "Cleanup automático finalizado"

Also applies to: 118-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/backups/restore-test.sh` around lines 55 - 73, The script currently
creates a temporary DB (DBNAME) and TOC files but can leave them orphaned if the
script exits early under set -e; add a trap on EXIT that runs a cleanup function
which drops the temporary database (using the same DBNAME logic: DROP DATABASE
IF EXISTS "$DBNAME" with the FORCE fallback logic already present) and removes
the "/tmp/toc-${DBNAME}.list" and "/tmp/toc-${DBNAME}.filtered" files; ensure
the cleanup function is defined before DB creation and the trap 'trap cleanup
EXIT' is registered immediately after DBNAME is set so it always runs even on
early failures.

# 5. Restore com tempo medido + captura de exit code real.
# Antes `|| true` mascarava falhas/timeouts; agora RESTORE_OK entra como
# primeira condição obrigatória no check de sucesso.
START=$(date +%s)
log "Iniciando pg_restore (timeout 10min)..."

RESTORE_OK=1
if ! timeout 600 pg_restore -d "$DBNAME" --no-owner --no-acl \
--use-list="/tmp/toc-${DBNAME}.filtered" --jobs=4 \
"$LATEST" >> "$LOG" 2>&1; then
RESTORE_OK=0
log "ERRO: pg_restore falhou (exit != 0 ou timeout 600s atingido)"
fi

END=$(date +%s)
DURATION=$((END - START))
log "Restore concluído em ${DURATION}s (RESTORE_OK=${RESTORE_OK})"

# 6. Validar contagens chave
TABLE_COUNT=$(psql -d "$DBNAME" -tAc "SELECT count(*) FROM information_schema.tables WHERE table_schema='public';" 2>/dev/null || echo "0")
CONTACTS=$(psql -d "$DBNAME" -tAc "SELECT count(*) FROM public.evolution_contacts;" 2>/dev/null || echo "0")
MESSAGES=$(psql -d "$DBNAME" -tAc "SELECT count(*) FROM public.evolution_messages;" 2>/dev/null || echo "0")
PROFILES=$(psql -d "$DBNAME" -tAc "SELECT count(*) FROM public.profiles;" 2>/dev/null || echo "0")
SIZE=$(psql -d "$DBNAME" -tAc "SELECT pg_size_pretty(pg_database_size('$DBNAME'));" 2>/dev/null || echo "unknown")

log "Resultado:"
log " - tables públicas: $TABLE_COUNT"
log " - evolution_contacts: $CONTACTS"
log " - evolution_messages: $MESSAGES"
log " - profiles: $PROFILES"
log " - tamanho restaurado: $SIZE"

# 7. Avaliar sucesso. RESTORE_OK == 1 é condição obrigatória primeiro;
# thresholds são guard adicional contra restore parcial que sobe alguma
# coisa mas não o suficiente para considerar válido.
if [ "$RESTORE_OK" -eq 1 ] && [ "$TABLE_COUNT" -ge 400 ] && [ "$CONTACTS" -ge 10000 ]; then
log "✅ RESTORE TEST PASSOU"
STATUS="PASSED"
else
log "❌ RESTORE TEST FALHOU - exit do pg_restore != 0 ou threshold não atingido"
STATUS="FAILED"
touch "/backups/RESTORE_TEST_FAILED_$(date +%Y%m%d_%H%M%S)"
Comment on lines +113 to +115
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return non-zero when restore test is marked failed

When validation fails, this branch only logs and creates RESTORE_TEST_FAILED_*, then the script continues to normal cleanup and exits successfully. That makes callers see a success status even when pg_restore failed or thresholds were not met, so automated schedulers/monitors that rely on process exit code will miss a failed DR test unless they parse marker files manually.

Useful? React with 👍 / 👎.

fi
Comment on lines +106 to +116
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Evite PASS em restore parcial.

No Line [109], o sucesso exige só TABLE_COUNT e CONTACTS, apesar de MESSAGES e PROFILES já serem medidos. Isso pode aprovar restore incompleto.

Diff sugerido
-if [ "$RESTORE_OK" -eq 1 ] && [ "$TABLE_COUNT" -ge 400 ] && [ "$CONTACTS" -ge 10000 ]; then
+if [ "$RESTORE_OK" -eq 1 ] \
+  && [ "$TABLE_COUNT" -ge 400 ] \
+  && [ "$CONTACTS" -ge 10000 ] \
+  && [ "$MESSAGES" -ge 1000000 ] \
+  && [ "$PROFILES" -ge 1 ]; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 7. Avaliar sucesso. RESTORE_OK == 1 é condição obrigatória primeiro;
# thresholds são guard adicional contra restore parcial que sobe alguma
# coisa mas não o suficiente para considerar válido.
if [ "$RESTORE_OK" -eq 1 ] && [ "$TABLE_COUNT" -ge 400 ] && [ "$CONTACTS" -ge 10000 ]; then
log "✅ RESTORE TEST PASSOU"
STATUS="PASSED"
else
log "❌ RESTORE TEST FALHOU - exit do pg_restore != 0 ou threshold não atingido"
STATUS="FAILED"
touch "/backups/RESTORE_TEST_FAILED_$(date +%Y%m%d_%H%M%S)"
fi
# 7. Avaliar sucesso. RESTORE_OK == 1 é condição obrigatória primeiro;
# thresholds são guard adicional contra restore parcial que sobe alguma
# coisa mas não o suficiente para considerar válido.
if [ "$RESTORE_OK" -eq 1 ] \
&& [ "$TABLE_COUNT" -ge 400 ] \
&& [ "$CONTACTS" -ge 10000 ] \
&& [ "$MESSAGES" -ge 1000000 ] \
&& [ "$PROFILES" -ge 1 ]; then
log "✅ RESTORE TEST PASSOU"
STATUS="PASSED"
else
log "❌ RESTORE TEST FALHOU - exit do pg_restore != 0 ou threshold não atingido"
STATUS="FAILED"
touch "/backups/RESTORE_TEST_FAILED_$(date +%Y%m%d_%H%M%S)"
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/backups/restore-test.sh` around lines 106 - 116, The current success
condition only checks RESTORE_OK, TABLE_COUNT and CONTACTS which allows partial
restores to pass; update the conditional that sets STATUS (and the log messages)
to also require measured MESSAGES and PROFILES thresholds (e.g. ensure MESSAGES
and PROFILES are >= their expected minimums) alongside RESTORE_OK, TABLE_COUNT
and CONTACTS, and keep the existing failed branch behavior (log, set
STATUS="FAILED", touch failure marker) so partial restores no longer report as
PASSED.


# 8. Cleanup (idempotente — DROP IF EXISTS WITH FORCE limpa mesmo se algo
# ficar pendente no BD temporário).
log "Dropping BD temporário"
psql -c "DROP DATABASE IF EXISTS \"$DBNAME\" WITH (FORCE);" >/dev/null 2>&1 \
|| psql -c "DROP DATABASE IF EXISTS \"$DBNAME\";" >/dev/null 2>&1 \
|| true
rm -f "/tmp/toc-${DBNAME}.list" "/tmp/toc-${DBNAME}.filtered"

log "==================== RESTORE TEST $STATUS ===================="
Loading
Loading