#!/bin/bash # Восстановление одного файла vzdump из restic (Yandex S3) через mount. # Не выкачивает весь репозиторий — подгружаются только нужные данные для выбранного файла. # Запускать на хосте Proxmox под root. Требуется FUSE (restic mount). # # Использование: # restore-one-vzdump-from-restic.sh [SNAPSHOT] [ПУТЬ_В_СНИМКЕ] [КУДА_СОХРАНИТЬ] # # Пример (последний снимок, CT 107, сохранить в /mnt/backup): # ./restore-one-vzdump-from-restic.sh # ./restore-one-vzdump-from-restic.sh latest /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-2026_02_26-02_03_14.tar.zst /mnt/backup # # Список снимков: restic snapshots # Список файлов в снимке: restic ls SNAPSHOT set -e ENV_FILE="${ENV_FILE:-/root/.restic-yandex.env}" MOUNT_DIR="${MOUNT_DIR:-/mnt/backup/restic-mount}" RESTIC_PASSWORD_FILE="${RESTIC_PASSWORD_FILE:-/root/.restic-password}" SNAPSHOT="${1:-latest}" # Путь к файлу внутри снимка (как в restic ls) — бэкапим /mnt/backup, пути вида /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-... FILE_IN_SNAPSHOT="${2:-}" OUTPUT_DIR="${3:-/mnt/backup}" if [ "$(id -u)" -ne 0 ]; then echo "Запускайте под root." exit 1 fi if [ ! -f "$ENV_FILE" ]; then echo "Нет файла $ENV_FILE. Скопируйте restic-yandex-env.example и задайте ключи." exit 1 fi # shellcheck source=/dev/null source "$ENV_FILE" for var in RESTIC_REPOSITORY AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY; do if [ -z "${!var}" ]; then echo "В $ENV_FILE не задано: $var" exit 1 fi done export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY RESTIC_REPOSITORY export RESTIC_PASSWORD_FILE export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-ru-central1}" if ! command -v restic >/dev/null 2>&1; then echo "restic не установлен. Установите: apt install restic." exit 1 fi if [ -z "$FILE_IN_SNAPSHOT" ]; then echo "Использование: $0 [SNAPSHOT] ПУТЬ_В_СНИМКЕ [КУДА_СОХРАНИТЬ]" echo "Пример: $0 latest /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-2026_02_26-02_03_14.tar.zst /mnt/backup" echo "" echo "Доступные снимки:" restic snapshots 2>/dev/null || true exit 1 fi # Resolve "latest" to snapshot ID (restic mount показывает ids//) if [ "$SNAPSHOT" = "latest" ]; then SNAPSHOT_ID=$(restic snapshots 2>/dev/null | grep -E '^[a-f0-9]{8}[[:space:]]' | tail -1 | awk '{print $1}') else SNAPSHOT_ID="$SNAPSHOT" fi [ -z "$SNAPSHOT_ID" ] && echo "Не удалось определить ID снимка." && exit 1 echo "Снимок: $SNAPSHOT_ID" mkdir -p "$OUTPUT_DIR" mkdir -p "$MOUNT_DIR" # Проверяем, не смонтировано ли уже if mountpoint -q "$MOUNT_DIR" 2>/dev/null; then echo "Точка монтирования $MOUNT_DIR уже занята. Размонтируйте: fusermount -u $MOUNT_DIR" exit 1 fi echo "Монтируем репозиторий в $MOUNT_DIR ..." restic mount "$MOUNT_DIR" & MOUNT_PID=$! trap 'kill $MOUNT_PID 2>/dev/null; fusermount -u "$MOUNT_DIR" 2>/dev/null; exit' EXIT INT TERM # В смонтированном репо файлы снимка лежат в ids//<путь> SOURCE_FILE="$MOUNT_DIR/ids/$SNAPSHOT_ID/$FILE_IN_SNAPSHOT" for i in $(seq 1 45); do if [ -e "$SOURCE_FILE" ] 2>/dev/null; then break fi sleep 1 done if [ ! -e "$SOURCE_FILE" ]; then echo "Файл не найден: $SOURCE_FILE (подождали 45 с). Проверьте: restic ls $SNAPSHOT_ID" exit 1 fi BASENAME_FILE="$(basename "$FILE_IN_SNAPSHOT")" OUTPUT_FILE="$OUTPUT_DIR/$BASENAME_FILE" echo "Копируем $SOURCE_FILE -> $OUTPUT_FILE ..." cp "$SOURCE_FILE" "$OUTPUT_FILE" echo "Готово: $OUTPUT_FILE ($(du -sh "$OUTPUT_FILE" | cut -f1))" # Размонтировать kill $MOUNT_PID 2>/dev/null || true wait $MOUNT_PID 2>/dev/null || true fusermount -u "$MOUNT_DIR" 2>/dev/null || true trap - EXIT INT TERM echo "Размонтировано. Для восстановления контейнера:" echo " pct create NEW_VMID $OUTPUT_FILE --restore 1 --storage local-lvm"