#!/usr/bin/env bash set -euo pipefail BACKUP_DIR="${1:-/mnt2/backed-up-drive/backupstore/volumes/2c/df/pvc-5c1f48e3-346f-4c35-8e6a-8fc0c4c3a842}" OUTPUT_IMG="${2:-./nextcloud-data-restored.img}" BACKUP_CFG="$BACKUP_DIR/backups/backup_backup-eac0221d1cab4a9c.cfg" BLOCKS_DIR="$BACKUP_DIR/blocks" if [[ ! -f "$BACKUP_CFG" ]]; then echo "Error: Backup config not found at $BACKUP_CFG" exit 1 fi echo "========================================" echo " Longhorn Backup Restore Tool" echo "========================================" echo "" echo "Backup: $BACKUP_DIR" echo "Output: $OUTPUT_IMG" echo "" SIZE=$(python3 -c "import json; print(json.load(open('$BACKUP_CFG'))['Size'])") BLOCK_COUNT=$(python3 -c "import json; print(len(json.load(open('$BACKUP_CFG'))['Blocks']))") echo "Volume size: $((SIZE / 1024 / 1024 / 1024)) GB ($SIZE bytes)" echo "Block count: $BLOCK_COUNT" echo "" read -p "Continue? [y/N] " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Aborted." exit 1 fi echo "" echo "Creating sparse image..." truncate -s "$SIZE" "$OUTPUT_IMG" echo "Restoring blocks..." python3 << 'PYEOF' import json import os import sys backup_cfg = os.environ['BACKUP_CFG'] blocks_dir = os.environ['BLOCKS_DIR'] output_img = os.environ['OUTPUT_IMG'] with open(backup_cfg) as f: data = json.load(f) blocks = data['Blocks'] total = len(blocks) with open(output_img, 'r+b') as img: for i, block in enumerate(blocks): offset = block['Offset'] checksum = block['BlockChecksum'] block_path = f"{blocks_dir}/{checksum[:2]}/{checksum[2:4]}/{checksum}.blk" if not os.path.exists(block_path): print(f"Warning: Block not found: {checksum}") continue with open(block_path, 'rb') as bf: img.seek(offset) img.write(bf.read()) if (i + 1) % 1000 == 0 or i + 1 == total: percent = (i + 1) * 100 // total bar = '=' * (percent // 2) + ' ' * (50 - percent // 2) sys.stdout.write(f"\r[{bar}] {percent}% ({i + 1}/{total})") sys.stdout.flush() print() PYEOF echo "" echo "========================================" echo " Restore Complete!" echo "========================================" echo "" echo "Image: $OUTPUT_IMG" echo "Size: $(du -sh "$OUTPUT_IMG" | cut -f1)" echo "" echo "To mount:" echo " sudo losetup -fP $OUTPUT_IMG" echo " sudo mount /dev/loopX /mnt/point" echo "" echo "Or directly:" echo " sudo mount -o loop $OUTPUT_IMG /mnt/point"