Backup e restore

O WordPress de cursos tem duas camadas de backup: um backup lógico diário (banco + arquivos) num bucket da Oracle Cloud, com retenção longa, e snapshots diários dos discos para voltar rápido. O restore padrão é feito no sandbox, o que também serve de ensaio.

Implementado em 03/10/2026. Só começa a valer depois do terraform apply (iac/oci-legacy-sites-backup.tf), do criar-secret-backup.sh e do sync do ArgoCD. Até o primeiro backup conferido, a lacuna de Infraestrutura continua aberta.

O que é copiado

Item Backup lógico Observação

Banco enj (posts, usuários, matrículas, pedidos, configurações)

sim

mysqldump --single-transaction

wp-content/uploads (imagens, PDFs, mídia)

sim

Plugins, temas, traduções, mu-plugins

sim

Core do WordPress e wp-config.php

sim

o wp-config.php tem salts e o prefixo iuxcl_

wp-content/cache

não

regenerado pelo plugin de cache

ai1wm-backups, updraft, aiowps_backups, debug.log

não

redundantes; podem ter cópias da época da invasão

Manifests (Deployment, nginx, PHP-FPM, Ingress)

—

estão no Git (gitops/legacy-sites/)

Secrets mariadb-credentials, wp-cursos-db

não

guardar os valores no gerenciador de senhas

Certificado legacy-sites-tls

não

o cert-manager emite outro

Bancos enj_root e enj_fuji, sites static-root e wp-fuji

não

fora do escopo

Os snapshots de disco copiam os volumes inteiros (mysql-data e wp-content-cursos), cache incluído.

Camada 1: backup lógico diário

Item Valor

Onde roda

CronJob wp-cursos-backup no namespace legacy-sites (gitops/legacy-sites/backup.yaml)

Quando

todo dia às 03:30 (horário de Brasília), fora da janela de uso

Destino

bucket OCI legacy-sites-backup, objetos em diario/<data-hora UTC>/: enj.sql.gz, webroot.tar.gz, SHA256SUMS

Retenção

diario/: 14 dias, em Standard (restore imediato). semanal/ (aos domingos, cópia do mesmo backup): 90 dias, em Archive (restore leva ~1h)

Credencial

usuário OCI legacy-sites-backup-sa, que só cria objetos: não apaga nem sobrescreve. Quem invadir o cluster não consegue destruir os backups antigos

Restore

usuário legacy-sites-restore-sa, só leitura, usado num Secret temporário pelo restaurar.sh

Monitoração

a skill wp-invasao alerta se o último backup bem-sucedido tiver mais de 26h

A retenção de 90 dias existe por causa do incidente de 2026: o site ficou invadido cerca de cinco semanas sem ninguém perceber. Um backup só de 14 dias teria apenas cópias já comprometidas.

Ativação (uma vez)

# 1. iac: bucket, lifecycle, usuários de backup e restore, política de snapshot.
#    Preencher legacy_sites_volume_ocids (terraform.tfvars) com os OCIDs dos volumes:
#    kubectl --context oracle get pv $(kubectl --context oracle -n legacy-sites get pvc mysql-data \
#      -o jsonpath='{.spec.volumeName}') -o jsonpath='{.spec.csi.volumeHandle}'   (idem wp-content-cursos)
T=$(grep -oE '^resource "[a-z_]+" "legacy-sites[a-z-]*"' oci-legacy-sites-backup.tf \
    | awk '{gsub(/"/,""); printf "-target=%s.%s ", $2, $3}')
terraform plan  $T -out=legacy-sites-backup.out
terraform apply legacy-sites-backup.out

# 2. gitops: usuário `backup` no MariaDB e Secrets (nenhum valor é impresso)
legacy-sites/criar-secret-backup.sh

# 3. commit/push do gitops (ArgoCD cria o CronJob) e um backup de teste
kubectl --context oracle -n legacy-sites create job --from=cronjob/wp-cursos-backup backup-teste-1
kubectl --context oracle -n legacy-sites logs -f job/backup-teste-1 --all-containers
legacy-sites/restaurar.sh --listar

Backup avulso

Antes de qualquer mudança arriscada (atualização de plugin, de PHP, a virada do guia de atualização):

kubectl --context oracle -n legacy-sites create job --from=cronjob/wp-cursos-backup antes-de-X

O avulso cai em diario/ e expira em 14 dias como os outros.

Camada 2: snapshots dos discos

Política legacy-sites-diario-7d da OCI (Terraform, iac/oci-legacy-sites-backup.tf): backup incremental diário às 04:00 dos Block Volumes de mysql-data e wp-content-cursos, retenção de 7 dias. O snapshot continua existindo mesmo que o PVC seja apagado.

É crash-consistent (o banco é copiado como estaria depois de uma queda de energia). Use para desastre de disco ou PVC apagado. Para voltar dados, use a camada 1.

Restore por snapshot (manual)

  1. Console da OCI → Block Storage → Block Volume Backups: escolher o backup e criar um volume a partir dele, no mesmo domínio de disponibilidade dos nós.

  2. Criar um PersistentVolume estático apontando para o OCID do volume novo (driver blockvolume.csi.oraclecloud.com) e um PVC ligado a ele.

  3. Parar o WordPress (e o MariaDB, se for o mysql-data), trocar o claimName no manifest do gitops e sincronizar.

  4. Conferir o site e rodar a skill wp-invasao.

Restore do backup lógico

Script gitops/legacy-sites/restaurar.sh. O download e a extração rodam num Job dentro do cluster.

legacy-sites/restaurar.sh --listar                                   # backups disponíveis
legacy-sites/restaurar.sh diario/2026-10-04T0630Z                    # no sandbox (padrão)
legacy-sites/restaurar.sh diario/2026-10-04T0630Z --destino producao # na produção
legacy-sites/restaurar.sh --desarquivar semanal/2026-09-28T0630Z     # semanal: reidratar antes (~1h)

Na produção, o script:

  1. pede para digitar o domínio;

  2. roda um backup avulso do estado atual (pule com --sem-backup-previo se o banco estiver corrompido);

  3. suspende o CronJob de backup e para o WordPress (site fora do ar);

  4. baixa o backup, confere o SHA256SUMS, recria o banco enj e substitui o webroot;

  5. sobe o WordPress, reativa o CronJob e testa a home.

Se o restore falhar, o WordPress fica parado, para não subir com metade dos dados.

No sandbox, depois do restore, o script roda os ajustes de segurança do sandbox (legacy-sites-staging/ajustar-sandbox.sh): wp-config.php apontando para o banco do sandbox, salts novos, ManageWP desligado, gateways em modo de teste.

Depois de uma invasão, escolha um backup anterior ao primeiro sinal dela (ver Incidente: invasão do WordPress de cursos (ago–out/2026)) e rode a skill wp-invasao depois do restore. O webroot muda com o restore: revise as diferenças e grave uma nova linha de base.

Ensaio mensal

Um restore que nunca foi testado não é backup. Uma vez por mês:

legacy-sites/restaurar.sh --listar
legacy-sites/restaurar.sh diario/<o mais recente>     # no sandbox

Conferir o sandbox pelo navegador do especialista (login, um curso, uma imagem de uploads) e anotar a data e a duração do restore.