Pular para o conteúdo
Nelcota
EN : Read in English

Backup e restauração

11 min de leitura Editar no GitHub

Capture e restaure um banco junto de seu snapshot de arquivos em disco. Você precisa de host/projeto inicializado e selecionado, ferramentas PostgreSQL pelo runtime, espaço em disco suficiente e destino externo privado quando configurado.

Correções na fonte após 0.4.0 ainda não foram publicadas: use a orientação da versão correspondente e verifique o artefato real. Mantenha um par compatível verificado antes de recuperação destrutiva; PITR do banco tem garantias diferentes de restauração de dump.

Backup

shell
nelcota -p shop backup            # pg_dump -Fc → projects/shop/backups/nelcota-shop-<date>.dump
                                  # (+ s3://<bucket>/shop/ when host.env has a bucket)
nelcota -p shop backup --upload   # requires the upload: fails if host.env has no bucket
nelcota backup --all --keep 7     # every project; keeps the 7 most recent local dumps (--keep ≥ 1)

backup faz upload automaticamente sempre que as variáveis S3 estão em host.env, então adicionar um bucket após init não exige outra alteração. --upload força o upload e falha se não houver um bucket.

O primeiro init (como root) instala uma tarefa cron diária às 03:00 (/etc/cron.d/nelcota-backup):

text
0 3 * * * root '/usr/local/bin/nelcota' -C '/opt/nelcota' backup --all --keep 7 >> /var/log/nelcota-backup.log 2>&1

Um projeto com problemas não impede o backup dos outros; o comando lista as falhas e termina com código diferente de zero. nelcota status mostra a idade do último backup de cada projeto, e nelcota doctor informa uma tarefa cron ausente ou um backup com mais de 26 horas.

Com armazenamento em disco, cada dump tem um diretório irmão <dump>.files/ com as versões imutáveis dos objetos e um manifesto SHA-256. O dump e a lista de arquivos compartilham um único snapshot exportado do Postgres. Um bloqueio SHARE em storage.objects impede alterações de metadados durante a captura; leituras continuam, mas escritas de arquivos podem aguardar.

O upload publica esse diretório em s3://<bucket>/<project>/<dump>.files/ antes de enviar o dump. Substituições e exclusões posteriores não podem alterar um snapshot antigo. Mantenha cada dump junto de seu diretório de arquivos; --keep remove ambos localmente. Configure regras de ciclo de vida remoto para os dois membros do par.

No Unix, dumps, arquivos de snapshot, manifestos e arquivos de ambiente são criados com modo 0600, antes de escrever dados. Diretórios de backup e arquivo usam 0700. Cópias mantêm a política privada em vez do modo da origem; downloads executam com umask 077 e seus conjuntos de arquivos são protegidos depois. Arquivos de ambiente existentes têm suas permissões restringidas antes de serem sobrescritos. Windows mantém a política de ACL do sistema de arquivos; os modos Unix não são simulados nele.

O dump usa o formato personalizado de pg_dump: inclui esquema, dados, políticas RLS, funções e o esquema auth (usuários com seus hashes argon2id). Papéis do cluster (anon, authenticated...) não estão no dump, mas as migrações do Nelcota os recriam em qualquer nova instalação.

Compatível com S3

Qualquer serviço com API S3: AWS S3, Backblaze B2, Cloudflare R2, Wasabi, MinIO. Variáveis em host.env (valem para todos os projetos):

shell
NELCOTA_BACKUP_S3_ENDPOINT=https://s3.us-west-002.backblazeb2.com
NELCOTA_BACKUP_S3_BUCKET=my-backups
NELCOTA_BACKUP_S3_ACCESS_KEY=...
NELCOTA_BACKUP_S3_SECRET_KEY=...
NELCOTA_BACKUP_S3_REGION=us-west-002

Uploads no Docker usam a imagem amazon/aws-cli. Hosts systemd executam AWS CLI v2 nativo, que você precisa instalar separadamente. Defina acesso privado ao bucket e regras explícitas de retenção remota para dumps e snapshots de arquivos. Teste readback e restauração em outro host; um upload bem-sucedido não prova recuperabilidade.

Guarde host.env e o .env de cada projeto em um lugar seguro, separado do bucket. Eles não estão no backup (contêm segredos). Sem o .env do projeto, a restauração ainda funciona, mas a chave JWT muda e todos os usuários precisam se autenticar novamente (as senhas continuam funcionando).

Restauração

shell
nelcota -p shop restore projects/shop/backups/nelcota-shop-20261006T030000Z.dump

A restauração pede confirmação (--yes a dispensa), coloca o site Caddy do projeto em manutenção (HTTP 503 com Retry-After), para o aplicativo e faz um backup de segurança do estado atual, mostrando seu caminho. Depois executa pg_restore --clean --if-exists em uma única transação (se algo falhar, o banco permanece como estava), inicia o aplicativo e reabre o tráfego quando ele está saudável. Se o aplicativo não ficar saudável, o site permanece em manutenção e o erro mostra como voltar:

shell
nelcota -p shop restore projects/shop/backups/<safety dump> --yes

Uma restauração também assume o controle de um bloqueio de manutenção deixado fechado por uma atualização ou restauração que falhou, pois é assim que esse projeto se recupera.

Use --files para restaurar o snapshot correspondente do disco. Usa o diretório irmão local quando disponível; caso contrário, busca o snapshot desse dump no bucket de backup. O manifesto, os tamanhos e os hashes dos arquivos são verificados antes de parar o aplicativo. As versões imutáveis são copiadas antes de restaurar o banco, então uma falha na cópia de arquivos deixa o banco intacto. Versões existentes sem relação com o snapshot permanecem para o coletor normal de órfãos. Backups feitos com o antigo espelho compartilhado de armazenamento não têm manifesto histórico de arquivos; continuam utilizáveis para restaurações apenas do banco.

Restaurar em uma nova VPS

shell
curl -fsSL https://raw.githubusercontent.com/Nelcota/Nelcota/main/scripts/install.sh | sh
mkdir -p /opt/nelcota && cd /opt/nelcota
nelcota init api.shop.com --yes
cp /safe/place/shop.env projects/shop/.env   # optional: keeps the JWT key
nelcota up
aws s3 cp s3://my-backups/shop/nelcota-shop-....dump projects/shop/backups/ --endpoint-url ...
nelcota -p shop restore projects/shop/backups/nelcota-shop-....dump --files --yes

Isso também restaura o snapshot de disco correspondente; baixe seu diretório/manifesto ou configure o bucket de backup do host para o CLI buscá-lo. Omita --files apenas em uma restauração intencional só do banco. Preserve e reconcilie a configuração privada do projeto/host, incluindo credenciais e SSO. O download direto com aws acima exige AWS CLI instalado e acesso privado configurado.

O teste de aceitação (scripts/acceptance.sh --local) faz um backup, escreve mais dados, restaura e verifica que o estado voltou ao do backup.

Quando um projeto é removido (nelcota -p <name> remove), um backup final é mantido em archive/, na pasta do host. Se o Postgres estiver parado, remove o inicia para o backup e se recusa a excluir qualquer coisa quando não consegue; --no-backup remove sem backup. Snapshots do disco são arquivados junto do dump final.

PITR (recuperação para um ponto no tempo)

Dumps diários perdem até 24 h de escritas no pior caso. Com PITR ativado, o Postgres envia cada segmento WAL para o armazenamento compatível com S3 conforme ele se preenche (e pelo menos uma vez por minuto), junto de backups-base periódicos, permitindo voltar a qualquer segundo coberto pelo arquivo: imediatamente antes de uma migração ruim ou de um DELETE sem WHERE.

shell
nelcota -p shop pitr enable                                    # archiving + first full backup
nelcota -p shop pitr status                                    # backups and the range covered
nelcota -p shop pitr backup                                    # base backup now (full Sunday, otherwise differential)
nelcota -p shop pitr restore --time "2026-10-07 14:30:00+00"   # back to that moment
nelcota -p shop pitr restore                                   # up to the last archived write
nelcota -p shop pitr disable                                   # stops archiving (keeps what is in S3)
  • Usa pgBackRest, instalado na imagem Postgres do projeto (postgres/Dockerfile: a imagem oficial postgres:17-alpine mais o pacote Alpine, portanto o diretório de dados não muda). Tudo vai para s3://<bucket>/<project>/pitr/, ao lado dos dumps.
  • Exige as configurações S3 em host.env (as mesmas usadas pelo upload de backup). pgBackRest usa S3 apenas sobre HTTPS. Para MinIO/SeaweedFS com autoridade certificadora privada, instale a CA confiável no runtime. NELCOTA_BACKUP_S3_VERIFY_TLS=false desativa a verificação e não deve ser usado na recuperação em produção. Para provedores que exigem URLs path-style, defina NELCOTA_BACKUP_S3_URI_STYLE=path.
  • enable escreve pgbackrest.env na pasta do projeto (modo 600, contém as chaves S3). Após alterar as configurações S3 em host.env, execute enable novamente para regenerá-lo.
  • O backup --all diário também faz um backup-base para projetos com PITR: completo aos domingos, diferencial nos outros dias. Dois backups completos são mantidos, então a janela alcança de uma a duas semanas atrás. Menos segmentos WAL para reproduzir significam uma restauração mais rápida.
  • PITR restaura apenas PostgreSQL. Não retorna bytes de objetos disco/S3 ao estado anterior. Metadados antigos podem referenciar versões já excluídas; mantenha histórico/versionamento no provedor ou um plano verificado de recuperação de arquivos. Pares de dump/snapshot de arquivos são outro mecanismo de recuperação.
  • restore para o projeto, reescreve o diretório de dados a partir do backup-base mais próximo, reproduz o WAL até o alvo e inicia tudo novamente. Descarta todas as escritas posteriores ao alvo, então pede confirmação (--yes a dispensa). Depois faz um novo backup completo, pois o banco restaurado continua em uma nova linha do tempo que os backups antigos não podem atender.
  • A recuperação é destrutiva. Alguns problemas de configuração/arquivo WAL são recusados cedo, mas um alvo inalcançável pode ser detectado durante o replay de WAL após a restauração dos arquivos. Antes de alterar os dados, a CLI persiste uma trava de recuperação fora do diretório de dados. Um erro ou uma restauração interrompida mantém essa trava após a saída da CLI, o reinício dos serviços e o reboot da máquina. Não suponha que o banco anterior ficou intacto ou será restaurado automaticamente. Use os passos de recuperação explícita abaixo.
  • Projetos criados antes do PITR são atualizados por enable: o Postgres passa para a imagem construída a partir de postgres/Dockerfile e recebe uma rede egress para alcançar o S3 (a rede internal não tem saída). Se docker-compose.yml foi editado manualmente, enable mostra as alterações necessárias.

O teste de aceitação (scripts/acceptance-pitr.sh) executa o ciclo completo com um servidor S3 local com TLS.

Trava de recuperação PITR

Este recurso pertence ao código Unreleased; use artefatos compatíveis da CLI e da aplicação. Execute alterações da trava e a recuperação no host Linux. O Docker recusa a restauração antes de alterar dados se a imagem da aplicação não oferecer suporte à trava. A recuperação nativa também exige o binário instalado da aplicação na versão correspondente. Entrypoints, comandos da aplicação ou locais do diretório de dados personalizados exigem integração revisada; são recusados em vez de ficarem desprotegidos silenciosamente.

A trava durável é active.json (ou um recovery.json parcialmente confirmado) em /var/lib/nelcota-pitr/ no systemd, ou em projects/<project>/pitr-fence/ no Docker. Registros privados vinculam a trava ao projeto e diretório proprietários. No Docker, active.json contém os bytes exatos do Compose original para a liberação atômica; não imprima nem publique seu conteúdo. A trava não expira e nunca é removida durante o boot. Não exclua esses arquivos, mova o diretório do host, contorne entrypoints nem desative as verificações de inicialização.

As units nativas do PostgreSQL e da aplicação verificam a trava em ExecStartPre, inclusive no boot, em systemctl start manual e na inicialização de clusters redirecionada pelo systemd. A verificação executa uma cópia dedicada em modo somente de validação em /usr/local/lib/nelcota/pitr-check, preservada durante o rollback do binário da aplicação, sem seguir um script dentro do diretório da trava. A CLI recusa up, inicializações/reinícios de serviços e execução direta do servidor enquanto a trava está ativa. Apenas executar up não é uma ação de recuperação: use pitr recover ou uma restauração com alvo coberto. Cada recusa identifica a trava e o comando de recuperação; o systemd registra a mensagem no journal do serviço. Uma unit recusada pode ficar failed com PID zero.

O Docker reescreve temporariamente docker-compose.yml e remove os containers antigos sem remover seus volumes de dados antes de qualquer escrita de restauração. O Compose bloqueado monta o diretório do projeto somente para leitura, para que a substituição do diretório da trava por um link simbólico permaneça visível às verificações de ambos os entrypoints; sua política de reinício fica em no. Isso impede que containers antigos com unless-stopped ou always reabram após o reinício do daemon ou do host. Uma inicialização manual do container ainda encerra na verificação da trava, mesmo que sua política de reinício seja alterada. O Docker inicia de forma assíncrona: inspecione o status de saída e os logs, não apenas o código de saída de docker start. A recuperação bem-sucedida restaura os bytes exatos do Compose original e up aplica suas políticas originais de reinício.

A recuperação valida os componentes do diretório e todos os arquivos de estado sem seguir links simbólicos e recusa proprietários incorretos, hardlinks ou tipos inesperados antes de alterar o estado PITR ou os serviços. A recusa informa o caminho rejeitado; preserve todos os marcadores e investigue a propriedade e a estrutura em vez de excluí-los.

Após a verificação e a parada limpa do servidor de recuperação, o Docker persiste um release.verified.json vinculado ao proprietário, prepara active.json com os bytes do Compose original e remove recovery.json. Um único rename atômico remove o último marcador e restaura docker-compose.yml ao mesmo tempo; ambos os diretórios são sincronizados. Uma falha antes dessa confirmação mantém os marcadores e o Compose protegido. Uma falha após ela deixa o Compose original sem marcadores. Os guards nativos permanecem instalados enquanto os marcadores são removidos e sincronizados. Repetir pitr recover --yes verifica novamente um diretório bloqueado ou conclui uma liberação já confirmada; uma falha na sincronização final não exige edições manuais. O registro de verificação mantido é metadado, não uma trava ativa, e nunca autoriza limpeza automática.

Inspecione a trava e preserve os diagnósticos da restauração que falhou:

shell
nelcota -C /opt/nelcota -p shop pitr status
# Native service diagnostics:
sudo journalctl -u postgresql@17-main -u nelcota-shop -n 60 --no-pager

Se a recuperação nativa recusar um binário instalado da aplicação mais antigo, mantenha os serviços parados e instale atomicamente seu artefato correspondente já verificado antes de repetir. Não o substitua por um download não verificado:

shell
sudo install -m 0755 /path/to/verified/nelcota /usr/local/lib/nelcota/nelcota.pitr-new
sudo mv /usr/local/lib/nelcota/nelcota.pitr-new /usr/local/lib/nelcota/nelcota

Quando arquivos podem ter sido substituídos ou a recuperação de WAL foi interrompida, repita a restauração com um alvo coberto pelo arquivo WAL. Essa operação explícita mantém as inicializações comuns bloqueadas enquanto uma instância temporária do PostgreSQL, exclusiva para recuperação, reproduz o WAL sem listener TCP nem autostart. Somente a promoção verificada e a parada da instância de recuperação liberam a trava:

shell
sudo nelcota -C /opt/nelcota -p shop pitr restore --time "2026-10-07 14:30:00+00" --yes

Para manter intencionalmente o diretório atual, primeiro confirme pelo erro e pelo histórico de backups que essa decisão é adequada. Depois execute a verificação:

shell
sudo nelcota -C /opt/nelcota -p shop pitr recover --yes
sudo nelcota -C /opt/nelcota -p shop up
nelcota -C /opt/nelcota -p shop status

recover para qualquer instância de recuperação pertencente ao projeto que tenha permanecido ativa, exige um diretório PG17 desligado de forma limpa, sem sinais de recuperação nem rótulo de backup, verifica o arquivo de controle e os checksums de páginas habilitados e então verifica heaps, TOAST e índices B-tree com pg_amcheck, usando no máximo dois workers paralelos. Pode instalar a extensão amcheck ausente em pg_catalog. Essa instância temporária do PostgreSQL não tem listener TCP e não pode iniciar automaticamente; os serviços comuns permanecem bloqueados. Após uma parada limpa, as verificações offline são repetidas antes da remoção durável da trava. --yes dispensa a pergunta, nunca a verificação. Verificações que falham mantêm a trava; use uma restauração com alvo coberto em vez de removê-la manualmente.

Essas verificações não comprovam a correção das regras da aplicação, a preservação de cada escrita confirmada nem a identidade dos bytes quando os checksums estavam desabilitados. Reconcilie os dados com seu backup/histórico verificado. Após iniciar, verifique uma leitura autorizada, o isolamento RLS de outro usuário, a recusa de acesso anônimo e um 404 para arquivo privado antes de reabrir o tráfego externo. As verificações de recuperação não substituem um ensaio de restauração em outro host nem a recuperação do par de arquivos.

Solução de problemas

Sem destino externo, --upload falha; um bucket parcialmente configurado também falha, em vez de ignorar a transferência. Confira espaço disponível, permissões/TLS do provedor e o par completo de dump/arquivos. Após falha de recuperação, preserve manutenção e diagnósticos privados e inspecione o runtime selecionado; não descarte o backup de segurança. Use um host descartável para ensaiar a recuperação.

Relacionados: deploy, armazenamento, exportação. Recuperação de atualizações controladas tem outro guia no repositório em docs/panel-updates.md.