atualiza versão para 0.1.3; corrige bugs críticos no restore incremental e ajusta a montagem de volumes para consistência de paths
This commit is contained in:
parent
73409523d2
commit
c9a76d7b12
10
README.md
10
README.md
|
|
@ -9,7 +9,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/VERSION-0.1.2-blue?style=flat-square" />
|
<img src="https://img.shields.io/badge/VERSION-0.1.3-blue?style=flat-square" />
|
||||||
<img src="https://img.shields.io/badge/NODE.JS-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white" />
|
<img src="https://img.shields.io/badge/NODE.JS-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white" />
|
||||||
<img src="https://img.shields.io/badge/DOCKER-ready-2496ED?style=flat-square&logo=docker&logoColor=white" />
|
<img src="https://img.shields.io/badge/DOCKER-ready-2496ED?style=flat-square&logo=docker&logoColor=white" />
|
||||||
<img src="https://img.shields.io/badge/READY-yes-brightgreen?style=flat-square" />
|
<img src="https://img.shields.io/badge/READY-yes-brightgreen?style=flat-square" />
|
||||||
|
|
@ -18,11 +18,17 @@
|
||||||
|
|
||||||
> ⚠️ **AVISO CRÍTICO:** Aplicação em estágio inicial de desenvolvimento. Não use em produção — há risco de perda de dados.
|
> ⚠️ **AVISO CRÍTICO:** Aplicação em estágio inicial de desenvolvimento. Não use em produção — há risco de perda de dados.
|
||||||
|
|
||||||
Versão atual: **0.1.2**
|
Versão atual: **0.1.3**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## <20> Changelog
|
## <20> Changelog
|
||||||
|
### [0.1.3] — 2026-05-09
|
||||||
|
|
||||||
|
#### Corrigido
|
||||||
|
- **Restore incremental não aplicava o archive incremental (bug crítico):** o `tar` frequentemente retorna código de saída `1` durante a extração em volumes (avisos de permissão/ownership que não são erros fatais). Como o script do helper era gerado com `set -e` e comandos encadeados por `&&`, qualquer exit code `1` do archive full abortava a cadeia antes de aplicar os archives incrementais. Corrigido: removido `set -e` e a junção por `&&`; cada comando `tar` agora usa `; RC=$?; [ $RC -le 1 ] || exit $RC` para aceitar código `0` ou `1` como sucesso e falhar apenas em `>= 2` (erros fatais do tar).
|
||||||
|
- **Path não-nativo (fora do Docker) montava volumes em `/restore/mN`:** o caminho de restore fora do Docker montava volumes em `/restore/m0`, `/restore/m1` etc. e extraía com `-C /restore`, mas os archives gerados pelo helper atualizado têm paths reais (`a0/...`, `var/lib/gitea/...`). Corrigido: o path não-nativo agora monta cada volume no seu path real (igual ao path nativo) e extrai com `-C /`, tornando os dois paths consistentes.
|
||||||
|
|
||||||
### [0.1.2] — 2026-05-09
|
### [0.1.2] — 2026-05-09
|
||||||
|
|
||||||
#### Corrigido
|
#### Corrigido
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "dockerbackup-app",
|
"name": "dockerbackup-app",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"description": "Aplicacao web para backup e restauracao de volumes Docker",
|
"description": "Aplicacao web para backup e restauracao de volumes Docker",
|
||||||
"main": "src/server.js",
|
"main": "src/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -916,7 +916,7 @@ class BackupService {
|
||||||
|
|
||||||
const helperBackupRoot = `/backuproot_base${selfBind.suffix}`;
|
const helperBackupRoot = `/backuproot_base${selfBind.suffix}`;
|
||||||
const helperBinds = [`${selfBind.source}:/backuproot_base:ro`];
|
const helperBinds = [`${selfBind.source}:/backuproot_base:ro`];
|
||||||
const helperCmds = ['set -e'];
|
const helperCmds = [];
|
||||||
|
|
||||||
// Montar cada volume no seu path real e preparar limpeza.
|
// Montar cada volume no seu path real e preparar limpeza.
|
||||||
for (const mount of currentMounts) {
|
for (const mount of currentMounts) {
|
||||||
|
|
@ -931,10 +931,14 @@ class BackupService {
|
||||||
pushLog('Limpando volumes e restaurando archives via helper.', 'preparando');
|
pushLog('Limpando volumes e restaurando archives via helper.', 'preparando');
|
||||||
|
|
||||||
// Adicionar extração de cada archive.
|
// Adicionar extração de cada archive.
|
||||||
|
// IMPORTANTE: não usar 'set -e' nem '&&' — tar retorna código 1 (avisos de
|
||||||
|
// permissão/ownership) ao extrair em volumes. Com set -e/&&, o incremental
|
||||||
|
// nunca seria aplicado porque o full aborta a cadeia. Tratar código 1 como
|
||||||
|
// sucesso e apenas falhar com código >= 2.
|
||||||
for (const [index, entry] of chain.entries()) {
|
for (const [index, entry] of chain.entries()) {
|
||||||
const archivePath = `${helperBackupRoot}/${entry.archiveRelativePath}`;
|
const archivePath = `${helperBackupRoot}/${entry.archiveRelativePath}`;
|
||||||
helperCmds.push(`echo "[${index + 1}/${chain.length}] Aplicando: ${entry.archiveRelativePath}" 1>&2`);
|
helperCmds.push(`echo "[${index + 1}/${chain.length}] Aplicando: ${entry.archiveRelativePath}" 1>&2`);
|
||||||
helperCmds.push(`tar -xzf ${shellQuote(archivePath)} -C /`);
|
helperCmds.push(`tar -xzf ${shellQuote(archivePath)} -C /; RC=$?; [ $RC -le 1 ] || exit $RC`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emite progresso inicial (o helper roda tudo de uma vez, sem granularidade por arquivo).
|
// Emite progresso inicial (o helper roda tudo de uma vez, sem granularidade por arquivo).
|
||||||
|
|
@ -946,7 +950,7 @@ class BackupService {
|
||||||
|
|
||||||
await this.dockerService.runHelper({
|
await this.dockerService.runHelper({
|
||||||
binds: helperBinds,
|
binds: helperBinds,
|
||||||
cmd: helperCmds.join(' && '),
|
cmd: helperCmds.join('\n'),
|
||||||
onOutput: (line) => {
|
onOutput: (line) => {
|
||||||
const normalized = String(line || '').trim();
|
const normalized = String(line || '').trim();
|
||||||
if (normalized) pushLog(normalized, 'restaurando');
|
if (normalized) pushLog(normalized, 'restaurando');
|
||||||
|
|
@ -1007,21 +1011,24 @@ class BackupService {
|
||||||
const backupRoot = normalizeDockerHostPath(profile.backupDir);
|
const backupRoot = normalizeDockerHostPath(profile.backupDir);
|
||||||
const wasRunning = inspect.State?.Running === true;
|
const wasRunning = inspect.State?.Running === true;
|
||||||
const binds = [`${backupRoot}:/backuproot:ro`];
|
const binds = [`${backupRoot}:/backuproot:ro`];
|
||||||
for (const [index, mount] of currentMounts.entries()) {
|
const helperCmds = [];
|
||||||
binds.push(`${getMountBindingSource(mount)}:/restore/m${index}`);
|
|
||||||
|
// Montar cada volume/bind no seu path real (igual ao path nativo).
|
||||||
|
// Archives criados pelo helper já têm entries com paths reais (a0/..., var/lib/gitea/...),
|
||||||
|
// então extrair com -C / coloca os arquivos nos volumes montados corretamente.
|
||||||
|
for (const mount of currentMounts) {
|
||||||
|
if (restorePaths && restorePaths.length > 0 && !restorePaths.includes(mount.destination)) continue;
|
||||||
|
binds.push(`${getMountBindingSource(mount)}:${mount.destination}`);
|
||||||
|
helperCmds.push(
|
||||||
|
`find ${shellQuote(mount.destination)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + 2>/dev/null || true`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanupCommands = currentMounts.map((_mount, index) => (
|
// Extração de cada archive sem set -e/&&: tar frequentemente retorna código 1
|
||||||
`find ${shellQuote(`/restore/m${index}`)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`
|
// (avisos de permissão/ownership) — aceitar código 1 como sucesso, falhar apenas em >= 2.
|
||||||
));
|
|
||||||
|
|
||||||
// Sem --listed-incremental: extração simples sobreposcrita de arquivos.
|
|
||||||
// O --listed-incremental=/dev/null (modo snapshot vazio) causava deleção incorreta
|
|
||||||
// de arquivos já restaurados pelo backup full ao aplicar os incrementais seguintes.
|
|
||||||
const restoreCommands = [];
|
|
||||||
for (const [index, entry] of chain.entries()) {
|
for (const [index, entry] of chain.entries()) {
|
||||||
restoreCommands.push(`echo "[${index + 1}/${chain.length}] Aplicando: ${entry.archiveRelativePath}" 1>&2`);
|
helperCmds.push(`echo "[${index + 1}/${chain.length}] Aplicando: ${entry.archiveRelativePath}" 1>&2`);
|
||||||
restoreCommands.push(`tar -xzf ${shellQuote(`/backuproot/${entry.archiveRelativePath}`)} -C /restore`);
|
helperCmds.push(`tar -xzf ${shellQuote(`/backuproot/${entry.archiveRelativePath}`)} -C /; RC=$?; [ $RC -le 1 ] || exit $RC`);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -1029,10 +1036,9 @@ class BackupService {
|
||||||
await this.dockerService.stopContainer(targetEntry.containerId);
|
await this.dockerService.stopContainer(targetEntry.containerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmd = ['set -eu', ...cleanupCommands, ...restoreCommands].join(' && ');
|
|
||||||
await this.dockerService.runHelper({
|
await this.dockerService.runHelper({
|
||||||
binds,
|
binds,
|
||||||
cmd,
|
cmd: helperCmds.join('\n'),
|
||||||
onOutput: (line) => {
|
onOutput: (line) => {
|
||||||
const normalized = String(line || '').trim();
|
const normalized = String(line || '').trim();
|
||||||
if (normalized) pushLog(normalized, 'restaurando');
|
if (normalized) pushLog(normalized, 'restaurando');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue