#!/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 OPTIONS --- BORG_PASSPHRASE="" SHOW_FOLDERS_ONLY=false SHOW_FILES=false # Default to showing files and directories while getopts "k:fp" 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" ;; f) SHOW_FOLDERS_ONLY=true echo "Only directories will be shown in fzf by default." SHOW_FILES=false ;; p) SHOW_FILES=true echo "Both files and directories will be shown in fzf." ;; *) echo "Usage: $0 [-k passphrase_file] [-f] [-p] " exit 1 ;; esac done shift $((OPTIND - 1)) # --- FALLBACK TO /run/secrets/borg_passwd IF NO KEY FILE --- if [ -z "$BORG_PASSPHRASE" ]; then if [ $# -eq 0 ]; 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/archives/servers/snowbelle}" # --- 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 -r) 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) if [ -z "$selected" ]; then echo "No archive selected." exit 1 fi echo "Selected archive: $selected" # --- GENERATE A UNIQUE, SHORTER MOUNT POINT --- MOUNT_POINT="/tmp/$(uuidgen | sha256sum | head -c 2)-restore-${selected}" 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 both files and directories with fd if [ "$SHOW_FILES" = true ]; then files=$(fd . "$MOUNT_POINT" | sort) # Show both files and directories else files=$(fd --type d . "$MOUNT_POINT" | sort) # Only directories fi else # Fall back to find if fd is not available if [ "$SHOW_FILES" = true ]; then files=$(find "$MOUNT_POINT" | sort) # Show both files and directories else files=$(find "$MOUNT_POINT" -type d | sort) # Only directories fi fi if [ -z "$files" ]; then echo "No files or directories found in archive." borg umount "$MOUNT_POINT" rm -rf "$MOUNT_POINT" exit 1 fi # --- FZF FILE/DIRECTORY SELECTION --- if [ "$SHOW_FILES" = true ]; then # If showing files and directories, we pass everything to fzf selected_items=$(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..) else # If only showing directories, pass directories to fzf selected_items=$(printf '%s\n' "$files" | sed "s|$MOUNT_POINT/||" | tac | fzf \ --multi \ --height=50% \ --border \ --prompt="Select directories to restore: " \ --preview "tree -C -d -L 3 $MOUNT_POINT/$(dirname {})" \ --preview-window=right:50% \ --delimiter='/' \ --with-nth=1..) fi if [ -z "$selected_items" ]; then echo "No items selected. Exiting." borg umount "$MOUNT_POINT" rm -rf "$MOUNT_POINT" exit 0 fi # --- SUMMARY OF SELECTED ITEMS --- echo "Selected items:" for item in $selected_items; do echo " $item" 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 items 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 SELECTED ITEMS (FILES OR DIRECTORIES) --- echo "Restoring selected items..." while IFS= read -r item; do dest_path="$DEST/$item" mkdir -p "$(dirname "$dest_path")" if [ -d "$MOUNT_POINT/$item" ]; then cp -r "$MOUNT_POINT/$item" "$dest_path" else cp -a "$MOUNT_POINT/$item" "$dest_path" fi echo "Restored: $item" done <<< "$selected_items" # --- CLEANUP --- borg umount "$MOUNT_POINT" rm -rf "$MOUNT_POINT" echo "Restore complete."