ajusta lógica de backup incremental e melhora documentação do método getSelfBindSource

This commit is contained in:
Alexander Sabino 2026-05-07 12:58:05 +01:00
parent b66ebda525
commit 01dd302b97
2 changed files with 18 additions and 21 deletions

View File

@ -445,20 +445,11 @@ class BackupService {
} else { } else {
// Fallback para containers sem GNU tar: usa helper container com GNU tar // Fallback para containers sem GNU tar: usa helper container com GNU tar
// montando os volumes diretamente — produz .snar como qualquer outro container. // montando os volumes diretamente — produz .snar como qualquer outro container.
if (runMode === 'incremental') { if (runMode === 'full') {
try { // Full: remove .snar anterior para forçar snapshot limpo no helper.
await fs.access(absoluteSnapshotPath);
tarIncrementalFlag = `--listed-incremental=${shellQuote('/backuproot/' + snapshotRelativePath)}`;
pushLog('Backup incremental via helper: snapshot anterior encontrado.', 'preparando');
} catch {
pushLog('Aviso: snapshot anterior nao encontrado, gerando backup completo via helper.', 'preparando');
tarIncrementalFlag = `--listed-incremental=${shellQuote('/backuproot/' + snapshotRelativePath)}`;
}
} else {
// Full: remove .snar anterior para forçar snapshot limpo.
await fs.rm(absoluteSnapshotPath, { force: true }).catch(() => null); await fs.rm(absoluteSnapshotPath, { force: true }).catch(() => null);
tarIncrementalFlag = `--listed-incremental=${shellQuote('/backuproot/' + snapshotRelativePath)}`;
} }
// O .snar é gerenciado pelo helper usando helperSnarPath calculado abaixo.
} }
// Containers BusyBox sem GNU tar: roda o tar num helper container que TEM GNU tar, // Containers BusyBox sem GNU tar: roda o tar num helper container que TEM GNU tar,
@ -469,28 +460,31 @@ class BackupService {
// Quando rodando dentro do Docker, backupRoot é um path interno do container da app. // Quando rodando dentro do Docker, backupRoot é um path interno do container da app.
// Precisamos do source real (host path ou volume name) para passar ao helper container. // Precisamos do source real (host path ou volume name) para passar ao helper container.
const backupRootSource = await this.dockerService.getSelfBindSource(backupRoot); const selfBind = await this.dockerService.getSelfBindSource(backupRoot);
if (!backupRootSource) { if (!selfBind) {
throw new Error( throw new Error(
`Nao foi possivel determinar o source do diretorio de backup (${backupRoot}) para o helper. ` + `Nao foi possivel determinar o source do diretorio de backup (${backupRoot}) para o helper. ` +
'Verifique se o diretorio de backup esta montado via volume ou bind no container da app.' 'Verifique se o diretorio de backup esta montado via volume ou bind no container da app.'
); );
} }
const helperBinds = [`${backupRootSource}:/backuproot`]; // suffix é o subpath dentro do volume (ex: /backups quando mount=/app/data e backupRoot=/app/data/backups)
const helperBackupRoot = `/backuproot_base${selfBind.suffix}`;
const helperBinds = [`${selfBind.source}:/backuproot_base`];
const helperRelPaths = []; const helperRelPaths = [];
for (const [index, mount] of activeMounts.entries()) { for (const [index, mount] of activeMounts.entries()) {
const src = mount.type === 'volume' ? mount.name : mount.source; const src = mount.type === 'volume' ? mount.name : mount.source;
helperBinds.push(`${src}:/payload/m${index}:ro`); helperBinds.push(`${src}:/payload/m${index}:ro`);
helperRelPaths.push(`payload/m${index}`); helperRelPaths.push(`payload/m${index}`);
} }
const helperArchivePath = `/backuproot/${archiveRelativePath}`; const helperArchivePath = `${helperBackupRoot}/${archiveRelativePath}`;
const helperSnarDir = path.posix.dirname(`/backuproot/${snapshotRelativePath}`); const helperSnarPath = `${helperBackupRoot}/${snapshotRelativePath}`;
const helperSnarDir = path.posix.dirname(helperSnarPath);
const helperCmd = [ const helperCmd = [
'set -u', 'set -u',
`mkdir -p ${shellQuote(path.posix.dirname(helperArchivePath))} ${shellQuote(helperSnarDir)}`, `mkdir -p ${shellQuote(path.posix.dirname(helperArchivePath))} ${shellQuote(helperSnarDir)}`,
`echo "__DBKP_TAR_BEGIN__" 1>&2`, `echo "__DBKP_TAR_BEGIN__" 1>&2`,
`tar --ignore-failed-read ${tarIncrementalFlag} -czvf ${shellQuote(helperArchivePath)} -C / ${helperRelPaths.map((p) => shellQuote(p)).join(' ')}; TAR_RC=$?; [ $TAR_RC -le 1 ] || exit $TAR_RC`, `tar --ignore-failed-read --listed-incremental=${shellQuote(helperSnarPath)} -czvf ${shellQuote(helperArchivePath)} -C / ${helperRelPaths.map((p) => shellQuote(p)).join(' ')}; TAR_RC=$?; [ $TAR_RC -le 1 ] || exit $TAR_RC`,
].join('; '); ].join('; ');
await this.dockerService.runHelper({ await this.dockerService.runHelper({

View File

@ -88,8 +88,9 @@ class DockerService {
} }
// Dado um caminho absoluto dentro do container da app (ex: /app/data/backups), // Dado um caminho absoluto dentro do container da app (ex: /app/data/backups),
// retorna o source do bind/volume que o cobre (para usar em helper binds). // retorna { source, suffix } onde source é o bind/volume que cobre o caminho
// Se não encontrar mount correspondente, retorna null. // e suffix é o subpath dentro desse mount (ex: source='data_vol', suffix='/backups').
// Retorna null se não encontrar mount correspondente.
async getSelfBindSource(containerPath) { async getSelfBindSource(containerPath) {
const mounts = await this._getSelfMounts(); const mounts = await this._getSelfMounts();
const normalized = containerPath.replace(/\/+$/, ''); const normalized = containerPath.replace(/\/+$/, '');
@ -100,7 +101,9 @@ class DockerService {
for (const mount of sorted) { for (const mount of sorted) {
const dest = (mount.Destination || '').replace(/\/+$/, ''); const dest = (mount.Destination || '').replace(/\/+$/, '');
if (normalized === dest || normalized.startsWith(dest + '/')) { if (normalized === dest || normalized.startsWith(dest + '/')) {
return mount.Type === 'volume' ? mount.Name : mount.Source; const source = mount.Type === 'volume' ? mount.Name : mount.Source;
const suffix = normalized.slice(dest.length) || '';
return { source, suffix };
} }
} }
return null; return null;