599 lines
22 KiB
Bash
Executable File
599 lines
22 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Enhanced Simple LVM Backup Script
|
|
# Supports three backup modes:
|
|
# 1. LV → LV: Update existing logical volume backup
|
|
# 2. LV → Raw: Create fresh backup on raw device
|
|
# 3. VG → Raw: Clone entire volume group to raw device
|
|
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
usage() {
|
|
echo "Enhanced Simple LVM Backup Script with Borg Support"
|
|
echo ""
|
|
echo "Usage:"
|
|
echo " $0 lv-to-lv SOURCE_LV TARGET_LV"
|
|
echo " $0 lv-to-raw SOURCE_LV TARGET_DEVICE"
|
|
echo " $0 vg-to-raw SOURCE_VG TARGET_DEVICE"
|
|
echo " $0 lv-to-borg SOURCE_LV REPO_PATH [--new-repo] [--encryption MODE] [--passphrase PASS]"
|
|
echo " $0 vg-to-borg SOURCE_VG REPO_PATH [--new-repo] [--encryption MODE] [--passphrase PASS]"
|
|
echo " $0 files-to-borg SOURCE_LV REPO_PATH [--new-repo] [--encryption MODE] [--passphrase PASS]"
|
|
echo ""
|
|
echo "Modes:"
|
|
echo " lv-to-lv - Update existing LV backup (SOURCE_LV → TARGET_LV)"
|
|
echo " lv-to-raw - Create fresh backup (SOURCE_LV → raw device)"
|
|
echo " vg-to-raw - Clone entire VG (SOURCE_VG → raw device)"
|
|
echo " lv-to-borg - Backup LV to Borg repository (block-level)"
|
|
echo " vg-to-borg - Backup entire VG to Borg repository (block-level)"
|
|
echo " files-to-borg - Backup LV files to Borg repository (file-level, space-efficient)"
|
|
echo ""
|
|
echo "Borg Options:"
|
|
echo " --new-repo Create new repository"
|
|
echo " --encryption MODE Encryption mode: none, repokey, keyfile (default: repokey)"
|
|
echo " --passphrase PASS Repository passphrase"
|
|
echo " --generous-snapshots Use 25% of LV size for snapshots (for very active systems)"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 lv-to-lv /dev/internal-vg/root /dev/backup-vg/root"
|
|
echo " $0 lv-to-raw /dev/internal-vg/root /dev/sdb"
|
|
echo " $0 vg-to-raw internal-vg /dev/sdb"
|
|
echo " $0 lv-to-borg /dev/internal-vg/root /path/to/borg/repo --new-repo"
|
|
echo " $0 files-to-borg /dev/internal-vg/home /path/to/borg/repo --new-repo"
|
|
echo ""
|
|
echo "List available sources/targets:"
|
|
echo " ./list_drives.sh"
|
|
echo ""
|
|
exit 1
|
|
}
|
|
|
|
log() {
|
|
echo -e "${GREEN}[$(date '+%H:%M:%S')] $1${NC}"
|
|
}
|
|
|
|
error() {
|
|
echo -e "${RED}[ERROR] $1${NC}" >&2
|
|
cleanup_and_exit 1
|
|
}
|
|
|
|
warn() {
|
|
echo -e "${YELLOW}[WARNING] $1${NC}"
|
|
}
|
|
|
|
info() {
|
|
echo -e "${BLUE}[INFO] $1${NC}"
|
|
}
|
|
|
|
cleanup_and_exit() {
|
|
local exit_code=${1:-0}
|
|
|
|
if [ -n "$SNAPSHOT_PATH" ] && lvs "$SNAPSHOT_PATH" >/dev/null 2>&1; then
|
|
warn "Cleaning up snapshot: $SNAPSHOT_PATH"
|
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
|
fi
|
|
|
|
exit $exit_code
|
|
}
|
|
|
|
# Trap for cleanup
|
|
trap 'cleanup_and_exit 130' INT TERM
|
|
|
|
# Parse arguments and options
|
|
if [ $# -lt 3 ]; then
|
|
usage
|
|
fi
|
|
|
|
MODE="$1"
|
|
SOURCE="$2"
|
|
TARGET="$3"
|
|
shift 3
|
|
|
|
# Parse Borg-specific options
|
|
NEW_REPO=false
|
|
ENCRYPTION="repokey"
|
|
PASSPHRASE=""
|
|
GENEROUS_SNAPSHOTS=false
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--new-repo)
|
|
NEW_REPO=true
|
|
shift
|
|
;;
|
|
--encryption)
|
|
ENCRYPTION="$2"
|
|
shift 2
|
|
;;
|
|
--passphrase)
|
|
PASSPHRASE="$2"
|
|
shift 2
|
|
;;
|
|
--generous-snapshots)
|
|
GENEROUS_SNAPSHOTS=true
|
|
shift
|
|
;;
|
|
*)
|
|
error "Unknown option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Check if running as root
|
|
if [ "$EUID" -ne 0 ]; then
|
|
error "This script must be run as root"
|
|
fi
|
|
|
|
# Validate mode
|
|
case "$MODE" in
|
|
"lv-to-lv"|"lv-to-raw"|"vg-to-raw"|"lv-to-borg"|"vg-to-borg"|"files-to-borg")
|
|
;;
|
|
*)
|
|
error "Invalid mode: $MODE"
|
|
;;
|
|
esac
|
|
|
|
# Check Borg requirements for Borg modes
|
|
if [[ "$MODE" == *"-to-borg" ]]; then
|
|
if ! command -v borg >/dev/null 2>&1; then
|
|
error "Borg Backup is not installed. Please install it: sudo apt install borgbackup"
|
|
fi
|
|
|
|
# Set up Borg environment
|
|
if [ -n "$PASSPHRASE" ]; then
|
|
export BORG_PASSPHRASE="$PASSPHRASE"
|
|
fi
|
|
fi
|
|
|
|
log "Enhanced Simple LVM Backup"
|
|
log "Mode: $MODE"
|
|
log "Source: $SOURCE"
|
|
log "Target: $TARGET"
|
|
|
|
# Mode-specific validation and execution
|
|
case "$MODE" in
|
|
"lv-to-lv")
|
|
# LV to LV backup
|
|
if [ ! -e "$SOURCE" ]; then
|
|
error "Source LV does not exist: $SOURCE"
|
|
fi
|
|
|
|
if [ ! -e "$TARGET" ]; then
|
|
error "Target LV does not exist: $TARGET"
|
|
fi
|
|
|
|
# Extract VG and LV names
|
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE" | tr -d ' ')
|
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
|
SNAPSHOT_NAME="${LV_NAME}_backup_snap_$(date +%s)"
|
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
|
|
|
info "This will update the existing backup LV"
|
|
echo ""
|
|
echo -e "${YELLOW}Source LV: $SOURCE${NC}"
|
|
echo -e "${YELLOW}Target LV: $TARGET${NC}"
|
|
echo -e "${YELLOW}The target LV will be overwritten with current source data${NC}"
|
|
echo ""
|
|
read -p "Continue? (yes/no): " confirm
|
|
|
|
if [ "$confirm" != "yes" ]; then
|
|
echo "Backup cancelled."
|
|
exit 0
|
|
fi
|
|
|
|
log "Creating snapshot of source LV"
|
|
lvcreate -L1G -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
|
|
|
log "Copying snapshot to target LV"
|
|
if command -v pv >/dev/null 2>&1; then
|
|
pv "$SNAPSHOT_PATH" | dd of="$TARGET" bs=4M || error "Copy failed"
|
|
else
|
|
dd if="$SNAPSHOT_PATH" of="$TARGET" bs=4M status=progress || error "Copy failed"
|
|
fi
|
|
|
|
log "Cleaning up snapshot"
|
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
|
SNAPSHOT_PATH=""
|
|
|
|
log "LV to LV backup completed successfully"
|
|
;;
|
|
|
|
"lv-to-raw")
|
|
# LV to raw device backup
|
|
if [ ! -e "$SOURCE" ]; then
|
|
error "Source LV does not exist: $SOURCE"
|
|
fi
|
|
|
|
if [ ! -e "$TARGET" ]; then
|
|
error "Target device does not exist: $TARGET"
|
|
fi
|
|
|
|
# Extract VG and LV names
|
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE" | tr -d ' ')
|
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
|
SNAPSHOT_NAME="${LV_NAME}_backup_snap_$(date +%s)"
|
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
|
|
|
info "This will create a fresh backup on raw device"
|
|
echo ""
|
|
echo -e "${YELLOW}Source LV: $SOURCE${NC}"
|
|
echo -e "${YELLOW}Target Device: $TARGET${NC}"
|
|
echo -e "${RED}WARNING: All data on $TARGET will be lost!${NC}"
|
|
echo ""
|
|
read -p "Continue? (yes/no): " confirm
|
|
|
|
if [ "$confirm" != "yes" ]; then
|
|
echo "Backup cancelled."
|
|
exit 0
|
|
fi
|
|
|
|
log "Creating snapshot of source LV"
|
|
lvcreate -L1G -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
|
|
|
log "Copying snapshot to target device"
|
|
if command -v pv >/dev/null 2>&1; then
|
|
pv "$SNAPSHOT_PATH" | dd of="$TARGET" bs=4M || error "Copy failed"
|
|
else
|
|
dd if="$SNAPSHOT_PATH" of="$TARGET" bs=4M status=progress || error "Copy failed"
|
|
fi
|
|
|
|
log "Cleaning up snapshot"
|
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
|
SNAPSHOT_PATH=""
|
|
|
|
log "LV to raw device backup completed successfully"
|
|
;;
|
|
|
|
"vg-to-raw")
|
|
# VG to raw device backup
|
|
if ! vgs "$SOURCE" >/dev/null 2>&1; then
|
|
error "Source VG does not exist: $SOURCE"
|
|
fi
|
|
|
|
if [ ! -e "$TARGET" ]; then
|
|
error "Target device does not exist: $TARGET"
|
|
fi
|
|
|
|
# Get the first PV of the source VG
|
|
SOURCE_PV=$(vgs --noheadings -o pv_name "$SOURCE" | head -n1 | tr -d ' ')
|
|
|
|
if [ -z "$SOURCE_PV" ]; then
|
|
error "No physical volumes found for VG: $SOURCE"
|
|
fi
|
|
|
|
info "This will clone the entire volume group"
|
|
echo ""
|
|
echo -e "${YELLOW}Source VG: $SOURCE${NC}"
|
|
echo -e "${YELLOW}Source PV: $SOURCE_PV${NC}"
|
|
echo -e "${YELLOW}Target Device: $TARGET${NC}"
|
|
echo -e "${RED}WARNING: All data on $TARGET will be lost!${NC}"
|
|
echo -e "${BLUE}This preserves LVM metadata and all logical volumes${NC}"
|
|
echo ""
|
|
read -p "Continue? (yes/no): " confirm
|
|
|
|
if [ "$confirm" != "yes" ]; then
|
|
echo "Backup cancelled."
|
|
exit 0
|
|
fi
|
|
|
|
log "Copying entire PV to target device"
|
|
log "This preserves LVM structure and all LVs"
|
|
|
|
if command -v pv >/dev/null 2>&1; then
|
|
pv "$SOURCE_PV" | dd of="$TARGET" bs=4M || error "Copy failed"
|
|
else
|
|
dd if="$SOURCE_PV" of="$TARGET" bs=4M status=progress || error "Copy failed"
|
|
fi
|
|
|
|
log "VG to raw device backup completed successfully"
|
|
log "Target device now contains complete LVM structure"
|
|
;;
|
|
|
|
"lv-to-borg")
|
|
# LV to Borg repository backup
|
|
if [ ! -e "$SOURCE" ]; then
|
|
error "Source LV does not exist: $SOURCE"
|
|
fi
|
|
|
|
# Extract VG and LV names
|
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE" | tr -d ' ')
|
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
|
SNAPSHOT_NAME="${LV_NAME}_borg_snap_$(date +%s)"
|
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
|
|
|
# Get LV size to determine appropriate snapshot size
|
|
LV_SIZE_BYTES=$(lvs --noheadings -o lv_size --units b "$SOURCE" | tr -d ' ' | sed 's/B$//')
|
|
# Use different percentages based on options
|
|
if [ "$GENEROUS_SNAPSHOTS" = true ]; then
|
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES / 4)) # 25%
|
|
MIN_SIZE=$((2 * 1024 * 1024 * 1024)) # 2GB minimum
|
|
else
|
|
# Auto mode: 10% normally, 15% for large LVs
|
|
if [ $LV_SIZE_BYTES -gt $((50 * 1024 * 1024 * 1024)) ]; then
|
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES * 15 / 100)) # 15% for >50GB
|
|
else
|
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES / 10)) # 10% normally
|
|
fi
|
|
MIN_SIZE=$((1024 * 1024 * 1024)) # 1GB minimum
|
|
fi
|
|
|
|
if [ $SNAPSHOT_SIZE_BYTES -lt $MIN_SIZE ]; then
|
|
SNAPSHOT_SIZE_BYTES=$MIN_SIZE
|
|
fi
|
|
SNAPSHOT_SIZE_GB=$((SNAPSHOT_SIZE_BYTES / 1073741824))
|
|
SNAPSHOT_SIZE="${SNAPSHOT_SIZE_GB}G"
|
|
|
|
info "This will backup LV to Borg repository"
|
|
echo ""
|
|
echo -e "${YELLOW}Source LV: $SOURCE${NC}"
|
|
echo -e "${YELLOW}Repository: $TARGET${NC}"
|
|
echo -e "${BLUE}Snapshot size: $SNAPSHOT_SIZE${NC}"
|
|
if [ "$NEW_REPO" = true ]; then
|
|
echo -e "${BLUE}Will create new repository${NC}"
|
|
else
|
|
echo -e "${BLUE}Will add to existing repository${NC}"
|
|
fi
|
|
echo -e "${BLUE}Encryption: $ENCRYPTION${NC}"
|
|
echo ""
|
|
read -p "Continue? (yes/no): " confirm
|
|
|
|
if [ "$confirm" != "yes" ]; then
|
|
echo "Backup cancelled."
|
|
exit 0
|
|
fi
|
|
|
|
# Initialize repository if needed
|
|
if [ "$NEW_REPO" = true ]; then
|
|
log "Creating new Borg repository: $TARGET"
|
|
if [ "$ENCRYPTION" = "none" ]; then
|
|
borg init --encryption=none "$TARGET" || error "Failed to initialize repository"
|
|
else
|
|
borg init --encryption="$ENCRYPTION" "$TARGET" || error "Failed to initialize repository"
|
|
fi
|
|
log "Repository initialized successfully"
|
|
fi
|
|
|
|
log "Creating snapshot of source LV"
|
|
lvcreate -L"$SNAPSHOT_SIZE" -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
|
|
|
# Create Borg archive
|
|
ARCHIVE_NAME="lv_${LV_NAME}_$(date +%Y%m%d_%H%M%S)"
|
|
log "Creating Borg archive (block-level): $ARCHIVE_NAME"
|
|
log "Backing up raw snapshot block device to Borg..."
|
|
log "This preserves exact block-level state including filesystem metadata"
|
|
|
|
dd if="$SNAPSHOT_PATH" bs=4M | borg create --stdin-name "${LV_NAME}.img" --progress --stats "$TARGET::$ARCHIVE_NAME" - || error "Borg backup failed"
|
|
|
|
log "Block-level Borg backup completed successfully"
|
|
|
|
# Cleanup
|
|
log "Cleaning up snapshot"
|
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
|
SNAPSHOT_PATH=""
|
|
|
|
log "LV to Borg backup completed successfully"
|
|
;;
|
|
|
|
"vg-to-borg")
|
|
# VG to Borg repository backup
|
|
if ! vgs "$SOURCE" >/dev/null 2>&1; then
|
|
error "Source VG does not exist: $SOURCE"
|
|
fi
|
|
|
|
info "This will backup entire VG to Borg repository"
|
|
echo ""
|
|
echo -e "${YELLOW}Source VG: $SOURCE${NC}"
|
|
echo -e "${YELLOW}Repository: $TARGET${NC}"
|
|
if [ "$NEW_REPO" = true ]; then
|
|
echo -e "${BLUE}Will create new repository${NC}"
|
|
else
|
|
echo -e "${BLUE}Will add to existing repository${NC}"
|
|
fi
|
|
echo -e "${BLUE}Encryption: $ENCRYPTION${NC}"
|
|
echo -e "${BLUE}This will backup all logical volumes in the VG${NC}"
|
|
echo ""
|
|
read -p "Continue? (yes/no): " confirm
|
|
|
|
if [ "$confirm" != "yes" ]; then
|
|
echo "Backup cancelled."
|
|
exit 0
|
|
fi
|
|
|
|
# Initialize repository if needed
|
|
if [ "$NEW_REPO" = true ]; then
|
|
log "Creating new Borg repository: $TARGET"
|
|
if [ "$ENCRYPTION" = "none" ]; then
|
|
borg init --encryption=none "$TARGET" || error "Failed to initialize repository"
|
|
else
|
|
borg init --encryption="$ENCRYPTION" "$TARGET" || error "Failed to initialize repository"
|
|
fi
|
|
log "Repository initialized successfully"
|
|
fi
|
|
|
|
# Get all LVs in VG
|
|
LV_LIST=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
|
if [ -z "$LV_LIST" ]; then
|
|
error "No logical volumes found in VG: $SOURCE"
|
|
fi
|
|
|
|
log "Found logical volumes: $(echo $LV_LIST | tr '\n' ' ')"
|
|
|
|
# Process each LV one by one to avoid space issues
|
|
for LV_NAME in $LV_LIST; do
|
|
SNAPSHOT_NAME="${LV_NAME}_borg_snap_$(date +%s)"
|
|
SNAPSHOT_PATH="/dev/$SOURCE/$SNAPSHOT_NAME"
|
|
LV_PATH="/dev/$SOURCE/$LV_NAME"
|
|
|
|
# Get LV size to determine appropriate snapshot size
|
|
LV_SIZE_BYTES=$(lvs --noheadings -o lv_size --units b "$LV_PATH" | tr -d ' ' | sed 's/B$//')
|
|
# Use different percentages based on options
|
|
if [ "$GENEROUS_SNAPSHOTS" = true ]; then
|
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES / 4)) # 25%
|
|
MIN_SIZE=$((2 * 1024 * 1024 * 1024)) # 2GB minimum
|
|
else
|
|
# Auto mode: 10% normally, 15% for large LVs
|
|
if [ $LV_SIZE_BYTES -gt $((50 * 1024 * 1024 * 1024)) ]; then
|
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES * 15 / 100)) # 15% for >50GB
|
|
else
|
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES / 10)) # 10% normally
|
|
fi
|
|
MIN_SIZE=$((1024 * 1024 * 1024)) # 1GB minimum
|
|
fi
|
|
|
|
if [ $SNAPSHOT_SIZE_BYTES -lt $MIN_SIZE ]; then
|
|
SNAPSHOT_SIZE_BYTES=$MIN_SIZE
|
|
fi
|
|
SNAPSHOT_SIZE_GB=$((SNAPSHOT_SIZE_BYTES / 1073741824))
|
|
SNAPSHOT_SIZE="${SNAPSHOT_SIZE_GB}G"
|
|
|
|
log "Processing LV: $LV_NAME"
|
|
log "Creating snapshot: $SNAPSHOT_NAME (size: $SNAPSHOT_SIZE)"
|
|
lvcreate -L"$SNAPSHOT_SIZE" -s -n "$SNAPSHOT_NAME" "$LV_PATH" || {
|
|
warn "Failed to create snapshot for $LV_NAME"
|
|
continue
|
|
}
|
|
|
|
# Create individual archive for this LV
|
|
ARCHIVE_NAME="vg_${SOURCE}_lv_${LV_NAME}_$(date +%Y%m%d_%H%M%S)"
|
|
log "Backing up $LV_NAME to archive: $ARCHIVE_NAME"
|
|
log "Streaming raw block device directly to Borg..."
|
|
|
|
dd if="$SNAPSHOT_PATH" bs=4M | borg create --stdin-name "${LV_NAME}.img" --progress --stats "$TARGET::$ARCHIVE_NAME" - || {
|
|
warn "Backup of $LV_NAME failed"
|
|
}
|
|
|
|
# Clean up this snapshot immediately to save space
|
|
log "Removing snapshot $SNAPSHOT_PATH"
|
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot $SNAPSHOT_PATH"
|
|
done
|
|
|
|
log "Block-level VG Borg backup completed successfully"
|
|
log "Created individual archives for each LV in VG $SOURCE"
|
|
|
|
log "VG to Borg backup completed successfully"
|
|
;;
|
|
|
|
"files-to-borg")
|
|
# Files to Borg repository backup
|
|
if [ ! -e "$SOURCE" ]; then
|
|
error "Source LV does not exist: $SOURCE"
|
|
fi
|
|
|
|
# Extract VG and LV names
|
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE" | tr -d ' ')
|
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
|
SNAPSHOT_NAME="${LV_NAME}_files_snap_$(date +%s)"
|
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
|
|
|
# Get LV size to determine appropriate snapshot size (file-level needs much smaller)
|
|
LV_SIZE_BYTES=$(lvs --noheadings -o lv_size --units b "$SOURCE" | tr -d ' ' | sed 's/B$//')
|
|
if [ "$GENEROUS_SNAPSHOTS" = true ]; then
|
|
# File-level generous: 5% with 1G min, 20G max
|
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES * 5 / 100))
|
|
[ $SNAPSHOT_SIZE_BYTES -lt $((1 * 1024 * 1024 * 1024)) ] && SNAPSHOT_SIZE_BYTES=$((1 * 1024 * 1024 * 1024))
|
|
[ $SNAPSHOT_SIZE_BYTES -gt $((20 * 1024 * 1024 * 1024)) ] && SNAPSHOT_SIZE_BYTES=$((20 * 1024 * 1024 * 1024))
|
|
else
|
|
# Auto: 3% with 1G min, 15G max
|
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES * 3 / 100))
|
|
[ $SNAPSHOT_SIZE_BYTES -lt $((1 * 1024 * 1024 * 1024)) ] && SNAPSHOT_SIZE_BYTES=$((1 * 1024 * 1024 * 1024))
|
|
[ $SNAPSHOT_SIZE_BYTES -gt $((15 * 1024 * 1024 * 1024)) ] && SNAPSHOT_SIZE_BYTES=$((15 * 1024 * 1024 * 1024))
|
|
fi
|
|
SNAPSHOT_SIZE_GB=$((SNAPSHOT_SIZE_BYTES / 1073741824))
|
|
SNAPSHOT_SIZE="${SNAPSHOT_SIZE_GB}G"
|
|
|
|
info "This will backup LV files to Borg repository (space-efficient)"
|
|
echo ""
|
|
echo -e "${YELLOW}Source LV: $SOURCE${NC}"
|
|
echo -e "${YELLOW}Repository: $TARGET${NC}"
|
|
echo -e "${BLUE}Snapshot size: $SNAPSHOT_SIZE${NC}"
|
|
echo -e "${BLUE}Mode: File-level backup (skips empty blocks)${NC}"
|
|
if [ "$NEW_REPO" = true ]; then
|
|
echo -e "${BLUE}Will create new repository${NC}"
|
|
else
|
|
echo -e "${BLUE}Will add to existing repository${NC}"
|
|
fi
|
|
echo -e "${BLUE}Encryption: $ENCRYPTION${NC}"
|
|
echo ""
|
|
read -p "Continue? (yes/no): " confirm
|
|
|
|
if [ "$confirm" != "yes" ]; then
|
|
echo "Backup cancelled."
|
|
exit 0
|
|
fi
|
|
|
|
# Initialize repository if needed
|
|
if [ "$NEW_REPO" = true ]; then
|
|
log "Creating new Borg repository: $TARGET"
|
|
if [ "$ENCRYPTION" = "none" ]; then
|
|
borg init --encryption=none "$TARGET" || error "Failed to initialize repository"
|
|
else
|
|
borg init --encryption="$ENCRYPTION" "$TARGET" || error "Failed to initialize repository"
|
|
fi
|
|
log "Repository initialized successfully"
|
|
fi
|
|
|
|
log "Creating snapshot of source LV"
|
|
lvcreate -L"$SNAPSHOT_SIZE" -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
|
|
|
# Create temporary mount point
|
|
TEMP_MOUNT=$(mktemp -d -t borg_files_backup_XXXXXX)
|
|
|
|
# Mount snapshot read-only with safe FS options
|
|
FS_TYPE=$(blkid -o value -s TYPE "$SNAPSHOT_PATH" 2>/dev/null || echo "")
|
|
if [ "$FS_TYPE" = "ext4" ] || [ "$FS_TYPE" = "ext3" ]; then
|
|
MNT_OPTS="ro,noload"
|
|
elif [ "$FS_TYPE" = "xfs" ]; then
|
|
MNT_OPTS="ro,norecovery"
|
|
else
|
|
MNT_OPTS="ro"
|
|
fi
|
|
log "Mounting snapshot to $TEMP_MOUNT (opts: $MNT_OPTS)"
|
|
mount -o "$MNT_OPTS" "$SNAPSHOT_PATH" "$TEMP_MOUNT" || error "Failed to mount snapshot"
|
|
|
|
# Create Borg archive
|
|
ARCHIVE_NAME="files_${LV_NAME}_$(date +%Y%m%d_%H%M%S)"
|
|
log "Creating Borg archive (file-level): $ARCHIVE_NAME"
|
|
log "Backing up files from mounted snapshot..."
|
|
log "This is space-efficient and skips empty blocks"
|
|
|
|
borg create --progress --stats --compression auto,zstd "$TARGET::$ARCHIVE_NAME" "$TEMP_MOUNT" || error "Borg file-level backup failed"
|
|
|
|
log "File-level Borg backup completed successfully"
|
|
|
|
# Cleanup
|
|
log "Cleaning up mount point and snapshot"
|
|
umount "$TEMP_MOUNT" || warn "Failed to unmount"
|
|
rmdir "$TEMP_MOUNT"
|
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
|
SNAPSHOT_PATH=""
|
|
|
|
log "Files to Borg backup completed successfully"
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo -e "${GREEN}SUCCESS: Backup completed!${NC}"
|
|
echo ""
|
|
echo "Next steps:"
|
|
case "$MODE" in
|
|
"lv-to-lv")
|
|
echo "- Your backup LV has been updated"
|
|
echo "- You can mount $TARGET to verify the backup"
|
|
;;
|
|
"lv-to-raw")
|
|
echo "- Your backup device contains a raw copy of the LV"
|
|
echo "- You can mount $TARGET directly or restore from it"
|
|
;;
|
|
"vg-to-raw")
|
|
echo "- Your backup device contains the complete LVM structure"
|
|
echo "- You can boot from $TARGET or import the VG for recovery"
|
|
echo "- To access: vgimport or boot directly from the device"
|
|
;;
|
|
esac
|
|
echo "" |