Files
timmy-home/scripts/restore_backup.sh
2026-04-14 23:56:46 -04:00

98 lines
2.8 KiB
Bash

#!/usr/bin/env bash
# restore_backup.sh — Restore an encrypted Hermes backup archive
# Usage: restore_backup.sh /path/to/hermes-backup-YYYYmmdd-HHMMSS.tar.gz.enc /restore/root
set -euo pipefail
ARCHIVE_PATH="${1:-}"
RESTORE_ROOT="${2:-}"
STAGE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/timmy-restore.XXXXXX")"
PLAINTEXT_ARCHIVE="${STAGE_DIR}/restore.tar.gz"
PASSFILE_CLEANUP=""
cleanup() {
rm -f "$PLAINTEXT_ARCHIVE"
rm -rf "$STAGE_DIR"
if [[ -n "$PASSFILE_CLEANUP" && -f "$PASSFILE_CLEANUP" ]]; then
rm -f "$PASSFILE_CLEANUP"
fi
}
trap cleanup EXIT
fail() {
echo "ERROR: $1" >&2
exit 1
}
resolve_passphrase_file() {
if [[ -n "${BACKUP_PASSPHRASE_FILE:-}" ]]; then
[[ -f "$BACKUP_PASSPHRASE_FILE" ]] || fail "BACKUP_PASSPHRASE_FILE does not exist: $BACKUP_PASSPHRASE_FILE"
echo "$BACKUP_PASSPHRASE_FILE"
return
fi
if [[ -n "${BACKUP_PASSPHRASE:-}" ]]; then
PASSFILE_CLEANUP="${STAGE_DIR}/backup.passphrase"
printf '%s' "$BACKUP_PASSPHRASE" > "$PASSFILE_CLEANUP"
chmod 600 "$PASSFILE_CLEANUP"
echo "$PASSFILE_CLEANUP"
return
fi
fail "Set BACKUP_PASSPHRASE_FILE or BACKUP_PASSPHRASE before restoring a backup."
}
sha256_file() {
local path="$1"
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$path" | awk '{print $1}'
elif command -v sha256sum >/dev/null 2>&1; then
sha256sum "$path" | awk '{print $1}'
else
python3 - <<'PY' "$path"
import hashlib
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
h = hashlib.sha256()
with path.open('rb') as f:
for chunk in iter(lambda: f.read(1024 * 1024), b''):
h.update(chunk)
print(h.hexdigest())
PY
fi
}
[[ -n "$ARCHIVE_PATH" ]] || fail "Usage: restore_backup.sh /path/to/archive.tar.gz.enc /restore/root"
[[ -n "$RESTORE_ROOT" ]] || fail "Usage: restore_backup.sh /path/to/archive.tar.gz.enc /restore/root"
[[ -f "$ARCHIVE_PATH" ]] || fail "Archive not found: $ARCHIVE_PATH"
if [[ "$ARCHIVE_PATH" == *.tar.gz.enc ]]; then
MANIFEST_PATH="${ARCHIVE_PATH%.tar.gz.enc}.json"
else
MANIFEST_PATH=""
fi
if [[ -n "$MANIFEST_PATH" && -f "$MANIFEST_PATH" ]]; then
EXPECTED_SHA="$(python3 - <<'PY' "$MANIFEST_PATH"
import json
import sys
with open(sys.argv[1], 'r', encoding='utf-8') as handle:
manifest = json.load(handle)
print(manifest['archive_sha256'])
PY
)"
ACTUAL_SHA="$(sha256_file "$ARCHIVE_PATH")"
[[ "$EXPECTED_SHA" == "$ACTUAL_SHA" ]] || fail "Archive SHA256 mismatch: expected $EXPECTED_SHA got $ACTUAL_SHA"
fi
PASSFILE="$(resolve_passphrase_file)"
mkdir -p "$RESTORE_ROOT"
openssl enc -d -aes-256-cbc -salt -pbkdf2 -iter 200000 \
-pass "file:${PASSFILE}" \
-in "$ARCHIVE_PATH" \
-out "$PLAINTEXT_ARCHIVE"
tar -xzf "$PLAINTEXT_ARCHIVE" -C "$RESTORE_ROOT"
echo "Restored backup into $RESTORE_ROOT"