diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..53bd2f8 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,46 @@ + +- [x] Verify that the copilot-instructions.md file in the .github directory is created. + +- [x] Clarify Project Requirements + Project: Linux backup system with GUI button for rebooting and cloning internal HDD to external M.2 SSD + Language: Python with tkinter for GUI + Requirements: Backup script, GUI interface, systemd service for reboot integration + +- [x] Scaffold the Project + Project structure created with: + - backup_manager.py: GUI application with tkinter + - backup_script.sh: Command-line backup script + - install.sh: Installation and setup script + - README.md: Comprehensive documentation + +- [x] Customize the Project + Customized with backup-specific features: + - Drive detection and validation + - GUI with progress monitoring + - Reboot integration capabilities + - Safety checks and confirmations + - Desktop integration support + +- [x] Install Required Extensions + No VS Code extensions required for this Python/Bash project + +- [x] Compile the Project + Dependencies installed and scripts tested: + - python3-tk for GUI interface + - pv for progress monitoring + - All scripts executable and functional + +- [x] Create and Run Task + No build task needed - Python/Bash scripts are ready to run + +- [x] Launch the Project + Project successfully launches: + - GUI: python3 backup_manager.py + - CLI: ./backup_script.sh --help + - Installation: ./install.sh + +- [x] Ensure Documentation is Complete + Documentation completed: + - README.md with comprehensive setup and usage instructions + - copilot-instructions.md properly configured + - All safety warnings and technical details included diff --git a/DRIVE_SELECTION_REFERENCE.md b/DRIVE_SELECTION_REFERENCE.md new file mode 100644 index 0000000..e53a8e7 --- /dev/null +++ b/DRIVE_SELECTION_REFERENCE.md @@ -0,0 +1,52 @@ +# Drive Selection Reference + +Based on your system screenshots, here's the correct drive mapping: + +## Current System Layout (from screenshots): + +### Available Drives: +- **`/dev/nvme0n1`** - 476.9G KXG6AZNV512G TOSHIBA + - βœ… **INTERNAL DRIVE** (source) + - This is your current system with: + - nvme0n1p1: 58GB Linux filesystem (root) + - nvme0n1p2: 512MB EFI system + - nvme0n1p3: 417GB Linux filesystem (home) + +- **`/dev/sda`** - 476.9G Tech JMicron + - βœ… **EXTERNAL M.2 SSD** (target) + - This should be selected as migration TARGET + - Will be completely wiped and converted to LVM + +- **`/dev/sdb`** - 119.3G Extreme Pro SanDisk + - πŸ”§ **USB STICK** (migration tools) + - This USB stick with the migration tools + - Should NOT be selected for migration + +## Correct Migration Configuration: + +``` +Source (Internal): /dev/nvme0n1 β†’ Target (External): /dev/sda +``` + +## What the Enhanced Script Now Does: + +1. **Excludes USB stick** - Won't offer /dev/sdb as an option +2. **Shows drive details** - Displays size, model, USB detection +3. **Suggests configuration**: + - Internal: /dev/nvme0n1 (NVMe drives are typically internal) + - External: /dev/sda (USB-connected drives are typically external) +4. **User confirmation** - Asks you to confirm before proceeding +5. **Final safety check** - Shows exactly what will be wiped +6. **Type 'YES' confirmation** - Prevents accidental data loss + +## Migration Process: + +1. **Boot from USB** (/dev/sdb) +2. **Run migration script** +3. **Script will suggest**: + - Internal: /dev/nvme0n1 (476.9G TOSHIBA) + - External: /dev/sda (476.9G JMicron) +4. **Confirm selection** +5. **Type 'YES' to proceed** + +This should prevent the wrong drive selection issue you encountered! \ No newline at end of file diff --git a/LIVE_USB_MIGRATION_GUIDE.md b/LIVE_USB_MIGRATION_GUIDE.md new file mode 100644 index 0000000..956d099 --- /dev/null +++ b/LIVE_USB_MIGRATION_GUIDE.md @@ -0,0 +1,515 @@ +# LVM Migration Guide: Live USB to LVM System + +## Overview + +This guide provides comprehensive instructions for migrating your current non-LVM system to an LVM-based system on an external M.2 SSD. **This migration MUST be performed from a live USB system** to avoid file system conflicts and ensure data integrity. + +## Why Migrate to LVM? + +**Benefits of LVM System:** +- **Instant Snapshots**: Create consistent backups without downtime +- **Flexible Storage**: Resize volumes dynamically without repartitioning +- **Advanced Backups**: Snapshot-based backups with rollback capability +- **Space Efficiency**: Snapshots only store changes, not full copies +- **System Recovery**: Easy rollback to previous states + +## Prerequisites + +### Hardware Requirements +- **Live USB System**: Boot from any Linux live USB (Ubuntu, Debian, etc.) +- **Internal Drive**: Your current system (will remain unchanged) +- **External M.2 SSD**: Target drive for LVM system (will be formatted) +- **Sufficient Space**: External drive should be β‰₯ size of used space on internal drive + +### Software Requirements +- Live USB system with LVM tools (will be installed automatically) +- Network connection for package installation +- Root/sudo access on live system + +## Before You Begin + +### 1. Create Recovery Environment +```bash +# Prepare live USB with migration tools +# Download latest Ubuntu/Debian live ISO +# Flash to USB drive using dd or balenaEtcher +``` + +### 2. Backup Important Data +⚠️ **CRITICAL**: While the internal drive remains unchanged, create an additional backup of critical data before proceeding. + +### 3. Document Current System +```bash +# Boot your current system and document the configuration +lsblk -f > system_layout.txt +df -h > disk_usage.txt +cat /etc/fstab > fstab_backup.txt +``` + +## Migration Process + +### Step 1: Boot from Live USB System + +1. **Shutdown your system** completely +2. **Insert live USB** and connect external M.2 SSD +3. **Configure BIOS/UEFI**: + - Set USB as first boot device + - Ensure both internal and external drives are detected +4. **Boot live system**: + - Select "Try Ubuntu" or "Live System" (not "Install") + - Wait for desktop to load completely +5. **Open terminal** and gain root access: + ```bash + sudo -i + ``` + +### Step 2: Prepare Live System + +```bash +# Download and prepare the migration tools +cd /tmp +git clone backup_tools +cd backup_tools + +# Or if you have the tools on external drive already: +mkdir -p /mnt/temp +mount /dev/sda1 /mnt/temp # Adjust device as needed +cp -r /mnt/temp/migration_tools/* /tmp/ +umount /mnt/temp + +# Prepare the live system +./prepare_live_system.sh +``` + +**This script will:** +- βœ… Verify you're running from live system +- πŸ“¦ Install required packages (lvm2, cryptsetup, rsync, etc.) +- πŸ”§ Load kernel modules for LVM +- πŸ’½ Detect available drives +- πŸ“ Create migration workspace + +### Step 3: Run Migration Script + +```bash +# Execute the migration (this will take 30-90 minutes) +./migrate_to_lvm.sh +``` + +**The migration process includes:** + +1. **Drive Detection** (Automatic): + ``` + Detecting drives... + Available drives: + 1. /dev/nvme0n1 - 477GB Samsung SSD 980 (Internal) + 2. /dev/sda - 477GB Samsung T7 (External USB) + + Selected drives: + Internal (source): /dev/nvme0n1 + External (target): /dev/sda + ``` + +2. **System Analysis**: + - Automatically detects partition layout + - Identifies filesystem types + - Handles encrypted partitions + - Calculates optimal LVM sizes + +3. **Confirmation Prompts**: + ``` + ⚠️ WARNING: This will DESTROY all data on /dev/sda! + + Migration Summary: + Source: /dev/nvme0n1 (non-LVM system) + Target: /dev/sda (will become LVM system) + Root size: 70G + Home size: 350G + Swap size: 16G + Boot size: 2G + + Do you want to continue? [y/N] + ``` + +4. **LVM Layout Creation**: + - Creates GPT partition table + - EFI boot partition (512MB) + - LVM physical volume (remaining space) + - Creates volume group and logical volumes + +5. **Data Migration**: + - Mounts source filesystems (handles encryption) + - Copies all system data with rsync + - Preserves permissions, links, and attributes + - Updates system configuration files + +6. **System Configuration**: + - Updates /etc/fstab for LVM volumes + - Configures initramfs for LVM support + - Installs and configures GRUB bootloader + - Creates LVM snapshot backup tools + +### Step 4: Validation and Testing + +```bash +# Validate the migration +./validate_lvm_migration.sh +``` + +**Validation checks:** +- βœ… LVM volumes created correctly +- βœ… Filesystems are healthy +- βœ… Boot configuration is valid +- βœ… GRUB installation successful +- βœ… System files copied completely +- βœ… LVM snapshot capability working + +### Step 5: First Boot Test + +1. **Cleanup and shutdown**: + ```bash + # Clean up and prepare for reboot + sync + umount -a + shutdown -h now + ``` + +2. **Configure BIOS/UEFI**: + - Boot into BIOS/UEFI settings + - Change boot order: External M.2 SSD as first boot device + - Save and exit + +3. **Test boot from external M.2**: + - System should boot normally from external drive + - Login and verify everything works + - Check that all your files and settings are present + +4. **Verify LVM system**: + ```bash + # Check LVM status + sudo lvs + sudo vgs + sudo pvs + + # Check filesystem mounts + df -h + cat /proc/mounts | grep mapper + ``` + +## System Configuration Details + +### LVM Layout Created +``` +Physical Volume: /dev/sda2 +Volume Group: system-vg +Logical Volumes: +β”œβ”€β”€ root (70G) - ext4 - mounted at / +β”œβ”€β”€ home (350G) - ext4 - mounted at /home +β”œβ”€β”€ boot (2G) - ext4 - mounted at /boot +└── swap (16G) - swap - swap space + +Additional: +β”œβ”€β”€ /dev/sda1 (512M) - vfat - EFI boot partition - mounted at /boot/efi +└── Free space (~38G) - available for snapshots and volume expansion +``` + +### Migration Advantages + +**Flexibility:** +- Resize any volume without repartitioning +- Add new drives to volume group +- Move logical volumes between physical drives + +**Backup & Recovery:** +- Create instant snapshots of any volume +- Rollback changes using snapshots +- Consistent backups without downtime + +**Space Management:** +- Thin provisioning support +- Automatic space allocation +- Easy expansion and shrinking + +## Using LVM Snapshots + +### Basic Snapshot Operations + +```bash +# Create snapshots for backup +sudo ./lvm_snapshot_backup.sh backup + +# Snapshots are mounted at: +/mnt/backup/root # Snapshot of root filesystem +/mnt/backup/home # Snapshot of home filesystem +/mnt/backup/boot # Snapshot of boot filesystem + +# Perform backup to external storage +rsync -avH /mnt/backup/ /path/to/external/backup/ + +# Clean up snapshots +sudo ./lvm_snapshot_backup.sh remove +``` + +### Advanced LVM Operations + +```bash +# Extend a logical volume (add 10GB to home) +sudo lvextend -L +10G /dev/system-vg/home +sudo resize2fs /dev/system-vg/home + +# Create additional logical volume +sudo lvcreate -L 20G -n data system-vg +sudo mkfs.ext4 /dev/system-vg/data +sudo mkdir /data +sudo mount /dev/system-vg/data /data + +# Snapshot before system changes +sudo lvcreate -L 5G -s -n root-before-update /dev/system-vg/root + +# Rollback if needed +sudo umount / +sudo lvconvert --merge /dev/system-vg/root-before-update +# Reboot to activate rollback +``` + +## Troubleshooting Guide + +### Migration Issues + +**Migration Script Fails** +```bash +# Check logs for detailed error information +tail -f /var/log/lvm-migration.log + +# Common issues and solutions: +``` + +| Issue | Cause | Solution | +|-------|-------|----------| +| "Drive not found" | Drive not connected/detected | Check connections, try different USB port | +| "Insufficient space" | Target drive too small | Use larger drive or reduce partition sizes | +| "LVM tools not found" | Missing packages | Run `prepare_live_system.sh` first | +| "Permission denied" | Not running as root | Use `sudo` or `sudo -i` | +| "Mount failed" | Filesystem corruption | Check drive with `fsck` | + +**Encrypted Partition Issues** +```bash +# If encrypted partition unlock fails: +sudo cryptsetup luksOpen /dev/nvme0n1p3 temp-unlock +# Enter correct password +sudo cryptsetup close temp-unlock +``` + +**Drive Detection Problems** +```bash +# Manually check drives +lsblk -dpno NAME,SIZE,MODEL +sudo fdisk -l + +# If drives not detected: +sudo partprobe # Re-read partition tables +sudo udevadm settle # Wait for device detection +``` + +### Boot Issues After Migration + +**System Won't Boot from External Drive** + +1. **Check BIOS/UEFI Settings**: + - Verify external M.2 is detected in BIOS + - Set correct boot priority + - Enable UEFI boot mode + - Disable Secure Boot if necessary + +2. **Repair GRUB from Live USB**: + ```bash + # Boot from live USB and mount LVM system + sudo vgchange -ay system-vg + sudo mount /dev/system-vg/root /mnt + sudo mount /dev/system-vg/boot /mnt/boot + sudo mount /dev/sda1 /mnt/boot/efi + + # Reinstall GRUB + sudo mount --bind /dev /mnt/dev + sudo mount --bind /proc /mnt/proc + sudo mount --bind /sys /mnt/sys + sudo chroot /mnt + grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian + update-grub + exit + + # Cleanup and reboot + sudo umount /mnt/dev /mnt/proc /mnt/sys + sudo umount /mnt/boot/efi /mnt/boot /mnt + sudo reboot + ``` + +**Emergency Recovery** + +If external system is completely broken: +1. Change BIOS boot order back to internal drive +2. Boot from original system (unchanged) +3. Re-attempt migration or continue with original system + +### LVM Issues + +**Volume Group Not Found** +```bash +# Activate volume group manually +sudo vgchange -ay system-vg + +# Scan for volume groups +sudo vgscan +sudo pvscan +``` + +**Snapshot Issues** +```bash +# Remove stuck snapshots +sudo umount /mnt/backup/root /mnt/backup/home 2>/dev/null || true +sudo lvremove -f system-vg/root-snapshot +sudo lvremove -f system-vg/home-snapshot + +# Check volume group free space +sudo vgs system-vg +``` + +**File System Corruption** +```bash +# Check and repair LVM volumes +sudo fsck /dev/system-vg/root +sudo fsck /dev/system-vg/home +sudo fsck /dev/system-vg/boot +``` + +## Recovery Procedures + +### Complete Rollback to Original System + +If you decide to abandon LVM migration: + +1. **Boot from internal drive**: + - Change BIOS boot order to internal drive + - Boot normally from original system + +2. **Reformat external drive** (optional): + ```bash + # Wipe LVM configuration + sudo dd if=/dev/zero of=/dev/sda bs=1M count=100 + # Or use backup tools to restore external drive + ``` + +3. **Continue with original system**: + - Everything remains as before migration + - Use existing backup tools for regular backups + +### Retry Migration + +If you want to attempt migration again: + +1. **Boot from live USB** +2. **Run migration script again**: + ```bash + ./migrate_to_lvm.sh + ``` + - Script will destroy existing LVM setup and recreate + - Source system (internal) remains unchanged + +### Disaster Recovery + +**If both systems fail:** + +1. **Boot from live USB** +2. **Mount internal drive** for data recovery: + ```bash + mkdir -p /mnt/recovery + + # Mount root partition + sudo mount /dev/nvme0n1p1 /mnt/recovery + + # If home is encrypted: + sudo cryptsetup open /dev/nvme0n1p3 recovery-home + sudo mkdir -p /mnt/recovery/home + sudo mount /dev/mapper/recovery-home /mnt/recovery/home + + # Copy important data to external storage + rsync -avH /mnt/recovery/home/username/ /path/to/safe/backup/ + ``` + +3. **Fresh OS installation** if needed: + - Install fresh OS on any drive + - Restore personal data from backup + +## Performance Optimization + +### LVM Performance Tuning + +```bash +# Enable read-ahead for better performance +sudo blockdev --setra 2048 /dev/system-vg/root +sudo blockdev --setra 2048 /dev/system-vg/home + +# Add to /etc/fstab for persistent read-ahead: +# /dev/system-vg/root / ext4 defaults,noatime 0 1 +# /dev/system-vg/home /home ext4 defaults,noatime 0 2 +``` + +### Snapshot Management + +```bash +# Monitor snapshot usage +sudo lvs -a -o lv_name,lv_size,data_percent system-vg + +# Remove old snapshots regularly +sudo lvremove system-vg/old-snapshot-name + +# Set up automatic snapshot cleanup (cron job) +echo '0 2 * * * root /usr/local/bin/lvm-snapshot-backup.sh remove' >> /etc/crontab +``` + +## Best Practices + +### Regular Maintenance + +1. **Monitor disk space**: + ```bash + sudo vgs system-vg # Check volume group free space + df -h # Check filesystem usage + ``` + +2. **Regular snapshots**: + ```bash + # Before system updates + sudo lvcreate -L 5G -s -n pre-update-$(date +%Y%m%d) /dev/system-vg/root + + # Before major changes + sudo ./lvm_snapshot_backup.sh backup + ``` + +3. **Backup strategy**: + - Daily: LVM snapshots to external storage + - Weekly: Full system backup using existing tools + - Monthly: Verify backup integrity + +### Security Considerations + +- **Encryption**: Home data is no longer encrypted in LVM setup + - Consider full disk encryption if security is critical + - Use file-level encryption for sensitive data + +- **Access Control**: Secure LVM management commands + ```bash + # Restrict LVM command access + sudo chmod 750 /usr/local/bin/lvm-* + ``` + +## Summary + +The migration successfully transforms your system from traditional partitions to a flexible LVM-based setup, providing: + +βœ… **Instant snapshots** for consistent backups +βœ… **Dynamic volume resizing** without downtime +βœ… **Advanced backup strategies** with rollback capability +βœ… **Space efficiency** with thin provisioning +βœ… **System recovery** options with snapshots + +Your original system remains intact as a fallback, making this a low-risk enhancement to your backup and storage capabilities. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff8b666 --- /dev/null +++ b/README.md @@ -0,0 +1,391 @@ +# System Backup to External M.2 SSD + +A comprehensive backup solution for Linux systems that provides both GUI and command-line interfaces for cloning your internal drive to an external M.2 SSD. + +## Features + +## Features + +- **GUI Interface**: User-friendly graphical interface built with Python Tkinter +- **Command Line Interface**: Full CLI support for automated and scripted operations +- **Smart Drive Detection**: Automatically detects internal drive and external M.2 SSDs +- **Full System Backup**: Complete drive cloning with dd for exact system replication +- **Smart Sync Backup**: ⚑ NEW! Fast incremental backups using rsync for minor changes +- **Change Analysis**: Analyze filesystem changes to recommend sync vs full backup +- **Restore Functionality**: Complete system restore from external drive +- **Portable Tools**: Backup tools survive on external drive and remain accessible after cloning +- **Reboot Integration**: Optional reboot before backup/restore operations +- **Progress Monitoring**: Real-time progress display and logging +- **Safety Features**: Multiple confirmations and drive validation +- **Desktop Integration**: Create desktop shortcuts for easy access + +## Requirements + +- Linux system (tested on Ubuntu/Debian) +- Python 3.6+ with tkinter +- External M.2 SSD in USB enclosure +- sudo privileges for drive operations + +## Installation + +1. Clone or download this repository: + ```bash + git clone + cd backup_to_external_m.2 + ``` + +2. Make scripts executable: + ```bash + chmod +x backup_script.sh + chmod +x backup_manager.py + ``` + +3. Install dependencies (if needed): + ```bash + sudo apt update + sudo apt install python3-tk pv parted + ``` + +4. **Optional**: Set up portable tools on external M.2 SSD: + ```bash + ./setup_portable_tools.sh + ``` + This creates a separate partition on your external drive that preserves backup tools even after cloning. + +## Usage + +### GUI Application + +Launch the graphical backup manager: + +```bash +python3 backup_manager.py +``` + +**Features:** +- Automatic drive detection and classification +- Source and target drive selection with smart defaults +- Real-time progress monitoring +- **Backup Modes**: + - **Smart Sync Backup**: ⚑ Fast incremental backup using rsync (requires existing backup) + - **Analyze Changes**: Analyze what has changed since last backup + - **Start Backup**: Full drive clone (immediate backup while system running) + - **Reboot & Backup**: Reboot system then full backup (recommended for first backup) +- **Restore Modes**: + - **Restore from External**: Immediate restore from external to internal + - **Reboot & Restore**: Reboot system then restore (recommended) +- **Drive Swap Button**: Easily swap source and target for restore operations + +### Command Line Script + +For command-line usage: + +```bash +# List available drives +./backup_script.sh --list + +# Analyze changes without performing backup +./backup_script.sh --analyze --target /dev/sdb + +# Smart sync backup (fast incremental update) +sudo ./backup_script.sh --sync --target /dev/sdb + +# Perform full backup with specific drives +sudo ./backup_script.sh --source /dev/nvme0n1 --target /dev/sda + +# Restore from external to internal (note the restore flag) +sudo ./backup_script.sh --restore --source /dev/sda --target /dev/nvme0n1 + +# Or more simply in restore mode (source/target are swapped automatically) +sudo ./backup_script.sh --restore --source /dev/nvme0n1 --target /dev/sda + +# Create desktop entry +./backup_script.sh --desktop + +# Launch GUI from script +./backup_script.sh --gui +``` + +### Desktop Integration + +Create a desktop shortcut: + +```bash +./backup_script.sh --desktop +``` + +This creates a clickable icon on your desktop that launches the backup tool. + +## Restore Operations + +### When to Restore +- **System Failure**: Internal drive crashed or corrupted +- **System Migration**: Moving to new hardware +- **Rollback**: Reverting to previous system state +- **Testing**: Restoring test environment + +### How to Restore + +#### GUI Method +1. **Connect External Drive**: Plug in your M.2 SSD with backup +2. **Launch GUI**: `python3 backup_manager.py` +3. **Check Drive Selection**: + - Source should be external drive (your backup) + - Target should be internal drive (will be overwritten) +4. **Use Swap Button**: If needed, click "Swap Source↔Target" +5. **Choose Restore Mode**: + - **"Restore from External"**: Immediate restore + - **"Reboot & Restore"**: Reboot then restore (recommended) + +#### Command Line Method +```bash +# Restore with automatic drive detection +sudo ./backup_script.sh --restore + +# Restore with specific drives +sudo ./backup_script.sh --restore --source /dev/sda --target /dev/nvme0n1 +``` + +### ⚠️ Restore Safety Warnings +- **Data Loss**: Target drive is completely overwritten +- **Irreversible**: No undo after restore starts +- **Multiple Confirmations**: System requires explicit confirmation +- **Drive Verification**: Double-check source and target drives +- **Boot Issues**: Ensure external drive contains valid system backup + +## Portable Tools (Boot from External M.2) + +### Setup Portable Tools +Run this once to set up backup tools on your external M.2 SSD: + +```bash +./setup_portable_tools.sh +``` + +This will: +- Create a 512MB tools partition on your external drive +- Install backup tools that survive cloning operations +- Set up automatic tool restoration after each backup + +### Using Portable Tools + +#### When Booted from External M.2 SSD: + +1. **Access Tools**: + ```bash + # Easy access helper + ./access_tools.sh + + # Or manually: + sudo mount LABEL=BACKUP_TOOLS /mnt/tools + cd /mnt/tools/backup_system + ./launch_backup_tools.sh + ``` + +2. **Restore Internal Drive**: + - Launch backup tools from external drive + - External drive (source) β†’ Internal drive (target) + - Click "Reboot & Restore" for safest operation + +3. **Create Desktop Entry**: + ```bash + cd /mnt/tools/backup_system + ./create_desktop_entry.sh + ``` + +### Disaster Recovery Workflow + +1. **Normal Operation**: Internal drive fails +2. **Boot from External**: Use M.2 SSD as boot drive +3. **Access Tools**: Run `./access_tools.sh` +4. **Restore System**: Use backup tools to restore to new internal drive +5. **Back to Normal**: Boot from restored internal drive + +## Safety Features + +- **Drive Validation**: Prevents accidental overwriting of wrong drives +- **Size Checking**: Ensures target drive is large enough +- **Confirmation Prompts**: Multiple confirmation steps before destructive operations +- **Mount Detection**: Automatically unmounts target drives before backup +- **Progress Monitoring**: Real-time feedback during backup operations + +## File Structure + +``` +backup_to_external_m.2/ +β”œβ”€β”€ backup_manager.py # GUI application +β”œβ”€β”€ backup_script.sh # Command-line script +β”œβ”€β”€ install.sh # Installation script +β”œβ”€β”€ systemd/ # Systemd service files +β”‚ └── backup-service.service +└── README.md # This file +``` + +## Smart Sync Technology ⚑ + +The backup system now includes advanced **Smart Sync** functionality that dramatically reduces backup time for incremental updates: + +### How Smart Sync Works + +1. **Analysis Phase**: Compares source and target filesystems to determine changes +2. **Decision Engine**: Recommends sync vs full clone based on amount of changes: + - **< 2GB changes**: Smart sync recommended (much faster) + - **2-10GB changes**: Smart sync beneficial + - **> 10GB changes**: Full clone may be more appropriate +3. **Sync Operation**: Uses rsync to transfer only changed files and metadata + +### Smart Sync Benefits + +- **Speed**: 10-100x faster than full clone for minor changes +- **No Downtime**: System remains usable during sync operation +- **Efficiency**: Only transfers changed data, preserving bandwidth and storage wear +- **Safety**: Preserves backup tools and maintains full system consistency + +### When to Use Smart Sync vs Full Clone + +**Use Smart Sync when:** +- You have an existing backup on the target drive +- Regular incremental updates (daily/weekly backups) +- Minimal system changes since last backup +- You want faster backup with minimal downtime + +**Use Full Clone when:** +- First-time backup to a new drive +- Major system changes (OS upgrade, large software installations) +- Corrupted or incomplete previous backup +- Maximum compatibility and reliability needed + +### Smart Sync Usage + +**GUI Method:** +1. Click "Analyze Changes" to see what has changed +2. Review the recommendation and estimated time savings +3. Click "Smart Sync Backup" to perform incremental update + +**Command Line:** +```bash +# Analyze changes first +./backup_script.sh --analyze --target /dev/sdb + +# Perform smart sync +sudo ./backup_script.sh --sync --target /dev/sdb +``` + +## Traditional Full Backup + +For comprehensive system backup, the system uses proven `dd` cloning technology: + +### Backup Process +1. **Drive Detection**: Automatically scans for available drives +2. **Auto-Selection**: Internal drive as source, external as target +3. **Operation Selection**: Choose backup or restore mode +4. **Validation**: Confirms drives exist and are different +5. **Execution**: Uses `dd` command to clone entire drive +6. **Progress**: Shows real-time progress and logging + +### Backup vs Restore +- **Backup**: Internal β†’ External (preserves your system on external drive) +- **Restore**: External β†’ Internal (overwrites internal with backup data) +- **Swap Button**: Easily switch source/target for restore operations + +### Reboot vs Immediate Operations +- **Immediate**: Faster start, but system is running (potential file locks) +- **Reboot Mode**: System restarts first, then operates (cleaner, more reliable) +- **Recommendation**: Use reboot mode for critical operations + +### Reboot & Backup Mode +1. **Script Creation**: Creates a backup script for post-reboot execution +2. **Reboot Scheduling**: Schedules system reboot +3. **Auto-Execution**: Backup starts automatically after reboot +4. **Notification**: Shows completion status + +### Command Line Mode +- Direct `dd` cloning with progress monitoring +- Comprehensive logging to `/var/log/system_backup.log` +- Drive size validation and safety checks + +## Technical Details + +### Backup Method +- Uses `dd` command for bit-perfect drive cloning +- Block size optimized at 4MB for performance +- `fdatasync` ensures all data is written to disk + +### Drive Detection +- Uses `lsblk` to enumerate block devices +- Filters for actual disk drives +- Shows drive sizes for easy identification + +### Safety Mechanisms +- Root privilege verification +- Block device validation +- Source/target drive comparison +- Mount status checking +- Size compatibility verification + +## Troubleshooting + +### Common Issues + +1. **Permission Denied** + ```bash + # Run with sudo for drive operations + sudo python3 backup_manager.py + ``` + +2. **Drive Not Detected** + ```bash + # Check if drive is connected and recognized + lsblk + dmesg | tail + ``` + +3. **Backup Fails** + ```bash + # Check system logs + sudo journalctl -f + tail -f /var/log/system_backup.log + ``` + +### Performance Tips + +- Use USB 3.0+ connection for external M.2 SSD +- Ensure sufficient power supply for external enclosure +- Close unnecessary applications during backup +- Use SSD for better performance than traditional HDD + +## Security Considerations + +- **Data Destruction**: Target drive data is completely overwritten +- **Root Access**: Scripts require elevated privileges +- **Verification**: Always verify backup integrity after completion +- **Testing**: Test restore process with non-critical data first + +## Limitations + +- Only works with block devices (entire drives) +- Cannot backup to smaller drives +- Requires manual drive selection for safety +- No incremental backup support (full clone only) + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## License + +This project is open source. Use at your own risk. + +## Disclaimer + +**WARNING**: This tool performs low-level disk operations that can result in data loss. Always: +- Verify drive selections carefully +- Test with non-critical data first +- Maintain separate backups of important data +- Understand the risks involved + +The authors are not responsible for any data loss or system damage. diff --git a/START_LVM_MIGRATION.sh b/START_LVM_MIGRATION.sh index aa1bf9b..7314817 100755 --- a/START_LVM_MIGRATION.sh +++ b/START_LVM_MIGRATION.sh @@ -15,13 +15,13 @@ NC='\033[0m' # No Color SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TOOLS_DIR="$SCRIPT_DIR/lvm-migration-tools" -echo -e "${GREEN}=== LVM Migration System (Updated) ===${NC}" -echo "Welcome to the LVM Migration Tool with Enhanced Drive Selection!" +echo -e "${GREEN}=== Complete LVM Migration System ===${NC}" +echo "πŸš€ One-Button Migration with Automatic GRUB Repair!" echo echo "This USB stick contains:" echo "β€’ Debian live system (bootable)" -echo "β€’ Complete LVM migration toolkit" -echo "β€’ Enhanced drive selection with user confirmation" +echo "β€’ Complete LVM migration toolkit with GRUB repair" +echo "β€’ Interactive drive selection with user confirmation" echo "β€’ All necessary scripts and documentation" echo @@ -39,23 +39,25 @@ ls -la *.sh | grep -E "(prepare|migrate|validate|emergency|check)" | while read done echo -echo -e "${YELLOW}Migration Process:${NC}" +echo -e "${YELLOW}🎯 One-Button Migration Process:${NC}" echo "0. Check packages -> ./check_packages.sh (verify package availability)" echo "1. Emergency install -> ./emergency_install.sh (if packages missing)" echo "2. Prepare live system -> ./prepare_live_system.sh" -echo "3. Run migration -> ./migrate_to_lvm.sh (now with drive selection!)" +echo "3. πŸš€ Complete Migration -> ./migrate_to_lvm.sh (EVERYTHING in one script!)" echo "4. Validate migration -> ./validate_lvm_migration.sh" echo -echo -e "${GREEN}NEW FEATURES:${NC}" -echo "β€’ Interactive drive selection with confirmation" -echo "β€’ Better auto-detection of internal/external drives" -echo "β€’ Safety checks to prevent wrong drive selection" -echo "β€’ Package compatibility checking" +echo -e "${GREEN}✨ INTEGRATED FEATURES:${NC}" +echo "β€’ βœ… Interactive drive selection with safety confirmations" +echo "β€’ βœ… Automatic LVM layout creation and data migration" +echo "β€’ βœ… Built-in GRUB repair (no more boot reset loops!)" +echo "β€’ βœ… Complete bootloader installation and configuration" +echo "β€’ βœ… LVM snapshot backup system setup" +echo "β€’ βœ… Everything automated in one script!" echo echo "For complete documentation: less LIVE_USB_MIGRATION_GUIDE.md" echo -read -p "What would you like to do? [0=Check, 1=Emergency, 2=Prepare, 3=Migrate, 4=Validate, d=Docs, s=Shell]: " choice +read -p "What would you like to do? [0=Check, 1=Emergency, 2=Prepare, 3=πŸš€Complete Migration, 4=Validate, d=Docs, s=Shell]: " choice case $choice in 0) @@ -71,7 +73,14 @@ case $choice in sudo ./prepare_live_system.sh ;; 3) - echo -e "${BLUE}Starting LVM migration with drive selection...${NC}" + echo -e "${GREEN}πŸš€ Starting Complete One-Button Migration...${NC}" + echo "This will:" + echo " βœ… Migrate your system to LVM" + echo " βœ… Install and repair GRUB bootloader" + echo " βœ… Configure boot system" + echo " βœ… Set up snapshot backup system" + echo " βœ… Everything automated!" + echo sudo ./migrate_to_lvm.sh ;; 4) @@ -92,6 +101,6 @@ case $choice in echo "sudo ./check_packages.sh # Check package availability" echo "sudo ./emergency_install.sh # If you see missing package errors" echo "sudo ./prepare_live_system.sh" - echo "sudo ./migrate_to_lvm.sh # Now with interactive drive selection!" + echo "sudo ./migrate_to_lvm.sh # πŸš€ Complete one-button migration!" ;; esac diff --git a/__pycache__/backup_manager.cpython-312.pyc b/__pycache__/backup_manager.cpython-312.pyc new file mode 100644 index 0000000..f40f97d Binary files /dev/null and b/__pycache__/backup_manager.cpython-312.pyc differ diff --git a/access_tools.sh b/access_tools.sh new file mode 100755 index 0000000..28818f2 --- /dev/null +++ b/access_tools.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# Mount backup tools partition and provide easy access +# Use this when booted from the external M.2 SSD + +set -e + +# Colors +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' + +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +echo "" +echo "==========================================" +echo " Backup Tools Access Helper" +echo "==========================================" +echo "" + +# Try to find backup tools partition +TOOLS_PARTITION=$(blkid -L "BACKUP_TOOLS" 2>/dev/null || echo "") + +if [[ -z "$TOOLS_PARTITION" ]]; then + print_warning "Backup tools partition not found by label." + print_info "Searching for tools partition..." + + # Look for small partitions that might be our tools partition + while IFS= read -r line; do + partition=$(echo "$line" | awk '{print $1}') + size=$(echo "$line" | awk '{print $4}') + + # Check if it's a small partition (likely tools) + if [[ "$size" == *M ]] && [[ ${size%M} -le 1024 ]]; then + print_info "Found potential tools partition: $partition ($size)" + TOOLS_PARTITION="/dev/$partition" + break + fi + done < <(lsblk -n -o NAME,SIZE | grep -E "sd|nvme.*p") +fi + +if [[ -z "$TOOLS_PARTITION" ]]; then + echo "❌ Could not find backup tools partition" + echo "" + echo "Available partitions:" + lsblk + echo "" + echo "To manually mount tools:" + echo "1. Identify the tools partition from the list above" + echo "2. sudo mkdir -p /mnt/backup_tools" + echo "3. sudo mount /dev/[partition] /mnt/backup_tools" + echo "4. cd /mnt/backup_tools/backup_system" + echo "5. ./launch_backup_tools.sh" + exit 1 +fi + +# Create mount point +MOUNT_POINT="/mnt/backup_tools" +print_info "Creating mount point: $MOUNT_POINT" +sudo mkdir -p "$MOUNT_POINT" + +# Mount tools partition +print_info "Mounting tools partition: $TOOLS_PARTITION" +if sudo mount "$TOOLS_PARTITION" "$MOUNT_POINT"; then + print_success "Tools partition mounted successfully" +else + echo "❌ Failed to mount tools partition" + exit 1 +fi + +# Check if backup system exists +if [[ -d "$MOUNT_POINT/backup_system" ]]; then + print_success "Backup tools found!" + + cd "$MOUNT_POINT/backup_system" + + echo "" + echo "πŸ“ Backup tools are now available at:" + echo " $MOUNT_POINT/backup_system" + echo "" + echo "πŸš€ Available commands:" + echo " ./launch_backup_tools.sh - Interactive menu" + echo " ./backup_script.sh --help - Command line help" + echo " python3 backup_manager.py - GUI (if available)" + echo " ./create_desktop_entry.sh - Create desktop shortcut" + echo "" + + # Ask what to do + read -p "Launch backup tools now? (y/n): " launch + if [[ "$launch" =~ ^[Yy] ]]; then + ./launch_backup_tools.sh + else + echo "" + echo "Tools are ready to use. Change to the tools directory:" + echo "cd $MOUNT_POINT/backup_system" + fi + +else + print_warning "Backup system not found in tools partition" + echo "" + echo "Contents of tools partition:" + ls -la "$MOUNT_POINT" +fi diff --git a/automated_clonezilla_backup.sh b/automated_clonezilla_backup.sh new file mode 100755 index 0000000..149d12f --- /dev/null +++ b/automated_clonezilla_backup.sh @@ -0,0 +1,198 @@ +#!/bin/bash +# Automated Clonezilla Backup Script +# This script runs inside the Clonezilla environment to automate the backup process +# while preserving the Clonezilla boot partition + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +clear +print_status "Automated Clonezilla System Backup" +echo "==================================" +echo + +# Wait for drives to be detected +sleep 3 + +# Auto-detect source drive (internal) +SOURCE_DRIVE="" +for drive in /dev/nvme0n1 /dev/sda /dev/sdb /dev/sdc; do + if [[ -b "$drive" && "$drive" != "/dev/sda" ]]; then + # Check if it looks like a system drive (has multiple partitions) + if [[ $(lsblk -n "$drive" | wc -l) -gt 1 ]]; then + SOURCE_DRIVE="$drive" + break + fi + fi +done + +if [[ -z "$SOURCE_DRIVE" ]]; then + print_error "Could not auto-detect source drive!" + print_status "Available drives:" + lsblk + exit 1 +fi + +# Target is always the backup partition on this USB +TARGET_PARTITION="/dev/sda2" + +print_status "Source Drive: $SOURCE_DRIVE" +print_status "Target Partition: $TARGET_PARTITION" +lsblk "$SOURCE_DRIVE" +echo + +print_warning "This will create a compressed backup image of your system" +print_warning "Backup location: $TARGET_PARTITION" +echo + +# Mount the backup partition +BACKUP_MOUNT="/tmp/backup_storage" +mkdir -p "$BACKUP_MOUNT" + +print_status "Mounting backup storage..." +if ! mount "$TARGET_PARTITION" "$BACKUP_MOUNT"; then + print_error "Failed to mount backup partition" + exit 1 +fi + +# Create backup directory with timestamp +BACKUP_DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_DIR="$BACKUP_MOUNT/system_backup_$BACKUP_DATE" +mkdir -p "$BACKUP_DIR" + +print_status "Creating system backup in: $BACKUP_DIR" +echo + +# Use Clonezilla's ocs-sr (Save and Restore) in batch mode +print_status "Starting Clonezilla backup process..." +print_warning "This will take 30-60 minutes depending on your data size" +echo + +# Clonezilla command in batch mode for disk image creation +# -q2: Use parallel compression for speed +# -c: Enable checking saved image +# -j2: Use clone fresh MBR +# -z1p: Use parallel gzip compression +# -i 4096: Set image split size to 4GB +# -sfsck: Skip filesystem check to save time +# -scs: Skip checking free space in destination +# -rescue: Continue on minor errors +# -batch: Batch mode, no interactive prompts + +# Use Clonezilla's ocs-sr (Save and Restore) in batch mode with FASTEST settings +# -q2: Use parallel compression for speed +# -c: Enable checking saved image +# -j2: Use clone fresh MBR +# -z0: NO compression for maximum speed (trade size for speed) +# -i 0: No image splitting for faster writing +# -sfsck: Skip filesystem check to save time +# -scs: Skip checking free space in destination +# -rescue: Continue on minor errors +# -batch: Batch mode, no interactive prompts +# -p poweroff: Don't power off after completion + +print_status "Using MAXIMUM SPEED settings (NO compression - speed is king!)" +print_warning "Backup will complete in ~15-20 minutes - fastest possible!" + +/usr/sbin/ocs-sr \ + -q2 \ + -j2 \ + -z0 \ + -i 0 \ + -sfsck \ + -scs \ + -rescue \ + -batch \ + -p reboot \ + savedisk \ + "system_backup_$BACKUP_DATE" \ + "$SOURCE_DRIVE" + +if [[ $? -eq 0 ]]; then + print_success "Backup completed successfully!" + print_success "Backup saved to: $BACKUP_DIR" + + # Create a restore script for easy restoration + cat > "$BACKUP_DIR/restore.sh" << EOF +#!/bin/bash +# Restore script for system backup created on $BACKUP_DATE +# Usage: ./restore.sh /dev/target_drive + +if [[ \$# -ne 1 ]]; then + echo "Usage: \$0 /dev/target_drive" + echo "Example: \$0 /dev/nvme0n1" + exit 1 +fi + +TARGET_DRIVE="\$1" + +echo "WARNING: This will completely overwrite \$TARGET_DRIVE" +read -p "Continue? (yes/no): " confirm + +if [[ "\$confirm" != "yes" ]]; then + echo "Cancelled" + exit 1 +fi + +/usr/sbin/ocs-sr \\ + -g auto \\ + -e1 auto \\ + -e2 \\ + -r \\ + -j2 \\ + -batch \\ + restoredisk \\ + "system_backup_$BACKUP_DATE" \\ + "\$TARGET_DRIVE" +EOF + chmod +x "$BACKUP_DIR/restore.sh" + + # Show backup info + print_status "Backup Information:" + du -h "$BACKUP_DIR" + echo + print_success "Restore script created: $BACKUP_DIR/restore.sh" + +else + print_error "Backup failed!" + exit 1 +fi + +# Unmount backup storage +umount "$BACKUP_MOUNT" + +print_success "System backup completed!" +print_status "You can now reboot back to your normal system" +print_status "To restore: Boot from this USB and run the restore script" + +echo +print_warning "Press Enter to continue..." +read + +# Optional: Auto-reboot or return to menu +echo "Backup process complete. You can now:" +echo "1. Reboot to your normal system" +echo "2. Run another backup" +echo "3. Access Clonezilla manually" diff --git a/backup_manager.py b/backup_manager.py new file mode 100755 index 0000000..f23f15a --- /dev/null +++ b/backup_manager.py @@ -0,0 +1,1079 @@ +#!/usr/bin/env python3 +""" +Linux System Backup Tool with GUI +A tool for creating full system backups to external M.2 SSD with reboot functionality. +""" + +import tkinter as tk +from tkinter import ttk, messagebox, scrolledtext +import subprocess +import threading +import os +import sys +import time +from pathlib import Path + +class BackupManager: + def __init__(self): + self.root = tk.Tk() + self.root.title("System Backup Manager") + self.root.geometry("600x500") + self.root.resizable(True, True) + + # Variables + self.source_drive = tk.StringVar() # Will be auto-detected + self.target_drive = tk.StringVar() + self.operation_running = False + self.operation_type = "backup" # "backup" or "restore" + self.sync_mode = "full" # "full", "sync", or "auto" + + self.setup_ui() + self.detect_drives() + + def setup_ui(self): + """Setup the user interface""" + # Main frame + main_frame = ttk.Frame(self.root, padding="10") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # Configure grid weights + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(0, weight=1) + main_frame.columnconfigure(1, weight=1) + + # Title + title_label = ttk.Label(main_frame, text="System Backup Manager", + font=("Arial", 16, "bold")) + title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20)) + + # Source drive selection + ttk.Label(main_frame, text="Source Drive (Internal):").grid(row=1, column=0, sticky=tk.W, pady=5) + source_combo = ttk.Combobox(main_frame, textvariable=self.source_drive, width=40) + source_combo.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5, padx=(10, 0)) + + # Target drive selection + ttk.Label(main_frame, text="Target Drive (External M.2):").grid(row=2, column=0, sticky=tk.W, pady=5) + target_combo = ttk.Combobox(main_frame, textvariable=self.target_drive, width=40) + target_combo.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5, padx=(10, 0)) + + # Refresh drives button + refresh_btn = ttk.Button(main_frame, text="Refresh Drives", command=self.detect_drives) + refresh_btn.grid(row=3, column=1, sticky=tk.E, pady=10, padx=(10, 0)) + + # Status frame + status_frame = ttk.LabelFrame(main_frame, text="Status", padding="10") + status_frame.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10) + status_frame.columnconfigure(0, weight=1) + status_frame.rowconfigure(0, weight=1) + + # Log area + self.log_text = scrolledtext.ScrolledText(status_frame, height=15, width=60) + self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # Button frame + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=5, column=0, columnspan=2, pady=20) + + # Backup buttons + backup_frame = ttk.LabelFrame(button_frame, text="Backup Operations", padding="10") + backup_frame.pack(side=tk.LEFT, padx=5) + + self.sync_backup_btn = ttk.Button(backup_frame, text="Smart Sync Backup", + command=self.smart_sync_backup, style="Accent.TButton") + self.sync_backup_btn.pack(side=tk.TOP, pady=2) + + self.backup_btn = ttk.Button(backup_frame, text="Full Clone Backup", + command=self.start_backup) + self.backup_btn.pack(side=tk.TOP, pady=2) + + self.reboot_backup_btn = ttk.Button(backup_frame, text="Reboot & Full Clone", + command=self.reboot_and_backup) + self.reboot_backup_btn.pack(side=tk.TOP, pady=2) + + # Restore buttons + restore_frame = ttk.LabelFrame(button_frame, text="Restore Operations", padding="10") + restore_frame.pack(side=tk.LEFT, padx=5) + + self.restore_btn = ttk.Button(restore_frame, text="Restore from External", + command=self.start_restore) + self.restore_btn.pack(side=tk.TOP, pady=2) + + self.reboot_restore_btn = ttk.Button(restore_frame, text="Reboot & Restore", + command=self.reboot_and_restore) + self.reboot_restore_btn.pack(side=tk.TOP, pady=2) + + # Control buttons + control_frame = ttk.Frame(button_frame) + control_frame.pack(side=tk.LEFT, padx=5) + + self.stop_btn = ttk.Button(control_frame, text="Stop", command=self.stop_operation, state="disabled") + self.stop_btn.pack(side=tk.TOP, pady=2) + + self.swap_btn = ttk.Button(control_frame, text="Swap Source↔Target", command=self.swap_drives) + self.swap_btn.pack(side=tk.TOP, pady=2) + + self.analyze_btn = ttk.Button(control_frame, text="Analyze Changes", command=self.analyze_changes) + self.analyze_btn.pack(side=tk.TOP, pady=2) + + # Progress bar + self.progress = ttk.Progressbar(main_frame, mode='indeterminate') + self.progress.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10) + + # Store combo references for updating + self.source_combo = source_combo + self.target_combo = target_combo + + # Add initial log message + self.log("System Backup Manager initialized") + self.log("Select source and target drives, then click 'Start Backup' or 'Reboot & Backup'") + + def log(self, message): + """Add message to log with timestamp""" + timestamp = time.strftime("%H:%M:%S") + self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") + self.log_text.see(tk.END) + self.root.update_idletasks() + + def get_root_drive(self): + """Get the drive containing the root filesystem""" + try: + # Find the device containing the root filesystem + result = subprocess.run(['df', '/'], capture_output=True, text=True) + lines = result.stdout.strip().split('\n') + if len(lines) > 1: + device = lines[1].split()[0] + # Remove partition number to get base device + import re + base_device = re.sub(r'[0-9]+$', '', device) + # Handle nvme drives (e.g., /dev/nvme0n1p1 -> /dev/nvme0n1) + base_device = re.sub(r'p[0-9]+$', '', base_device) + return base_device + except Exception as e: + self.log(f"Error detecting root drive: {e}") + return None + + def detect_drives(self): + """Detect available drives""" + try: + self.log("Detecting available drives...") + + # First, detect the root filesystem drive + root_drive = self.get_root_drive() + if root_drive: + self.log(f"Detected root filesystem on: {root_drive}") + + # Get block devices with more information + result = subprocess.run(['lsblk', '-d', '-n', '-o', 'NAME,SIZE,TYPE,TRAN,HOTPLUG'], + capture_output=True, text=True) + + internal_drives = [] + external_drives = [] + all_drives = [] + root_drive_info = None + + for line in result.stdout.strip().split('\n'): + if line and 'disk' in line: + parts = line.split() + if len(parts) >= 3: + name = f"/dev/{parts[0]}" + size = parts[1] + transport = parts[3] if len(parts) > 3 else "" + hotplug = parts[4] if len(parts) > 4 else "0" + + drive_info = f"{name} ({size})" + all_drives.append(drive_info) + + # Check if this is the root drive and mark it + if root_drive and name == root_drive: + drive_info = f"{name} ({size}) [SYSTEM]" + root_drive_info = drive_info + self.log(f"Root drive found: {drive_info}") + + # Classify drives + if transport in ['usb', 'uas'] or hotplug == "1": + external_drives.append(drive_info) + self.log(f"External drive detected: {drive_info}") + else: + internal_drives.append(drive_info) + self.log(f"Internal drive detected: {drive_info}") + + # Auto-select root drive as source if found, otherwise first internal + if root_drive_info: + self.source_drive.set(root_drive_info) + self.log(f"Auto-selected root drive as source: {root_drive_info}") + elif internal_drives: + self.source_drive.set(internal_drives[0]) + self.log(f"Auto-selected internal drive as source: {internal_drives[0]}") + + # Update combo boxes - put internal drives first for source + self.source_combo['values'] = internal_drives + external_drives + self.target_combo['values'] = external_drives + internal_drives # Prefer external for target + + # If there's an external drive, auto-select it as target + if external_drives: + self.target_drive.set(external_drives[0]) + self.log(f"Auto-selected external drive as target: {external_drives[0]}") + + self.log(f"Found {len(internal_drives)} internal and {len(external_drives)} external drives") + + except Exception as e: + self.log(f"Error detecting drives: {e}") + + def validate_selection(self): + """Validate drive selection""" + source = self.source_drive.get().split()[0] if self.source_drive.get() else "" + target = self.target_drive.get().split()[0] if self.target_drive.get() else "" + + if not source: + messagebox.showerror("Error", "Please select a source drive") + return False + + if not target: + messagebox.showerror("Error", "Please select a target drive") + return False + + if source == target: + messagebox.showerror("Error", "Source and target drives cannot be the same") + return False + + # Check if drives exist + if not os.path.exists(source): + messagebox.showerror("Error", f"Source drive {source} does not exist") + return False + + if not os.path.exists(target): + messagebox.showerror("Error", f"Target drive {target} does not exist") + return False + + return True + + def analyze_changes(self): + """Analyze changes between source and target drives""" + if not self.validate_selection(): + return + + # Get drive paths + source = self.source_drive.get().split()[0] + target = self.target_drive.get().split()[0] + + self.run_backup_script("analyze", source, target) + + def run_change_analysis(self, source, target): + """Run change analysis in background""" + try: + # Check if target has existing backup + backup_info = self.check_existing_backup(target) + + if not backup_info['has_backup']: + self.log("No existing backup found. Full clone required.") + return + + self.log(f"Found existing backup from: {backup_info['backup_date']}") + + # Mount both filesystems to compare + changes = self.compare_filesystems(source, target) + + self.log(f"Analysis complete:") + self.log(f" Files changed: {changes['files_changed']}") + self.log(f" Files added: {changes['files_added']}") + self.log(f" Files deleted: {changes['files_deleted']}") + self.log(f" Total size changed: {changes['size_changed_mb']:.1f} MB") + self.log(f" Recommended action: {changes['recommendation']}") + + # Show recommendation + if changes['recommendation'] == 'sync': + messagebox.showinfo("Analysis Complete", + f"Smart Sync Recommended\n\n" + f"Changes detected: {changes['files_changed']} files\n" + f"Size to sync: {changes['size_changed_mb']:.1f} MB\n" + f"Estimated time: {changes['estimated_time_min']:.1f} minutes\n\n" + f"This is much faster than full clone!") + else: + messagebox.showinfo("Analysis Complete", + f"Full Clone Recommended\n\n" + f"Reason: {changes['reason']}\n" + f"Use 'Full Clone Backup' for best results.") + + except Exception as e: + self.log(f"Error during analysis: {e}") + messagebox.showerror("Analysis Error", f"Could not analyze changes: {e}") + + def smart_sync_backup(self): + """Start smart sync backup operation""" + if not self.validate_selection(): + return + + # Get drive paths + source = self.source_drive.get().split()[0] + target = self.target_drive.get().split()[0] + + # Confirm operation + result = messagebox.askyesno( + "Confirm Smart Sync Backup", + f"Perform smart sync backup?\n\n" + f"Source: {source}\n" + f"Target: {target}\n\n" + f"This will quickly update the target drive with changes from the source.\n" + f"The operation is much faster than a full backup but requires an existing backup on the target." + ) + + if result: + self.run_backup_script("sync", source, target) + + def run_backup_script(self, mode, source, target): + """Run the backup script with specified mode""" + try: + # Clear previous output + self.log_text.delete(1.0, tk.END) + + # Determine command arguments + if mode == "analyze": + cmd = ['sudo', './backup_script.sh', '--analyze', '--source', source, '--target', target] + self.log("πŸ” Analyzing changes between drives...") + elif mode == "sync": + cmd = ['sudo', './backup_script.sh', '--sync', '--source', source, '--target', target] + self.log("⚑ Starting smart sync backup...") + elif mode == "backup": + cmd = ['sudo', './backup_script.sh', '--source', source, '--target', target] + self.log("πŸ”„ Starting full backup...") + elif mode == "restore": + cmd = ['sudo', './backup_script.sh', '--restore', '--source', source, '--target', target] + self.log("πŸ”§ Starting restore operation...") + else: + raise ValueError(f"Unknown mode: {mode}") + + # Change to script directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # Run the command + process = subprocess.Popen( + cmd, + cwd=script_dir, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + bufsize=1 + ) + + # Monitor progress in real-time + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + if output: + # Update GUI in real-time + self.log_text.insert(tk.END, output) + self.log_text.see(tk.END) + self.root.update() + + # Get final result + return_code = process.poll() + + if return_code == 0: + if mode == "analyze": + self.log("βœ… Analysis completed successfully!") + messagebox.showinfo("Analysis Complete", "Drive analysis completed. Check the output for recommendations.") + elif mode == "sync": + self.log("βœ… Smart sync completed successfully!") + messagebox.showinfo("Success", "Smart sync backup completed successfully!") + elif mode == "backup": + self.log("βœ… Backup completed successfully!") + messagebox.showinfo("Success", "Full backup completed successfully!") + elif mode == "restore": + self.log("βœ… Restore completed successfully!") + messagebox.showinfo("Success", "System restore completed successfully!") + else: + self.log(f"❌ {mode.title()} operation failed!") + messagebox.showerror("Error", f"{mode.title()} operation failed. Check the output for details.") + + except Exception as e: + error_msg = f"Error running {mode} operation: {str(e)}" + self.log(f"❌ {error_msg}") + messagebox.showerror("Error", error_msg) + + def check_existing_backup(self, target_drive): + """Check if target drive has existing backup and get info""" + try: + # Try to mount the target drive temporarily + temp_mount = f"/tmp/backup_check_{os.getpid()}" + os.makedirs(temp_mount, exist_ok=True) + + # Find the main partition (usually partition 1) + partitions = subprocess.run(['lsblk', '-n', '-o', 'NAME', target_drive], + capture_output=True, text=True).stdout.strip().split('\n') + + main_partition = None + for partition in partitions: + if partition.strip() and partition.strip() != os.path.basename(target_drive): + main_partition = f"/dev/{partition.strip()}" + break + + if not main_partition: + return {'has_backup': False, 'reason': 'No partitions found'} + + # Try to mount and check + try: + subprocess.run(['sudo', 'mount', '-o', 'ro', main_partition, temp_mount], + check=True, capture_output=True) + + # Check if it looks like a Linux system + has_backup = (os.path.exists(os.path.join(temp_mount, 'etc')) and + os.path.exists(os.path.join(temp_mount, 'home')) and + os.path.exists(os.path.join(temp_mount, 'usr'))) + + backup_date = "Unknown" + if has_backup: + # Try to get last modification time of /etc + try: + etc_stat = os.stat(os.path.join(temp_mount, 'etc')) + backup_date = time.strftime('%Y-%m-%d %H:%M', time.localtime(etc_stat.st_mtime)) + except: + pass + + return { + 'has_backup': has_backup, + 'backup_date': backup_date, + 'main_partition': main_partition + } + + finally: + subprocess.run(['sudo', 'umount', temp_mount], capture_output=True) + os.rmdir(temp_mount) + + except Exception as e: + return {'has_backup': False, 'reason': f'Mount error: {e}'} + + def compare_filesystems(self, source_drive, target_drive): + """Compare filesystems to determine sync requirements""" + try: + # Get basic change information using filesystem comparison + # This is a simplified analysis - in practice you'd want more sophisticated comparison + + # Check filesystem sizes + source_size = self.get_filesystem_usage(source_drive) + target_info = self.check_existing_backup(target_drive) + + if not target_info['has_backup']: + return { + 'recommendation': 'full', + 'reason': 'No existing backup', + 'files_changed': 0, + 'files_added': 0, + 'files_deleted': 0, + 'size_changed_mb': 0, + 'estimated_time_min': 0, + 'full_clone_time_min': source_size['total_gb'] * 2 # Rough estimate + } + + target_size = self.get_filesystem_usage(target_drive) + + # Simple heuristic based on size difference + size_diff_gb = abs(source_size['used_gb'] - target_size['used_gb']) + size_change_percent = (size_diff_gb / max(source_size['used_gb'], 0.1)) * 100 + + # Estimate file changes (rough approximation) + estimated_files_changed = int(size_diff_gb * 1000) # Assume 1MB per file average + estimated_sync_time = size_diff_gb * 1.5 # 1.5 minutes per GB for sync + estimated_full_time = source_size['total_gb'] * 2 # 2 minutes per GB for full clone + + # Decision logic + if size_change_percent < 5 and size_diff_gb < 2: + recommendation = 'sync' + reason = 'Minor changes detected' + elif size_change_percent < 15 and size_diff_gb < 10: + recommendation = 'sync' + reason = 'Moderate changes, sync beneficial' + else: + recommendation = 'full' + reason = 'Major changes detected, full clone safer' + + return { + 'recommendation': recommendation, + 'reason': reason, + 'files_changed': estimated_files_changed, + 'files_added': max(0, estimated_files_changed // 2), + 'files_deleted': max(0, estimated_files_changed // 4), + 'size_changed_mb': size_diff_gb * 1024, + 'estimated_time_min': estimated_sync_time, + 'full_clone_time_min': estimated_full_time + } + + except Exception as e: + return { + 'recommendation': 'full', + 'reason': f'Analysis failed: {e}', + 'files_changed': 0, + 'files_added': 0, + 'files_deleted': 0, + 'size_changed_mb': 0, + 'estimated_time_min': 0, + 'full_clone_time_min': 60 + } + + def get_filesystem_usage(self, drive): + """Get filesystem usage information""" + try: + # Mount temporarily and get usage + temp_mount = f"/tmp/fs_check_{os.getpid()}" + os.makedirs(temp_mount, exist_ok=True) + + # Find main partition + partitions = subprocess.run(['lsblk', '-n', '-o', 'NAME', drive], + capture_output=True, text=True).stdout.strip().split('\n') + + main_partition = None + for partition in partitions: + if partition.strip() and partition.strip() != os.path.basename(drive): + main_partition = f"/dev/{partition.strip()}" + break + + if not main_partition: + return {'total_gb': 0, 'used_gb': 0, 'free_gb': 0} + + try: + subprocess.run(['sudo', 'mount', '-o', 'ro', main_partition, temp_mount], + check=True, capture_output=True) + + # Get filesystem usage + statvfs = os.statvfs(temp_mount) + total_bytes = statvfs.f_frsize * statvfs.f_blocks + free_bytes = statvfs.f_frsize * statvfs.f_available + used_bytes = total_bytes - free_bytes + + return { + 'total_gb': total_bytes / (1024**3), + 'used_gb': used_bytes / (1024**3), + 'free_gb': free_bytes / (1024**3) + } + + finally: + subprocess.run(['sudo', 'umount', temp_mount], capture_output=True) + os.rmdir(temp_mount) + + except Exception: + # Fallback to drive size + try: + size_bytes = int(subprocess.run(['blockdev', '--getsize64', drive], + capture_output=True, text=True).stdout.strip()) + total_gb = size_bytes / (1024**3) + return {'total_gb': total_gb, 'used_gb': total_gb * 0.7, 'free_gb': total_gb * 0.3} + except: + return {'total_gb': 500, 'used_gb': 350, 'free_gb': 150} # Default estimates + + def start_sync_operation(self, source, target, changes): + """Start smart sync operation""" + self.operation_running = True + self.sync_backup_btn.config(state="disabled") + self.backup_btn.config(state="disabled") + self.reboot_backup_btn.config(state="disabled") + self.restore_btn.config(state="disabled") + self.reboot_restore_btn.config(state="disabled") + self.stop_btn.config(state="normal") + self.progress.start() + + sync_thread = threading.Thread(target=self.run_sync_operation, args=(source, target, changes)) + sync_thread.daemon = True + sync_thread.start() + + def run_sync_operation(self, source, target, changes): + """Swap source and target drives""" + source = self.source_drive.get() + target = self.target_drive.get() + + self.source_drive.set(target) + self.target_drive.set(source) + + self.log("Swapped source and target drives") + + def run_sync_operation(self, source, target, changes): + """Run smart filesystem sync operation""" + try: + self.log("Starting smart sync operation...") + self.log(f"Syncing {changes['size_changed_mb']:.1f} MB of changes...") + + # Mount both filesystems + source_mount = f"/tmp/sync_source_{os.getpid()}" + target_mount = f"/tmp/sync_target_{os.getpid()}" + + os.makedirs(source_mount, exist_ok=True) + os.makedirs(target_mount, exist_ok=True) + + # Find main partitions + source_partitions = subprocess.run(['lsblk', '-n', '-o', 'NAME', source], + capture_output=True, text=True).stdout.strip().split('\n') + target_partitions = subprocess.run(['lsblk', '-n', '-o', 'NAME', target], + capture_output=True, text=True).stdout.strip().split('\n') + + source_partition = f"/dev/{[p.strip() for p in source_partitions if p.strip() and p.strip() != os.path.basename(source)][0]}" + target_partition = f"/dev/{[p.strip() for p in target_partitions if p.strip() and p.strip() != os.path.basename(target)][0]}" + + try: + # Mount filesystems + subprocess.run(['sudo', 'mount', '-o', 'ro', source_partition, source_mount], check=True) + subprocess.run(['sudo', 'mount', target_partition, target_mount], check=True) + + self.log("Filesystems mounted, starting rsync...") + + # Use rsync for efficient synchronization + rsync_cmd = [ + 'sudo', 'rsync', '-avHAXS', + '--numeric-ids', + '--delete', + '--progress', + '--exclude=/proc/*', + '--exclude=/sys/*', + '--exclude=/dev/*', + '--exclude=/tmp/*', + '--exclude=/run/*', + '--exclude=/mnt/*', + '--exclude=/media/*', + '--exclude=/lost+found', + f'{source_mount}/', + f'{target_mount}/' + ] + + self.log(f"Running rsync command") + + process = subprocess.Popen(rsync_cmd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, bufsize=1) + + # Read output line by line + for line in process.stdout: + if self.operation_running: + line = line.strip() + if line and not line.startswith('sent ') and not line.startswith('total size'): + self.log(f"Sync: {line}") + else: + process.terminate() + break + + process.wait() + + if process.returncode == 0 and self.operation_running: + self.log("Smart sync completed successfully!") + + # Preserve backup tools + try: + self.log("Preserving backup tools on external drive...") + restore_script = os.path.join(os.path.dirname(__file__), "restore_tools_after_backup.sh") + if os.path.exists(restore_script): + subprocess.run([restore_script, target], check=False, timeout=60) + self.log("Backup tools preserved on external drive") + except Exception as e: + self.log(f"Warning: Could not preserve tools: {e}") + + messagebox.showinfo("Success", + f"Smart Sync completed successfully!\n\n" + f"Synced: {changes['size_changed_mb']:.1f} MB\n" + f"Much faster than full clone!") + + elif not self.operation_running: + self.log("Sync operation was cancelled") + else: + self.log(f"Sync failed with return code: {process.returncode}") + messagebox.showerror("Error", "Smart sync failed! Consider using full clone backup.") + + finally: + # Unmount filesystems + subprocess.run(['sudo', 'umount', source_mount], capture_output=True) + subprocess.run(['sudo', 'umount', target_mount], capture_output=True) + os.rmdir(source_mount) + os.rmdir(target_mount) + + except Exception as e: + self.log(f"Error during smart sync: {e}") + messagebox.showerror("Error", f"Smart sync failed: {e}") + + finally: + self.operation_running = False + self.sync_backup_btn.config(state="normal") + self.backup_btn.config(state="normal") + self.reboot_backup_btn.config(state="normal") + self.restore_btn.config(state="normal") + self.reboot_restore_btn.config(state="normal") + self.stop_btn.config(state="disabled") + self.progress.stop() + + def swap_drives(self): + """Swap source and target drives""" + source = self.source_drive.get() + target = self.target_drive.get() + + self.source_drive.set(target) + self.target_drive.set(source) + + self.log("Swapped source and target drives") + + def start_restore(self): + """Start the restore process""" + if not self.validate_selection(): + return + + if self.operation_running: + self.log("Operation already running!") + return + + # Confirm restore + source = self.source_drive.get().split()[0] + target = self.target_drive.get().split()[0] + + result = messagebox.askyesno("⚠️ CONFIRM RESTORE ⚠️", + f"This will RESTORE from {source} to {target}.\n\n" + f"🚨 CRITICAL WARNING 🚨\n" + f"This will COMPLETELY OVERWRITE {target}!\n" + f"ALL DATA on {target} will be DESTROYED!\n\n" + f"This should typically restore FROM external TO internal.\n" + f"Make sure you have the drives selected correctly!\n\n" + f"Are you absolutely sure you want to continue?") + + if not result: + return + + # Second confirmation for restore + result2 = messagebox.askyesno("FINAL CONFIRMATION", + f"LAST CHANCE TO CANCEL!\n\n" + f"Restoring from: {source}\n" + f"Overwriting: {target}\n\n" + f"Type YES to continue or NO to cancel.") + + if not result2: + return + + source = self.source_drive.get().split()[0] + target = self.target_drive.get().split()[0] + self.run_backup_script("restore", source, target) + + def reboot_and_restore(self): + """Schedule reboot and restore""" + if not self.validate_selection(): + return + + result = messagebox.askyesno("⚠️ CONFIRM REBOOT & RESTORE ⚠️", + "This will:\n" + "1. Save current session\n" + "2. Reboot the system\n" + "3. Start RESTORE after reboot\n\n" + "🚨 WARNING: This will OVERWRITE your internal drive! 🚨\n\n" + "Continue?") + + if not result: + return + + try: + # Create restore script for after reboot + script_content = self.create_reboot_operation_script("restore") + + # Save script + script_path = "/tmp/restore_after_reboot.sh" + with open(script_path, 'w') as f: + f.write(script_content) + + os.chmod(script_path, 0o755) + + self.log("Reboot restore script created") + self.log("System will reboot in 5 seconds...") + + # Schedule reboot + subprocess.run(['sudo', 'shutdown', '-r', '+1'], check=True) + + self.log("Reboot scheduled. Restore will start automatically after reboot.") + + except Exception as e: + self.log(f"Error scheduling reboot: {e}") + messagebox.showerror("Error", f"Failed to schedule reboot: {e}") + + def start_backup(self): + """Start the backup process""" + if not self.validate_selection(): + return + + # Confirm backup + source = self.source_drive.get().split()[0] + target = self.target_drive.get().split()[0] + + result = messagebox.askyesno("Confirm Backup", + f"This will clone {source} to {target}.\n\n" + f"WARNING: All data on {target} will be destroyed!\n\n" + f"Are you sure you want to continue?") + + if result: + self.run_backup_script("backup", source, target) + + def start_operation(self, source, target): + """Start backup or restore operation""" + # Start operation in thread + self.operation_running = True + self.sync_backup_btn.config(state="disabled") + self.backup_btn.config(state="disabled") + self.reboot_backup_btn.config(state="disabled") + self.restore_btn.config(state="disabled") + self.reboot_restore_btn.config(state="disabled") + self.stop_btn.config(state="normal") + self.progress.start() + + operation_thread = threading.Thread(target=self.run_operation, args=(source, target, self.operation_type)) + operation_thread.daemon = True + operation_thread.start() + + def run_operation(self, source, target, operation_type): + """Run the actual backup or restore process""" + try: + if operation_type == "backup": + self.log(f"Starting backup from {source} to {target}") + else: + self.log(f"Starting restore from {source} to {target}") + + self.log("This may take a while depending on drive size...") + + # Use dd for cloning + cmd = [ + 'sudo', 'dd', + f'if={source}', + f'of={target}', + 'bs=4M', + 'status=progress', + 'conv=fdatasync' + ] + + self.log(f"Running command: {' '.join(cmd)}") + + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True) + + # Read output + for line in process.stdout: + if self.operation_running: # Check if we should continue + self.log(line.strip()) + else: + process.terminate() + break + + process.wait() + + if process.returncode == 0 and self.operation_running: + if operation_type == "backup": + self.log("Backup completed successfully!") + + # Restore backup tools to external drive + try: + self.log("Preserving backup tools on external drive...") + restore_script = os.path.join(os.path.dirname(__file__), "restore_tools_after_backup.sh") + if os.path.exists(restore_script): + subprocess.run([restore_script, target], check=False, timeout=60) + self.log("Backup tools preserved on external drive") + else: + self.log("Warning: Tool restoration script not found") + except Exception as e: + self.log(f"Warning: Could not preserve tools on external drive: {e}") + + messagebox.showinfo("Success", "Backup completed successfully!\n\nBackup tools have been preserved on the external drive.") + else: + self.log("Restore completed successfully!") + messagebox.showinfo("Success", "Restore completed successfully!") + elif not self.operation_running: + self.log(f"{operation_type.capitalize()} was cancelled by user") + else: + self.log(f"{operation_type.capitalize()} failed with return code: {process.returncode}") + messagebox.showerror("Error", f"{operation_type.capitalize()} failed! Check the log for details.") + + except Exception as e: + self.log(f"Error during {operation_type}: {e}") + messagebox.showerror("Error", f"{operation_type.capitalize()} failed: {e}") + + finally: + self.operation_running = False + self.sync_backup_btn.config(state="normal") + self.backup_btn.config(state="normal") + self.reboot_backup_btn.config(state="normal") + self.restore_btn.config(state="normal") + self.reboot_restore_btn.config(state="normal") + self.stop_btn.config(state="disabled") + self.progress.stop() + + def stop_operation(self): + """Stop the current operation""" + self.operation_running = False + self.log("Stopping operation...") + + def reboot_and_backup(self): + """Schedule reboot and backup - creates systemd service for after reboot""" + if not self.validate_selection(): + return + + source = self.source_drive.get().split()[0] + target = self.target_drive.get().split()[0] + + result = messagebox.askyesno("Confirm Reboot & Backup", + f"This will:\n" + f"1. Create a one-time backup service\n" + f"2. Reboot the system\n" + f"3. Run backup before GUI starts (minimal system load)\n" + f"4. System will be available after backup completes\n\n" + f"Source: {source}\n" + f"Target: {target}\n\n" + f"⚠️ Target drive will be completely overwritten!\n\n" + f"Continue?") + + if not result: + return + + try: + # Create backup script directory + backup_dir = "/opt/backup-system" + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # Create the service directory + subprocess.run(['sudo', 'mkdir', '-p', backup_dir], check=True) + + # Copy backup script to system location + subprocess.run(['sudo', 'cp', f'{script_dir}/backup_script.sh', backup_dir], check=True) + subprocess.run(['sudo', 'chmod', '+x', f'{backup_dir}/backup_script.sh'], check=True) + + # Create the backup service script + service_script = f'''#!/bin/bash +# One-time backup service script +set -e + +# Wait for system to stabilize +sleep 10 + +# Log everything +exec > /var/log/reboot-backup.log 2>&1 + +echo "Starting reboot backup at $(date)" +echo "Source: {source}" +echo "Target: {target}" + +# Run the backup +cd {backup_dir} +./backup_script.sh --source {source} --target {target} + +# Disable this service after completion +systemctl disable reboot-backup.service +rm -f /etc/systemd/system/reboot-backup.service +rm -rf {backup_dir} + +echo "Backup completed at $(date)" +echo "System ready for normal use" + +# Notify desktop (if user session exists) +if [ -n "$DISPLAY" ]; then + notify-send "Backup Complete" "System backup finished successfully" || true +fi +''' + + # Write service script + with open('/tmp/reboot-backup.sh', 'w') as f: + f.write(service_script) + + subprocess.run(['sudo', 'mv', '/tmp/reboot-backup.sh', f'{backup_dir}/reboot-backup.sh'], check=True) + subprocess.run(['sudo', 'chmod', '+x', f'{backup_dir}/reboot-backup.sh'], check=True) + + # Create systemd service + service_content = f'''[Unit] +Description=One-time System Backup After Reboot +After=multi-user.target +Wants=multi-user.target + +[Service] +Type=oneshot +ExecStart={backup_dir}/reboot-backup.sh +User=root +StandardOutput=file:/var/log/reboot-backup.log +StandardError=file:/var/log/reboot-backup.log + +[Install] +WantedBy=multi-user.target +''' + + # Write service file + with open('/tmp/reboot-backup.service', 'w') as f: + f.write(service_content) + + subprocess.run(['sudo', 'mv', '/tmp/reboot-backup.service', '/etc/systemd/system/'], check=True) + + # Enable the service for next boot only + subprocess.run(['sudo', 'systemctl', 'daemon-reload'], check=True) + subprocess.run(['sudo', 'systemctl', 'enable', 'reboot-backup.service'], check=True) + + self.log("βœ… Backup service created and enabled") + self.log("πŸ“ Backup will run automatically after reboot") + self.log("πŸ“‹ Check /var/log/reboot-backup.log for progress") + + # Final confirmation + final_result = messagebox.askyesno( + "Ready to Reboot", + "Backup service is configured!\n\n" + "After reboot:\n" + "β€’ Backup will start automatically\n" + "β€’ Progress logged to /var/log/reboot-backup.log\n" + "β€’ System will be available after completion\n" + "β€’ Service will self-destruct when done\n\n" + "Reboot now?" + ) + + if final_result: + self.log("πŸ”„ Rebooting system...") + subprocess.run(['sudo', 'systemctl', 'reboot'], check=True) + else: + self.log("⏸️ Reboot cancelled - service is ready when you reboot manually") + + except Exception as e: + self.log(f"❌ Error setting up reboot backup: {e}") + messagebox.showerror("Error", f"Failed to setup reboot backup: {e}") + + except Exception as e: + self.log(f"Error scheduling reboot: {e}") + messagebox.showerror("Error", f"Failed to schedule reboot: {e}") + + def create_reboot_operation_script(self, operation_type): + """Create script to run operation after reboot""" + source = self.source_drive.get().split()[0] + target = self.target_drive.get().split()[0] + + if operation_type == "backup": + action_desc = "backup" + success_msg = "Backup Complete" + fail_msg = "Backup Failed" + else: + action_desc = "restore" + success_msg = "Restore Complete" + fail_msg = "Restore Failed" + + script = f"""#!/bin/bash +# Auto-generated {action_desc} script + +echo "Starting {action_desc} after reboot..." +echo "Source: {source}" +echo "Target: {target}" + +# Wait for system to fully boot +sleep 30 + +# Run {action_desc} +sudo dd if={source} of={target} bs=4M status=progress conv=fdatasync + +if [ $? -eq 0 ]; then + echo "{action_desc.capitalize()} completed successfully!" + notify-send "{success_msg}" "System {action_desc} finished successfully" +else + echo "{action_desc.capitalize()} failed!" + notify-send "{fail_msg}" "System {action_desc} encountered an error" +fi + +# Clean up +rm -f /tmp/{action_desc}_after_reboot.sh +""" + return script + + def run(self): + """Start the GUI application""" + self.root.mainloop() + +if __name__ == "__main__": + # Check if running as root for certain operations + if os.geteuid() != 0: + print("Note: Some operations may require sudo privileges") + + app = BackupManager() + app.run() diff --git a/backup_script.sh b/backup_script.sh new file mode 100755 index 0000000..8faf330 --- /dev/null +++ b/backup_script.sh @@ -0,0 +1,576 @@ +#!/bin/bash +# System Backup Script - Command Line Version +# For use with cron jobs or manual execution + +set -e + +# Configuration +SOURCE_DRIVE="" # Will be auto-detected +TARGET_DRIVE="" # Will be detected or specified +RESTORE_MODE=false # Restore mode flag +SYNC_MODE=false # Smart sync mode flag +ANALYZE_ONLY=false # Analysis only mode +LOG_FILE="/var/log/system_backup.log" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Logging function +log() { + local message="$1" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + # Log to console + echo "${timestamp} - ${message}" + + # Log to file if writable + if [[ -w "$LOG_FILE" ]] || [[ -w "$(dirname "$LOG_FILE")" ]]; then + echo "${timestamp} - ${message}" >> "$LOG_FILE" 2>/dev/null + fi +} + +# Error handling +error_exit() { + log "${RED}ERROR: $1${NC}" + exit 1 +} + +# Success message +success() { + log "${GREEN}SUCCESS: $1${NC}" +} + +# Warning message +warning() { + log "${YELLOW}WARNING: $1${NC}" +} + +# Check if running as root +check_root() { + if [[ $EUID -ne 0 ]]; then + error_exit "This script must be run as root (use sudo)" + fi +} + +# Detect root filesystem drive +detect_root_drive() { + log "Detecting root filesystem drive..." + + # Find the device containing the root filesystem + local root_device=$(df / | tail -1 | awk '{print $1}') + + # Remove partition number to get base device + local base_device=$(echo "$root_device" | sed 's/[0-9]*$//') + + # Handle nvme drives (e.g., /dev/nvme0n1p1 -> /dev/nvme0n1) + base_device=$(echo "$base_device" | sed 's/p$//') + + echo "$base_device" +} + +# Check if target has existing backup +check_existing_backup() { + local target_drive=$1 + local temp_mount="/tmp/backup_check_$$" + + log "Checking for existing backup on $target_drive..." + + # Get main partition + local main_partition=$(lsblk -n -o NAME "$target_drive" | grep -v "^$(basename "$target_drive")$" | head -1) + if [[ -z "$main_partition" ]]; then + echo "false" + return + fi + + main_partition="/dev/$main_partition" + + # Try to mount and check + mkdir -p "$temp_mount" + if mount -o ro "$main_partition" "$temp_mount" 2>/dev/null; then + if [[ -d "$temp_mount/etc" && -d "$temp_mount/home" && -d "$temp_mount/usr" ]]; then + echo "true" + else + echo "false" + fi + umount "$temp_mount" 2>/dev/null + else + echo "false" + fi + rmdir "$temp_mount" 2>/dev/null +} + +# Analyze changes between source and target +analyze_changes() { + local source_drive=$1 + local target_drive=$2 + + log "Analyzing changes between $source_drive and $target_drive..." + + # Check if target has existing backup + local has_backup=$(check_existing_backup "$target_drive") + + if [[ "$has_backup" != "true" ]]; then + log "No existing backup found. Full clone required." + echo "FULL_CLONE_REQUIRED" + return + fi + + # Get filesystem usage for both drives + local source_size=$(get_filesystem_size "$source_drive") + local target_size=$(get_filesystem_size "$target_drive") + + # Calculate difference in GB + local size_diff=$((${source_size} - ${target_size})) + local size_diff_abs=${size_diff#-} # Absolute value + + # Convert to GB (sizes are in KB) + local size_diff_gb=$((size_diff_abs / 1024 / 1024)) + + log "Source filesystem size: $((source_size / 1024 / 1024)) GB" + log "Target filesystem size: $((target_size / 1024 / 1024)) GB" + log "Size difference: ${size_diff_gb} GB" + + # Decision logic + if [[ $size_diff_gb -lt 2 ]]; then + log "Recommendation: Smart sync (minimal changes)" + echo "SYNC_RECOMMENDED" + elif [[ $size_diff_gb -lt 10 ]]; then + log "Recommendation: Smart sync (moderate changes)" + echo "SYNC_BENEFICIAL" + else + log "Recommendation: Full clone (major changes)" + echo "FULL_CLONE_RECOMMENDED" + fi +} + +# Get filesystem size in KB +get_filesystem_size() { + local drive=$1 + local temp_mount="/tmp/size_check_$$" + + # Get main partition + local main_partition=$(lsblk -n -o NAME "$drive" | grep -v "^$(basename "$drive")$" | head -1) + if [[ -z "$main_partition" ]]; then + echo "0" + return + fi + + main_partition="/dev/$main_partition" + + # Mount and get usage + mkdir -p "$temp_mount" + if mount -o ro "$main_partition" "$temp_mount" 2>/dev/null; then + local used_kb=$(df "$temp_mount" | tail -1 | awk '{print $3}') + umount "$temp_mount" 2>/dev/null + echo "$used_kb" + else + echo "0" + fi + rmdir "$temp_mount" 2>/dev/null +} + +# Perform smart sync backup +smart_sync_backup() { + local source=$1 + local target=$2 + + log "Starting smart sync backup..." + + # Mount both filesystems + local source_mount="/tmp/sync_source_$$" + local target_mount="/tmp/sync_target_$$" + + mkdir -p "$source_mount" "$target_mount" + + # Get main partitions + local source_partition=$(lsblk -n -o NAME "$source" | grep -v "^$(basename "$source")$" | head -1) + local target_partition=$(lsblk -n -o NAME "$target" | grep -v "^$(basename "$target")$" | head -1) + + source_partition="/dev/$source_partition" + target_partition="/dev/$target_partition" + + log "Mounting filesystems for sync..." + mount -o ro "$source_partition" "$source_mount" || error_exit "Failed to mount source" + mount "$target_partition" "$target_mount" || error_exit "Failed to mount target" + + # Perform rsync + log "Starting rsync synchronization..." + rsync -avHAXS \ + --numeric-ids \ + --delete \ + --progress \ + --exclude=/proc/* \ + --exclude=/sys/* \ + --exclude=/dev/* \ + --exclude=/tmp/* \ + --exclude=/run/* \ + --exclude=/mnt/* \ + --exclude=/media/* \ + --exclude=/lost+found \ + "$source_mount/" "$target_mount/" || { + + # Cleanup on failure + umount "$source_mount" 2>/dev/null + umount "$target_mount" 2>/dev/null + rmdir "$source_mount" "$target_mount" 2>/dev/null + error_exit "Smart sync failed" + } + + # Cleanup + umount "$source_mount" 2>/dev/null + umount "$target_mount" 2>/dev/null + rmdir "$source_mount" "$target_mount" 2>/dev/null + + success "Smart sync completed successfully!" +} + +# Detect external drives +detect_external_drives() { + log "Detecting external drives..." + + # Get all block devices + lsblk -d -n -o NAME,SIZE,TYPE,TRAN | while read -r line; do + if [[ $line == *"disk"* ]] && [[ $line == *"usb"* ]]; then + drive_name=$(echo "$line" | awk '{print $1}') + drive_size=$(echo "$line" | awk '{print $2}') + echo "/dev/$drive_name ($drive_size)" + fi + done +} + +# Validate drives +validate_drives() { + if [[ ! -b "$SOURCE_DRIVE" ]]; then + error_exit "Source drive $SOURCE_DRIVE does not exist or is not a block device" + fi + + if [[ ! -b "$TARGET_DRIVE" ]]; then + error_exit "Target drive $TARGET_DRIVE does not exist or is not a block device" + fi + + if [[ "$SOURCE_DRIVE" == "$TARGET_DRIVE" ]]; then + error_exit "Source and target drives cannot be the same" + fi + + # Check if target drive is mounted + if mount | grep -q "$TARGET_DRIVE"; then + warning "Target drive $TARGET_DRIVE is currently mounted. Unmounting..." + umount "$TARGET_DRIVE"* 2>/dev/null || true + fi +} + +# Get drive size +get_drive_size() { + local drive=$1 + blockdev --getsize64 "$drive" +} + +# Clone drive +clone_drive() { + local source=$1 + local target=$2 + + log "Starting drive clone operation..." + log "Source: $source" + log "Target: $target" + + # Get sizes + source_size=$(get_drive_size "$source") + target_size=$(get_drive_size "$target") + + log "Source size: $(numfmt --to=iec-i --suffix=B $source_size)" + log "Target size: $(numfmt --to=iec-i --suffix=B $target_size)" + + if [[ $target_size -lt $source_size ]]; then + error_exit "Target drive is smaller than source drive" + fi + + # Start cloning + log "Starting clone operation with dd..." + + if command -v pv >/dev/null 2>&1; then + # Use pv for progress if available + dd if="$source" bs=4M | pv -s "$source_size" | dd of="$target" bs=4M conv=fdatasync + else + # Use dd with status=progress + dd if="$source" of="$target" bs=4M status=progress conv=fdatasync + fi + + if [[ $? -eq 0 ]]; then + success "Drive cloning completed successfully!" + + # Restore backup tools to external drive if this was a backup operation + if [[ "$RESTORE_MODE" != true ]]; then + log "Preserving backup tools on external drive..." + local restore_script="$(dirname "$0")/restore_tools_after_backup.sh" + if [[ -f "$restore_script" ]]; then + "$restore_script" "$target" || log "Warning: Could not preserve backup tools" + else + log "Warning: Tool preservation script not found" + fi + fi + + # Sync to ensure all data is written + log "Syncing data to disk..." + sync + + # Verify partition table + log "Verifying partition table on target drive..." + fdisk -l "$target" | head -20 | tee -a "$LOG_FILE" + + success "Backup verification completed!" + else + error_exit "Drive cloning failed!" + fi +} + +# Create desktop entry +create_desktop_entry() { + local desktop_file="$HOME/Desktop/System-Backup.desktop" + local script_path=$(realpath "$0") + + cat > "$desktop_file" << EOF +[Desktop Entry] +Version=1.0 +Type=Application +Name=System Backup +Comment=Clone internal drive to external M.2 SSD +Exec=gnome-terminal -- sudo "$script_path" --gui +Icon=drive-harddisk +Terminal=false +Categories=System;Utility; +EOF + + chmod +x "$desktop_file" + log "Desktop entry created: $desktop_file" +} + +# Show usage +show_usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -s, --source DRIVE Source drive (auto-detected if not specified)" + echo " -t, --target DRIVE Target drive (required)" + echo " -r, --restore Restore mode (reverse source and target)" + echo " --sync Smart sync mode (faster incremental backup)" + echo " --analyze Analyze changes without performing backup" + echo " -l, --list List available drives" + echo " -d, --desktop Create desktop entry" + echo " --gui Launch GUI version" + echo " -h, --help Show this help" + echo "" + echo "Examples:" + echo " $0 --list" + echo " $0 --analyze --target /dev/sdb" + echo " $0 --sync --target /dev/sdb" + echo " $0 --source /dev/nvme0n1 --target /dev/sdb" + echo " $0 --restore --source /dev/sdb --target /dev/nvme0n1" + echo " $0 --desktop" + echo " $0 --gui" +} + +# Main function +main() { + # Auto-detect source drive if not specified + if [[ -z "$SOURCE_DRIVE" ]]; then + SOURCE_DRIVE=$(detect_root_drive) + log "Auto-detected root filesystem drive: $SOURCE_DRIVE" + fi + + # Parse command line arguments + while [[ $# -gt 0 ]]; do + case $1 in + -s|--source) + SOURCE_DRIVE="$2" + shift 2 + ;; + -t|--target) + TARGET_DRIVE="$2" + shift 2 + ;; + -r|--restore) + RESTORE_MODE=true + shift + ;; + --sync) + SYNC_MODE=true + shift + ;; + --analyze) + ANALYZE_ONLY=true + shift + ;; + -l|--list) + echo "Available drives:" + lsblk -d -o NAME,SIZE,TYPE,TRAN + echo "" + echo "External drives:" + detect_external_drives + exit 0 + ;; + -d|--desktop) + create_desktop_entry + exit 0 + ;; + --gui) + python3 "$(dirname "$0")/backup_manager.py" + exit 0 + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + error_exit "Unknown option: $1" + ;; + esac + done + + # In restore mode, swap source and target for user convenience + if [[ "$RESTORE_MODE" == true ]]; then + if [[ -n "$SOURCE_DRIVE" && -n "$TARGET_DRIVE" ]]; then + log "Restore mode: swapping source and target drives" + TEMP_DRIVE="$SOURCE_DRIVE" + SOURCE_DRIVE="$TARGET_DRIVE" + TARGET_DRIVE="$TEMP_DRIVE" + fi + fi + + # Check if target drive is specified + if [[ -z "$TARGET_DRIVE" ]]; then + echo "Available external drives:" + detect_external_drives + echo "" + read -p "Enter target drive (e.g., /dev/sdb): " TARGET_DRIVE + + if [[ -z "$TARGET_DRIVE" ]]; then + error_exit "Target drive not specified" + fi + fi + + # Check root privileges + check_root + + # Validate drives + validate_drives + + # Handle analyze mode + if [[ "$ANALYZE_ONLY" == "true" ]]; then + echo "" + echo "πŸ” ANALYZING CHANGES" + echo "Source: $SOURCE_DRIVE" + echo "Target: $TARGET_DRIVE" + echo "" + + analysis_result=$(analyze_changes "$SOURCE_DRIVE" "$TARGET_DRIVE") + + case "$analysis_result" in + "FULL_CLONE_REQUIRED") + echo "πŸ“‹ ANALYSIS RESULT: Full Clone Required" + echo "β€’ No existing backup found on target drive" + echo "β€’ Complete drive cloning is necessary" + ;; + "SYNC_RECOMMENDED") + echo "βœ… ANALYSIS RESULT: Smart Sync Recommended" + echo "β€’ Minimal changes detected (< 2GB difference)" + echo "β€’ Smart sync will be much faster than full clone" + ;; + "SYNC_BENEFICIAL") + echo "⚑ ANALYSIS RESULT: Smart Sync Beneficial" + echo "β€’ Moderate changes detected (< 10GB difference)" + echo "β€’ Smart sync recommended for faster backup" + ;; + "FULL_CLONE_RECOMMENDED") + echo "πŸ”„ ANALYSIS RESULT: Full Clone Recommended" + echo "β€’ Major changes detected (> 10GB difference)" + echo "β€’ Full clone may be more appropriate" + ;; + esac + + echo "" + echo "Use --sync flag to perform smart sync backup" + exit 0 + fi + + # Handle sync mode + if [[ "$SYNC_MODE" == "true" ]]; then + echo "" + echo "⚑ SMART SYNC BACKUP" + echo "Source: $SOURCE_DRIVE" + echo "Target: $TARGET_DRIVE" + echo "" + + # Check if sync is possible + analysis_result=$(analyze_changes "$SOURCE_DRIVE" "$TARGET_DRIVE") + + if [[ "$analysis_result" == "FULL_CLONE_REQUIRED" ]]; then + error_exit "Smart sync not possible: No existing backup found. Use full backup first." + fi + + echo "Analysis: $analysis_result" + echo "" + read -p "Proceed with smart sync? (yes/no): " confirm + if [[ "$confirm" != "yes" ]]; then + log "Smart sync cancelled by user" + exit 0 + fi + + smart_sync_backup "$SOURCE_DRIVE" "$TARGET_DRIVE" + + success "Smart sync backup completed successfully!" + echo "Smart sync completed! External drive is up to date." + exit 0 + fi + + # Confirm operation + echo "" + if [[ "$RESTORE_MODE" == true ]]; then + echo "🚨 RESTORE CONFIGURATION 🚨" + echo "This will RESTORE (overwrite target with source data):" + else + echo "BACKUP CONFIGURATION:" + echo "This will BACKUP (copy source to target):" + fi + echo "Source: $SOURCE_DRIVE" + echo "Target: $TARGET_DRIVE" + echo "" + echo "WARNING: All data on $TARGET_DRIVE will be DESTROYED!" + + if [[ "$RESTORE_MODE" == true ]]; then + echo "" + echo "⚠️ CRITICAL WARNING FOR RESTORE MODE ⚠️" + echo "You are about to OVERWRITE $TARGET_DRIVE" + echo "Make sure this is what you intend to do!" + echo "" + read -p "Type 'RESTORE' to confirm or anything else to cancel: " confirm + if [[ "$confirm" != "RESTORE" ]]; then + log "Restore operation cancelled by user" + exit 0 + fi + else + read -p "Are you sure you want to continue? (yes/no): " confirm + if [[ "$confirm" != "yes" ]]; then + log "Operation cancelled by user" + exit 0 + fi + fi + + # Start operation + if [[ "$RESTORE_MODE" == true ]]; then + log "Starting system restore operation..." + else + log "Starting system backup operation..." + fi + clone_drive "$SOURCE_DRIVE" "$TARGET_DRIVE" + + log "System backup completed successfully!" + echo "" + echo "Backup completed! You can now safely remove the external drive." +} + +# Run main function +main "$@" diff --git a/bootstrap_usb_tools.sh b/bootstrap_usb_tools.sh new file mode 100755 index 0000000..c8960dd --- /dev/null +++ b/bootstrap_usb_tools.sh @@ -0,0 +1,166 @@ +#!/bin/bash + +# USB Live System Migration Bootstrap Script +# This script downloads and sets up the LVM migration tools on a live system + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${GREEN}=== LVM Migration Bootstrap ===${NC}" +echo "Setting up LVM migration tools on live system..." + +# Create working directory +WORK_DIR="/tmp/lvm-migration" +mkdir -p "$WORK_DIR" +cd "$WORK_DIR" + +echo "Working directory: $WORK_DIR" + +# Function to create the prepare script inline +create_prepare_script() { + cat > prepare_live_system.sh << 'EOF' +#!/bin/bash +# Live System Preparation Script for LVM Migration +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; } +success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } + +log "Updating package lists..." +apt update || { error "Failed to update package lists"; } + +log "Installing required packages..." +apt install -y lvm2 cryptsetup rsync parted pv grub-efi-amd64 e2fsprogs dosfstools || { + error "Failed to install required packages" +} + +log "Loading kernel modules..." +modprobe dm_mod dm_crypt dm_snapshot || true + +success "Live system prepared for LVM migration!" +echo "Now run: ./migrate_to_lvm.sh" +EOF + chmod +x prepare_live_system.sh +} + +# Function to create the main migration script inline +create_migration_script() { + # This is a simplified version - the full script is quite large + cat > migrate_to_lvm.sh << 'EOF' +#!/bin/bash +# LVM Migration Script - Bootstrap Version +# Downloads the full migration script and runs it + +REPO_URL="YOUR_REPO_URL_HERE" # Replace with actual repo URL +FULL_SCRIPT_URL="$REPO_URL/migrate_to_lvm.sh" + +echo "Downloading full migration script..." +wget -O migrate_to_lvm_full.sh "$FULL_SCRIPT_URL" || { + echo "Cannot download from repository. Using embedded version..." + # Here you would embed the full script or provide local copy + echo "Please manually copy the full migrate_to_lvm.sh script" + exit 1 +} + +chmod +x migrate_to_lvm_full.sh +./migrate_to_lvm_full.sh "$@" +EOF + chmod +x migrate_to_lvm.sh +} + +# Create a manual setup guide +create_manual_setup() { + cat > SETUP_INSTRUCTIONS.txt << 'EOF' +LVM Migration Setup Instructions +=============================== + +1. Boot from this USB stick into live system +2. Open terminal and run: sudo -i +3. Run: /media/*/migration_tools/bootstrap.sh + OR manually follow these steps: + +Manual Setup: +1. Update packages: apt update +2. Install tools: apt install -y lvm2 cryptsetup rsync parted pv grub-efi-amd64 +3. Load modules: modprobe dm_mod dm_crypt dm_snapshot +4. Create workspace: mkdir -p /tmp/lvm-migration && cd /tmp/lvm-migration +5. Copy migration scripts from external drive or USB +6. Run: ./migrate_to_lvm.sh + +Drive Configuration (adjust as needed): +- Internal drive: /dev/nvme0n1 (source) +- External M.2: /dev/sdc (target) +- USB stick: /dev/sdb (tools) + +Important Notes: +- This will DESTROY all data on the external M.2 drive +- Internal drive remains unchanged as backup +- External M.2 will become bootable LVM system +- Update BIOS boot order after migration + +For full documentation, see LIVE_USB_MIGRATION_GUIDE.md +EOF +} + +# Create all the files +echo "Creating preparation script..." +create_prepare_script + +echo "Creating migration bootstrap..." +create_migration_script + +echo "Creating setup instructions..." +create_manual_setup + +# Create a simple launcher script +cat > bootstrap.sh << 'EOF' +#!/bin/bash +echo "=== LVM Migration Bootstrap ===" +echo "1. Prepare live system (install packages)" +echo "2. Run LVM migration" +echo "3. Show instructions" +read -p "Select option [1-3]: " choice + +case $choice in + 1) sudo ./prepare_live_system.sh ;; + 2) sudo ./migrate_to_lvm.sh ;; + 3) cat SETUP_INSTRUCTIONS.txt ;; + *) echo "Invalid option" ;; +esac +EOF +chmod +x bootstrap.sh + +echo -e "${GREEN}Bootstrap scripts created in $WORK_DIR${NC}" +echo +echo "Contents:" +ls -la + +echo +echo -e "${YELLOW}Next steps:${NC}" +echo "1. Copy the migration tools from your development directory to a location accessible from live USB" +echo "2. Boot from the USB stick" +echo "3. Run the bootstrap script to set up the migration environment" + +# Copy the actual migration tools if they exist in current directory +if [ -f "../migrate_to_lvm.sh" ]; then + echo + echo "Copying actual migration tools..." + cp ../migrate_to_lvm.sh ../lvm_snapshot_backup.sh ../validate_lvm_migration.sh . 2>/dev/null || true + cp ../LIVE_USB_MIGRATION_GUIDE.md . 2>/dev/null || true + echo "Migration tools copied to $WORK_DIR" +fi + +success "Bootstrap setup complete!" \ No newline at end of file diff --git a/check_packages.sh b/check_packages.sh new file mode 100755 index 0000000..efee89e --- /dev/null +++ b/check_packages.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Package Availability Checker for Live Systems +# Checks what packages are available before attempting installation + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log() { echo -e "${BLUE}[INFO]${NC} $1"; } +success() { echo -e "${GREEN}[FOUND]${NC} $1"; } +warning() { echo -e "${YELLOW}[MISSING]${NC} $1"; } + +echo -e "${GREEN}=== Package Availability Checker ===${NC}" +echo "Checking package availability for LVM migration..." +echo + +# Detect distribution +if [ -f /etc/os-release ]; then + . /etc/os-release + echo "Distribution: $PRETTY_NAME" + echo "ID: $ID" + echo "Version: $VERSION_ID" + echo +fi + +# Function to check if a package exists in repositories +check_package() { + local pkg="$1" + if apt-cache show "$pkg" >/dev/null 2>&1; then + success "$pkg" + return 0 + else + warning "$pkg" + return 1 + fi +} + +# Function to check multiple package alternatives +check_alternatives() { + local desc="$1" + shift + local packages=("$@") + + echo -e "${BLUE}$desc:${NC}" + local found=0 + for pkg in "${packages[@]}"; do + if check_package "$pkg"; then + ((found++)) + fi + done + + if [ $found -eq 0 ]; then + echo -e " ${RED}⚠ No packages found for $desc${NC}" + fi + echo +} + +echo "Updating package cache..." +apt update >/dev/null 2>&1 || warning "Could not update package cache" +echo + +# Check critical packages +total_missing=0 + +check_alternatives "LVM Tools" "lvm2" "lvm" +check_alternatives "Device Mapper" "dmsetup" "device-mapper" +check_alternatives "Cryptsetup" "cryptsetup" "cryptsetup-bin" +check_alternatives "Filesystem Tools" "e2fsprogs" "dosfstools" "parted" +check_alternatives "Backup Tools" "rsync" "pv" +check_alternatives "GRUB EFI" "grub-efi-amd64" "grub-efi" "grub-efi-amd64-bin" +check_alternatives "GRUB PC" "grub-pc-bin" "grub-pc" +check_alternatives "GRUB Common" "grub-common" "grub2-common" +check_alternatives "Initramfs" "initramfs-tools" "dracut" +check_alternatives "System Tools" "util-linux" "coreutils" "bc" + +echo -e "${BLUE}=== Summary ===${NC}" +echo -e "${GREEN}βœ“ Package availability checked${NC}" +echo "Most packages should be available for installation." + +echo +echo "To install packages, run:" +echo " sudo ./emergency_install.sh" +echo +echo "To check individual commands after installation:" +echo " command -v lvm && echo 'LVM available'" +echo " command -v cryptsetup && echo 'Cryptsetup available'" +echo " command -v grub-install && echo 'GRUB available'" \ No newline at end of file diff --git a/clean_external_lvm_boot.sh b/clean_external_lvm_boot.sh new file mode 100755 index 0000000..918f2ee --- /dev/null +++ b/clean_external_lvm_boot.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# Clean External LVM Boot Configuration Script +# Fixes: 1) Encrypted home partition passphrase prompt during boot +# 2) Internal drive entries appearing in GRUB menu + +set -e + +LOG_FILE="/tmp/clean_external_boot_$(date +%Y%m%d_%H%M%S).log" + +echo "🧹 Cleaning External LVM Boot Configuration - $(date)" | tee "$LOG_FILE" +echo "====================================================" | tee -a "$LOG_FILE" + +# Function to cleanup mounts on exit +cleanup() { + echo "🧹 Cleaning up mounts..." | tee -a "$LOG_FILE" + sudo umount /tmp/external-root/proc 2>/dev/null || true + sudo umount /tmp/external-root/sys 2>/dev/null || true + sudo umount /tmp/external-root/dev 2>/dev/null || true + sudo umount /tmp/external-root/boot/efi 2>/dev/null || true + sudo umount /tmp/external-root/boot 2>/dev/null || true + sudo umount /tmp/external-root 2>/dev/null || true + sudo rmdir /tmp/external-root 2>/dev/null || true +} +trap cleanup EXIT + +# Check if LVM is active +echo "πŸ“‹ Checking LVM status..." | tee -a "$LOG_FILE" +if ! sudo lvs system-vg/root &>/dev/null; then + echo "❌ LVM system-vg/root not found. Please ensure the external M.2 is connected." | tee -a "$LOG_FILE" + exit 1 +fi + +# Mount external LVM system +echo "πŸ’Ύ Mounting external LVM system..." | tee -a "$LOG_FILE" +sudo mkdir -p /tmp/external-root +sudo mount /dev/system-vg/root /tmp/external-root +sudo mount /dev/system-vg/boot /tmp/external-root/boot +sudo mount /dev/sda1 /tmp/external-root/boot/efi + +# Bind mount system directories for chroot +echo "πŸ”— Setting up chroot environment..." | tee -a "$LOG_FILE" +sudo mount --bind /proc /tmp/external-root/proc +sudo mount --bind /sys /tmp/external-root/sys +sudo mount --bind /dev /tmp/external-root/dev + +echo "πŸ”§ Fixing encrypted home partition issue..." | tee -a "$LOG_FILE" + +# Backup current files +sudo cp /tmp/external-root/etc/crypttab /tmp/external-root/etc/crypttab.backup.$(date +%Y%m%d_%H%M%S) +sudo cp /tmp/external-root/etc/default/grub /tmp/external-root/etc/default/grub.backup.$(date +%Y%m%d_%H%M%S) + +# Remove internal drive encryption from crypttab +echo " β€’ Cleaning /etc/crypttab..." | tee -a "$LOG_FILE" +sudo tee /tmp/external-root/etc/crypttab << 'EOF' > /dev/null +# /etc/crypttab: mappings for encrypted partitions. +# +# External LVM system - no encrypted partitions needed +# Internal drive encryption removed to prevent boot prompts +# +# +EOF + +echo "πŸŽ›οΈ Disabling GRUB os-prober..." | tee -a "$LOG_FILE" + +# Disable os-prober in GRUB to prevent internal drive detection +sudo sed -i 's/#GRUB_DISABLE_OS_PROBER=false/GRUB_DISABLE_OS_PROBER=true/' /tmp/external-root/etc/default/grub + +# Add the line if it doesn't exist +if ! grep -q "GRUB_DISABLE_OS_PROBER" /tmp/external-root/etc/default/grub; then + echo "GRUB_DISABLE_OS_PROBER=true" | sudo tee -a /tmp/external-root/etc/default/grub > /dev/null +fi + +echo "πŸ”„ Updating system configuration..." | tee -a "$LOG_FILE" + +# Update initramfs to reflect crypttab changes +echo " β€’ Updating initramfs..." | tee -a "$LOG_FILE" +sudo chroot /tmp/external-root update-initramfs -u -k all + +# Regenerate GRUB configuration without os-prober +echo " β€’ Regenerating GRUB configuration..." | tee -a "$LOG_FILE" +sudo chroot /tmp/external-root update-grub + +echo "βœ… Verification..." | tee -a "$LOG_FILE" + +# Verify no internal drive references +INTERNAL_REFS=$(sudo grep -c "nvme0n1p1\|b6d5bc23-1077-4ab3-8b55-918fb121847e" /tmp/external-root/boot/grub/grub.cfg 2>/dev/null || echo "0") +GRUB_ENTRIES=$(sudo grep -c "menuentry.*Kubuntu" /tmp/external-root/boot/grub/grub.cfg 2>/dev/null || echo "0") + +echo " β€’ Internal drive references in GRUB: $INTERNAL_REFS" | tee -a "$LOG_FILE" +echo " β€’ Kubuntu menu entries: $GRUB_ENTRIES" | tee -a "$LOG_FILE" + +if [ "$INTERNAL_REFS" -eq 0 ] && [ "$GRUB_ENTRIES" -gt 0 ]; then + echo "βœ… SUCCESS: External LVM boot configuration cleaned!" | tee -a "$LOG_FILE" + echo "" | tee -a "$LOG_FILE" + echo "πŸš€ Results:" | tee -a "$LOG_FILE" + echo " β€’ No more encrypted home partition prompts during boot" | tee -a "$LOG_FILE" + echo " β€’ Clean GRUB menu with only external LVM entries" | tee -a "$LOG_FILE" + echo " β€’ Internal drive completely excluded from boot process" | tee -a "$LOG_FILE" + echo "" | tee -a "$LOG_FILE" + echo "🎯 Next steps:" | tee -a "$LOG_FILE" + echo " 1. Reboot system" | tee -a "$LOG_FILE" + echo " 2. Should boot directly without any prompts" | tee -a "$LOG_FILE" + echo " 3. GRUB menu should only show Kubuntu entries" | tee -a "$LOG_FILE" +else + echo "⚠️ Warning: Configuration may need manual review" | tee -a "$LOG_FILE" + echo " Internal refs: $INTERNAL_REFS, Kubuntu entries: $GRUB_ENTRIES" | tee -a "$LOG_FILE" +fi + +echo "" | tee -a "$LOG_FILE" +echo "πŸ“‹ Log saved to: $LOG_FILE" | tee -a "$LOG_FILE" +echo "🧹 External LVM boot configuration cleaning completed at $(date)" | tee -a "$LOG_FILE" \ No newline at end of file diff --git a/create_alpine_backup_usb.sh b/create_alpine_backup_usb.sh new file mode 100755 index 0000000..236a3cb --- /dev/null +++ b/create_alpine_backup_usb.sh @@ -0,0 +1,307 @@ +#!/bin/bash +# Create Truly Bootable Backup USB with Alpine Linux +# This creates a complete bootable environment that preserves existing boot capability + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if running as root +if [[ $EUID -eq 0 ]]; then + print_error "Do not run as root. Script will use sudo when needed." + exit 1 +fi + +print_status "Bootable Backup USB Creator (Preserves Existing Boot)" +echo "==========================================================" +echo + +# Check current USB drive status +USB_DRIVE="/dev/sda" # Your current USB + +print_status "Analyzing current USB structure..." +lsblk "$USB_DRIVE" +echo + +# Check if it already has a bootable system +HAS_BOOT=$(lsblk "$USB_DRIVE" | grep -i boot || true) +if [[ -n "$HAS_BOOT" ]]; then + print_warning "USB appears to have existing boot partition" + print_warning "We'll preserve this and add backup functionality" +else + print_status "No existing boot detected - will create complete bootable system" +fi + +read -p "Continue to make this USB fully bootable with backup functionality? (yes/no): " confirm +if [[ "$confirm" != "yes" ]]; then + exit 0 +fi + +# Create temporary directory for Alpine Linux +WORK_DIR="/tmp/alpine_usb_$$" +mkdir -p "$WORK_DIR" +cd "$WORK_DIR" + +print_status "Downloading Alpine Linux (minimal, fast-booting)..." + +# Download Alpine Linux (very small, perfect for this) +ALPINE_VERSION="3.18.4" +ALPINE_ISO="alpine-standard-${ALPINE_VERSION}-x86_64.iso" + +if [[ ! -f "$ALPINE_ISO" ]]; then + wget "https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/$ALPINE_ISO" || { + print_error "Failed to download Alpine Linux" + exit 1 + } +fi + +print_status "Preparing bootable USB with backup functionality..." + +# Mount the ISO to extract files +ISO_MOUNT="$WORK_DIR/iso_mount" +mkdir -p "$ISO_MOUNT" +sudo mount -o loop "$ALPINE_ISO" "$ISO_MOUNT" + +# Create mount points for USB partitions +BOOT_MOUNT="$WORK_DIR/usb_boot" +DATA_MOUNT="$WORK_DIR/usb_data" +mkdir -p "$BOOT_MOUNT" "$DATA_MOUNT" + +# Mount USB partitions (created by previous script) +sudo mount "${USB_DRIVE}1" "$BOOT_MOUNT" +sudo mount "${USB_DRIVE}2" "$DATA_MOUNT" + +print_status "Installing Alpine Linux boot files..." + +# Copy Alpine Linux files to boot partition +sudo cp -r "$ISO_MOUNT"/* "$BOOT_MOUNT/" + +# Install GRUB for UEFI/BIOS boot +print_status "Installing GRUB bootloader..." +sudo grub-install --target=x86_64-efi --efi-directory="$BOOT_MOUNT" --boot-directory="$BOOT_MOUNT/boot" --removable --force + +# Create GRUB configuration for automatic backup boot +sudo tee "$BOOT_MOUNT/boot/grub/grub.cfg" > /dev/null << 'EOF' +set timeout=10 +set default=0 + +menuentry "Automatic System Backup" { + linux /boot/vmlinuz-lts modules=loop,squashfs,sd-mod,usb-storage quiet alpine_dev=sda2:/backup-tools/alpine.apkovl.tar.gz + initrd /boot/initramfs-lts +} + +menuentry "Manual Backup Mode" { + linux /boot/vmlinuz-lts modules=loop,squashfs,sd-mod,usb-storage alpine_dev=sda2:/backup-tools/alpine.apkovl.tar.gz + initrd /boot/initramfs-lts +} + +menuentry "Alpine Linux (Standard)" { + linux /boot/vmlinuz-lts modules=loop,squashfs,sd-mod,usb-storage + initrd /boot/initramfs-lts +} +EOF + +print_status "Creating backup environment overlay..." + +# Create Alpine overlay for automatic backup +OVERLAY_DIR="$WORK_DIR/overlay" +mkdir -p "$OVERLAY_DIR/etc/init.d" +mkdir -p "$OVERLAY_DIR/usr/local/bin" +mkdir -p "$OVERLAY_DIR/etc/runlevels/default" + +# Create backup script that runs on boot +sudo tee "$OVERLAY_DIR/usr/local/bin/backup-menu" > /dev/null << 'EOF' +#!/bin/sh +# Interactive backup menu + +clear +echo "========================================" +echo " AUTOMATIC BACKUP SYSTEM" +echo "========================================" +echo +echo "Available drives:" +lsblk -d -o NAME,SIZE,TYPE,TRAN | grep -E "(disk|NAME)" +echo + +# Auto-detect drives +INTERNAL_CANDIDATES=$(lsblk -d -n -o NAME,TYPE,TRAN | grep "disk" | grep -v "usb" | awk '{print "/dev/" $1}') +EXTERNAL_CANDIDATES=$(lsblk -d -n -o NAME,TYPE,TRAN | grep "disk" | grep "usb" | awk '{print "/dev/" $1}' | grep -v sda) + +echo "Drive Selection:" +echo "==================" + +# Select source drive +echo "Available internal drives:" +echo "$INTERNAL_CANDIDATES" | nl -v 1 +echo +read -p "Select source drive number (or enter path): " SOURCE_CHOICE + +if echo "$SOURCE_CHOICE" | grep -q "^[0-9]"; then + SOURCE_DRIVE=$(echo "$INTERNAL_CANDIDATES" | sed -n "${SOURCE_CHOICE}p") +else + SOURCE_DRIVE="$SOURCE_CHOICE" +fi + +# Select target drive +echo +echo "Available external drives:" +echo "$EXTERNAL_CANDIDATES" | nl -v 1 +echo +read -p "Select target drive number (or enter path): " TARGET_CHOICE + +if echo "$TARGET_CHOICE" | grep -q "^[0-9]"; then + TARGET_DRIVE=$(echo "$EXTERNAL_CANDIDATES" | sed -n "${TARGET_CHOICE}p") +else + TARGET_DRIVE="$TARGET_CHOICE" +fi + +echo +echo "BACKUP CONFIGURATION:" +echo "====================" +echo "Source (will be copied FROM): $SOURCE_DRIVE" +echo "Target (will be overwritten): $TARGET_DRIVE" +echo +echo "⚠️ ALL DATA ON $TARGET_DRIVE WILL BE DESTROYED! ⚠️" +echo +read -p "Continue with backup? (yes/no): " CONFIRM + +if [ "$CONFIRM" != "yes" ]; then + echo "Backup cancelled" + read -p "Press Enter to shutdown..." + poweroff + exit 0 +fi + +echo +echo "Starting backup..." +echo "==================" + +# Get drive sizes for progress +SOURCE_SIZE=$(blockdev --getsize64 "$SOURCE_DRIVE") +SOURCE_SIZE_GB=$((SOURCE_SIZE / 1024 / 1024 / 1024)) + +echo "Copying $SOURCE_SIZE_GB GB from $SOURCE_DRIVE to $TARGET_DRIVE" +echo + +# Perform backup with progress +if command -v pv >/dev/null 2>&1; then + dd if="$SOURCE_DRIVE" bs=4M | pv -s "$SOURCE_SIZE" | dd of="$TARGET_DRIVE" bs=4M conv=fdatasync +else + dd if="$SOURCE_DRIVE" of="$TARGET_DRIVE" bs=4M status=progress conv=fdatasync +fi + +if [ $? -eq 0 ]; then + echo + echo "========================================" + echo " BACKUP COMPLETED SUCCESSFULLY!" + echo "========================================" + echo "Source: $SOURCE_DRIVE" + echo "Target: $TARGET_DRIVE" + echo "Size: $SOURCE_SIZE_GB GB" +else + echo + echo "========================================" + echo " BACKUP FAILED!" + echo "========================================" +fi + +echo +echo "System will shutdown in 30 seconds..." +echo "Press Ctrl+C to cancel shutdown" +sleep 30 +poweroff +EOF + +chmod +x "$OVERLAY_DIR/usr/local/bin/backup-menu" + +# Create init script to run backup on boot +sudo tee "$OVERLAY_DIR/etc/init.d/autobackup" > /dev/null << 'EOF' +#!/sbin/openrc-run + +name="autobackup" +description="Automatic backup system" + +depend() { + need localmount + after * +} + +start() { + ebegin "Starting automatic backup system" + + # Wait for devices to settle + sleep 5 + + # Check kernel command line for auto mode + if grep -q "autobackup" /proc/cmdline; then + /usr/local/bin/backup-menu + else + # Manual mode - provide choice + echo "Press 'b' for backup or any other key to continue to shell..." + read -t 10 -n 1 choice + if [ "$choice" = "b" ] || [ "$choice" = "B" ]; then + /usr/local/bin/backup-menu + fi + fi + + eend $? +} +EOF + +chmod +x "$OVERLAY_DIR/etc/init.d/autobackup" + +# Enable the service +ln -sf /etc/init.d/autobackup "$OVERLAY_DIR/etc/runlevels/default/autobackup" + +# Create the overlay archive +cd "$OVERLAY_DIR" +tar czf "$DATA_MOUNT/alpine.apkovl.tar.gz" * + +# Copy our backup tools to the data partition as well +SCRIPT_DIR=$(dirname "$(realpath "$0")") +sudo cp "$SCRIPT_DIR"/*.sh "$DATA_MOUNT/" + +print_status "Installing additional tools..." + +# Create an Alpine package cache with useful tools +sudo mkdir -p "$DATA_MOUNT/apk-cache" + +# Cleanup +cd "$WORK_DIR/.." +sudo umount "$ISO_MOUNT" "$BOOT_MOUNT" "$DATA_MOUNT" 2>/dev/null || true +rm -rf "$WORK_DIR" + +print_success "Fully bootable backup USB created!" +print_success "USB: $USB_DRIVE" +echo +print_success "WHAT YOU CAN NOW DO:" +print_success "1. Boot from this USB in BIOS/UEFI menu" +print_success "2. Select 'Automatic System Backup' from GRUB menu" +print_success "3. Choose source and target drives from interactive menu" +print_success "4. Backup runs automatically with progress display" +print_success "5. System shuts down when complete" +echo +print_warning "The USB remains bootable for future backups!" +print_warning "Your backup tools are preserved in the data partition" +EOF diff --git a/create_auto_startup.sh b/create_auto_startup.sh new file mode 100644 index 0000000..bb5951c --- /dev/null +++ b/create_auto_startup.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Create automatic startup script for Clonezilla Live + +cat > /tmp/auto-backup-startup.sh << 'EOF' +#!/bin/bash +# Automatic startup script for Clonezilla Live +# This runs when auto_backup=true is passed as kernel parameter + +if grep -q "auto_backup=true" /proc/cmdline; then + echo "Automatic backup mode detected" + sleep 3 + + # Mount the backup partition + mkdir -p /tmp/backup_mount + mount /dev/sda2 /tmp/backup_mount 2>/dev/null + + if [ -f /tmp/backup_mount/automated_clonezilla_backup.sh ]; then + echo "Running automated backup script..." + /tmp/backup_mount/automated_clonezilla_backup.sh + else + echo "Automated backup script not found, starting manual Clonezilla" + ocs-live-general + fi +fi +EOF + +chmod +x /tmp/auto-backup-startup.sh +echo "Auto-startup script created" diff --git a/create_bootable_backup.sh b/create_bootable_backup.sh new file mode 100755 index 0000000..9c72249 --- /dev/null +++ b/create_bootable_backup.sh @@ -0,0 +1,243 @@ +#!/bin/bash +# Create Bootable Backup USB Script +# This creates a bootable USB that can perform system backups + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if running as root +if [[ $EUID -eq 0 ]]; then + print_error "Do not run as root. Script will use sudo when needed." + exit 1 +fi + +print_status "Bootable Backup USB Creator" +echo "===========================================" +echo + +print_warning "This will create a bootable USB drive that can:" +print_warning "1. Boot into a minimal Linux environment" +print_warning "2. Automatically detect and backup your internal drive" +print_warning "3. Work completely offline (no running OS interference)" +echo + +# List available USB drives +print_status "Available USB drives:" +lsblk -d -o NAME,SIZE,TYPE,TRAN | grep "usb" || { + print_error "No USB drives detected!" + exit 1 +} + +echo +read -p "Enter the USB drive to make bootable (e.g., /dev/sdb): " USB_DRIVE + +if [[ ! -b "$USB_DRIVE" ]]; then + print_error "Device $USB_DRIVE does not exist!" + exit 1 +fi + +# Confirm USB drive selection +print_warning "⚠️ ALL DATA ON $USB_DRIVE WILL BE DESTROYED! ⚠️" +print_warning "This USB will become a bootable backup tool" +echo +lsblk "$USB_DRIVE" +echo +read -p "Are you sure you want to continue? (yes/no): " confirm + +if [[ "$confirm" != "yes" ]]; then + print_error "Operation cancelled" + exit 1 +fi + +print_status "Creating bootable backup USB..." + +# Unmount any existing partitions +sudo umount "${USB_DRIVE}"* 2>/dev/null || true + +# Create partition table and bootable partition +print_status "Creating partition table..." +sudo parted "$USB_DRIVE" --script mklabel gpt +sudo parted "$USB_DRIVE" --script mkpart ESP fat32 1MiB 512MiB +sudo parted "$USB_DRIVE" --script mkpart backup ext4 512MiB 100% +sudo parted "$USB_DRIVE" --script set 1 boot on + +# Format partitions +print_status "Formatting partitions..." +if [[ "$USB_DRIVE" == *"nvme"* ]]; then + BOOT_PART="${USB_DRIVE}p1" + DATA_PART="${USB_DRIVE}p2" +else + BOOT_PART="${USB_DRIVE}1" + DATA_PART="${USB_DRIVE}2" +fi + +sudo mkfs.fat -F32 -n "BOOT" "$BOOT_PART" +sudo mkfs.ext4 -L "BACKUP_TOOLS" "$DATA_PART" + +# Mount partitions +BOOT_MOUNT="/tmp/usb_boot_$$" +DATA_MOUNT="/tmp/usb_data_$$" +sudo mkdir -p "$BOOT_MOUNT" "$DATA_MOUNT" +sudo mount "$BOOT_PART" "$BOOT_MOUNT" +sudo mount "$DATA_PART" "$DATA_MOUNT" + +print_status "Installing backup tools..." + +# Copy backup scripts to data partition +SCRIPT_DIR=$(dirname "$(realpath "$0")") +sudo cp -r "$SCRIPT_DIR"/* "$DATA_MOUNT/" +sudo chmod +x "$DATA_MOUNT"/*.sh + +# Create autorun script for backup +sudo tee "$DATA_MOUNT/autorun_backup.sh" > /dev/null << 'EOF' +#!/bin/bash +# Auto-run backup script when booted from USB + +set -e + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +echo -e "${GREEN}" +echo "========================================" +echo " BOOTABLE BACKUP SYSTEM STARTED" +echo "========================================" +echo -e "${NC}" + +# Wait for drives to be detected +sleep 5 + +echo "Detecting drives..." +echo + +# Auto-detect internal drive (largest non-USB drive) +INTERNAL_DRIVE=$(lsblk -d -n -o NAME,SIZE,TYPE,TRAN | grep "disk" | grep -v "usb" | sort -k2 -hr | head -1 | awk '{print "/dev/" $1}') + +# Auto-detect backup target (largest USB drive that's not the boot drive) +BOOT_USB=$(df /backup-tools | tail -1 | awk '{print $1}' | sed 's/[0-9]*$//') +TARGET_DRIVE=$(lsblk -d -n -o NAME,SIZE,TYPE,TRAN | grep "disk" | grep "usb" | awk '{print "/dev/" $1}' | grep -v "$BOOT_USB" | head -1) + +echo "Auto-detected configuration:" +echo "Internal drive: $INTERNAL_DRIVE" +echo "Target drive: $TARGET_DRIVE" +echo + +if [[ -z "$INTERNAL_DRIVE" || -z "$TARGET_DRIVE" ]]; then + echo -e "${RED}Could not auto-detect drives. Manual selection required.${NC}" + echo "Available drives:" + lsblk -d -o NAME,SIZE,TYPE,TRAN + echo + read -p "Enter source drive: " INTERNAL_DRIVE + read -p "Enter target drive: " TARGET_DRIVE +fi + +echo -e "${YELLOW}FINAL CONFIRMATION${NC}" +echo "Source (internal): $INTERNAL_DRIVE" +echo "Target (backup): $TARGET_DRIVE" +echo +echo -e "${RED}⚠️ TARGET DRIVE WILL BE COMPLETELY OVERWRITTEN! ⚠️${NC}" +echo +read -p "Continue with backup? (yes/no): " confirm + +if [[ "$confirm" != "yes" ]]; then + echo "Backup cancelled" + exit 0 +fi + +echo -e "${GREEN}Starting backup...${NC}" +cd /backup-tools +./backup_script.sh --source "$INTERNAL_DRIVE" --target "$TARGET_DRIVE" + +echo -e "${GREEN}" +echo "========================================" +echo " BACKUP COMPLETED SUCCESSFULLY!" +echo "========================================" +echo -e "${NC}" +echo "You can now:" +echo "1. Remove the USB drives" +echo "2. Reboot to your normal system" +echo "3. Use smart sync for future backups" +echo +read -p "Press Enter to shutdown system..." +shutdown -h now +EOF + +sudo chmod +x "$DATA_MOUNT/autorun_backup.sh" + +print_status "Downloading minimal Linux for USB boot..." + +# Check if we have a Linux ISO or create a simple boot setup +print_warning "Note: You'll need to manually add a bootable Linux distro" +print_warning "Recommendation: Use Ubuntu Live USB creator or similar" +print_warning "Then copy the backup tools to the USB" + +# Create instructions file +sudo tee "$DATA_MOUNT/INSTRUCTIONS.txt" > /dev/null << EOF +BOOTABLE BACKUP USB INSTRUCTIONS +================================== + +This USB contains backup tools but needs a bootable Linux environment. + +SETUP STEPS: +1. Use Ubuntu's "Startup Disk Creator" or similar tool +2. Create a bootable Ubuntu Live USB on this drive +3. Boot from this USB +4. Open terminal and run: + cd /media/ubuntu/BACKUP_TOOLS + sudo ./autorun_backup.sh + +AUTOMATIC BACKUP: +- The script will auto-detect your internal drive +- Auto-detect external backup target +- Perform complete system backup +- Shutdown when complete + +AFTER FIRST BACKUP: +- Boot back to your normal system +- Use the GUI for smart sync backups: + python3 backup_manager.py +- Click "Smart Sync Backup" for fast updates + +MANUAL BACKUP: +If auto-detection fails, you can run manually: +sudo ./backup_script.sh --source /dev/nvme0n1 --target /dev/sda +EOF + +# Cleanup +sudo umount "$BOOT_MOUNT" "$DATA_MOUNT" +sudo rmdir "$BOOT_MOUNT" "$DATA_MOUNT" + +print_success "Bootable backup USB preparation complete!" +print_success "USB: $USB_DRIVE" +echo +print_warning "NEXT STEPS:" +print_warning "1. Use Ubuntu's 'Startup Disk Creator' to make this USB bootable" +print_warning "2. Boot from USB in BIOS/UEFI boot menu" +print_warning "3. Run: sudo /media/ubuntu/BACKUP_TOOLS/autorun_backup.sh" +echo +print_success "After first backup, use GUI smart sync for incremental updates!" diff --git a/create_clonezilla_backup.sh b/create_clonezilla_backup.sh new file mode 100755 index 0000000..b54e3e4 --- /dev/null +++ b/create_clonezilla_backup.sh @@ -0,0 +1,412 @@ +#!/bin/bash +# Create Clonezilla-based Backup USB +# This adapts Clonezilla Live with our custom backup functionality + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_status "Clonezilla-based Backup USB Creator" +echo "======================================" +echo + +print_warning "This will enhance Clonezilla Live with our custom backup tools" +print_warning "You'll get both Clonezilla functionality AND our automated backup" +echo + +# Check if we have a USB drive +USB_DRIVE="/dev/sda" +if [[ ! -b "$USB_DRIVE" ]]; then + print_error "USB drive $USB_DRIVE not found!" + exit 1 +fi + +print_status "Current USB: $USB_DRIVE" +lsblk "$USB_DRIVE" +echo + +# Download Clonezilla if not already present +CLONEZILLA_ISO="clonezilla-live-3.1.0-22-amd64.iso" +# Using OSDN mirror which is typically faster than SourceForge +CLONEZILLA_URL="https://osdn.net/dl/clonezilla/$CLONEZILLA_ISO" + +if [[ ! -f "$CLONEZILLA_ISO" ]]; then + print_status "Downloading Clonezilla Live from OSDN mirror..." + wget "$CLONEZILLA_URL" || { + print_warning "OSDN mirror failed, trying GitHub mirror..." + CLONEZILLA_URL="https://github.com/stevenshiau/clonezilla/releases/download/3.1.0-22/$CLONEZILLA_ISO" + wget "$CLONEZILLA_URL" || { + print_warning "GitHub mirror failed, trying SourceForge as fallback..." + CLONEZILLA_URL="https://downloads.sourceforge.net/clonezilla/$CLONEZILLA_ISO" + wget "$CLONEZILLA_URL" || { + print_error "Failed to download Clonezilla from all mirrors" + exit 1 + } + } + } +fi + +print_warning "⚠️ This will COMPLETELY RECREATE the USB drive with Clonezilla! ⚠️" +print_warning "All current data will be lost!" +echo +read -p "Continue? (yes/no): " confirm + +if [[ "$confirm" != "yes" ]]; then + print_error "Operation cancelled" + exit 1 +fi + +print_status "Creating Clonezilla Live USB with backup tools..." + +# Unmount any mounted partitions +sudo umount "${USB_DRIVE}"* 2>/dev/null || true + +# Create new partition table +print_status "Creating partition table..." +sudo parted "$USB_DRIVE" --script mklabel msdos +sudo parted "$USB_DRIVE" --script mkpart primary fat32 1MiB 4GiB +sudo parted "$USB_DRIVE" --script mkpart primary ext4 4GiB 100% +sudo parted "$USB_DRIVE" --script set 1 boot on + +# Format partitions +print_status "Formatting partitions..." +if [[ "$USB_DRIVE" == *"nvme"* ]]; then + BOOT_PART="${USB_DRIVE}p1" + DATA_PART="${USB_DRIVE}p2" +else + BOOT_PART="${USB_DRIVE}1" + DATA_PART="${USB_DRIVE}2" +fi + +sudo mkfs.fat -F32 -n "CLONEZILLA" "$BOOT_PART" +sudo mkfs.ext4 -L "BACKUP_TOOLS" "$DATA_PART" + +# Mount partitions +BOOT_MOUNT="/tmp/clonezilla_boot_$$" +DATA_MOUNT="/tmp/clonezilla_data_$$" +ISO_MOUNT="/tmp/clonezilla_iso_$$" + +sudo mkdir -p "$BOOT_MOUNT" "$DATA_MOUNT" "$ISO_MOUNT" +sudo mount "$BOOT_PART" "$BOOT_MOUNT" +sudo mount "$DATA_PART" "$DATA_MOUNT" +sudo mount -o loop "$CLONEZILLA_ISO" "$ISO_MOUNT" + +print_status "Installing Clonezilla Live..." + +# Copy Clonezilla files +sudo cp -r "$ISO_MOUNT"/* "$BOOT_MOUNT/" + +# Install GRUB +print_status "Installing GRUB bootloader..." +sudo grub-install --target=i386-pc --boot-directory="$BOOT_MOUNT/boot" "$USB_DRIVE" + +# Create enhanced GRUB configuration +print_status "Creating enhanced GRUB menu..." +sudo tee "$BOOT_MOUNT/boot/grub/grub.cfg" > /dev/null << 'EOF' +set timeout=15 +set default=0 + +menuentry "πŸš€ AUTOMATIC SYSTEM BACKUP" { + linux /live/vmlinuz boot=live union=overlay username=user config components quiet noswap edd=on nomodeset ocs_live_run="ocs-live-general" ocs_live_extra_param="" keyboard-layouts= ocs_live_batch="no" locales= vga=788 ip= net.ifnames=0 nosplash i915.blacklist=yes radeonhd.blacklist=yes nouveau.blacklist=yes vmwgfx.enable_fbdev=1 systemd.show_status=0 + initrd /live/initrd.img +} + +menuentry "πŸ”§ MANUAL BACKUP MODE" { + linux /live/vmlinuz boot=live union=overlay username=user config components quiet noswap edd=on nomodeset ocs_live_run="ocs-live-general" ocs_live_extra_param="" keyboard-layouts= ocs_live_batch="no" locales= vga=788 ip= net.ifnames=0 nosplash i915.blacklist=yes radeonhd.blacklist=yes nouveau.blacklist=yes vmwgfx.enable_fbdev=1 systemd.show_status=0 custom_backup=manual + initrd /live/initrd.img +} + +menuentry "πŸ“¦ CLONEZILLA LIVE (Original)" { + linux /live/vmlinuz boot=live union=overlay username=user config components quiet noswap edd=on nomodeset ocs_live_run="ocs-live-general" ocs_live_extra_param="" keyboard-layouts= ocs_live_batch="no" locales= vga=788 ip= net.ifnames=0 nosplash i915.blacklist=yes radeonhd.blacklist=yes nouveau.blacklist=yes vmwgfx.enable_fbdev=1 + initrd /live/initrd.img +} + +menuentry "πŸ› οΈ CLONEZILLA EXPERT MODE" { + linux /live/vmlinuz boot=live union=overlay username=user config components quiet noswap edd=on nomodeset ocs_live_run="ocs-expert" ocs_live_extra_param="" keyboard-layouts= ocs_live_batch="no" locales= vga=788 ip= net.ifnames=0 nosplash i915.blacklist=yes radeonhd.blacklist=yes nouveau.blacklist=yes vmwgfx.enable_fbdev=1 + initrd /live/initrd.img +} + +menuentry "πŸ” MEMORY TEST" { + linux16 /live/memtest +} +EOF + +print_status "Installing backup tools to data partition..." + +# Copy our backup scripts +SCRIPT_DIR=$(dirname "$(realpath "$0")") +sudo cp "$SCRIPT_DIR"/*.sh "$DATA_MOUNT/" +sudo chmod +x "$DATA_MOUNT"/*.sh + +# Also place the automated script in the Clonezilla filesystem for direct access +sudo mkdir -p "$BOOT_MOUNT/live/image/backup_tools" +sudo cp "$SCRIPT_DIR/automated_clonezilla_backup.sh" "$BOOT_MOUNT/live/image/backup_tools/" +sudo chmod +x "$BOOT_MOUNT/live/image/backup_tools/automated_clonezilla_backup.sh" + +# Create custom startup script that checks for auto_backup parameter +sudo tee "$BOOT_MOUNT/live/image/auto-start.sh" > /dev/null << 'EOF' +#!/bin/bash +# Check if auto_backup=true is in kernel parameters +if grep -q "auto_backup=true" /proc/cmdline; then + echo "Starting automatic backup mode..." + sleep 2 + # Mount backup partition and run our script + mkdir -p /tmp/backup_mount + mount /dev/sda2 /tmp/backup_mount 2>/dev/null + if [ -f /tmp/backup_mount/automated_clonezilla_backup.sh ]; then + /tmp/backup_mount/automated_clonezilla_backup.sh + else + echo "Backup script not found, starting normal Clonezilla" + ocs-live-general + fi +else + # Normal Clonezilla startup + ocs-live-general +fi +EOF + +sudo chmod +x "$BOOT_MOUNT/live/image/auto-start.sh" + +# Create startup script for Clonezilla +sudo tee "$DATA_MOUNT/auto-backup.sh" > /dev/null << 'EOF' +#!/bin/bash +# Auto-backup script for Clonezilla Live + +export PATH=/usr/bin:/bin:/sbin:/usr/sbin:/usr/local/bin + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' + +clear +echo -e "${GREEN}" +echo "================================================" +echo " CLONEZILLA-BASED AUTOMATIC BACKUP" +echo "================================================" +echo -e "${NC}" + +# Check if we're in auto mode +AUTO_MODE="false" +if grep -q "custom_backup=auto" /proc/cmdline; then + AUTO_MODE="true" +fi + +# Wait for drives to settle +echo "Waiting for drives to be detected..." +sleep 5 + +# Function to list drives +list_drives() { + echo -e "${BLUE}Available drives:${NC}" + echo "==================" + lsblk -d -o NAME,SIZE,TYPE,TRAN,MODEL | grep -E "(NAME|disk)" + echo +} + +# Function to get drive selection +select_drives() { + list_drives + + echo -e "${YELLOW}SELECT SOURCE DRIVE (internal, to backup FROM):${NC}" + INTERNAL_DRIVES=$(lsblk -d -n -o NAME,TRAN | grep -v usb | grep -v loop | awk '{print $1}') + echo "$INTERNAL_DRIVES" | nl -v 1 + echo + read -p "Enter source drive number or path: " SOURCE_INPUT + + if echo "$SOURCE_INPUT" | grep -q "^[0-9]"; then + SOURCE_DRIVE="/dev/$(echo "$INTERNAL_DRIVES" | sed -n "${SOURCE_INPUT}p")" + else + SOURCE_DRIVE="$SOURCE_INPUT" + fi + + echo + echo -e "${YELLOW}SELECT TARGET DRIVE (external, will be OVERWRITTEN):${NC}" + EXTERNAL_DRIVES=$(lsblk -d -n -o NAME,TRAN | grep -v loop | awk '{print $1}') + echo "$EXTERNAL_DRIVES" | nl -v 1 + echo + read -p "Enter target drive number or path: " TARGET_INPUT + + if echo "$TARGET_INPUT" | grep -q "^[0-9]"; then + TARGET_DRIVE="/dev/$(echo "$EXTERNAL_DRIVES" | sed -n "${TARGET_INPUT}p")" + else + TARGET_DRIVE="$TARGET_INPUT" + fi +} + +# Function to perform backup +perform_backup() { + echo + echo -e "${GREEN}BACKUP CONFIGURATION:${NC}" + echo "====================" + echo "Source: $SOURCE_DRIVE" + echo "Target: $TARGET_DRIVE" + echo + + # Show sizes + if [[ -b "$SOURCE_DRIVE" ]]; then + SOURCE_SIZE=$(blockdev --getsize64 "$SOURCE_DRIVE" 2>/dev/null || echo "0") + SOURCE_GB=$((SOURCE_SIZE / 1024 / 1024 / 1024)) + echo "Source size: ${SOURCE_GB}GB" + fi + + if [[ -b "$TARGET_DRIVE" ]]; then + TARGET_SIZE=$(blockdev --getsize64 "$TARGET_DRIVE" 2>/dev/null || echo "0") + TARGET_GB=$((TARGET_SIZE / 1024 / 1024 / 1024)) + echo "Target size: ${TARGET_GB}GB" + fi + + echo + echo -e "${RED}⚠️ ALL DATA ON $TARGET_DRIVE WILL BE DESTROYED! ⚠️${NC}" + echo + + if [[ "$AUTO_MODE" == "true" ]]; then + echo "Auto mode - starting backup in 10 seconds..." + echo "(Press Ctrl+C to cancel)" + sleep 10 + else + read -p "Continue with backup? (yes/no): " CONFIRM + if [[ "$CONFIRM" != "yes" ]]; then + echo "Backup cancelled" + return 1 + fi + fi + + echo + echo -e "${GREEN}Starting backup...${NC}" + echo "==================" + + # Use dd for raw backup (like Clonezilla but simpler) + if command -v pv >/dev/null 2>&1; then + dd if="$SOURCE_DRIVE" bs=4M 2>/dev/null | pv -s "$SOURCE_SIZE" | dd of="$TARGET_DRIVE" bs=4M conv=fdatasync 2>/dev/null + else + dd if="$SOURCE_DRIVE" of="$TARGET_DRIVE" bs=4M status=progress conv=fdatasync + fi + + RESULT=$? + + echo + if [[ $RESULT -eq 0 ]]; then + echo -e "${GREEN}" + echo "================================================" + echo " BACKUP COMPLETED SUCCESSFULLY!" + echo "================================================" + echo -e "${NC}" + echo "Your system has been backed up to $TARGET_DRIVE" + echo "You can now use smart sync for future updates" + else + echo -e "${RED}" + echo "================================================" + echo " BACKUP FAILED!" + echo "================================================" + echo -e "${NC}" + fi + + echo + echo "System will shutdown in 30 seconds..." + echo "(Press any key to cancel shutdown)" + + if read -t 30 -n 1; then + echo + echo "Shutdown cancelled" + echo "You can now use Clonezilla or run another backup" + else + echo + echo "Shutting down..." + sudo shutdown -h now + fi +} + +# Main execution +if [[ "$AUTO_MODE" == "true" ]]; then + echo "πŸš€ AUTOMATIC BACKUP MODE" + echo "=========================" + echo +fi + +select_drives +perform_backup +EOF + +sudo chmod +x "$DATA_MOUNT/auto-backup.sh" + +# Create instructions +sudo tee "$DATA_MOUNT/README.txt" > /dev/null << 'EOF' +CLONEZILLA-BASED BACKUP USB +=========================== + +This USB combines Clonezilla Live with custom backup automation. + +BOOT MENU OPTIONS: +πŸš€ Automatic System Backup - Boots directly to backup menu +πŸ”§ Manual Backup Mode - Access to both backup tools and Clonezilla +πŸ“¦ Clonezilla Live - Original Clonezilla functionality +πŸ› οΈ Clonezilla Expert - Advanced Clonezilla options + +AUTOMATIC MODE: +- Auto-detects drives +- Interactive drive selection +- Raw dd backup (like Clonezilla) +- Progress display +- Auto-shutdown when complete + +MANUAL MODE: +- Access to shell +- Run: /media/user/BACKUP_TOOLS/auto-backup.sh +- Or use Clonezilla GUI + +ADVANTAGES: +βœ… Proven boot compatibility (Clonezilla) +βœ… Professional disk cloning tools +βœ… Both automatic and manual modes +βœ… Raw disk backup for maximum compatibility +βœ… Works on virtually any hardware + +AFTER FIRST BACKUP: +Boot back to your normal system and use smart sync: +python3 backup_manager.py +EOF + +# Cleanup +sudo umount "$ISO_MOUNT" "$BOOT_MOUNT" "$DATA_MOUNT" +sudo rmdir "$ISO_MOUNT" "$BOOT_MOUNT" "$DATA_MOUNT" + +print_success "Clonezilla-based backup USB created!" +print_success "USB: $USB_DRIVE" +echo +print_success "FEATURES:" +print_success "βœ… Clonezilla Live base (proven boot compatibility)" +print_success "βœ… Custom backup automation" +print_success "βœ… Automatic and manual modes" +print_success "βœ… Professional disk cloning" +print_success "βœ… Works on any hardware" +echo +print_warning "BOOT OPTIONS:" +print_warning "πŸš€ Automatic System Backup - Direct to backup" +print_warning "πŸ”§ Manual Backup Mode - Shell + Clonezilla access" +print_warning "πŸ“¦ Clonezilla Live - Original functionality" +echo +print_success "Ready to test! This should boot reliably in QEMU and real hardware." diff --git a/create_dd_backup_usb.sh b/create_dd_backup_usb.sh new file mode 100755 index 0000000..4fbd2f0 --- /dev/null +++ b/create_dd_backup_usb.sh @@ -0,0 +1,332 @@ +#!/bin/bash +# Create TRUE Plug-and-Play DD-based Backup USB +# Boot = Automatic backup with dd, no questions asked + +set -e + +USB_DRIVE="/dev/sda" + +echo "Creating TRUE PLUG-AND-PLAY Backup USB with DD" +echo "==============================================" +echo "β€’ Boot USB = Automatic backup starts immediately" +echo "β€’ Uses dd for maximum speed and reliability" +echo "β€’ 15-20 minute full disk backup" +echo "β€’ Also includes restore functionality" +echo + +# Check if we have a suitable live Linux ISO +ALPINE_ISO="alpine-extended-3.18.4-x86_64.iso" +if [[ ! -f "$ALPINE_ISO" ]]; then + echo "Downloading lightweight Alpine Linux..." + wget "https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/$ALPINE_ISO" || { + echo "Download failed. Please download Alpine Linux ISO manually." + exit 1 + } +fi + +read -p "Continue to create TRUE automatic backup USB? (yes/no): " confirm +if [[ "$confirm" != "yes" ]]; then + exit 1 +fi + +# Unmount and create single partition +sudo umount "${USB_DRIVE}"* 2>/dev/null || true +sudo parted "$USB_DRIVE" --script mklabel msdos +sudo parted "$USB_DRIVE" --script mkpart primary fat32 1MiB 100% +sudo parted "$USB_DRIVE" --script set 1 boot on + +# Format +USB_PART="${USB_DRIVE}1" +sudo mkfs.fat -F32 -n "AUTOBACKUP" "$USB_PART" + +# Mount and install Alpine +USB_MOUNT="/tmp/autobackup_$$" +ISO_MOUNT="/tmp/alpine_iso_$$" + +sudo mkdir -p "$USB_MOUNT" "$ISO_MOUNT" +sudo mount "$USB_PART" "$USB_MOUNT" +sudo mount -o loop "$ALPINE_ISO" "$ISO_MOUNT" + +echo "Installing Alpine Linux..." +sudo cp -R "$ISO_MOUNT"/* "$USB_MOUNT/" + +echo "Installing GRUB..." +sudo grub-install --target=i386-pc --boot-directory="$USB_MOUNT/boot" "$USB_DRIVE" + +# Create truly automatic backup script +sudo tee "$USB_MOUNT/auto_backup.sh" > /dev/null << 'EOF' +#!/bin/sh +# TRUE automatic backup script - no user interaction + +clear +echo "==========================================" +echo " AUTOMATIC SYSTEM BACKUP STARTING" +echo "==========================================" +echo "" +echo "This will backup your internal drive to this USB" +echo "Estimated time: 15-20 minutes" +echo "" +echo "Starting in 10 seconds... (Ctrl+C to cancel)" +echo "" + +# 10 second countdown +for i in 10 9 8 7 6 5 4 3 2 1; do + echo -n "$i... " + sleep 1 +done +echo "" +echo "" + +# Auto-detect internal drive (exclude USB drives) +INTERNAL_DRIVE="" +for drive in /dev/nvme0n1 /dev/sda /dev/sdb /dev/sdc; do + if [ -b "$drive" ]; then + # Check if it's not a USB drive and has partitions + if ! echo "$drive" | grep -q "/dev/sda" && [ "$(lsblk -n "$drive" | wc -l)" -gt 1 ]; then + INTERNAL_DRIVE="$drive" + break + fi + fi +done + +if [ -z "$INTERNAL_DRIVE" ]; then + echo "ERROR: Could not detect internal drive!" + echo "Available drives:" + lsblk + echo "" + echo "Press Enter to try manual backup..." + read dummy + /auto_backup_manual.sh + exit 1 +fi + +# Get drive sizes +INTERNAL_SIZE=$(blockdev --getsize64 "$INTERNAL_DRIVE" 2>/dev/null || echo "0") +USB_SIZE=$(blockdev --getsize64 /dev/sda 2>/dev/null || echo "0") + +INTERNAL_GB=$((INTERNAL_SIZE / 1024 / 1024 / 1024)) +USB_GB=$((USB_SIZE / 1024 / 1024 / 1024)) + +echo "BACKUP CONFIGURATION:" +echo "Source: $INTERNAL_DRIVE (${INTERNAL_GB}GB)" +echo "Target: /dev/sda (${USB_GB}GB)" +echo "" + +# Check space +if [ "$INTERNAL_SIZE" -gt "$USB_SIZE" ]; then + echo "WARNING: Target drive might be too small!" + echo "This backup may not complete successfully." + echo "" +fi + +echo "Starting backup with maximum speed..." +echo "Progress will be shown below:" +echo "" + +# Create backup directory and file +mkdir -p /mnt/backup +mount /dev/sda1 /mnt/backup 2>/dev/null || { + echo "ERROR: Could not mount USB for backup storage" + exit 1 +} + +BACKUP_FILE="/mnt/backup/system_backup_$(date +%Y%m%d_%H%M%S).img" + +echo "Backup file: $BACKUP_FILE" +echo "" + +# Perform backup with progress using dd and pv +if which pv >/dev/null 2>&1; then + # Use pv for progress if available + dd if="$INTERNAL_DRIVE" bs=4M status=none | pv -s "$INTERNAL_SIZE" | dd of="$BACKUP_FILE" bs=4M status=none +else + # Fallback to dd with progress + dd if="$INTERNAL_DRIVE" of="$BACKUP_FILE" bs=4M status=progress +fi + +# Verify and finish +sync +echo "" +echo "==========================================" +echo " BACKUP COMPLETED SUCCESSFULLY!" +echo "==========================================" +echo "" +echo "Backup saved to: $BACKUP_FILE" +echo "Backup size: $(du -h "$BACKUP_FILE" | cut -f1)" +echo "" + +# Create restore script +cat > "/mnt/backup/restore_$(date +%Y%m%d_%H%M%S).sh" << RESTORE_EOF +#!/bin/sh +# Restore script for backup created $(date) + +BACKUP_FILE="$BACKUP_FILE" +TARGET_DRIVE="\$1" + +if [ -z "\$TARGET_DRIVE" ]; then + echo "Usage: \$0 /dev/target_drive" + echo "Example: \$0 /dev/nvme0n1" + echo "" + echo "Available drives:" + lsblk + exit 1 +fi + +echo "WARNING: This will completely overwrite \$TARGET_DRIVE" +echo "Source: \$BACKUP_FILE" +echo "Target: \$TARGET_DRIVE" +echo "" +read -p "Type 'RESTORE' to confirm: " confirm + +if [ "\$confirm" != "RESTORE" ]; then + echo "Cancelled" + exit 1 +fi + +echo "Restoring system..." +if which pv >/dev/null 2>&1; then + pv "\$BACKUP_FILE" | dd of="\$TARGET_DRIVE" bs=4M status=none +else + dd if="\$BACKUP_FILE" of="\$TARGET_DRIVE" bs=4M status=progress +fi + +sync +echo "Restore completed! System should be bootable." +RESTORE_EOF + +chmod +x "/mnt/backup/restore_$(date +%Y%m%d_%H%M%S).sh" + +echo "Restore script created for easy system recovery" +echo "" +echo "System will reboot in 10 seconds..." +echo "Remove USB and boot normally, or press Ctrl+C to stay in backup mode" + +sleep 10 +umount /mnt/backup +reboot +EOF + +# Create manual backup script for fallback +sudo tee "$USB_MOUNT/auto_backup_manual.sh" > /dev/null << 'EOF' +#!/bin/sh +# Manual backup mode - for when auto-detection fails + +echo "==========================================" +echo " MANUAL BACKUP MODE" +echo "==========================================" +echo "" +echo "Available drives:" +lsblk +echo "" + +echo "Enter source drive (internal drive to backup):" +read -p "Source (e.g., /dev/nvme0n1): " SOURCE_DRIVE + +if [ ! -b "$SOURCE_DRIVE" ]; then + echo "ERROR: $SOURCE_DRIVE is not a valid block device" + exit 1 +fi + +echo "" +echo "Backup will be saved to this USB drive (/dev/sda)" +echo "Source: $SOURCE_DRIVE" +echo "Target: USB backup file" +echo "" +read -p "Press Enter to start backup or Ctrl+C to cancel..." + +# Same backup process as automatic mode +mkdir -p /mnt/backup +mount /dev/sda1 /mnt/backup + +BACKUP_FILE="/mnt/backup/manual_backup_$(date +%Y%m%d_%H%M%S).img" + +echo "Creating backup: $BACKUP_FILE" +echo "" + +if which pv >/dev/null 2>&1; then + SOURCE_SIZE=$(blockdev --getsize64 "$SOURCE_DRIVE") + dd if="$SOURCE_DRIVE" bs=4M status=none | pv -s "$SOURCE_SIZE" | dd of="$BACKUP_FILE" bs=4M status=none +else + dd if="$SOURCE_DRIVE" of="$BACKUP_FILE" bs=4M status=progress +fi + +sync +echo "" +echo "Manual backup completed!" +echo "Backup saved to: $BACKUP_FILE" +umount /mnt/backup +EOF + +sudo chmod +x "$USB_MOUNT/auto_backup.sh" +sudo chmod +x "$USB_MOUNT/auto_backup_manual.sh" + +# Create GRUB menu for true automation +sudo tee "$USB_MOUNT/boot/grub/grub.cfg" > /dev/null << 'EOF' +set timeout=5 +set default=0 + +menuentry "AUTOMATIC BACKUP (5 second countdown)" { + linux /boot/vmlinuz-lts root=/dev/sda1 rw quiet init=/auto_backup.sh + initrd /boot/initramfs-lts +} + +menuentry "Manual Backup Mode" { + linux /boot/vmlinuz-lts root=/dev/sda1 rw quiet init=/auto_backup_manual.sh + initrd /boot/initramfs-lts +} + +menuentry "Alpine Linux (Recovery Console)" { + linux /boot/vmlinuz-lts root=/dev/sda1 rw quiet + initrd /boot/initramfs-lts +} +EOF + +# Install pv for progress monitoring +sudo mkdir -p "$USB_MOUNT/apks" +echo "Adding progress monitoring tool..." + +# Create final instructions +sudo tee "$USB_MOUNT/TRUE_PLUG_AND_PLAY_INSTRUCTIONS.txt" > /dev/null << 'EOF' +TRUE PLUG-AND-PLAY BACKUP USB +============================ + +πŸš€ AUTOMATIC BACKUP: +1. Boot from this USB +2. Wait 5 seconds (automatic backup starts) +3. Wait for 10-second countdown +4. Backup runs automatically (15-20 minutes) +5. System reboots when done + +πŸ”§ MANUAL BACKUP: +1. Boot from USB +2. Select "Manual Backup Mode" +3. Follow prompts to select drives +4. Backup proceeds automatically + +πŸ’Ύ RESTORE SYSTEM: +1. Boot from USB +2. Select "Alpine Linux (Recovery Console)" +3. Run: /restore_XXXXXXXX.sh /dev/target_drive +4. Follow prompts + +TRULY AUTOMATIC: +- No Clonezilla menus +- No device selection +- No compression choices +- Just boot and wait! + +Created: $(date) +EOF + +# Cleanup +sudo umount "$ISO_MOUNT" "$USB_MOUNT" +sudo rmdir "$USB_MOUNT" "$ISO_MOUNT" + +echo "" +echo "βœ… TRUE PLUG-AND-PLAY BACKUP USB CREATED!" +echo "βœ… Boot USB = 5 second countdown then AUTOMATIC backup" +echo "βœ… Uses dd for maximum reliability and speed" +echo "βœ… Creates restore scripts automatically" +echo "βœ… No menus, no choices - just boot and wait!" +echo "" +echo "DISASTER RECOVERY: Boot USB, wait 15 seconds, backup happens!" diff --git a/diagnose_boot_issue.sh b/diagnose_boot_issue.sh new file mode 100755 index 0000000..5dec252 --- /dev/null +++ b/diagnose_boot_issue.sh @@ -0,0 +1,179 @@ +#!/bin/bash + +# LVM Boot Diagnostics Script +# Checks the current state of the LVM migration and identifies boot issues + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; } +success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + +echo -e "${GREEN}=== LVM Boot Diagnostics ===${NC}" +echo + +# Check system state +log "Checking current system state..." +echo "Currently booted from: $(df / | tail -1 | awk '{print $1}')" +echo "Running kernel: $(uname -r)" +echo "System: $(hostname)" +echo + +# Check available drives +log "Available block devices:" +lsblk -f + +echo + +# Check LVM status +log "LVM Status:" +echo "Physical Volumes:" +sudo pvs 2>/dev/null || echo "No PVs found" + +echo "Volume Groups:" +sudo vgs 2>/dev/null || echo "No VGs found" + +echo "Logical Volumes:" +sudo lvs 2>/dev/null || echo "No LVs found" + +echo + +# Check for system-vg specifically +if sudo vgs system-vg >/dev/null 2>&1; then + success "Found system-vg volume group" + + log "system-vg details:" + sudo vgs system-vg + sudo lvs system-vg + + # Try to mount and check contents + log "Checking external system contents..." + + if [ ! -d /tmp/check-external ]; then + mkdir -p /tmp/check-external + + if sudo mount /dev/system-vg/root /tmp/check-external >/dev/null 2>&1; then + success "External root filesystem is mountable" + + # Check key system directories + for dir in "/etc" "/boot" "/usr" "/var"; do + if [ -d "/tmp/check-external$dir" ]; then + success "Found system directory: $dir" + else + warning "Missing system directory: $dir" + fi + done + + # Check for GRUB files + if [ -d "/tmp/check-external/boot/grub" ]; then + success "GRUB directory found" + if [ -f "/tmp/check-external/boot/grub/grub.cfg" ]; then + success "GRUB configuration found" + else + warning "GRUB configuration missing" + fi + else + warning "GRUB directory missing" + fi + + # Check fstab + if [ -f "/tmp/check-external/etc/fstab" ]; then + success "fstab found" + log "fstab LVM entries:" + grep -E "system-vg|UUID=" "/tmp/check-external/etc/fstab" || echo "No LVM entries found" + else + warning "fstab missing" + fi + + sudo umount /tmp/check-external + else + error "Cannot mount external root filesystem" + fi + fi + +else + error "system-vg volume group not found" + echo "This suggests the LVM migration did not complete successfully" +fi + +echo + +# Check EFI partition +log "Checking for EFI boot partition..." +if [ -b /dev/sda1 ]; then + success "Found EFI partition /dev/sda1" + + if [ ! -d /tmp/check-efi ]; then + mkdir -p /tmp/check-efi + + if sudo mount /dev/sda1 /tmp/check-efi >/dev/null 2>&1; then + success "EFI partition is mountable" + + if [ -d "/tmp/check-efi/EFI" ]; then + success "EFI directory found" + log "EFI boot entries:" + ls -la "/tmp/check-efi/EFI/" 2>/dev/null || echo "No EFI entries" + + if [ -f "/tmp/check-efi/EFI/debian/grubx64.efi" ]; then + success "Debian GRUB EFI bootloader found" + else + warning "Debian GRUB EFI bootloader missing" + fi + else + warning "EFI directory missing" + fi + + sudo umount /tmp/check-efi + else + error "Cannot mount EFI partition" + fi + fi +else + error "EFI partition /dev/sda1 not found" +fi + +echo + +# Provide diagnosis and recommendations +log "=== DIAGNOSIS ===" + +if sudo vgs system-vg >/dev/null 2>&1; then + success "LVM migration appears to have completed" + + if [ -b /dev/sda1 ] && sudo mount /dev/sda1 /tmp/check-efi >/dev/null 2>&1; then + if [ -f "/tmp/check-efi/EFI/debian/grubx64.efi" ]; then + success "GRUB bootloader appears to be installed" + echo + echo -e "${BLUE}Likely causes of boot reset issue:${NC}" + echo "1. GRUB configuration points to wrong device" + echo "2. initramfs missing LVM support" + echo "3. BIOS/UEFI boot order incorrect" + echo "4. Secure Boot enabled (conflicts with GRUB)" + echo + echo -e "${GREEN}Recommended action:${NC}" + echo "Run: sudo ./fix_grub_boot.sh" + else + warning "GRUB bootloader missing" + echo -e "${GREEN}Recommended action:${NC}" + echo "Run: sudo ./fix_grub_boot.sh" + fi + sudo umount /tmp/check-efi 2>/dev/null || true + else + error "EFI partition issues detected" + echo -e "${GREEN}Recommended action:${NC}" + echo "Run: sudo ./fix_grub_boot.sh" + fi +else + error "LVM migration incomplete or failed" + echo -e "${GREEN}Recommended action:${NC}" + echo "Re-run migration: sudo ./migrate_to_lvm.sh" +fi + +# Cleanup +rm -rf /tmp/check-external /tmp/check-efi 2>/dev/null || true \ No newline at end of file diff --git a/emergency_install.sh b/emergency_install.sh new file mode 100755 index 0000000..b049777 --- /dev/null +++ b/emergency_install.sh @@ -0,0 +1,204 @@ +#!/bin/bash + +# Emergency Package Installer for LVM Migration +# Handles different Debian/Ubuntu distributions and package availability + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } +success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + +echo -e "${GREEN}=== Emergency Package Installer ===${NC}" +echo "Installing all packages required for LVM migration..." + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + error "This script must be run as root. Use: sudo $0" + exit 1 +fi + +# Detect distribution +if [ -f /etc/os-release ]; then + . /etc/os-release + DISTRO="$ID" + VERSION="$VERSION_ID" + log "Detected distribution: $PRETTY_NAME" +else + DISTRO="unknown" + warning "Could not detect distribution" +fi + +# Update package lists +log "Updating package lists..." +apt update || warning "Failed to update package lists" + +# Function to try installing a package with alternatives +try_install_package() { + local primary="$1" + shift + local alternatives=("$@") + + log "Installing $primary..." + if apt install -y "$primary" >/dev/null 2>&1; then + success "Installed $primary" + return 0 + fi + + # Try alternatives + for alt in "${alternatives[@]}"; do + log "Trying alternative: $alt" + if apt install -y "$alt" >/dev/null 2>&1; then + success "Installed $alt (alternative for $primary)" + return 0 + fi + done + + warning "Failed to install $primary or any alternatives" + return 1 +} + +# Install packages with error handling and alternatives +log "Installing core utilities..." +try_install_package "util-linux" +try_install_package "coreutils" +try_install_package "bc" +try_install_package "bsdmainutils" "bsdutils" + +log "Installing LVM and device mapper tools..." +try_install_package "lvm2" +try_install_package "dmsetup" "device-mapper" + +log "Installing encryption tools..." +try_install_package "cryptsetup" "cryptsetup-bin" + +log "Installing filesystem tools..." +try_install_package "e2fsprogs" +try_install_package "dosfstools" "mtools" +try_install_package "parted" + +log "Installing backup and monitoring tools..." +try_install_package "rsync" +try_install_package "pv" "pipe-viewer" + +log "Installing GRUB bootloader components..." +# Different distributions may have different GRUB package names +case "$DISTRO" in + debian) + try_install_package "grub-efi-amd64" "grub-efi" "grub-efi-amd64-bin" + try_install_package "grub-pc-bin" "grub-pc" + try_install_package "grub-common" + ;; + ubuntu) + try_install_package "grub-efi-amd64" "grub-efi" + try_install_package "grub-pc-bin" "grub-pc" + try_install_package "grub-common" + try_install_package "grub2-common" + ;; + *) + # Generic attempt for unknown distributions + warning "Unknown distribution, trying generic GRUB packages..." + try_install_package "grub-efi-amd64" "grub-efi" "grub" + try_install_package "grub-pc-bin" "grub-pc" "grub" + try_install_package "grub-common" "grub2-common" + ;; +esac + +log "Installing kernel and initramfs tools..." +try_install_package "initramfs-tools" "dracut" +# Don't install kernel on live system as it's not needed and may cause issues +# try_install_package "linux-image-generic" "linux-image-amd64" + +log "Installing additional required tools..." +try_install_package "udev" "systemd-udev" +try_install_package "kmod" "module-init-tools" + +# Load kernel modules +log "Loading required kernel modules..." +modprobe dm_mod 2>/dev/null || warning "Failed to load dm_mod" +modprobe dm_crypt 2>/dev/null || warning "Failed to load dm_crypt" +modprobe dm_snapshot 2>/dev/null || warning "Failed to load dm_snapshot" + +# Check if LVM service is available and start it +if systemctl list-unit-files | grep -q lvm2; then + log "Starting LVM services..." + systemctl start lvm2-monitor 2>/dev/null || warning "Could not start lvm2-monitor" + systemctl start lvm2-lvmpolld 2>/dev/null || warning "Could not start lvm2-lvmpolld" +fi + +# Verify critical tools are available +log "Verifying tool installation..." +missing_critical=() + +# Check for tool availability with alternatives +check_tool() { + local primary="$1" + shift + local alternatives=("$@") + + if command -v "$primary" >/dev/null 2>&1; then + return 0 + fi + + for alt in "${alternatives[@]}"; do + if command -v "$alt" >/dev/null 2>&1; then + return 0 + fi + done + + missing_critical+=("$primary") + return 1 +} + +check_tool "lvm" "lvm2" +check_tool "vgdisplay" "lvm" +check_tool "pvcreate" "lvm" +check_tool "lvcreate" "lvm" +check_tool "cryptsetup" +check_tool "rsync" +check_tool "parted" +check_tool "pv" +check_tool "mkfs.ext4" "mke2fs" +check_tool "mkfs.fat" "mkfs.vfat" +check_tool "grub-install" "grub2-install" +check_tool "update-grub" "grub-mkconfig" +check_tool "update-initramfs" "dracut" + +if [ ${#missing_critical[@]} -eq 0 ]; then + success "All critical tools are now available!" + echo + echo "Available tools:" + for cmd in lvm vgdisplay cryptsetup rsync parted pv mkfs.ext4 grub-install; do + if command -v "$cmd" >/dev/null 2>&1; then + echo " βœ“ $cmd: $(which $cmd)" + fi + done + echo + echo "You can now run: ./migrate_to_lvm.sh" +else + error "Still missing critical tools: ${missing_critical[*]}" + echo + echo "You may need to:" + echo "1. Check internet connection for package downloads" + echo "2. Try different package repositories" + echo "3. Install packages manually with different names" + echo "4. Use a different live system distribution" + exit 1 +fi + +echo +echo -e "${GREEN}Installation completed successfully!${NC}" +echo "The system is now ready for LVM migration." +echo +echo "Next steps:" +echo "1. Run: ./migrate_to_lvm.sh" +echo "2. Follow the interactive prompts" +echo "3. Validate with: ./validate_lvm_migration.sh" \ No newline at end of file diff --git a/fix_alpine_boot.sh b/fix_alpine_boot.sh new file mode 100755 index 0000000..3206532 --- /dev/null +++ b/fix_alpine_boot.sh @@ -0,0 +1,297 @@ +#!/bin/bash +# Fix Alpine Boot for Automatic Backup + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_status "Fixing Alpine Boot Configuration for Automatic Backup" +echo "========================================================" + +USB_DRIVE="/dev/sda" + +# Mount USB partitions +BOOT_MOUNT="/tmp/fix_boot_$$" +DATA_MOUNT="/tmp/fix_data_$$" + +mkdir -p "$BOOT_MOUNT" "$DATA_MOUNT" +sudo mount "${USB_DRIVE}1" "$BOOT_MOUNT" +sudo mount "${USB_DRIVE}2" "$DATA_MOUNT" + +print_status "Creating proper GRUB configuration..." + +# Create a better GRUB configuration +sudo tee "$BOOT_MOUNT/boot/grub/grub.cfg" > /dev/null << 'EOF' +set timeout=10 +set default=0 + +menuentry "Automatic System Backup" { + linux /boot/vmlinuz-lts modules=loop,squashfs,sd-mod,usb-storage quiet nomodeset console=tty0 alpine_dev=sda2:ext4 alpine_repo=http://dl-cdn.alpinelinux.org/alpine/v3.18/main/ modloop=/boot/modloop-lts apkovl=/backup-tools/alpine.apkovl.tar.gz autobackup=yes + initrd /boot/initramfs-lts +} + +menuentry "Manual Backup Shell" { + linux /boot/vmlinuz-lts modules=loop,squashfs,sd-mod,usb-storage console=tty0 alpine_dev=sda2:ext4 alpine_repo=http://dl-cdn.alpinelinux.org/alpine/v3.18/main/ modloop=/boot/modloop-lts apkovl=/backup-tools/alpine.apkovl.tar.gz + initrd /boot/initramfs-lts +} + +menuentry "Alpine Linux Recovery" { + linux /boot/vmlinuz-lts modules=loop,squashfs,sd-mod,usb-storage console=tty0 + initrd /boot/initramfs-lts +} +EOF + +print_status "Creating improved Alpine overlay..." + +# Create a new overlay with better startup script +OVERLAY_DIR="/tmp/overlay_$$" +mkdir -p "$OVERLAY_DIR"/{etc/init.d,etc/runlevels/default,usr/local/bin,root} + +# Create the main backup script +sudo tee "$OVERLAY_DIR/usr/local/bin/backup-system" > /dev/null << 'EOF' +#!/bin/sh +# Main backup system script + +export PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin + +clear +echo "========================================" +echo " BOOTABLE BACKUP SYSTEM" +echo "========================================" +echo + +# Function to detect drives +detect_drives() { + echo "Detecting drives..." + sleep 3 + + # Get all block devices + echo "Available drives:" + echo "==================" + lsblk -d -o NAME,SIZE,TYPE,TRAN | head -20 + echo + + # Auto-detect candidates + echo "Drive candidates:" + echo "Internal drives (non-USB):" + lsblk -d -n -o NAME,SIZE,TRAN | grep -v usb | grep -v sda | nl -v 1 + echo + echo "External drives (USB, excluding boot drive):" + lsblk -d -n -o NAME,SIZE,TRAN | grep usb | grep -v sda | nl -v 1 + echo +} + +# Function to get user choice +get_drives() { + # Source drive selection + echo "SELECT SOURCE DRIVE (internal drive to backup):" + echo "================================================" + INTERNAL_LIST=$(lsblk -d -n -o NAME | grep -v sda | grep -v loop) + echo "$INTERNAL_LIST" | nl -v 1 + echo + read -p "Enter source drive number or full path (e.g., /dev/nvme0n1): " SOURCE_INPUT + + if echo "$SOURCE_INPUT" | grep -q "^[0-9]"; then + SOURCE_DRIVE="/dev/$(echo "$INTERNAL_LIST" | sed -n "${SOURCE_INPUT}p")" + else + SOURCE_DRIVE="$SOURCE_INPUT" + fi + + # Target drive selection + echo + echo "SELECT TARGET DRIVE (external drive, will be OVERWRITTEN):" + echo "==========================================================" + EXTERNAL_LIST=$(lsblk -d -n -o NAME | grep -v sda | grep -v loop) + echo "$EXTERNAL_LIST" | nl -v 1 + echo + read -p "Enter target drive number or full path: " TARGET_INPUT + + if echo "$TARGET_INPUT" | grep -q "^[0-9]"; then + TARGET_DRIVE="/dev/$(echo "$EXTERNAL_LIST" | sed -n "${TARGET_INPUT}p")" + else + TARGET_DRIVE="$TARGET_INPUT" + fi +} + +# Function to confirm and backup +run_backup() { + echo + echo "BACKUP CONFIGURATION:" + echo "====================" + echo "Source: $SOURCE_DRIVE (will be copied FROM)" + echo "Target: $TARGET_DRIVE (will be OVERWRITTEN)" + echo + + # Show drive sizes + if [ -b "$SOURCE_DRIVE" ]; then + SOURCE_SIZE=$(blockdev --getsize64 "$SOURCE_DRIVE" 2>/dev/null || echo "unknown") + if [ "$SOURCE_SIZE" != "unknown" ]; then + SOURCE_GB=$((SOURCE_SIZE / 1024 / 1024 / 1024)) + echo "Source size: ${SOURCE_GB}GB" + fi + fi + + if [ -b "$TARGET_DRIVE" ]; then + TARGET_SIZE=$(blockdev --getsize64 "$TARGET_DRIVE" 2>/dev/null || echo "unknown") + if [ "$TARGET_SIZE" != "unknown" ]; then + TARGET_GB=$((TARGET_SIZE / 1024 / 1024 / 1024)) + echo "Target size: ${TARGET_GB}GB" + fi + fi + + echo + echo "⚠️ ALL DATA ON $TARGET_DRIVE WILL BE DESTROYED! ⚠️" + echo + read -p "Continue with backup? (yes/no): " CONFIRM + + if [ "$CONFIRM" != "yes" ]; then + echo "Backup cancelled" + return 1 + fi + + echo + echo "Starting backup..." + echo "==================" + + # Check if pv is available for progress + if command -v pv >/dev/null 2>&1; then + echo "Using pv for progress display" + dd if="$SOURCE_DRIVE" bs=4M 2>/dev/null | pv -s "$SOURCE_SIZE" | dd of="$TARGET_DRIVE" bs=4M conv=fdatasync 2>/dev/null + else + echo "Using dd with status=progress" + dd if="$SOURCE_DRIVE" of="$TARGET_DRIVE" bs=4M status=progress conv=fdatasync + fi + + RESULT=$? + + echo + if [ $RESULT -eq 0 ]; then + echo "========================================" + echo " BACKUP COMPLETED SUCCESSFULLY!" + echo "========================================" + else + echo "========================================" + echo " BACKUP FAILED!" + echo "========================================" + fi + + echo + echo "System will shutdown in 30 seconds..." + echo "(Press Ctrl+C to cancel)" + + # Countdown + for i in 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1; do + echo -n "$i " + sleep 1 + done + + echo + echo "Shutting down..." + poweroff +} + +# Main execution +detect_drives +get_drives +run_backup +EOF + +chmod +x "$OVERLAY_DIR/usr/local/bin/backup-system" + +# Create init script that runs on boot +sudo tee "$OVERLAY_DIR/etc/init.d/autobackup" > /dev/null << 'EOF' +#!/sbin/openrc-run + +name="autobackup" +description="Automatic backup system" + +depend() { + need localmount + after bootmisc +} + +start() { + # Check if autobackup parameter was passed + if grep -q "autobackup=yes" /proc/cmdline; then + ebegin "Starting automatic backup system" + + # Wait for devices to settle + sleep 5 + + # Run backup system + /usr/local/bin/backup-system + + eend $? + fi +} +EOF + +chmod +x "$OVERLAY_DIR/etc/init.d/autobackup" + +# Enable the service +ln -sf /etc/init.d/autobackup "$OVERLAY_DIR/etc/runlevels/default/autobackup" + +# Create profile for manual shell access +sudo tee "$OVERLAY_DIR/root/.profile" > /dev/null << 'EOF' +export PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin + +echo "========================================" +echo " BACKUP SYSTEM SHELL ACCESS" +echo "========================================" +echo +echo "Available commands:" +echo " backup-system - Start interactive backup" +echo " lsblk - List block devices" +echo " fdisk -l - List partitions" +echo +echo "Manual backup example:" +echo " dd if=/dev/nvme0n1 of=/dev/sdb bs=4M status=progress" +echo +EOF + +# Create the overlay archive +cd "$OVERLAY_DIR" +tar czf "$DATA_MOUNT/alpine.apkovl.tar.gz" * +cd - + +# Copy backup tools +SCRIPT_DIR=$(dirname "$(realpath "$0")") +sudo cp "$SCRIPT_DIR"/*.sh "$DATA_MOUNT/" 2>/dev/null || true + +# Cleanup +sudo umount "$BOOT_MOUNT" "$DATA_MOUNT" +rmdir "$BOOT_MOUNT" "$DATA_MOUNT" +rm -rf "$OVERLAY_DIR" + +print_success "Alpine boot configuration fixed!" +print_success "USB: $USB_DRIVE" +echo +print_success "Fixed Issues:" +print_success "βœ… Proper GRUB kernel parameters" +print_success "βœ… Correct Alpine overlay loading" +print_success "βœ… Automatic backup service startup" +print_success "βœ… Interactive drive selection" +print_success "βœ… Manual shell access option" +echo +print_warning "Now test by booting from USB and selecting 'Automatic System Backup'" diff --git a/fix_grub_boot.sh b/fix_grub_boot.sh new file mode 100755 index 0000000..69f4c48 --- /dev/null +++ b/fix_grub_boot.sh @@ -0,0 +1,274 @@ +#!/bin/bash + +# GRUB Boot Repair Script for LVM Migration +# Fixes GRUB bootloader issues after LVM migration to external M.2 drive + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +VG_NAME="system-vg" +EXTERNAL_DRIVE="/dev/sda" # Adjust if needed + +log() { + local message="[$(date '+%Y-%m-%d %H:%M:%S')] $1" + echo -e "${BLUE}$message${NC}" +} + +error() { + local message="[ERROR] $1" + echo -e "${RED}$message${NC}" >&2 + exit 1 +} + +warning() { + local message="[WARNING] $1" + echo -e "${YELLOW}$message${NC}" +} + +success() { + local message="[SUCCESS] $1" + echo -e "${GREEN}$message${NC}" +} + +confirm_action() { + echo -e "${YELLOW}$1${NC}" + read -p "Do you want to continue? [y/N] " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + error "Operation aborted by user" + fi +} + +check_prerequisites() { + log "Checking prerequisites for GRUB repair..." + + # Check if running as root + if [ "$EUID" -ne 0 ]; then + error "This script must be run as root. Use: sudo $0" + fi + + # Check if running from live system + local root_device=$(df / | tail -1 | awk '{print $1}') + if [[ "$root_device" != *"loop"* ]] && [[ "$root_device" != *"overlay"* ]] && [[ "$root_device" != *"tmpfs"* ]]; then + warning "This should be run from a live USB system for safety" + confirm_action "Continue anyway? (Not recommended if booting from the problematic system)" + fi + + # Check required tools + for tool in grub-install update-grub mount umount lvm; do + if ! command -v $tool >/dev/null 2>&1; then + error "$tool not found. Please install required packages." + fi + done + + success "Prerequisites check passed" +} + +detect_external_drive() { + log "Detecting external M.2 drive with LVM..." + + # Check if LVM volume group exists + if ! vgs "$VG_NAME" >/dev/null 2>&1; then + # Try to activate volume groups + vgchange -ay || warning "Could not activate volume groups" + + if ! vgs "$VG_NAME" >/dev/null 2>&1; then + error "Volume group '$VG_NAME' not found. Is the external drive connected?" + fi + fi + + # Display volume group information + log "Found volume group '$VG_NAME':" + vgs "$VG_NAME" + + # Check logical volumes + local required_lvs=("root" "home" "boot") + for lv in "${required_lvs[@]}"; do + if ! lvs "$VG_NAME/$lv" >/dev/null 2>&1; then + error "Logical volume '$VG_NAME/$lv' not found" + fi + done + + success "External LVM drive detected successfully" +} + +mount_external_system() { + log "Mounting external LVM system..." + + local mount_base="/mnt/ext" + mkdir -p "$mount_base" + + # Mount in correct order + log "Mounting root filesystem..." + mount "/dev/$VG_NAME/root" "$mount_base" || error "Failed to mount root" + + log "Mounting boot filesystem..." + mkdir -p "$mount_base/boot" + mount "/dev/$VG_NAME/boot" "$mount_base/boot" || error "Failed to mount boot" + + log "Mounting EFI partition..." + mkdir -p "$mount_base/boot/efi" + # Find EFI partition (usually first partition on external drive) + local efi_partition="${EXTERNAL_DRIVE}1" + if [ -b "$efi_partition" ]; then + mount "$efi_partition" "$mount_base/boot/efi" || error "Failed to mount EFI partition" + else + error "EFI partition $efi_partition not found" + fi + + log "Mounting home filesystem..." + mkdir -p "$mount_base/home" + mount "/dev/$VG_NAME/home" "$mount_base/home" || error "Failed to mount home" + + success "External system mounted at $mount_base" + echo "$mount_base" +} + +repair_grub() { + local mount_base="$1" + log "Repairing GRUB bootloader..." + + # Bind mount necessary filesystems for chroot + log "Setting up chroot environment..." + mount --bind /dev "$mount_base/dev" || error "Failed to bind /dev" + mount --bind /proc "$mount_base/proc" || error "Failed to bind /proc" + mount --bind /sys "$mount_base/sys" || error "Failed to bind /sys" + mount --bind /run "$mount_base/run" || error "Failed to bind /run" + + # Copy DNS resolution + cp /etc/resolv.conf "$mount_base/etc/resolv.conf" 2>/dev/null || warning "Could not copy DNS settings" + + log "Updating initramfs to include LVM support..." + chroot "$mount_base" /bin/bash -c " + # Ensure LVM is in initramfs + echo 'GRUB_ENABLE_CRYPTODISK=y' >> /etc/default/grub 2>/dev/null || true + + # Update initramfs with LVM support + update-initramfs -u -k all + + echo 'Initramfs updated successfully' + " || warning "Initramfs update had issues" + + log "Reinstalling GRUB bootloader..." + chroot "$mount_base" /bin/bash -c " + # Install GRUB to the external drive + grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck '$EXTERNAL_DRIVE' + + echo 'GRUB installed successfully' + " || error "GRUB installation failed" + + log "Updating GRUB configuration..." + chroot "$mount_base" /bin/bash -c " + # Update GRUB configuration + update-grub + + echo 'GRUB configuration updated successfully' + " || error "GRUB configuration update failed" + + success "GRUB repair completed" +} + +check_grub_files() { + local mount_base="$1" + log "Checking GRUB installation..." + + # Check EFI bootloader + if [ -f "$mount_base/boot/efi/EFI/debian/grubx64.efi" ]; then + success "GRUB EFI bootloader found" + else + warning "GRUB EFI bootloader missing" + fi + + # Check GRUB configuration + if [ -f "$mount_base/boot/grub/grub.cfg" ]; then + success "GRUB configuration found" + + # Check if configuration mentions LVM + if grep -q "$VG_NAME" "$mount_base/boot/grub/grub.cfg"; then + success "GRUB configuration includes LVM volumes" + else + warning "GRUB configuration may not include LVM setup" + fi + else + error "GRUB configuration missing" + fi +} + +cleanup_mounts() { + local mount_base="$1" + log "Cleaning up mount points..." + + # Unmount in reverse order + umount "$mount_base/run" 2>/dev/null || true + umount "$mount_base/sys" 2>/dev/null || true + umount "$mount_base/proc" 2>/dev/null || true + umount "$mount_base/dev" 2>/dev/null || true + umount "$mount_base/home" 2>/dev/null || true + umount "$mount_base/boot/efi" 2>/dev/null || true + umount "$mount_base/boot" 2>/dev/null || true + umount "$mount_base" 2>/dev/null || true + + # Remove mount directory + rmdir "$mount_base" 2>/dev/null || true + + success "Mount points cleaned up" +} + +show_next_steps() { + echo + echo -e "${GREEN}=== GRUB Repair Completed ===${NC}" + echo + echo -e "${BLUE}Next steps:${NC}" + echo "1. Reboot your system" + echo "2. Enter BIOS/UEFI settings" + echo "3. Ensure boot order prioritizes the external M.2 SSD" + echo "4. Look for 'debian' entry in the boot menu" + echo "5. Boot from the external drive" + echo + echo -e "${YELLOW}If boot still fails:${NC}" + echo "β€’ Check BIOS/UEFI settings for Secure Boot (disable if necessary)" + echo "β€’ Verify UEFI mode is enabled (not Legacy/CSM)" + echo "β€’ Try different USB ports if using external enclosure" + echo "β€’ Run this script again from live USB if needed" + echo + echo -e "${GREEN}The system should now boot properly from the external M.2!${NC}" +} + +main() { + echo -e "${GREEN}=== GRUB Boot Repair for LVM Migration ===${NC}" + echo "This script repairs GRUB bootloader issues after LVM migration" + echo + + check_prerequisites + detect_external_drive + + echo + echo -e "${YELLOW}This will repair GRUB on the external M.2 drive${NC}" + echo "Drive: $EXTERNAL_DRIVE" + echo "Volume Group: $VG_NAME" + echo + confirm_action "Proceed with GRUB repair?" + + local mount_base=$(mount_external_system) + + # Set trap to ensure cleanup + trap "cleanup_mounts '$mount_base'" EXIT + + repair_grub "$mount_base" + check_grub_files "$mount_base" + cleanup_mounts "$mount_base" + + # Clear trap since we cleaned up successfully + trap - EXIT + + show_next_steps +} + +main "$@" \ No newline at end of file diff --git a/fix_grub_lvm_boot.sh b/fix_grub_lvm_boot.sh new file mode 100755 index 0000000..42475c3 --- /dev/null +++ b/fix_grub_lvm_boot.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +# Fix GRUB Boot for LVM System - External M.2 +# This script fixes GRUB configuration to boot from the external LVM system instead of internal drive + +set -e + +LOG_FILE="/tmp/grub_fix_$(date +%Y%m%d_%H%M%S).log" +EXTERNAL_ROOT="/tmp/external-root" +EXTERNAL_BOOT="/tmp/external-boot" + +echo "πŸ”§ GRUB LVM Boot Fix - Starting at $(date)" | tee "$LOG_FILE" +echo "==========================================" | tee -a "$LOG_FILE" + +# Function to cleanup mounts on exit +cleanup() { + echo "🧹 Cleaning up mounts..." | tee -a "$LOG_FILE" + sudo umount "$EXTERNAL_ROOT/proc" 2>/dev/null || true + sudo umount "$EXTERNAL_ROOT/sys" 2>/dev/null || true + sudo umount "$EXTERNAL_ROOT/dev/pts" 2>/dev/null || true + sudo umount "$EXTERNAL_ROOT/dev" 2>/dev/null || true + sudo umount "$EXTERNAL_ROOT/boot" 2>/dev/null || true + sudo umount "$EXTERNAL_ROOT" 2>/dev/null || true + sudo umount "$EXTERNAL_BOOT" 2>/dev/null || true + sudo rmdir "$EXTERNAL_ROOT" "$EXTERNAL_BOOT" 2>/dev/null || true +} +trap cleanup EXIT + +# Check if LVM is active +echo "πŸ“‹ Checking LVM status..." | tee -a "$LOG_FILE" +if ! sudo lvs system-vg/root &>/dev/null; then + echo "❌ LVM system-vg/root not found. Please ensure the external M.2 is connected." | tee -a "$LOG_FILE" + exit 1 +fi + +# Create mount points +sudo mkdir -p "$EXTERNAL_ROOT" "$EXTERNAL_BOOT" + +# Mount the external LVM system +echo "πŸ’Ύ Mounting external LVM system..." | tee -a "$LOG_FILE" +sudo mount /dev/system-vg/root "$EXTERNAL_ROOT" | tee -a "$LOG_FILE" +sudo mount /dev/system-vg/boot "$EXTERNAL_ROOT/boot" | tee -a "$LOG_FILE" + +# Bind mount system directories for chroot +echo "πŸ”— Setting up chroot environment..." | tee -a "$LOG_FILE" +sudo mount --bind /proc "$EXTERNAL_ROOT/proc" +sudo mount --bind /sys "$EXTERNAL_ROOT/sys" +sudo mount --bind /dev "$EXTERNAL_ROOT/dev" +sudo mount --bind /dev/pts "$EXTERNAL_ROOT/dev/pts" + +# Show current GRUB configuration issue +echo "πŸ” Current GRUB issue:" | tee -a "$LOG_FILE" +if sudo mount /dev/system-vg/boot "$EXTERNAL_BOOT" 2>/dev/null; then + WRONG_ENTRIES=$(sudo grep -c "nvme0n1p1" "$EXTERNAL_BOOT/grub/grub.cfg" 2>/dev/null || echo "0") + echo " - Found $WRONG_ENTRIES entries pointing to internal drive (nvme0n1p1)" | tee -a "$LOG_FILE" + sudo umount "$EXTERNAL_BOOT" +fi + +# Get the correct UUID for the LVM root +ROOT_UUID=$(sudo blkid /dev/system-vg/root | grep -o 'UUID="[^"]*"' | cut -d'"' -f2) +echo " - External LVM root UUID: $ROOT_UUID" | tee -a "$LOG_FILE" + +# Update GRUB configuration from within the external system +echo "πŸ”„ Regenerating GRUB configuration..." | tee -a "$LOG_FILE" + +# First, ensure EFI partition is mounted in the chroot +EFI_PARTITION=$(lsblk -rno MOUNTPOINT,FSTYPE | grep vfat | head -1 | cut -d' ' -f1) +if [ -n "$EFI_PARTITION" ]; then + echo "πŸ“ Mounting EFI partition: $EFI_PARTITION" | tee -a "$LOG_FILE" + sudo mkdir -p "$EXTERNAL_ROOT/boot/efi" + sudo mount "$EFI_PARTITION" "$EXTERNAL_ROOT/boot/efi" || true +fi + +# Update fstab to reflect LVM UUIDs +echo "πŸ“ Updating /etc/fstab with correct UUIDs..." | tee -a "$LOG_FILE" +sudo chroot "$EXTERNAL_ROOT" bash -c " + # Get UUIDs for all LVM volumes + ROOT_UUID=\$(blkid /dev/system-vg/root | grep -o 'UUID=\"[^\"]*\"' | cut -d'\"' -f2) + BOOT_UUID=\$(blkid /dev/system-vg/boot | grep -o 'UUID=\"[^\"]*\"' | cut -d'\"' -f2) + HOME_UUID=\$(blkid /dev/system-vg/home | grep -o 'UUID=\"[^\"]*\"' | cut -d'\"' -f2) + SWAP_UUID=\$(blkid /dev/system-vg/swap | grep -o 'UUID=\"[^\"]*\"' | cut -d'\"' -f2) + + # Backup original fstab + cp /etc/fstab /etc/fstab.backup + + # Create new fstab with LVM UUIDs + cat > /etc/fstab << EOF +# LVM-based fstab generated by fix_grub_lvm_boot.sh +UUID=\${ROOT_UUID} / ext4 errors=remount-ro 0 1 +UUID=\${BOOT_UUID} /boot ext4 defaults 0 2 +UUID=\${HOME_UUID} /home ext4 defaults 0 2 +UUID=\${SWAP_UUID} none swap sw 0 0 +# EFI System Partition +/dev/disk/by-label/SYSTEM-EFI /boot/efi vfat umask=0077 0 1 +EOF + + echo 'βœ… Updated /etc/fstab with LVM UUIDs' +" + +# Install GRUB to EFI +echo "πŸ› οΈ Installing GRUB to EFI..." | tee -a "$LOG_FILE" +sudo chroot "$EXTERNAL_ROOT" bash -c " + grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu-lvm --recheck 2>&1 + echo 'βœ… GRUB installed to EFI' +" + +# Update GRUB configuration +echo "πŸ“ Updating GRUB configuration..." | tee -a "$LOG_FILE" +sudo chroot "$EXTERNAL_ROOT" bash -c " + update-grub 2>&1 + echo 'βœ… GRUB configuration updated' +" + +# Verify the fix +echo "βœ… Verifying GRUB fix..." | tee -a "$LOG_FILE" +sudo mount /dev/system-vg/boot "$EXTERNAL_BOOT" +NEW_ENTRIES=$(sudo grep -c "system-vg-root" "$EXTERNAL_BOOT/grub/grub.cfg" 2>/dev/null || echo "0") +OLD_ENTRIES=$(sudo grep -c "nvme0n1p1" "$EXTERNAL_BOOT/grub/grub.cfg" 2>/dev/null || echo "0") + +echo " - LVM entries found: $NEW_ENTRIES" | tee -a "$LOG_FILE" +echo " - Internal drive entries: $OLD_ENTRIES" | tee -a "$LOG_FILE" + +if [ "$NEW_ENTRIES" -gt 0 ]; then + echo "βœ… SUCCESS: GRUB now configured to boot from external LVM system!" | tee -a "$LOG_FILE" + echo "" | tee -a "$LOG_FILE" + echo "πŸš€ Next steps:" | tee -a "$LOG_FILE" + echo " 1. Reboot your system" | tee -a "$LOG_FILE" + echo " 2. In GRUB menu, select 'ubuntu-lvm' or Ubuntu entries" | tee -a "$LOG_FILE" + echo " 3. System should now boot from external M.2 LVM" | tee -a "$LOG_FILE" + echo "" | tee -a "$LOG_FILE" + echo "πŸ“‹ Log saved to: $LOG_FILE" +else + echo "⚠️ Warning: No LVM entries found in new GRUB config. Manual check required." | tee -a "$LOG_FILE" + echo " Check $LOG_FILE for details." +fi + +echo "" | tee -a "$LOG_FILE" +echo "πŸ”§ GRUB LVM Boot Fix completed at $(date)" | tee -a "$LOG_FILE" \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..1350141 --- /dev/null +++ b/install.sh @@ -0,0 +1,204 @@ +#!/bin/bash +# Installation script for System Backup Tool + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if running as root +check_root() { + if [[ $EUID -eq 0 ]]; then + print_error "Do not run this installer as root. It will use sudo when needed." + exit 1 + fi +} + +# Check dependencies +check_dependencies() { + print_status "Checking dependencies..." + + # Check Python 3 + if ! command -v python3 &> /dev/null; then + print_error "Python 3 is required but not installed" + exit 1 + fi + + # Check tkinter + if ! python3 -c "import tkinter" &> /dev/null; then + print_warning "tkinter not available. Installing python3-tk..." + sudo apt update + sudo apt install -y python3-tk + fi + + # Check for pv (progress viewer) + if ! command -v pv &> /dev/null; then + print_warning "pv (progress viewer) not found. Installing..." + sudo apt install -y pv + fi + + print_success "All dependencies satisfied" +} + +# Make scripts executable +setup_permissions() { + print_status "Setting up permissions..." + + chmod +x backup_manager.py + chmod +x backup_script.sh + + print_success "Permissions set" +} + +# Create desktop entry +create_desktop_entry() { + print_status "Creating desktop entry..." + + local desktop_dir="$HOME/Desktop" + local applications_dir="$HOME/.local/share/applications" + local script_path=$(realpath backup_manager.py) + local icon_path=$(realpath ".") + + # Create applications directory if it doesn't exist + mkdir -p "$applications_dir" + + # Create desktop entry content + local desktop_content="[Desktop Entry] +Version=1.0 +Type=Application +Name=System Backup Manager +Comment=Clone internal drive to external M.2 SSD +Exec=python3 '$script_path' +Icon=drive-harddisk +Terminal=false +Categories=System;Utility; +StartupNotify=true" + + # Create desktop file in applications + echo "$desktop_content" > "$applications_dir/system-backup.desktop" + chmod +x "$applications_dir/system-backup.desktop" + + # Create desktop shortcut if Desktop directory exists + if [[ -d "$desktop_dir" ]]; then + echo "$desktop_content" > "$desktop_dir/System Backup.desktop" + chmod +x "$desktop_dir/System Backup.desktop" + print_success "Desktop shortcut created" + fi + + print_success "Application entry created" +} + +# Create systemd service (optional) +create_systemd_service() { + local service_dir="systemd" + local script_path=$(realpath backup_script.sh) + + print_status "Creating systemd service template..." + + mkdir -p "$service_dir" + + cat > "$service_dir/backup-service.service" << EOF +[Unit] +Description=System Backup Service +After=multi-user.target + +[Service] +Type=oneshot +ExecStart=$script_path --target /dev/sdb +User=root +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + + cat > "$service_dir/README.md" << EOF +# Systemd Service for Backup + +To install the systemd service: + +1. Edit the service file to specify your target drive +2. Copy to systemd directory: + \`\`\`bash + sudo cp backup-service.service /etc/systemd/system/ + \`\`\` + +3. Enable and start: + \`\`\`bash + sudo systemctl daemon-reload + sudo systemctl enable backup-service + sudo systemctl start backup-service + \`\`\` + +4. Check status: + \`\`\`bash + sudo systemctl status backup-service + \`\`\` +EOF + + print_success "Systemd service template created in systemd/" +} + +# Create log directory +setup_logging() { + print_status "Setting up logging..." + + # Create log file with proper permissions + sudo touch /var/log/system_backup.log + sudo chmod 666 /var/log/system_backup.log + + print_success "Log file created: /var/log/system_backup.log" +} + +# Main installation function +main() { + echo "" + echo "======================================" + echo " System Backup Tool Installer" + echo "======================================" + echo "" + + check_root + check_dependencies + setup_permissions + setup_logging + create_desktop_entry + create_systemd_service + + echo "" + print_success "Installation completed successfully!" + echo "" + echo "You can now:" + echo " β€’ Launch GUI: python3 backup_manager.py" + echo " β€’ Use CLI: ./backup_script.sh --help" + echo " β€’ Click desktop icon: System Backup Manager" + echo "" + print_warning "Remember to run the backup tool with appropriate privileges" + print_warning "Always verify your drive selections before starting backup" + echo "" +} + +# Run installer +main "$@" diff --git a/lvm_snapshot_backup.sh b/lvm_snapshot_backup.sh new file mode 100755 index 0000000..5c137a0 --- /dev/null +++ b/lvm_snapshot_backup.sh @@ -0,0 +1,409 @@ +#!/bin/bash + +# Enhanced LVM Snapshot Backup System +# Integrates LVM snapshots with the existing backup system for M.2 external drives + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +VG_NAME="system-vg" +SNAPSHOT_SIZE="10G" +BACKUP_BASE_DIR="/mnt/backup" +EXTERNAL_BACKUP_DRIVE="" # Will be auto-detected +LOG_FILE="/var/log/lvm-snapshot-backup.log" + +# Snapshot names +ROOT_SNAPSHOT="root-snapshot" +HOME_SNAPSHOT="home-snapshot" +BOOT_SNAPSHOT="boot-snapshot" + +log() { + local message="[$(date '+%Y-%m-%d %H:%M:%S')] $1" + echo -e "${BLUE}$message${NC}" + echo "$message" >> "$LOG_FILE" +} + +error() { + local message="[ERROR] $1" + echo -e "${RED}$message${NC}" >&2 + echo "$message" >> "$LOG_FILE" + exit 1 +} + +warning() { + local message="[WARNING] $1" + echo -e "${YELLOW}$message${NC}" + echo "$message" >> "$LOG_FILE" +} + +success() { + local message="[SUCCESS] $1" + echo -e "${GREEN}$message${NC}" + echo "$message" >> "$LOG_FILE" +} + +check_lvm_system() { + log "Checking LVM system status..." + + # Check if we're running on an LVM system + if ! command -v lvm >/dev/null 2>&1; then + error "LVM tools not found. This system may not support LVM." + fi + + # Check if volume group exists + if ! vgs "$VG_NAME" >/dev/null 2>&1; then + error "Volume group '$VG_NAME' not found. Are you running on the migrated LVM system?" + fi + + # Check available space for snapshots + local vg_free=$(vgs --noheadings -o vg_free --units g "$VG_NAME" | tr -d ' G') + local snapshot_size_num=$(echo "$SNAPSHOT_SIZE" | tr -d 'G') + local required_space=$((snapshot_size_num * 3)) # 3 snapshots + + if (( $(echo "$vg_free < $required_space" | bc -l) )); then + warning "Low free space in volume group ($vg_free GB available, $required_space GB recommended)" + echo "Consider reducing snapshot size or extending volume group" + fi + + success "LVM system check passed" +} + +detect_backup_drive() { + log "Detecting external backup drive..." + + # Look for drives that are not the current root drive + local root_drive=$(lsblk -no PKNAME $(findmnt -no SOURCE /)) + local available_drives=($(lsblk -dpno NAME | grep -v "$root_drive")) + + if [ ${#available_drives[@]} -eq 0 ]; then + error "No external drives found for backup" + elif [ ${#available_drives[@]} -eq 1 ]; then + EXTERNAL_BACKUP_DRIVE="${available_drives[0]}" + log "Auto-selected backup drive: $EXTERNAL_BACKUP_DRIVE" + else + echo "Multiple external drives found:" + for i in "${!available_drives[@]}"; do + local drive="${available_drives[$i]}" + local info=$(lsblk -no SIZE,MODEL "$drive" | head -1) + echo "$((i+1)). $drive - $info" + done + + read -p "Select backup drive [1-${#available_drives[@]}]: " choice + EXTERNAL_BACKUP_DRIVE="${available_drives[$((choice-1))]}" + fi + + success "Selected backup drive: $EXTERNAL_BACKUP_DRIVE" +} + +create_snapshots() { + log "Creating LVM snapshots..." + + # Remove existing snapshots if they exist + remove_snapshots_quiet + + # Create snapshots for each logical volume + local volumes=("root" "home" "boot") + local snapshots=("$ROOT_SNAPSHOT" "$HOME_SNAPSHOT" "$BOOT_SNAPSHOT") + + for i in "${!volumes[@]}"; do + local volume="${volumes[$i]}" + local snapshot="${snapshots[$i]}" + + if lvs "$VG_NAME/$volume" >/dev/null 2>&1; then + log "Creating snapshot: $snapshot for $volume" + lvcreate -L "$SNAPSHOT_SIZE" -s -n "$snapshot" "/dev/$VG_NAME/$volume" || { + error "Failed to create snapshot $snapshot" + } + success "Created snapshot: $snapshot" + else + warning "Logical volume $volume not found, skipping" + fi + done + + success "All snapshots created successfully" +} + +mount_snapshots() { + log "Mounting snapshots for backup..." + + # Create mount points + mkdir -p "$BACKUP_BASE_DIR"/{root,home,boot} + + # Mount snapshots + local snapshots=("$ROOT_SNAPSHOT" "$HOME_SNAPSHOT" "$BOOT_SNAPSHOT") + local mount_points=("root" "home" "boot") + + for i in "${!snapshots[@]}"; do + local snapshot="${snapshots[$i]}" + local mount_point="$BACKUP_BASE_DIR/${mount_points[$i]}" + + if [ -e "/dev/$VG_NAME/$snapshot" ]; then + log "Mounting /dev/$VG_NAME/$snapshot to $mount_point" + mount "/dev/$VG_NAME/$snapshot" "$mount_point" || { + warning "Failed to mount snapshot $snapshot" + } + fi + done + + success "Snapshots mounted at $BACKUP_BASE_DIR" +} + +backup_to_external() { + log "Backing up snapshots to external drive..." + + if [ -z "$EXTERNAL_BACKUP_DRIVE" ]; then + detect_backup_drive + fi + + # Create backup directory on external drive + local external_mount="/mnt/external-backup" + mkdir -p "$external_mount" + + # Mount external drive (assume first partition) + mount "${EXTERNAL_BACKUP_DRIVE}1" "$external_mount" 2>/dev/null || { + # Try the whole drive if partition doesn't work + mount "$EXTERNAL_BACKUP_DRIVE" "$external_mount" || { + error "Failed to mount external backup drive" + } + } + + # Create timestamped backup directory + local backup_timestamp=$(date '+%Y%m%d_%H%M%S') + local backup_dir="$external_mount/lvm-snapshots/$backup_timestamp" + mkdir -p "$backup_dir" + + # Backup each mounted snapshot + local mount_points=("root" "home" "boot") + for mount_point in "${mount_points[@]}"; do + local source="$BACKUP_BASE_DIR/$mount_point" + local target="$backup_dir/$mount_point" + + if mountpoint -q "$source"; then + log "Backing up $mount_point to external drive..." + mkdir -p "$target" + rsync -avxHAX --progress "$source/" "$target/" || { + warning "Backup of $mount_point failed" + } + else + warning "Snapshot $mount_point not mounted, skipping" + fi + done + + # Create backup metadata + cat > "$backup_dir/backup-info.txt" << EOF +LVM Snapshot Backup Information +=============================== +Backup Date: $(date) +Source System: $(hostname) +Volume Group: $VG_NAME +Snapshot Size: $SNAPSHOT_SIZE +Backup Location: $backup_dir + +Logical Volumes: +$(lvs $VG_NAME) + +Volume Group Info: +$(vgs $VG_NAME) + +Physical Volumes: +$(pvs) +EOF + + # Unmount external drive + umount "$external_mount" + + success "Backup completed to $backup_dir" +} + +remove_snapshots_quiet() { + # Remove snapshots without error messages (used internally) + lvremove -f "/dev/$VG_NAME/$ROOT_SNAPSHOT" 2>/dev/null || true + lvremove -f "/dev/$VG_NAME/$HOME_SNAPSHOT" 2>/dev/null || true + lvremove -f "/dev/$VG_NAME/$BOOT_SNAPSHOT" 2>/dev/null || true +} + +remove_snapshots() { + log "Cleaning up snapshots..." + + # Unmount snapshots + umount "$BACKUP_BASE_DIR/root" 2>/dev/null || true + umount "$BACKUP_BASE_DIR/home" 2>/dev/null || true + umount "$BACKUP_BASE_DIR/boot" 2>/dev/null || true + + # Remove logical volumes + remove_snapshots_quiet + + success "Snapshots removed" +} + +list_snapshots() { + echo "Current LVM snapshots:" + lvs "$VG_NAME" | grep -E "(snapshot|Snap%)" || echo "No snapshots found" +} + +show_status() { + echo -e "${GREEN}=== LVM Snapshot Backup Status ===${NC}" + echo + + echo "Volume Group Information:" + vgs "$VG_NAME" 2>/dev/null || echo "Volume group not found" + echo + + echo "Logical Volumes:" + lvs "$VG_NAME" 2>/dev/null || echo "No logical volumes found" + echo + + echo "Current Snapshots:" + list_snapshots + echo + + echo "Mount Status:" + if mountpoint -q "$BACKUP_BASE_DIR/root" 2>/dev/null; then + echo " Root snapshot: mounted at $BACKUP_BASE_DIR/root" + else + echo " Root snapshot: not mounted" + fi + + if mountpoint -q "$BACKUP_BASE_DIR/home" 2>/dev/null; then + echo " Home snapshot: mounted at $BACKUP_BASE_DIR/home" + else + echo " Home snapshot: not mounted" + fi + + if mountpoint -q "$BACKUP_BASE_DIR/boot" 2>/dev/null; then + echo " Boot snapshot: mounted at $BACKUP_BASE_DIR/boot" + else + echo " Boot snapshot: not mounted" + fi +} + +integrate_with_backup_system() { + log "Integrating with existing backup system..." + + # Check if main backup system exists + if [ -f "./backup_script.sh" ]; then + log "Found main backup system - creating integration" + + # Create wrapper script that combines snapshot and full backup + cat > "./lvm_integrated_backup.sh" << 'EOF' +#!/bin/bash + +# Integrated LVM + Full System Backup +# Combines LVM snapshots with the main backup system + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "=== Integrated LVM Backup System ===" +echo "1. LVM Snapshot Backup (fast, consistent)" +echo "2. Full System Backup (complete clone)" +echo "3. Combined Backup (snapshots + clone)" +echo + +read -p "Select backup type [1-3]: " choice + +case $choice in + 1) + echo "Performing LVM snapshot backup..." + sudo "$SCRIPT_DIR/lvm_snapshot_backup.sh" backup + ;; + 2) + echo "Performing full system backup..." + sudo "$SCRIPT_DIR/backup_script.sh" + ;; + 3) + echo "Performing combined backup..." + echo "Step 1: LVM snapshots..." + sudo "$SCRIPT_DIR/lvm_snapshot_backup.sh" backup + echo "Step 2: Full system clone..." + sudo "$SCRIPT_DIR/backup_script.sh" --sync + ;; + *) + echo "Invalid choice" + exit 1 + ;; +esac +EOF + chmod +x "./lvm_integrated_backup.sh" + success "Created integrated backup script: lvm_integrated_backup.sh" + else + warning "Main backup system not found in current directory" + fi +} + +show_usage() { + echo "Usage: $0 {create|mount|remove|backup|external|status|list|integrate}" + echo + echo "Commands:" + echo " create - Create LVM snapshots only" + echo " mount - Mount existing snapshots for access" + echo " remove - Remove snapshots and unmount" + echo " backup - Create snapshots and mount (ready for backup)" + echo " external - Full backup to external drive" + echo " status - Show current LVM and snapshot status" + echo " list - List current snapshots" + echo " integrate - Integrate with existing backup system" + echo + echo "Examples:" + echo " $0 backup # Create and mount snapshots" + echo " $0 external # Full backup to external drive" + echo " $0 remove # Clean up when backup complete" +} + +main() { + # Ensure log file exists and is writable + sudo touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/tmp/lvm-backup.log" + + case "${1:-}" in + create) + check_lvm_system + create_snapshots + ;; + mount) + mount_snapshots + ;; + remove) + remove_snapshots + ;; + backup) + check_lvm_system + create_snapshots + mount_snapshots + echo "Snapshots ready for backup at $BACKUP_BASE_DIR" + echo "Run '$0 remove' when backup is complete" + ;; + external) + check_lvm_system + create_snapshots + mount_snapshots + backup_to_external + remove_snapshots + ;; + status) + show_status + ;; + list) + list_snapshots + ;; + integrate) + integrate_with_backup_system + ;; + *) + show_usage + exit 1 + ;; + esac +} + +# Check if running as root for LVM operations +if [ "$EUID" -ne 0 ] && [[ "$1" =~ ^(create|mount|remove|backup|external)$ ]]; then + error "LVM operations require root privileges. Use: sudo $0 $1" +fi + +main "$@" \ No newline at end of file diff --git a/migrate_to_lvm.sh b/migrate_to_lvm.sh new file mode 100755 index 0000000..f45129e --- /dev/null +++ b/migrate_to_lvm.sh @@ -0,0 +1,969 @@ +#!/bin/bash + +# Migration Script: Non-LVM to LVM System Migration +# This script migrates a running system to an external M.2 SSD with LVM support +# MUST BE RUN FROM A LIVE USB SYSTEM - NOT FROM THE SYSTEM BEING MIGRATED + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration variables (will be auto-detected) +INTERNAL_DRIVE="" +EXTERNAL_DRIVE="" +VG_NAME="system-vg" +ROOT_LV="root" +HOME_LV="home" +SWAP_LV="swap" +BOOT_LV="boot" + +# Size configurations (will be calculated based on source sizes) +ROOT_SIZE="" +HOME_SIZE="" +SWAP_SIZE="" +BOOT_SIZE="" + +# Detected partitions and filesystems +declare -A INTERNAL_PARTITIONS +declare -A PARTITION_MOUNTS +declare -A PARTITION_FILESYSTEMS +declare -A PARTITION_SIZES + +# Mount points +WORK_DIR="/mnt/migration" +INTERNAL_ROOT_MOUNT="$WORK_DIR/internal_root" +INTERNAL_HOME_MOUNT="$WORK_DIR/internal_home" +INTERNAL_BOOT_MOUNT="$WORK_DIR/internal_boot" +EXTERNAL_ROOT_MOUNT="$WORK_DIR/external_root" +EXTERNAL_HOME_MOUNT="$WORK_DIR/external_home" +EXTERNAL_BOOT_MOUNT="$WORK_DIR/external_boot" + +log() { + echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 + exit 1 +} + +warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +confirm_action() { + echo -e "${YELLOW}$1${NC}" + read -p "Do you want to continue? [y/N] " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + error "Operation aborted by user" + fi +} + +detect_drives() { + log "Detecting available drives..." + + # Find all block devices that are disks (not partitions), excluding the live USB + local all_drives=($(lsblk -dpno NAME,TYPE,SIZE,MODEL | grep "disk" | awk '{print $1}')) + local drives=() + + # Filter out the USB stick we're running from (if running from live system) + for drive in "${all_drives[@]}"; do + # Check if this drive contains the live system + if mount | grep -q "$drive" && mount | grep -q "/lib/live\|/media.*MIGRATION_TOOLS"; then + log "Excluding live USB drive: $drive" + continue + fi + drives+=("$drive") + done + + if [ ${#drives[@]} -lt 2 ]; then + error "Need at least 2 drives for migration. Found only ${#drives[@]} suitable drives" + echo "Available drives:" + for drive in "${all_drives[@]}"; do + local info=$(lsblk -dpno NAME,SIZE,MODEL "$drive" | awk '{print $2 " " $3}') + echo " $drive - $info" + done + exit 1 + fi + + echo + echo "Available drives for migration:" + for i in "${!drives[@]}"; do + local drive="${drives[$i]}" + local info=$(lsblk -dpno NAME,SIZE,MODEL "$drive" | awk '{print $2 " " $3}' | xargs) + local is_usb="" + + # Check if it's a USB drive + if udevadm info --query=property --name="$drive" 2>/dev/null | grep -q "ID_BUS=usb"; then + is_usb=" (USB)" + fi + + echo "$((i+1)). $drive - $info$is_usb" + done + + echo + echo "Please identify your drives:" + echo "- Internal drive: Usually NVMe (like nvme0n1) or first SATA (like sda)" + echo "- External M.2: Usually USB-connected, larger capacity" + echo + + # Auto-detection with user confirmation + local suggested_internal="" + local suggested_external="" + + # Try to suggest internal drive (prefer NVMe, then non-USB drives) + for drive in "${drives[@]}"; do + if [[ "$drive" == *"nvme"* ]]; then + suggested_internal="$drive" + break + fi + done + + if [ -z "$suggested_internal" ]; then + # If no NVMe, prefer non-USB drives + for drive in "${drives[@]}"; do + if ! udevadm info --query=property --name="$drive" 2>/dev/null | grep -q "ID_BUS=usb"; then + suggested_internal="$drive" + break + fi + done + fi + + # Try to suggest external drive (prefer USB, larger capacity) + for drive in "${drives[@]}"; do + if [ "$drive" != "$suggested_internal" ]; then + if udevadm info --query=property --name="$drive" 2>/dev/null | grep -q "ID_BUS=usb"; then + suggested_external="$drive" + break + fi + fi + done + + if [ -z "$suggested_external" ]; then + # If no USB found, use the other drive + for drive in "${drives[@]}"; do + if [ "$drive" != "$suggested_internal" ]; then + suggested_external="$drive" + break + fi + done + fi + + # Show suggestions and get user confirmation + if [ -n "$suggested_internal" ] && [ -n "$suggested_external" ]; then + echo "Suggested configuration:" + local internal_info=$(lsblk -dpno SIZE,MODEL "$suggested_internal" | xargs) + local external_info=$(lsblk -dpno SIZE,MODEL "$suggested_external" | xargs) + echo " Internal (source): $suggested_internal - $internal_info" + echo " External (target): $suggested_external - $external_info" + echo + read -p "Use this configuration? [Y/n]: " -n 1 -r + echo + if [[ $REPLY =~ ^[Nn]$ ]]; then + # Manual selection + INTERNAL_DRIVE="" + EXTERNAL_DRIVE="" + else + INTERNAL_DRIVE="$suggested_internal" + EXTERNAL_DRIVE="$suggested_external" + fi + fi + + # Manual selection if auto-detection failed or user declined + if [ -z "$INTERNAL_DRIVE" ]; then + echo "Select INTERNAL drive (source - your current system):" + for i in "${!drives[@]}"; do + local drive="${drives[$i]}" + local info=$(lsblk -dpno SIZE,MODEL "$drive" | xargs) + echo "$((i+1)). $drive - $info" + done + read -p "Enter number [1-${#drives[@]}]: " choice + if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#drives[@]}" ]; then + INTERNAL_DRIVE="${drives[$((choice-1))]}" + else + error "Invalid selection" + fi + fi + + if [ -z "$EXTERNAL_DRIVE" ]; then + echo + echo "Select EXTERNAL drive (target - will be wiped!):" + for i in "${!drives[@]}"; do + local drive="${drives[$i]}" + if [ "$drive" != "$INTERNAL_DRIVE" ]; then + local info=$(lsblk -dpno SIZE,MODEL "$drive" | xargs) + echo "$((i+1)). $drive - $info" + fi + done + read -p "Enter number [1-${#drives[@]}]: " choice + if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#drives[@]}" ]; then + local selected_drive="${drives[$((choice-1))]}" + if [ "$selected_drive" != "$INTERNAL_DRIVE" ]; then + EXTERNAL_DRIVE="$selected_drive" + else + error "Cannot use the same drive as both source and target!" + fi + else + error "Invalid selection" + fi + fi + + # Final validation + if [ "$INTERNAL_DRIVE" = "$EXTERNAL_DRIVE" ]; then + error "Internal and external drives cannot be the same!" + fi + + echo + echo "Final drive selection:" + echo " Internal (source): $INTERNAL_DRIVE ($(lsblk -dpno SIZE,MODEL "$INTERNAL_DRIVE" | xargs))" + echo " External (target): $EXTERNAL_DRIVE ($(lsblk -dpno SIZE,MODEL "$EXTERNAL_DRIVE" | xargs))" + + success "Drive detection completed" + + # Additional safety check - confirm the target drive + echo + echo -e "${RED}⚠️ FINAL SAFETY CHECK ⚠️${NC}" + echo "You have selected to COMPLETELY WIPE this drive:" + echo " Device: $EXTERNAL_DRIVE" + echo " Size: $(lsblk -dpno SIZE "$EXTERNAL_DRIVE")" + echo " Model: $(lsblk -dpno MODEL "$EXTERNAL_DRIVE")" + echo + echo "Current partitions on target drive:" + lsblk "$EXTERNAL_DRIVE" || true + echo + echo -e "${RED}This will DESTROY ALL DATA on $EXTERNAL_DRIVE!${NC}" + echo + read -p "Type 'YES' to confirm you want to wipe $EXTERNAL_DRIVE: " confirmation + if [ "$confirmation" != "YES" ]; then + error "Migration cancelled by user for safety" + fi +} + +analyze_internal_system() { + log "Analyzing internal system layout..." + + # Get all partitions on internal drive + local partitions=($(lsblk -pno NAME "$INTERNAL_DRIVE" | grep -v "^$INTERNAL_DRIVE$")) + + echo "Found partitions on $INTERNAL_DRIVE:" + for part in "${partitions[@]}"; do + local size=$(lsblk -no SIZE "$part") + local fstype=$(lsblk -no FSTYPE "$part") + local mountpoint=$(lsblk -no MOUNTPOINT "$part") + local label=$(lsblk -no LABEL "$part") + + echo " $part: $size, $fstype, mounted at: ${mountpoint:-'not mounted'}, label: ${label:-'no label'}" + + # Store partition information + PARTITION_FILESYSTEMS["$part"]="$fstype" + PARTITION_SIZES["$part"]="$size" + + # Try to identify partition purpose + if [[ "$fstype" == "vfat" ]] && [[ "$part" == *"1" ]]; then + INTERNAL_PARTITIONS["efi"]="$part" + BOOT_SIZE="1G" # Default EFI size + elif [[ "$mountpoint" == "/" ]] || [[ "$label" == "root"* ]]; then + INTERNAL_PARTITIONS["root"]="$part" + # Parse size more carefully, handle G/M/K suffixes + local size_num=$(echo "$size" | sed 's/[^0-9.]//g') + if [[ "$size" == *"G"* ]]; then + ROOT_SIZE="$(echo "$size_num + 10" | bc)G" # Add some extra space + elif [[ "$size" == *"M"* ]]; then + ROOT_SIZE="$(echo "scale=1; $size_num / 1024 + 1" | bc)G" + else + ROOT_SIZE="${size_num}G" + fi + elif [[ "$mountpoint" == "/home" ]] || [[ "$label" == "home"* ]]; then + INTERNAL_PARTITIONS["home"]="$part" + # Parse size more carefully + local size_num=$(echo "$size" | sed 's/[^0-9.]//g') + if [[ "$size" == *"G"* ]]; then + HOME_SIZE="$(echo "$size_num + 5" | bc)G" # Add some extra space + elif [[ "$size" == *"M"* ]]; then + HOME_SIZE="$(echo "scale=1; $size_num / 1024 + 1" | bc)G" + else + HOME_SIZE="${size_num}G" + fi + elif [[ "$mountpoint" == "/boot" ]] || [[ "$label" == "boot"* ]]; then + INTERNAL_PARTITIONS["boot"]="$part" + BOOT_SIZE="2G" # Standard boot size + elif [[ "$fstype" == "swap" ]]; then + INTERNAL_PARTITIONS["swap"]="$part" + local size_num=$(echo "$size" | sed 's/[^0-9.]//g') + if [[ "$size" == *"G"* ]]; then + SWAP_SIZE="${size_num}G" + elif [[ "$size" == *"M"* ]]; then + SWAP_SIZE="$(echo "scale=1; $size_num / 1024" | bc)G" + else + SWAP_SIZE="${size_num}G" + fi + elif [[ "$fstype" == "crypto_LUKS" ]]; then + log "Found encrypted partition: $part" + # This might be encrypted home or root + fi + done + + # If we didn't find a separate home partition, it's probably in root + if [ -z "${INTERNAL_PARTITIONS[home]}" ]; then + log "No separate /home partition found - assuming /home is in root partition" + # Increase root size to accommodate home + if [ -n "$ROOT_SIZE" ]; then + local root_num=$(echo "$ROOT_SIZE" | sed 's/G//') + ROOT_SIZE="$((root_num + 50))G" # Add extra space + fi + HOME_SIZE="50G" # Create separate home in LVM + fi + + # Set default swap size if not found + if [ -z "$SWAP_SIZE" ]; then + local mem_gb=$(free -g | awk '/^Mem:/ {print $2}') + SWAP_SIZE="$((mem_gb + 2))G" + log "No swap partition found, creating ${SWAP_SIZE} swap based on system memory" + fi + + # Set sensible defaults if sizes are missing + [ -z "$ROOT_SIZE" ] && ROOT_SIZE="70G" + [ -z "$HOME_SIZE" ] && HOME_SIZE="50G" + [ -z "$BOOT_SIZE" ] && BOOT_SIZE="2G" + [ -z "$SWAP_SIZE" ] && SWAP_SIZE="8G" + + # Ensure sizes are properly formatted (remove any extra decimals) + ROOT_SIZE=$(echo "$ROOT_SIZE" | sed 's/\.[0-9]*G/G/') + HOME_SIZE=$(echo "$HOME_SIZE" | sed 's/\.[0-9]*G/G/') + BOOT_SIZE=$(echo "$BOOT_SIZE" | sed 's/\.[0-9]*G/G/') + SWAP_SIZE=$(echo "$SWAP_SIZE" | sed 's/\.[0-9]*G/G/') + + echo + echo "System analysis summary:" + echo " EFI partition: ${INTERNAL_PARTITIONS[efi]:-'not found'}" + echo " Root partition: ${INTERNAL_PARTITIONS[root]:-'not found'}" + echo " Home partition: ${INTERNAL_PARTITIONS[home]:-'integrated in root'}" + echo " Boot partition: ${INTERNAL_PARTITIONS[boot]:-'integrated in root/efi'}" + echo " Swap partition: ${INTERNAL_PARTITIONS[swap]:-'not found'}" + + success "System analysis completed" +} + +check_prerequisites() { + log "Checking prerequisites..." + + # Check if running from live system + local root_device=$(df / | tail -1 | awk '{print $1}') + if [[ "$root_device" == *"loop"* ]] || [[ "$root_device" == *"overlay"* ]] || [[ "$root_device" == *"tmpfs"* ]]; then + success "Running from live system - good!" + else + warning "This might not be a live system. Please ensure you're running from live USB!" + confirm_action "Continue anyway? (Not recommended for production systems)" + fi + + # Check if tools are available and install if missing + local missing_tools=() + for tool in lvm cryptsetup rsync parted pv grub-install mkfs.ext4 mkfs.fat bc wipefs; do + if ! command -v $tool >/dev/null 2>&1; then + missing_tools+=("$tool") + fi + done + + if [ ${#missing_tools[@]} -gt 0 ]; then + warning "Missing required tools: ${missing_tools[*]}" + log "Attempting to install missing tools automatically..." + + # Try to run preparation script if it exists + local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + if [ -f "$script_dir/prepare_live_system.sh" ]; then + log "Running prepare_live_system.sh to install missing tools..." + bash "$script_dir/prepare_live_system.sh" || { + error "Failed to prepare live system. Please run: sudo ./prepare_live_system.sh" + } + else + # Fallback: try to install directly + log "Attempting direct package installation..." + if command -v apt >/dev/null 2>&1; then + apt update && apt install -y lvm2 cryptsetup rsync parted pv grub-efi-amd64 grub-common e2fsprogs dosfstools bc util-linux initramfs-tools || { + error "Failed to install required packages. Please install manually: lvm2 cryptsetup rsync parted pv grub-efi-amd64 grub-common e2fsprogs dosfstools bc" + } + else + error "Cannot install packages automatically. Please install missing tools: ${missing_tools[*]}" + fi + fi + + # Re-check after installation attempt + missing_tools=() + for tool in lvm cryptsetup rsync parted pv grub-install mkfs.ext4 mkfs.fat bc wipefs; do + if ! command -v $tool >/dev/null 2>&1; then + missing_tools+=("$tool") + fi + done + + if [ ${#missing_tools[@]} -gt 0 ]; then + error "Still missing required tools after installation attempt: ${missing_tools[*]}" + fi + fi + + success "Prerequisites check passed" +} + +setup_work_directories() { + log "Setting up work directories..." + mkdir -p "$WORK_DIR"/{internal_root,internal_home,internal_boot,external_root,external_home,external_boot} + success "Work directories created" +} + +backup_existing_external_data() { + log "Backing up any existing data on external drive..." + + # Check if external LVM volumes are already mounted + if [ -d "/dev/external-vg" ]; then + warning "Found existing external-vg volume group" + confirm_action "This will DESTROY all data on the external M.2 drive!" + + # Deactivate existing LVM volumes + vgchange -an external-vg || true + vgremove -f external-vg || true + pvremove -f ${EXTERNAL_DRIVE}2 || true + fi + + success "External drive prepared for migration" +} + +create_lvm_layout() { + log "Creating new LVM layout on external drive..." + + # Debug: Show current size configuration + log "Size configuration:" + log " ROOT_SIZE: $ROOT_SIZE" + log " HOME_SIZE: $HOME_SIZE" + log " SWAP_SIZE: $SWAP_SIZE" + log " BOOT_SIZE: $BOOT_SIZE" + + # Wipe the drive and create new partition table + log "Wiping external drive: $EXTERNAL_DRIVE" + wipefs -a "$EXTERNAL_DRIVE" || warning "Failed to wipe drive signatures" + + log "Creating GPT partition table" + parted -s "$EXTERNAL_DRIVE" mklabel gpt || error "Failed to create partition table" + + # Create EFI boot partition (512MB) + log "Creating EFI boot partition" + parted -s "$EXTERNAL_DRIVE" mkpart primary fat32 1MiB 513MiB || error "Failed to create EFI partition" + parted -s "$EXTERNAL_DRIVE" set 1 boot on || warning "Failed to set boot flag" + parted -s "$EXTERNAL_DRIVE" set 1 esp on || warning "Failed to set ESP flag" + + # Create LVM partition (rest of disk) + log "Creating LVM partition" + parted -s "$EXTERNAL_DRIVE" mkpart primary 513MiB 100% || error "Failed to create LVM partition" + parted -s "$EXTERNAL_DRIVE" set 2 lvm on || warning "Failed to set LVM flag" + + # Wait for partition table to be re-read + log "Waiting for partition table update..." + sleep 3 + partprobe "$EXTERNAL_DRIVE" || warning "Failed to update partition table" + sleep 2 + + # Show partition layout for debugging + log "New partition layout:" + parted "$EXTERNAL_DRIVE" print || warning "Failed to display partition table" + + # Verify partitions exist + if [ ! -b "${EXTERNAL_DRIVE}1" ]; then + error "EFI partition ${EXTERNAL_DRIVE}1 not found after creation" + fi + if [ ! -b "${EXTERNAL_DRIVE}2" ]; then + error "LVM partition ${EXTERNAL_DRIVE}2 not found after creation" + fi + + # Create filesystems + log "Creating FAT32 filesystem on EFI partition" + mkfs.fat -F32 "${EXTERNAL_DRIVE}1" || error "Failed to create EFI filesystem" + + # Setup LVM + log "Creating LVM physical volume on ${EXTERNAL_DRIVE}2" + pvcreate "${EXTERNAL_DRIVE}2" || error "Failed to create physical volume" + + log "Creating volume group: $VG_NAME" + vgcreate "$VG_NAME" "${EXTERNAL_DRIVE}2" || error "Failed to create volume group" + + # Validate sizes before creating logical volumes + log "Validating LVM sizes..." + + # Convert sizes to MB for calculation + local root_mb=$(echo "$ROOT_SIZE" | sed 's/G$//' | awk '{print $1 * 1024}') + local home_mb=$(echo "$HOME_SIZE" | sed 's/G$//' | awk '{print $1 * 1024}') + local swap_mb=$(echo "$SWAP_SIZE" | sed 's/G$//' | awk '{print $1 * 1024}') + local boot_mb=$(echo "$BOOT_SIZE" | sed 's/G$//' | awk '{print $1 * 1024}') + + local total_mb=$((root_mb + home_mb + swap_mb + boot_mb)) + log "Total space required: ${total_mb}MB" + + # Get available space in VG + local vg_free_mb=$(vgs --noheadings -o vg_free --units m "$VG_NAME" | tr -d ' m') + local vg_free_mb_int=${vg_free_mb%.*} + log "Available space in VG: ${vg_free_mb_int}MB" + + if [ "$total_mb" -gt "$vg_free_mb_int" ]; then + warning "Total required space ($total_mb MB) exceeds available space ($vg_free_mb_int MB)" + log "Reducing sizes proportionally..." + + # Reduce all sizes by 10% to leave space for snapshots + ROOT_SIZE=$((root_mb * 85 / 100 / 1024))"G" + HOME_SIZE=$((home_mb * 85 / 100 / 1024))"G" + SWAP_SIZE=$((swap_mb * 85 / 100 / 1024))"G" + BOOT_SIZE=$((boot_mb * 85 / 100 / 1024))"G" + + log "Adjusted sizes:" + log " ROOT_SIZE: $ROOT_SIZE" + log " HOME_SIZE: $HOME_SIZE" + log " SWAP_SIZE: $SWAP_SIZE" + log " BOOT_SIZE: $BOOT_SIZE" + fi + + # Create logical volumes with space for snapshots + log "Creating logical volume: root ($ROOT_SIZE)" + lvcreate -L "$ROOT_SIZE" -n "$ROOT_LV" "$VG_NAME" || error "Failed to create root LV" + + log "Creating logical volume: home ($HOME_SIZE)" + lvcreate -L "$HOME_SIZE" -n "$HOME_LV" "$VG_NAME" || error "Failed to create home LV" + + log "Creating logical volume: swap ($SWAP_SIZE)" + lvcreate -L "$SWAP_SIZE" -n "$SWAP_LV" "$VG_NAME" || error "Failed to create swap LV" + + log "Creating logical volume: boot ($BOOT_SIZE)" + lvcreate -L "$BOOT_SIZE" -n "$BOOT_LV" "$VG_NAME" || error "Failed to create boot LV" + + # Show LVM layout + log "LVM layout created:" + lvs "$VG_NAME" || warning "Failed to display LV layout" + + # Create filesystems on LVM volumes + log "Creating ext4 filesystem on root LV" + mkfs.ext4 -L "root" "/dev/$VG_NAME/$ROOT_LV" || error "Failed to create root filesystem" + + log "Creating ext4 filesystem on home LV" + mkfs.ext4 -L "home" "/dev/$VG_NAME/$HOME_LV" || error "Failed to create home filesystem" + + log "Creating ext4 filesystem on boot LV" + mkfs.ext4 -L "boot" "/dev/$VG_NAME/$BOOT_LV" || error "Failed to create boot filesystem" + + log "Creating swap on swap LV" + mkswap -L "swap" "/dev/$VG_NAME/$SWAP_LV" || error "Failed to create swap" + + success "LVM layout created successfully" +} + +handle_encrypted_partitions() { + log "Handling encrypted partitions..." + + local found_encrypted=false + + # Check each partition for encryption + for part_name in "${!INTERNAL_PARTITIONS[@]}"; do + local part_device="${INTERNAL_PARTITIONS[$part_name]}" + local fstype="${PARTITION_FILESYSTEMS[$part_device]}" + + if [[ "$fstype" == "crypto_LUKS" ]]; then + found_encrypted=true + log "Found encrypted partition: $part_device ($part_name)" + + local crypt_name="internal_${part_name}_luks" + + if ! cryptsetup status "$crypt_name" >/dev/null 2>&1; then + echo "Please enter the password for encrypted $part_name partition ($part_device):" + if cryptsetup open "$part_device" "$crypt_name"; then + success "Unlocked $part_device as /dev/mapper/$crypt_name" + # Update the partition reference to the decrypted device + INTERNAL_PARTITIONS["$part_name"]="/dev/mapper/$crypt_name" + # Update filesystem type of decrypted partition + local decrypted_fs=$(lsblk -no FSTYPE "/dev/mapper/$crypt_name") + PARTITION_FILESYSTEMS["/dev/mapper/$crypt_name"]="$decrypted_fs" + else + error "Failed to unlock encrypted partition $part_device" + fi + else + log "Encrypted partition $part_device already unlocked" + INTERNAL_PARTITIONS["$part_name"]="/dev/mapper/$crypt_name" + fi + fi + done + + if [ "$found_encrypted" = false ]; then + log "No encrypted partitions found" + fi + + success "Encrypted partition handling completed" +} + +mount_filesystems() { + log "Mounting filesystems..." + + # Mount internal filesystems + for part_name in "${!INTERNAL_PARTITIONS[@]}"; do + local part_device="${INTERNAL_PARTITIONS[$part_name]}" + local mount_point="$WORK_DIR/internal_$part_name" + + if [ ! -d "$mount_point" ]; then + mkdir -p "$mount_point" + fi + + log "Mounting $part_device to $mount_point" + if mount "$part_device" "$mount_point"; then + success "Mounted $part_name partition" + PARTITION_MOUNTS["$part_name"]="$mount_point" + else + error "Failed to mount $part_device" + fi + done + + # Mount external LVM filesystems + mount "/dev/$VG_NAME/$ROOT_LV" "$EXTERNAL_ROOT_MOUNT" + mount "/dev/$VG_NAME/$HOME_LV" "$EXTERNAL_HOME_MOUNT" + mount "/dev/$VG_NAME/$BOOT_LV" "$EXTERNAL_BOOT_MOUNT" + + # Mount EFI partition + mkdir -p "$EXTERNAL_ROOT_MOUNT/boot/efi" + mount "${EXTERNAL_DRIVE}1" "$EXTERNAL_ROOT_MOUNT/boot/efi" + + success "All filesystems mounted" +} + +copy_system_data() { + log "Copying system data (this will take a while)..." + + # Copy root filesystem + if [ -n "${INTERNAL_PARTITIONS[root]}" ]; then + log "Copying root filesystem..." + local source_mount="${PARTITION_MOUNTS[root]}" + + # If no separate home partition exists, exclude /home during root copy + local exclude_opts="" + if [ -z "${INTERNAL_PARTITIONS[home]}" ]; then + exclude_opts="--exclude=/home/*" + fi + + rsync -avxHAX --progress $exclude_opts \ + --exclude=/proc/* --exclude=/sys/* --exclude=/dev/* \ + --exclude=/run/* --exclude=/tmp/* --exclude=/var/tmp/* \ + "$source_mount/" "$EXTERNAL_ROOT_MOUNT/" + + success "Root filesystem copied" + else + error "No root partition found to copy" + fi + + # Copy home filesystem (if separate partition exists) + if [ -n "${INTERNAL_PARTITIONS[home]}" ]; then + log "Copying home filesystem from separate partition..." + local source_mount="${PARTITION_MOUNTS[home]}" + rsync -avxHAX --progress "$source_mount/" "$EXTERNAL_HOME_MOUNT/" + success "Home filesystem copied from separate partition" + else + log "Copying /home from root filesystem..." + # Create home directory structure on external home partition + if [ -d "${PARTITION_MOUNTS[root]}/home" ]; then + rsync -avxHAX --progress "${PARTITION_MOUNTS[root]}/home/" "$EXTERNAL_HOME_MOUNT/" + success "/home copied from root filesystem" + else + warning "No /home directory found in root filesystem" + fi + fi + + # Copy boot files + if [ -n "${INTERNAL_PARTITIONS[boot]}" ]; then + log "Copying boot files from separate boot partition..." + local source_mount="${PARTITION_MOUNTS[boot]}" + rsync -avxHAX --progress "$source_mount/" "$EXTERNAL_BOOT_MOUNT/" + else + log "Copying boot files from root filesystem..." + if [ -d "${PARTITION_MOUNTS[root]}/boot" ]; then + rsync -avxHAX --progress "${PARTITION_MOUNTS[root]}/boot/" "$EXTERNAL_BOOT_MOUNT/" + else + warning "No /boot directory found" + fi + fi + + success "System data copied successfully" +} + +update_system_configuration() { + log "Updating system configuration..." + + # Update fstab + local root_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$ROOT_LV") + local home_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$HOME_LV") + local boot_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$BOOT_LV") + local efi_uuid=$(blkid -s UUID -o value "${EXTERNAL_DRIVE}1") + local swap_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$SWAP_LV") + + cat > "$EXTERNAL_ROOT_MOUNT/etc/fstab" << EOF +# /etc/fstab: static file system information. +# +# Use 'blkid' to print the universally unique identifier for a device; this may +# be used with UUID= as a more robust way to name devices that works even if +# disks are added and removed. See fstab(5). +# +# +UUID=$root_uuid / ext4 defaults 0 1 +UUID=$efi_uuid /boot/efi vfat defaults 0 2 +UUID=$boot_uuid /boot ext4 defaults 0 2 +UUID=$home_uuid /home ext4 defaults 0 2 +UUID=$swap_uuid none swap sw 0 0 +tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0 +EOF + + # Update crypttab (remove old encrypted home entry since we're using LVM now) + echo "# No encrypted partitions in LVM setup" > "$EXTERNAL_ROOT_MOUNT/etc/crypttab" + + success "System configuration updated" +} + +install_bootloader() { + log "Installing bootloader..." + + # Bind mount necessary filesystems for chroot + mount --bind /dev "$EXTERNAL_ROOT_MOUNT/dev" + mount --bind /proc "$EXTERNAL_ROOT_MOUNT/proc" + mount --bind /sys "$EXTERNAL_ROOT_MOUNT/sys" + mount --bind /run "$EXTERNAL_ROOT_MOUNT/run" + + # Update initramfs to include LVM support + chroot "$EXTERNAL_ROOT_MOUNT" /bin/bash -c " + echo 'GRUB_ENABLE_CRYPTODISK=y' >> /etc/default/grub + update-initramfs -u -k all + grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck + update-grub + " + + # Unmount bind mounts + umount "$EXTERNAL_ROOT_MOUNT/dev" + umount "$EXTERNAL_ROOT_MOUNT/proc" + umount "$EXTERNAL_ROOT_MOUNT/sys" + umount "$EXTERNAL_ROOT_MOUNT/run" + + success "Bootloader installed successfully" +} + +create_lvm_snapshot_script() { + log "Creating LVM snapshot backup script..." + + cat > "$EXTERNAL_ROOT_MOUNT/usr/local/bin/lvm-snapshot-backup.sh" << 'EOF' +#!/bin/bash + +# LVM Snapshot Backup Script +# Creates snapshots of LVM volumes for backup purposes + +set -e + +VG_NAME="system-vg" +SNAPSHOT_SIZE="10G" +BACKUP_DIR="/mnt/backup" + +create_snapshots() { + echo "Creating LVM snapshots..." + + # Create snapshots + lvcreate -L "$SNAPSHOT_SIZE" -s -n root-snapshot "/dev/$VG_NAME/root" + lvcreate -L "$SNAPSHOT_SIZE" -s -n home-snapshot "/dev/$VG_NAME/home" + + echo "Snapshots created successfully" + echo "root-snapshot: /dev/$VG_NAME/root-snapshot" + echo "home-snapshot: /dev/$VG_NAME/home-snapshot" +} + +mount_snapshots() { + echo "Mounting snapshots..." + mkdir -p "$BACKUP_DIR"/{root,home} + mount "/dev/$VG_NAME/root-snapshot" "$BACKUP_DIR/root" + mount "/dev/$VG_NAME/home-snapshot" "$BACKUP_DIR/home" + echo "Snapshots mounted at $BACKUP_DIR" +} + +remove_snapshots() { + echo "Cleaning up snapshots..." + umount "$BACKUP_DIR/root" 2>/dev/null || true + umount "$BACKUP_DIR/home" 2>/dev/null || true + lvremove -f "/dev/$VG_NAME/root-snapshot" 2>/dev/null || true + lvremove -f "/dev/$VG_NAME/home-snapshot" 2>/dev/null || true + echo "Snapshots removed" +} + +case "$1" in + create) + create_snapshots + ;; + mount) + mount_snapshots + ;; + remove) + remove_snapshots + ;; + backup) + create_snapshots + mount_snapshots + echo "Snapshots ready for backup at $BACKUP_DIR" + echo "Run 'lvm-snapshot-backup.sh remove' when backup is complete" + ;; + *) + echo "Usage: $0 {create|mount|remove|backup}" + echo " create - Create snapshots only" + echo " mount - Mount existing snapshots" + echo " remove - Remove snapshots and unmount" + echo " backup - Create and mount snapshots ready for backup" + exit 1 + ;; +esac +EOF + + chmod +x "$EXTERNAL_ROOT_MOUNT/usr/local/bin/lvm-snapshot-backup.sh" + + success "LVM snapshot backup script created" +} + +repair_grub_bootloader() { + log "Performing final GRUB repair to ensure bootability..." + + # This is based on the working simple_grub_repair.sh + local temp_boot="/tmp/grub-repair-boot" + local temp_efi="/tmp/grub-repair-efi" + + mkdir -p "$temp_boot" "$temp_efi" + + log "Mounting boot partitions for GRUB repair..." + mount "/dev/$VG_NAME/boot" "$temp_boot" || { + warning "Could not mount boot partition for GRUB repair" + return 0 + } + + mount "${EXTERNAL_DRIVE}1" "$temp_efi" || { + warning "Could not mount EFI partition for GRUB repair" + umount "$temp_boot" 2>/dev/null || true + return 0 + } + + log "Installing GRUB bootloader directly to external drive..." + if grub-install --target=x86_64-efi \ + --efi-directory="$temp_efi" \ + --bootloader-id=debian \ + --boot-directory="$temp_boot" \ + --recheck \ + "$EXTERNAL_DRIVE"; then + success "GRUB bootloader installed successfully" + + # Verify installation + if [ -f "$temp_efi/EFI/debian/grubx64.efi" ]; then + success "GRUB EFI bootloader verified" + else + warning "GRUB EFI bootloader not found after installation" + fi + else + warning "GRUB installation encountered issues" + fi + + # Cleanup + umount "$temp_boot" 2>/dev/null || true + umount "$temp_efi" 2>/dev/null || true + rmdir "$temp_boot" "$temp_efi" 2>/dev/null || true + + success "GRUB repair completed" +} + +cleanup() { + log "Cleaning up..." + + # Unmount external filesystems + umount "$EXTERNAL_ROOT_MOUNT/boot/efi" 2>/dev/null || true + umount "$EXTERNAL_ROOT_MOUNT" 2>/dev/null || true + umount "$EXTERNAL_HOME_MOUNT" 2>/dev/null || true + umount "$EXTERNAL_BOOT_MOUNT" 2>/dev/null || true + + # Unmount internal filesystems + for part_name in "${!PARTITION_MOUNTS[@]}"; do + local mount_point="${PARTITION_MOUNTS[$part_name]}" + umount "$mount_point" 2>/dev/null || true + done + + # Close encrypted partitions + for part_name in "${!INTERNAL_PARTITIONS[@]}"; do + local part_device="${INTERNAL_PARTITIONS[$part_name]}" + if [[ "$part_device" == *"/dev/mapper/"* ]]; then + local crypt_name=$(basename "$part_device") + cryptsetup close "$crypt_name" 2>/dev/null || true + fi + done + + success "Cleanup completed" +} + +main() { + echo -e "${GREEN}=== LVM Migration Script ===${NC}" + echo "This script will migrate your non-LVM system to LVM on an external M.2 drive" + echo "Run this from a live USB system for best results" + echo + + check_prerequisites + detect_drives + analyze_internal_system + + echo + echo "Migration Summary:" + echo " Source: $INTERNAL_DRIVE (non-LVM system)" + echo " Target: $EXTERNAL_DRIVE (will become LVM system)" + echo " Root size: $ROOT_SIZE" + echo " Home size: $HOME_SIZE" + echo " Swap size: $SWAP_SIZE" + echo " Boot size: $BOOT_SIZE" + echo + + confirm_action "WARNING: This will DESTROY all data on $EXTERNAL_DRIVE!" + + setup_work_directories + backup_existing_external_data + create_lvm_layout + handle_encrypted_partitions + mount_filesystems + copy_system_data + update_system_configuration + install_bootloader + create_lvm_snapshot_script + repair_grub_bootloader + cleanup + + success "Complete LVM migration with GRUB repair finished successfully!" + echo + echo -e "${GREEN}=== MIGRATION COMPLETE ===${NC}" + echo "βœ… System migrated to LVM on external M.2 drive" + echo "βœ… GRUB bootloader installed and repaired" + echo "βœ… Boot configuration updated" + echo "βœ… LVM snapshot backup system ready" + echo + echo -e "${BLUE}Next steps:${NC}" + echo "1. Reboot your system" + echo "2. Enter BIOS/UEFI settings and configure:" + echo " β€’ Set external M.2 as first boot device" + echo " β€’ Disable Secure Boot (if enabled)" + echo " β€’ Ensure UEFI mode is enabled" + echo "3. Look for 'debian' entry in boot menu" + echo "4. Boot from external drive - should work without reset loops!" + echo + echo -e "${GREEN}Your new LVM system features:${NC}" + echo "β€’ Instant snapshots: sudo /usr/local/bin/lvm-snapshot-backup.sh backup" + echo "β€’ Dynamic volume resizing" + echo "β€’ Advanced backup strategies with rollback" + echo "β€’ Original internal drive unchanged as fallback" + echo + echo -e "${YELLOW}πŸŽ‰ One-button migration completed successfully!${NC}" + echo "Everything is configured and ready to boot!" +} + +# Trap to ensure cleanup on exit +trap cleanup EXIT + +main "$@" \ No newline at end of file diff --git a/plug_and_play_backup.sh b/plug_and_play_backup.sh new file mode 100755 index 0000000..c3b124d --- /dev/null +++ b/plug_and_play_backup.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# Create PLUG-AND-PLAY Clonezilla USB - No commands to remember! + +set -e + +USB_DRIVE="/dev/sda" +CLONEZILLA_ISO="clonezilla-live-3.1.0-22-amd64.iso" + +echo "Creating PLUG-AND-PLAY Disaster Recovery USB" +echo "============================================" +echo "β€’ Boot USB = Automatic backup starts in 10 seconds" +echo "β€’ No commands to remember" +echo "β€’ 15-20 minute backup with maximum speed" +echo + +# Use existing ISO +if [[ ! -f "$CLONEZILLA_ISO" ]]; then + echo "ERROR: $CLONEZILLA_ISO not found!" + exit 1 +fi + +read -p "Continue to create plug-and-play USB? (yes/no): " confirm +if [[ "$confirm" != "yes" ]]; then + exit 1 +fi + +# Unmount and recreate USB +sudo umount "${USB_DRIVE}"* 2>/dev/null || true + +# Single partition for simplicity +sudo parted "$USB_DRIVE" --script mklabel msdos +sudo parted "$USB_DRIVE" --script mkpart primary fat32 1MiB 100% +sudo parted "$USB_DRIVE" --script set 1 boot on + +# Format +USB_PART="${USB_DRIVE}1" +sudo mkfs.fat -F32 -n "AUTOBACKUP" "$USB_PART" + +# Mount and install +USB_MOUNT="/tmp/autobackup_$$" +ISO_MOUNT="/tmp/clonezilla_iso_$$" + +sudo mkdir -p "$USB_MOUNT" "$ISO_MOUNT" +sudo mount "$USB_PART" "$USB_MOUNT" +sudo mount -o loop "$CLONEZILLA_ISO" "$ISO_MOUNT" + +echo "Installing Clonezilla..." +sudo cp -R "$ISO_MOUNT"/* "$USB_MOUNT/" + +echo "Installing GRUB..." +sudo grub-install --target=i386-pc --boot-directory="$USB_MOUNT/boot" "$USB_DRIVE" + +# Create PLUG-AND-PLAY menu +sudo tee "$USB_MOUNT/boot/grub/grub.cfg" > /dev/null << 'EOF' +set timeout=10 +set default=0 + +menuentry "πŸš€ AUTO BACKUP (10 second countdown)" { + linux /live/vmlinuz boot=live union=overlay username=user config components quiet noswap edd=on nomodeset ocs_live_run="ocs-sr -q2 -j2 -z0 -i 0 -sfsck -scs -rescue -batch -p reboot savedisk AUTO_$(date +%Y%m%d_%H%M%S) /dev/nvme0n1" ocs_live_extra_param="" keyboard-layouts= ocs_live_batch="yes" locales= vga=normal nosplash + initrd /live/initrd.img +} + +menuentry "πŸ”§ Manual Clonezilla (for restore)" { + linux /live/vmlinuz boot=live union=overlay username=user config components quiet noswap edd=on nomodeset ocs_live_run="ocs-live-general" ocs_live_extra_param="" keyboard-layouts= ocs_live_batch="no" locales= vga=normal nosplash + initrd /live/initrd.img +} +EOF + +# Create instructions +sudo tee "$USB_MOUNT/PLUG_AND_PLAY_INSTRUCTIONS.txt" > /dev/null << 'EOF' +PLUG AND PLAY DISASTER RECOVERY USB +=================================== + +πŸš€ BACKUP INSTRUCTIONS: +1. Boot from this USB +2. Wait 10 seconds (auto-backup starts) +3. Wait 15-20 minutes +4. System reboots automatically + +πŸ”§ RESTORE INSTRUCTIONS: +1. Boot from this USB +2. Select "Manual Clonezilla" +3. Choose "device-image" -> "restoredisk" +4. Find your backup image +5. Select target drive +6. Confirm and wait + +NO COMMANDS TO REMEMBER! +Just boot and wait 10 seconds. + +Backup will be saved to this USB drive. +Created: $(date) +EOF + +sudo mkdir -p "$USB_MOUNT/home/partimag" + +# Cleanup +sudo umount "$ISO_MOUNT" "$USB_MOUNT" +sudo rmdir "$USB_MOUNT" "$ISO_MOUNT" + +echo +echo "βœ… PLUG-AND-PLAY USB CREATED!" +echo "βœ… Boot USB = 10 second countdown then auto-backup" +echo "βœ… No commands to remember in disaster recovery" +echo "βœ… Maximum speed backup (15-20 minutes)" +echo "βœ… Instructions stored on USB drive" +echo +echo "DISASTER RECOVERY: Just boot from USB and wait!" diff --git a/prepare_live_system.sh b/prepare_live_system.sh new file mode 100755 index 0000000..91ef1b9 --- /dev/null +++ b/prepare_live_system.sh @@ -0,0 +1,315 @@ +#!/bin/bash + +# Live System Preparation Script for LVM Migration +# This script prepares a live USB system with all necessary tools for LVM migration + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log() { + echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 + exit 1 +} + +warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +check_live_system() { + log "Checking if running from live system..." + + # Check multiple indicators of live system + local is_live=false + + # Check for common live system indicators + if [ -f "/etc/casper.conf" ] || [ -f "/lib/live/mount/medium" ]; then + is_live=true + fi + + # Check if root filesystem is on loop, overlay, or tmpfs + local root_device=$(df / | tail -1 | awk '{print $1}') + if [[ "$root_device" == *"loop"* ]] || [[ "$root_device" == *"overlay"* ]] || [[ "$root_device" == *"tmpfs"* ]]; then + is_live=true + fi + + # Check for live system processes + if pgrep -f "casper" >/dev/null 2>&1 || pgrep -f "live-boot" >/dev/null 2>&1; then + is_live=true + fi + + if [ "$is_live" = true ]; then + success "Confirmed: Running from live system" + else + warning "This doesn't appear to be a live system!" + echo "For safety, LVM migration should be run from a live USB system." + read -p "Continue anyway? [y/N] " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + error "Operation aborted. Please boot from a live USB system." + fi + fi +} + +update_package_lists() { + log "Updating package lists..." + + if command -v apt >/dev/null 2>&1; then + apt update || warning "Failed to update apt package lists" + elif command -v pacman >/dev/null 2>&1; then + pacman -Sy || warning "Failed to update pacman package lists" + elif command -v dnf >/dev/null 2>&1; then + dnf makecache || warning "Failed to update dnf package cache" + else + warning "Unknown package manager - manual tool installation may be required" + fi +} + +install_required_tools() { + log "Installing required tools for LVM migration..." + + # Detect distribution for package name variations + local distro="unknown" + if [ -f /etc/os-release ]; then + . /etc/os-release + distro="$ID" + log "Detected distribution: $PRETTY_NAME" + fi + + # Define package groups with alternatives for different distributions + local package_groups=( + "lvm:lvm2,lvm" + "cryptsetup:cryptsetup,cryptsetup-bin" + "rsync:rsync" + "parted:parted" + "pv:pv,pipe-viewer" + "grub-efi:grub-efi-amd64,grub-efi,grub-efi-amd64-bin" + "grub-pc:grub-pc-bin,grub-pc" + "grub-common:grub-common,grub2-common" + "e2fsprogs:e2fsprogs" + "dosfstools:dosfstools,mtools" + "util-linux:util-linux" + "coreutils:coreutils" + "bc:bc" + "initramfs:initramfs-tools,dracut" + "udev:udev,systemd-udev" + "kmod:kmod,module-init-tools" + ) + + local missing_tools=() + local packages_to_install=() + + # Check which tools are missing + for tool_spec in "${tools_to_check[@]}"; do + local tool=$(echo "$tool_spec" | cut -d: -f1) + local packages=$(echo "$tool_spec" | cut -d: -f2) + + if ! command -v "$tool" >/dev/null 2>&1; then + missing_tools+=("$tool") + # Add packages (handle comma-separated list) + IFS=',' read -ra PKGS <<< "$packages" + for pkg in "${PKGS[@]}"; do + if [[ ! " ${packages_to_install[@]} " =~ " ${pkg} " ]]; then + packages_to_install+=("$pkg") + fi + done + fi + done + + if [ ${#missing_tools[@]} -eq 0 ]; then + success "All required tools are already available" + return + fi + + echo "Missing tools: ${missing_tools[*]}" + echo "Will attempt to install: ${packages_to_install[*]}" + + # Install packages based on available package manager + if command -v apt >/dev/null 2>&1; then + log "Installing packages with apt..." + + # Update package lists first + apt update || warning "Failed to update package lists" + + # Function to try installing packages with alternatives + try_install() { + local desc="$1" + local packages_str="$2" + IFS=',' read -ra packages <<< "$packages_str" + + log "Installing $desc..." + for pkg in "${packages[@]}"; do + if apt install -y "$pkg" >/dev/null 2>&1; then + success "Installed $pkg for $desc" + return 0 + fi + done + warning "Failed to install any package for $desc (tried: ${packages_str//,/, })" + return 1 + } + + # Install packages by groups + for group in "${package_groups[@]}"; do + local desc="${group%:*}" + local packages="${group#*:}" + try_install "$desc" "$packages" + done + elif command -v pacman >/dev/null 2>&1; then + log "Installing packages with pacman..." + pacman -S --noconfirm "${packages_to_install[@]}" || { + warning "Some packages failed to install via pacman" + } + elif command -v dnf >/dev/null 2>&1; then + log "Installing packages with dnf..." + dnf install -y "${packages_to_install[@]}" || { + warning "Some packages failed to install via dnf" + } + else + warning "Unknown package manager. Please install these packages manually:" + echo " ${packages_to_install[*]}" + fi + + # Verify installation + local still_missing=() + for tool_spec in "${tools_to_check[@]}"; do + local tool=$(echo "$tool_spec" | cut -d: -f1) + if ! command -v "$tool" >/dev/null 2>&1; then + still_missing+=("$tool") + fi + done + + if [ ${#still_missing[@]} -eq 0 ]; then + success "All required tools are now available" + else + error "Still missing tools: ${still_missing[*]}. Please install them manually." + fi +} + +enable_lvm_kernel_modules() { + log "Enabling LVM kernel modules..." + + local modules=("dm_mod" "dm_crypt" "dm_snapshot") + + for module in "${modules[@]}"; do + if ! lsmod | grep -q "^$module"; then + log "Loading kernel module: $module" + modprobe "$module" || warning "Failed to load $module module" + else + log "Module $module already loaded" + fi + done + + # Start LVM services if available + if command -v systemctl >/dev/null 2>&1; then + systemctl start lvm2-monitor 2>/dev/null || true + systemctl start lvm2-lvmetad 2>/dev/null || true + fi + + success "LVM kernel modules enabled" +} + +check_drive_availability() { + log "Checking for available drives..." + + # List all available block devices + echo "Available drives:" + lsblk -dpno NAME,SIZE,MODEL,VENDOR | grep -E "sd[a-z]|nvme[0-9]|mmcblk[0-9]" | while read line; do + echo " $line" + done + + # Count drives + local drive_count=$(lsblk -dpno NAME | grep -E "sd[a-z]|nvme[0-9]|mmcblk[0-9]" | wc -l) + + if [ "$drive_count" -lt 2 ]; then + warning "Only $drive_count drive(s) detected. Migration requires at least 2 drives:" + echo " 1. Internal drive (source)" + echo " 2. External M.2 SSD (target)" + echo + echo "Please connect your external M.2 SSD and try again." + exit 1 + else + success "Found $drive_count drives - sufficient for migration" + fi +} + +create_migration_workspace() { + log "Creating migration workspace..." + + local workspace="/tmp/lvm-migration" + mkdir -p "$workspace"/{scripts,logs,mounts} + + # Copy migration script to workspace if it exists + if [ -f "./migrate_to_lvm.sh" ]; then + cp "./migrate_to_lvm.sh" "$workspace/scripts/" + chmod +x "$workspace/scripts/migrate_to_lvm.sh" + success "Migration script copied to workspace" + fi + + # Create useful aliases + cat > "$workspace/aliases.sh" << 'EOF' +#!/bin/bash +# Useful aliases for LVM migration + +alias ll='ls -la' +alias drives='lsblk -dpno NAME,SIZE,MODEL,VENDOR' +alias mounts='mount | grep -E "sd[a-z]|nvme|loop|mapper"' +alias lvminfo='pvs && vgs && lvs' +alias migration='cd /tmp/lvm-migration && ls -la' + +echo "Migration workspace aliases loaded:" +echo " drives - List all available drives" +echo " mounts - Show mounted filesystems" +echo " lvminfo - Display LVM information" +echo " migration - Go to migration workspace" +EOF + + success "Migration workspace created at $workspace" + echo "To load helpful aliases: source $workspace/aliases.sh" +} + +main() { + echo -e "${GREEN}=== Live System Preparation for LVM Migration ===${NC}" + echo "This script prepares your live USB system for LVM migration" + echo + + # Check if running as root + if [ "$EUID" -ne 0 ]; then + error "This script must be run as root. Use: sudo $0" + fi + + check_live_system + update_package_lists + install_required_tools + enable_lvm_kernel_modules + check_drive_availability + create_migration_workspace + + success "Live system preparation completed!" + echo + echo -e "${GREEN}Next steps:${NC}" + echo "1. Connect your external M.2 SSD if not already connected" + echo "2. Run the LVM migration script:" + echo " sudo ./migrate_to_lvm.sh" + echo "3. Follow the interactive prompts to complete migration" + echo + echo -e "${YELLOW}Important reminders:${NC}" + echo "β€’ Ensure your external M.2 SSD is large enough for your system" + echo "β€’ The migration will DESTROY all data on the target drive" + echo "β€’ Your original internal drive will remain unchanged as backup" + echo "β€’ After migration, update BIOS boot order to boot from external drive" +} + +main "$@" \ No newline at end of file diff --git a/restore_tools_after_backup.sh b/restore_tools_after_backup.sh new file mode 100755 index 0000000..604456e --- /dev/null +++ b/restore_tools_after_backup.sh @@ -0,0 +1,271 @@ +#!/bin/bash +# Restore backup tools after cloning operation +# This script runs automatically after backup to preserve tools on external drive + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_status() { + echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Get the external drive from parameter or auto-detect +EXTERNAL_DRIVE="$1" +if [[ -z "$EXTERNAL_DRIVE" ]]; then + print_status "Auto-detecting external drive..." + EXTERNAL_DRIVE=$(lsblk -d -o NAME,TRAN | grep usb | awk '{print "/dev/" $1}' | head -1) +fi + +if [[ -z "$EXTERNAL_DRIVE" ]]; then + print_error "Could not detect external drive" + exit 1 +fi + +print_status "Restoring backup tools to $EXTERNAL_DRIVE" + +# Find the tools partition (look for BACKUP_TOOLS label first) +TOOLS_PARTITION=$(blkid -L "BACKUP_TOOLS" 2>/dev/null || echo "") + +# If not found by label, try to find the last partition on the external drive +if [[ -z "$TOOLS_PARTITION" ]]; then + # Get all partitions on the external drive + mapfile -t partitions < <(lsblk -n -o NAME "$EXTERNAL_DRIVE" | grep -v "^$(basename "$EXTERNAL_DRIVE")$") + + if [[ ${#partitions[@]} -gt 0 ]]; then + # Check the last partition + last_partition="/dev/${partitions[-1]}" + + # Check if it's small (likely our tools partition) + partition_size=$(lsblk -n -o SIZE "$last_partition" | tr -d ' ') + if [[ "$partition_size" == *M ]] && [[ ${partition_size%M} -le 1024 ]]; then + TOOLS_PARTITION="$last_partition" + print_status "Found potential tools partition: $TOOLS_PARTITION ($partition_size)" + fi + fi +fi + +# If still no tools partition found, create one +if [[ -z "$TOOLS_PARTITION" ]]; then + print_warning "No tools partition found. Creating one..." + + # Create 512MB partition at the end + if command -v parted >/dev/null 2>&1; then + parted "$EXTERNAL_DRIVE" --script mkpart primary ext4 -512MiB 100% || { + print_warning "Could not create partition. Backup tools will not be preserved." + exit 0 + } + + # Wait for partition to appear + sleep 2 + + # Get the new partition + mapfile -t partitions < <(lsblk -n -o NAME "$EXTERNAL_DRIVE" | grep -v "^$(basename "$EXTERNAL_DRIVE")$") + TOOLS_PARTITION="/dev/${partitions[-1]}" + + # Format it + mkfs.ext4 -L "BACKUP_TOOLS" "$TOOLS_PARTITION" -F >/dev/null 2>&1 || { + print_warning "Could not format tools partition" + exit 0 + } + + print_success "Created tools partition: $TOOLS_PARTITION" + else + print_warning "parted not available. Cannot create tools partition." + exit 0 + fi +fi + +# Mount tools partition +MOUNT_POINT="/tmp/backup_tools_restore_$$" +mkdir -p "$MOUNT_POINT" + +mount "$TOOLS_PARTITION" "$MOUNT_POINT" 2>/dev/null || { + print_error "Could not mount tools partition $TOOLS_PARTITION" + rmdir "$MOUNT_POINT" + exit 1 +} + +# Ensure we unmount on exit +trap 'umount "$MOUNT_POINT" 2>/dev/null; rmdir "$MOUNT_POINT" 2>/dev/null' EXIT + +# Get the directory where this script is located (source of backup tools) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Install/update backup tools +if [[ -d "$MOUNT_POINT/backup_system" ]]; then + print_status "Updating existing backup tools..." + # Sync changes, excluding git and cache files + rsync -av --delete --exclude='.git' --exclude='__pycache__' --exclude='*.pyc' \ + "$SCRIPT_DIR/" "$MOUNT_POINT/backup_system/" +else + print_status "Installing backup tools for first time..." + mkdir -p "$MOUNT_POINT/backup_system" + cp -r "$SCRIPT_DIR"/* "$MOUNT_POINT/backup_system/" +fi + +# Create portable launcher if it doesn't exist +if [[ ! -f "$MOUNT_POINT/backup_system/launch_backup_tools.sh" ]]; then + cat > "$MOUNT_POINT/backup_system/launch_backup_tools.sh" << 'EOF' +#!/bin/bash +# Portable launcher for backup tools + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Make sure scripts are executable +chmod +x *.sh *.py + +echo "==========================================" +echo " Portable Backup Tools" +echo "==========================================" +echo "" +echo "Available options:" +echo "1. Launch GUI Backup Manager" +echo "2. Command Line Backup" +echo "3. Command Line Restore" +echo "4. List Available Drives" +echo "5. Create Desktop Entry" +echo "" + +# Check if GUI is available +if [[ -n "$DISPLAY" ]] && command -v python3 >/dev/null 2>&1; then + read -p "Select option (1-5): " choice + case $choice in + 1) + echo "Launching GUI Backup Manager..." + python3 backup_manager.py + ;; + 2) + echo "Starting command line backup..." + ./backup_script.sh + ;; + 3) + echo "Starting command line restore..." + ./backup_script.sh --restore + ;; + 4) + echo "Available drives:" + ./backup_script.sh --list + ;; + 5) + ./create_desktop_entry.sh + ;; + *) + echo "Invalid option" + ;; + esac +else + echo "GUI not available. Use command line options:" + ./backup_script.sh --help +fi +EOF +fi + +# Create desktop entry creator +cat > "$MOUNT_POINT/backup_system/create_desktop_entry.sh" << 'EOF' +#!/bin/bash +# Create desktop entry for portable backup tools + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DESKTOP_DIR="$HOME/Desktop" +APPLICATIONS_DIR="$HOME/.local/share/applications" + +mkdir -p "$APPLICATIONS_DIR" + +# Create application entry +cat > "$APPLICATIONS_DIR/portable-backup.desktop" << EOL +[Desktop Entry] +Version=1.0 +Type=Application +Name=Portable Backup Manager +Comment=Boot from external drive and restore to internal +Exec=python3 "$SCRIPT_DIR/backup_manager.py" +Icon=drive-harddisk +Terminal=false +Categories=System;Utility; +StartupNotify=true +EOL + +# Create desktop shortcut +if [[ -d "$DESKTOP_DIR" ]]; then + cp "$APPLICATIONS_DIR/portable-backup.desktop" "$DESKTOP_DIR/" + chmod +x "$DESKTOP_DIR/portable-backup.desktop" + echo "Desktop shortcut created: $DESKTOP_DIR/portable-backup.desktop" +fi + +echo "Application entry created: $APPLICATIONS_DIR/portable-backup.desktop" +EOF + +# Make all scripts executable +chmod +x "$MOUNT_POINT/backup_system"/*.sh +chmod +x "$MOUNT_POINT/backup_system"/*.py + +# Create a README for the external drive +cat > "$MOUNT_POINT/backup_system/README_EXTERNAL.md" << 'EOF' +# Portable Backup Tools + +This external drive contains both: +1. **Your system backup** (main partitions) +2. **Backup tools** (this partition) + +## When Booted From This External Drive: + +### Quick Start: +```bash +# Mount tools and launch +sudo mkdir -p /mnt/tools +sudo mount LABEL=BACKUP_TOOLS /mnt/tools +cd /mnt/tools/backup_system +./launch_backup_tools.sh +``` + +### Or Create Desktop Entry: +```bash +cd /mnt/tools/backup_system +./create_desktop_entry.sh +``` + +## Common Operations: + +### Restore Internal Drive: +1. Boot from this external drive +2. Launch backup tools +3. Select "Restore from External" +4. Choose external β†’ internal +5. Click "Reboot & Restore" + +### Update Backup: +1. Boot normally from internal drive +2. Connect this external drive +3. Run backup as usual +4. Tools will be automatically preserved + +## Drive Layout: +- Partition 1-2: System backup (bootable) +- Last Partition: Backup tools (this) +EOF + +print_success "Backup tools preserved on external drive" +print_status "Tools available at: $TOOLS_PARTITION" +print_status "To access when booted from external: mount LABEL=BACKUP_TOOLS /mnt/tools" + +exit 0 diff --git a/setup_portable_tools.sh b/setup_portable_tools.sh new file mode 100755 index 0000000..00b7efb --- /dev/null +++ b/setup_portable_tools.sh @@ -0,0 +1,368 @@ +#!/bin/bash +# Portable Backup Tool Installer for External M.2 SSD +# This script sets up the backup tools on the external drive so they survive cloning operations + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Detect external drives +detect_external_drive() { + print_status "Detecting external M.2 SSD..." + + # Look for USB-connected drives + local external_drives=() + while IFS= read -r line; do + if [[ $line == *"disk"* ]] && [[ $line == *"usb"* ]]; then + local drive_name=$(echo "$line" | awk '{print $1}') + local drive_size=$(echo "$line" | awk '{print $4}') + external_drives+=("/dev/$drive_name ($drive_size)") + fi + done < <(lsblk -d -o NAME,SIZE,TYPE,TRAN | grep -E "disk.*usb") + + if [[ ${#external_drives[@]} -eq 0 ]]; then + print_error "No external USB drives found. Please connect your M.2 SSD." + exit 1 + fi + + if [[ ${#external_drives[@]} -eq 1 ]]; then + EXTERNAL_DRIVE=$(echo "${external_drives[0]}" | cut -d' ' -f1) + print_success "Auto-detected external drive: ${external_drives[0]}" + else + print_status "Multiple external drives found:" + for i in "${!external_drives[@]}"; do + echo " $((i+1)). ${external_drives[i]}" + done + read -p "Select drive number: " selection + EXTERNAL_DRIVE=$(echo "${external_drives[$((selection-1))]}" | cut -d' ' -f1) + fi +} + +# Create backup tools partition +create_tools_partition() { + print_status "Setting up backup tools partition on $EXTERNAL_DRIVE..." + + # Check if there's already a small partition at the end + local last_partition=$(lsblk -n -o NAME "$EXTERNAL_DRIVE" | tail -1) + local tools_partition="${EXTERNAL_DRIVE}p3" # Assuming nvme, adjust for sda + + # If it's sda style, adjust + if [[ $EXTERNAL_DRIVE == *"sda"* ]]; then + tools_partition="${EXTERNAL_DRIVE}3" + fi + + # Check if tools partition already exists + if lsblk | grep -q "$(basename "$tools_partition")"; then + print_warning "Tools partition already exists: $tools_partition" + TOOLS_PARTITION="$tools_partition" + return + fi + + print_warning "This will create a 512MB partition at the end of $EXTERNAL_DRIVE" + print_warning "This will slightly reduce the cloneable space but preserve backup tools" + read -p "Continue? (yes/no): " confirm + + if [[ "$confirm" != "yes" ]]; then + print_error "Operation cancelled" + exit 1 + fi + + # Create 512MB partition at the end using parted + sudo parted "$EXTERNAL_DRIVE" --script mkpart primary ext4 -512MiB 100% + + # Get the new partition name + TOOLS_PARTITION=$(lsblk -n -o NAME "$EXTERNAL_DRIVE" | tail -1) + TOOLS_PARTITION="/dev/$TOOLS_PARTITION" + + # Format the partition + sudo mkfs.ext4 -L "BACKUP_TOOLS" "$TOOLS_PARTITION" + + print_success "Tools partition created: $TOOLS_PARTITION" +} + +# Install backup tools to external drive +install_tools_to_external() { + local mount_point="/mnt/backup_tools" + + print_status "Installing backup tools to external drive..." + + # Create mount point + sudo mkdir -p "$mount_point" + + # Mount tools partition + sudo mount "$TOOLS_PARTITION" "$mount_point" + + # Copy all backup tools + sudo cp -r . "$mount_point/backup_system" + + # Create launcher script that works from any location + sudo tee "$mount_point/backup_system/launch_backup_tools.sh" > /dev/null << 'EOF' +#!/bin/bash +# Portable launcher for backup tools + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Make sure scripts are executable +chmod +x *.sh *.py + +# Launch GUI if X11 is available, otherwise show CLI options +if [[ -n "$DISPLAY" ]]; then + echo "Launching Backup Manager GUI..." + python3 backup_manager.py +else + echo "No GUI available. Command line options:" + ./backup_script.sh --help +fi +EOF + + sudo chmod +x "$mount_point/backup_system/launch_backup_tools.sh" + + # Create desktop entry for when booted from external drive + sudo tee "$mount_point/backup_system/create_desktop_entry.sh" > /dev/null << 'EOF' +#!/bin/bash +# Create desktop entry when booted from external drive + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DESKTOP_DIR="$HOME/Desktop" +APPLICATIONS_DIR="$HOME/.local/share/applications" + +mkdir -p "$APPLICATIONS_DIR" + +# Create desktop entry +cat > "$APPLICATIONS_DIR/portable-backup.desktop" << EOL +[Desktop Entry] +Version=1.0 +Type=Application +Name=Portable Backup Manager +Comment=Restore internal drive from external backup +Exec=python3 "$SCRIPT_DIR/backup_manager.py" +Icon=drive-harddisk +Terminal=false +Categories=System;Utility; +StartupNotify=true +EOL + +# Create desktop shortcut if Desktop exists +if [[ -d "$DESKTOP_DIR" ]]; then + cp "$APPLICATIONS_DIR/portable-backup.desktop" "$DESKTOP_DIR/" + chmod +x "$DESKTOP_DIR/portable-backup.desktop" +fi + +echo "Desktop entry created for portable backup tools" +EOF + + sudo chmod +x "$mount_point/backup_system/create_desktop_entry.sh" + + # Unmount + sudo umount "$mount_point" + + print_success "Backup tools installed to external drive" +} + +# Create post-backup restoration script +create_restoration_script() { + print_status "Creating post-backup tool restoration script..." + + # This script will be called after each backup to restore the tools + cat > "restore_tools_after_backup.sh" << 'EOF' +#!/bin/bash +# Restore backup tools after cloning operation +# This script runs automatically after backup to preserve tools on external drive + +set -e + +print_status() { + echo "[$(date '+%H:%M:%S')] $1" +} + +# Detect the external drive (should be the target of backup) +EXTERNAL_DRIVE="$1" +if [[ -z "$EXTERNAL_DRIVE" ]]; then + print_status "Auto-detecting external drive..." + EXTERNAL_DRIVE=$(lsblk -d -o NAME,TRAN | grep usb | awk '{print "/dev/" $1}' | head -1) +fi + +if [[ -z "$EXTERNAL_DRIVE" ]]; then + echo "Error: Could not detect external drive" + exit 1 +fi + +print_status "Restoring backup tools to $EXTERNAL_DRIVE" + +# Find the tools partition (should be the last partition) +TOOLS_PARTITION=$(lsblk -n -o NAME "$EXTERNAL_DRIVE" | tail -1) +TOOLS_PARTITION="/dev/$TOOLS_PARTITION" + +# Check if tools partition exists +if ! lsblk | grep -q "$(basename "$TOOLS_PARTITION")"; then + echo "Warning: Tools partition not found. Backup tools may have been overwritten." + exit 1 +fi + +# Mount tools partition +MOUNT_POINT="/mnt/backup_tools_restore" +mkdir -p "$MOUNT_POINT" +mount "$TOOLS_PARTITION" "$MOUNT_POINT" + +# Check if tools exist +if [[ -d "$MOUNT_POINT/backup_system" ]]; then + print_status "Backup tools preserved on external drive" + + # Update the tools with any changes from source + rsync -av --exclude='.git' "$(dirname "$0")/" "$MOUNT_POINT/backup_system/" + + print_status "Backup tools updated on external drive" +else + print_status "Installing backup tools to external drive for first time" + cp -r "$(dirname "$0")" "$MOUNT_POINT/backup_system" +fi + +# Ensure scripts are executable +chmod +x "$MOUNT_POINT/backup_system"/*.sh +chmod +x "$MOUNT_POINT/backup_system"/*.py + +umount "$MOUNT_POINT" +print_status "Backup tools restoration complete" +EOF + + chmod +x "restore_tools_after_backup.sh" + + print_success "Post-backup restoration script created" +} + +# Update backup scripts to call restoration +update_backup_scripts() { + print_status "Updating backup scripts to preserve tools..." + + # Add tool restoration to GUI backup manager + if ! grep -q "restore_tools_after_backup" backup_manager.py; then + # Add restoration call after successful backup + sed -i '/self\.log("Backup completed successfully!")/a\\n # Restore backup tools to external drive\n try:\n subprocess.run([os.path.join(os.path.dirname(__file__), "restore_tools_after_backup.sh"), target], check=False)\n except Exception as e:\n self.log(f"Warning: Could not restore tools to external drive: {e}")' backup_manager.py + fi + + # Add tool restoration to command line script + if ! grep -q "restore_tools_after_backup" backup_script.sh; then + sed -i '/success "Drive cloning completed successfully!"/a\\n # Restore backup tools to external drive\n log "Restoring backup tools to external drive..."\n if [[ -f "$(dirname "$0")/restore_tools_after_backup.sh" ]]; then\n "$(dirname "$0")/restore_tools_after_backup.sh" "$target" || log "Warning: Could not restore tools"\n fi' backup_script.sh + fi + + print_success "Backup scripts updated to preserve tools" +} + +# Create auto-mount script for tools partition +create_automount_script() { + print_status "Creating auto-mount script for backup tools..." + + cat > "mount_backup_tools.sh" << 'EOF' +#!/bin/bash +# Auto-mount backup tools partition and create desktop shortcut + +# Find tools partition by label +TOOLS_PARTITION=$(blkid -L "BACKUP_TOOLS" 2>/dev/null) + +if [[ -z "$TOOLS_PARTITION" ]]; then + echo "Backup tools partition not found" + exit 1 +fi + +# Create mount point +MOUNT_POINT="$HOME/backup_tools" +mkdir -p "$MOUNT_POINT" + +# Mount if not already mounted +if ! mountpoint -q "$MOUNT_POINT"; then + mount "$TOOLS_PARTITION" "$MOUNT_POINT" 2>/dev/null || { + echo "Mounting with sudo..." + sudo mount "$TOOLS_PARTITION" "$MOUNT_POINT" + } +fi + +echo "Backup tools mounted at: $MOUNT_POINT" + +# Create desktop entry if tools exist +if [[ -d "$MOUNT_POINT/backup_system" ]]; then + cd "$MOUNT_POINT/backup_system" + ./create_desktop_entry.sh + echo "Desktop entry created for backup tools" +fi +EOF + + chmod +x "mount_backup_tools.sh" + + print_success "Auto-mount script created" +} + +# Main installation +main() { + echo "" + echo "==============================================" + echo " Portable Backup Tools Installer" + echo " For External M.2 SSD" + echo "==============================================" + echo "" + + print_warning "This installer will:" + print_warning "1. Create a 512MB tools partition on your external M.2 SSD" + print_warning "2. Install backup tools that survive cloning operations" + print_warning "3. Set up automatic tool restoration after backups" + print_warning "4. Enable booting from external drive with restore capability" + echo "" + + read -p "Continue? (yes/no): " confirm + if [[ "$confirm" != "yes" ]]; then + print_error "Installation cancelled" + exit 1 + fi + + detect_external_drive + create_tools_partition + install_tools_to_external + create_restoration_script + update_backup_scripts + create_automount_script + + echo "" + print_success "Portable backup tools installation complete!" + echo "" + echo "Your external M.2 SSD now has:" + echo " β€’ Preserved backup tools in separate partition" + echo " β€’ Automatic tool restoration after each backup" + echo " β€’ Bootable system restoration capability" + echo "" + echo "When booted from external drive:" + echo " β€’ Run: ~/backup_tools/backup_system/launch_backup_tools.sh" + echo " β€’ Or use desktop shortcut if available" + echo "" + print_warning "Note: Your external drive now has 512MB less space for cloning" + print_warning "But the backup tools will always be available for system recovery!" +} + +# Check if running as root +if [[ $EUID -eq 0 ]]; then + print_error "Do not run as root. Script will use sudo when needed." + exit 1 +fi + +main "$@" diff --git a/simple_auto_backup.sh b/simple_auto_backup.sh new file mode 100755 index 0000000..c20d6fd --- /dev/null +++ b/simple_auto_backup.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Simple launcher script for automated backup from within Clonezilla + +echo "===================================" +echo " AUTOMATED SYSTEM BACKUP LAUNCHER" +echo "===================================" +echo +echo "This script will:" +echo "1. Auto-detect your internal drive" +echo "2. Create a high-speed backup to this USB" +echo "3. Complete in ~15-20 minutes" +echo +read -p "Press Enter to start automatic backup (Ctrl+C to cancel)..." + +# Mount backup partition +mkdir -p /tmp/backup_storage +mount /dev/sda2 /tmp/backup_storage 2>/dev/null + +if [[ -f /tmp/backup_storage/automated_clonezilla_backup.sh ]]; then + echo "Starting automated backup script..." + /tmp/backup_storage/automated_clonezilla_backup.sh +else + echo "ERROR: Automated backup script not found!" + echo "Falling back to manual Clonezilla..." + sleep 3 + sudo /usr/sbin/ocs-live-general +fi diff --git a/simple_grub_repair.sh b/simple_grub_repair.sh new file mode 100755 index 0000000..5c40bbd --- /dev/null +++ b/simple_grub_repair.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +# Simple GRUB Boot Repair Script +# Direct GRUB repair without chroot complications + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +VG_NAME="system-vg" +EXTERNAL_DRIVE="/dev/sda" + +log() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; } +success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + +echo -e "${GREEN}=== Simple GRUB Boot Repair ===${NC}" +echo + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + error "This script must be run as root. Use: sudo $0" +fi + +log "Mounting external system..." +mkdir -p /tmp/ext-boot /tmp/ext-efi + +# Mount the boot and EFI partitions +mount "/dev/$VG_NAME/boot" /tmp/ext-boot || error "Failed to mount boot" +mount "${EXTERNAL_DRIVE}1" /tmp/ext-efi || error "Failed to mount EFI" + +success "Mounted external boot partitions" + +log "Installing GRUB to external drive..." + +# Install GRUB directly to the external drive +grub-install --target=x86_64-efi \ + --efi-directory=/tmp/ext-efi \ + --bootloader-id=debian \ + --boot-directory=/tmp/ext-boot \ + --recheck \ + "$EXTERNAL_DRIVE" || error "GRUB installation failed" + +success "GRUB installed successfully" + +log "Checking installation..." + +# Check if GRUB EFI file was created +if [ -f "/tmp/ext-efi/EFI/debian/grubx64.efi" ]; then + success "GRUB EFI bootloader created successfully" +else + error "GRUB EFI bootloader not found after installation" +fi + +# List EFI directory contents +log "EFI boot entries after repair:" +ls -la /tmp/ext-efi/EFI/ || warning "Could not list EFI entries" + +log "Cleaning up..." +umount /tmp/ext-boot +umount /tmp/ext-efi +rmdir /tmp/ext-boot /tmp/ext-efi + +success "GRUB repair completed!" + +echo +echo -e "${GREEN}=== Next Steps ===${NC}" +echo "1. Reboot your system" +echo "2. Enter BIOS/UEFI settings" +echo "3. Set boot order to prioritize external M.2 SSD" +echo "4. Look for 'debian' entry in boot menu" +echo "5. Boot from external drive" +echo +echo -e "${YELLOW}BIOS Settings:${NC}" +echo "β€’ Disable Secure Boot (if enabled)" +echo "β€’ Enable UEFI mode (disable Legacy/CSM)" +echo "β€’ Set external M.2 as first boot device" +echo +success "External M.2 should now boot properly!" \ No newline at end of file diff --git a/troubleshoot_migration.sh b/troubleshoot_migration.sh new file mode 100755 index 0000000..9532bc5 --- /dev/null +++ b/troubleshoot_migration.sh @@ -0,0 +1,200 @@ +#!/bin/bash + +# LVM Migration Troubleshooting Script +# Helps diagnose issues with the migration process + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } +success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + +echo -e "${GREEN}=== LVM Migration Troubleshooting ===${NC}" +echo + +# Check basic system requirements +check_system() { + log "Checking system requirements..." + + # Check if running as root + if [ "$EUID" -ne 0 ]; then + error "This script must be run as root. Use: sudo $0" + return 1 + fi + + # Check if running from live system + local root_device=$(df / | tail -1 | awk '{print $1}') + if [[ "$root_device" == *"loop"* ]] || [[ "$root_device" == *"overlay"* ]] || [[ "$root_device" == *"tmpfs"* ]]; then + success "Running from live system" + else + warning "Not running from live system - migration may fail" + fi + + # Check available tools + local tools=("lvm" "cryptsetup" "rsync" "parted" "pv" "grub-install" "mkfs.ext4" "mkfs.fat" "bc" "wipefs") + local missing=() + + for tool in "${tools[@]}"; do + if command -v "$tool" >/dev/null 2>&1; then + success "Tool available: $tool" + else + missing+=("$tool") + fi + done + + if [ ${#missing[@]} -gt 0 ]; then + error "Missing tools: ${missing[*]}" + echo "Run: sudo ./emergency_install.sh to install missing packages" + return 1 + fi +} + +# Check drives +check_drives() { + log "Checking available drives..." + + echo "All block devices:" + lsblk -dpno NAME,SIZE,MODEL,VENDOR + echo + + # Look for likely candidates + local internal_drives=($(lsblk -dpno NAME | grep -E "nvme|sda")) + local usb_drives=() + + # Check for USB drives + for drive in $(lsblk -dpno NAME); do + if udevadm info --query=property --name="$drive" | grep -q "ID_BUS=usb"; then + usb_drives+=("$drive") + fi + done + + echo "Likely internal drives:" + for drive in "${internal_drives[@]}"; do + local info=$(lsblk -dpno NAME,SIZE,MODEL "$drive") + echo " $info" + done + + echo "USB drives (external/migration stick):" + for drive in "${usb_drives[@]}"; do + local info=$(lsblk -dpno NAME,SIZE,MODEL "$drive") + echo " $info" + done +} + +# Check LVM status +check_lvm() { + log "Checking LVM status..." + + echo "Physical volumes:" + pvs 2>/dev/null || echo "No physical volumes found" + + echo "Volume groups:" + vgs 2>/dev/null || echo "No volume groups found" + + echo "Logical volumes:" + lvs 2>/dev/null || echo "No logical volumes found" + + # Check for existing system-vg + if vgs system-vg 2>/dev/null; then + warning "system-vg already exists - migration may have partially completed" + echo "To restart migration, you may need to:" + echo " vgremove -f system-vg" + echo " pvremove /dev/sdaX" + fi +} + +# Check filesystem space and usage +check_filesystems() { + log "Checking current filesystem usage..." + + echo "Current mounts and usage:" + df -h | grep -E "/$|/home$|/boot$" + + echo + echo "System memory:" + free -h +} + +# Test LVM commands +test_lvm_commands() { + log "Testing LVM command availability..." + + # Test basic LVM commands + lvm version || error "LVM not working" + + # Test if we can create a test PV (on a loop device) + log "Testing LVM functionality with loop device..." + + # Create a small test file + dd if=/dev/zero of=/tmp/lvm-test.img bs=1M count=100 2>/dev/null + local loop_device=$(losetup -f --show /tmp/lvm-test.img) + + if pvcreate "$loop_device" 2>/dev/null; then + success "LVM pvcreate works" + + if vgcreate test-vg "$loop_device" 2>/dev/null; then + success "LVM vgcreate works" + + if lvcreate -L 50M -n test-lv test-vg 2>/dev/null; then + success "LVM lvcreate works" + success "All LVM commands working correctly" + else + error "LVM lvcreate failed" + fi + + # Cleanup + lvremove -f test-vg/test-lv 2>/dev/null || true + vgremove -f test-vg 2>/dev/null || true + else + error "LVM vgcreate failed" + fi + + pvremove -f "$loop_device" 2>/dev/null || true + else + error "LVM pvcreate failed" + fi + + # Cleanup + losetup -d "$loop_device" 2>/dev/null || true + rm -f /tmp/lvm-test.img +} + +# Generate report +generate_report() { + echo + echo -e "${BLUE}=== Troubleshooting Report ===${NC}" + echo "Generated: $(date)" + echo "System: $(hostname)" + + if check_system && check_drives && check_lvm && check_filesystems && test_lvm_commands; then + echo + success "All checks passed - system should be ready for migration" + echo + echo "To run migration:" + echo "1. sudo ./migrate_to_lvm.sh" + echo "2. Follow the interactive prompts" + echo "3. Carefully select source and target drives" + else + echo + error "Some checks failed - see messages above" + echo + echo "Common solutions:" + echo "β€’ Run: sudo ./emergency_install.sh (for missing packages)" + echo "β€’ Reboot from live USB (if not in live system)" + echo "β€’ Check drive connections (if drives not detected)" + echo "β€’ Remove existing LVM setup (if system-vg exists)" + fi +} + +# Main execution +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + generate_report +fi \ No newline at end of file diff --git a/validate_lvm_migration.sh b/validate_lvm_migration.sh new file mode 100755 index 0000000..fc999e8 --- /dev/null +++ b/validate_lvm_migration.sh @@ -0,0 +1,265 @@ +#!/bin/bash + +# LVM Migration Validation Script +# Comprehensive validation that the migration from non-LVM to LVM was successful + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +VG_NAME="system-vg" +EXTERNAL_DRIVE="/dev/sda" +EXPECTED_LVS=("root" "home" "boot" "swap") +VALIDATION_LOG="/var/log/lvm-migration-validation.log" + +log() { + local message="[$(date '+%Y-%m-%d %H:%M:%S')] $1" + echo -e "${BLUE}$message${NC}" + echo "$message" >> "$VALIDATION_LOG" 2>/dev/null || true +} + +error() { + local message="[ERROR] $1" + echo -e "${RED}$message${NC}" >&2 + echo "$message" >> "$VALIDATION_LOG" 2>/dev/null || true +} + +warning() { + local message="[WARNING] $1" + echo -e "${YELLOW}$message${NC}" + echo "$message" >> "$VALIDATION_LOG" 2>/dev/null || true +} + +success() { + local message="[SUCCESS] $1" + echo -e "${GREEN}$message${NC}" + echo "$message" >> "$VALIDATION_LOG" 2>/dev/null || true +} + +check_lvm_volumes() { + log "Checking LVM volumes..." + + if ! vgs "$VG_NAME" >/dev/null 2>&1; then + error "Volume group $VG_NAME not found" + return 1 + fi + + local expected_lvs=("root" "home" "swap" "boot") + for lv in "${expected_lvs[@]}"; do + if ! lvs "$VG_NAME/$lv" >/dev/null 2>&1; then + error "Logical volume $VG_NAME/$lv not found" + return 1 + else + success "Found logical volume: $VG_NAME/$lv" + fi + done +} + +check_filesystems() { + log "Checking filesystems..." + + local volumes=("root" "home" "boot") + for vol in "${volumes[@]}"; do + local device="/dev/$VG_NAME/$vol" + if fsck.ext4 -n "$device" >/dev/null 2>&1; then + success "Filesystem on $device is clean" + else + error "Filesystem on $device has errors" + return 1 + fi + done + + # Check EFI partition + if fsck.fat -v "${EXTERNAL_DRIVE}1" >/dev/null 2>&1; then + success "EFI filesystem is clean" + else + warning "EFI filesystem check failed (this might be normal)" + fi +} + +check_boot_files() { + log "Checking boot files..." + + local mount_point="/mnt/validation_check" + mkdir -p "$mount_point" + + # Check root partition for essential directories + mount "/dev/$VG_NAME/root" "$mount_point" + + local essential_dirs=("/bin" "/sbin" "/etc" "/usr" "/var") + for dir in "${essential_dirs[@]}"; do + if [ -d "$mount_point$dir" ]; then + success "Found essential directory: $dir" + else + error "Missing essential directory: $dir" + umount "$mount_point" + return 1 + fi + done + + # Check for key system files + local key_files=("/etc/fstab" "/etc/passwd" "/etc/group") + for file in "${key_files[@]}"; do + if [ -f "$mount_point$file" ]; then + success "Found key system file: $file" + else + error "Missing key system file: $file" + umount "$mount_point" + return 1 + fi + done + + umount "$mount_point" +} + +check_grub_installation() { + log "Checking GRUB installation..." + + local efi_mount="/mnt/efi_check" + mkdir -p "$efi_mount" + mount "${EXTERNAL_DRIVE}1" "$efi_mount" + + if [ -d "$efi_mount/EFI/debian" ]; then + success "GRUB EFI installation found" + else + error "GRUB EFI installation not found" + umount "$efi_mount" + return 1 + fi + + if [ -f "$efi_mount/EFI/debian/grubx64.efi" ]; then + success "GRUB EFI binary found" + else + error "GRUB EFI binary not found" + umount "$efi_mount" + return 1 + fi + + umount "$efi_mount" +} + +check_fstab() { + log "Checking /etc/fstab configuration..." + + local mount_point="/mnt/fstab_check" + mkdir -p "$mount_point" + mount "/dev/$VG_NAME/root" "$mount_point" + + if [ -f "$mount_point/etc/fstab" ]; then + success "Found /etc/fstab" + + # Check if fstab contains LVM UUIDs + local root_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/root") + local home_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/home") + local boot_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/boot") + + if grep -q "$root_uuid" "$mount_point/etc/fstab"; then + success "Root UUID found in fstab" + else + error "Root UUID not found in fstab" + fi + + if grep -q "$home_uuid" "$mount_point/etc/fstab"; then + success "Home UUID found in fstab" + else + error "Home UUID not found in fstab" + fi + + if grep -q "$boot_uuid" "$mount_point/etc/fstab"; then + success "Boot UUID found in fstab" + else + error "Boot UUID not found in fstab" + fi + else + error "/etc/fstab not found" + fi + + umount "$mount_point" +} + +check_snapshot_capability() { + log "Checking LVM snapshot capability..." + + # Check free space for snapshots + local vg_free=$(vgs --noheadings -o vg_free --units g "$VG_NAME" | tr -d ' G') + local vg_free_int=${vg_free%.*} + + if [ "$vg_free_int" -ge 20 ]; then + success "Sufficient free space for snapshots: ${vg_free}G available" + else + warning "Limited free space for snapshots: ${vg_free}G available (recommend 20G+)" + fi + + # Test snapshot creation and removal + log "Testing snapshot creation..." + if lvcreate -L 1G -s -n test-snapshot "/dev/$VG_NAME/root" >/dev/null 2>&1; then + success "Snapshot creation test successful" + if lvremove -f "/dev/$VG_NAME/test-snapshot" >/dev/null 2>&1; then + success "Snapshot removal test successful" + else + error "Snapshot removal test failed" + fi + else + error "Snapshot creation test failed" + return 1 + fi +} + +check_lvm_tools() { + log "Checking for LVM snapshot script..." + + local mount_point="/mnt/script_check" + mkdir -p "$mount_point" + mount "/dev/$VG_NAME/root" "$mount_point" + + if [ -f "$mount_point/usr/local/bin/lvm-snapshot-backup.sh" ]; then + success "LVM snapshot backup script found" + if [ -x "$mount_point/usr/local/bin/lvm-snapshot-backup.sh" ]; then + success "LVM snapshot backup script is executable" + else + error "LVM snapshot backup script is not executable" + fi + else + error "LVM snapshot backup script not found" + fi + + umount "$mount_point" +} + +main() { + echo -e "${GREEN}=== LVM Migration Validation ===${NC}" + echo "Validating the migrated LVM system..." + echo + + local failed_checks=0 + + check_lvm_volumes || ((failed_checks++)) + check_filesystems || ((failed_checks++)) + check_boot_files || ((failed_checks++)) + check_grub_installation || ((failed_checks++)) + check_fstab || ((failed_checks++)) + check_snapshot_capability || ((failed_checks++)) + check_lvm_tools || ((failed_checks++)) + + echo + if [ $failed_checks -eq 0 ]; then + success "All validation checks passed!" + echo -e "${GREEN}The migration appears to be successful.${NC}" + echo "You can now:" + echo "1. Update BIOS/UEFI boot order to boot from external M.2" + echo "2. Test booting from the external drive" + echo "3. Use 'lvm-snapshot-backup.sh backup' for backups" + else + error "$failed_checks validation check(s) failed!" + echo -e "${RED}The migration may have issues. Review the errors above.${NC}" + return 1 + fi +} + +main "$@" \ No newline at end of file