Ir para o conteúdo
Q
QuoteNode

Wiki

Backup e recuperação

Como o QuoteNode gere os backups de base de dados e ficheiros — arquivos cifrados com age, restauração guiada por manifesto, kits de recuperação e perfis de runtime.

Backup e recuperação

O QuoteNode inclui um sistema de backups integrado que protege os seus dados comerciais contra falhas de hardware, eliminações acidentais e incidentes operacionais.

O que é incluído no backup

Cada backup cria um arquivo que contém:

  1. Dump da base de dados — um dump completo do PostgreSQL em formato personalizado (pg_dump --format=custom), incluindo todas as tabelas, sequências e restrições.
  2. Armazenamento de ficheiros — todos os ficheiros carregados (imagens de produtos, logótipos de empresa) e os PDF gerados, arquivados como files.tar.gz com caminhos relativos.
  3. Manifesto do backup — um ficheiro backup-manifest.json que contém a versão de origem, o modo de cifragem, o modo de payload da base de dados, as somas de verificação e os metadados de compatibilidade de restauração.
  4. Somas de verificação de integridade — um ficheiro checksums.sha256 que contém os hashes SHA-256 de todos os componentes do arquivo.

Cifragem do arquivo

Os novos backups cifrados usam a cifragem por destinatário age (AGE_RECIPIENT). É o único modelo de cifragem suportado para os novos arquivos.

  • O processo de backup usa apenas um destinatário age público (age1...) — não é necessária qualquer chave privada no servidor para criar backups.
  • Os arquivos cifrados têm a extensão .tar.age.
  • A soma de verificação SHA-256 do arquivo cifrado é guardada junto ao arquivo para verificação do lado do operador.
  • A decifragem requer a identidade age privada correspondente, mantida exclusivamente no kit de recuperação do operador.
  • A identidade age privada em bruto nunca é guardada na base de dados, nos registos, nos DTO nem nos argumentos de processo após a confirmação da configuração.

Backups locais não cifrados

Os backups locais não cifrados (.tar) só são permitidos quando todas as condições seguintes se verificam:

  • BACKUP_ARCHIVE_ENCRYPTION_MODE=NONE
  • BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=true
  • O destino é o armazenamento local
  • BACKUP_DATABASE_PAYLOAD_MODE=APP_ENCRYPTED
  • O administrador reconheceu explicitamente o risco na interface

Os arquivos não cifrados são bloqueados para o armazenamento remoto e para os payloads com PII decifradas.

Configuração (.env). O destinatário age é gerado pelo kit de recuperação (Administração → Segurança → Backup) e guardado na instância — não coloca qualquer chave no .env. Defina apenas as duas variáveis de modo:

BACKUP_ENABLED=true
BACKUP_ARCHIVE_ENCRYPTION_MODE=AGE_RECIPIENT
BACKUP_DATABASE_PAYLOAD_MODE=APP_ENCRYPTED
# Alternative — encrypted archive + decrypted PII (cross-instance portability):
# BACKUP_DATABASE_PAYLOAD_MODE=PII_DECRYPTED
BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=false

Para desativar a cifragem do arquivo (apenas local, não recomendado — as PII continuam cifradas no payload):

BACKUP_ARCHIVE_ENCRYPTION_MODE=NONE
BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=true

Com AGE_RECIPIENT, se ainda não tiver sido gerado nenhum destinatário, o backup falha de forma explícita em vez de escrever um arquivo desprotegido. A combinação NONE + PII_DECRYPTED é rejeitada.

Modos de payload da base de dados

Modo Descrição
APP_ENCRYPTED Predefinido. Os campos PII permanecem cifrados com a chave da aplicação DB_ENCRYPTION_KEY. A restauração requer a mesma chave de cifragem ou uma impressão digital de chave correspondente.
PII_DECRYPTED Os campos PII são decifrados para uma base de dados temporária antes do arquivamento. Os hashes das palavras-passe dos utilizadores permanecem inalterados. Requer a cifragem do arquivo AGE_RECIPIENT. Útil para a portabilidade entre instâncias.

Ao restaurar um backup PII_DECRYPTED num destino com ENCRYPT_PII=true, o processo de restauração volta a cifrar automaticamente as PII com a DB_ENCRYPTION_KEY do destino antes do arranque normal da aplicação.

Kit de recuperação

Durante a configuração, o administrador gera (ou importa) um destinatário age para os backups cifrados. O kit de recuperação é um ficheiro ZIP que contém:

  • quotenode-age-identity.txt — a identidade age privada (devolvida apenas uma vez)
  • quotenode-age-recipient.txt — o destinatário público e a impressão digital
  • quotenode-backup-verify.sh — script de verificação local (Linux/macOS)
  • quotenode-backup-verify.ps1 — script de verificação local (Windows)
  • README.md — comandos de verificação e restauração

Crítico: a identidade age privada é devolvida apenas uma vez durante a geração do kit. Se for perdida, os arquivos cifrados não poderão ser decifrados. Guarde o kit de recuperação num local offline seguro.

O administrador tem de confirmar que o kit foi guardado e testado antes de o assistente de configuração marcar o backup como pronto para produção.

Verificação local de backups

Os backups transferidos podem ser verificados fora da aplicação em execução através dos scripts do kit de recuperação:

scripts/quotenode-backup-verify.sh \
  --archive /path/backup.tar.age \
  --identity /path/quotenode-age-identity.txt \
  --expected-sha256 <sha256-from-ui-or-sidecar>

O verificador:

  1. Verifica a soma de verificação SHA-256 do arquivo cifrado em relação ao valor esperado ou ao ficheiro acompanhante .sha256.
  2. Decifra com age -d para um diretório temporário privado.
  3. Valida as entradas tar (rejeita caminhos absolutos, .., ligações simbólicas e ficheiros de dispositivo).
  4. Verifica se backup-manifest.json existe e tem uma versão suportada.
  5. Executa sha256sum -c checksums.sha256.
  6. Executa pg_restore --list db.dump quando o pg_restore está disponível.
  7. Remove por predefinição os dados temporários decifrados (use --keep-decrypted para os manter).

Backup online (predefinido)

Os backups online são executados enquanto a aplicação atende pedidos. Não há tempo de inatividade.

  • O pg_dump em formato personalizado cria um instantâneo transacionalmente consistente sem bloquear tabelas nem consultas.
  • O armazenamento de ficheiros é arquivado em paralelo — os ficheiros carregados e os PDF são imutáveis após a criação, pelo que o arquivo é sempre consistente.
  • O PostgreSQL não precisa de parar para produzir um dump consistente da base de dados.

Como são executados os backups: escolher uma topologia

Existem duas formas de executar continuamente os backups agendados. Ambas produzem arquivos idênticos — diferem apenas em onde a tarefa de backup é executada.

Topologia Quando usar Contentores
Worker dedicado (predefinido) Produção. Os backups são executados num contentor backup-worker separado, pelo que nunca competem com o tráfego web por memória ou CPU. backend web + backup-worker
In-process Portátil / avaliação / pequenas instalações de host único. O backend web executa também os backups — um contentor a menos para operar. apenas backend web

A própria tarefa de backup é o mesmo pipeline de shell (pg_dump → arquivamento → cifragem) em ambos os casos, executado fora do heap da JVM, pelo que o seu custo de memória é modesto.

Como selecionar uma topologia

Duas variáveis de ambiente funcionam em conjunto:

  • JOBS_MODE decide qual contentor executa efetivamente a tarefa de backup agendada.
  • BACKUP_RUNTIME_PROFILE é um rótulo registado nos registos e manifestos dos backups, para que possa ver, a posteriori, como cada backup foi produzido.
Topologia Backend web Backup worker
Worker dedicado (predefinido) JOBS_MODE=web + BACKUP_RUNTIME_PROFILE=WORKER contentor JOBS_MODE=backup-only
In-process JOBS_MODE=all + BACKUP_RUNTIME_PROFILE=WEB_MAINTENANCE (nenhum)

O ficheiro Compose de produção incluído usa a topologia de worker dedicado. Os guias de portátil/avaliação usam in-process (JOBS_MODE=all + BACKUP_RUNTIME_PROFILE=WEB_MAINTENANCE) para que execute apenas um contentor de backend.

Referência dos perfis de runtime

BACKUP_RUNTIME_PROFILE aceita quatro valores. Os dois primeiros são para as topologias contínuas acima; os dois últimos para backups pontuais geridos pelo operador.

Perfil Descrição
WORKER Predefinido. Um contentor backup-worker permanente executa os backups agendados através de cron.
WEB_MAINTENANCE O backend web executa diretamente os backups agendados. Não é necessário nenhum contentor worker adicional.
ONE_SHOT Um cron externo ou operador executa scripts/backup-one-shot.sh e termina. Sem worker permanente.
OFFLINE_MAINTENANCE Um script automatizado anuncia o tempo de inatividade, para frontend/backend/backup-worker, mantém o PostgreSQL em execução, executa o backup, reinicia os serviços e verifica o estado.

Backup pontual (one-shot)

COMPOSE_PROJECT_NAME=quotenode COMPOSE_ENV_FILE=infra/.env.prod \
  BACKUP_ARCHIVE_ENCRYPTION_MODE=AGE_RECIPIENT \
  BACKUP_AGE_RECIPIENT=age1... \
  bash scripts/backup-one-shot.sh

Manutenção offline

COMPOSE_PROJECT_NAME=quotenode COMPOSE_ENV_FILE=infra/.env.prod \
  BACKUP_OFFLINE_NOTICE_SECONDS=300 \
  bash scripts/backup-offline-maintenance.sh

O script de manutenção offline:

  1. Anuncia uma manutenção agendada (período de tolerância configurável).
  2. Ativa opcionalmente uma página de manutenção estática enquanto os serviços estão inativos.
  3. Para o frontend, o backend e o backup-worker (mantém o PostgreSQL em execução).
  4. Executa um backup pontual.
  5. Reinicia todos os serviços.
  6. Verifica o estado do backend.
  7. Escreve um diário de manutenção JSONL ao nível do host.

Se o script falhar em qualquer ponto, um trap de segurança reinicia automaticamente os serviços.

Use --dry-run para pré-visualizar o plano sem o executar:

bash scripts/backup-offline-maintenance.sh --dry-run

Agendamento

  • Agendamento predefinido: todos os dias às 2:00 (0 0 2 * * *, configurável através de BACKUP_CRON)
  • Ativar/desativar: BACKUP_ENABLED=true|false
  • Acionamento manual: os administradores podem acionar um backup imediato a partir do painel de administração ou através da API (POST /api/v1/admin/backup/trigger). O acionador responde de imediato (HTTP 202) enquanto o backup é executado de forma assíncrona.
  • Proteção contra concorrência: um bloqueio suportado pela base de dados garante que apenas um backup é executado de cada vez em todos os workers e acionamentos manuais.

Para os perfis ONE_SHOT e OFFLINE_MAINTENANCE, o agendador interno está desativado — os backups são geridos externamente.

Opções de armazenamento

Armazenamento local (predefinido)

Os backups são guardados em BACKUP_LOCAL_DIR (predefinição: /app/data/backups), mapeado para um volume Docker.

Em produção, os backups locais devem ser complementados com cópias remotas.

Armazenamento remoto via rclone

Se BACKUP_RCLONE_REMOTE estiver configurado, os backups são automaticamente carregados para um destino remoto após a criação local. O rclone suporta mais de 70 fornecedores de armazenamento na cloud, incluindo os compatíveis com S3, Google Cloud Storage, Azure Blob, SFTP e WebDAV.

Transferência de backups

A transferência de arquivos de backup requer autorização reforçada (step-up):

  1. Navegue até Definições > Backup.
  2. Clique em Transferir na entrada do backup.
  3. Introduza a sua palavra-passe atual (e o código TOTP, se a MFA estiver ativada).
  4. O sistema emite uma concessão de transferência de utilização única e de curta duração (predefinição: 120 segundos).
  5. O arquivo é transferido automaticamente.

O token de concessão é guardado apenas como hash SHA-256. A criação da concessão e a transferência são auditadas e os restantes administradores são notificados.

Nota: uma transferência direta sem concessão devolve 403. Os backups em armazenamento remoto não podem ser transferidos através do painel de administração.

Metadados e trilho de auditoria

Cada backup é registado na tabela backup_logs com:

  • Estado (RUNNING, SUCCESS, FAILED)
  • Iniciado por (SCHEDULER ou ADMIN)
  • Hora de início/conclusão
  • Tamanho (bytes) e soma de verificação SHA-256
  • Destino (caminho local ou URL remoto)
  • Versão do manifesto e versão da app de origem
  • Modo de cifragem do arquivo e impressão digital
  • Modo de payload da base de dados
  • Perfil de runtime
  • Estado de verificação (NOT_RUN, PASSED, FAILED, SKIPPED_TIMEOUT)
  • Estado do teste de restauração e estado de compatibilidade de restauração

Restauração

Utilização de scripts/restore.sh

O script de restauração canónico gere todo o ciclo de vida:

COMPOSE_PROJECT_NAME=quotenode \
  COMPOSE_ENV_FILE=infra/.env.prod \
  RESTORE_AGE_IDENTITY_FILE=/path/to/quotenode-age-identity.txt \
  [email protected] \
  RESTORE_ADMIN_PASSWORD_FILE=/path/to/password \
  bash scripts/restore.sh /path/to/backup.tar.age

O script:

  1. Extrai e valida o arquivo (manifesto, somas de verificação, segurança das entradas tar).
  2. Executa verificações preliminares (capacidade de disco, versão do PostgreSQL, conectividade à base de dados).
  3. Cria um backup de segurança do destino antes de qualquer operação destrutiva.
  4. Escreve um diário de restauração ao nível do host (JSONL, sobrevive à substituição da base de dados).
  5. Restaura a base de dados.
  6. Prepara os ficheiros num diretório temporário e troca-os no lugar.
  7. Preserva a identidade do destino (ID de instância, metadados de cifragem dos backups, estado da licença).
  8. Limpa sessões, ligações públicas de propostas e tokens de notificação.
  9. Repõe ou força a alteração do acesso de administrador.
  10. Volta a cifrar as PII se o backup for PII_DECRYPTED e o destino tiver ENCRYPT_PII=true.

Restauração entre instâncias

Ao restaurar um backup de uma instalação diferente, o modo predefinido é PRESERVE_TARGET_IDENTITY:

  • O ID de instância de destino, o destinatário age dos backups, os metadados do kit de recuperação e o estado da licença são preservados.
  • As sessões, as ligações públicas de propostas e os tokens de notificação são limpos.
  • A palavra-passe de administrador é reposta/é forçada a sua alteração.
  • Os dados de negócio são totalmente substituídos (sem fusão).

Compatibilidade de restauração

A verificação preliminar da restauração valida:

  • Backup mais antigo numa app mais recente: permitido (compatível).
  • Backup mais recente numa app mais antiga: bloqueado.
  • Manifesto em falta: bloqueado.
  • Impressão digital de chave de cifragem incorreta para um payload APP_ENCRYPTED: bloqueado antes do trabalho destrutivo.
  • PII_DECRYPTED sem cifragem do arquivo: bloqueado.

Garantia de acesso de administrador

A restauração garante o acesso de administrador através de uma das seguintes opções:

  • RESTORE_ADMIN_PASSWORD_FILE — palavra-passe fornecida pelo operador (definida com forcePasswordChange=true)
  • RESTORE_ADMIN_PASSWORD — apenas para utilização local/de teste
  • Token de reposição de utilização única gerado automaticamente — impresso na consola e no diário ao nível do host

Execução de teste (dry run)

RESTORE_DRY_RUN=true bash scripts/restore.sh /path/to/backup.tar.age

Valida o arquivo, verifica as somas de verificação e o manifesto e comunica o plano de restauração sem modificar nada.

Recuperação de desastres — reconstrução do zero

O seu servidor desapareceu. Tem um arquivo de backup (.tar.age ou .tar) e um novo servidor com Docker. Eis como restaurar.

Passo 1 — Preparar o novo servidor

mkdir quotenode && cd quotenode

Configure docker-compose.yml e .env conforme descrito no Guia de instalação.

Crítico para os backups APP_ENCRYPTED: use a mesma DB_ENCRYPTION_KEY da sua instalação original. Sem ela, as PII cifradas ficarão permanentemente ilegíveis.

Crítico para os arquivos cifrados: precisa do ficheiro de identidade privada do kit de recuperação (quotenode-age-identity.txt) para decifrar o arquivo.

Passo 2 — Copiar o backup e o script de restauração

scp backup.tar.age user@new-server:~/quotenode/
scp scripts/restore.sh user@new-server:~/quotenode/

Passo 3 — Executar a restauração

RESTORE_AGE_IDENTITY_FILE=./quotenode-age-identity.txt \
  [email protected] \
  RESTORE_ADMIN_PASSWORD_FILE=./admin-password \
  ./restore.sh --fresh-install backup.tar.age

Passo 4 — Verificar

Abra o seu domínio e inicie sessão. Todos os clientes, propostas, produtos e definições deverão estar exatamente como estavam no momento do backup.

Retenção de backups

  • Local: são mantidos os BACKUP_RETENTION_DAILY (predefinição: 7) backups bem-sucedidos mais recentes.
  • Remoto: configure políticas de ciclo de vida no seu fornecedor de armazenamento.
  • Os backups com PII decifradas devem ter uma retenção mais rigorosa devido à sua natureza sensível.

Tempo limite do backup

Cada backup tem um tempo limite configurável (predefinição: 30 minutos através de BACKUP_TIMEOUT_MINUTES). Se for excedido, o backup é terminado e registado como FAILED.

Estratégia recomendada

  1. Ative backups automáticos diários com cifragem AGE_RECIPIENT e armazenamento remoto.
  2. Gere um kit de recuperação durante a configuração e guarde-o num local offline seguro.
  3. Verifique o seu kit de recuperação em relação a um backup real usando os scripts de verificação incluídos.
  4. Teste periodicamente os procedimentos de restauração — um backup que nunca foi testado não é um backup.
  5. Transfira um backup pelo menos uma vez por mês para um local offline (requer autorização reforçada).
  6. Monitorize o estado dos backups no painel de administração — surgem avisos para backups desatualizados, falhados ou não cifrados.
  7. Mantenha pelo menos 7 dias de histórico de backups (a predefinição).

Last reviewed: Recently