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
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:
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
result = parseBiggestInt(tsStr)
|
elif name.endsWith(".tar"): name[7..^5]
|
||||||
|
else: ""
|
||||||
|
if tsStr.len > 0:
|
||||||
|
result = parseBiggestInt(tsStr)
|
||||||
|
else:
|
||||||
|
result = 0
|
||||||
else:
|
else:
|
||||||
result = 0
|
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.
|
||||||
let (outStr, exitCode) = execCmdEx(cmd)
|
if input.endsWith(".gz"):
|
||||||
if exitCode == 0:
|
let cmd = "gzip -l " & quoteShell(input) & " 2>/dev/null | awk 'NR==2{print $2}'"
|
||||||
try:
|
let (outStr, exitCode) = execCmdEx(cmd)
|
||||||
result = parseBiggestInt(strip(outStr))
|
if exitCode == 0:
|
||||||
except:
|
try:
|
||||||
result = 0
|
result = parseBiggestInt(strip(outStr))
|
||||||
|
except:
|
||||||
|
result = getFileSize(input) # fallback
|
||||||
|
else:
|
||||||
|
result = getFileSize(input)
|
||||||
else:
|
else:
|
||||||
result = 0
|
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))
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user