From ea361904ba55935989966d6547289dd200a2383f Mon Sep 17 00:00:00 2001 From: Kevin Bataille Date: Sat, 25 Jul 2026 19:01:33 +0200 Subject: [PATCH] feat: add non-destructive file-restore mode to restore.sh --- restore.sh | 21 +++++++++++++++++++++ tests/test_restore.sh | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/restore.sh b/restore.sh index 42864f3..c6034df 100755 --- a/restore.sh +++ b/restore.sh @@ -65,6 +65,27 @@ list_archives() { borg list --lock-wait 60 } +extract_path() { + local rel_path="$1" dest_dir="$2" archive="$3" + mkdir -p "$dest_dir" + ( cd "$dest_dir" && run_cmd borg extract --lock-wait 600 "${REPO}::${archive}" "${ARCHIVE_TARGET_PATH}/${rel_path}" ) + echo "${dest_dir%/}/${ARCHIVE_TARGET_PATH}/${rel_path}" +} + +cmd_file() { + local rel_path="$1" dest + resolve_archive + dest="${DEST:-/tmp/restore-file-$$}" + step "Restoring '$rel_path' from archive $RESOLVED_ARCHIVE into $dest" + if [[ "$DRY_RUN" == true ]]; then + log "[DRY-RUN] would extract ${ARCHIVE_TARGET_PATH}/${rel_path} from ${REPO}::${RESOLVED_ARCHIVE} into $dest" + return 0 + fi + local final + final="$(extract_path "$rel_path" "$dest" "$RESOLVED_ARCHIVE" | tail -n1)" + log "Restored file available at: $final" +} + usage() { cat <<'EOF' Usage: diff --git a/tests/test_restore.sh b/tests/test_restore.sh index 8aa3d2e..ca509ac 100755 --- a/tests/test_restore.sh +++ b/tests/test_restore.sh @@ -59,10 +59,44 @@ test_list_archives() { rm -rf "$mockdir" } +test_file_restore_extracts_to_dest() { + local mockdir dest out final + mockdir="$(mktemp -d)" + dest="$(mktemp -d)" + setup_mock_bin "$mockdir" + out="$(PATH="$mockdir:$PATH" bash "$RESTORE" file photos/img.jpg --dest "$dest")" + final="$dest/home/srv/files/content/photos/img.jpg" + assert_contains "$out" "Restored file available at: $final" "file mode reports final path" + if [[ -f "$final" ]]; then + echo "PASS: extracted file exists on disk" + else + echo "FAIL: extracted file missing at $final" + FAILURES=$((FAILURES + 1)) + fi + rm -rf "$mockdir" "$dest" +} + +test_file_restore_dry_run_makes_no_borg_call() { + local mockdir dest + mockdir="$(mktemp -d)" + dest="$(mktemp -d)" + setup_mock_bin "$mockdir" + PATH="$mockdir:$PATH" bash "$RESTORE" file photos/img.jpg --dest "$dest" --dry-run >/dev/null + if [[ -s "$mockdir/mock.log" ]] && grep -q "^borg extract" "$mockdir/mock.log"; then + echo "FAIL: dry-run invoked borg extract" + FAILURES=$((FAILURES + 1)) + else + echo "PASS: dry-run made no borg extract call" + fi + rm -rf "$mockdir" "$dest" +} + test_help_exits_zero test_no_args_exits_one test_unknown_command_exits_one test_list_archives +test_file_restore_extracts_to_dest +test_file_restore_dry_run_makes_no_borg_call echo "-----" if [[ "$FAILURES" -gt 0 ]]; then