feat: Add Borg Backup support to simple LVM backup system
- Add LV → Borg and VG → Borg backup modes - GUI: Borg settings panel with repo path, encryption, passphrase - CLI: Enhanced script with Borg options (--new-repo, --encryption, --passphrase) - Automatic repository initialization for new repos - Support for all Borg encryption modes (none, repokey, keyfile) - Mount snapshots temporarily for file-level Borg backups - Comprehensive cleanup of snapshots and mount points - Updated documentation and examples Benefits: - Deduplication and compression - Strong encryption support - Incremental backups capability - Space-efficient storage - Still maintains simple snapshot → backup → cleanup workflow
This commit is contained in:
20
README.md
20
README.md
@@ -1,4 +1,4 @@
|
|||||||
# Enhanced Simple LVM Backup System
|
# Enhanced Simple LVM Backup System with Borg Support
|
||||||
|
|
||||||
A reliable, straightforward backup system for LVM-enabled Linux systems. Born from the need for simple, dependable backups without complex logic that can cause system issues.
|
A reliable, straightforward backup system for LVM-enabled Linux systems. Born from the need for simple, dependable backups without complex logic that can cause system issues.
|
||||||
|
|
||||||
@@ -6,22 +6,24 @@ A reliable, straightforward backup system for LVM-enabled Linux systems. Born fr
|
|||||||
|
|
||||||
**Simple is better.** This system does exactly three things:
|
**Simple is better.** This system does exactly three things:
|
||||||
1. Create LVM snapshot
|
1. Create LVM snapshot
|
||||||
2. Copy data with dd/pv
|
2. Copy data (dd/pv for raw, Borg for repositories)
|
||||||
3. Clean up snapshot
|
3. Clean up snapshot
|
||||||
|
|
||||||
No complex migration logic, no fancy features that can break. Just reliable backups.
|
No complex migration logic, no fancy features that can break. Just reliable backups.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Three Backup Modes
|
### Five Backup Modes
|
||||||
|
|
||||||
- **LV → LV**: Update existing logical volume backups
|
- **LV → LV**: Update existing logical volume backups
|
||||||
- **LV → Raw**: Create fresh backups on raw devices
|
- **LV → Raw**: Create fresh backups on raw devices
|
||||||
- **VG → Raw**: Clone entire volume groups with LVM metadata
|
- **VG → Raw**: Clone entire volume groups with LVM metadata
|
||||||
|
- **LV → Borg**: Backup logical volume to Borg repository (deduplicated, compressed, encrypted)
|
||||||
|
- **VG → Borg**: Backup entire volume group to Borg repository
|
||||||
|
|
||||||
### Two Interfaces
|
### Two Interfaces
|
||||||
|
|
||||||
- **GUI**: `simple_backup_gui.py` - User-friendly interface
|
- **GUI**: `simple_backup_gui.py` - User-friendly interface with Borg configuration
|
||||||
- **CLI**: `enhanced_simple_backup.sh` - Command-line power user interface
|
- **CLI**: `enhanced_simple_backup.sh` - Command-line power user interface
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -53,6 +55,15 @@ sudo ./enhanced_simple_backup.sh vg-to-raw internal-vg /dev/sdb
|
|||||||
sudo python3 simple_backup_gui.py
|
sudo python3 simple_backup_gui.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Borg repository backups:**
|
||||||
|
```bash
|
||||||
|
# Create new Borg repo and backup LV
|
||||||
|
sudo ./enhanced_simple_backup.sh lv-to-borg /dev/internal-vg/root /path/to/borg/repo --new-repo --encryption repokey --passphrase mypass
|
||||||
|
|
||||||
|
# Backup entire VG to existing repo
|
||||||
|
sudo ./enhanced_simple_backup.sh vg-to-borg internal-vg /path/to/borg/repo --passphrase mypass
|
||||||
|
```
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
### Core System
|
### Core System
|
||||||
@@ -71,6 +82,7 @@ sudo python3 simple_backup_gui.py
|
|||||||
- Root access (`sudo`)
|
- Root access (`sudo`)
|
||||||
- Python 3 + tkinter (for GUI)
|
- Python 3 + tkinter (for GUI)
|
||||||
- `pv` command (optional, for progress display)
|
- `pv` command (optional, for progress display)
|
||||||
|
- **Borg Backup** (for Borg modes): `sudo apt install borgbackup`
|
||||||
|
|
||||||
## Safety Features
|
## Safety Features
|
||||||
|
|
||||||
|
|||||||
@@ -16,22 +16,33 @@ BLUE='\033[0;34m'
|
|||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "Enhanced Simple LVM Backup Script"
|
echo "Enhanced Simple LVM Backup Script with Borg Support"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
echo " $0 lv-to-lv SOURCE_LV TARGET_LV"
|
echo " $0 lv-to-lv SOURCE_LV TARGET_LV"
|
||||||
echo " $0 lv-to-raw SOURCE_LV TARGET_DEVICE"
|
echo " $0 lv-to-raw SOURCE_LV TARGET_DEVICE"
|
||||||
echo " $0 vg-to-raw SOURCE_VG 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 ""
|
echo ""
|
||||||
echo "Modes:"
|
echo "Modes:"
|
||||||
echo " lv-to-lv - Update existing LV backup (SOURCE_LV → TARGET_LV)"
|
echo " lv-to-lv - Update existing LV backup (SOURCE_LV → TARGET_LV)"
|
||||||
echo " lv-to-raw - Create fresh backup (SOURCE_LV → raw device)"
|
echo " lv-to-raw - Create fresh backup (SOURCE_LV → raw device)"
|
||||||
echo " vg-to-raw - Clone entire VG (SOURCE_VG → raw device)"
|
echo " vg-to-raw - Clone entire VG (SOURCE_VG → raw device)"
|
||||||
|
echo " lv-to-borg - Backup LV to Borg repository"
|
||||||
|
echo " vg-to-borg - Backup entire VG to Borg repository"
|
||||||
|
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 ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
echo " $0 lv-to-lv /dev/internal-vg/root /dev/backup-vg/root"
|
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 lv-to-raw /dev/internal-vg/root /dev/sdb"
|
||||||
echo " $0 vg-to-raw internal-vg /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 vg-to-borg internal-vg /path/to/borg/repo --encryption repokey --passphrase mypass"
|
||||||
echo ""
|
echo ""
|
||||||
echo "List available sources/targets:"
|
echo "List available sources/targets:"
|
||||||
echo " ./list_drives.sh"
|
echo " ./list_drives.sh"
|
||||||
@@ -70,14 +81,40 @@ cleanup_and_exit() {
|
|||||||
# Trap for cleanup
|
# Trap for cleanup
|
||||||
trap 'cleanup_and_exit 130' INT TERM
|
trap 'cleanup_and_exit 130' INT TERM
|
||||||
|
|
||||||
# Check arguments
|
# Parse arguments and options
|
||||||
if [ $# -ne 3 ]; then
|
if [ $# -lt 3 ]; then
|
||||||
usage
|
usage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MODE="$1"
|
MODE="$1"
|
||||||
SOURCE="$2"
|
SOURCE="$2"
|
||||||
TARGET="$3"
|
TARGET="$3"
|
||||||
|
shift 3
|
||||||
|
|
||||||
|
# Parse Borg-specific options
|
||||||
|
NEW_REPO=false
|
||||||
|
ENCRYPTION="repokey"
|
||||||
|
PASSPHRASE=""
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--new-repo)
|
||||||
|
NEW_REPO=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--encryption)
|
||||||
|
ENCRYPTION="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--passphrase)
|
||||||
|
PASSPHRASE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
# Check if running as root
|
# Check if running as root
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
@@ -86,13 +123,25 @@ fi
|
|||||||
|
|
||||||
# Validate mode
|
# Validate mode
|
||||||
case "$MODE" in
|
case "$MODE" in
|
||||||
"lv-to-lv"|"lv-to-raw"|"vg-to-raw")
|
"lv-to-lv"|"lv-to-raw"|"vg-to-raw"|"lv-to-borg"|"vg-to-borg")
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
error "Invalid mode: $MODE"
|
error "Invalid mode: $MODE"
|
||||||
;;
|
;;
|
||||||
esac
|
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 "Enhanced Simple LVM Backup"
|
||||||
log "Mode: $MODE"
|
log "Mode: $MODE"
|
||||||
log "Source: $SOURCE"
|
log "Source: $SOURCE"
|
||||||
@@ -236,6 +285,174 @@ case "$MODE" in
|
|||||||
log "VG to raw device backup completed successfully"
|
log "VG to raw device backup completed successfully"
|
||||||
log "Target device now contains complete LVM structure"
|
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"
|
||||||
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
||||||
|
|
||||||
|
info "This will backup LV to Borg repository"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Source LV: $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 ""
|
||||||
|
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 -L1G -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
||||||
|
|
||||||
|
# Create temporary mount point
|
||||||
|
TEMP_MOUNT=$(mktemp -d -t borg_backup_XXXXXX)
|
||||||
|
|
||||||
|
# Mount snapshot
|
||||||
|
log "Mounting snapshot to $TEMP_MOUNT"
|
||||||
|
mount "$SNAPSHOT_PATH" "$TEMP_MOUNT" || error "Failed to mount snapshot"
|
||||||
|
|
||||||
|
# Create Borg archive
|
||||||
|
ARCHIVE_NAME="lv_${LV_NAME}_$(date +%Y%m%d_%H%M%S)"
|
||||||
|
log "Creating Borg archive: $ARCHIVE_NAME"
|
||||||
|
log "This may take a long time depending on data size..."
|
||||||
|
|
||||||
|
borg create --progress --stats "$TARGET::$ARCHIVE_NAME" "$TEMP_MOUNT" || error "Borg backup failed"
|
||||||
|
|
||||||
|
log "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 "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' ' ')"
|
||||||
|
|
||||||
|
# Create base temp directory
|
||||||
|
BASE_TEMP_DIR=$(mktemp -d -t borg_vg_backup_XXXXXX)
|
||||||
|
SNAPSHOTS_CREATED=""
|
||||||
|
|
||||||
|
# Create snapshots and mount them
|
||||||
|
for LV_NAME in $LV_LIST; do
|
||||||
|
SNAPSHOT_NAME="${LV_NAME}_borg_snap"
|
||||||
|
SNAPSHOT_PATH="/dev/$SOURCE/$SNAPSHOT_NAME"
|
||||||
|
LV_PATH="/dev/$SOURCE/$LV_NAME"
|
||||||
|
MOUNT_POINT="$BASE_TEMP_DIR/$LV_NAME"
|
||||||
|
|
||||||
|
log "Creating snapshot: $SNAPSHOT_NAME"
|
||||||
|
lvcreate -L500M -s -n "$SNAPSHOT_NAME" "$LV_PATH" || {
|
||||||
|
warn "Failed to create snapshot for $LV_NAME"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
SNAPSHOTS_CREATED="$SNAPSHOTS_CREATED $SNAPSHOT_PATH"
|
||||||
|
|
||||||
|
# Create mount point and mount
|
||||||
|
mkdir -p "$MOUNT_POINT"
|
||||||
|
log "Mounting $SNAPSHOT_PATH to $MOUNT_POINT"
|
||||||
|
mount "$SNAPSHOT_PATH" "$MOUNT_POINT" || {
|
||||||
|
warn "Failed to mount $SNAPSHOT_PATH"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create Borg archive
|
||||||
|
ARCHIVE_NAME="vg_${SOURCE}_$(date +%Y%m%d_%H%M%S)"
|
||||||
|
log "Creating Borg archive: $ARCHIVE_NAME"
|
||||||
|
log "This may take a long time depending on data size..."
|
||||||
|
|
||||||
|
borg create --progress --stats "$TARGET::$ARCHIVE_NAME" "$BASE_TEMP_DIR" || error "Borg backup failed"
|
||||||
|
|
||||||
|
log "Borg backup of entire VG completed successfully"
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
log "Cleaning up mount points and snapshots"
|
||||||
|
for MOUNT_POINT in "$BASE_TEMP_DIR"/*; do
|
||||||
|
if mountpoint -q "$MOUNT_POINT" 2>/dev/null; then
|
||||||
|
umount "$MOUNT_POINT" || warn "Failed to unmount $MOUNT_POINT"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -rf "$BASE_TEMP_DIR"
|
||||||
|
|
||||||
|
for SNAPSHOT_PATH in $SNAPSHOTS_CREATED; do
|
||||||
|
log "Removing snapshot $SNAPSHOT_PATH"
|
||||||
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot $SNAPSHOT_PATH"
|
||||||
|
done
|
||||||
|
|
||||||
|
log "VG to Borg backup completed successfully"
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Enhanced script to list available backup sources and targets
|
# Enhanced script to list available backup sources and targets
|
||||||
# Shows options for all three backup modes
|
# Shows options for all three backup modes
|
||||||
|
|
||||||
echo "=== Enhanced Simple LVM Backup - Available Options ==="
|
echo "=== Enhanced Simple LVM Backup with Borg Support - Available Options ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo "=== SOURCES ==="
|
echo "=== SOURCES ==="
|
||||||
@@ -74,6 +74,12 @@ echo ""
|
|||||||
echo "3. Entire VG to Raw Device (complete clone):"
|
echo "3. Entire VG to Raw Device (complete clone):"
|
||||||
echo " sudo ./enhanced_simple_backup.sh vg-to-raw internal-vg /dev/sdb"
|
echo " sudo ./enhanced_simple_backup.sh vg-to-raw internal-vg /dev/sdb"
|
||||||
echo ""
|
echo ""
|
||||||
|
echo "4. LV to Borg Repository (encrypted backup):"
|
||||||
|
echo " sudo ./enhanced_simple_backup.sh lv-to-borg /dev/internal-vg/root /path/to/borg/repo --new-repo"
|
||||||
|
echo ""
|
||||||
|
echo "5. Entire VG to Borg Repository (all LVs):"
|
||||||
|
echo " sudo ./enhanced_simple_backup.sh vg-to-borg internal-vg /path/to/borg/repo --encryption repokey"
|
||||||
|
echo ""
|
||||||
echo "=== GUI VERSION ==="
|
echo "=== GUI VERSION ==="
|
||||||
echo " sudo python3 simple_backup_gui.py"
|
echo " sudo python3 simple_backup_gui.py"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -82,5 +88,7 @@ echo "- Always run as root (sudo)"
|
|||||||
echo "- lv-to-lv: Updates existing backup LV"
|
echo "- lv-to-lv: Updates existing backup LV"
|
||||||
echo "- lv-to-raw: Creates fresh backup, overwrites target device"
|
echo "- lv-to-raw: Creates fresh backup, overwrites target device"
|
||||||
echo "- vg-to-raw: Clones entire VG including LVM metadata"
|
echo "- vg-to-raw: Clones entire VG including LVM metadata"
|
||||||
|
echo "- lv-to-borg/vg-to-borg: Creates deduplicated, compressed, encrypted backups"
|
||||||
|
echo "- Borg backups require: sudo apt install borgbackup"
|
||||||
echo "- Make sure target devices are unmounted before backup"
|
echo "- Make sure target devices are unmounted before backup"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -45,6 +45,12 @@ class SimpleBackupGUI:
|
|||||||
ttk.Radiobutton(mode_frame, text="Entire VG → Device",
|
ttk.Radiobutton(mode_frame, text="Entire VG → Device",
|
||||||
variable=self.mode_var, value="vg_to_raw",
|
variable=self.mode_var, value="vg_to_raw",
|
||||||
command=self.on_mode_change).pack(side=tk.LEFT, padx=5)
|
command=self.on_mode_change).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Radiobutton(mode_frame, text="LV → Borg Repo",
|
||||||
|
variable=self.mode_var, value="lv_to_borg",
|
||||||
|
command=self.on_mode_change).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Radiobutton(mode_frame, text="VG → Borg Repo",
|
||||||
|
variable=self.mode_var, value="vg_to_borg",
|
||||||
|
command=self.on_mode_change).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
# Source selection
|
# Source selection
|
||||||
ttk.Label(main_frame, text="Source:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
ttk.Label(main_frame, text="Source:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||||
@@ -58,30 +64,63 @@ class SimpleBackupGUI:
|
|||||||
self.target_combo = ttk.Combobox(main_frame, textvariable=self.target_var, width=50)
|
self.target_combo = ttk.Combobox(main_frame, textvariable=self.target_var, width=50)
|
||||||
self.target_combo.grid(row=2, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5)
|
self.target_combo.grid(row=2, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5)
|
||||||
|
|
||||||
|
# Borg-specific settings (hidden initially)
|
||||||
|
self.borg_frame = ttk.LabelFrame(main_frame, text="Borg Backup Settings", padding="5")
|
||||||
|
self.borg_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
|
||||||
|
self.borg_frame.grid_remove() # Hide initially
|
||||||
|
|
||||||
|
# Repo path
|
||||||
|
ttk.Label(self.borg_frame, text="Repository Path:").grid(row=0, column=0, sticky=tk.W, pady=2)
|
||||||
|
self.repo_path_var = tk.StringVar()
|
||||||
|
repo_entry = ttk.Entry(self.borg_frame, textvariable=self.repo_path_var, width=40)
|
||||||
|
repo_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=2)
|
||||||
|
ttk.Button(self.borg_frame, text="Browse", command=self.browse_repo).grid(row=0, column=2, padx=5, pady=2)
|
||||||
|
|
||||||
|
# Create new repo checkbox
|
||||||
|
self.create_new_repo = tk.BooleanVar()
|
||||||
|
ttk.Checkbutton(self.borg_frame, text="Create new repository",
|
||||||
|
variable=self.create_new_repo).grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=2)
|
||||||
|
|
||||||
|
# Encryption settings
|
||||||
|
ttk.Label(self.borg_frame, text="Encryption Mode:").grid(row=2, column=0, sticky=tk.W, pady=2)
|
||||||
|
self.encryption_var = tk.StringVar(value="repokey")
|
||||||
|
encryption_combo = ttk.Combobox(self.borg_frame, textvariable=self.encryption_var,
|
||||||
|
values=["none", "repokey", "keyfile"], state="readonly", width=15)
|
||||||
|
encryption_combo.grid(row=2, column=1, sticky=tk.W, pady=2)
|
||||||
|
|
||||||
|
# Passphrase
|
||||||
|
ttk.Label(self.borg_frame, text="Passphrase:").grid(row=3, column=0, sticky=tk.W, pady=2)
|
||||||
|
self.passphrase_var = tk.StringVar()
|
||||||
|
passphrase_entry = ttk.Entry(self.borg_frame, textvariable=self.passphrase_var, show="*", width=40)
|
||||||
|
passphrase_entry.grid(row=3, column=1, sticky=(tk.W, tk.E), pady=2)
|
||||||
|
|
||||||
|
# Configure borg frame grid
|
||||||
|
self.borg_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
# Refresh button
|
# Refresh button
|
||||||
ttk.Button(main_frame, text="Refresh", command=self.refresh_drives).grid(row=3, column=0, pady=10)
|
ttk.Button(main_frame, text="Refresh", command=self.refresh_drives).grid(row=4, column=0, pady=10)
|
||||||
|
|
||||||
# Backup button
|
# Backup button
|
||||||
self.backup_btn = ttk.Button(main_frame, text="Start Backup",
|
self.backup_btn = ttk.Button(main_frame, text="Start Backup",
|
||||||
command=self.start_backup, style="Accent.TButton")
|
command=self.start_backup, style="Accent.TButton")
|
||||||
self.backup_btn.grid(row=3, column=1, pady=10)
|
self.backup_btn.grid(row=4, column=1, pady=10)
|
||||||
|
|
||||||
# Emergency stop
|
# Emergency stop
|
||||||
self.stop_btn = ttk.Button(main_frame, text="Emergency Stop",
|
self.stop_btn = ttk.Button(main_frame, text="Emergency Stop",
|
||||||
command=self.emergency_stop, state="disabled")
|
command=self.emergency_stop, state="disabled")
|
||||||
self.stop_btn.grid(row=3, column=2, pady=10)
|
self.stop_btn.grid(row=4, column=2, pady=10)
|
||||||
|
|
||||||
# Progress area
|
# Progress area
|
||||||
ttk.Label(main_frame, text="Progress:").grid(row=4, column=0, sticky=tk.W, pady=(20, 5))
|
ttk.Label(main_frame, text="Progress:").grid(row=5, column=0, sticky=tk.W, pady=(20, 5))
|
||||||
|
|
||||||
self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
|
self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
|
||||||
self.progress.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
|
self.progress.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
|
||||||
|
|
||||||
# Log area
|
# Log area
|
||||||
ttk.Label(main_frame, text="Log:").grid(row=6, column=0, sticky=tk.W, pady=(10, 5))
|
ttk.Label(main_frame, text="Log:").grid(row=7, column=0, sticky=tk.W, pady=(10, 5))
|
||||||
|
|
||||||
log_frame = ttk.Frame(main_frame)
|
log_frame = ttk.Frame(main_frame)
|
||||||
log_frame.grid(row=7, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
log_frame.grid(row=8, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||||
|
|
||||||
self.log_text = tk.Text(log_frame, height=15, width=70)
|
self.log_text = tk.Text(log_frame, height=15, width=70)
|
||||||
scrollbar = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
|
scrollbar = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
|
||||||
@@ -94,13 +133,30 @@ class SimpleBackupGUI:
|
|||||||
self.root.columnconfigure(0, weight=1)
|
self.root.columnconfigure(0, weight=1)
|
||||||
self.root.rowconfigure(0, weight=1)
|
self.root.rowconfigure(0, weight=1)
|
||||||
main_frame.columnconfigure(1, weight=1)
|
main_frame.columnconfigure(1, weight=1)
|
||||||
main_frame.rowconfigure(7, weight=1)
|
main_frame.rowconfigure(8, weight=1)
|
||||||
log_frame.columnconfigure(0, weight=1)
|
log_frame.columnconfigure(0, weight=1)
|
||||||
log_frame.rowconfigure(0, weight=1)
|
log_frame.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
def browse_repo(self):
|
||||||
|
"""Browse for Borg repository path"""
|
||||||
|
from tkinter import filedialog
|
||||||
|
path = filedialog.askdirectory(title="Select Borg Repository Directory")
|
||||||
|
if path:
|
||||||
|
self.repo_path_var.set(path)
|
||||||
|
|
||||||
def on_mode_change(self):
|
def on_mode_change(self):
|
||||||
"""Handle backup mode change"""
|
"""Handle backup mode change"""
|
||||||
self.refresh_drives()
|
mode = self.mode_var.get()
|
||||||
|
|
||||||
|
# Show/hide Borg settings
|
||||||
|
if mode in ["lv_to_borg", "vg_to_borg"]:
|
||||||
|
self.borg_frame.grid()
|
||||||
|
# Clear target combo for Borg modes (repo path is used instead)
|
||||||
|
self.target_combo['values'] = []
|
||||||
|
self.target_var.set("")
|
||||||
|
else:
|
||||||
|
self.borg_frame.grid_remove()
|
||||||
|
self.refresh_drives()
|
||||||
|
|
||||||
def log(self, message):
|
def log(self, message):
|
||||||
"""Add message to log"""
|
"""Add message to log"""
|
||||||
@@ -198,6 +254,10 @@ class SimpleBackupGUI:
|
|||||||
else:
|
else:
|
||||||
self.log("No target LVs found")
|
self.log("No target LVs found")
|
||||||
self.target_combo['values'] = []
|
self.target_combo['values'] = []
|
||||||
|
elif mode in ["lv_to_borg", "vg_to_borg"]:
|
||||||
|
# No target combo for Borg modes - repository path is used
|
||||||
|
self.target_combo['values'] = []
|
||||||
|
self.log("Using Borg repository path instead of target device")
|
||||||
else:
|
else:
|
||||||
# Show raw block devices (lv_to_raw and vg_to_raw)
|
# Show raw block devices (lv_to_raw and vg_to_raw)
|
||||||
success, output = self.run_command("lsblk -dno NAME,SIZE,MODEL | grep -E '^sd|^nvme'", show_output=False)
|
success, output = self.run_command("lsblk -dno NAME,SIZE,MODEL | grep -E '^sd|^nvme'", show_output=False)
|
||||||
@@ -220,25 +280,61 @@ class SimpleBackupGUI:
|
|||||||
|
|
||||||
def start_backup(self):
|
def start_backup(self):
|
||||||
"""Start the backup process"""
|
"""Start the backup process"""
|
||||||
if not self.source_var.get() or not self.target_var.get():
|
|
||||||
messagebox.showerror("Error", "Please select both source and target")
|
|
||||||
return
|
|
||||||
|
|
||||||
mode = self.mode_var.get()
|
mode = self.mode_var.get()
|
||||||
source = self.source_var.get().split()[0]
|
|
||||||
target = self.target_var.get().split()[0]
|
|
||||||
|
|
||||||
# Build confirmation message based on mode
|
# Validate inputs based on mode
|
||||||
if mode == "lv_to_lv":
|
if mode in ["lv_to_borg", "vg_to_borg"]:
|
||||||
msg = f"Update existing LV backup:\n\nSource LV: {source}\nTarget LV: {target}\n\n"
|
if not self.source_var.get() or not self.repo_path_var.get():
|
||||||
msg += "This will overwrite the target LV with current source data.\n\nContinue?"
|
messagebox.showerror("Error", "Please select source and repository path")
|
||||||
elif mode == "lv_to_raw":
|
return
|
||||||
msg = f"Create fresh backup:\n\nSource LV: {source}\nTarget Device: {target}\n\n"
|
|
||||||
msg += "WARNING: Target device will be completely overwritten!\n\nContinue?"
|
# Check if borg is installed
|
||||||
elif mode == "vg_to_raw":
|
success, _ = self.run_command("which borg", show_output=False)
|
||||||
msg = f"Clone entire Volume Group:\n\nSource VG: {source}\nTarget Device: {target}\n\n"
|
if not success:
|
||||||
msg += "WARNING: Target device will be completely overwritten!\n"
|
messagebox.showerror("Error", "Borg Backup is not installed. Please install it first:\nsudo apt install borgbackup")
|
||||||
msg += "This will clone ALL logical volumes in the VG.\n\nContinue?"
|
return
|
||||||
|
|
||||||
|
source = self.source_var.get().split()[0]
|
||||||
|
repo_path = self.repo_path_var.get()
|
||||||
|
|
||||||
|
# Build confirmation message for Borg
|
||||||
|
if mode == "lv_to_borg":
|
||||||
|
msg = f"Borg backup of LV:\n\nSource LV: {source}\nRepository: {repo_path}\n\n"
|
||||||
|
if self.create_new_repo.get():
|
||||||
|
msg += "This will create a new Borg repository.\n"
|
||||||
|
else:
|
||||||
|
msg += "This will add to existing Borg repository.\n"
|
||||||
|
msg += f"Encryption: {self.encryption_var.get()}\n\nContinue?"
|
||||||
|
else: # vg_to_borg
|
||||||
|
msg = f"Borg backup of entire VG:\n\nSource VG: {source}\nRepository: {repo_path}\n\n"
|
||||||
|
if self.create_new_repo.get():
|
||||||
|
msg += "This will create a new Borg repository.\n"
|
||||||
|
else:
|
||||||
|
msg += "This will add to existing Borg repository.\n"
|
||||||
|
msg += f"Encryption: {self.encryption_var.get()}\n"
|
||||||
|
msg += "This will backup ALL logical volumes in the VG.\n\nContinue?"
|
||||||
|
|
||||||
|
target = repo_path
|
||||||
|
else:
|
||||||
|
# Original validation for non-Borg modes
|
||||||
|
if not self.source_var.get() or not self.target_var.get():
|
||||||
|
messagebox.showerror("Error", "Please select both source and target")
|
||||||
|
return
|
||||||
|
|
||||||
|
source = self.source_var.get().split()[0]
|
||||||
|
target = self.target_var.get().split()[0]
|
||||||
|
|
||||||
|
# Build confirmation message based on mode
|
||||||
|
if mode == "lv_to_lv":
|
||||||
|
msg = f"Update existing LV backup:\n\nSource LV: {source}\nTarget LV: {target}\n\n"
|
||||||
|
msg += "This will overwrite the target LV with current source data.\n\nContinue?"
|
||||||
|
elif mode == "lv_to_raw":
|
||||||
|
msg = f"Create fresh backup:\n\nSource LV: {source}\nTarget Device: {target}\n\n"
|
||||||
|
msg += "WARNING: Target device will be completely overwritten!\n\nContinue?"
|
||||||
|
elif mode == "vg_to_raw":
|
||||||
|
msg = f"Clone entire Volume Group:\n\nSource VG: {source}\nTarget Device: {target}\n\n"
|
||||||
|
msg += "WARNING: Target device will be completely overwritten!\n"
|
||||||
|
msg += "This will clone ALL logical volumes in the VG.\n\nContinue?"
|
||||||
|
|
||||||
if not messagebox.askyesno("Confirm Backup", msg):
|
if not messagebox.askyesno("Confirm Backup", msg):
|
||||||
return
|
return
|
||||||
@@ -264,6 +360,10 @@ class SimpleBackupGUI:
|
|||||||
self.backup_lv_to_raw(source, target)
|
self.backup_lv_to_raw(source, target)
|
||||||
elif mode == "vg_to_raw":
|
elif mode == "vg_to_raw":
|
||||||
self.backup_vg_to_raw(source, target)
|
self.backup_vg_to_raw(source, target)
|
||||||
|
elif mode == "lv_to_borg":
|
||||||
|
self.backup_lv_to_borg(source, target)
|
||||||
|
elif mode == "vg_to_borg":
|
||||||
|
self.backup_vg_to_borg(source, target)
|
||||||
|
|
||||||
self.log("=== Backup completed successfully! ===")
|
self.log("=== Backup completed successfully! ===")
|
||||||
self.root.after(0, lambda: messagebox.showinfo("Success", "Backup completed successfully!"))
|
self.root.after(0, lambda: messagebox.showinfo("Success", "Backup completed successfully!"))
|
||||||
@@ -389,6 +489,200 @@ class SimpleBackupGUI:
|
|||||||
|
|
||||||
self.log("VG copy completed - target device now contains complete LVM structure")
|
self.log("VG copy completed - target device now contains complete LVM structure")
|
||||||
|
|
||||||
|
def backup_lv_to_borg(self, source_lv, repo_path):
|
||||||
|
"""Backup LV to Borg repository"""
|
||||||
|
self.log("Mode: LV to Borg Repository")
|
||||||
|
|
||||||
|
# Set up environment for Borg
|
||||||
|
borg_env = os.environ.copy()
|
||||||
|
if self.passphrase_var.get():
|
||||||
|
borg_env['BORG_PASSPHRASE'] = self.passphrase_var.get()
|
||||||
|
|
||||||
|
# Initialize repository if needed
|
||||||
|
if self.create_new_repo.get():
|
||||||
|
self.log(f"Creating new Borg repository: {repo_path}")
|
||||||
|
encryption = self.encryption_var.get()
|
||||||
|
if encryption == "none":
|
||||||
|
init_cmd = f"borg init --encryption=none {repo_path}"
|
||||||
|
else:
|
||||||
|
init_cmd = f"borg init --encryption={encryption} {repo_path}"
|
||||||
|
|
||||||
|
result = subprocess.run(init_cmd, shell=True, capture_output=True, text=True, env=borg_env)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"Failed to initialize Borg repository: {result.stderr}")
|
||||||
|
self.log("Repository initialized successfully")
|
||||||
|
|
||||||
|
# Create snapshot
|
||||||
|
vg_name = source_lv.split('/')[2]
|
||||||
|
lv_name = source_lv.split('/')[3]
|
||||||
|
snapshot_name = f"{lv_name}_borg_snap"
|
||||||
|
self.current_snapshot = f"/dev/{vg_name}/{snapshot_name}"
|
||||||
|
|
||||||
|
self.log(f"Creating snapshot: {snapshot_name}")
|
||||||
|
success, output = self.run_command(f"lvcreate -L1G -s -n {snapshot_name} {source_lv}")
|
||||||
|
if not success:
|
||||||
|
raise Exception(f"Failed to create snapshot: {output}")
|
||||||
|
|
||||||
|
# Create a temporary mount point
|
||||||
|
import tempfile
|
||||||
|
temp_mount = tempfile.mkdtemp(prefix="borg_backup_")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Mount the snapshot
|
||||||
|
self.log(f"Mounting snapshot to {temp_mount}")
|
||||||
|
success, output = self.run_command(f"mount {self.current_snapshot} {temp_mount}")
|
||||||
|
if not success:
|
||||||
|
raise Exception(f"Failed to mount snapshot: {output}")
|
||||||
|
|
||||||
|
# Create Borg backup
|
||||||
|
archive_name = f"lv_{lv_name}_{time.strftime('%Y%m%d_%H%M%S')}"
|
||||||
|
self.log(f"Creating Borg archive: {archive_name}")
|
||||||
|
self.log("This may take a long time depending on data size...")
|
||||||
|
|
||||||
|
borg_cmd = f"borg create --progress --stats {repo_path}::{archive_name} {temp_mount}"
|
||||||
|
|
||||||
|
# Run borg with environment
|
||||||
|
process = subprocess.Popen(borg_cmd, shell=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, text=True, env=borg_env)
|
||||||
|
|
||||||
|
# Stream output
|
||||||
|
for line in process.stdout:
|
||||||
|
self.log(line.strip())
|
||||||
|
|
||||||
|
process.wait()
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise Exception("Borg backup failed")
|
||||||
|
|
||||||
|
self.log("Borg backup completed successfully")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cleanup: unmount and remove temp directory
|
||||||
|
self.log("Cleaning up mount point")
|
||||||
|
self.run_command(f"umount {temp_mount}", show_output=False)
|
||||||
|
os.rmdir(temp_mount)
|
||||||
|
|
||||||
|
# Remove snapshot
|
||||||
|
self.log("Cleaning up snapshot")
|
||||||
|
success, output = self.run_command(f"lvremove -f {self.current_snapshot}")
|
||||||
|
if not success:
|
||||||
|
self.log(f"Warning: Failed to remove snapshot: {output}")
|
||||||
|
else:
|
||||||
|
self.log("Snapshot cleaned up")
|
||||||
|
|
||||||
|
self.current_snapshot = None
|
||||||
|
|
||||||
|
def backup_vg_to_borg(self, source_vg, repo_path):
|
||||||
|
"""Backup entire VG to Borg repository"""
|
||||||
|
self.log("Mode: Entire VG to Borg Repository")
|
||||||
|
|
||||||
|
# Set up environment for Borg
|
||||||
|
borg_env = os.environ.copy()
|
||||||
|
if self.passphrase_var.get():
|
||||||
|
borg_env['BORG_PASSPHRASE'] = self.passphrase_var.get()
|
||||||
|
|
||||||
|
# Initialize repository if needed
|
||||||
|
if self.create_new_repo.get():
|
||||||
|
self.log(f"Creating new Borg repository: {repo_path}")
|
||||||
|
encryption = self.encryption_var.get()
|
||||||
|
if encryption == "none":
|
||||||
|
init_cmd = f"borg init --encryption=none {repo_path}"
|
||||||
|
else:
|
||||||
|
init_cmd = f"borg init --encryption={encryption} {repo_path}"
|
||||||
|
|
||||||
|
result = subprocess.run(init_cmd, shell=True, capture_output=True, text=True, env=borg_env)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"Failed to initialize Borg repository: {result.stderr}")
|
||||||
|
self.log("Repository initialized successfully")
|
||||||
|
|
||||||
|
# Get all LVs in the VG
|
||||||
|
success, output = self.run_command(f"lvs --noheadings -o lv_name {source_vg}", show_output=False)
|
||||||
|
if not success:
|
||||||
|
raise Exception(f"Failed to get LVs for VG {source_vg}")
|
||||||
|
|
||||||
|
lv_names = [lv.strip() for lv in output.strip().split('\n') if lv.strip()]
|
||||||
|
if not lv_names:
|
||||||
|
raise Exception(f"No logical volumes found in VG {source_vg}")
|
||||||
|
|
||||||
|
self.log(f"Found {len(lv_names)} logical volumes: {', '.join(lv_names)}")
|
||||||
|
|
||||||
|
snapshots_created = []
|
||||||
|
mount_points = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create snapshots for all LVs
|
||||||
|
for lv_name in lv_names:
|
||||||
|
snapshot_name = f"{lv_name}_borg_snap"
|
||||||
|
snapshot_path = f"/dev/{source_vg}/{snapshot_name}"
|
||||||
|
lv_path = f"/dev/{source_vg}/{lv_name}"
|
||||||
|
|
||||||
|
self.log(f"Creating snapshot: {snapshot_name}")
|
||||||
|
success, output = self.run_command(f"lvcreate -L500M -s -n {snapshot_name} {lv_path}")
|
||||||
|
if not success:
|
||||||
|
raise Exception(f"Failed to create snapshot for {lv_name}: {output}")
|
||||||
|
|
||||||
|
snapshots_created.append(snapshot_path)
|
||||||
|
|
||||||
|
# Mount all snapshots
|
||||||
|
import tempfile
|
||||||
|
base_temp_dir = tempfile.mkdtemp(prefix="borg_vg_backup_")
|
||||||
|
|
||||||
|
for i, snapshot_path in enumerate(snapshots_created):
|
||||||
|
lv_name = lv_names[i]
|
||||||
|
mount_point = os.path.join(base_temp_dir, lv_name)
|
||||||
|
os.makedirs(mount_point)
|
||||||
|
mount_points.append(mount_point)
|
||||||
|
|
||||||
|
self.log(f"Mounting {snapshot_path} to {mount_point}")
|
||||||
|
success, output = self.run_command(f"mount {snapshot_path} {mount_point}")
|
||||||
|
if not success:
|
||||||
|
self.log(f"Warning: Failed to mount {snapshot_path}: {output}")
|
||||||
|
# Continue with other LVs
|
||||||
|
|
||||||
|
# Create Borg backup of the entire VG structure
|
||||||
|
archive_name = f"vg_{source_vg}_{time.strftime('%Y%m%d_%H%M%S')}"
|
||||||
|
self.log(f"Creating Borg archive: {archive_name}")
|
||||||
|
self.log("This may take a long time depending on data size...")
|
||||||
|
|
||||||
|
borg_cmd = f"borg create --progress --stats {repo_path}::{archive_name} {base_temp_dir}"
|
||||||
|
|
||||||
|
# Run borg with environment
|
||||||
|
process = subprocess.Popen(borg_cmd, shell=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, text=True, env=borg_env)
|
||||||
|
|
||||||
|
# Stream output
|
||||||
|
for line in process.stdout:
|
||||||
|
self.log(line.strip())
|
||||||
|
|
||||||
|
process.wait()
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise Exception("Borg backup failed")
|
||||||
|
|
||||||
|
self.log("Borg backup of entire VG completed successfully")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cleanup: unmount all mount points
|
||||||
|
for mount_point in mount_points:
|
||||||
|
if os.path.ismount(mount_point):
|
||||||
|
self.log(f"Unmounting {mount_point}")
|
||||||
|
self.run_command(f"umount {mount_point}", show_output=False)
|
||||||
|
|
||||||
|
# Remove temp directory
|
||||||
|
if 'base_temp_dir' in locals():
|
||||||
|
import shutil
|
||||||
|
try:
|
||||||
|
shutil.rmtree(base_temp_dir)
|
||||||
|
except:
|
||||||
|
self.log(f"Warning: Could not remove temp directory {base_temp_dir}")
|
||||||
|
|
||||||
|
# Remove all snapshots
|
||||||
|
for snapshot_path in snapshots_created:
|
||||||
|
self.log(f"Removing snapshot {snapshot_path}")
|
||||||
|
success, output = self.run_command(f"lvremove -f {snapshot_path}")
|
||||||
|
if not success:
|
||||||
|
self.log(f"Warning: Failed to remove snapshot {snapshot_path}: {output}")
|
||||||
|
|
||||||
|
self.current_snapshot = None
|
||||||
|
|
||||||
def cleanup_on_error(self):
|
def cleanup_on_error(self):
|
||||||
"""Clean up on error"""
|
"""Clean up on error"""
|
||||||
if self.current_snapshot:
|
if self.current_snapshot:
|
||||||
|
|||||||
Reference in New Issue
Block a user