fix(backup): fix 7 bugs in backup/restore — strip-components, multi-db path, verify+log in HTTP handler, parseBackupFilename for .tar, getArchiveSize gzip -l, dead code removal
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled

This commit is contained in:
2026-05-25 20:44:14 +03:00
parent a37a62c69e
commit a5abb6031b
3 changed files with 34 additions and 17 deletions
+1
View File
@@ -18,6 +18,7 @@ clients/nim/tests/test_client
src/baradadb src/baradadb
src/barabadb/client/client src/barabadb/client/client
src/barabadb/core/raft src/barabadb/core/raft
src/barabadb/core/backup
# Temp # Temp
*.tmp *.tmp
+19 -11
View File
@@ -151,28 +151,37 @@ proc formatTimestamp*(ts: int64): string =
result = $ts result = $ts
proc parseBackupFilename*(filename: string): int64 = proc parseBackupFilename*(filename: string): int64 =
## Try to extract timestamp from backup_1234567890.tar.gz ## Try to extract timestamp from backup_1234567890.tar.gz (or .tar)
try: try:
let name = extractFilename(filename) let name = extractFilename(filename)
if name.startsWith("backup_"): if name.startsWith("backup_"):
let tsStr = name[7..^8] # skip "backup_" and ".tar.gz" let tsStr = if name.endsWith(".tar.gz"): name[7..^8]
elif name.endsWith(".tar"): name[7..^5]
else: ""
if tsStr.len > 0:
result = parseBiggestInt(tsStr) result = parseBiggestInt(tsStr)
else: else:
result = 0 result = 0
else:
result = 0
except: except:
result = 0 result = 0
proc getArchiveSize*(input: string): int64 = proc getArchiveSize*(input: string): int64 =
## Return uncompressed size estimate from tar archive ## Return uncompressed size in bytes from archive.
let cmd = "tar -tzf " & quoteShell(input) & " | wc -l" ## Uses gzip -l for .gz; falls back to file size for plain .tar.
if input.endsWith(".gz"):
let cmd = "gzip -l " & quoteShell(input) & " 2>/dev/null | awk 'NR==2{print $2}'"
let (outStr, exitCode) = execCmdEx(cmd) let (outStr, exitCode) = execCmdEx(cmd)
if exitCode == 0: if exitCode == 0:
try: try:
result = parseBiggestInt(strip(outStr)) result = parseBiggestInt(strip(outStr))
except: except:
result = 0 result = getFileSize(input) # fallback
else: else:
result = 0 result = getFileSize(input)
else:
result = getFileSize(input)
proc getFreeSpace*(path: string): int64 = proc getFreeSpace*(path: string): int64 =
## Return free disk space in bytes for the filesystem containing path ## Return free disk space in bytes for the filesystem containing path
@@ -287,7 +296,7 @@ proc restoreDataDir*(input: string, dataDir: string, verbose: bool = false, dryR
createDir(dataDir) createDir(dataDir)
let cmd = "tar -xzf " & quoteShell(input) & " -C " & quoteShell(dataDir) let cmd = "tar -xzf " & quoteShell(input) & " --strip-components=1 -C " & quoteShell(dataDir)
if verbose: if verbose:
echo "Running: ", cmd echo "Running: ", cmd
@@ -523,7 +532,6 @@ proc backupAllDatabases*(dataRoot: string, output: string, excludes: seq[string]
if fileExists(output): if fileExists(output):
echo "WARNING: Overwriting existing file: ", output echo "WARNING: Overwriting existing file: ", output
let workDir = getCurrentDir()
let tempDir = getTempDir() / "baradb_backup_" & $getTime().toUnix() let tempDir = getTempDir() / "baradb_backup_" & $getTime().toUnix()
createDir(tempDir / "databases") createDir(tempDir / "databases")
@@ -608,7 +616,7 @@ proc restoreAllDatabases*(input: string, dataRoot: string, verbose: bool = false
createDir(dataRoot) createDir(dataRoot)
let cmd = "tar -xzf " & quoteShell(input) & " -C " & quoteShell(dataRoot) let cmd = "tar -xzf " & quoteShell(input) & " -C " & quoteShell(parentDir(dataRoot))
if verbose: if verbose:
echo "Running: ", cmd echo "Running: ", cmd
@@ -625,8 +633,8 @@ proc restoreAllDatabases*(input: string, dataRoot: string, verbose: bool = false
echo "Rollback complete. Data restored to previous state." echo "Rollback complete. Data restored to previous state."
return false return false
# Verify metadata # Verify metadata (extracted to parent dir due to archive layout)
let metaPath = dataRoot / BACKUP_META_FILE let metaPath = parentDir(dataRoot) / BACKUP_META_FILE
if fileExists(metaPath): if fileExists(metaPath):
try: try:
let meta = parseJson(readFile(metaPath)) let meta = parseJson(readFile(metaPath))
+8
View File
@@ -524,6 +524,12 @@ proc restoreHandler(server: HttpServer): RequestHandler =
let dbName = if body != nil and "database" in body: body["database"].getStr() else: "" let dbName = if body != nil and "database" in body: body["database"].getStr() else: ""
let dataRoot = server.registry.dataRoot let dataRoot = server.registry.dataRoot
try: try:
# Verify archive integrity first
if not verifyArchive(inputFile, false):
logRestore(inputFile, dataRoot, false)
ctx.json(%*{"error": "Archive verification failed — file may be corrupted"}, 500)
return
let meta = readBackupMeta(inputFile) let meta = readBackupMeta(inputFile)
let isMultiDb = meta != nil and meta{"databases"} != nil let isMultiDb = meta != nil and meta{"databases"} != nil
var ok = false var ok = false
@@ -534,6 +540,8 @@ proc restoreHandler(server: HttpServer): RequestHandler =
ok = restoreDataDir(inputFile, dbDir, false, false) ok = restoreDataDir(inputFile, dbDir, false, false)
else: else:
ok = restoreAllDatabases(inputFile, dataRoot, false, false) ok = restoreAllDatabases(inputFile, dataRoot, false, false)
logRestore(inputFile, dataRoot, ok)
if ok: if ok:
# Reload databases after restore # Reload databases after restore
server.registry.loadExistingDatabases() server.registry.loadExistingDatabases()