scripts: add maven-backup.sh for encrypted DB backup/restore
Simple shell script for backing up, restoring, and verifying the encrypted SQLite database (AES-256-GCM with MVNC1\0 magic header). Commands: backup — cp + magic-verify to MAVEN_BACKUP_DIR restore — cp back with confirmation prompt verify — check magic header via od (no xxd/jq dependency) list — show all backups with size and validity Portable: uses only POSIX sh, od, grep, stat. Config path resolved from mavend.json via grep or env var overrides.
This commit is contained in:
Executable
+250
@@ -0,0 +1,250 @@
|
||||
#!/bin/bash
|
||||
# maven-backup.sh — backup/restore/verify the encrypted Maven SQLite database.
|
||||
#
|
||||
# The encrypted DB (AES-256-GCM with 6-byte magic "MVNC1\0") is the only
|
||||
# persistent state. Backup is `cp` + magic-verify; restore is `cp` back.
|
||||
#
|
||||
# Usage:
|
||||
# maven-backup.sh backup — create timestamped backup
|
||||
# maven-backup.sh restore <backup-file> — restore from backup
|
||||
# maven-backup.sh verify [backup-file] — verify magic header (default: latest)
|
||||
# maven-backup.sh list — list available backups
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
CONFIG_FILE="${MAVEN_CONFIG:-/opt/maven/config/mavend.json}"
|
||||
BACKUP_DIR="${MAVEN_BACKUP_DIR:-/var/backups/maven}"
|
||||
STATE_DIR="${MAVEN_STATE_DIR:-/var/lib/maven}"
|
||||
DB_FILE="${MAVEN_DB_FILE:-maven.db.enc}"
|
||||
DB_PATH="$STATE_DIR/$DB_FILE"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $SCRIPT_NAME {backup|restore|verify|list} [backup-file]
|
||||
|
||||
Commands:
|
||||
backup Create a timestamped backup of the encrypted DB
|
||||
restore <backup-file> Restore the DB from a backup file
|
||||
verify [backup-file] Verify backup integrity (default: latest)
|
||||
list List available backups
|
||||
|
||||
Environment:
|
||||
MAVEN_CONFIG Path to mavend.json (default: /opt/maven/config/mavend.json)
|
||||
MAVEN_BACKUP_DIR Backup directory (default: /var/backups/maven)
|
||||
MAVEN_STATE_DIR State directory with the DB file (default: /var/lib/maven)
|
||||
MAVEN_DB_FILE Database filename (default: maven.db.enc)
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# check_magic: verify the file starts with the correct 6-byte magic header.
|
||||
# Magic is "MVNC1\0" = bytes: 4d 56 4e 43 31 00
|
||||
check_magic() {
|
||||
local file="$1"
|
||||
if [ ! -f "$file" ]; then
|
||||
error "file not found: $file"
|
||||
return 1
|
||||
fi
|
||||
local magic
|
||||
magic=$(od -A n -t x1 -N 6 "$file" 2>/dev/null | tr -d ' \n')
|
||||
if [ "$magic" = "4d564e433100" ]; then
|
||||
return 0
|
||||
fi
|
||||
error "invalid magic header in $file (expected MVNC1\\0)"
|
||||
return 1
|
||||
}
|
||||
|
||||
# file_size: get file size in bytes (Linux stat).
|
||||
file_size() {
|
||||
local file="$1"
|
||||
stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo "0"
|
||||
}
|
||||
|
||||
# read_config_value: read a JSON string value from the config file using grep.
|
||||
read_config_value() {
|
||||
local key="$1"
|
||||
grep -o "\"$key\": *\"[^\"]*\"" "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*: *"\(.*\)"/\1/'
|
||||
}
|
||||
|
||||
# resolve_db_path: determine the actual DB path from config or defaults.
|
||||
resolve_db_path() {
|
||||
local config_db
|
||||
config_db=$(read_config_value "db_path")
|
||||
if [ -n "$config_db" ]; then
|
||||
echo "$config_db"
|
||||
else
|
||||
echo "$DB_PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_backup() {
|
||||
local actual_db
|
||||
actual_db=$(resolve_db_path)
|
||||
|
||||
if [ ! -f "$actual_db" ]; then
|
||||
error "database not found at $actual_db"
|
||||
error "is mavend running? has it been started at least once?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
local timestamp
|
||||
timestamp=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
local backup_file="$BACKUP_DIR/maven-db-$timestamp.enc"
|
||||
|
||||
info "backing up $actual_db → $backup_file"
|
||||
cp "$actual_db" "$backup_file"
|
||||
sync
|
||||
|
||||
if check_magic "$backup_file"; then
|
||||
local size
|
||||
size=$(file_size "$backup_file")
|
||||
info "backup complete: $backup_file ($size bytes)"
|
||||
echo "$backup_file"
|
||||
else
|
||||
error "backup file failed magic check — removing"
|
||||
rm -f "$backup_file"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_restore() {
|
||||
local backup_file="$1"
|
||||
local actual_db
|
||||
actual_db=$(resolve_db_path)
|
||||
|
||||
if [ ! -f "$backup_file" ]; then
|
||||
error "backup file not found: $backup_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! check_magic "$backup_file"; then
|
||||
error "backup file is not a valid encrypted Maven database"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$actual_db" ]; then
|
||||
local backup_size restore_size
|
||||
backup_size=$(file_size "$backup_file")
|
||||
restore_size=$(file_size "$actual_db")
|
||||
warn "about to OVERWRITE $actual_db ($restore_size bytes)"
|
||||
warn "with $backup_file ($backup_size bytes)"
|
||||
echo -n "continue? [y/N] "
|
||||
read -r confirm
|
||||
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
||||
info "restore cancelled"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
info "restoring $backup_file → $actual_db"
|
||||
cp "$backup_file" "$actual_db"
|
||||
sync
|
||||
|
||||
if check_magic "$actual_db"; then
|
||||
info "restore complete: $actual_db"
|
||||
else
|
||||
error "restored file failed magic check — database may be corrupt"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_verify() {
|
||||
local target="$1"
|
||||
|
||||
if [ ! -f "$target" ]; then
|
||||
error "file not found: $target"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if check_magic "$target"; then
|
||||
local size
|
||||
size=$(file_size "$target")
|
||||
info "valid encrypted database: $target ($size bytes)"
|
||||
return 0
|
||||
else
|
||||
error "NOT a valid encrypted database: $target"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
info "no backups directory: $BACKUP_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local backups
|
||||
backups=$(ls -1 "$BACKUP_DIR"/maven-db-*.enc 2>/dev/null || true)
|
||||
if [ -z "$backups" ]; then
|
||||
info "no backups found in $BACKUP_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Available backups in $BACKUP_DIR:"
|
||||
echo ""
|
||||
printf "%-30s %12s %s\n" "Filename" "Size" "Valid"
|
||||
echo "$(printf '=%*s' 70 '' | tr ' ' '=')"
|
||||
for f in $backups; do
|
||||
local name size valid
|
||||
name=$(basename "$f")
|
||||
size=$(file_size "$f")
|
||||
if check_magic "$f" >/dev/null 2>&1; then
|
||||
valid="${GREEN}✓${NC}"
|
||||
else
|
||||
valid="${RED}✗${NC}"
|
||||
fi
|
||||
printf "%-30s %12s %b\n" "$name" "$size" "$valid"
|
||||
done
|
||||
}
|
||||
|
||||
# --- Main ---
|
||||
|
||||
case "${1:-help}" in
|
||||
backup)
|
||||
cmd_backup
|
||||
;;
|
||||
restore)
|
||||
if [ -z "${2:-}" ]; then
|
||||
error "restore requires a backup file path"
|
||||
echo ""
|
||||
cmd_list
|
||||
exit 1
|
||||
fi
|
||||
cmd_restore "$2"
|
||||
;;
|
||||
verify)
|
||||
target="${2:-}"
|
||||
if [ -z "$target" ]; then
|
||||
if [ -d "$BACKUP_DIR" ]; then
|
||||
target=$(ls -t "$BACKUP_DIR"/maven-db-*.enc 2>/dev/null | head -1 || true)
|
||||
fi
|
||||
if [ -z "$target" ]; then
|
||||
target=$(resolve_db_path)
|
||||
info "no backups found; verifying live database"
|
||||
fi
|
||||
fi
|
||||
cmd_verify "$target"
|
||||
;;
|
||||
list)
|
||||
cmd_list
|
||||
;;
|
||||
help|--help|-h)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
error "unknown command: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user