#!/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 — 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 < 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