95 lines
2.5 KiB
Bash
Executable File
95 lines
2.5 KiB
Bash
Executable File
#!/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"
|