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:
- 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. - Armazenamento de ficheiros — todos os ficheiros carregados (imagens de produtos, logótipos de empresa) e os PDF gerados, arquivados como
files.tar.gzcom caminhos relativos. - Manifesto do backup — um ficheiro
backup-manifest.jsonque 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. - Somas de verificação de integridade — um ficheiro
checksums.sha256que 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=NONEBACKUP_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çãoNONE+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 digitalquotenode-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:
- Verifica a soma de verificação SHA-256 do arquivo cifrado em relação ao valor esperado ou ao ficheiro acompanhante
.sha256. - Decifra com
age -dpara um diretório temporário privado. - Valida as entradas tar (rejeita caminhos absolutos,
.., ligações simbólicas e ficheiros de dispositivo). - Verifica se
backup-manifest.jsonexiste e tem uma versão suportada. - Executa
sha256sum -c checksums.sha256. - Executa
pg_restore --list db.dumpquando opg_restoreestá disponível. - Remove por predefinição os dados temporários decifrados (use
--keep-decryptedpara 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_dumpem 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_MODEdecide 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:
- Anuncia uma manutenção agendada (período de tolerância configurável).
- Ativa opcionalmente uma página de manutenção estática enquanto os serviços estão inativos.
- Para o frontend, o backend e o backup-worker (mantém o PostgreSQL em execução).
- Executa um backup pontual.
- Reinicia todos os serviços.
- Verifica o estado do backend.
- 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 deBACKUP_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):
- Navegue até Definições > Backup.
- Clique em Transferir na entrada do backup.
- Introduza a sua palavra-passe atual (e o código TOTP, se a MFA estiver ativada).
- O sistema emite uma concessão de transferência de utilização única e de curta duração (predefinição: 120 segundos).
- 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:
- Extrai e valida o arquivo (manifesto, somas de verificação, segurança das entradas tar).
- Executa verificações preliminares (capacidade de disco, versão do PostgreSQL, conectividade à base de dados).
- Cria um backup de segurança do destino antes de qualquer operação destrutiva.
- Escreve um diário de restauração ao nível do host (JSONL, sobrevive à substituição da base de dados).
- Restaura a base de dados.
- Prepara os ficheiros num diretório temporário e troca-os no lugar.
- Preserva a identidade do destino (ID de instância, metadados de cifragem dos backups, estado da licença).
- Limpa sessões, ligações públicas de propostas e tokens de notificação.
- Repõe ou força a alteração do acesso de administrador.
- Volta a cifrar as PII se o backup for
PII_DECRYPTEDe o destino tiverENCRYPT_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_DECRYPTEDsem 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 comforcePasswordChange=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_KEYda 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
- Ative backups automáticos diários com cifragem
AGE_RECIPIENTe armazenamento remoto. - Gere um kit de recuperação durante a configuração e guarde-o num local offline seguro.
- Verifique o seu kit de recuperação em relação a um backup real usando os scripts de verificação incluídos.
- Teste periodicamente os procedimentos de restauração — um backup que nunca foi testado não é um backup.
- Transfira um backup pelo menos uma vez por mês para um local offline (requer autorização reforçada).
- Monitorize o estado dos backups no painel de administração — surgem avisos para backups desatualizados, falhados ou não cifrados.
- Mantenha pelo menos 7 dias de histórico de backups (a predefinição).