#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=backup.env source "$SCRIPT_DIR/backup.env" # ============================================================================== # RESTORE-SPECIFIC CONFIGURATION # ============================================================================== BACKUP_GROUP="host/${HOSTNAME}" # / ARCHIVE_NAME="${HOSTNAME}-root.pxar" # archive created by backup.sh usage() { cat >&2 <<'EOF' Usage: restore.sh [mount] [MOUNTPOINT] [SNAPSHOT] FUSE-mount a snapshot for browsing or restoring single files. restore.sh restore TARGET [PATTERN ...] [--snapshot SNAPSHOT] Bulk-restore files/directories into TARGET. Faster than the FUSE mount for many files. PATTERN is a glob matched against paths inside the archive (e.g. "var/vmail/example.com/*"); repeatable. With no PATTERN the whole archive is extracted. restore.sh shell [--snapshot SNAPSHOT] Interactive shell to browse the backup and selectively restore files. Without SNAPSHOT (or --snapshot), you are prompted to pick one from a list of available snapshots (default: the most recent). EOF exit 1 } # Prints a snapshot reference on stdout. When stdin is a TTY and there is more # than one snapshot, shows a numbered menu (newest first) and prompts. select_snapshot() { local -a snaps mapfile -t snaps < <( proxmox-backup-client snapshot list "$BACKUP_GROUP" \ --ns "$BACKUP_NS" --output-format json \ | jq -r 'sort_by(."backup-time") | reverse | .[] | "\(."backup-type")/\(."backup-id")/\(."backup-time" | todateiso8601)"' ) if [ "${#snaps[@]}" -eq 0 ]; then echo "error: no snapshots found in group $BACKUP_GROUP" >&2 exit 1 fi # Non-interactive (piped/cron): use the latest without prompting. if [ ! -t 0 ]; then printf '%s\n' "${snaps[0]}" return fi echo "Available snapshots:" >&2 local i for i in "${!snaps[@]}"; do printf ' %d) %s\n' "$((i + 1))" "${snaps[$i]}" >&2 done local choice="" read -r -p "Select a snapshot [1-${#snaps[@]}] (default 1): " choice choice="${choice:-1}" case "$choice" in *[!0-9]*) echo "error: invalid selection: $choice" >&2; exit 1 ;; esac local idx=$((choice - 1)) if [ "$idx" -lt 0 ] || [ "$idx" -ge "${#snaps[@]}" ]; then echo "error: selection out of range: $choice" >&2 exit 1 fi printf '%s\n' "${snaps[$idx]}" } CMD="mount" case "${1:-}" in mount|restore|shell) CMD="$1"; shift ;; esac case "$CMD" in mount) MOUNTPOINT="${1:-/mnt/pbs-restore}" SNAPSHOT="${2:-}" [ -z "$SNAPSHOT" ] && SNAPSHOT="$(select_snapshot)" if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then echo "error: already mounted at $MOUNTPOINT" >&2 exit 1 fi mkdir -p "$MOUNTPOINT" echo "Snapshot: $SNAPSHOT" echo "Archive: $ARCHIVE_NAME" echo "Mountpoint: $MOUNTPOINT" # The client daemonizes by default, so its exit code only tells us # whether it started; verify the FUSE mount actually appears. proxmox-backup-client mount "$SNAPSHOT" "$ARCHIVE_NAME" "$MOUNTPOINT" \ --ns "$BACKUP_NS" \ --keyfile "$ENCRYPTION_KEY_FILE" for _ in $(seq 1 100); do mountpoint -q "$MOUNTPOINT" 2>/dev/null && break sleep 0.1 done mountpoint -q "$MOUNTPOINT" 2>/dev/null \ || { echo "error: mount did not appear" >&2; exit 1; } echo echo "Mounted at $MOUNTPOINT. Copy out the files you need, then unmount:" echo " umount $MOUNTPOINT" ;; restore) TARGET="${1:-}" [ -n "$TARGET" ] || usage shift PATTERNS=() SNAPSHOT="" while [ $# -gt 0 ]; do case "$1" in --snapshot) SNAPSHOT="${2:-}"; [ -n "$SNAPSHOT" ] || usage; shift 2 ;; *) PATTERNS+=("$1"); shift ;; esac done [ -z "$SNAPSHOT" ] && SNAPSHOT="$(select_snapshot)" mkdir -p "$TARGET" echo "Snapshot: $SNAPSHOT" echo "Archive: $ARCHIVE_NAME" echo "Target: $TARGET" [ "${#PATTERNS[@]}" -gt 0 ] && printf 'Patterns: %s\n' "${PATTERNS[*]}" ARGS=(restore "$SNAPSHOT" "$ARCHIVE_NAME" "$TARGET" --ns "$BACKUP_NS" --keyfile "$ENCRYPTION_KEY_FILE") for p in "${PATTERNS[@]}"; do ARGS+=(--pattern "$p") done proxmox-backup-client "${ARGS[@]}" echo echo "Restored into $TARGET" ;; shell) SNAPSHOT="" while [ $# -gt 0 ]; do case "$1" in --snapshot) SNAPSHOT="${2:-}"; [ -n "$SNAPSHOT" ] || usage; shift 2 ;; *) usage ;; esac done [ -z "$SNAPSHOT" ] && SNAPSHOT="$(select_snapshot)" exec proxmox-backup-client catalog shell "$SNAPSHOT" "$ARCHIVE_NAME" \ --ns "$BACKUP_NS" \ --keyfile "$ENCRYPTION_KEY_FILE" ;; *) usage ;; esac