172 lines
5.1 KiB
Bash
Executable file
172 lines
5.1 KiB
Bash
Executable file
#!/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}" # <backup-type>/<backup-id>
|
|
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
|