3f09cdb5ae
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.
251 lines
6.7 KiB
Bash
Executable File
251 lines
6.7 KiB
Bash
Executable File
#!/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
|