#!/usr/bin/env bash set -euo pipefail # --- SUDO CHECK --- if [ "$EUID" -ne 0 ]; then echo "This script requires root privileges. Re-running with sudo..." exec sudo "$0" "$@" fi # --- HANDLE -k OPTION FOR KEY FILE --- BORG_PASSPHRASE="" while getopts "k:" opt; do case "$opt" in k) BORG_PASSPHRASE=$(<"$OPTARG") if [ -z "$BORG_PASSPHRASE" ]; then echo "Error: The key file is empty." exit 1 fi echo "Using passphrase from key file: $OPTARG" ;; *) echo "Usage: $0 [-k passphrase_file] " exit 1 ;; esac done shift $((OPTIND - 1)) # --- FALLBACK TO /run/secrets/borg_passwd IF NO KEY FILE --- if [ -z "$BORG_PASSPHRASE" ]; then if [ -f "/run/secrets/borg_passwd" ]; then BORG_PASSPHRASE=$(<"/run/secrets/borg_passwd") echo "Using passphrase from /run/secrets/borg_passwd" else # Prompt user for passphrase if neither -k nor /run/secrets/borg_passwd is available read -s -p "Enter Borg repository passphrase: " BORG_PASSPHRASE echo fi fi export BORG_PASSPHRASE # --- DEFAULT REPO --- REPO="${1:-/holocron/backups}" # --- CHECK REQUIRED COMMANDS --- for cmd in borg fzf find tree cp mkdir; do command -v "$cmd" >/dev/null || { echo "Error: '$cmd' is required but not installed."; exit 1; } done # --- LIST ARCHIVES (sorted, newest last) --- mapfile -t archives < <(borg list --format="{archive}{NL}" "$REPO" | sort) if [ ${#archives[@]} -eq 0 ]; then echo "No archives found in $REPO" exit 1 fi # --- FZF ARCHIVE SELECT --- selected=$(printf '%s\n' "${archives[@]}" | fzf --prompt="Select archive: " --height=40% --border --reverse) if [ -z "$selected" ]; then echo "No archive selected." exit 1 fi echo "Selected archive: $selected" # --- GENERATE A UNIQUE, SHORTER MOUNT POINT --- MOUNT_POINT="/tmp/borg-mount-${selected}-$(uuidgen | sha256sum | head -c 6)" mkdir -p "$MOUNT_POINT" # --- MOUNT ARCHIVE --- echo "Mounting '$selected' to $MOUNT_POINT..." borg mount "$REPO::$selected" "$MOUNT_POINT" if [ ! -d "$MOUNT_POINT" ]; then echo "Error: mount failed." exit 1 fi # --- LIST FILES AND DIRECTORIES --- echo "Scanning files and directories..." if command -v fd >/dev/null 2>&1; then # List files and directories using fd (can handle both files and dirs) files=$(fd --type f --type d . "$MOUNT_POINT" | sort) else # Fall back to find if fd is not available files=$(find "$MOUNT_POINT" -type f -o -type d | sort) fi if [ -z "$files" ]; then echo "No files or directories found in archive." borg umount "$MOUNT_POINT" rm -rf "$MOUNT_POINT" exit 1 fi # --- HIERARCHICAL FZF FILE/DIRECTORY SELECTION (REVERSED) --- # We reverse the order of files to display the latest (newest) files/folders at the top. selected_files=$(printf '%s\n' "$files" | sed "s|$MOUNT_POINT/||" | tac | fzf \ --multi \ --height=50% \ --border \ --prompt="Select files or directories to restore: " \ --preview "tree -C -L 5 $MOUNT_POINT/$(dirname {})" \ --preview-window=right:50% \ --delimiter='/' \ --with-nth=1..) if [ -z "$selected_files" ]; then echo "No files or directories selected. Exiting." borg umount "$MOUNT_POINT" rm -rf "$MOUNT_POINT" exit 0 fi # --- SUMMARY OF SELECTED FILES/DIRECTORIES --- echo "Selected files and directories:" for file in $selected_files; do echo " $file" done # --- OPTIONS MENU (concise) --- # Default to option 1 if no input is given echo "Select restore destination: 1) Restore to ./${selected}_restore 2) Restore to original dirs 3) Quit" read -p "Enter your choice (1/2/3) [default: 1]: " choice # Default to option 1 if user presses Enter without providing input choice="${choice:-1}" # --- SET RESTORE DESTINATION BASED ON USER CHOICE --- case "$choice" in 1) DEST="./${selected}_restore" ;; 2) DEST="$MOUNT_POINT" ;; 3) echo "Quitting. No files restored." borg umount "$MOUNT_POINT" rm -rf "$MOUNT_POINT" exit 0 ;; *) echo "Invalid choice. Exiting." borg umount "$MOUNT_POINT" rm -rf "$MOUNT_POINT" exit 1 ;; esac mkdir -p "$DEST" # --- RESTORE FILES AND DIRECTORIES --- echo "Restoring selected files and directories..." while IFS= read -r file; do # Path is already stripped of /tmp, so no need for further modification dest_path="$DEST/$file" mkdir -p "$(dirname "$dest_path")" # If it's a directory, we use cp -r to ensure the directory structure is restored if [ -d "$MOUNT_POINT/$file" ]; then cp -r "$MOUNT_POINT/$file" "$dest_path" else cp -a "$MOUNT_POINT/$file" "$dest_path" fi echo "Restored: $file" done <<< "$selected_files" # --- CLEANUP --- borg umount "$MOUNT_POINT" rm -rf "$MOUNT_POINT" echo "Restore complete."