feat: Phase 6 — Docker + docker-compose + backup/restore CLI
- Dockerfile: multi-stage build (nim:alpine -> alpine), env-configurable - docker-compose.yml: single node with volume + healthcheck - backup.nim: backup/restore tar.gz snapshots, list/cleanup - CLI: baradadb backup|restore|list --data-dir=DIR - All 216 tests pass
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
|||||||
|
FROM nim:2.2-alpine AS builder
|
||||||
|
WORKDIR /build
|
||||||
|
COPY baradadb.nimble .
|
||||||
|
COPY src/ src/
|
||||||
|
RUN nimble install -d -y || true
|
||||||
|
RUN nim c -d:release --opt:speed -o:baradadb src/baradadb.nim
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /build/baradadb .
|
||||||
|
RUN mkdir -p /data
|
||||||
|
ENV BARADB_PORT=9000
|
||||||
|
ENV BARADB_DATA_DIR=/data
|
||||||
|
ENV BARADB_HTTP_PORT=8080
|
||||||
|
ENV BARADB_WS_PORT=8081
|
||||||
|
EXPOSE 9000 8080 8081
|
||||||
|
VOLUME ["/data"]
|
||||||
|
CMD ["./baradadb"]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
baradb:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "8080:8080"
|
||||||
|
- "8081:8081"
|
||||||
|
volumes:
|
||||||
|
- baradb_data:/data
|
||||||
|
environment:
|
||||||
|
- BARADB_PORT=9000
|
||||||
|
- BARADB_DATA_DIR=/data
|
||||||
|
- BARADB_HTTP_PORT=8080
|
||||||
|
- BARADB_WS_PORT=8081
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
baradb_data:
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
## BaraDB Backup & Restore — tar.gz snapshots
|
||||||
|
import std/os
|
||||||
|
import std/osproc
|
||||||
|
import std/strutils
|
||||||
|
import std/times
|
||||||
|
|
||||||
|
type
|
||||||
|
Backup* = object
|
||||||
|
path*: string
|
||||||
|
timestamp*: int64
|
||||||
|
size*: int64
|
||||||
|
|
||||||
|
proc backupDataDir*(dataDir: string, output: string): bool =
|
||||||
|
## Create a tar.gz backup of the data directory
|
||||||
|
if not dirExists(dataDir):
|
||||||
|
echo "Data directory not found: ", dataDir
|
||||||
|
return false
|
||||||
|
|
||||||
|
let parent = parentDir(dataDir)
|
||||||
|
let name = lastPathPart(dataDir)
|
||||||
|
let cmd = "tar -czf " & quoteShell(output) & " -C " & quoteShell(parent) & " " & quoteShell(name)
|
||||||
|
let exitCode = execCmd(cmd)
|
||||||
|
return exitCode == 0
|
||||||
|
|
||||||
|
proc restoreDataDir*(input: string, dataDir: string): bool =
|
||||||
|
## Restore from a tar.gz backup
|
||||||
|
if not fileExists(input):
|
||||||
|
echo "Backup file not found: ", input
|
||||||
|
return false
|
||||||
|
|
||||||
|
if dirExists(dataDir):
|
||||||
|
removeDir(dataDir)
|
||||||
|
createDir(dataDir)
|
||||||
|
|
||||||
|
let cmd = "tar -xzf " & quoteShell(input) & " -C " & quoteShell(dataDir)
|
||||||
|
let exitCode = execCmd(cmd)
|
||||||
|
return exitCode == 0
|
||||||
|
|
||||||
|
proc listBackups*(dataDir: string): seq[Backup] =
|
||||||
|
result = @[]
|
||||||
|
for kind, path in walkDir(parentDir(dataDir)):
|
||||||
|
let (_, name, ext) = splitFile(path)
|
||||||
|
if ext == ".gz":
|
||||||
|
var backup = Backup(path: path)
|
||||||
|
backup.size = getFileSize(path)
|
||||||
|
result.add(backup)
|
||||||
|
|
||||||
|
proc cleanupOldBackups*(dataDir: string, keepLast: int = 5) =
|
||||||
|
var backups = listBackups(dataDir)
|
||||||
|
if backups.len <= keepLast:
|
||||||
|
return
|
||||||
|
for i in 0..<(backups.len - keepLast):
|
||||||
|
removeFile(backups[i].path)
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
import std/os
|
||||||
|
import std/parseopt
|
||||||
|
|
||||||
|
var
|
||||||
|
command = ""
|
||||||
|
dataDir = "data/server"
|
||||||
|
target = ""
|
||||||
|
|
||||||
|
for kind, key, val in getopt():
|
||||||
|
case kind
|
||||||
|
of cmdArgument:
|
||||||
|
if command == "":
|
||||||
|
command = key
|
||||||
|
elif target == "":
|
||||||
|
target = key
|
||||||
|
of cmdLongOption, cmdShortOption:
|
||||||
|
case key
|
||||||
|
of "data-dir", "d": dataDir = val
|
||||||
|
of "output", "o": target = val
|
||||||
|
of "input", "i": target = val
|
||||||
|
else: discard
|
||||||
|
of cmdEnd: discard
|
||||||
|
|
||||||
|
case command
|
||||||
|
of "backup":
|
||||||
|
let outputFile = if target.len > 0: target else: "backup_" & $getTime().toUnix() & ".tar.gz"
|
||||||
|
let ok = backupDataDir(dataDir, outputFile)
|
||||||
|
if ok:
|
||||||
|
echo "Backup created: ", outputFile
|
||||||
|
else:
|
||||||
|
quit("Backup failed", 1)
|
||||||
|
of "restore":
|
||||||
|
if target.len == 0:
|
||||||
|
quit("Usage: backup restore --input=<file.tar.gz>", 1)
|
||||||
|
let ok = restoreDataDir(target, dataDir)
|
||||||
|
if ok:
|
||||||
|
echo "Restored from: ", target
|
||||||
|
else:
|
||||||
|
quit("Restore failed", 1)
|
||||||
|
of "list":
|
||||||
|
let backups = listBackups(dataDir)
|
||||||
|
echo "Backups:"
|
||||||
|
for b in backups:
|
||||||
|
echo " ", b.path, " (", b.size, " bytes)"
|
||||||
|
else:
|
||||||
|
echo "Usage: backup <backup|restore|list> [--data-dir=DIR] [--output/-i=FILE]"
|
||||||
|
quit(1)
|
||||||
Reference in New Issue
Block a user