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:
2026-05-06 11:39:18 +03:00
parent 3cdec95143
commit 10ab464a43
3 changed files with 143 additions and 0 deletions
+18
View File
@@ -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"]
+23
View File
@@ -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:
+102
View File
@@ -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)