#!/bin/bash # Восстановление одного файла vzdump из restic (Yandex S3) через mount. # Не выкачивает весь репозиторий — подгружаются только нужные данные для выбранного файла. # Запускать на хосте Proxmox под root. Требуется FUSE (restic mount). # Секреты из Vaultwarden (объект RESTIC), файл /root/.bw-master (chmod 600). # # Использование: # 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 MOUNT_DIR="${MOUNT_DIR:-/mnt/backup/restic-mount}" 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 # Секреты из Vaultwarden (объект RESTIC) BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}" if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/backup/proxmox-phase1-backup.md" exit 1 fi if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then echo "Установите bw (Bitwarden CLI) и jq для получения секретов из Vaultwarden." exit 1 fi export BW_SESSION BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw 2>/dev/null) || { echo "Не удалось разблокировать Vaultwarden."; exit 1; } RESTIC_ITEM=$(bw get item "RESTIC" 2>/dev/null) || { echo "Не найден объект RESTIC в Vaultwarden."; exit 1; } export RESTIC_REPOSITORY RESTIC_REPOSITORY=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="RESTIC_REPOSITORY") | .value') export AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="AWS_ACCESS_KEY_ID") | .value') export AWS_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="AWS_SECRET_ACCESS_KEY") | .value') export AWS_DEFAULT_REGION AWS_DEFAULT_REGION=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="AWS_DEFAULT_REGION") | .value') [ -z "$AWS_DEFAULT_REGION" ] && AWS_DEFAULT_REGION="ru-central1" RESTIC_PASS=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="RESTIC_BACKUP_KEY") | .value') for var in RESTIC_REPOSITORY AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY; do if [ -z "${!var}" ]; then echo "В Vaultwarden (RESTIC) не задано поле для $var" exit 1 fi done RESTIC_PASSWORD_FILE=$(mktemp -u) echo -n "$RESTIC_PASS" > "$RESTIC_PASSWORD_FILE" chmod 600 "$RESTIC_PASSWORD_FILE" trap 'rm -f "$RESTIC_PASSWORD_FILE"' EXIT INT TERM export RESTIC_PASSWORD_FILE 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 'rm -f "$RESTIC_PASSWORD_FILE"; 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"