Backup e restauração
O backup do Notarium é um comando embutido na imagem, não uma cópia de arquivos feita de fora. O notarium backup monta um ZIP lógico enquanto o serviço segue atendendo: a leitura continua disponível o tempo todo, e a escrita só é segurada em dois checkpoints curtos. Basta ter o Docker e um contêiner rodando — não é preciso parar o serviço.
meta.db em usocp /data/meta.db não é backup. O banco de metadados roda em modo WAL: linhas já commitadas podem ainda estar no meta.db-wal, e arquivos copiados um a um não formam um instantâneo de um único momento. Uma instância restaurada a partir dessa cópia perde dados em silêncio.
Fazer um backup
docker compose exec -T notarium backup > notarium-20260731.zip
Isto é um backup de verdade, não um teste do tipo "será que funciona": o comando monta o arquivo de backup, passa-o pelo próprio verificador e só então envia os bytes para o stdout. A flag -T é obrigatória — sem ela o Compose aloca um pseudoterminal, e um stream binário não atravessa um deles intacto: o resultado é um arquivo de backup corrompido. O docker exec puro não aloca pseudoterminal por padrão, então lá a flag não é necessária. A saída padrão fica reservada estritamente para os bytes do ZIP; o progresso e o resumo final vão para o stderr e nunca contaminam o arquivo de backup.
Para uma tarefa agendada
O redirecionamento com > tem uma armadilha: o arquivo com o nome final é criado antes de o comando fazer qualquer trabalho. Se o Docker ou o backup morrer no meio do caminho, sobra um arquivo com o nome certo e conteúdo incompleto. Abaixo vai uma sequência de publicação segura contra falhas: escreva em um arquivo temporário, force a gravação em disco e publique com um hard link atômico.
backup="notarium-$(date -u +%Y%m%dT%H%M%SZ).zip"
partial="${backup}.partial.$$"
set -eu
umask 077
committed=0
cleanup() { test "$committed" -eq 1 || rm -f "$partial"; }
trap cleanup EXIT
docker compose exec -T notarium backup > "$partial"
sync -f "$partial"
sync -f "$(dirname "$backup")"
committed=1
ln "$partial" "$backup"
if sync -f "$(dirname "$backup")"; then
if rm "$partial"; then
sync -f "$(dirname "$backup")" ||
echo "backup warning: final is durable; partial cleanup fsync failed" >&2
else
echo "backup warning: final is durable; retaining recovery partial $partial" >&2
fi
else
echo "backup warning: final is visible; retaining durable recovery partial $partial" >&2
fi
trap - EXIT
O set -e impede que o arquivo de backup seja publicado se o Docker ou o backup retornarem erro. O nome temporário carrega o PID, então duas tarefas rodando ao mesmo tempo não brigam pelo mesmo arquivo. O arquivo temporário e o diretório dele são gravados em disco antes do ponto de publicação, e a publicação em si é um hard link atômico que nunca sobrescreve: duas tarefas mirando o mesmo nome de destino não têm como se atropelar.
Falhas depois do ponto de publicação são avisos, não alarmes falsos: o arquivo final está no lugar e, em casos ambíguos, o temporário é mantido como cópia reserva. Até um código de saída não-zero ambíguo do ln preserva essa cópia — o link pode ter sido criado logo antes de o processo ser interrompido. O umask 077 deixa o arquivo de backup legível somente para o dono. Mantenha o temporário e o final no mesmo sistema de arquivos.
Para um contêiner subido com docker run puro sob o nome notarium, tudo acima vale igual — muda uma linha:
docker exec notarium backup > "$partial"
O script auxiliar acima existe porque o transporte via stdout não consegue publicar um arquivo sozinho. Quando o diretório dos arquivos de backup já está montado — um compartilhamento de backup, NFS, um volume scratch ao lado de uma raiz somente leitura —, o backup --output /path/archive.zip faz o mesmo trabalho: escreve em um arquivo temporário ao lado do destino, grava em disco, verifica e publica com um hard link atômico que nunca sobrescreve; em caso de falha, não deixa nada para trás sob o nome de destino. Aí nenhum script auxiliar é necessário, e o stdout carrega um único resumo JSON.
O comando é executado com docker exec dentro do contêiner que já está atendendo — não como um contêiner separado: tirar um instantâneo consistente exige coordenação com a aplicação em execução.
Quando um backup pode falhar
O backup foi construído para que você nunca fique, sem perceber, com um arquivo de backup inconsistente: se o instantâneo não puder ser tirado, o comando termina com erro e não publica nada. Dois casos em que isso acontece:
- Um fluxo contínuo de edições. Os dados precisam ficar parados enquanto o arquivo de backup é montado; uma escrita que se sobreponha dispara uma nova tentativa e, sob um fluxo interminável de edições, o comando desiste com erro. Na prática, isso aparece em uma instância movimentada — é só tentar de novo mais tarde.
- Uma importação ou exportação longa em andamento. O backup precisa de duas pausas bem curtas na escrita, e uma tarefa longa não cabe nelas. Não agende um backup na mesma janela de uma importação em massa.
Nenhum dos dois casos prejudica o serviço: a fila de escrita é liberada na hora, e o comando do operador nunca segura a aplicação. De um jeito ou de outro, a leitura comum fica disponível durante todo o backup.
O que vai dentro do arquivo de backup
| Caminho no arquivo de backup | O que é |
|---|---|
data/meta.db | Contas, sessões, participação, identificadores estáveis, histórico de versões e estado das tarefas. |
data/spaces/ | A fonte da verdade em Markdown, incluindo a memória do agente e os arquivos marcadores de projeto. |
data/jobs/ | Artefatos prontos e uploads duráveis de importação. |
manifest.json | Versão do formato, marca de tempo, o conjunto exato de diretórios, além de tamanho, mtime e SHA-256 de cada arquivo. |
O diretório derivado data/engine/ não entra: os índices são reconstruídos a partir dos arquivos depois de uma restauração. Dos arquivos incompletos, só os internos ficam de fora — os temporários da escrita atômica de notas, importações com upload pela metade e pedaços de artefatos de exportação. Arquivos comuns do usuário cujo nome termina em .part permanecem no arquivo de backup: eles são legítimos.
O ZIP contém contas e estado de sessão vindos do banco de metadados. Trate-o como segredo: o umask 077 no trecho acima deixa cada novo arquivo de backup legível somente para o dono.
Verificação
A verificação não altera nada e deve fazer parte de toda rotina de backup:
docker compose exec -T notarium backup verify < notarium-20260722.zip
# para docker run puro:
docker exec -i notarium backup verify < notarium-20260722.zip
Em caso de sucesso, o comando imprime um único resumo JSON e sai com código zero. São rejeitados: caminhos inseguros e duplicados, arquivos ausentes do manifesto, um conjunto de diretórios que não bate exatamente, divergências de tamanho e de hash, metadados de tempo inválidos, estouro de limites e falha na verificação de integridade do SQLite. O diretório de dados em uso não é lido nem alterado nesse processo.
Os hashes pegam corrupção acidental, mas não protegem contra adulteração: quem conseguir trocar tanto o conteúdo quanto o manifesto passa na verificação. Trate o armazenamento dos backups como estado confiável de acesso restrito, ou acrescente assinatura ou criptografia na camada que transporta o arquivo de backup.
Restauração
A restauração é uma operação de desastre, feita offline. Ela só aceita uma raiz de dados limpa e vazia, e nunca se mistura a uma instância existente nem a sobrescreve.
Prepare um volume novo e aponte o serviço para ele antes de começar:
set -eu
docker compose stop notarium
# tire o volume antigo do caminho; monte um /data vazio no compose
docker compose run --rm --no-deps -T notarium restore \
< notarium-20260722.zip
docker compose up -d --force-recreate --no-deps notarium
Depois disso, o contêiner precisa ser recriado, não apenas iniciado: o docker compose start traria de volta o contêiner antigo com a configuração de montagem antiga. Guarde o volume antigo até ter conferido a instância restaurada.
A restauração verifica o arquivo de backup inteiro antes de instalar qualquer coisa. Se o processo for interrompido no meio da instalação, fica um marcador explícito: considere aquele destino de uso único e restaure em um novo destino vazio, em vez de insistir ou tentar mesclar.
O que conferir depois de uma restauração:
- Entrar com uma conta do backup.
- Abrir alguns espaços e confirmar que endereços e identificadores estão intactos.
- Abrir uma nota que você tinha editado e olhar o histórico dela.
- Conferir as tarefas de importação e exportação cujos uploads ou artefatos importam para você.
O banco de metadados que você está restaurando precisa trazer um registro de migrações que o build de destino aceite. Um banco não vazio e sem registro falha fechado — a restauração não adivinha a versão dele nem carimba uma por conta própria. Veja Banco de dados.
Limites
O comando embutido só suporta o layout canônico: uma única raiz de dados e o banco de metadados em um arquivo SQLite. Se META_DB_URL apontar para o Postgres, ou se as notas ficarem fora de DATA_DIR, o comando falha fechado em vez de entregar um arquivo de backup pela metade — nesse caso, use as ferramentas próprias do seu banco somadas a instantâneos dos diretórios montados.
Os arquivos intermediários do backup e da verificação ficam em /tmp por padrão. Um backup em streaming se verifica antes de publicar, então pode precisar temporariamente de espaço para o arquivo de backup mais dois estágios expandidos; uma verificação avulsa precisa do arquivo de backup mais um. A restauração faz buffer do stream de entrada no diretório temporário (scratch), mas expande o arquivo de backup direto na própria raiz de dados nova. Se o sistema de arquivos raiz do contêiner estiver montado somente leitura, ou se você tiver muitos dados, aponte NOTARIUM_BACKUP_TMPDIR para um diretório montado com permissão de escrita.
A entrada comprimida e a expandida têm teto de 64 GiB e um milhão de entradas; os nomes, as estruturas internas do ZIP e o manifest.json têm um limite de memória separado, de 32 MiB. Instalações grandes e confiáveis podem elevar NOTARIUM_BACKUP_MAX_BYTES, NOTARIUM_BACKUP_MAX_ENTRIES e NOTARIUM_BACKUP_MAX_METADATA_BYTES — veja Variáveis de ambiente.
A seguir
- CLI da imagem — o contrato completo de comandos, streams e códigos de saída.
- Banco de dados — o que exatamente o banco de metadados guarda e por que ele nunca pode ficar de fora de um backup.
- Produção — proxy reverso, o invariante de instância única, operação do dia a dia.