Compare commits
19 Commits
5828140a35
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb076fd9b3 | ||
|
|
a8bc889e47 | ||
|
|
bfd3c08fb3 | ||
|
|
543b95cb61 | ||
|
|
7708b98674 | ||
|
|
f12d07be7f | ||
|
|
c86fee78cb | ||
|
|
aee3d5019c | ||
|
|
179a84e442 | ||
|
|
72f9838f55 | ||
|
|
871a57947d | ||
|
|
56c07dbe49 | ||
|
|
29347fc8a6 | ||
| a02628b22e | |||
| ab4a99b978 | |||
|
|
d697a7bcb5 | ||
|
|
26f6994e17 | ||
|
|
5b7cc3773c | ||
|
|
ceb1f6fa4f |
479
README.md
479
README.md
@@ -1,391 +1,144 @@
|
|||||||
# System Backup to External M.2 SSD
|
# Enhanced Simple LVM Backup System with Borg Support
|
||||||
|
|
||||||
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.
|
A reliable, straightforward backup system for LVM-enabled Linux systems. Born from the need for simple, dependable backups without complex logic that can cause system issues.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
**Simple is better.** This system does exactly three things:
|
||||||
|
1. Create LVM snapshot
|
||||||
|
2. Copy data (dd/pv for raw, Borg for repositories)
|
||||||
|
3. Clean up snapshot
|
||||||
|
|
||||||
|
No complex migration logic, no fancy features that can break. Just reliable backups.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
## Features
|
### Five Backup Modes
|
||||||
|
|
||||||
- **GUI Interface**: User-friendly graphical interface built with Python Tkinter
|
- **LV → LV**: Update existing logical volume backups
|
||||||
- **Command Line Interface**: Full CLI support for automated and scripted operations
|
- **LV → Raw**: Create fresh backups on raw devices
|
||||||
- **Smart Drive Detection**: Automatically detects internal drive and external M.2 SSDs
|
- **VG → Raw**: Clone entire volume groups with LVM metadata
|
||||||
- **Full System Backup**: Complete drive cloning with dd for exact system replication
|
- **LV → Borg**: Backup logical volume **block device** to Borg repository (preserves exact block-level state)
|
||||||
- **Smart Sync Backup**: ⚡ NEW! Fast incremental backups using rsync for minor changes
|
- **VG → Borg**: Backup **all block devices** from volume group to Borg repository
|
||||||
- **Change Analysis**: Analyze filesystem changes to recommend sync vs full backup
|
|
||||||
- **Restore Functionality**: Complete system restore from external drive
|
### Two Interfaces
|
||||||
- **Portable Tools**: Backup tools survive on external drive and remain accessible after cloning
|
|
||||||
- **Reboot Integration**: Optional reboot before backup/restore operations
|
- **GUI**: `simple_backup_gui.py` - User-friendly interface with Borg configuration
|
||||||
- **Progress Monitoring**: Real-time progress display and logging
|
- **CLI**: `enhanced_simple_backup.sh` - Command-line power user interface
|
||||||
- **Safety Features**: Multiple confirmations and drive validation
|
|
||||||
- **Desktop Integration**: Create desktop shortcuts for easy access
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. See Available Options
|
||||||
|
```bash
|
||||||
|
sudo ./list_drives.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Choose Your Backup Mode
|
||||||
|
|
||||||
|
**Update existing backup:**
|
||||||
|
```bash
|
||||||
|
sudo ./enhanced_simple_backup.sh lv-to-lv /dev/internal-vg/root /dev/backup-vg/root
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fresh backup to external drive:**
|
||||||
|
```bash
|
||||||
|
sudo ./enhanced_simple_backup.sh lv-to-raw /dev/internal-vg/root /dev/sdb
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone entire system:**
|
||||||
|
```bash
|
||||||
|
sudo ./enhanced_simple_backup.sh vg-to-raw internal-vg /dev/sdb
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use the GUI:**
|
||||||
|
```bash
|
||||||
|
sudo python3 simple_backup_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Borg repository backups (block-level):**
|
||||||
|
```bash
|
||||||
|
# Create new Borg repo and backup LV as block device
|
||||||
|
sudo ./enhanced_simple_backup.sh lv-to-borg /dev/internal-vg/root /path/to/borg/repo --new-repo --encryption repokey --passphrase mypass
|
||||||
|
|
||||||
|
# Backup entire VG as multiple block devices to existing repo
|
||||||
|
sudo ./enhanced_simple_backup.sh vg-to-borg internal-vg /path/to/borg/repo --passphrase mypass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Difference:**
|
||||||
|
- **LV → Borg**: Stores one snapshot as `{lv_name}.img` in a single archive
|
||||||
|
- **VG → Borg**: Creates separate archives for each LV (space-efficient, processes one LV at a time)
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
### Core System
|
||||||
|
- `simple_backup_gui.py` - GUI interface with mode selection
|
||||||
|
- `enhanced_simple_backup.sh` - CLI with three backup modes
|
||||||
|
- `list_drives.sh` - Helper to show available sources/targets
|
||||||
|
- `SIMPLE_BACKUP_README.md` - Detailed documentation
|
||||||
|
|
||||||
|
### Legacy Files
|
||||||
|
- `old_scripts/` - Previous complex implementations (archived)
|
||||||
|
- Various `.md` files - Documentation from previous iterations
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Linux system (tested on Ubuntu/Debian)
|
- LVM-enabled Linux system
|
||||||
- Python 3.6+ with tkinter
|
- Root access (`sudo`)
|
||||||
- External M.2 SSD in USB enclosure
|
- Python 3 + tkinter (for GUI)
|
||||||
- sudo privileges for drive operations
|
- `pv` command (optional, for progress display)
|
||||||
|
- **Borg Backup** (for Borg modes): `sudo apt install borgbackup`
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Clone or download this repository:
|
|
||||||
```bash
|
|
||||||
git clone <repository-url>
|
|
||||||
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
|
## Safety Features
|
||||||
|
|
||||||
- **Drive Validation**: Prevents accidental overwriting of wrong drives
|
- Clear confirmations before destructive operations
|
||||||
- **Size Checking**: Ensures target drive is large enough
|
- Automatic snapshot cleanup on errors
|
||||||
- **Confirmation Prompts**: Multiple confirmation steps before destructive operations
|
- Emergency stop functionality (GUI)
|
||||||
- **Mount Detection**: Automatically unmounts target drives before backup
|
- Input validation and error handling
|
||||||
- **Progress Monitoring**: Real-time feedback during backup operations
|
- Timestamped snapshot names to avoid collisions on retries
|
||||||
|
- Read-only snapshot mounts with filesystem-aware safety flags (ext4: noload, xfs: norecovery)
|
||||||
|
- Strict shell mode in CLI (set -euo pipefail) for reliable error propagation
|
||||||
|
- Pre-flight checks for VG free space before creating snapshots
|
||||||
|
|
||||||
## File Structure
|
## What This System Does NOT Do
|
||||||
|
|
||||||
```
|
- Complex migration between different LVM setups
|
||||||
backup_to_external_m.2/
|
- Automatic boot repair or GRUB handling
|
||||||
├── backup_manager.py # GUI application
|
- Multiple backup formats or compression
|
||||||
├── backup_script.sh # Command-line script
|
- Network backups or cloud integration
|
||||||
├── install.sh # Installation script
|
- File-level incremental backups
|
||||||
├── systemd/ # Systemd service files
|
|
||||||
│ └── backup-service.service
|
|
||||||
└── README.md # This file
|
|
||||||
```
|
|
||||||
|
|
||||||
## Smart Sync Technology ⚡
|
For those features, use dedicated tools like Borg Backup, CloneZilla, or rsync.
|
||||||
|
|
||||||
The backup system now includes advanced **Smart Sync** functionality that dramatically reduces backup time for incremental updates:
|
## Why This Approach?
|
||||||
|
|
||||||
### How Smart Sync Works
|
Previous versions of this project included complex migration logic that occasionally caused system issues. This version returns to basics:
|
||||||
|
|
||||||
1. **Analysis Phase**: Compares source and target filesystems to determine changes
|
✅ Uses standard LVM commands only
|
||||||
2. **Decision Engine**: Recommends sync vs full clone based on amount of changes:
|
✅ Minimal chance of errors
|
||||||
- **< 2GB changes**: Smart sync recommended (much faster)
|
✅ Easy to understand and debug
|
||||||
- **2-10GB changes**: Smart sync beneficial
|
✅ Predictable behavior
|
||||||
- **> 10GB changes**: Full clone may be more appropriate
|
✅ No hidden "smart" features
|
||||||
3. **Sync Operation**: Uses rsync to transfer only changed files and metadata
|
|
||||||
|
|
||||||
### Smart Sync Benefits
|
Notes and limitations:
|
||||||
|
- Multi-PV VGs: Raw clone and VG→Borg flows require careful handling. If a VG spans multiple PVs, the simple raw clone path is not supported; use block-level per-LV backups or full-disk cloning tools.
|
||||||
|
- File-level backups are for data safety, not bootability. Use block-level backups for exact bootable images.
|
||||||
|
- Ensure the Borg repository is not located on the same LV being snapshotted.
|
||||||
|
|
||||||
- **Speed**: 10-100x faster than full clone for minor changes
|
## Recovery
|
||||||
- **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
|
### From LV Backup
|
||||||
|
Mount the backup LV and copy files, or use it to restore your system.
|
||||||
|
|
||||||
**Use Smart Sync when:**
|
### From Raw Device Backup
|
||||||
- You have an existing backup on the target drive
|
Boot directly from the device, or restore using dd in reverse.
|
||||||
- Regular incremental updates (daily/weekly backups)
|
|
||||||
- Minimal system changes since last backup
|
|
||||||
- You want faster backup with minimal downtime
|
|
||||||
|
|
||||||
**Use Full Clone when:**
|
### From VG Clone
|
||||||
- First-time backup to a new drive
|
Import the volume group or boot directly from the cloned device.
|
||||||
- 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
|
## Contributing
|
||||||
|
|
||||||
1. Fork the repository
|
Keep it simple. Any additions should follow the core principle: minimal logic, maximum reliability.
|
||||||
2. Create a feature branch
|
|
||||||
3. Make your changes
|
|
||||||
4. Test thoroughly
|
|
||||||
5. Submit a pull request
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is open source. Use at your own risk.
|
Open source - use and modify as needed.
|
||||||
|
|
||||||
## 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.
|
|
||||||
119
SIMPLE_BACKUP_README.md
Normal file
119
SIMPLE_BACKUP_README.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# Simple LVM Backup System
|
||||||
|
|
||||||
|
**A much simpler, safer approach to LVM backups.**
|
||||||
|
|
||||||
|
After issues with complex backup systems, this is a return to basics:
|
||||||
|
- Simple LVM snapshot creation
|
||||||
|
- Direct block-level copy with dd/pv
|
||||||
|
- Minimal logic, maximum reliability
|
||||||
|
|
||||||
|
## What This Does
|
||||||
|
|
||||||
|
1. **Creates an LVM snapshot** of your source volume
|
||||||
|
2. **Copies it block-for-block** to your target drive
|
||||||
|
3. **Cleans up the snapshot** when done
|
||||||
|
|
||||||
|
That's it. No complex migration logic, no fancy features that can break.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. See what's available
|
||||||
|
```bash
|
||||||
|
sudo ./list_drives.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Run a simple backup
|
||||||
|
```bash
|
||||||
|
sudo ./simple_backup.sh /dev/your-vg/your-lv /dev/your-target-drive
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Or use the GUI
|
||||||
|
```bash
|
||||||
|
sudo python3 simple_backup_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Backup your root volume to an external SSD:**
|
||||||
|
```bash
|
||||||
|
sudo ./simple_backup.sh /dev/internal-vg/root /dev/sdb
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backup your home volume:**
|
||||||
|
```bash
|
||||||
|
sudo ./simple_backup.sh /dev/internal-vg/home /dev/nvme1n1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
⚠️ **WARNING: The target drive will be completely overwritten!**
|
||||||
|
|
||||||
|
- Always run as root (`sudo`)
|
||||||
|
- Target device will lose ALL existing data
|
||||||
|
- Make sure target device is unmounted before backup
|
||||||
|
- The backup is a complete block-level clone
|
||||||
|
- You need at least 1GB free space in your volume group for the snapshot
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
This is exactly what happens, no hidden complexity:
|
||||||
|
|
||||||
|
1. `lvcreate -L1G -s -n backup_snap /dev/vg/lv` - Create snapshot
|
||||||
|
2. `pv /dev/vg/backup_snap | dd of=/dev/target bs=4M` - Copy data
|
||||||
|
3. `lvremove -f /dev/vg/backup_snap` - Remove snapshot
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `simple_backup.sh` - Command-line backup script
|
||||||
|
- `simple_backup_gui.py` - Simple GUI version
|
||||||
|
- `list_drives.sh` - Helper to show available drives
|
||||||
|
|
||||||
|
## Why This Approach?
|
||||||
|
|
||||||
|
The previous complex scripts had too much logic and caused system issues. This approach:
|
||||||
|
|
||||||
|
- ✅ Uses standard LVM commands
|
||||||
|
- ✅ Minimal chance of errors
|
||||||
|
- ✅ Easy to understand and debug
|
||||||
|
- ✅ Does exactly what you expect
|
||||||
|
- ✅ No hidden "smart" features
|
||||||
|
|
||||||
|
## Recovery
|
||||||
|
|
||||||
|
To boot from your backup:
|
||||||
|
1. Connect the external drive
|
||||||
|
2. Boot from it directly, or
|
||||||
|
3. Use it as a recovery drive to restore your system
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- LVM-enabled system
|
||||||
|
- Root access
|
||||||
|
- Python 3 + tkinter (for GUI)
|
||||||
|
- `pv` command (optional, for progress display)
|
||||||
|
|
||||||
|
## If Something Goes Wrong
|
||||||
|
|
||||||
|
The script will try to clean up snapshots automatically. If it fails:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List any remaining snapshots
|
||||||
|
sudo lvs | grep snap
|
||||||
|
|
||||||
|
# Remove manually if needed
|
||||||
|
sudo lvremove /dev/vg/snapshot_name
|
||||||
|
```
|
||||||
|
|
||||||
|
## No More Complex Features
|
||||||
|
|
||||||
|
This system intentionally does NOT include:
|
||||||
|
- Automatic drive detection with complex logic
|
||||||
|
- Migration between different LVM setups
|
||||||
|
- Boot repair or GRUB handling
|
||||||
|
- Multiple backup formats
|
||||||
|
- Configuration files
|
||||||
|
- Complex error handling
|
||||||
|
|
||||||
|
If you need those features, use dedicated tools like CloneZilla or Borg Backup.
|
||||||
|
|
||||||
|
This is for simple, reliable block-level LVM backups. Nothing more, nothing less.
|
||||||
Binary file not shown.
@@ -1,986 +0,0 @@
|
|||||||
#!/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_var.get().split()[0]
|
|
||||||
target = self.target_var.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_var.get().split()[0]
|
|
||||||
target = self.target_var.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.output_text.delete(1.0, tk.END)
|
|
||||||
|
|
||||||
# Determine command arguments
|
|
||||||
if mode == "analyze":
|
|
||||||
cmd = ['sudo', './backup_script.sh', '--analyze', '--source', source, '--target', target]
|
|
||||||
self.log_message("🔍 Analyzing changes between drives...")
|
|
||||||
elif mode == "sync":
|
|
||||||
cmd = ['sudo', './backup_script.sh', '--sync', '--source', source, '--target', target]
|
|
||||||
self.log_message("⚡ Starting smart sync backup...")
|
|
||||||
elif mode == "backup":
|
|
||||||
cmd = ['sudo', './backup_script.sh', '--source', source, '--target', target]
|
|
||||||
self.log_message("🔄 Starting full backup...")
|
|
||||||
elif mode == "restore":
|
|
||||||
cmd = ['sudo', './backup_script.sh', '--restore', '--source', source, '--target', target]
|
|
||||||
self.log_message("🔧 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.output_text.insert(tk.END, output)
|
|
||||||
self.output_text.see(tk.END)
|
|
||||||
self.root.update()
|
|
||||||
|
|
||||||
# Get final result
|
|
||||||
return_code = process.poll()
|
|
||||||
|
|
||||||
if return_code == 0:
|
|
||||||
if mode == "analyze":
|
|
||||||
self.log_message("✅ Analysis completed successfully!")
|
|
||||||
messagebox.showinfo("Analysis Complete", "Drive analysis completed. Check the output for recommendations.")
|
|
||||||
elif mode == "sync":
|
|
||||||
self.log_message("✅ Smart sync completed successfully!")
|
|
||||||
messagebox.showinfo("Success", "Smart sync backup completed successfully!")
|
|
||||||
elif mode == "backup":
|
|
||||||
self.log_message("✅ Backup completed successfully!")
|
|
||||||
messagebox.showinfo("Success", "Full backup completed successfully!")
|
|
||||||
elif mode == "restore":
|
|
||||||
self.log_message("✅ Restore completed successfully!")
|
|
||||||
messagebox.showinfo("Success", "System restore completed successfully!")
|
|
||||||
else:
|
|
||||||
self.log_message(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_message(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_var.get().split()[0]
|
|
||||||
target = self.target_var.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_var.get().split()[0]
|
|
||||||
target = self.target_var.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"""
|
|
||||||
if not self.validate_selection():
|
|
||||||
return
|
|
||||||
|
|
||||||
result = messagebox.askyesno("Confirm Reboot & Backup",
|
|
||||||
"This will:\n"
|
|
||||||
"1. Save current session\n"
|
|
||||||
"2. Reboot the system\n"
|
|
||||||
"3. Start backup after reboot\n\n"
|
|
||||||
"Continue?")
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Create backup script for after reboot
|
|
||||||
script_content = self.create_reboot_operation_script("backup")
|
|
||||||
|
|
||||||
# Save script
|
|
||||||
script_path = "/tmp/backup_after_reboot.sh"
|
|
||||||
with open(script_path, 'w') as f:
|
|
||||||
f.write(script_content)
|
|
||||||
|
|
||||||
os.chmod(script_path, 0o755)
|
|
||||||
|
|
||||||
self.log("Reboot backup script created")
|
|
||||||
self.log("System will reboot in 5 seconds...")
|
|
||||||
|
|
||||||
# Schedule reboot
|
|
||||||
subprocess.run(['sudo', 'shutdown', '-r', '+1'], check=True)
|
|
||||||
|
|
||||||
self.log("Reboot scheduled. Backup 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 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()
|
|
||||||
599
enhanced_simple_backup.sh
Executable file
599
enhanced_simple_backup.sh
Executable file
@@ -0,0 +1,599 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Enhanced Simple LVM Backup Script
|
||||||
|
# Supports three backup modes:
|
||||||
|
# 1. LV → LV: Update existing logical volume backup
|
||||||
|
# 2. LV → Raw: Create fresh backup on raw device
|
||||||
|
# 3. VG → Raw: Clone entire volume group to raw device
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Enhanced Simple LVM Backup Script with Borg Support"
|
||||||
|
echo ""
|
||||||
|
echo "Usage:"
|
||||||
|
echo " $0 lv-to-lv SOURCE_LV TARGET_LV"
|
||||||
|
echo " $0 lv-to-raw SOURCE_LV TARGET_DEVICE"
|
||||||
|
echo " $0 vg-to-raw SOURCE_VG TARGET_DEVICE"
|
||||||
|
echo " $0 lv-to-borg SOURCE_LV REPO_PATH [--new-repo] [--encryption MODE] [--passphrase PASS]"
|
||||||
|
echo " $0 vg-to-borg SOURCE_VG REPO_PATH [--new-repo] [--encryption MODE] [--passphrase PASS]"
|
||||||
|
echo " $0 files-to-borg SOURCE_LV REPO_PATH [--new-repo] [--encryption MODE] [--passphrase PASS]"
|
||||||
|
echo ""
|
||||||
|
echo "Modes:"
|
||||||
|
echo " lv-to-lv - Update existing LV backup (SOURCE_LV → TARGET_LV)"
|
||||||
|
echo " lv-to-raw - Create fresh backup (SOURCE_LV → raw device)"
|
||||||
|
echo " vg-to-raw - Clone entire VG (SOURCE_VG → raw device)"
|
||||||
|
echo " lv-to-borg - Backup LV to Borg repository (block-level)"
|
||||||
|
echo " vg-to-borg - Backup entire VG to Borg repository (block-level)"
|
||||||
|
echo " files-to-borg - Backup LV files to Borg repository (file-level, space-efficient)"
|
||||||
|
echo ""
|
||||||
|
echo "Borg Options:"
|
||||||
|
echo " --new-repo Create new repository"
|
||||||
|
echo " --encryption MODE Encryption mode: none, repokey, keyfile (default: repokey)"
|
||||||
|
echo " --passphrase PASS Repository passphrase"
|
||||||
|
echo " --generous-snapshots Use 25% of LV size for snapshots (for very active systems)"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 lv-to-lv /dev/internal-vg/root /dev/backup-vg/root"
|
||||||
|
echo " $0 lv-to-raw /dev/internal-vg/root /dev/sdb"
|
||||||
|
echo " $0 vg-to-raw internal-vg /dev/sdb"
|
||||||
|
echo " $0 lv-to-borg /dev/internal-vg/root /path/to/borg/repo --new-repo"
|
||||||
|
echo " $0 files-to-borg /dev/internal-vg/home /path/to/borg/repo --new-repo"
|
||||||
|
echo ""
|
||||||
|
echo "List available sources/targets:"
|
||||||
|
echo " ./list_drives.sh"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo -e "${GREEN}[$(date '+%H:%M:%S')] $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo -e "${RED}[ERROR] $1${NC}" >&2
|
||||||
|
cleanup_and_exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
echo -e "${YELLOW}[WARNING] $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
echo -e "${BLUE}[INFO] $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_and_exit() {
|
||||||
|
local exit_code=${1:-0}
|
||||||
|
|
||||||
|
if [ -n "$SNAPSHOT_PATH" ] && lvs "$SNAPSHOT_PATH" >/dev/null 2>&1; then
|
||||||
|
warn "Cleaning up snapshot: $SNAPSHOT_PATH"
|
||||||
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap for cleanup
|
||||||
|
trap 'cleanup_and_exit 130' INT TERM
|
||||||
|
|
||||||
|
# Parse arguments and options
|
||||||
|
if [ $# -lt 3 ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODE="$1"
|
||||||
|
SOURCE="$2"
|
||||||
|
TARGET="$3"
|
||||||
|
shift 3
|
||||||
|
|
||||||
|
# Parse Borg-specific options
|
||||||
|
NEW_REPO=false
|
||||||
|
ENCRYPTION="repokey"
|
||||||
|
PASSPHRASE=""
|
||||||
|
GENEROUS_SNAPSHOTS=false
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--new-repo)
|
||||||
|
NEW_REPO=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--encryption)
|
||||||
|
ENCRYPTION="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--passphrase)
|
||||||
|
PASSPHRASE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--generous-snapshots)
|
||||||
|
GENEROUS_SNAPSHOTS=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
error "This script must be run as root"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate mode
|
||||||
|
case "$MODE" in
|
||||||
|
"lv-to-lv"|"lv-to-raw"|"vg-to-raw"|"lv-to-borg"|"vg-to-borg"|"files-to-borg")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Invalid mode: $MODE"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check Borg requirements for Borg modes
|
||||||
|
if [[ "$MODE" == *"-to-borg" ]]; then
|
||||||
|
if ! command -v borg >/dev/null 2>&1; then
|
||||||
|
error "Borg Backup is not installed. Please install it: sudo apt install borgbackup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set up Borg environment
|
||||||
|
if [ -n "$PASSPHRASE" ]; then
|
||||||
|
export BORG_PASSPHRASE="$PASSPHRASE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Enhanced Simple LVM Backup"
|
||||||
|
log "Mode: $MODE"
|
||||||
|
log "Source: $SOURCE"
|
||||||
|
log "Target: $TARGET"
|
||||||
|
|
||||||
|
# Mode-specific validation and execution
|
||||||
|
case "$MODE" in
|
||||||
|
"lv-to-lv")
|
||||||
|
# LV to LV backup
|
||||||
|
if [ ! -e "$SOURCE" ]; then
|
||||||
|
error "Source LV does not exist: $SOURCE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "$TARGET" ]; then
|
||||||
|
error "Target LV does not exist: $TARGET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract VG and LV names
|
||||||
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE" | tr -d ' ')
|
||||||
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
||||||
|
SNAPSHOT_NAME="${LV_NAME}_backup_snap_$(date +%s)"
|
||||||
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
||||||
|
|
||||||
|
info "This will update the existing backup LV"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Source LV: $SOURCE${NC}"
|
||||||
|
echo -e "${YELLOW}Target LV: $TARGET${NC}"
|
||||||
|
echo -e "${YELLOW}The target LV will be overwritten with current source data${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? (yes/no): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Creating snapshot of source LV"
|
||||||
|
lvcreate -L1G -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
||||||
|
|
||||||
|
log "Copying snapshot to target LV"
|
||||||
|
if command -v pv >/dev/null 2>&1; then
|
||||||
|
pv "$SNAPSHOT_PATH" | dd of="$TARGET" bs=4M || error "Copy failed"
|
||||||
|
else
|
||||||
|
dd if="$SNAPSHOT_PATH" of="$TARGET" bs=4M status=progress || error "Copy failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Cleaning up snapshot"
|
||||||
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
||||||
|
SNAPSHOT_PATH=""
|
||||||
|
|
||||||
|
log "LV to LV backup completed successfully"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"lv-to-raw")
|
||||||
|
# LV to raw device backup
|
||||||
|
if [ ! -e "$SOURCE" ]; then
|
||||||
|
error "Source LV does not exist: $SOURCE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "$TARGET" ]; then
|
||||||
|
error "Target device does not exist: $TARGET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract VG and LV names
|
||||||
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE" | tr -d ' ')
|
||||||
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
||||||
|
SNAPSHOT_NAME="${LV_NAME}_backup_snap_$(date +%s)"
|
||||||
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
||||||
|
|
||||||
|
info "This will create a fresh backup on raw device"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Source LV: $SOURCE${NC}"
|
||||||
|
echo -e "${YELLOW}Target Device: $TARGET${NC}"
|
||||||
|
echo -e "${RED}WARNING: All data on $TARGET will be lost!${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? (yes/no): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Creating snapshot of source LV"
|
||||||
|
lvcreate -L1G -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
||||||
|
|
||||||
|
log "Copying snapshot to target device"
|
||||||
|
if command -v pv >/dev/null 2>&1; then
|
||||||
|
pv "$SNAPSHOT_PATH" | dd of="$TARGET" bs=4M || error "Copy failed"
|
||||||
|
else
|
||||||
|
dd if="$SNAPSHOT_PATH" of="$TARGET" bs=4M status=progress || error "Copy failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Cleaning up snapshot"
|
||||||
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
||||||
|
SNAPSHOT_PATH=""
|
||||||
|
|
||||||
|
log "LV to raw device backup completed successfully"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"vg-to-raw")
|
||||||
|
# VG to raw device backup
|
||||||
|
if ! vgs "$SOURCE" >/dev/null 2>&1; then
|
||||||
|
error "Source VG does not exist: $SOURCE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "$TARGET" ]; then
|
||||||
|
error "Target device does not exist: $TARGET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the first PV of the source VG
|
||||||
|
SOURCE_PV=$(vgs --noheadings -o pv_name "$SOURCE" | head -n1 | tr -d ' ')
|
||||||
|
|
||||||
|
if [ -z "$SOURCE_PV" ]; then
|
||||||
|
error "No physical volumes found for VG: $SOURCE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "This will clone the entire volume group"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Source VG: $SOURCE${NC}"
|
||||||
|
echo -e "${YELLOW}Source PV: $SOURCE_PV${NC}"
|
||||||
|
echo -e "${YELLOW}Target Device: $TARGET${NC}"
|
||||||
|
echo -e "${RED}WARNING: All data on $TARGET will be lost!${NC}"
|
||||||
|
echo -e "${BLUE}This preserves LVM metadata and all logical volumes${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? (yes/no): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Copying entire PV to target device"
|
||||||
|
log "This preserves LVM structure and all LVs"
|
||||||
|
|
||||||
|
if command -v pv >/dev/null 2>&1; then
|
||||||
|
pv "$SOURCE_PV" | dd of="$TARGET" bs=4M || error "Copy failed"
|
||||||
|
else
|
||||||
|
dd if="$SOURCE_PV" of="$TARGET" bs=4M status=progress || error "Copy failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "VG to raw device backup completed successfully"
|
||||||
|
log "Target device now contains complete LVM structure"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"lv-to-borg")
|
||||||
|
# LV to Borg repository backup
|
||||||
|
if [ ! -e "$SOURCE" ]; then
|
||||||
|
error "Source LV does not exist: $SOURCE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract VG and LV names
|
||||||
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE" | tr -d ' ')
|
||||||
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
||||||
|
SNAPSHOT_NAME="${LV_NAME}_borg_snap_$(date +%s)"
|
||||||
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
||||||
|
|
||||||
|
# Get LV size to determine appropriate snapshot size
|
||||||
|
LV_SIZE_BYTES=$(lvs --noheadings -o lv_size --units b "$SOURCE" | tr -d ' ' | sed 's/B$//')
|
||||||
|
# Use different percentages based on options
|
||||||
|
if [ "$GENEROUS_SNAPSHOTS" = true ]; then
|
||||||
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES / 4)) # 25%
|
||||||
|
MIN_SIZE=$((2 * 1024 * 1024 * 1024)) # 2GB minimum
|
||||||
|
else
|
||||||
|
# Auto mode: 10% normally, 15% for large LVs
|
||||||
|
if [ $LV_SIZE_BYTES -gt $((50 * 1024 * 1024 * 1024)) ]; then
|
||||||
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES * 15 / 100)) # 15% for >50GB
|
||||||
|
else
|
||||||
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES / 10)) # 10% normally
|
||||||
|
fi
|
||||||
|
MIN_SIZE=$((1024 * 1024 * 1024)) # 1GB minimum
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $SNAPSHOT_SIZE_BYTES -lt $MIN_SIZE ]; then
|
||||||
|
SNAPSHOT_SIZE_BYTES=$MIN_SIZE
|
||||||
|
fi
|
||||||
|
SNAPSHOT_SIZE_GB=$((SNAPSHOT_SIZE_BYTES / 1073741824))
|
||||||
|
SNAPSHOT_SIZE="${SNAPSHOT_SIZE_GB}G"
|
||||||
|
|
||||||
|
info "This will backup LV to Borg repository"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Source LV: $SOURCE${NC}"
|
||||||
|
echo -e "${YELLOW}Repository: $TARGET${NC}"
|
||||||
|
echo -e "${BLUE}Snapshot size: $SNAPSHOT_SIZE${NC}"
|
||||||
|
if [ "$NEW_REPO" = true ]; then
|
||||||
|
echo -e "${BLUE}Will create new repository${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${BLUE}Will add to existing repository${NC}"
|
||||||
|
fi
|
||||||
|
echo -e "${BLUE}Encryption: $ENCRYPTION${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? (yes/no): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize repository if needed
|
||||||
|
if [ "$NEW_REPO" = true ]; then
|
||||||
|
log "Creating new Borg repository: $TARGET"
|
||||||
|
if [ "$ENCRYPTION" = "none" ]; then
|
||||||
|
borg init --encryption=none "$TARGET" || error "Failed to initialize repository"
|
||||||
|
else
|
||||||
|
borg init --encryption="$ENCRYPTION" "$TARGET" || error "Failed to initialize repository"
|
||||||
|
fi
|
||||||
|
log "Repository initialized successfully"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Creating snapshot of source LV"
|
||||||
|
lvcreate -L"$SNAPSHOT_SIZE" -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
||||||
|
|
||||||
|
# Create Borg archive
|
||||||
|
ARCHIVE_NAME="lv_${LV_NAME}_$(date +%Y%m%d_%H%M%S)"
|
||||||
|
log "Creating Borg archive (block-level): $ARCHIVE_NAME"
|
||||||
|
log "Backing up raw snapshot block device to Borg..."
|
||||||
|
log "This preserves exact block-level state including filesystem metadata"
|
||||||
|
|
||||||
|
dd if="$SNAPSHOT_PATH" bs=4M | borg create --stdin-name "${LV_NAME}.img" --progress --stats "$TARGET::$ARCHIVE_NAME" - || error "Borg backup failed"
|
||||||
|
|
||||||
|
log "Block-level Borg backup completed successfully"
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
log "Cleaning up snapshot"
|
||||||
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
||||||
|
SNAPSHOT_PATH=""
|
||||||
|
|
||||||
|
log "LV to Borg backup completed successfully"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"vg-to-borg")
|
||||||
|
# VG to Borg repository backup
|
||||||
|
if ! vgs "$SOURCE" >/dev/null 2>&1; then
|
||||||
|
error "Source VG does not exist: $SOURCE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "This will backup entire VG to Borg repository"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Source VG: $SOURCE${NC}"
|
||||||
|
echo -e "${YELLOW}Repository: $TARGET${NC}"
|
||||||
|
if [ "$NEW_REPO" = true ]; then
|
||||||
|
echo -e "${BLUE}Will create new repository${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${BLUE}Will add to existing repository${NC}"
|
||||||
|
fi
|
||||||
|
echo -e "${BLUE}Encryption: $ENCRYPTION${NC}"
|
||||||
|
echo -e "${BLUE}This will backup all logical volumes in the VG${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? (yes/no): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize repository if needed
|
||||||
|
if [ "$NEW_REPO" = true ]; then
|
||||||
|
log "Creating new Borg repository: $TARGET"
|
||||||
|
if [ "$ENCRYPTION" = "none" ]; then
|
||||||
|
borg init --encryption=none "$TARGET" || error "Failed to initialize repository"
|
||||||
|
else
|
||||||
|
borg init --encryption="$ENCRYPTION" "$TARGET" || error "Failed to initialize repository"
|
||||||
|
fi
|
||||||
|
log "Repository initialized successfully"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get all LVs in VG
|
||||||
|
LV_LIST=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
||||||
|
if [ -z "$LV_LIST" ]; then
|
||||||
|
error "No logical volumes found in VG: $SOURCE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Found logical volumes: $(echo $LV_LIST | tr '\n' ' ')"
|
||||||
|
|
||||||
|
# Process each LV one by one to avoid space issues
|
||||||
|
for LV_NAME in $LV_LIST; do
|
||||||
|
SNAPSHOT_NAME="${LV_NAME}_borg_snap_$(date +%s)"
|
||||||
|
SNAPSHOT_PATH="/dev/$SOURCE/$SNAPSHOT_NAME"
|
||||||
|
LV_PATH="/dev/$SOURCE/$LV_NAME"
|
||||||
|
|
||||||
|
# Get LV size to determine appropriate snapshot size
|
||||||
|
LV_SIZE_BYTES=$(lvs --noheadings -o lv_size --units b "$LV_PATH" | tr -d ' ' | sed 's/B$//')
|
||||||
|
# Use different percentages based on options
|
||||||
|
if [ "$GENEROUS_SNAPSHOTS" = true ]; then
|
||||||
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES / 4)) # 25%
|
||||||
|
MIN_SIZE=$((2 * 1024 * 1024 * 1024)) # 2GB minimum
|
||||||
|
else
|
||||||
|
# Auto mode: 10% normally, 15% for large LVs
|
||||||
|
if [ $LV_SIZE_BYTES -gt $((50 * 1024 * 1024 * 1024)) ]; then
|
||||||
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES * 15 / 100)) # 15% for >50GB
|
||||||
|
else
|
||||||
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES / 10)) # 10% normally
|
||||||
|
fi
|
||||||
|
MIN_SIZE=$((1024 * 1024 * 1024)) # 1GB minimum
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $SNAPSHOT_SIZE_BYTES -lt $MIN_SIZE ]; then
|
||||||
|
SNAPSHOT_SIZE_BYTES=$MIN_SIZE
|
||||||
|
fi
|
||||||
|
SNAPSHOT_SIZE_GB=$((SNAPSHOT_SIZE_BYTES / 1073741824))
|
||||||
|
SNAPSHOT_SIZE="${SNAPSHOT_SIZE_GB}G"
|
||||||
|
|
||||||
|
log "Processing LV: $LV_NAME"
|
||||||
|
log "Creating snapshot: $SNAPSHOT_NAME (size: $SNAPSHOT_SIZE)"
|
||||||
|
lvcreate -L"$SNAPSHOT_SIZE" -s -n "$SNAPSHOT_NAME" "$LV_PATH" || {
|
||||||
|
warn "Failed to create snapshot for $LV_NAME"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create individual archive for this LV
|
||||||
|
ARCHIVE_NAME="vg_${SOURCE}_lv_${LV_NAME}_$(date +%Y%m%d_%H%M%S)"
|
||||||
|
log "Backing up $LV_NAME to archive: $ARCHIVE_NAME"
|
||||||
|
log "Streaming raw block device directly to Borg..."
|
||||||
|
|
||||||
|
dd if="$SNAPSHOT_PATH" bs=4M | borg create --stdin-name "${LV_NAME}.img" --progress --stats "$TARGET::$ARCHIVE_NAME" - || {
|
||||||
|
warn "Backup of $LV_NAME failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean up this snapshot immediately to save space
|
||||||
|
log "Removing snapshot $SNAPSHOT_PATH"
|
||||||
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot $SNAPSHOT_PATH"
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Block-level VG Borg backup completed successfully"
|
||||||
|
log "Created individual archives for each LV in VG $SOURCE"
|
||||||
|
|
||||||
|
log "VG to Borg backup completed successfully"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"files-to-borg")
|
||||||
|
# Files to Borg repository backup
|
||||||
|
if [ ! -e "$SOURCE" ]; then
|
||||||
|
error "Source LV does not exist: $SOURCE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract VG and LV names
|
||||||
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE" | tr -d ' ')
|
||||||
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE" | tr -d ' ')
|
||||||
|
SNAPSHOT_NAME="${LV_NAME}_files_snap_$(date +%s)"
|
||||||
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
||||||
|
|
||||||
|
# Get LV size to determine appropriate snapshot size (file-level needs much smaller)
|
||||||
|
LV_SIZE_BYTES=$(lvs --noheadings -o lv_size --units b "$SOURCE" | tr -d ' ' | sed 's/B$//')
|
||||||
|
if [ "$GENEROUS_SNAPSHOTS" = true ]; then
|
||||||
|
# File-level generous: 5% with 1G min, 20G max
|
||||||
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES * 5 / 100))
|
||||||
|
[ $SNAPSHOT_SIZE_BYTES -lt $((1 * 1024 * 1024 * 1024)) ] && SNAPSHOT_SIZE_BYTES=$((1 * 1024 * 1024 * 1024))
|
||||||
|
[ $SNAPSHOT_SIZE_BYTES -gt $((20 * 1024 * 1024 * 1024)) ] && SNAPSHOT_SIZE_BYTES=$((20 * 1024 * 1024 * 1024))
|
||||||
|
else
|
||||||
|
# Auto: 3% with 1G min, 15G max
|
||||||
|
SNAPSHOT_SIZE_BYTES=$((LV_SIZE_BYTES * 3 / 100))
|
||||||
|
[ $SNAPSHOT_SIZE_BYTES -lt $((1 * 1024 * 1024 * 1024)) ] && SNAPSHOT_SIZE_BYTES=$((1 * 1024 * 1024 * 1024))
|
||||||
|
[ $SNAPSHOT_SIZE_BYTES -gt $((15 * 1024 * 1024 * 1024)) ] && SNAPSHOT_SIZE_BYTES=$((15 * 1024 * 1024 * 1024))
|
||||||
|
fi
|
||||||
|
SNAPSHOT_SIZE_GB=$((SNAPSHOT_SIZE_BYTES / 1073741824))
|
||||||
|
SNAPSHOT_SIZE="${SNAPSHOT_SIZE_GB}G"
|
||||||
|
|
||||||
|
info "This will backup LV files to Borg repository (space-efficient)"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Source LV: $SOURCE${NC}"
|
||||||
|
echo -e "${YELLOW}Repository: $TARGET${NC}"
|
||||||
|
echo -e "${BLUE}Snapshot size: $SNAPSHOT_SIZE${NC}"
|
||||||
|
echo -e "${BLUE}Mode: File-level backup (skips empty blocks)${NC}"
|
||||||
|
if [ "$NEW_REPO" = true ]; then
|
||||||
|
echo -e "${BLUE}Will create new repository${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${BLUE}Will add to existing repository${NC}"
|
||||||
|
fi
|
||||||
|
echo -e "${BLUE}Encryption: $ENCRYPTION${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? (yes/no): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize repository if needed
|
||||||
|
if [ "$NEW_REPO" = true ]; then
|
||||||
|
log "Creating new Borg repository: $TARGET"
|
||||||
|
if [ "$ENCRYPTION" = "none" ]; then
|
||||||
|
borg init --encryption=none "$TARGET" || error "Failed to initialize repository"
|
||||||
|
else
|
||||||
|
borg init --encryption="$ENCRYPTION" "$TARGET" || error "Failed to initialize repository"
|
||||||
|
fi
|
||||||
|
log "Repository initialized successfully"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Creating snapshot of source LV"
|
||||||
|
lvcreate -L"$SNAPSHOT_SIZE" -s -n "$SNAPSHOT_NAME" "$SOURCE" || error "Failed to create snapshot"
|
||||||
|
|
||||||
|
# Create temporary mount point
|
||||||
|
TEMP_MOUNT=$(mktemp -d -t borg_files_backup_XXXXXX)
|
||||||
|
|
||||||
|
# Mount snapshot read-only with safe FS options
|
||||||
|
FS_TYPE=$(blkid -o value -s TYPE "$SNAPSHOT_PATH" 2>/dev/null || echo "")
|
||||||
|
if [ "$FS_TYPE" = "ext4" ] || [ "$FS_TYPE" = "ext3" ]; then
|
||||||
|
MNT_OPTS="ro,noload"
|
||||||
|
elif [ "$FS_TYPE" = "xfs" ]; then
|
||||||
|
MNT_OPTS="ro,norecovery"
|
||||||
|
else
|
||||||
|
MNT_OPTS="ro"
|
||||||
|
fi
|
||||||
|
log "Mounting snapshot to $TEMP_MOUNT (opts: $MNT_OPTS)"
|
||||||
|
mount -o "$MNT_OPTS" "$SNAPSHOT_PATH" "$TEMP_MOUNT" || error "Failed to mount snapshot"
|
||||||
|
|
||||||
|
# Create Borg archive
|
||||||
|
ARCHIVE_NAME="files_${LV_NAME}_$(date +%Y%m%d_%H%M%S)"
|
||||||
|
log "Creating Borg archive (file-level): $ARCHIVE_NAME"
|
||||||
|
log "Backing up files from mounted snapshot..."
|
||||||
|
log "This is space-efficient and skips empty blocks"
|
||||||
|
|
||||||
|
borg create --progress --stats --compression auto,zstd "$TARGET::$ARCHIVE_NAME" "$TEMP_MOUNT" || error "Borg file-level backup failed"
|
||||||
|
|
||||||
|
log "File-level Borg backup completed successfully"
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
log "Cleaning up mount point and snapshot"
|
||||||
|
umount "$TEMP_MOUNT" || warn "Failed to unmount"
|
||||||
|
rmdir "$TEMP_MOUNT"
|
||||||
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
||||||
|
SNAPSHOT_PATH=""
|
||||||
|
|
||||||
|
log "Files to Borg backup completed successfully"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}SUCCESS: Backup completed!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
case "$MODE" in
|
||||||
|
"lv-to-lv")
|
||||||
|
echo "- Your backup LV has been updated"
|
||||||
|
echo "- You can mount $TARGET to verify the backup"
|
||||||
|
;;
|
||||||
|
"lv-to-raw")
|
||||||
|
echo "- Your backup device contains a raw copy of the LV"
|
||||||
|
echo "- You can mount $TARGET directly or restore from it"
|
||||||
|
;;
|
||||||
|
"vg-to-raw")
|
||||||
|
echo "- Your backup device contains the complete LVM structure"
|
||||||
|
echo "- You can boot from $TARGET or import the VG for recovery"
|
||||||
|
echo "- To access: vgimport or boot directly from the device"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo ""
|
||||||
99
list_drives.sh
Executable file
99
list_drives.sh
Executable file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Enhanced script to list available backup sources and targets
|
||||||
|
# Shows options for all three backup modes
|
||||||
|
|
||||||
|
echo "=== Enhanced Simple LVM Backup with Borg Support - Available Options ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== SOURCES ==="
|
||||||
|
echo ""
|
||||||
|
echo "Logical Volumes (for lv-to-lv and lv-to-raw modes):"
|
||||||
|
echo "---------------------------------------------------"
|
||||||
|
if command -v lvs >/dev/null 2>&1; then
|
||||||
|
echo "Path Size VG Name"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
lvs --noheadings -o lv_path,lv_size,vg_name | while read lv_path lv_size vg_name; do
|
||||||
|
printf "%-24s %-8s %s\n" "$lv_path" "$lv_size" "$vg_name"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "LVM not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Volume Groups (for vg-to-raw mode):"
|
||||||
|
echo "-----------------------------------"
|
||||||
|
if command -v vgs >/dev/null 2>&1; then
|
||||||
|
echo "VG Name Size PV Count"
|
||||||
|
echo "----------------------------------"
|
||||||
|
vgs --noheadings -o vg_name,vg_size,pv_count | while read vg_name vg_size pv_count; do
|
||||||
|
printf "%-16s %-8s %s PVs\n" "$vg_name" "$vg_size" "$pv_count"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "LVM not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== TARGETS ==="
|
||||||
|
echo ""
|
||||||
|
echo "Existing Logical Volumes (for lv-to-lv mode):"
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
if command -v lvs >/dev/null 2>&1; then
|
||||||
|
echo "Path Size VG Name"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
lvs --noheadings -o lv_path,lv_size,vg_name | while read lv_path lv_size vg_name; do
|
||||||
|
# Highlight external/backup VGs
|
||||||
|
if [[ "$vg_name" == *"migration"* ]] || [[ "$vg_name" == *"external"* ]] || [[ "$vg_name" == *"backup"* ]]; then
|
||||||
|
printf "%-24s %-8s %s (backup VG)\n" "$lv_path" "$lv_size" "$vg_name"
|
||||||
|
else
|
||||||
|
printf "%-24s %-8s %s\n" "$lv_path" "$lv_size" "$vg_name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "LVM not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Raw Block Devices (for lv-to-raw and vg-to-raw modes):"
|
||||||
|
echo "------------------------------------------------------"
|
||||||
|
echo "Device Size Model"
|
||||||
|
echo "-------------------------"
|
||||||
|
lsblk -dno NAME,SIZE,MODEL | grep -E '^sd|^nvme' | while read name size model; do
|
||||||
|
printf "/dev/%-6s %-8s %s\n" "$name" "$size" "$model"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== USAGE EXAMPLES ==="
|
||||||
|
echo ""
|
||||||
|
echo "1. LV to LV (update existing backup):"
|
||||||
|
echo " sudo ./enhanced_simple_backup.sh lv-to-lv /dev/internal-vg/root /dev/backup-vg/root"
|
||||||
|
echo ""
|
||||||
|
echo "2. LV to Raw Device (fresh backup):"
|
||||||
|
echo " sudo ./enhanced_simple_backup.sh lv-to-raw /dev/internal-vg/root /dev/sdb"
|
||||||
|
echo ""
|
||||||
|
echo "3. Entire VG to Raw Device (complete clone):"
|
||||||
|
echo " sudo ./enhanced_simple_backup.sh vg-to-raw internal-vg /dev/sdb"
|
||||||
|
echo ""
|
||||||
|
echo "4. LV to Borg Repository (block-level backup):"
|
||||||
|
echo " sudo ./enhanced_simple_backup.sh lv-to-borg /dev/internal-vg/root /path/to/borg/repo --new-repo"
|
||||||
|
echo " → Stores raw snapshot as 'root.img' in Borg repo"
|
||||||
|
echo ""
|
||||||
|
echo "5. Entire VG to Borg Repository (all LVs as block devices):"
|
||||||
|
echo " sudo ./enhanced_simple_backup.sh vg-to-borg internal-vg /path/to/borg/repo --encryption repokey"
|
||||||
|
echo " → Creates separate archives for each LV (space-efficient, one LV at a time)"
|
||||||
|
echo ""
|
||||||
|
echo "=== GUI VERSION ==="
|
||||||
|
echo " sudo python3 simple_backup_gui.py"
|
||||||
|
echo ""
|
||||||
|
echo "=== IMPORTANT NOTES ==="
|
||||||
|
echo "- Always run as root (sudo)"
|
||||||
|
echo "- lv-to-lv: Updates existing backup LV"
|
||||||
|
echo "- lv-to-raw: Creates fresh backup, overwrites target device"
|
||||||
|
echo "- vg-to-raw: Clones entire VG including LVM metadata"
|
||||||
|
echo "- lv-to-borg/vg-to-borg: Creates block-level backups in Borg (preserves exact LV state)"
|
||||||
|
echo "- LV→Borg: Single archive with one .img file, VG→Borg: Separate archives per LV"
|
||||||
|
echo "- VG→Borg processes one LV at a time to avoid space issues"
|
||||||
|
echo "- Borg backups are deduplicated, compressed, and encrypted"
|
||||||
|
echo "- Borg backups require: sudo apt install borgbackup"
|
||||||
|
echo "- Make sure target devices are unmounted before backup"
|
||||||
|
echo ""
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,576 +0,0 @@
|
|||||||
#!/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 "$@"
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
#!/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 "$@"
|
|
||||||
154
old_scripts/BOOT_FIX_SUMMARY.md
Normal file
154
old_scripts/BOOT_FIX_SUMMARY.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# Internal NVMe Boot Repair Summary
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
After resizing the home partition, the internal NVMe drive (nvme0n1) was showing a "reset system" message on boot, suspected GRUB issue.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
The GRUB bootloader needed to be reinstalled after the partition resize, and boot failure flags needed to be cleared from the GRUB environment.
|
||||||
|
|
||||||
|
## Solution Applied
|
||||||
|
|
||||||
|
### 1. Updated GRUB Configuration
|
||||||
|
```bash
|
||||||
|
sudo update-grub
|
||||||
|
```
|
||||||
|
- Regenerated `/boot/grub/grub.cfg` with current system configuration
|
||||||
|
- Found and registered kernel images: 6.8.0-84 and 6.8.0-83
|
||||||
|
|
||||||
|
### 2. Reinstalled GRUB Bootloader
|
||||||
|
```bash
|
||||||
|
sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --recheck /dev/nvme0n1
|
||||||
|
```
|
||||||
|
- Installed GRUB to internal NVMe drive
|
||||||
|
- Files installed to `/boot/efi/EFI/ubuntu/`:
|
||||||
|
- shimx64.efi (secure boot shim)
|
||||||
|
- grubx64.efi (GRUB bootloader)
|
||||||
|
- grub.cfg (initial configuration)
|
||||||
|
|
||||||
|
### 3. Cleared Boot Failure Flags
|
||||||
|
```bash
|
||||||
|
sudo grub-editenv - unset recordfail
|
||||||
|
sudo grub-editenv - unset boot_success
|
||||||
|
```
|
||||||
|
- Removed any boot failure tracking that might cause "reset system" message
|
||||||
|
- GRUB environment now clean
|
||||||
|
|
||||||
|
## Current System Status
|
||||||
|
|
||||||
|
### Boot Configuration
|
||||||
|
- **Currently Running From**: External USB M.2 (sda - migration-vg)
|
||||||
|
- Root: `/dev/mapper/migration-vg-root` → mounted as `/`
|
||||||
|
- Home: `/dev/mapper/luks-home-internal` (on external) → mounted as `/home`
|
||||||
|
|
||||||
|
- **Internal Drive**: NVMe (nvme0n1 - internal-vg) - NOW BOOTABLE
|
||||||
|
- EFI: `/dev/nvme0n1p1` → mounted as `/boot/efi`
|
||||||
|
- Boot: `/dev/internal-vg/boot` → mounted as `/boot`
|
||||||
|
- Root: `/dev/internal-vg/root` (ready but not currently root)
|
||||||
|
- Home: `/dev/internal-vg/home` (LUKS encrypted, not currently mounted)
|
||||||
|
|
||||||
|
### EFI Boot Entries
|
||||||
|
```
|
||||||
|
BootOrder: 0001,001C,001B,0000,... (Ubuntu is first)
|
||||||
|
Boot0001* Ubuntu - Points to /boot/efi/EFI/ubuntu/shimx64.efi
|
||||||
|
Boot001F* USB HDD - Currently active (external M.2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Partition Layout
|
||||||
|
**Internal NVMe (nvme0n1):**
|
||||||
|
- nvme0n1p1: 511MB EFI partition (757B-A377)
|
||||||
|
- nvme0n1p2: 476.4GB LVM (internal-vg)
|
||||||
|
- internal-vg/root: 56GB ext4
|
||||||
|
- internal-vg/boot: 2GB ext4
|
||||||
|
- internal-vg/home: 404GB LUKS encrypted
|
||||||
|
- internal-vg/swap: 8GB swap
|
||||||
|
|
||||||
|
**External M.2 (sda):**
|
||||||
|
- sda1: EFI partition
|
||||||
|
- sda2: 476.4GB LVM (migration-vg)
|
||||||
|
- Identical structure to internal-vg
|
||||||
|
|
||||||
|
## Testing the Fix
|
||||||
|
|
||||||
|
### To Boot from Internal Drive:
|
||||||
|
1. **Shutdown the system**:
|
||||||
|
```bash
|
||||||
|
sudo shutdown -h now
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Disconnect the external USB M.2 drive** (sda)
|
||||||
|
|
||||||
|
3. **Power on the system**
|
||||||
|
|
||||||
|
4. **Expected Behavior**:
|
||||||
|
- System should boot from internal NVMe
|
||||||
|
- No more "reset system" message
|
||||||
|
- GRUB menu should appear normally
|
||||||
|
- System will boot into internal-vg volumes
|
||||||
|
|
||||||
|
### If You Want to Keep Both Drives Connected:
|
||||||
|
1. Change boot order in BIOS to prefer internal NVMe over USB
|
||||||
|
2. Or use BIOS boot menu (usually F12) to manually select internal drive
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
|
||||||
|
Check internal drive boot readiness:
|
||||||
|
```bash
|
||||||
|
./verify_internal_boot.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Check EFI boot entries:
|
||||||
|
```bash
|
||||||
|
efibootmgr -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify GRUB configuration:
|
||||||
|
```bash
|
||||||
|
sudo cat /boot/grub/grub.cfg | grep -A5 "menuentry"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
1. **Both drives have identical LVM structure** (internal-vg and migration-vg)
|
||||||
|
- Both have root, boot, home, and swap volumes
|
||||||
|
- Home volumes are both LUKS encrypted
|
||||||
|
|
||||||
|
2. **Current /etc/fstab** is configured for internal-vg:
|
||||||
|
```
|
||||||
|
/dev/internal-vg/root → /
|
||||||
|
/dev/internal-vg/boot → /boot
|
||||||
|
/dev/internal-vg/home → /home (via LUKS)
|
||||||
|
```
|
||||||
|
This is correct for booting from internal drive.
|
||||||
|
|
||||||
|
3. **System is currently running from external drive** due to boot order/USB boot
|
||||||
|
- This is temporary for testing
|
||||||
|
- Internal drive is fully configured and ready
|
||||||
|
|
||||||
|
4. **No data loss risk** - all changes were to bootloader configuration only
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Once you've verified the internal drive boots correctly:
|
||||||
|
|
||||||
|
1. **Run the backup** to clone internal-vg to migration-vg:
|
||||||
|
```bash
|
||||||
|
sudo ./lvm_block_backup.sh
|
||||||
|
```
|
||||||
|
This will create a block-level backup of internal drive to external drive.
|
||||||
|
|
||||||
|
2. **Keep external drive as emergency backup**:
|
||||||
|
- Can boot from it if internal drive fails
|
||||||
|
- Already has identical LVM structure
|
||||||
|
|
||||||
|
## Files Created
|
||||||
|
- `verify_internal_boot.sh` - Script to verify boot configuration
|
||||||
|
- `BOOT_FIX_SUMMARY.md` - This documentation
|
||||||
|
|
||||||
|
## What Was Fixed
|
||||||
|
✅ GRUB bootloader reinstalled to internal NVMe
|
||||||
|
✅ GRUB configuration updated with current kernels
|
||||||
|
✅ Boot failure flags cleared
|
||||||
|
✅ EFI boot entry verified
|
||||||
|
✅ Internal drive ready to boot
|
||||||
|
|
||||||
|
The "reset system" message should no longer appear when booting from the internal drive.
|
||||||
52
old_scripts/DRIVE_SELECTION_REFERENCE.md
Normal file
52
old_scripts/DRIVE_SELECTION_REFERENCE.md
Normal file
@@ -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!
|
||||||
234
old_scripts/INTERNAL_DRIVE_RECOVERY.md
Normal file
234
old_scripts/INTERNAL_DRIVE_RECOVERY.md
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
# Internal Drive Recovery - Complete Configuration
|
||||||
|
|
||||||
|
## ✅ What Was Fixed
|
||||||
|
|
||||||
|
Your internal NVMe drive is now **fully configured to boot independently** from the external M.2 backup drive.
|
||||||
|
|
||||||
|
### Changes Made:
|
||||||
|
|
||||||
|
#### 1. **Fixed LUKS Encryption Configuration** (`/etc/crypttab`)
|
||||||
|
**Problem**: Both internal and external drives have the same LUKS UUID, causing the system to mount home from whichever drive it found first (which was the external).
|
||||||
|
|
||||||
|
**Solution**: Changed `/etc/crypttab` to use device path instead of UUID:
|
||||||
|
```bash
|
||||||
|
# OLD (using UUID - ambiguous):
|
||||||
|
luks-home-internal UUID=e4d30f4f-3aac-48a7-a1ca-83539598555b none luks
|
||||||
|
|
||||||
|
# NEW (using device path - specific):
|
||||||
|
luks-home-internal /dev/internal-vg/home none luks
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backup**: `/etc/crypttab.backup.20251006`
|
||||||
|
|
||||||
|
#### 2. **Updated Initramfs**
|
||||||
|
Regenerated the initial RAM filesystem to include the new crypttab configuration:
|
||||||
|
```bash
|
||||||
|
sudo update-initramfs -u -k all
|
||||||
|
```
|
||||||
|
This ensures the system uses the internal drive's home partition during boot.
|
||||||
|
|
||||||
|
#### 3. **Reinstalled GRUB Bootloader**
|
||||||
|
```bash
|
||||||
|
sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu
|
||||||
|
```
|
||||||
|
- Created Ubuntu EFI boot entry (Boot0001)
|
||||||
|
- Installed to internal drive's EFI partition (nvme0n1p1)
|
||||||
|
|
||||||
|
#### 4. **Set EFI Boot Order**
|
||||||
|
```bash
|
||||||
|
sudo efibootmgr -o 0001,001C,001B,0000,...
|
||||||
|
```
|
||||||
|
- **Ubuntu (Boot0001)** is now the FIRST boot option
|
||||||
|
- System will boot from internal drive by default
|
||||||
|
- External drive boot entries (001F USB HDD) are lower priority
|
||||||
|
|
||||||
|
## 📊 Current System Configuration
|
||||||
|
|
||||||
|
### Internal NVMe Drive (nvme0n1) - PRIMARY
|
||||||
|
```
|
||||||
|
nvme0n1 (476.9 GB)
|
||||||
|
├─ nvme0n1p1 → /boot/efi (EFI partition, 511MB)
|
||||||
|
└─ nvme0n1p2 → internal-vg (LVM)
|
||||||
|
├─ root (56GB) → / (root filesystem)
|
||||||
|
├─ boot (2GB) → /boot (kernel images)
|
||||||
|
├─ home (404GB) → /home (LUKS encrypted) ← NOW CONFIGURED
|
||||||
|
└─ swap (8GB) → swap
|
||||||
|
```
|
||||||
|
|
||||||
|
### External M.2 USB (sda) - BACKUP
|
||||||
|
```
|
||||||
|
sda (476.9 GB)
|
||||||
|
├─ sda1 → EFI partition (unmounted)
|
||||||
|
└─ sda2 → migration-vg (LVM)
|
||||||
|
├─ root (56GB) → (unmounted)
|
||||||
|
├─ boot (2GB) → (unmounted)
|
||||||
|
├─ home (404GB) → (unmounted, LUKS encrypted)
|
||||||
|
└─ swap (8GB) → (inactive)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mount Points (After Reboot)
|
||||||
|
```
|
||||||
|
/dev/internal-vg/root → /
|
||||||
|
/dev/internal-vg/boot → /boot
|
||||||
|
/dev/mapper/luks-home-internal → /home (from internal-vg/home)
|
||||||
|
/dev/internal-vg/swap → swap
|
||||||
|
/dev/nvme0n1p1 → /boot/efi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 LUKS Encryption Details
|
||||||
|
|
||||||
|
Both drives have identical LUKS setup:
|
||||||
|
- **UUID**: e4d30f4f-3aac-48a7-a1ca-83539598555b (same on both!)
|
||||||
|
- **Type**: LUKS2
|
||||||
|
- **Cipher**: aes-xts-plain64
|
||||||
|
- **Keysize**: 512 bits
|
||||||
|
|
||||||
|
The system now uses device path (`/dev/internal-vg/home`) instead of UUID to ensure it mounts the internal drive's home partition.
|
||||||
|
|
||||||
|
## 🚀 Testing Instructions
|
||||||
|
|
||||||
|
### Test 1: Boot with Both Drives Connected
|
||||||
|
```bash
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
**Expected**: System boots from internal drive, uses internal home partition.
|
||||||
|
|
||||||
|
**Verify after boot**:
|
||||||
|
```bash
|
||||||
|
mount | grep -E "/ |/boot|/home"
|
||||||
|
# Should show all mounts from internal-vg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test 2: Boot with ONLY Internal Drive (Full Independence Test)
|
||||||
|
```bash
|
||||||
|
# 1. Shutdown system
|
||||||
|
sudo shutdown -h now
|
||||||
|
|
||||||
|
# 2. Physically disconnect external USB M.2 drive (sda)
|
||||||
|
|
||||||
|
# 3. Power on system
|
||||||
|
|
||||||
|
# Expected: System boots completely from internal NVMe
|
||||||
|
```
|
||||||
|
|
||||||
|
**If everything works**, you'll have:
|
||||||
|
- GRUB menu appears (no "reset system" message should appear after first clean boot)
|
||||||
|
- System boots to login screen
|
||||||
|
- Can log in with encrypted home partition
|
||||||
|
- All data accessible from internal drive
|
||||||
|
|
||||||
|
## 🔍 Verification Commands
|
||||||
|
|
||||||
|
After reboot, run these to verify internal drive is being used:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check what's mounted where
|
||||||
|
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
|
||||||
|
|
||||||
|
# Verify home is from internal drive
|
||||||
|
sudo cryptsetup status luks-home-internal
|
||||||
|
# Should show: device: /dev/mapper/internal--vg-home
|
||||||
|
|
||||||
|
# Check boot order
|
||||||
|
efibootmgr | grep -A1 BootOrder
|
||||||
|
# Should show: BootOrder: 0001,... (Ubuntu first)
|
||||||
|
|
||||||
|
# Verify all internal volumes
|
||||||
|
mount | grep internal-vg
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Configuration Files Modified
|
||||||
|
|
||||||
|
1. **`/etc/crypttab`** - Updated to use internal-vg/home device path
|
||||||
|
- Backup: `/etc/crypttab.backup.20251006`
|
||||||
|
|
||||||
|
2. **`/boot/initrd.img-*`** - Regenerated with new crypttab
|
||||||
|
|
||||||
|
3. **`/boot/efi/EFI/ubuntu/`** - GRUB bootloader reinstalled
|
||||||
|
|
||||||
|
4. **UEFI NVRAM** - Boot order updated (Boot0001 first)
|
||||||
|
|
||||||
|
## 🛡️ About the "Reset System" Message
|
||||||
|
|
||||||
|
The "reset system" message you saw **before GRUB** is a **Lenovo BIOS/UEFI firmware notification**, not a GRUB error.
|
||||||
|
|
||||||
|
### Why it appears:
|
||||||
|
- BIOS detected improper shutdown or boot failure
|
||||||
|
- Partition resize triggered hardware change detection
|
||||||
|
- BIOS boot counter wasn't acknowledged
|
||||||
|
|
||||||
|
### How to clear it:
|
||||||
|
**Option 1** (Easiest): Enter BIOS setup during boot
|
||||||
|
- Press **F1** during Lenovo logo
|
||||||
|
- Just enter BIOS (don't need to change anything)
|
||||||
|
- Press **F10** to save and exit
|
||||||
|
- This acknowledges the BIOS warning
|
||||||
|
|
||||||
|
**Option 2**: Clean shutdown cycle
|
||||||
|
```bash
|
||||||
|
sudo shutdown -h now
|
||||||
|
# Wait 10 seconds
|
||||||
|
# Power on
|
||||||
|
# BIOS may clear flag after successful boot
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3**: Wait for successful boots
|
||||||
|
- After 2-3 successful clean boots, BIOS usually clears the flag automatically
|
||||||
|
|
||||||
|
## ✨ What You Can Do Now
|
||||||
|
|
||||||
|
### 1. Use Internal Drive Normally
|
||||||
|
Your internal NVMe is fully functional and independent. You can:
|
||||||
|
- Boot without external drive connected
|
||||||
|
- Use system normally with all data on internal drive
|
||||||
|
- Keep external drive disconnected
|
||||||
|
|
||||||
|
### 2. Run the Backup
|
||||||
|
Now you can create a proper block-level backup:
|
||||||
|
```bash
|
||||||
|
cd ~/Nextcloud/entwicklung/Werkzeuge/backup_to_external_m.2
|
||||||
|
sudo ./lvm_block_backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will clone internal-vg to migration-vg on the external drive.
|
||||||
|
|
||||||
|
### 3. Keep External as Emergency Backup
|
||||||
|
The external drive can serve as:
|
||||||
|
- **Bootable backup**: If internal fails, boot from external
|
||||||
|
- **Recovery system**: Access data if internal has issues
|
||||||
|
- **Migration tool**: Already has identical LVM structure
|
||||||
|
|
||||||
|
## 📚 Related Files in This Directory
|
||||||
|
|
||||||
|
- `lvm_block_backup.sh` - Block-level backup script (internal → external)
|
||||||
|
- `BOOT_FIX_SUMMARY.md` - Details about GRUB reinstallation
|
||||||
|
- `verify_internal_boot.sh` - Boot configuration verification script
|
||||||
|
- `clear_bios_boot_flag.sh` - BIOS message diagnostic tool
|
||||||
|
- `README_BACKUP.md` - Backup strategy documentation
|
||||||
|
|
||||||
|
## ⚠️ Important Notes
|
||||||
|
|
||||||
|
1. **Same LUKS UUID**: Both drives have identical UUIDs because they were cloned. The device path in crypttab ensures the right one is used.
|
||||||
|
|
||||||
|
2. **Don't mix drives during boot**: If both drives are connected and you manually select the wrong one in BIOS boot menu, the system might use the external drive instead.
|
||||||
|
|
||||||
|
3. **Backup before cloning**: The `lvm_block_backup.sh` will **OVERWRITE** the external drive's volumes. Make sure any unique data on the external is backed up first.
|
||||||
|
|
||||||
|
4. **Home data**: Currently your home directory data is on the external drive. After the first clean reboot, verify if you need to copy any recent data from external to internal before running the backup.
|
||||||
|
|
||||||
|
## 🎯 Summary
|
||||||
|
|
||||||
|
**Status**: ✅ **Internal drive is fully configured and ready**
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
- Crypttab uses internal drive explicitly
|
||||||
|
- Initramfs updated
|
||||||
|
- GRUB reinstalled
|
||||||
|
- Boot order corrected (Ubuntu first)
|
||||||
|
|
||||||
|
**Next Step**:
|
||||||
|
1. **Test** - Reboot to verify internal drive works
|
||||||
|
2. **Backup** - Run `lvm_block_backup.sh` to clone to external
|
||||||
|
3. **Normal Use** - Use internal drive, keep external as backup
|
||||||
|
|
||||||
|
**BIOS Message**: Will likely clear after 1-2 clean boots or entering BIOS setup (F1)
|
||||||
59
old_scripts/LVM_MIGRATION_GUIDE.txt
Normal file
59
old_scripts/LVM_MIGRATION_GUIDE.txt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
=== LVM MIGRATION OPTIONS ===
|
||||||
|
|
||||||
|
You now have TWO migration options on this USB stick:
|
||||||
|
|
||||||
|
OPTION 1: IMPROVED LVM MIGRATION (Recommended)
|
||||||
|
File: improved_lvm_migration.sh
|
||||||
|
- Fixes the boot issues from the previous failed attempt
|
||||||
|
- Properly configures initramfs with LVM modules
|
||||||
|
- Better GRUB configuration for LVM
|
||||||
|
- Handles LUKS + LVM combination correctly
|
||||||
|
- More robust error handling
|
||||||
|
|
||||||
|
OPTION 2: SIMPLE 1-TO-1 CLONE (Fallback)
|
||||||
|
File: direct_clone_backup.sh
|
||||||
|
- Creates exact copy without LVM conversion
|
||||||
|
- Use if LVM migration still has issues
|
||||||
|
|
||||||
|
=== WHAT WAS WRONG WITH PREVIOUS LVM MIGRATION ===
|
||||||
|
|
||||||
|
The original migrate_to_lvm.sh failed because:
|
||||||
|
❌ Initramfs missing LVM modules
|
||||||
|
❌ Poor GRUB LVM configuration
|
||||||
|
❌ LUKS + LVM combination issues
|
||||||
|
❌ Insufficient boot verification
|
||||||
|
|
||||||
|
=== IMPROVEMENTS IN NEW LVM SCRIPT ===
|
||||||
|
|
||||||
|
✅ Proper initramfs LVM module inclusion
|
||||||
|
✅ Correct GRUB configuration for LVM
|
||||||
|
✅ Better LUKS + LVM handling
|
||||||
|
✅ Boot configuration verification
|
||||||
|
✅ More robust error recovery
|
||||||
|
|
||||||
|
=== USAGE INSTRUCTIONS ===
|
||||||
|
|
||||||
|
1. Boot from this USB stick (Debian Live)
|
||||||
|
|
||||||
|
2. For LVM migration (recommended):
|
||||||
|
sudo ./improved_lvm_migration.sh
|
||||||
|
|
||||||
|
3. For simple clone (if LVM fails):
|
||||||
|
sudo ./direct_clone_backup.sh
|
||||||
|
|
||||||
|
4. Verify boot readiness:
|
||||||
|
sudo ./verify_boot_readiness.sh
|
||||||
|
|
||||||
|
5. If needed, repair issues:
|
||||||
|
sudo ./boot_repair_tools.sh
|
||||||
|
|
||||||
|
=== EXPECTED RESULTS WITH IMPROVED LVM ===
|
||||||
|
|
||||||
|
✅ Boots properly asking for LUKS password
|
||||||
|
✅ No reset loops or boot failures
|
||||||
|
✅ Full LVM functionality (snapshots, resizing)
|
||||||
|
✅ Proper initramfs with LVM support
|
||||||
|
✅ Working GRUB configuration
|
||||||
|
|
||||||
|
The improved script addresses all the issues that caused
|
||||||
|
the boot failure shown in your screenshot.
|
||||||
49
old_scripts/README_BACKUP.md
Normal file
49
old_scripts/README_BACKUP.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# LVM Block-Level Backup Tool
|
||||||
|
|
||||||
|
## What This Does
|
||||||
|
|
||||||
|
This tool performs a **proper** LVM snapshot backup by:
|
||||||
|
|
||||||
|
1. ✅ Creating consistent LVM snapshots of your internal drive
|
||||||
|
2. ✅ Cloning entire volumes **block-for-block** to external drive
|
||||||
|
3. ✅ Creating an exact, bootable copy of your system
|
||||||
|
4. ✅ Using full drive capacity efficiently
|
||||||
|
|
||||||
|
## Why This Is Better
|
||||||
|
|
||||||
|
- **Fast**: Block-level copying is much faster than file copying
|
||||||
|
- **Complete**: Exact bit-for-bit copy including all metadata
|
||||||
|
- **Bootable**: External drive can boot your system directly
|
||||||
|
- **Space Efficient**: Uses full drive capacity, no filesystem overhead
|
||||||
|
- **Consistent**: Snapshots ensure data consistency during backup
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Simple one-command backup
|
||||||
|
sudo ./lvm_block_backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Both drives must have LVM setup
|
||||||
|
- External drive volume group: `migration-vg`
|
||||||
|
- Internal drive volume group: `internal-vg`
|
||||||
|
- At least 6GB free space for snapshots
|
||||||
|
|
||||||
|
## What Gets Backed Up
|
||||||
|
|
||||||
|
- **Root volume**: Complete system, applications, configurations
|
||||||
|
- **Home volume**: All user data (including encrypted LUKS)
|
||||||
|
- **Boot volume**: Kernels, bootloader, boot configuration
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
After backup, your external drive contains an exact copy that can:
|
||||||
|
- Boot independently from BIOS/UEFI menu
|
||||||
|
- Be mounted to access files
|
||||||
|
- Serve as complete system recovery
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: The `old_scripts/` directory contains 40+ previous backup attempts that didn't work properly. This single script replaces all of them.
|
||||||
105
old_scripts/README_FIRST.txt
Normal file
105
old_scripts/README_FIRST.txt
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
╔═══════════════════════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║ ✅ INTERNAL DRIVE RECOVERY COMPLETE ║
|
||||||
|
║ ║
|
||||||
|
╚═══════════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
Your internal NVMe drive has been fully configured and is ready to
|
||||||
|
boot independently!
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
📋 WHAT WAS DONE:
|
||||||
|
|
||||||
|
1. ✅ Fixed /etc/crypttab to use internal-vg/home explicitly
|
||||||
|
2. ✅ Updated initramfs with new LUKS configuration
|
||||||
|
3. ✅ Reinstalled GRUB bootloader on internal NVMe
|
||||||
|
4. ✅ Created Ubuntu EFI boot entry (Boot0001)
|
||||||
|
5. ✅ Set boot order: Ubuntu FIRST in boot priority
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
🚀 NEXT STEPS:
|
||||||
|
|
||||||
|
STEP 1: REBOOT THE SYSTEM
|
||||||
|
$ sudo reboot
|
||||||
|
|
||||||
|
After reboot, the system will use the internal drive's home
|
||||||
|
partition instead of the external one.
|
||||||
|
|
||||||
|
STEP 2: VERIFY CONFIGURATION
|
||||||
|
$ cd ~/Nextcloud/entwicklung/Werkzeuge/backup_to_external_m.2
|
||||||
|
$ ./verify_internal_config.sh
|
||||||
|
|
||||||
|
All 7 checks should pass after reboot.
|
||||||
|
|
||||||
|
STEP 3: TEST FULL INDEPENDENCE (Optional but Recommended)
|
||||||
|
$ sudo shutdown -h now
|
||||||
|
|
||||||
|
Then:
|
||||||
|
- Physically disconnect the external USB M.2 drive
|
||||||
|
- Power on the system
|
||||||
|
- System should boot completely from internal NVMe
|
||||||
|
- You should be able to log in and access all your data
|
||||||
|
|
||||||
|
STEP 4: RECONNECT EXTERNAL & CREATE BACKUP
|
||||||
|
After verifying internal drive works:
|
||||||
|
- Reconnect external drive
|
||||||
|
- Run the backup script:
|
||||||
|
$ sudo ./lvm_block_backup.sh
|
||||||
|
|
||||||
|
This will create a block-level clone of internal → external
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
⚠️ ABOUT THE "RESET SYSTEM" MESSAGE:
|
||||||
|
|
||||||
|
The message you see BEFORE GRUB is from Lenovo BIOS, not GRUB.
|
||||||
|
|
||||||
|
To clear it:
|
||||||
|
• Press F1 during boot → Enter BIOS → Save & Exit (F10)
|
||||||
|
OR
|
||||||
|
• After 1-2 clean boots, it should disappear automatically
|
||||||
|
|
||||||
|
This is just a BIOS notification - your system IS bootable!
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
📚 DOCUMENTATION:
|
||||||
|
|
||||||
|
All changes are documented in:
|
||||||
|
• INTERNAL_DRIVE_RECOVERY.md - Complete technical details
|
||||||
|
• BOOT_FIX_SUMMARY.md - GRUB bootloader fixes
|
||||||
|
• verify_internal_config.sh - Configuration verification
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
🎯 CURRENT STATUS:
|
||||||
|
|
||||||
|
✅ Internal drive fully configured
|
||||||
|
✅ Boot order corrected (Ubuntu first)
|
||||||
|
✅ GRUB properly installed
|
||||||
|
⏳ PENDING: Reboot to activate home partition change
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
💾 BACKUP STRATEGY:
|
||||||
|
|
||||||
|
Once verified, you'll have:
|
||||||
|
|
||||||
|
INTERNAL NVMe (nvme0n1) = PRIMARY SYSTEM
|
||||||
|
- Daily use
|
||||||
|
- Main operating system
|
||||||
|
- All your data
|
||||||
|
|
||||||
|
EXTERNAL M.2 (sda) = BACKUP/EMERGENCY BOOT
|
||||||
|
- Block-level clone of internal
|
||||||
|
- Bootable rescue system
|
||||||
|
- Emergency access if internal fails
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
Ready to reboot? Run:
|
||||||
|
sudo reboot
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════
|
||||||
@@ -1,31 +1,10 @@
|
|||||||
# System Backup and LVM Migration Tools
|
# System Backup to External M.2 SSD
|
||||||
|
|
||||||
A comprehensive toolset for Linux system backup and LVM migration that provides both GUI and command-line interfaces for backing up to external M.2 SSDs and migrating systems to LVM.
|
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
|
||||||
|
|
||||||
### Traditional Backup 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**: ⚡ 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
|
|
||||||
|
|
||||||
### 🆕 LVM Migration Features
|
|
||||||
- **Automated LVM Migration**: Complete migration from traditional partitions to LVM
|
|
||||||
- **Size Detection**: Automatic source partition size detection and matching
|
|
||||||
- **Data Preservation**: Full data integrity with permissions, ownership, and timestamps
|
|
||||||
- **External Drive Support**: Optimized for USB 3.0+ M.2 external drives
|
|
||||||
- **GRUB Installation**: Automatic EFI bootloader installation and configuration
|
|
||||||
- **System Validation**: Built-in verification of migration success
|
|
||||||
- **One-Button Operation**: Complete migration with a single command
|
|
||||||
|
|
||||||
- **GUI Interface**: User-friendly graphical interface built with Python Tkinter
|
- **GUI Interface**: User-friendly graphical interface built with Python Tkinter
|
||||||
- **Command Line Interface**: Full CLI support for automated and scripted operations
|
- **Command Line Interface**: Full CLI support for automated and scripted operations
|
||||||
@@ -49,8 +28,6 @@ A comprehensive toolset for Linux system backup and LVM migration that provides
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Traditional Backup Installation
|
|
||||||
|
|
||||||
1. Clone or download this repository:
|
1. Clone or download this repository:
|
||||||
```bash
|
```bash
|
||||||
git clone <repository-url>
|
git clone <repository-url>
|
||||||
@@ -69,53 +46,6 @@ A comprehensive toolset for Linux system backup and LVM migration that provides
|
|||||||
sudo apt install python3-tk pv parted
|
sudo apt install python3-tk pv parted
|
||||||
```
|
```
|
||||||
|
|
||||||
### LVM Migration Setup
|
|
||||||
|
|
||||||
The LVM migration tools require a live USB environment:
|
|
||||||
|
|
||||||
1. Boot from Debian/Ubuntu Live USB
|
|
||||||
2. Clone this repository to the live environment
|
|
||||||
3. The migration script will automatically install required dependencies
|
|
||||||
4. Run the migration with a single command:
|
|
||||||
```bash
|
|
||||||
sudo ./migrate_to_lvm.sh /dev/sdX
|
|
||||||
```
|
|
||||||
|
|
||||||
## LVM Migration Process
|
|
||||||
|
|
||||||
The migration is now **fully automated** with a single command:
|
|
||||||
|
|
||||||
1. **Preparation**: Boot from live USB, run the migration script
|
|
||||||
2. **Detection**: Automatically detect source partition sizes and layout
|
|
||||||
3. **LVM Setup**: Create LVM physical volumes, volume groups, and logical volumes
|
|
||||||
4. **Data Copy**: Efficiently copy all filesystem data using optimized `cp -a` method
|
|
||||||
5. **Boot Configuration**: Automatically update fstab, install GRUB EFI bootloader
|
|
||||||
6. **Validation**: Verify all components are properly configured
|
|
||||||
7. **Ready**: System is immediately bootable from external drive
|
|
||||||
|
|
||||||
### One-Button Migration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo ./migrate_to_lvm.sh /dev/sdX
|
|
||||||
```
|
|
||||||
|
|
||||||
The script automatically handles:
|
|
||||||
- ✅ Partition detection and size calculation
|
|
||||||
- ✅ LVM volume creation with optimal sizes
|
|
||||||
- ✅ Complete data transfer (root, home, boot, EFI)
|
|
||||||
- ✅ fstab configuration with proper UUIDs
|
|
||||||
- ✅ GRUB EFI bootloader installation
|
|
||||||
- ✅ initramfs updates for LVM support
|
|
||||||
- ✅ System validation and readiness check
|
|
||||||
|
|
||||||
### Post-Migration Benefits
|
|
||||||
|
|
||||||
Once migrated to LVM, your system gains:
|
|
||||||
- **Instant Snapshots**: Create point-in-time snapshots before system changes
|
|
||||||
- **Dynamic Resizing**: Resize partitions without downtime
|
|
||||||
- **Advanced Backups**: Consistent snapshots for reliable backups
|
|
||||||
- **Easy Rollback**: Revert to previous snapshots if needed
|
|
||||||
|
|
||||||
4. **Optional**: Set up portable tools on external M.2 SSD:
|
4. **Optional**: Set up portable tools on external M.2 SSD:
|
||||||
```bash
|
```bash
|
||||||
./setup_portable_tools.sh
|
./setup_portable_tools.sh
|
||||||
111
old_scripts/SIMPLE_CLONE_SOLUTION.md
Normal file
111
old_scripts/SIMPLE_CLONE_SOLUTION.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Simple and Reliable 1-to-1 Clone Solution
|
||||||
|
|
||||||
|
## Problem Analysis
|
||||||
|
|
||||||
|
The LVM migration failed because it introduced too much complexity:
|
||||||
|
- Complex LVM setup with volume groups and logical volumes
|
||||||
|
- Initramfs configuration issues with LVM modules
|
||||||
|
- Boot loader configuration changes
|
||||||
|
- Multiple mount points and potential failure scenarios
|
||||||
|
|
||||||
|
The screenshot shows a boot failure where the system can't properly initialize, likely because the initramfs doesn't have the right LVM configuration.
|
||||||
|
|
||||||
|
## Solution: Direct 1-to-1 Clone
|
||||||
|
|
||||||
|
Instead of converting to LVM, create an exact bit-perfect copy that preserves your original structure:
|
||||||
|
|
||||||
|
### Key Advantages:
|
||||||
|
✅ **Preserves original encryption** - LUKS works exactly as before
|
||||||
|
✅ **No LVM complexity** - Simple partition structure
|
||||||
|
✅ **Minimal boot changes** - Only UUID updates needed
|
||||||
|
✅ **Reliable boot process** - Same as your working internal drive
|
||||||
|
✅ **Emergency fallback** - Original internal drive unchanged
|
||||||
|
|
||||||
|
## Usage Instructions
|
||||||
|
|
||||||
|
### Step 1: Create the Clone
|
||||||
|
```bash
|
||||||
|
# Run from live USB system
|
||||||
|
sudo ./direct_clone_backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script will:
|
||||||
|
- Detect your internal and external drives
|
||||||
|
- Create a bit-perfect clone (using dd)
|
||||||
|
- Update UUIDs to prevent conflicts
|
||||||
|
- Fix /etc/fstab with new UUIDs
|
||||||
|
- Install/repair GRUB bootloader
|
||||||
|
- Verify the clone integrity
|
||||||
|
|
||||||
|
### Step 2: Verify Boot Readiness
|
||||||
|
```bash
|
||||||
|
# Verify the cloned drive will boot properly
|
||||||
|
sudo ./verify_boot_readiness.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script checks:
|
||||||
|
- Partition structure integrity
|
||||||
|
- Filesystem health
|
||||||
|
- Essential boot files presence
|
||||||
|
- GRUB configuration
|
||||||
|
- Boot readiness tests
|
||||||
|
|
||||||
|
### Step 3: Fix Issues (if needed)
|
||||||
|
```bash
|
||||||
|
# If verification fails, repair the clone
|
||||||
|
sudo ./boot_repair_tools.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Available repair options:
|
||||||
|
- Full repair (recommended)
|
||||||
|
- GRUB repair only
|
||||||
|
- /etc/fstab fix only
|
||||||
|
- Initramfs regeneration
|
||||||
|
- Boot configuration check
|
||||||
|
|
||||||
|
## What This Solution Does Differently
|
||||||
|
|
||||||
|
### ❌ LVM Migration (Complex, Failed)
|
||||||
|
- Converts partition structure to LVM
|
||||||
|
- Requires initramfs LVM module configuration
|
||||||
|
- Changes boot process significantly
|
||||||
|
- Multiple potential failure points
|
||||||
|
- Complex recovery if something goes wrong
|
||||||
|
|
||||||
|
### ✅ Direct Clone (Simple, Reliable)
|
||||||
|
- Preserves exact original structure
|
||||||
|
- No initramfs changes needed
|
||||||
|
- Minimal boot process changes
|
||||||
|
- Only UUID conflicts to resolve
|
||||||
|
- Easy recovery with original drive
|
||||||
|
|
||||||
|
## Boot Process Comparison
|
||||||
|
|
||||||
|
### Original Failed LVM Boot:
|
||||||
|
1. GRUB loads → 2. Initramfs loads → 3. **FAILS** (LVM modules/config issue) → Boot failure
|
||||||
|
|
||||||
|
### New Direct Clone Boot:
|
||||||
|
1. GRUB loads → 2. Initramfs loads → 3. **WORKS** (identical to original) → LUKS password → System boots
|
||||||
|
|
||||||
|
## Expected Results
|
||||||
|
|
||||||
|
After running the direct clone:
|
||||||
|
1. **Boots like original** - Same encryption, same password prompt
|
||||||
|
2. **No reset loops** - Stable boot process
|
||||||
|
3. **Identical experience** - Everything works as before
|
||||||
|
4. **Safe fallback** - Original internal drive unchanged
|
||||||
|
|
||||||
|
## Emergency Recovery
|
||||||
|
|
||||||
|
If something goes wrong:
|
||||||
|
1. Boot from original internal drive (unchanged)
|
||||||
|
2. Run `boot_repair_tools.sh` on external drive
|
||||||
|
3. Or re-run `direct_clone_backup.sh` to start over
|
||||||
|
|
||||||
|
## Key Files Created
|
||||||
|
|
||||||
|
1. **`direct_clone_backup.sh`** - Main cloning script
|
||||||
|
2. **`verify_boot_readiness.sh`** - Boot verification tool
|
||||||
|
3. **`boot_repair_tools.sh`** - Emergency repair toolkit
|
||||||
|
|
||||||
|
This solution avoids all the complexity that caused the LVM migration to fail while giving you a working 1-to-1 copy that boots reliably.
|
||||||
@@ -15,13 +15,13 @@ NC='\033[0m' # No Color
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TOOLS_DIR="$SCRIPT_DIR/lvm-migration-tools"
|
TOOLS_DIR="$SCRIPT_DIR/lvm-migration-tools"
|
||||||
|
|
||||||
echo -e "${GREEN}=== LVM Migration System (Updated) ===${NC}"
|
echo -e "${GREEN}=== Complete LVM Migration System ===${NC}"
|
||||||
echo "Welcome to the LVM Migration Tool with Enhanced Drive Selection!"
|
echo "🚀 One-Button Migration with Automatic GRUB Repair!"
|
||||||
echo
|
echo
|
||||||
echo "This USB stick contains:"
|
echo "This USB stick contains:"
|
||||||
echo "• Debian live system (bootable)"
|
echo "• Debian live system (bootable)"
|
||||||
echo "• Complete LVM migration toolkit"
|
echo "• Complete LVM migration toolkit with GRUB repair"
|
||||||
echo "• Enhanced drive selection with user confirmation"
|
echo "• Interactive drive selection with user confirmation"
|
||||||
echo "• All necessary scripts and documentation"
|
echo "• All necessary scripts and documentation"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
@@ -39,23 +39,25 @@ ls -la *.sh | grep -E "(prepare|migrate|validate|emergency|check)" | while read
|
|||||||
done
|
done
|
||||||
|
|
||||||
echo
|
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 "0. Check packages -> ./check_packages.sh (verify package availability)"
|
||||||
echo "1. Emergency install -> ./emergency_install.sh (if packages missing)"
|
echo "1. Emergency install -> ./emergency_install.sh (if packages missing)"
|
||||||
echo "2. Prepare live system -> ./prepare_live_system.sh"
|
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 "4. Validate migration -> ./validate_lvm_migration.sh"
|
||||||
echo
|
echo
|
||||||
echo -e "${GREEN}NEW FEATURES:${NC}"
|
echo -e "${GREEN}✨ INTEGRATED FEATURES:${NC}"
|
||||||
echo "• Interactive drive selection with confirmation"
|
echo "• ✅ Interactive drive selection with safety confirmations"
|
||||||
echo "• Better auto-detection of internal/external drives"
|
echo "• ✅ Automatic LVM layout creation and data migration"
|
||||||
echo "• Safety checks to prevent wrong drive selection"
|
echo "• ✅ Built-in GRUB repair (no more boot reset loops!)"
|
||||||
echo "• Package compatibility checking"
|
echo "• ✅ Complete bootloader installation and configuration"
|
||||||
|
echo "• ✅ LVM snapshot backup system setup"
|
||||||
|
echo "• ✅ Everything automated in one script!"
|
||||||
echo
|
echo
|
||||||
echo "For complete documentation: less LIVE_USB_MIGRATION_GUIDE.md"
|
echo "For complete documentation: less LIVE_USB_MIGRATION_GUIDE.md"
|
||||||
echo
|
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
|
case $choice in
|
||||||
0)
|
0)
|
||||||
@@ -71,7 +73,14 @@ case $choice in
|
|||||||
sudo ./prepare_live_system.sh
|
sudo ./prepare_live_system.sh
|
||||||
;;
|
;;
|
||||||
3)
|
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
|
sudo ./migrate_to_lvm.sh
|
||||||
;;
|
;;
|
||||||
4)
|
4)
|
||||||
@@ -92,6 +101,6 @@ case $choice in
|
|||||||
echo "sudo ./check_packages.sh # Check package availability"
|
echo "sudo ./check_packages.sh # Check package availability"
|
||||||
echo "sudo ./emergency_install.sh # If you see missing package errors"
|
echo "sudo ./emergency_install.sh # If you see missing package errors"
|
||||||
echo "sudo ./prepare_live_system.sh"
|
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
|
esac
|
||||||
21
old_scripts/START_SIMPLE_CLONE.txt
Normal file
21
old_scripts/START_SIMPLE_CLONE.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
=== SIMPLE 1-TO-1 CLONE INSTRUCTIONS ===
|
||||||
|
|
||||||
|
IMPORTANT: Boot from this USB stick first!
|
||||||
|
|
||||||
|
1. REBOOT and boot from this USB stick (Debian Live)
|
||||||
|
|
||||||
|
2. Once in live system, open terminal and run:
|
||||||
|
cd /media/migration_tools (or wherever this partition mounts)
|
||||||
|
|
||||||
|
3. Run the clone process:
|
||||||
|
sudo ./direct_clone_backup.sh
|
||||||
|
|
||||||
|
4. Verify the clone:
|
||||||
|
sudo ./verify_boot_readiness.sh
|
||||||
|
|
||||||
|
5. If needed, repair issues:
|
||||||
|
sudo ./boot_repair_tools.sh
|
||||||
|
|
||||||
|
This creates a working 1-to-1 copy without LVM complexity!
|
||||||
|
|
||||||
|
Read SIMPLE_CLONE_SOLUTION.md for full details.
|
||||||
389
old_scripts/boot_repair_tools.sh
Normal file
389
old_scripts/boot_repair_tools.sh
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Boot Repair and Emergency Recovery Tools
|
||||||
|
# Provides tools to fix boot issues after cloning
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
TARGET_DRIVE=""
|
||||||
|
WORK_DIR="/mnt/repair_work"
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_target_drive() {
|
||||||
|
log "Detecting target drive to repair..."
|
||||||
|
|
||||||
|
# List available drives
|
||||||
|
echo "Available drives:"
|
||||||
|
lsblk -dpno NAME,SIZE,MODEL | grep -v "loop\|ram"
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Enter the drive to repair (e.g., /dev/sdb): " TARGET_DRIVE
|
||||||
|
|
||||||
|
if [ ! -b "$TARGET_DRIVE" ]; then
|
||||||
|
error "Drive $TARGET_DRIVE not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Selected drive for repair: $TARGET_DRIVE"
|
||||||
|
lsblk "$TARGET_DRIVE"
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Is this correct? [y/N]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
error "Operation cancelled"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Target drive selected: $TARGET_DRIVE"
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_target_system() {
|
||||||
|
log "Mounting target system for repair..."
|
||||||
|
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
|
||||||
|
# Find partitions
|
||||||
|
local partitions=($(lsblk -pno NAME "$TARGET_DRIVE" | grep -v "^$TARGET_DRIVE$"))
|
||||||
|
local root_partition=""
|
||||||
|
local boot_partition=""
|
||||||
|
local efi_partition=""
|
||||||
|
|
||||||
|
for part in "${partitions[@]}"; do
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
local size_bytes=$(lsblk -bno SIZE "$part")
|
||||||
|
|
||||||
|
# Detect partitions by filesystem and size
|
||||||
|
if [[ "$fstype" == "vfat" && "$size_bytes" -lt 1073741824 ]]; then # Less than 1GB
|
||||||
|
efi_partition="$part"
|
||||||
|
elif [[ "$fstype" == "ext4" && "$size_bytes" -lt 5368709120 ]]; then # Between 100MB and 5GB
|
||||||
|
boot_partition="$part"
|
||||||
|
elif [[ "$fstype" == "ext4" && "$size_bytes" -gt 5368709120 ]]; then # Larger than 5GB
|
||||||
|
root_partition="$part"
|
||||||
|
elif [[ "$fstype" == "crypto_LUKS" ]]; then
|
||||||
|
# Try to unlock encrypted partition
|
||||||
|
local crypt_name="repair_$(basename "$part")"
|
||||||
|
echo "Found encrypted partition: $part"
|
||||||
|
echo "Please enter the password to unlock for repair:"
|
||||||
|
if cryptsetup open "$part" "$crypt_name"; then
|
||||||
|
local decrypted_fs=$(lsblk -no FSTYPE "/dev/mapper/$crypt_name")
|
||||||
|
if [[ "$decrypted_fs" == "ext4" ]]; then
|
||||||
|
root_partition="/dev/mapper/$crypt_name"
|
||||||
|
log "Using decrypted partition as root: $root_partition"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warning "Could not unlock encrypted partition"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$root_partition" ]; then
|
||||||
|
error "Could not find root partition to repair"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount filesystems
|
||||||
|
log "Mounting root partition: $root_partition"
|
||||||
|
mount "$root_partition" "$WORK_DIR" || error "Failed to mount root partition"
|
||||||
|
|
||||||
|
if [ -n "$boot_partition" ]; then
|
||||||
|
log "Mounting boot partition: $boot_partition"
|
||||||
|
mkdir -p "$WORK_DIR/boot"
|
||||||
|
mount "$boot_partition" "$WORK_DIR/boot" || warning "Failed to mount boot partition"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$efi_partition" ]; then
|
||||||
|
log "Mounting EFI partition: $efi_partition"
|
||||||
|
mkdir -p "$WORK_DIR/boot/efi"
|
||||||
|
mount "$efi_partition" "$WORK_DIR/boot/efi" || warning "Failed to mount EFI partition"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Target system mounted at $WORK_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
repair_grub() {
|
||||||
|
log "Repairing GRUB bootloader..."
|
||||||
|
|
||||||
|
# Bind mount necessary filesystems
|
||||||
|
mount --bind /dev "$WORK_DIR/dev"
|
||||||
|
mount --bind /proc "$WORK_DIR/proc"
|
||||||
|
mount --bind /sys "$WORK_DIR/sys"
|
||||||
|
mount --bind /run "$WORK_DIR/run"
|
||||||
|
|
||||||
|
# Repair GRUB
|
||||||
|
chroot "$WORK_DIR" /bin/bash -c "
|
||||||
|
echo 'Updating initramfs...'
|
||||||
|
update-initramfs -u -k all
|
||||||
|
|
||||||
|
echo 'Reinstalling GRUB...'
|
||||||
|
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck $TARGET_DRIVE
|
||||||
|
|
||||||
|
echo 'Updating GRUB configuration...'
|
||||||
|
update-grub
|
||||||
|
|
||||||
|
echo 'GRUB repair completed'
|
||||||
|
"
|
||||||
|
|
||||||
|
# Unmount bind mounts
|
||||||
|
umount "$WORK_DIR/dev" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/proc" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/sys" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/run" 2>/dev/null || true
|
||||||
|
|
||||||
|
success "GRUB repair completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_fstab() {
|
||||||
|
log "Checking and fixing /etc/fstab..."
|
||||||
|
|
||||||
|
if [ ! -f "$WORK_DIR/etc/fstab" ]; then
|
||||||
|
warning "/etc/fstab not found"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup current fstab
|
||||||
|
cp "$WORK_DIR/etc/fstab" "$WORK_DIR/etc/fstab.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
|
||||||
|
# Get current UUIDs of all partitions
|
||||||
|
local partitions=($(lsblk -pno NAME "$TARGET_DRIVE" | grep -v "^$TARGET_DRIVE$"))
|
||||||
|
|
||||||
|
echo "Current partition UUIDs:"
|
||||||
|
for part in "${partitions[@]}"; do
|
||||||
|
local uuid=$(lsblk -no UUID "$part")
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
if [ -n "$uuid" ]; then
|
||||||
|
echo " $part ($fstype): $uuid"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Current /etc/fstab content:"
|
||||||
|
cat "$WORK_DIR/etc/fstab"
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Do you want to regenerate /etc/fstab with current UUIDs? [y/N]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
# Generate new fstab
|
||||||
|
echo "# /etc/fstab: static file system information." > "$WORK_DIR/etc/fstab.new"
|
||||||
|
echo "# Regenerated by repair script on $(date)" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
echo "#" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
echo "# <file system> <mount point> <type> <options> <dump> <pass>" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
|
||||||
|
for part in "${partitions[@]}"; do
|
||||||
|
local uuid=$(lsblk -no UUID "$part")
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
|
||||||
|
if [ -n "$uuid" ]; then
|
||||||
|
case "$fstype" in
|
||||||
|
"vfat")
|
||||||
|
echo "UUID=$uuid /boot/efi vfat defaults 0 2" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
;;
|
||||||
|
"ext4")
|
||||||
|
# Determine if it's boot or root by size
|
||||||
|
local size_bytes=$(lsblk -bno SIZE "$part")
|
||||||
|
if [ "$size_bytes" -lt 5368709120 ]; then # Less than 5GB = boot
|
||||||
|
echo "UUID=$uuid /boot ext4 defaults 0 2" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
else # Root partition
|
||||||
|
echo "UUID=$uuid / ext4 defaults 0 1" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"swap")
|
||||||
|
echo "UUID=$uuid none swap sw 0 0" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Add tmpfs
|
||||||
|
echo "tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
|
||||||
|
# Show new fstab
|
||||||
|
echo
|
||||||
|
echo "New /etc/fstab content:"
|
||||||
|
cat "$WORK_DIR/etc/fstab.new"
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Apply this new /etc/fstab? [y/N]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
mv "$WORK_DIR/etc/fstab.new" "$WORK_DIR/etc/fstab"
|
||||||
|
success "/etc/fstab updated"
|
||||||
|
else
|
||||||
|
rm "$WORK_DIR/etc/fstab.new"
|
||||||
|
log "New fstab not applied"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "fstab check completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_boot_configuration() {
|
||||||
|
log "Checking boot configuration..."
|
||||||
|
|
||||||
|
# Check if EFI boot entry exists
|
||||||
|
if command -v efibootmgr >/dev/null 2>&1; then
|
||||||
|
echo "Current EFI boot entries:"
|
||||||
|
efibootmgr
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check GRUB configuration
|
||||||
|
if [ -f "$WORK_DIR/etc/default/grub" ]; then
|
||||||
|
echo "GRUB configuration (/etc/default/grub):"
|
||||||
|
cat "$WORK_DIR/etc/default/grub"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if initramfs exists
|
||||||
|
echo "Available kernels and initramfs:"
|
||||||
|
ls -la "$WORK_DIR/boot/vmlinuz-"* 2>/dev/null || echo "No kernels found"
|
||||||
|
ls -la "$WORK_DIR/boot/initrd.img-"* 2>/dev/null || echo "No initramfs found"
|
||||||
|
echo
|
||||||
|
|
||||||
|
success "Boot configuration check completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
regenerate_initramfs() {
|
||||||
|
log "Regenerating initramfs..."
|
||||||
|
|
||||||
|
# Bind mount necessary filesystems
|
||||||
|
mount --bind /dev "$WORK_DIR/dev"
|
||||||
|
mount --bind /proc "$WORK_DIR/proc"
|
||||||
|
mount --bind /sys "$WORK_DIR/sys"
|
||||||
|
mount --bind /run "$WORK_DIR/run"
|
||||||
|
|
||||||
|
chroot "$WORK_DIR" /bin/bash -c "
|
||||||
|
echo 'Regenerating initramfs for all kernels...'
|
||||||
|
update-initramfs -u -k all
|
||||||
|
echo 'Initramfs regeneration completed'
|
||||||
|
"
|
||||||
|
|
||||||
|
# Unmount bind mounts
|
||||||
|
umount "$WORK_DIR/dev" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/proc" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/sys" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/run" 2>/dev/null || true
|
||||||
|
|
||||||
|
success "Initramfs regenerated"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_repair() {
|
||||||
|
log "Cleaning up repair environment..."
|
||||||
|
|
||||||
|
# Unmount all filesystems
|
||||||
|
umount "$WORK_DIR/boot/efi" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/boot" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Close encrypted volumes
|
||||||
|
for mapper in /dev/mapper/repair_*; do
|
||||||
|
if [ -b "$mapper" ]; then
|
||||||
|
local crypt_name=$(basename "$mapper")
|
||||||
|
cryptsetup close "$crypt_name" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove work directory
|
||||||
|
rmdir "$WORK_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
success "Cleanup completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}=== Boot Repair and Emergency Recovery Tools ===${NC}"
|
||||||
|
echo "This script helps fix boot issues after cloning"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Available repair options:"
|
||||||
|
echo "1. Full repair (mount system, fix fstab, repair GRUB, regenerate initramfs)"
|
||||||
|
echo "2. GRUB repair only"
|
||||||
|
echo "3. fstab fix only"
|
||||||
|
echo "4. Initramfs regeneration only"
|
||||||
|
echo "5. Check boot configuration"
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Select repair option [1-5]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
|
||||||
|
case $REPLY in
|
||||||
|
1)
|
||||||
|
log "Performing full repair..."
|
||||||
|
detect_target_drive
|
||||||
|
mount_target_system
|
||||||
|
fix_fstab
|
||||||
|
regenerate_initramfs
|
||||||
|
repair_grub
|
||||||
|
check_boot_configuration
|
||||||
|
cleanup_repair
|
||||||
|
success "Full repair completed!"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
log "Performing GRUB repair only..."
|
||||||
|
detect_target_drive
|
||||||
|
mount_target_system
|
||||||
|
repair_grub
|
||||||
|
cleanup_repair
|
||||||
|
success "GRUB repair completed!"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
log "Fixing fstab only..."
|
||||||
|
detect_target_drive
|
||||||
|
mount_target_system
|
||||||
|
fix_fstab
|
||||||
|
cleanup_repair
|
||||||
|
success "fstab fix completed!"
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
log "Regenerating initramfs only..."
|
||||||
|
detect_target_drive
|
||||||
|
mount_target_system
|
||||||
|
regenerate_initramfs
|
||||||
|
cleanup_repair
|
||||||
|
success "Initramfs regeneration completed!"
|
||||||
|
;;
|
||||||
|
5)
|
||||||
|
log "Checking boot configuration..."
|
||||||
|
detect_target_drive
|
||||||
|
mount_target_system
|
||||||
|
check_boot_configuration
|
||||||
|
cleanup_repair
|
||||||
|
success "Boot configuration check completed!"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Invalid option selected"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}Repair completed. Next steps:${NC}"
|
||||||
|
echo "1. Reboot your system"
|
||||||
|
echo "2. Set the repaired drive as first boot device in BIOS/UEFI"
|
||||||
|
echo "3. Try booting from the external drive"
|
||||||
|
echo "4. If issues persist, run this script again or check system logs"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap to ensure cleanup on exit
|
||||||
|
trap cleanup_repair EXIT
|
||||||
|
|
||||||
|
main "$@"
|
||||||
39
old_scripts/build-deb.sh
Normal file
39
old_scripts/build-deb.sh
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build script for LVM Backup Manager .deb package
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔨 Building LVM Backup Manager .deb package..."
|
||||||
|
|
||||||
|
# Cleanup old builds
|
||||||
|
rm -f lvm-backup-manager_*.deb
|
||||||
|
|
||||||
|
# Set proper permissions
|
||||||
|
find deb-package -type f -exec chmod 644 {} \;
|
||||||
|
find deb-package -type d -exec chmod 755 {} \;
|
||||||
|
chmod 755 deb-package/DEBIAN/postinst
|
||||||
|
chmod 755 deb-package/usr/bin/*
|
||||||
|
|
||||||
|
# Calculate package size
|
||||||
|
PACKAGE_SIZE=$(du -s deb-package | cut -f1)
|
||||||
|
echo "Installed-Size: $PACKAGE_SIZE" >> deb-package/DEBIAN/control
|
||||||
|
|
||||||
|
# Build the package
|
||||||
|
echo "📦 Creating .deb package..."
|
||||||
|
dpkg-deb --build deb-package lvm-backup-manager_1.0.0_all.deb
|
||||||
|
|
||||||
|
# Verify the package
|
||||||
|
echo "✅ Verifying package..."
|
||||||
|
dpkg-deb --info lvm-backup-manager_1.0.0_all.deb
|
||||||
|
dpkg-deb --contents lvm-backup-manager_1.0.0_all.deb
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 Package built successfully!"
|
||||||
|
echo "📦 File: lvm-backup-manager_1.0.0_all.deb"
|
||||||
|
echo ""
|
||||||
|
echo "To install:"
|
||||||
|
echo " sudo dpkg -i lvm-backup-manager_1.0.0_all.deb"
|
||||||
|
echo " sudo apt-get install -f # Fix any missing dependencies"
|
||||||
|
echo ""
|
||||||
|
echo "To test the GUI:"
|
||||||
|
echo " sudo lvm-backup-manager"
|
||||||
112
old_scripts/clean_external_lvm_boot.sh
Executable file
112
old_scripts/clean_external_lvm_boot.sh
Executable file
@@ -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
|
||||||
|
#
|
||||||
|
# <name> <device> <password> <options>
|
||||||
|
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"
|
||||||
107
old_scripts/clear_bios_boot_flag.sh
Executable file
107
old_scripts/clear_bios_boot_flag.sh
Executable file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Clear Lenovo BIOS Boot Flags
|
||||||
|
# This script helps clear the "reset system" message shown before GRUB
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Lenovo BIOS Boot Flag Clear Utility ==="
|
||||||
|
echo ""
|
||||||
|
echo "The 'reset system' message you see before GRUB is from the Lenovo BIOS/UEFI firmware."
|
||||||
|
echo "This typically indicates the BIOS detected an improper shutdown or boot failure."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Current boot configuration:"
|
||||||
|
echo "- Root: internal-vg on nvme0n1 (internal drive)"
|
||||||
|
echo "- Boot: internal-vg on nvme0n1 (internal drive)"
|
||||||
|
echo "- EFI: nvme0n1p1 (internal drive)"
|
||||||
|
echo "- Home: migration-vg on sda (external drive)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Checking GRUB installation on internal drive..."
|
||||||
|
if [ -f /boot/efi/EFI/ubuntu/shimx64.efi ]; then
|
||||||
|
echo "✓ GRUB bootloader is properly installed on internal nvme0n1"
|
||||||
|
else
|
||||||
|
echo "✗ GRUB bootloader missing - need to reinstall"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Checking EFI boot entries..."
|
||||||
|
if efibootmgr | grep -q "ubuntu"; then
|
||||||
|
echo "✓ Ubuntu boot entry exists in UEFI firmware"
|
||||||
|
efibootmgr | grep ubuntu
|
||||||
|
else
|
||||||
|
echo "✗ Ubuntu boot entry missing"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Solutions to Clear BIOS 'Reset System' Message ==="
|
||||||
|
echo ""
|
||||||
|
echo "OPTION 1: Clean Shutdown and Boot Sequence (Recommended)"
|
||||||
|
echo " 1. Disconnect the external M.2 USB drive (sda)"
|
||||||
|
echo " 2. Perform a clean shutdown: sudo shutdown -h now"
|
||||||
|
echo " 3. Wait 10 seconds after system powers off"
|
||||||
|
echo " 4. Power on the system"
|
||||||
|
echo " 5. The BIOS should recognize the clean boot and clear the flag"
|
||||||
|
echo ""
|
||||||
|
echo "OPTION 2: Enter BIOS Setup"
|
||||||
|
echo " 1. Reboot and press F1 (or F2) during POST to enter BIOS"
|
||||||
|
echo " 2. Go to 'Startup' or 'Boot' menu"
|
||||||
|
echo " 3. Check for any warnings or errors"
|
||||||
|
echo " 4. Save and exit BIOS (F10)"
|
||||||
|
echo " 5. This acknowledges any BIOS messages and clears flags"
|
||||||
|
echo ""
|
||||||
|
echo "OPTION 3: Reset BIOS Boot Order"
|
||||||
|
echo " 1. Enter BIOS (F1 during boot)"
|
||||||
|
echo " 2. Go to Startup → Boot menu"
|
||||||
|
echo " 3. Ensure 'Ubuntu' is at the top of boot order"
|
||||||
|
echo " 4. Disable 'Fast Boot' if enabled (can cause issues with dual boot)"
|
||||||
|
echo " 5. Save and exit"
|
||||||
|
echo ""
|
||||||
|
echo "OPTION 4: Update UEFI Boot Entry Priority (from Linux)"
|
||||||
|
echo " This will ensure Ubuntu on internal drive is the first boot option:"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Do you want to set Ubuntu as the first boot option now? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
UBUNTU_ENTRY=$(efibootmgr | grep -i ubuntu | cut -d'*' -f1 | cut -d't' -f2 | head -1)
|
||||||
|
if [ -n "$UBUNTU_ENTRY" ]; then
|
||||||
|
echo "Setting Boot$UBUNTU_ENTRY (Ubuntu) as first boot option..."
|
||||||
|
sudo efibootmgr -n $UBUNTU_ENTRY
|
||||||
|
echo "✓ Next boot will use Ubuntu entry"
|
||||||
|
echo ""
|
||||||
|
echo "To make this permanent:"
|
||||||
|
BOOT_ORDER=$(efibootmgr | grep BootOrder | cut -d':' -f2 | tr -d ' ')
|
||||||
|
NEW_ORDER="$UBUNTU_ENTRY,${BOOT_ORDER//,${UBUNTU_ENTRY}/}"
|
||||||
|
NEW_ORDER="${NEW_ORDER//${UBUNTU_ENTRY},,/,}"
|
||||||
|
echo "Run: sudo efibootmgr -o $NEW_ORDER"
|
||||||
|
else
|
||||||
|
echo "Could not find Ubuntu boot entry"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Additional Diagnostics ==="
|
||||||
|
echo ""
|
||||||
|
echo "Check system journal for boot issues:"
|
||||||
|
echo " sudo journalctl -b -p err"
|
||||||
|
echo ""
|
||||||
|
echo "Check hardware clock:"
|
||||||
|
echo " sudo hwclock --show"
|
||||||
|
echo ""
|
||||||
|
echo "Verify GRUB is working:"
|
||||||
|
echo " sudo grub-editenv list"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Why This Happens ==="
|
||||||
|
echo ""
|
||||||
|
echo "The Lenovo BIOS shows 'reset system' when:"
|
||||||
|
echo "1. System was not shut down properly (power loss, crash, forced shutdown)"
|
||||||
|
echo "2. BIOS detected a boot failure and wants you to acknowledge it"
|
||||||
|
echo "3. Hardware configuration changed (like resizing partitions)"
|
||||||
|
echo "4. BIOS boot counter wasn't properly reset after successful boot"
|
||||||
|
echo ""
|
||||||
|
echo "Since your GRUB is properly installed and working, this is just a BIOS"
|
||||||
|
echo "notification that needs to be acknowledged through clean boot cycle."
|
||||||
0
lvm-migration-tools/create_auto_startup.sh → old_scripts/create_auto_startup.sh
Executable file → Normal file
0
lvm-migration-tools/create_auto_startup.sh → old_scripts/create_auto_startup.sh
Executable file → Normal file
19
old_scripts/deb-package/DEBIAN/control
Normal file
19
old_scripts/deb-package/DEBIAN/control
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Package: lvm-backup-manager
|
||||||
|
Version: 1.0.0
|
||||||
|
Section: utils
|
||||||
|
Priority: optional
|
||||||
|
Architecture: all
|
||||||
|
Depends: python3, python3-tk, lvm2
|
||||||
|
Maintainer: LVM Backup Team <backup@example.com>
|
||||||
|
Description: Professional LVM Backup Manager with GUI
|
||||||
|
A modern, user-friendly graphical interface for creating
|
||||||
|
block-level backups of LVM volumes. Features include:
|
||||||
|
.
|
||||||
|
* Intuitive drive selection with size information
|
||||||
|
* Real-time progress monitoring
|
||||||
|
* Time estimation and speed indicators
|
||||||
|
* Professional logging and verification
|
||||||
|
* Safe snapshot-based backup process
|
||||||
|
.
|
||||||
|
This tool creates exact, bootable clones of your LVM
|
||||||
|
volumes for complete system backup and recovery.
|
||||||
21
old_scripts/deb-package/DEBIAN/postinst
Normal file
21
old_scripts/deb-package/DEBIAN/postinst
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Post-installation script for LVM Backup Manager
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Set proper permissions
|
||||||
|
chmod +x /usr/bin/lvm-backup-manager
|
||||||
|
chmod +x /usr/bin/lvm-block-backup
|
||||||
|
|
||||||
|
# Create symlink for easy access
|
||||||
|
if [ ! -L /usr/local/bin/lvm-backup-gui ]; then
|
||||||
|
ln -s /usr/bin/lvm-backup-manager /usr/local/bin/lvm-backup-gui
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "LVM Backup Manager installed successfully!"
|
||||||
|
echo "You can now launch it from:"
|
||||||
|
echo " - Applications menu: System Tools → LVM Backup Manager"
|
||||||
|
echo " - Terminal: sudo lvm-backup-manager"
|
||||||
|
echo " - Desktop: Double-click the LVM Backup Manager icon"
|
||||||
|
|
||||||
|
exit 0
|
||||||
23
old_scripts/deb-package/usr/bin/lvm-backup-manager
Normal file
23
old_scripts/deb-package/usr/bin/lvm-backup-manager
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# LVM Backup Manager GUI Wrapper
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "LVM Backup Manager requires root privileges."
|
||||||
|
echo "Please run with: sudo lvm-backup-manager"
|
||||||
|
|
||||||
|
# Try to launch with pkexec if available
|
||||||
|
if command -v pkexec >/dev/null 2>&1; then
|
||||||
|
exec pkexec "$0" "$@"
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set proper path
|
||||||
|
SCRIPT_DIR="$(dirname "$0")"
|
||||||
|
export PATH="$SCRIPT_DIR:$PATH"
|
||||||
|
|
||||||
|
# Launch the GUI
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
exec python3 "$SCRIPT_DIR/lvm_backup_gui.py" "$@"
|
||||||
193
old_scripts/deb-package/usr/bin/lvm-block-backup
Normal file
193
old_scripts/deb-package/usr/bin/lvm-block-backup
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LVM Block-Level Backup Script
|
||||||
|
# Creates consistent snapshots and clones entire volumes block-for-block
|
||||||
|
# This is the ONLY backup script you need!
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SOURCE_VG="internal-vg"
|
||||||
|
TARGET_VG="migration-vg"
|
||||||
|
SNAPSHOT_SIZE="2G"
|
||||||
|
LOG_FILE="/var/log/lvm-block-backup.log"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
local message="[SUCCESS] $1"
|
||||||
|
echo -e "${GREEN}$message${NC}"
|
||||||
|
echo "$message" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
warning() {
|
||||||
|
local message="[WARNING] $1"
|
||||||
|
echo -e "${YELLOW}$message${NC}"
|
||||||
|
echo "$message" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_requirements() {
|
||||||
|
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"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if source VG exists
|
||||||
|
if ! vgs "$SOURCE_VG" >/dev/null 2>&1; then
|
||||||
|
error "Source volume group '$SOURCE_VG' not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if target VG exists
|
||||||
|
if ! vgs "$TARGET_VG" >/dev/null 2>&1; then
|
||||||
|
error "Target volume group '$TARGET_VG' not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if volumes exist
|
||||||
|
local volumes=("root" "home" "boot")
|
||||||
|
for vol in "${volumes[@]}"; do
|
||||||
|
if ! lvs "$SOURCE_VG/$vol" >/dev/null 2>&1; then
|
||||||
|
error "Source logical volume '$SOURCE_VG/$vol' not found"
|
||||||
|
fi
|
||||||
|
if ! lvs "$TARGET_VG/$vol" >/dev/null 2>&1; then
|
||||||
|
error "Target logical volume '$TARGET_VG/$vol' not found"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check available space for snapshots
|
||||||
|
local vg_free=$(vgs --noheadings -o vg_free --units g "$SOURCE_VG" | tr -d ' G')
|
||||||
|
local vg_free_int=${vg_free%.*}
|
||||||
|
|
||||||
|
if [ "$vg_free_int" -lt 6 ]; then
|
||||||
|
error "Insufficient free space for snapshots. Need at least 6GB, have ${vg_free}GB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "System requirements check passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_snapshots() {
|
||||||
|
log "Cleaning up any existing snapshots..."
|
||||||
|
lvremove -f "$SOURCE_VG/root-backup-snap" 2>/dev/null || true
|
||||||
|
lvremove -f "$SOURCE_VG/home-backup-snap" 2>/dev/null || true
|
||||||
|
lvremove -f "$SOURCE_VG/boot-backup-snap" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
create_snapshots() {
|
||||||
|
log "Creating LVM snapshots for consistent backup..."
|
||||||
|
|
||||||
|
cleanup_snapshots
|
||||||
|
|
||||||
|
# Create snapshots
|
||||||
|
lvcreate -L "$SNAPSHOT_SIZE" -s -n root-backup-snap "$SOURCE_VG/root" || error "Failed to create root snapshot"
|
||||||
|
lvcreate -L "$SNAPSHOT_SIZE" -s -n home-backup-snap "$SOURCE_VG/home" || error "Failed to create home snapshot"
|
||||||
|
lvcreate -L 1G -s -n boot-backup-snap "$SOURCE_VG/boot" || error "Failed to create boot snapshot"
|
||||||
|
|
||||||
|
success "Snapshots created successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
clone_volumes() {
|
||||||
|
log "Starting block-level volume cloning..."
|
||||||
|
|
||||||
|
# Unmount target volumes if mounted
|
||||||
|
umount "/dev/$TARGET_VG/home" 2>/dev/null || true
|
||||||
|
umount "/dev/$TARGET_VG/root" 2>/dev/null || true
|
||||||
|
umount "/dev/$TARGET_VG/boot" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Clone root volume
|
||||||
|
log "Cloning root volume (this may take a while)..."
|
||||||
|
dd if="/dev/$SOURCE_VG/root-backup-snap" of="/dev/$TARGET_VG/root" bs=64M status=progress || error "Failed to clone root volume"
|
||||||
|
success "Root volume cloned"
|
||||||
|
|
||||||
|
# Clone home volume
|
||||||
|
log "Cloning home volume (this will take the longest)..."
|
||||||
|
dd if="/dev/$SOURCE_VG/home-backup-snap" of="/dev/$TARGET_VG/home" bs=64M status=progress || error "Failed to clone home volume"
|
||||||
|
success "Home volume cloned"
|
||||||
|
|
||||||
|
# Clone boot volume
|
||||||
|
log "Cloning boot volume..."
|
||||||
|
dd if="/dev/$SOURCE_VG/boot-backup-snap" of="/dev/$TARGET_VG/boot" bs=64M status=progress || error "Failed to clone boot volume"
|
||||||
|
success "Boot volume cloned"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_backup() {
|
||||||
|
log "Verifying backup integrity..."
|
||||||
|
|
||||||
|
# Check filesystem integrity
|
||||||
|
fsck -n "/dev/$TARGET_VG/root" || warning "Root filesystem check showed issues"
|
||||||
|
fsck -n "/dev/$TARGET_VG/boot" || warning "Boot filesystem check showed issues"
|
||||||
|
|
||||||
|
# Note: Can't check encrypted home volume without decryption
|
||||||
|
|
||||||
|
success "Backup verification completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_backup_info() {
|
||||||
|
log "Creating backup information..."
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
=====================================
|
||||||
|
LVM Block-Level Backup Complete
|
||||||
|
=====================================
|
||||||
|
Date: $(date)
|
||||||
|
Source: $SOURCE_VG
|
||||||
|
Target: $TARGET_VG
|
||||||
|
|
||||||
|
Volume Information:
|
||||||
|
$(lvs $SOURCE_VG $TARGET_VG)
|
||||||
|
|
||||||
|
The external drive now contains an exact block-level copy of your internal drive.
|
||||||
|
You can boot from the external drive by selecting it in your BIOS/UEFI boot menu.
|
||||||
|
|
||||||
|
To mount the backup volumes:
|
||||||
|
sudo mount /dev/$TARGET_VG/root /mnt/backup-root
|
||||||
|
sudo mount /dev/$TARGET_VG/boot /mnt/backup-boot
|
||||||
|
# Home volume needs LUKS decryption first
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}=== LVM Block-Level Backup Tool ===${NC}"
|
||||||
|
echo "This will create an exact copy of your internal LVM volumes to the external drive."
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Are you sure you want to proceed? This will OVERWRITE data on $TARGET_VG! [y/N]: " confirm
|
||||||
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_requirements
|
||||||
|
create_snapshots
|
||||||
|
clone_volumes
|
||||||
|
cleanup_snapshots
|
||||||
|
verify_backup
|
||||||
|
show_backup_info
|
||||||
|
|
||||||
|
success "Backup completed successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap to cleanup on exit
|
||||||
|
trap cleanup_snapshots EXIT
|
||||||
|
|
||||||
|
main "$@"
|
||||||
541
old_scripts/deb-package/usr/bin/lvm_backup_gui.py
Normal file
541
old_scripts/deb-package/usr/bin/lvm_backup_gui.py
Normal file
@@ -0,0 +1,541 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
LVM Backup GUI - Professional interface for LVM snapshot backups
|
||||||
|
Creates block-level clones of LVM volumes with progress monitoring
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, messagebox, scrolledtext
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
class LVMBackupGUI:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("LVM Backup Manager")
|
||||||
|
self.root.geometry("900x700")
|
||||||
|
self.root.resizable(True, True)
|
||||||
|
|
||||||
|
# Configure style
|
||||||
|
self.setup_styles()
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
self.source_vg = tk.StringVar()
|
||||||
|
self.target_vg = tk.StringVar()
|
||||||
|
self.backup_running = False
|
||||||
|
self.backup_process = None
|
||||||
|
|
||||||
|
# Create GUI
|
||||||
|
self.create_widgets()
|
||||||
|
self.refresh_drives()
|
||||||
|
|
||||||
|
def setup_styles(self):
|
||||||
|
"""Configure modern styling"""
|
||||||
|
style = ttk.Style()
|
||||||
|
|
||||||
|
# Configure colors and fonts
|
||||||
|
self.colors = {
|
||||||
|
'primary': '#2196F3',
|
||||||
|
'secondary': '#FFC107',
|
||||||
|
'success': '#4CAF50',
|
||||||
|
'danger': '#F44336',
|
||||||
|
'warning': '#FF9800',
|
||||||
|
'light': '#F5F5F5',
|
||||||
|
'dark': '#333333'
|
||||||
|
}
|
||||||
|
|
||||||
|
style.configure('Title.TLabel', font=('Arial', 16, 'bold'))
|
||||||
|
style.configure('Heading.TLabel', font=('Arial', 12, 'bold'))
|
||||||
|
style.configure('Info.TLabel', font=('Arial', 10))
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
"""Create the main GUI interface"""
|
||||||
|
|
||||||
|
# Main container with padding
|
||||||
|
main_frame = ttk.Frame(self.root, padding="20")
|
||||||
|
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="🛡️ LVM Backup Manager", style='Title.TLabel')
|
||||||
|
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
|
||||||
|
|
||||||
|
# Source drive selection
|
||||||
|
self.create_drive_selection_frame(main_frame, "Source Drive", 1, self.source_vg, True)
|
||||||
|
|
||||||
|
# Arrow
|
||||||
|
arrow_label = ttk.Label(main_frame, text="⬇️", font=('Arial', 20))
|
||||||
|
arrow_label.grid(row=2, column=1, pady=10)
|
||||||
|
|
||||||
|
# Target drive selection
|
||||||
|
self.create_drive_selection_frame(main_frame, "Target Drive", 3, self.target_vg, False)
|
||||||
|
|
||||||
|
# Backup info frame
|
||||||
|
self.create_backup_info_frame(main_frame, 4)
|
||||||
|
|
||||||
|
# Control buttons
|
||||||
|
self.create_control_frame(main_frame, 5)
|
||||||
|
|
||||||
|
# Progress frame
|
||||||
|
self.create_progress_frame(main_frame, 6)
|
||||||
|
|
||||||
|
# Log frame
|
||||||
|
self.create_log_frame(main_frame, 7)
|
||||||
|
|
||||||
|
def create_drive_selection_frame(self, parent, title, row, var, is_source):
|
||||||
|
"""Create drive selection section"""
|
||||||
|
frame = ttk.LabelFrame(parent, text=title, padding="10")
|
||||||
|
frame.grid(row=row, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# Drive dropdown
|
||||||
|
ttk.Label(frame, text="Volume Group:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
|
||||||
|
|
||||||
|
combo = ttk.Combobox(frame, textvariable=var, state='readonly', width=30)
|
||||||
|
combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
|
||||||
|
combo.bind('<<ComboboxSelected>>', lambda e: self.update_drive_info())
|
||||||
|
|
||||||
|
refresh_btn = ttk.Button(frame, text="🔄 Refresh", command=self.refresh_drives)
|
||||||
|
refresh_btn.grid(row=0, column=2)
|
||||||
|
|
||||||
|
# Drive info labels
|
||||||
|
info_frame = ttk.Frame(frame)
|
||||||
|
info_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(10, 0))
|
||||||
|
info_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# Store references for updating
|
||||||
|
setattr(self, f"{title.lower().replace(' ', '_')}_info", info_frame)
|
||||||
|
|
||||||
|
if is_source:
|
||||||
|
self.source_combo = combo
|
||||||
|
else:
|
||||||
|
self.target_combo = combo
|
||||||
|
|
||||||
|
def create_backup_info_frame(self, parent, row):
|
||||||
|
"""Create backup information display"""
|
||||||
|
frame = ttk.LabelFrame(parent, text="📊 Backup Information", padding="10")
|
||||||
|
frame.grid(row=row, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
self.backup_info_labels = {}
|
||||||
|
|
||||||
|
info_items = [
|
||||||
|
("Total Size:", "total_size"),
|
||||||
|
("Estimated Time:", "est_time"),
|
||||||
|
("Transfer Speed:", "speed"),
|
||||||
|
("Status:", "status")
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (label, key) in enumerate(info_items):
|
||||||
|
ttk.Label(frame, text=label).grid(row=i//2, column=(i%2)*2, sticky=tk.W, padx=(0, 10), pady=2)
|
||||||
|
value_label = ttk.Label(frame, text="Not calculated", style='Info.TLabel')
|
||||||
|
value_label.grid(row=i//2, column=(i%2)*2+1, sticky=tk.W, padx=(0, 20), pady=2)
|
||||||
|
self.backup_info_labels[key] = value_label
|
||||||
|
|
||||||
|
def create_control_frame(self, parent, row):
|
||||||
|
"""Create control buttons"""
|
||||||
|
frame = ttk.Frame(parent)
|
||||||
|
frame.grid(row=row, column=0, columnspan=3, pady=20)
|
||||||
|
|
||||||
|
self.start_btn = ttk.Button(frame, text="▶️ Start Backup", command=self.start_backup, style='Accent.TButton')
|
||||||
|
self.start_btn.pack(side=tk.LEFT, padx=(0, 10))
|
||||||
|
|
||||||
|
self.stop_btn = ttk.Button(frame, text="⏹️ Stop Backup", command=self.stop_backup, state='disabled')
|
||||||
|
self.stop_btn.pack(side=tk.LEFT, padx=(0, 10))
|
||||||
|
|
||||||
|
self.verify_btn = ttk.Button(frame, text="✅ Verify Backup", command=self.verify_backup)
|
||||||
|
self.verify_btn.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
def create_progress_frame(self, parent, row):
|
||||||
|
"""Create progress monitoring"""
|
||||||
|
frame = ttk.LabelFrame(parent, text="📈 Progress", padding="10")
|
||||||
|
frame.grid(row=row, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Overall progress
|
||||||
|
ttk.Label(frame, text="Overall Progress:").grid(row=0, column=0, sticky=tk.W)
|
||||||
|
self.overall_progress = ttk.Progressbar(frame, mode='determinate', length=400)
|
||||||
|
self.overall_progress.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(5, 10))
|
||||||
|
|
||||||
|
# Current operation
|
||||||
|
self.current_operation = ttk.Label(frame, text="Ready to start backup", style='Info.TLabel')
|
||||||
|
self.current_operation.grid(row=2, column=0, sticky=tk.W)
|
||||||
|
|
||||||
|
# Time remaining
|
||||||
|
self.time_remaining = ttk.Label(frame, text="", style='Info.TLabel')
|
||||||
|
self.time_remaining.grid(row=3, column=0, sticky=tk.W)
|
||||||
|
|
||||||
|
def create_log_frame(self, parent, row):
|
||||||
|
"""Create log output"""
|
||||||
|
frame = ttk.LabelFrame(parent, text="📝 Log Output", padding="10")
|
||||||
|
frame.grid(row=row, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.rowconfigure(0, weight=1)
|
||||||
|
parent.rowconfigure(row, weight=1)
|
||||||
|
|
||||||
|
self.log_text = scrolledtext.ScrolledText(frame, height=12, width=80, font=('Consolas', 9))
|
||||||
|
self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||||
|
|
||||||
|
# Add initial message
|
||||||
|
self.log("LVM Backup Manager initialized")
|
||||||
|
self.log("Select source and target drives to begin")
|
||||||
|
|
||||||
|
def log(self, message):
|
||||||
|
"""Add message to log with timestamp"""
|
||||||
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
log_message = f"[{timestamp}] {message}\n"
|
||||||
|
|
||||||
|
self.log_text.insert(tk.END, log_message)
|
||||||
|
self.log_text.see(tk.END)
|
||||||
|
self.root.update_idletasks()
|
||||||
|
|
||||||
|
def refresh_drives(self):
|
||||||
|
"""Scan for available LVM volume groups"""
|
||||||
|
try:
|
||||||
|
self.log("Scanning for LVM volume groups...")
|
||||||
|
|
||||||
|
# Get volume groups
|
||||||
|
result = subprocess.run(['sudo', 'vgs', '--noheadings', '-o', 'vg_name,vg_size,vg_free'],
|
||||||
|
capture_output=True, text=True, check=True)
|
||||||
|
|
||||||
|
vgs = []
|
||||||
|
for line in result.stdout.strip().split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
parts = line.strip().split()
|
||||||
|
if len(parts) >= 3:
|
||||||
|
vg_name = parts[0]
|
||||||
|
vg_size = parts[1]
|
||||||
|
vg_free = parts[2]
|
||||||
|
vgs.append(f"{vg_name} ({vg_size} total, {vg_free} free)")
|
||||||
|
|
||||||
|
# Update comboboxes
|
||||||
|
self.source_combo['values'] = vgs
|
||||||
|
self.target_combo['values'] = vgs
|
||||||
|
|
||||||
|
if vgs:
|
||||||
|
self.log(f"Found {len(vgs)} volume groups")
|
||||||
|
else:
|
||||||
|
self.log("No LVM volume groups found")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
self.log(f"Error scanning drives: {e}")
|
||||||
|
messagebox.showerror("Error", "Failed to scan for LVM volume groups. Make sure you have LVM installed and proper permissions.")
|
||||||
|
|
||||||
|
def update_drive_info(self):
|
||||||
|
"""Update drive information when selection changes"""
|
||||||
|
if not self.source_vg.get() or not self.target_vg.get():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
source_vg = self.source_vg.get().split()[0]
|
||||||
|
target_vg = self.target_vg.get().split()[0]
|
||||||
|
|
||||||
|
# Get detailed volume information
|
||||||
|
source_info = self.get_vg_details(source_vg)
|
||||||
|
target_info = self.get_vg_details(target_vg)
|
||||||
|
|
||||||
|
# Calculate backup information
|
||||||
|
self.calculate_backup_info(source_info, target_info)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Error updating drive info: {e}")
|
||||||
|
|
||||||
|
def get_vg_details(self, vg_name):
|
||||||
|
"""Get detailed information about a volume group"""
|
||||||
|
try:
|
||||||
|
# Get VG info
|
||||||
|
vg_result = subprocess.run(['sudo', 'vgs', vg_name, '--noheadings', '-o', 'vg_size,vg_free,vg_uuid'],
|
||||||
|
capture_output=True, text=True, check=True)
|
||||||
|
vg_parts = vg_result.stdout.strip().split()
|
||||||
|
|
||||||
|
# Get LV info
|
||||||
|
lv_result = subprocess.run(['sudo', 'lvs', vg_name, '--noheadings', '-o', 'lv_name,lv_size'],
|
||||||
|
capture_output=True, text=True, check=True)
|
||||||
|
|
||||||
|
volumes = []
|
||||||
|
total_lv_size = 0
|
||||||
|
for line in lv_result.stdout.strip().split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
parts = line.strip().split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
lv_name = parts[0]
|
||||||
|
lv_size = parts[1]
|
||||||
|
volumes.append((lv_name, lv_size))
|
||||||
|
# Convert size to bytes for calculation
|
||||||
|
size_bytes = self.parse_size_to_bytes(lv_size)
|
||||||
|
total_lv_size += size_bytes
|
||||||
|
|
||||||
|
return {
|
||||||
|
'name': vg_name,
|
||||||
|
'total_size': vg_parts[0],
|
||||||
|
'free_size': vg_parts[1],
|
||||||
|
'uuid': vg_parts[2],
|
||||||
|
'volumes': volumes,
|
||||||
|
'total_lv_size_bytes': total_lv_size
|
||||||
|
}
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_size_to_bytes(self, size_str):
|
||||||
|
"""Parse LVM size string to bytes"""
|
||||||
|
size_str = size_str.strip()
|
||||||
|
multipliers = {'B': 1, 'K': 1024, 'M': 1024**2, 'G': 1024**3, 'T': 1024**4}
|
||||||
|
|
||||||
|
# Extract number and unit
|
||||||
|
if size_str[-1].upper() in multipliers:
|
||||||
|
number = float(size_str[:-1])
|
||||||
|
unit = size_str[-1].upper()
|
||||||
|
else:
|
||||||
|
number = float(size_str)
|
||||||
|
unit = 'B'
|
||||||
|
|
||||||
|
return int(number * multipliers.get(unit, 1))
|
||||||
|
|
||||||
|
def calculate_backup_info(self, source_info, target_info):
|
||||||
|
"""Calculate and display backup information"""
|
||||||
|
if not source_info or not target_info:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate total size to backup
|
||||||
|
total_bytes = source_info['total_lv_size_bytes']
|
||||||
|
total_gb = total_bytes / (1024**3)
|
||||||
|
|
||||||
|
# Estimate time (based on typical speeds: 200-400 MB/s)
|
||||||
|
avg_speed_mbs = 250 # MB/s
|
||||||
|
est_seconds = total_bytes / (avg_speed_mbs * 1024 * 1024)
|
||||||
|
est_time = str(timedelta(seconds=int(est_seconds)))
|
||||||
|
|
||||||
|
# Update labels
|
||||||
|
self.backup_info_labels['total_size'].config(text=f"{total_gb:.1f} GB")
|
||||||
|
self.backup_info_labels['est_time'].config(text=est_time)
|
||||||
|
self.backup_info_labels['speed'].config(text=f"~{avg_speed_mbs} MB/s")
|
||||||
|
self.backup_info_labels['status'].config(text="Ready")
|
||||||
|
|
||||||
|
self.log(f"Backup calculation: {total_gb:.1f} GB, estimated {est_time}")
|
||||||
|
|
||||||
|
def start_backup(self):
|
||||||
|
"""Start the backup process"""
|
||||||
|
if not self.source_vg.get() or not self.target_vg.get():
|
||||||
|
messagebox.showerror("Error", "Please select both source and target drives")
|
||||||
|
return
|
||||||
|
|
||||||
|
source_vg = self.source_vg.get().split()[0]
|
||||||
|
target_vg = self.target_vg.get().split()[0]
|
||||||
|
|
||||||
|
if source_vg == target_vg:
|
||||||
|
messagebox.showerror("Error", "Source and target cannot be the same drive")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Confirm backup
|
||||||
|
if not messagebox.askyesno("Confirm Backup",
|
||||||
|
f"This will overwrite all data on {target_vg}.\n\nAre you sure you want to continue?"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update UI state
|
||||||
|
self.backup_running = True
|
||||||
|
self.start_btn.config(state='disabled')
|
||||||
|
self.stop_btn.config(state='normal')
|
||||||
|
self.overall_progress.config(value=0)
|
||||||
|
self.backup_info_labels['status'].config(text="Running...")
|
||||||
|
|
||||||
|
# Start backup in thread
|
||||||
|
self.backup_thread = threading.Thread(target=self.run_backup, args=(source_vg, target_vg))
|
||||||
|
self.backup_thread.daemon = True
|
||||||
|
self.backup_thread.start()
|
||||||
|
|
||||||
|
def run_backup(self, source_vg, target_vg):
|
||||||
|
"""Run the actual backup process"""
|
||||||
|
try:
|
||||||
|
self.log(f"Starting backup: {source_vg} → {target_vg}")
|
||||||
|
|
||||||
|
# Modify the backup script to work with our selected VGs
|
||||||
|
script_path = os.path.join(os.path.dirname(__file__), 'lvm_block_backup.sh')
|
||||||
|
|
||||||
|
# Create a temporary script with modified VG names
|
||||||
|
temp_script = self.create_temp_script(script_path, source_vg, target_vg)
|
||||||
|
|
||||||
|
# Run the backup script
|
||||||
|
self.backup_process = subprocess.Popen(
|
||||||
|
['sudo', temp_script],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
text=True,
|
||||||
|
bufsize=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Monitor progress
|
||||||
|
self.monitor_backup_progress()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Backup failed: {e}")
|
||||||
|
self.backup_finished(False)
|
||||||
|
|
||||||
|
def create_temp_script(self, original_script, source_vg, target_vg):
|
||||||
|
"""Create a temporary script with modified VG names"""
|
||||||
|
temp_script = '/tmp/lvm_backup_gui_temp.sh'
|
||||||
|
|
||||||
|
with open(original_script, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Replace VG names
|
||||||
|
content = content.replace('SOURCE_VG="internal-vg"', f'SOURCE_VG="{source_vg}"')
|
||||||
|
content = content.replace('TARGET_VG="migration-vg"', f'TARGET_VG="{target_vg}"')
|
||||||
|
|
||||||
|
# Make it auto-answer 'y' to confirmation
|
||||||
|
content = content.replace('read -p "Are you sure you want to proceed?', 'echo "Auto-confirmed by GUI"; confirm="y"; #read -p "Are you sure you want to proceed?')
|
||||||
|
|
||||||
|
with open(temp_script, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
os.chmod(temp_script, 0o755)
|
||||||
|
return temp_script
|
||||||
|
|
||||||
|
def monitor_backup_progress(self):
|
||||||
|
"""Monitor backup progress and update UI"""
|
||||||
|
if not self.backup_running or not self.backup_process:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Read output
|
||||||
|
line = self.backup_process.stdout.readline()
|
||||||
|
if line:
|
||||||
|
line = line.strip()
|
||||||
|
self.log(line)
|
||||||
|
|
||||||
|
# Parse progress from dd output
|
||||||
|
if 'kopiert' in line or 'copied' in line:
|
||||||
|
self.parse_dd_progress(line)
|
||||||
|
elif 'SUCCESS' in line:
|
||||||
|
if 'Root volume cloned' in line:
|
||||||
|
self.overall_progress.config(value=33)
|
||||||
|
self.current_operation.config(text="Root volume completed ✅")
|
||||||
|
elif 'Home volume cloned' in line:
|
||||||
|
self.overall_progress.config(value=90)
|
||||||
|
self.current_operation.config(text="Home volume completed ✅")
|
||||||
|
elif 'Boot volume cloned' in line:
|
||||||
|
self.overall_progress.config(value=95)
|
||||||
|
self.current_operation.config(text="Boot volume completed ✅")
|
||||||
|
elif 'Cloning' in line:
|
||||||
|
if 'root' in line.lower():
|
||||||
|
self.current_operation.config(text="📁 Cloning root volume...")
|
||||||
|
elif 'home' in line.lower():
|
||||||
|
self.current_operation.config(text="🏠 Cloning home volume...")
|
||||||
|
elif 'boot' in line.lower():
|
||||||
|
self.current_operation.config(text="⚡ Cloning boot volume...")
|
||||||
|
|
||||||
|
# Check if process is still running
|
||||||
|
if self.backup_process.poll() is None:
|
||||||
|
# Schedule next check
|
||||||
|
self.root.after(100, self.monitor_backup_progress)
|
||||||
|
else:
|
||||||
|
# Process finished
|
||||||
|
success = self.backup_process.returncode == 0
|
||||||
|
self.backup_finished(success)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Error monitoring progress: {e}")
|
||||||
|
self.backup_finished(False)
|
||||||
|
|
||||||
|
def parse_dd_progress(self, line):
|
||||||
|
"""Parse dd progress output"""
|
||||||
|
try:
|
||||||
|
# Look for speed information
|
||||||
|
if 'MB/s' in line:
|
||||||
|
speed_match = re.search(r'(\d+(?:\.\d+)?)\s*MB/s', line)
|
||||||
|
if speed_match:
|
||||||
|
speed = speed_match.group(1)
|
||||||
|
self.backup_info_labels['speed'].config(text=f"{speed} MB/s")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def backup_finished(self, success):
|
||||||
|
"""Handle backup completion"""
|
||||||
|
self.backup_running = False
|
||||||
|
self.start_btn.config(state='normal')
|
||||||
|
self.stop_btn.config(state='disabled')
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.overall_progress.config(value=100)
|
||||||
|
self.current_operation.config(text="✅ Backup completed successfully!")
|
||||||
|
self.backup_info_labels['status'].config(text="Completed")
|
||||||
|
self.log("🎉 Backup completed successfully!")
|
||||||
|
messagebox.showinfo("Success", "Backup completed successfully!")
|
||||||
|
else:
|
||||||
|
self.current_operation.config(text="❌ Backup failed")
|
||||||
|
self.backup_info_labels['status'].config(text="Failed")
|
||||||
|
self.log("❌ Backup failed")
|
||||||
|
messagebox.showerror("Error", "Backup failed. Check the log for details.")
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
if hasattr(self, 'backup_process'):
|
||||||
|
self.backup_process = None
|
||||||
|
|
||||||
|
def stop_backup(self):
|
||||||
|
"""Stop the running backup"""
|
||||||
|
if self.backup_process:
|
||||||
|
self.log("Stopping backup...")
|
||||||
|
self.backup_process.terminate()
|
||||||
|
self.backup_finished(False)
|
||||||
|
|
||||||
|
def verify_backup(self):
|
||||||
|
"""Verify the backup integrity"""
|
||||||
|
if not self.target_vg.get():
|
||||||
|
messagebox.showerror("Error", "Please select a target drive to verify")
|
||||||
|
return
|
||||||
|
|
||||||
|
target_vg = self.target_vg.get().split()[0]
|
||||||
|
|
||||||
|
self.log(f"Verifying backup on {target_vg}...")
|
||||||
|
|
||||||
|
def verify_thread():
|
||||||
|
try:
|
||||||
|
# Run filesystem checks
|
||||||
|
result = subprocess.run(['sudo', 'fsck', '-n', f'/dev/{target_vg}/root'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.log("✅ Root filesystem verification passed")
|
||||||
|
else:
|
||||||
|
self.log("⚠️ Root filesystem verification issues detected")
|
||||||
|
|
||||||
|
result = subprocess.run(['sudo', 'fsck', '-n', f'/dev/{target_vg}/boot'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.log("✅ Boot filesystem verification passed")
|
||||||
|
else:
|
||||||
|
self.log("⚠️ Boot filesystem verification issues detected")
|
||||||
|
|
||||||
|
self.log("Verification completed")
|
||||||
|
messagebox.showinfo("Verification", "Backup verification completed. Check log for details.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Verification error: {e}")
|
||||||
|
messagebox.showerror("Error", f"Verification failed: {e}")
|
||||||
|
|
||||||
|
thread = threading.Thread(target=verify_thread)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point"""
|
||||||
|
# Check if running as root
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
messagebox.showerror("Permission Error",
|
||||||
|
"This application requires root privileges.\n\n" +
|
||||||
|
"Please run with: sudo python3 lvm_backup_gui.py")
|
||||||
|
return
|
||||||
|
|
||||||
|
root = tk.Tk()
|
||||||
|
app = LVMBackupGUI(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name=LVM Backup Manager
|
||||||
|
Comment=Professional LVM volume backup with GUI
|
||||||
|
Exec=pkexec lvm-backup-manager
|
||||||
|
Icon=lvm-backup-manager
|
||||||
|
Categories=System;Administration;
|
||||||
|
Keywords=backup;lvm;snapshot;clone;system;recovery;
|
||||||
|
StartupNotify=true
|
||||||
|
StartupWMClass=LVM Backup Manager
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# LVM Backup Manager
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
LVM Backup Manager is a professional graphical interface for creating block-level backups of LVM (Logical Volume Manager) volumes. It provides an intuitive way to clone entire volume groups while maintaining data consistency through LVM snapshots.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* **Intuitive GUI**: Easy-to-use interface with drive selection and progress monitoring
|
||||||
|
* **Real-time Progress**: Live progress bars and speed indicators during backup
|
||||||
|
* **Safety Features**: Confirmation dialogs and verification tools
|
||||||
|
* **Professional Logging**: Detailed logs with timestamps for troubleshooting
|
||||||
|
* **Snapshot-based**: Uses LVM snapshots for consistent, live backups
|
||||||
|
* **Block-level Cloning**: Creates exact, bootable copies of your volumes
|
||||||
|
|
||||||
|
## System Requirements
|
||||||
|
|
||||||
|
* Linux system with LVM2 installed
|
||||||
|
* Python 3.x with Tkinter support
|
||||||
|
* Root/sudo privileges for LVM operations
|
||||||
|
* External storage device with LVM volume group
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### GUI Application
|
||||||
|
```bash
|
||||||
|
sudo lvm-backup-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Line (legacy)
|
||||||
|
```bash
|
||||||
|
sudo lvm-block-backup
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Detection**: Scans for available LVM volume groups
|
||||||
|
2. **Selection**: Choose source and target volume groups
|
||||||
|
3. **Calculation**: Estimates backup time and data size
|
||||||
|
4. **Snapshots**: Creates consistent snapshots of source volumes
|
||||||
|
5. **Cloning**: Performs block-level copy using dd command
|
||||||
|
6. **Verification**: Checks filesystem integrity of backup
|
||||||
|
7. **Cleanup**: Removes temporary snapshots
|
||||||
|
|
||||||
|
## Safety Notes
|
||||||
|
|
||||||
|
* **Always verify** your target drive selection
|
||||||
|
* **Backup process will overwrite** all data on target volume group
|
||||||
|
* **Test your backups** using the verification feature
|
||||||
|
* **Keep multiple backup copies** for critical data
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions, check the log output in the GUI application or examine system logs.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This software is provided as-is for backup and recovery purposes.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="diskGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#FFC107;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#FF9800;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Background circle -->
|
||||||
|
<circle cx="32" cy="32" r="30" fill="url(#bgGradient)" stroke="#1565C0" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- Hard drive icon -->
|
||||||
|
<rect x="12" y="20" width="20" height="14" rx="2" fill="url(#diskGradient)" stroke="#E65100" stroke-width="1"/>
|
||||||
|
<rect x="14" y="22" width="16" height="2" fill="#FFF3E0" opacity="0.8"/>
|
||||||
|
<rect x="14" y="25" width="16" height="2" fill="#FFF3E0" opacity="0.6"/>
|
||||||
|
<rect x="14" y="28" width="16" height="2" fill="#FFF3E0" opacity="0.4"/>
|
||||||
|
<circle cx="29" cy="30" r="1.5" fill="#E65100"/>
|
||||||
|
|
||||||
|
<!-- Arrow pointing right -->
|
||||||
|
<path d="M 35 27 L 42 27 L 38 23 M 42 27 L 38 31" stroke="#4CAF50" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Second hard drive -->
|
||||||
|
<rect x="44" y="20" width="20" height="14" rx="2" fill="url(#diskGradient)" stroke="#E65100" stroke-width="1"/>
|
||||||
|
<rect x="46" y="22" width="16" height="2" fill="#FFF3E0" opacity="0.8"/>
|
||||||
|
<rect x="46" y="25" width="16" height="2" fill="#FFF3E0" opacity="0.6"/>
|
||||||
|
<rect x="46" y="28" width="16" height="2" fill="#FFF3E0" opacity="0.4"/>
|
||||||
|
<circle cx="61" cy="30" r="1.5" fill="#E65100"/>
|
||||||
|
|
||||||
|
<!-- Shield/protection symbol -->
|
||||||
|
<path d="M 32 40 L 28 44 L 32 48 L 36 44 Z" fill="#4CAF50" stroke="#2E7D32" stroke-width="1"/>
|
||||||
|
<path d="M 30 44 L 32 46 L 36 42" stroke="white" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- LVM text -->
|
||||||
|
<text x="32" y="58" text-anchor="middle" font-family="Arial" font-size="8" font-weight="bold" fill="white">LVM</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
667
old_scripts/direct_clone_backup.sh
Normal file
667
old_scripts/direct_clone_backup.sh
Normal file
@@ -0,0 +1,667 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Direct 1-to-1 Clone Script
|
||||||
|
# Creates an exact copy of internal drive to external drive without LVM conversion
|
||||||
|
# Preserves all partitions, LUKS encryption, and boot structures exactly as they are
|
||||||
|
# MUST BE RUN FROM A LIVE USB 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
|
||||||
|
|
||||||
|
# Configuration variables
|
||||||
|
INTERNAL_DRIVE=""
|
||||||
|
EXTERNAL_DRIVE=""
|
||||||
|
WORK_DIR="/mnt/clone_work"
|
||||||
|
|
||||||
|
# Partition mapping
|
||||||
|
declare -A PARTITION_MAP
|
||||||
|
declare -A PARTITION_FILESYSTEMS
|
||||||
|
declare -A PARTITION_LABELS
|
||||||
|
declare -A PARTITION_UUIDS
|
||||||
|
|
||||||
|
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.*live\|overlay"; then
|
||||||
|
log "Excluding live USB drive: $drive"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
drives+=("$drive")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#drives[@]} -lt 2 ]; then
|
||||||
|
error "Need at least 2 drives for cloning. 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 cloning:"
|
||||||
|
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"
|
||||||
|
|
||||||
|
# Show partition layout
|
||||||
|
echo " Partitions:"
|
||||||
|
lsblk "$drive" | tail -n +2 | sed 's/^/ /'
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
# 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 completely overwritten!):"
|
||||||
|
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
|
||||||
|
|
||||||
|
# Check drive sizes
|
||||||
|
local internal_size_bytes=$(lsblk -bno SIZE "$INTERNAL_DRIVE")
|
||||||
|
local external_size_bytes=$(lsblk -bno SIZE "$EXTERNAL_DRIVE")
|
||||||
|
|
||||||
|
if [ "$external_size_bytes" -lt "$internal_size_bytes" ]; then
|
||||||
|
error "External drive ($EXTERNAL_DRIVE) is smaller than internal drive ($INTERNAL_DRIVE). Cannot clone."
|
||||||
|
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"
|
||||||
|
|
||||||
|
# Final safety check
|
||||||
|
echo
|
||||||
|
echo -e "${RED}⚠️ FINAL SAFETY CHECK ⚠️${NC}"
|
||||||
|
echo "You are about to COMPLETELY CLONE this drive:"
|
||||||
|
echo " Source: $INTERNAL_DRIVE"
|
||||||
|
echo " Target: $EXTERNAL_DRIVE (will be completely overwritten!)"
|
||||||
|
echo
|
||||||
|
echo "Current partitions on target drive that will be DESTROYED:"
|
||||||
|
lsblk "$EXTERNAL_DRIVE" || true
|
||||||
|
echo
|
||||||
|
echo -e "${RED}This will DESTROY ALL DATA on $EXTERNAL_DRIVE!${NC}"
|
||||||
|
echo "The entire drive will be overwritten with an exact copy of $INTERNAL_DRIVE"
|
||||||
|
echo
|
||||||
|
read -p "Type 'CLONE' to confirm you want to overwrite $EXTERNAL_DRIVE: " confirmation
|
||||||
|
if [ "$confirmation" != "CLONE" ]; then
|
||||||
|
error "Clone operation cancelled by user for safety"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
analyze_source_drive() {
|
||||||
|
log "Analyzing source drive structure..."
|
||||||
|
|
||||||
|
# Get partition information
|
||||||
|
local partitions=($(lsblk -pno NAME "$INTERNAL_DRIVE" | grep -v "^$INTERNAL_DRIVE$"))
|
||||||
|
|
||||||
|
echo "Source drive structure:"
|
||||||
|
lsblk "$INTERNAL_DRIVE"
|
||||||
|
echo
|
||||||
|
|
||||||
|
for part in "${partitions[@]}"; do
|
||||||
|
local size=$(lsblk -no SIZE "$part")
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
local label=$(lsblk -no LABEL "$part")
|
||||||
|
local uuid=$(lsblk -no UUID "$part")
|
||||||
|
local mountpoint=$(lsblk -no MOUNTPOINT "$part")
|
||||||
|
|
||||||
|
echo " $part:"
|
||||||
|
echo " Size: $size"
|
||||||
|
echo " Filesystem: ${fstype:-'unknown'}"
|
||||||
|
echo " Label: ${label:-'none'}"
|
||||||
|
echo " UUID: ${uuid:-'none'}"
|
||||||
|
echo " Mounted at: ${mountpoint:-'not mounted'}"
|
||||||
|
|
||||||
|
# Store information for later use
|
||||||
|
PARTITION_FILESYSTEMS["$part"]="$fstype"
|
||||||
|
PARTITION_LABELS["$part"]="$label"
|
||||||
|
PARTITION_UUIDS["$part"]="$uuid"
|
||||||
|
|
||||||
|
# Check if it's encrypted
|
||||||
|
if [[ "$fstype" == "crypto_LUKS" ]]; then
|
||||||
|
log "Found LUKS encrypted partition: $part"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
success "Source drive 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. For safety, this should be run from a live USB!"
|
||||||
|
confirm_action "Continue anyway? (Not recommended)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for required tools
|
||||||
|
local missing_tools=()
|
||||||
|
for tool in dd pv rsync cryptsetup grub-install lsblk blkid partprobe; 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 "Installing missing tools..."
|
||||||
|
apt update && apt install -y util-linux pv rsync cryptsetup grub-common grub-efi-amd64 || {
|
||||||
|
error "Failed to install required tools. Please install manually: ${missing_tools[*]}"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Prerequisites check passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
perform_direct_clone() {
|
||||||
|
log "Starting direct drive clone..."
|
||||||
|
log "This will create an exact bit-for-bit copy of $INTERNAL_DRIVE to $EXTERNAL_DRIVE"
|
||||||
|
|
||||||
|
# Get drive size for progress tracking
|
||||||
|
local total_size_bytes=$(lsblk -bno SIZE "$INTERNAL_DRIVE")
|
||||||
|
local total_size_human=$(lsblk -no SIZE "$INTERNAL_DRIVE")
|
||||||
|
|
||||||
|
log "Cloning $total_size_human from $INTERNAL_DRIVE to $EXTERNAL_DRIVE"
|
||||||
|
log "This will take a while depending on drive size and speed..."
|
||||||
|
|
||||||
|
# Use dd with progress monitoring via pv
|
||||||
|
if dd if="$INTERNAL_DRIVE" bs=64M status=none | pv -s "$total_size_bytes" -w 80 | dd of="$EXTERNAL_DRIVE" bs=64M status=none; then
|
||||||
|
success "Drive clone completed successfully"
|
||||||
|
else
|
||||||
|
error "Drive clone failed!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Force kernel to re-read partition table
|
||||||
|
log "Updating partition table on cloned drive..."
|
||||||
|
partprobe "$EXTERNAL_DRIVE" || warning "Failed to update partition table"
|
||||||
|
sync
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
success "Direct clone operation completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_uuids_and_boot() {
|
||||||
|
log "Fixing UUIDs and boot configuration on cloned drive..."
|
||||||
|
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
|
||||||
|
# Get new partition list from cloned drive
|
||||||
|
local new_partitions=($(lsblk -pno NAME "$EXTERNAL_DRIVE" | grep -v "^$EXTERNAL_DRIVE$"))
|
||||||
|
|
||||||
|
# Create new UUIDs for all partitions to avoid conflicts
|
||||||
|
for part in "${new_partitions[@]}"; do
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
local old_uuid=$(lsblk -no UUID "$part")
|
||||||
|
|
||||||
|
log "Processing partition $part (filesystem: ${fstype:-'unknown'})"
|
||||||
|
|
||||||
|
# Skip encrypted partitions - they'll keep their UUID
|
||||||
|
if [[ "$fstype" == "crypto_LUKS" ]]; then
|
||||||
|
log "Skipping LUKS partition UUID change - encryption handles this"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip swap partitions for now - we'll handle them separately
|
||||||
|
if [[ "$fstype" == "swap" ]]; then
|
||||||
|
log "Regenerating swap UUID for $part"
|
||||||
|
swapoff "$part" 2>/dev/null || true
|
||||||
|
mkswap -U random "$part" || warning "Failed to regenerate swap UUID"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate new UUID for filesystem partitions
|
||||||
|
if [[ -n "$fstype" && "$fstype" != "crypto_LUKS" ]]; then
|
||||||
|
case "$fstype" in
|
||||||
|
"ext2"|"ext3"|"ext4")
|
||||||
|
log "Generating new UUID for ext filesystem on $part"
|
||||||
|
tune2fs -U random "$part" || warning "Failed to change UUID for $part"
|
||||||
|
;;
|
||||||
|
"vfat")
|
||||||
|
log "Generating new UUID for FAT filesystem on $part"
|
||||||
|
# For FAT32, we'll use mlabel (part of mtools) if available, or skip
|
||||||
|
if command -v mlabel >/dev/null 2>&1; then
|
||||||
|
# Generate a random 8-character hex string for FAT32
|
||||||
|
local new_fat_uuid=$(openssl rand -hex 4 | tr '[:lower:]' '[:upper:]')
|
||||||
|
echo "mtools_skip_check=1" > ~/.mtoolsrc
|
||||||
|
mlabel -i "$part" -N "${new_fat_uuid:0:8}" || warning "Failed to change FAT UUID"
|
||||||
|
rm -f ~/.mtoolsrc
|
||||||
|
else
|
||||||
|
warning "Cannot change FAT UUID - mtools not available"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "Skipping UUID change for unknown filesystem type: $fstype"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Now we need to update /etc/fstab on the cloned system
|
||||||
|
log "Mounting cloned system to update configuration..."
|
||||||
|
|
||||||
|
# Find the root partition (usually the largest ext4 partition or check for typical structure)
|
||||||
|
local root_partition=""
|
||||||
|
local boot_partition=""
|
||||||
|
local efi_partition=""
|
||||||
|
|
||||||
|
for part in "${new_partitions[@]}"; do
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
local size_bytes=$(lsblk -bno SIZE "$part")
|
||||||
|
|
||||||
|
# Detect EFI partition (usually small FAT32)
|
||||||
|
if [[ "$fstype" == "vfat" && "$size_bytes" -lt 1073741824 ]]; then # Less than 1GB
|
||||||
|
efi_partition="$part"
|
||||||
|
log "Detected EFI partition: $part"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect boot partition (usually ext4, smaller than root)
|
||||||
|
if [[ "$fstype" == "ext4" && "$size_bytes" -lt 5368709120 && "$size_bytes" -gt 104857600 ]]; then # Between 100MB and 5GB
|
||||||
|
boot_partition="$part"
|
||||||
|
log "Detected boot partition: $part"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect root partition (usually largest ext4 or check for encrypted)
|
||||||
|
if [[ "$fstype" == "ext4" && "$size_bytes" -gt 5368709120 ]]; then # Larger than 5GB
|
||||||
|
root_partition="$part"
|
||||||
|
log "Detected root partition: $part"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle LUKS encrypted partitions
|
||||||
|
if [[ "$fstype" == "crypto_LUKS" ]]; then
|
||||||
|
log "Found encrypted partition that might be root: $part"
|
||||||
|
# We'll try to unlock it if needed
|
||||||
|
local crypt_name="cloned_root_$(basename "$part")"
|
||||||
|
echo "This appears to be an encrypted partition. Please enter the password to mount and update configuration:"
|
||||||
|
if cryptsetup open "$part" "$crypt_name"; then
|
||||||
|
# Check if the decrypted partition is the root
|
||||||
|
local decrypted_fs=$(lsblk -no FSTYPE "/dev/mapper/$crypt_name")
|
||||||
|
if [[ "$decrypted_fs" == "ext4" ]]; then
|
||||||
|
root_partition="/dev/mapper/$crypt_name"
|
||||||
|
log "Using decrypted partition as root: $root_partition"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warning "Could not unlock encrypted partition. Configuration update may be incomplete."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$root_partition" ]; then
|
||||||
|
warning "Could not automatically detect root partition. Manual configuration may be needed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount the cloned root filesystem
|
||||||
|
log "Mounting cloned root filesystem: $root_partition"
|
||||||
|
mount "$root_partition" "$WORK_DIR" || {
|
||||||
|
error "Failed to mount cloned root filesystem"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mount boot partition if exists
|
||||||
|
if [ -n "$boot_partition" ]; then
|
||||||
|
log "Mounting boot partition: $boot_partition"
|
||||||
|
mkdir -p "$WORK_DIR/boot"
|
||||||
|
mount "$boot_partition" "$WORK_DIR/boot" || warning "Failed to mount boot partition"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount EFI partition if exists
|
||||||
|
if [ -n "$efi_partition" ]; then
|
||||||
|
log "Mounting EFI partition: $efi_partition"
|
||||||
|
mkdir -p "$WORK_DIR/boot/efi"
|
||||||
|
mount "$efi_partition" "$WORK_DIR/boot/efi" || warning "Failed to mount EFI partition"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update /etc/fstab with new UUIDs
|
||||||
|
if [ -f "$WORK_DIR/etc/fstab" ]; then
|
||||||
|
log "Updating /etc/fstab with new UUIDs..."
|
||||||
|
cp "$WORK_DIR/etc/fstab" "$WORK_DIR/etc/fstab.backup"
|
||||||
|
|
||||||
|
# Generate new fstab with current UUIDs
|
||||||
|
echo "# /etc/fstab: static file system information." > "$WORK_DIR/etc/fstab.new"
|
||||||
|
echo "# Updated after cloning $(date)" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
echo "#" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
echo "# <file system> <mount point> <type> <options> <dump> <pass>" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
|
||||||
|
# Add entries for each partition with current UUIDs
|
||||||
|
for part in "${new_partitions[@]}"; do
|
||||||
|
local current_uuid=$(lsblk -no UUID "$part")
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
|
||||||
|
if [ -n "$current_uuid" ]; then
|
||||||
|
case "$part" in
|
||||||
|
*"1")
|
||||||
|
if [[ "$fstype" == "vfat" ]]; then
|
||||||
|
echo "UUID=$current_uuid /boot/efi vfat defaults 0 2" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"2")
|
||||||
|
if [[ "$fstype" == "ext4" ]]; then
|
||||||
|
# Could be boot or root - determine by size
|
||||||
|
local size_bytes=$(lsblk -bno SIZE "$part")
|
||||||
|
if [ "$size_bytes" -lt 5368709120 ]; then # Less than 5GB = boot
|
||||||
|
echo "UUID=$current_uuid /boot ext4 defaults 0 2" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
else # Root partition
|
||||||
|
echo "UUID=$current_uuid / ext4 defaults 0 1" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"3")
|
||||||
|
if [[ "$fstype" == "ext4" ]]; then
|
||||||
|
echo "UUID=$current_uuid / ext4 defaults 0 1" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
elif [[ "$fstype" == "swap" ]]; then
|
||||||
|
echo "UUID=$current_uuid none swap sw 0 0" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ "$fstype" == "swap" ]]; then
|
||||||
|
echo "UUID=$current_uuid none swap sw 0 0" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Add tmpfs entry
|
||||||
|
echo "tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0" >> "$WORK_DIR/etc/fstab.new"
|
||||||
|
|
||||||
|
# Replace old fstab with new one
|
||||||
|
mv "$WORK_DIR/etc/fstab.new" "$WORK_DIR/etc/fstab"
|
||||||
|
|
||||||
|
success "Updated /etc/fstab with new UUIDs"
|
||||||
|
else
|
||||||
|
warning "/etc/fstab not found in cloned system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "UUID and boot configuration updated"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_bootloader() {
|
||||||
|
log "Installing/repairing bootloader on cloned drive..."
|
||||||
|
|
||||||
|
# Bind mount necessary filesystems for chroot
|
||||||
|
mount --bind /dev "$WORK_DIR/dev"
|
||||||
|
mount --bind /proc "$WORK_DIR/proc"
|
||||||
|
mount --bind /sys "$WORK_DIR/sys"
|
||||||
|
mount --bind /run "$WORK_DIR/run"
|
||||||
|
|
||||||
|
# Update GRUB configuration and reinstall bootloader
|
||||||
|
chroot "$WORK_DIR" /bin/bash -c "
|
||||||
|
# Update initramfs to ensure all modules are included
|
||||||
|
update-initramfs -u -k all
|
||||||
|
|
||||||
|
# Reinstall GRUB bootloader
|
||||||
|
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck $EXTERNAL_DRIVE
|
||||||
|
|
||||||
|
# Update GRUB configuration
|
||||||
|
update-grub
|
||||||
|
" || warning "Some bootloader operations failed but continuing..."
|
||||||
|
|
||||||
|
# Unmount bind mounts
|
||||||
|
umount "$WORK_DIR/dev" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/proc" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/sys" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/run" 2>/dev/null || true
|
||||||
|
|
||||||
|
success "Bootloader installation completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_clone() {
|
||||||
|
log "Cleaning up..."
|
||||||
|
|
||||||
|
# Unmount all filesystems
|
||||||
|
umount "$WORK_DIR/boot/efi" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/boot" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Close any encrypted volumes we opened
|
||||||
|
for mapper in /dev/mapper/cloned_*; do
|
||||||
|
if [ -b "$mapper" ]; then
|
||||||
|
local crypt_name=$(basename "$mapper")
|
||||||
|
cryptsetup close "$crypt_name" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove work directory
|
||||||
|
rmdir "$WORK_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
success "Cleanup completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_clone() {
|
||||||
|
log "Performing basic verification of cloned drive..."
|
||||||
|
|
||||||
|
# Check if partition table was copied correctly
|
||||||
|
log "Verifying partition table..."
|
||||||
|
local internal_partcount=$(lsblk -no NAME "$INTERNAL_DRIVE" | grep -c "^[├└]─")
|
||||||
|
local external_partcount=$(lsblk -no NAME "$EXTERNAL_DRIVE" | grep -c "^[├└]─")
|
||||||
|
|
||||||
|
if [ "$internal_partcount" -eq "$external_partcount" ]; then
|
||||||
|
success "Partition count matches: $internal_partcount partitions"
|
||||||
|
else
|
||||||
|
warning "Partition count mismatch: internal=$internal_partcount, external=$external_partcount"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show final layout
|
||||||
|
echo
|
||||||
|
echo "Original drive layout:"
|
||||||
|
lsblk "$INTERNAL_DRIVE"
|
||||||
|
echo
|
||||||
|
echo "Cloned drive layout:"
|
||||||
|
lsblk "$EXTERNAL_DRIVE"
|
||||||
|
|
||||||
|
success "Basic verification completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}=== Direct 1-to-1 Clone Script ===${NC}"
|
||||||
|
echo "This script creates an exact copy of your internal drive to external drive"
|
||||||
|
echo "WITHOUT any LVM conversion - preserves original structure exactly"
|
||||||
|
echo "Run this from a live USB system for best results"
|
||||||
|
echo
|
||||||
|
|
||||||
|
check_prerequisites
|
||||||
|
detect_drives
|
||||||
|
analyze_source_drive
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Clone Summary:"
|
||||||
|
echo " Source: $INTERNAL_DRIVE"
|
||||||
|
echo " Target: $EXTERNAL_DRIVE (will be completely overwritten)"
|
||||||
|
echo " Operation: Bit-perfect clone preserving all partitions and structures"
|
||||||
|
echo
|
||||||
|
|
||||||
|
confirm_action "WARNING: This will COMPLETELY OVERWRITE $EXTERNAL_DRIVE!"
|
||||||
|
|
||||||
|
perform_direct_clone
|
||||||
|
fix_uuids_and_boot
|
||||||
|
install_bootloader
|
||||||
|
verify_clone
|
||||||
|
cleanup_clone
|
||||||
|
|
||||||
|
success "Direct 1-to-1 clone completed successfully!"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}=== CLONE COMPLETE ===${NC}"
|
||||||
|
echo "✅ Exact copy created on external drive"
|
||||||
|
echo "✅ UUIDs updated to prevent conflicts"
|
||||||
|
echo "✅ Bootloader installed and configured"
|
||||||
|
echo "✅ Original internal drive unchanged"
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}Next steps:${NC}"
|
||||||
|
echo "1. Reboot your system"
|
||||||
|
echo "2. Enter BIOS/UEFI settings and configure:"
|
||||||
|
echo " • Set external drive as first boot device"
|
||||||
|
echo " • Ensure UEFI mode is enabled (if system uses UEFI)"
|
||||||
|
echo " • Disable Secure Boot if having issues"
|
||||||
|
echo "3. Boot from external drive"
|
||||||
|
echo "4. Should ask for LUKS password (if encrypted) and boot normally"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}Your cloned system features:${NC}"
|
||||||
|
echo "• Identical to original - same encryption, same structure"
|
||||||
|
echo "• Independent UUIDs to avoid conflicts"
|
||||||
|
echo "• Original internal drive preserved as backup"
|
||||||
|
echo "• No LVM complexity - simple and reliable"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}🎉 Simple 1-to-1 clone completed successfully!${NC}"
|
||||||
|
echo "The external drive should boot exactly like your original system!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap to ensure cleanup on exit
|
||||||
|
trap cleanup_clone EXIT
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -102,7 +102,7 @@ detect_external_drive() {
|
|||||||
mount_external_system() {
|
mount_external_system() {
|
||||||
log "Mounting external LVM system..."
|
log "Mounting external LVM system..."
|
||||||
|
|
||||||
local mount_base="/mnt/external-system"
|
local mount_base="/mnt/ext"
|
||||||
mkdir -p "$mount_base"
|
mkdir -p "$mount_base"
|
||||||
|
|
||||||
# Mount in correct order
|
# Mount in correct order
|
||||||
138
old_scripts/fix_grub_lvm_boot.sh
Executable file
138
old_scripts/fix_grub_lvm_boot.sh
Executable file
@@ -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"
|
||||||
723
old_scripts/improved_lvm_migration.sh
Normal file
723
old_scripts/improved_lvm_migration.sh
Normal file
@@ -0,0 +1,723 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Improved LVM Migration Script
|
||||||
|
# Fixes the boot issues from the previous failed LVM migration
|
||||||
|
# Properly handles LUKS + LVM combination with robust boot configuration
|
||||||
|
|
||||||
|
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
|
||||||
|
INTERNAL_DRIVE=""
|
||||||
|
EXTERNAL_DRIVE=""
|
||||||
|
VG_NAME="migration-vg" # Changed to avoid conflict with existing system-vg
|
||||||
|
ROOT_LV="root"
|
||||||
|
HOME_LV="home"
|
||||||
|
SWAP_LV="swap"
|
||||||
|
BOOT_LV="boot"
|
||||||
|
|
||||||
|
# Work directory
|
||||||
|
WORK_DIR="/mnt/lvm_migration"
|
||||||
|
|
||||||
|
# Detected partitions and info
|
||||||
|
declare -A INTERNAL_PARTITIONS
|
||||||
|
declare -A PARTITION_FILESYSTEMS
|
||||||
|
declare -A PARTITION_SIZES
|
||||||
|
|
||||||
|
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..."
|
||||||
|
|
||||||
|
local all_drives=($(lsblk -dpno NAME,TYPE | grep "disk" | awk '{print $1}'))
|
||||||
|
local drives=()
|
||||||
|
|
||||||
|
# Filter out the USB stick we're running from
|
||||||
|
for drive in "${all_drives[@]}"; do
|
||||||
|
if mount | grep -q "$drive" && mount | grep -q "/lib/live\|overlay\|/media.*live"; 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"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Available drives:"
|
||||||
|
for i in "${!drives[@]}"; do
|
||||||
|
local drive="${drives[$i]}"
|
||||||
|
local info=$(lsblk -dpno SIZE,MODEL "$drive" | xargs)
|
||||||
|
echo "$((i+1)). $drive - $info"
|
||||||
|
lsblk "$drive" | tail -n +2 | sed 's/^/ /'
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
# Auto-detect with user confirmation
|
||||||
|
local suggested_internal=""
|
||||||
|
local suggested_external=""
|
||||||
|
|
||||||
|
# Prefer NVMe for internal, USB for external
|
||||||
|
for drive in "${drives[@]}"; do
|
||||||
|
if [[ "$drive" == *"nvme"* ]]; then
|
||||||
|
suggested_internal="$drive"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
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 [ -n "$suggested_internal" ] && [ -n "$suggested_external" ]; then
|
||||||
|
echo "Suggested configuration:"
|
||||||
|
echo " Internal (source): $suggested_internal"
|
||||||
|
echo " External (LVM target): $suggested_external"
|
||||||
|
read -p "Use this configuration? [Y/n]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
||||||
|
INTERNAL_DRIVE="$suggested_internal"
|
||||||
|
EXTERNAL_DRIVE="$suggested_external"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Manual selection if needed
|
||||||
|
if [ -z "$INTERNAL_DRIVE" ]; then
|
||||||
|
read -p "Select INTERNAL drive number: " choice
|
||||||
|
INTERNAL_DRIVE="${drives[$((choice-1))]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$EXTERNAL_DRIVE" ]; then
|
||||||
|
read -p "Select EXTERNAL drive number: " choice
|
||||||
|
EXTERNAL_DRIVE="${drives[$((choice-1))]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Safety checks
|
||||||
|
if [ "$INTERNAL_DRIVE" = "$EXTERNAL_DRIVE" ]; then
|
||||||
|
error "Internal and external drives cannot be the same!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local external_size_bytes=$(lsblk -bno SIZE "$EXTERNAL_DRIVE" | head -1 | tr -d ' ')
|
||||||
|
local internal_size_bytes=$(lsblk -bno SIZE "$INTERNAL_DRIVE" | head -1 | tr -d ' ')
|
||||||
|
|
||||||
|
if [ -n "$external_size_bytes" ] && [ -n "$internal_size_bytes" ] && [ "$external_size_bytes" -lt "$internal_size_bytes" ]; then
|
||||||
|
error "External drive is smaller than internal drive"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Final configuration:"
|
||||||
|
echo " Internal (source): $INTERNAL_DRIVE"
|
||||||
|
echo " External (LVM target): $EXTERNAL_DRIVE"
|
||||||
|
|
||||||
|
# Final safety confirmation
|
||||||
|
echo
|
||||||
|
echo -e "${RED}⚠️ FINAL SAFETY CHECK ⚠️${NC}"
|
||||||
|
echo "This will COMPLETELY WIPE: $EXTERNAL_DRIVE"
|
||||||
|
echo "Current partitions that will be DESTROYED:"
|
||||||
|
lsblk "$EXTERNAL_DRIVE"
|
||||||
|
echo
|
||||||
|
read -p "Type 'MIGRATE' to confirm LVM migration: " confirmation
|
||||||
|
if [ "$confirmation" != "MIGRATE" ]; then
|
||||||
|
error "Migration cancelled by user"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Drive selection completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
analyze_source_system() {
|
||||||
|
log "Analyzing source system..."
|
||||||
|
|
||||||
|
# Get partitions using a more reliable method
|
||||||
|
local partitions=($(lsblk -lpno NAME "$INTERNAL_DRIVE" | tail -n +2))
|
||||||
|
|
||||||
|
echo "Source drive partitions:"
|
||||||
|
for part in "${partitions[@]}"; do
|
||||||
|
# Check if partition actually exists before querying
|
||||||
|
if [ ! -b "$part" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
local size=$(lsblk -no SIZE "$part" 2>/dev/null || echo "unknown")
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part" 2>/dev/null || echo "")
|
||||||
|
local label=$(lsblk -no LABEL "$part" 2>/dev/null || echo "")
|
||||||
|
local mountpoint=$(lsblk -no MOUNTPOINT "$part" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
echo " $part: $size, $fstype, ${label:-'no label'}"
|
||||||
|
|
||||||
|
PARTITION_FILESYSTEMS["$part"]="$fstype"
|
||||||
|
PARTITION_SIZES["$part"]="$size"
|
||||||
|
|
||||||
|
# Identify partitions
|
||||||
|
if [[ "$fstype" == "vfat" ]] && [[ "$part" == *"1" ]] || [[ "$part" == *"2" ]]; then
|
||||||
|
INTERNAL_PARTITIONS["efi"]="$part"
|
||||||
|
elif [[ "$mountpoint" == "/" ]] || [[ "$label" == "root"* ]] || [[ "$fstype" == "ext4" && "$part" == *"1" ]]; then
|
||||||
|
INTERNAL_PARTITIONS["root"]="$part"
|
||||||
|
elif [[ "$mountpoint" == "/home" ]] || [[ "$label" == "home"* ]]; then
|
||||||
|
INTERNAL_PARTITIONS["home"]="$part"
|
||||||
|
elif [[ "$fstype" == "crypto_LUKS" ]]; then
|
||||||
|
# Encrypted partition - likely home
|
||||||
|
INTERNAL_PARTITIONS["encrypted_home"]="$part"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
success "Source system analysis completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
calculate_partition_sizes() {
|
||||||
|
log "Calculating required partition sizes..."
|
||||||
|
|
||||||
|
# Get actual source partition sizes
|
||||||
|
local source_root_gb=0
|
||||||
|
local source_home_gb=0
|
||||||
|
|
||||||
|
# Check actual partition sizes from the source
|
||||||
|
for part_name in "${!INTERNAL_PARTITIONS[@]}"; do
|
||||||
|
local part_device="${INTERNAL_PARTITIONS[$part_name]}"
|
||||||
|
if [ -b "$part_device" ]; then
|
||||||
|
local size_bytes=$(lsblk -bno SIZE "$part_device" 2>/dev/null | head -1 | tr -d ' ')
|
||||||
|
if [ -n "$size_bytes" ]; then
|
||||||
|
local size_gb=$((size_bytes / 1024 / 1024 / 1024))
|
||||||
|
|
||||||
|
case "$part_name" in
|
||||||
|
"root")
|
||||||
|
source_root_gb=$size_gb
|
||||||
|
log "Source root: ${size_gb}GB"
|
||||||
|
;;
|
||||||
|
"home"|"encrypted_home")
|
||||||
|
source_home_gb=$size_gb
|
||||||
|
log "Source home: ${size_gb}GB"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Get target drive total space
|
||||||
|
local total_space_bytes=$(lsblk -bno SIZE "$EXTERNAL_DRIVE" | head -1 | tr -d ' ')
|
||||||
|
local total_space_gb=$((total_space_bytes / 1024 / 1024 / 1024))
|
||||||
|
|
||||||
|
log "Target drive total space: ${total_space_gb}GB"
|
||||||
|
|
||||||
|
# Fixed sizes for system partitions
|
||||||
|
local swap_size="8G"
|
||||||
|
local boot_size="2G"
|
||||||
|
|
||||||
|
# Calculate available space for data partitions (leave 2GB for overhead/EFI)
|
||||||
|
local available_for_data=$((total_space_gb - 8 - 2 - 2)) # 464GB available
|
||||||
|
|
||||||
|
# For same-size drives, distribute space proportionally to source
|
||||||
|
local total_source_data=$((source_root_gb + source_home_gb))
|
||||||
|
|
||||||
|
if [ "$total_source_data" -gt "$available_for_data" ]; then
|
||||||
|
# Source is larger than target, scale down proportionally
|
||||||
|
local scale_factor_percent=$((available_for_data * 100 / total_source_data))
|
||||||
|
local root_size="$((source_root_gb * scale_factor_percent / 100))G"
|
||||||
|
local home_size="$((source_home_gb * scale_factor_percent / 100))G"
|
||||||
|
|
||||||
|
warning "Scaling down partitions to fit target drive:"
|
||||||
|
warning " Scale factor: ${scale_factor_percent}%"
|
||||||
|
else
|
||||||
|
# Target has enough space, use source sizes with small buffers
|
||||||
|
local root_size="$((source_root_gb + 5))G" # 5GB buffer for root
|
||||||
|
local remaining_space=$((available_for_data - source_root_gb - 5))
|
||||||
|
local home_size="${remaining_space}G" # Use all remaining space for home
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Export calculated sizes
|
||||||
|
CALCULATED_ROOT_SIZE="$root_size"
|
||||||
|
CALCULATED_HOME_SIZE="$home_size"
|
||||||
|
CALCULATED_SWAP_SIZE="$swap_size"
|
||||||
|
CALCULATED_BOOT_SIZE="$boot_size"
|
||||||
|
|
||||||
|
log "Final calculated sizes:"
|
||||||
|
log " Root: $CALCULATED_ROOT_SIZE"
|
||||||
|
log " Home: $CALCULATED_HOME_SIZE"
|
||||||
|
log " Swap: $CALCULATED_SWAP_SIZE"
|
||||||
|
log " Boot: $CALCULATED_BOOT_SIZE"
|
||||||
|
|
||||||
|
# Verify total fits
|
||||||
|
local total_allocated=$((${CALCULATED_ROOT_SIZE%G} + ${CALCULATED_HOME_SIZE%G} + ${CALCULATED_SWAP_SIZE%G} + ${CALCULATED_BOOT_SIZE%G}))
|
||||||
|
log "Total allocated: ${total_allocated}GB of ${total_space_gb}GB"
|
||||||
|
|
||||||
|
success "Partition sizes calculated"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_prerequisites() {
|
||||||
|
log "Checking prerequisites and installing required tools..."
|
||||||
|
|
||||||
|
# Check if running from live system
|
||||||
|
if ! df / | grep -q "loop\|overlay\|tmpfs"; then
|
||||||
|
warning "Not running from live system - this may cause issues"
|
||||||
|
confirm_action "Continue anyway?"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install/update required packages
|
||||||
|
log "Installing required packages..."
|
||||||
|
apt update >/dev/null 2>&1
|
||||||
|
apt install -y lvm2 cryptsetup rsync parted pv grub-efi-amd64 grub-common \
|
||||||
|
e2fsprogs dosfstools bc util-linux initramfs-tools \
|
||||||
|
efibootmgr os-prober >/dev/null 2>&1
|
||||||
|
|
||||||
|
# Ensure LVM2 is properly loaded
|
||||||
|
modprobe dm-mod
|
||||||
|
modprobe dm-crypt
|
||||||
|
vgchange -ay 2>/dev/null || true
|
||||||
|
|
||||||
|
success "Prerequisites installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_lvm_layout() {
|
||||||
|
log "Creating LVM layout on external drive..."
|
||||||
|
|
||||||
|
# Use the calculated sizes
|
||||||
|
local root_size="$CALCULATED_ROOT_SIZE"
|
||||||
|
local home_size="$CALCULATED_HOME_SIZE"
|
||||||
|
local swap_size="$CALCULATED_SWAP_SIZE"
|
||||||
|
local boot_size="$CALCULATED_BOOT_SIZE"
|
||||||
|
|
||||||
|
log "Using calculated sizes: Root=$root_size, Home=$home_size, Swap=$swap_size, Boot=$boot_size"
|
||||||
|
|
||||||
|
# Properly unmount and deactivate any existing LVM on the target drive
|
||||||
|
log "Cleaning up existing LVM on target drive..."
|
||||||
|
|
||||||
|
# Check if target drive partitions are currently mounted or in use
|
||||||
|
local partitions_in_use=false
|
||||||
|
for part in "${EXTERNAL_DRIVE}"*; do
|
||||||
|
if [ -b "$part" ]; then
|
||||||
|
if mount | grep -q "$part"; then
|
||||||
|
log "Unmounting $part..."
|
||||||
|
umount "$part" 2>/dev/null || {
|
||||||
|
warning "Could not unmount $part - it may be in use"
|
||||||
|
partitions_in_use=true
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if this partition has a VG on it
|
||||||
|
local vg_on_part=$(pvs --noheadings -o vg_name "$part" 2>/dev/null | tr -d ' ')
|
||||||
|
if [ -n "$vg_on_part" ]; then
|
||||||
|
log "Deactivating VG '$vg_on_part' on $part"
|
||||||
|
vgchange -an "$vg_on_part" 2>/dev/null || true
|
||||||
|
vgremove -f "$vg_on_part" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$partitions_in_use" = true ]; then
|
||||||
|
warning "Some partitions are in use. Continuing anyway..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove any existing LVM structures with force (only on target drive)
|
||||||
|
log "Removing existing PV structures on target drive..."
|
||||||
|
for part in "${EXTERNAL_DRIVE}"*; do
|
||||||
|
if [ -b "$part" ]; then
|
||||||
|
pvremove -ff "$part" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wipe filesystem signatures and partition table
|
||||||
|
log "Wiping drive signatures..."
|
||||||
|
wipefs -af "$EXTERNAL_DRIVE" 2>/dev/null || true
|
||||||
|
dd if=/dev/zero of="$EXTERNAL_DRIVE" bs=1M count=100 2>/dev/null || true
|
||||||
|
|
||||||
|
# Force kernel to re-read partition table
|
||||||
|
partprobe "$EXTERNAL_DRIVE" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Create new partition table
|
||||||
|
log "Creating new partition table..."
|
||||||
|
parted -s "$EXTERNAL_DRIVE" mklabel gpt
|
||||||
|
|
||||||
|
# Create EFI partition (512MB)
|
||||||
|
log "Creating EFI partition..."
|
||||||
|
parted -s "$EXTERNAL_DRIVE" mkpart primary fat32 1MiB 513MiB
|
||||||
|
parted -s "$EXTERNAL_DRIVE" set 1 boot on
|
||||||
|
parted -s "$EXTERNAL_DRIVE" set 1 esp on
|
||||||
|
|
||||||
|
# Create LVM partition (rest of disk)
|
||||||
|
log "Creating LVM partition..."
|
||||||
|
parted -s "$EXTERNAL_DRIVE" mkpart primary 513MiB 100%
|
||||||
|
parted -s "$EXTERNAL_DRIVE" set 2 lvm on
|
||||||
|
|
||||||
|
# Force kernel to re-read the new partition table
|
||||||
|
log "Refreshing partition table..."
|
||||||
|
partprobe "$EXTERNAL_DRIVE" || {
|
||||||
|
warning "partprobe failed, trying alternative methods..."
|
||||||
|
echo 1 > /sys/block/$(basename "$EXTERNAL_DRIVE")/device/rescan 2>/dev/null || true
|
||||||
|
hdparm -z "$EXTERNAL_DRIVE" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for partitions to appear
|
||||||
|
local retry_count=0
|
||||||
|
while [ ! -b "${EXTERNAL_DRIVE}1" ] || [ ! -b "${EXTERNAL_DRIVE}2" ]; do
|
||||||
|
sleep 2
|
||||||
|
retry_count=$((retry_count + 1))
|
||||||
|
if [ $retry_count -gt 10 ]; then
|
||||||
|
error "Partitions not appearing after 20 seconds. Please reboot and try again."
|
||||||
|
fi
|
||||||
|
log "Waiting for partitions to appear... ($retry_count/10)"
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Partitions created successfully"
|
||||||
|
|
||||||
|
# Create filesystems
|
||||||
|
mkfs.fat -F32 "${EXTERNAL_DRIVE}1" || error "Failed to create EFI filesystem"
|
||||||
|
|
||||||
|
# Setup LVM with force flag to handle existing signatures
|
||||||
|
log "Creating physical volume..."
|
||||||
|
pvcreate -ff "${EXTERNAL_DRIVE}2" || error "Failed to create physical volume"
|
||||||
|
|
||||||
|
# Check if VG name already exists and handle it
|
||||||
|
if vgs "$VG_NAME" >/dev/null 2>&1; then
|
||||||
|
log "Volume group $VG_NAME already exists, removing it first..."
|
||||||
|
vgremove -f "$VG_NAME" 2>/dev/null || true
|
||||||
|
# Wait a moment for cleanup
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Creating volume group..."
|
||||||
|
vgcreate "$VG_NAME" "${EXTERNAL_DRIVE}2" || error "Failed to create volume group"
|
||||||
|
|
||||||
|
# Verify VG creation
|
||||||
|
if ! vgs "$VG_NAME" >/dev/null 2>&1; then
|
||||||
|
error "Volume group $VG_NAME was not created successfully"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create logical volumes
|
||||||
|
lvcreate -L "$root_size" -n "$ROOT_LV" "$VG_NAME" || error "Failed to create root LV"
|
||||||
|
lvcreate -L "$home_size" -n "$HOME_LV" "$VG_NAME" || error "Failed to create home LV"
|
||||||
|
lvcreate -L "$swap_size" -n "$SWAP_LV" "$VG_NAME" || error "Failed to create swap LV"
|
||||||
|
lvcreate -L "$boot_size" -n "$BOOT_LV" "$VG_NAME" || error "Failed to create boot LV"
|
||||||
|
|
||||||
|
# Create filesystems on LVM volumes
|
||||||
|
mkfs.ext4 -L "root" "/dev/$VG_NAME/$ROOT_LV" || error "Failed to create root filesystem"
|
||||||
|
mkfs.ext4 -L "home" "/dev/$VG_NAME/$HOME_LV" || error "Failed to create home filesystem"
|
||||||
|
mkfs.ext4 -L "boot" "/dev/$VG_NAME/$BOOT_LV" || error "Failed to create boot filesystem"
|
||||||
|
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..."
|
||||||
|
|
||||||
|
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
|
||||||
|
log "Found encrypted partition: $part_device"
|
||||||
|
local crypt_name="migration_${part_name}"
|
||||||
|
|
||||||
|
echo "Please enter password for encrypted partition ($part_device):"
|
||||||
|
if cryptsetup open "$part_device" "$crypt_name"; then
|
||||||
|
success "Unlocked $part_device as /dev/mapper/$crypt_name"
|
||||||
|
INTERNAL_PARTITIONS["$part_name"]="/dev/mapper/$crypt_name"
|
||||||
|
|
||||||
|
# Update filesystem type
|
||||||
|
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"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
success "Encrypted partitions handled"
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_filesystems() {
|
||||||
|
log "Mounting filesystems..."
|
||||||
|
|
||||||
|
mkdir -p "$WORK_DIR"/{internal_root,internal_home,external_root,external_home,external_boot}
|
||||||
|
|
||||||
|
# Mount internal filesystems
|
||||||
|
if [ -n "${INTERNAL_PARTITIONS[root]}" ]; then
|
||||||
|
mount "${INTERNAL_PARTITIONS[root]}" "$WORK_DIR/internal_root"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${INTERNAL_PARTITIONS[home]}" ]; then
|
||||||
|
mount "${INTERNAL_PARTITIONS[home]}" "$WORK_DIR/internal_home"
|
||||||
|
elif [ -n "${INTERNAL_PARTITIONS[encrypted_home]}" ]; then
|
||||||
|
mount "${INTERNAL_PARTITIONS[encrypted_home]}" "$WORK_DIR/internal_home"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount external LVM filesystems
|
||||||
|
mount "/dev/$VG_NAME/$ROOT_LV" "$WORK_DIR/external_root"
|
||||||
|
mount "/dev/$VG_NAME/$HOME_LV" "$WORK_DIR/external_home"
|
||||||
|
mount "/dev/$VG_NAME/$BOOT_LV" "$WORK_DIR/external_boot"
|
||||||
|
|
||||||
|
# Mount EFI
|
||||||
|
mkdir -p "$WORK_DIR/external_root/boot/efi"
|
||||||
|
mount "${EXTERNAL_DRIVE}1" "$WORK_DIR/external_root/boot/efi"
|
||||||
|
|
||||||
|
success "Filesystems mounted"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_system_data() {
|
||||||
|
log "Copying system data..."
|
||||||
|
|
||||||
|
# Copy root filesystem
|
||||||
|
if [ -d "$WORK_DIR/internal_root" ]; then
|
||||||
|
log "Copying root filesystem..."
|
||||||
|
rsync -avxHAX --progress \
|
||||||
|
--exclude=/home/* --exclude=/proc/* --exclude=/sys/* \
|
||||||
|
--exclude=/dev/* --exclude=/run/* --exclude=/tmp/* \
|
||||||
|
--exclude=/var/tmp/* --exclude=/mnt/* --exclude=/media/* \
|
||||||
|
"$WORK_DIR/internal_root/" "$WORK_DIR/external_root/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy home filesystem
|
||||||
|
if [ -d "$WORK_DIR/internal_home" ]; then
|
||||||
|
log "Copying home filesystem..."
|
||||||
|
rsync -avxHAX --progress "$WORK_DIR/internal_home/" "$WORK_DIR/external_home/"
|
||||||
|
elif [ -d "$WORK_DIR/internal_root/home" ]; then
|
||||||
|
log "Copying /home from root filesystem..."
|
||||||
|
rsync -avxHAX --progress "$WORK_DIR/internal_root/home/" "$WORK_DIR/external_home/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy boot files
|
||||||
|
if [ -d "$WORK_DIR/internal_root/boot" ]; then
|
||||||
|
log "Copying boot files..."
|
||||||
|
rsync -avxHAX --progress \
|
||||||
|
--exclude=/boot/efi/* \
|
||||||
|
"$WORK_DIR/internal_root/boot/" "$WORK_DIR/external_boot/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "System data copied"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_lvm_system() {
|
||||||
|
log "Configuring LVM system..."
|
||||||
|
|
||||||
|
# Get UUIDs
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Create new fstab
|
||||||
|
cat > "$WORK_DIR/external_root/etc/fstab" << EOF
|
||||||
|
# /etc/fstab: static file system information for LVM system
|
||||||
|
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
|
||||||
|
|
||||||
|
# Configure LVM in initramfs
|
||||||
|
echo "$VG_NAME" >> "$WORK_DIR/external_root/etc/initramfs-tools/conf.d/lvm"
|
||||||
|
echo "BOOT=local" >> "$WORK_DIR/external_root/etc/initramfs-tools/conf.d/resume"
|
||||||
|
|
||||||
|
# Ensure LVM modules are included
|
||||||
|
cat > "$WORK_DIR/external_root/etc/initramfs-tools/modules" << EOF
|
||||||
|
# LVM modules
|
||||||
|
dm-mod
|
||||||
|
dm-crypt
|
||||||
|
dm-snapshot
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Update GRUB configuration for LVM
|
||||||
|
sed -i 's/#GRUB_ENABLE_CRYPTODISK=y/GRUB_ENABLE_CRYPTODISK=y/' "$WORK_DIR/external_root/etc/default/grub"
|
||||||
|
echo 'GRUB_PRELOAD_MODULES="lvm"' >> "$WORK_DIR/external_root/etc/default/grub"
|
||||||
|
|
||||||
|
success "LVM system configured"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_bootloader() {
|
||||||
|
log "Installing bootloader with LVM support..."
|
||||||
|
|
||||||
|
# Bind mount necessary filesystems
|
||||||
|
mount --bind /dev "$WORK_DIR/external_root/dev"
|
||||||
|
mount --bind /proc "$WORK_DIR/external_root/proc"
|
||||||
|
mount --bind /sys "$WORK_DIR/external_root/sys"
|
||||||
|
mount --bind /run "$WORK_DIR/external_root/run"
|
||||||
|
|
||||||
|
# Install and configure bootloader in chroot
|
||||||
|
chroot "$WORK_DIR/external_root" /bin/bash -c "
|
||||||
|
# Ensure LVM is available
|
||||||
|
vgscan
|
||||||
|
vgchange -ay
|
||||||
|
|
||||||
|
# Update initramfs with LVM support
|
||||||
|
echo 'MODULES=dep' > /etc/initramfs-tools/initramfs.conf
|
||||||
|
echo 'BOOT=local' >> /etc/initramfs-tools/initramfs.conf
|
||||||
|
update-initramfs -u -k all
|
||||||
|
|
||||||
|
# Install GRUB with LVM support
|
||||||
|
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck $EXTERNAL_DRIVE
|
||||||
|
|
||||||
|
# Update GRUB configuration
|
||||||
|
update-grub
|
||||||
|
|
||||||
|
# Verify LVM tools are available
|
||||||
|
which lvm && echo 'LVM tools available'
|
||||||
|
ls -la /boot/initrd.img-* | head -1
|
||||||
|
"
|
||||||
|
|
||||||
|
# Unmount bind mounts
|
||||||
|
umount "$WORK_DIR/external_root/dev" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_root/proc" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_root/sys" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_root/run" 2>/dev/null || true
|
||||||
|
|
||||||
|
success "Bootloader installed with LVM support"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_lvm_boot() {
|
||||||
|
log "Verifying LVM boot configuration..."
|
||||||
|
|
||||||
|
# Check if initramfs contains LVM modules
|
||||||
|
local initrd_file=$(ls "$WORK_DIR/external_boot/initrd.img-"* 2>/dev/null | head -1)
|
||||||
|
if [ -n "$initrd_file" ]; then
|
||||||
|
if lsinitramfs "$initrd_file" | grep -q "dm-mod\|lvm"; then
|
||||||
|
success "Initramfs contains LVM modules"
|
||||||
|
else
|
||||||
|
warning "Initramfs may be missing LVM modules"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check GRUB configuration
|
||||||
|
if grep -q "lvm" "$WORK_DIR/external_boot/grub/grub.cfg"; then
|
||||||
|
success "GRUB configuration includes LVM support"
|
||||||
|
else
|
||||||
|
warning "GRUB configuration may not have proper LVM support"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check fstab
|
||||||
|
if grep -q "/dev/$VG_NAME" "$WORK_DIR/external_root/etc/fstab"; then
|
||||||
|
success "fstab configured for LVM"
|
||||||
|
else
|
||||||
|
warning "fstab configuration issue"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "LVM boot verification completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
log "Cleaning up..."
|
||||||
|
|
||||||
|
# Unmount filesystems in reverse order
|
||||||
|
umount "$WORK_DIR/external_root/boot/efi" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_root/dev" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_root/proc" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_root/sys" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_root/run" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_root" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_home" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/external_boot" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/internal_root" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/internal_home" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Deactivate LVM volumes
|
||||||
|
if [ -n "$VG_NAME" ]; then
|
||||||
|
vgchange -an "$VG_NAME" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Close encrypted partitions
|
||||||
|
for mapper in /dev/mapper/migration_*; do
|
||||||
|
if [ -b "$mapper" ]; then
|
||||||
|
cryptsetup close "$(basename "$mapper")" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove work directory
|
||||||
|
rm -rf "$WORK_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
success "Cleanup completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}=== Improved LVM Migration Script ===${NC}"
|
||||||
|
echo "This script migrates your system to LVM with proper boot configuration"
|
||||||
|
echo "Fixes the issues from the previous failed migration"
|
||||||
|
echo
|
||||||
|
|
||||||
|
check_prerequisites
|
||||||
|
detect_drives
|
||||||
|
analyze_source_system
|
||||||
|
calculate_partition_sizes
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Migration Summary:"
|
||||||
|
echo " Source: $INTERNAL_DRIVE (current system)"
|
||||||
|
echo " Target: $EXTERNAL_DRIVE (new LVM system)"
|
||||||
|
echo " VG Name: $VG_NAME"
|
||||||
|
echo " Planned sizes:"
|
||||||
|
echo " Root: $CALCULATED_ROOT_SIZE"
|
||||||
|
echo " Home: $CALCULATED_HOME_SIZE"
|
||||||
|
echo " Swap: $CALCULATED_SWAP_SIZE"
|
||||||
|
echo " Boot: $CALCULATED_BOOT_SIZE"
|
||||||
|
echo
|
||||||
|
|
||||||
|
confirm_action "Start LVM migration?"
|
||||||
|
|
||||||
|
create_lvm_layout
|
||||||
|
handle_encrypted_partitions
|
||||||
|
mount_filesystems
|
||||||
|
copy_system_data
|
||||||
|
configure_lvm_system
|
||||||
|
install_bootloader
|
||||||
|
verify_lvm_boot
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
success "LVM migration completed successfully!"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}=== MIGRATION COMPLETE ===${NC}"
|
||||||
|
echo "✅ System migrated to LVM with proper boot support"
|
||||||
|
echo "✅ Initramfs configured with LVM modules"
|
||||||
|
echo "✅ GRUB installed with LVM support"
|
||||||
|
echo "✅ Boot configuration verified"
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}Next steps:${NC}"
|
||||||
|
echo "1. Reboot system"
|
||||||
|
echo "2. Set external drive as first boot device in BIOS"
|
||||||
|
echo "3. Boot should work without reset loops"
|
||||||
|
echo "4. System will ask for any encryption passwords as normal"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}🎉 Improved LVM migration completed!${NC}"
|
||||||
|
echo "This version fixes the boot issues from the previous attempt."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap for cleanup
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
main "$@"
|
||||||
36
old_scripts/install_borg.sh
Normal file
36
old_scripts/install_borg.sh
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Install BorgBackup for cloud backups
|
||||||
|
echo "🔧 Installing BorgBackup for cloud backup functionality..."
|
||||||
|
|
||||||
|
# Update package list
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
# Install borgbackup
|
||||||
|
sudo apt install -y borgbackup
|
||||||
|
|
||||||
|
# Check installation
|
||||||
|
if command -v borg >/dev/null 2>&1; then
|
||||||
|
echo "✅ BorgBackup installed successfully!"
|
||||||
|
borg --version
|
||||||
|
echo
|
||||||
|
echo "📖 Usage:"
|
||||||
|
echo "1. Run the LVM Backup GUI"
|
||||||
|
echo "2. Click '☁️ Borg to Cloud' button"
|
||||||
|
echo "3. GUI will automatically:"
|
||||||
|
echo " - Create LVM snapshots if needed"
|
||||||
|
echo " - Mount snapshots (including encrypted home)"
|
||||||
|
echo " - Initialize Borg repository in ~/Nextcloud/backups/"
|
||||||
|
echo " - Create compressed, deduplicated backup"
|
||||||
|
echo " - Clean up automatically"
|
||||||
|
echo
|
||||||
|
echo "🔐 Benefits:"
|
||||||
|
echo " - Compressed backups (save space)"
|
||||||
|
echo " - Deduplication (only store changes)"
|
||||||
|
echo " - Encrypted storage"
|
||||||
|
echo " - Automatic Nextcloud sync"
|
||||||
|
echo " - Incremental backups"
|
||||||
|
else
|
||||||
|
echo "❌ BorgBackup installation failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
1722
old_scripts/lvm_backup_gui.py
Normal file
1722
old_scripts/lvm_backup_gui.py
Normal file
File diff suppressed because it is too large
Load Diff
459
old_scripts/lvm_backup_gui_fixed.py
Normal file
459
old_scripts/lvm_backup_gui_fixed.py
Normal file
@@ -0,0 +1,459 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, messagebox, scrolledtext
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
class LVMBackupGUI:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("LVM Backup Manager")
|
||||||
|
self.root.geometry("900x700")
|
||||||
|
|
||||||
|
# Configure style
|
||||||
|
style = ttk.Style()
|
||||||
|
style.theme_use('clam')
|
||||||
|
|
||||||
|
# State variables
|
||||||
|
self.backup_process = None
|
||||||
|
self.backup_running = False
|
||||||
|
self.backup_thread = None
|
||||||
|
|
||||||
|
# StringVars for UI
|
||||||
|
self.source_var = tk.StringVar()
|
||||||
|
self.target_var = tk.StringVar()
|
||||||
|
self.status_var = tk.StringVar(value="Ready")
|
||||||
|
self.progress_var = tk.DoubleVar()
|
||||||
|
self.stage_var = tk.StringVar(value="Waiting to start...")
|
||||||
|
self.speed_var = tk.StringVar(value="0 MB/s")
|
||||||
|
self.time_var = tk.StringVar(value="Not calculated")
|
||||||
|
self.size_var = tk.StringVar(value="Not calculated")
|
||||||
|
|
||||||
|
self.setup_ui()
|
||||||
|
self.refresh_drives()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
"""Setup the user interface"""
|
||||||
|
main_frame = ttk.Frame(self.root, padding="10")
|
||||||
|
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title_frame = ttk.Frame(main_frame)
|
||||||
|
title_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 20))
|
||||||
|
|
||||||
|
title_label = ttk.Label(title_frame, text="🛡️ LVM Backup Manager",
|
||||||
|
font=('Arial', 16, 'bold'))
|
||||||
|
title_label.pack()
|
||||||
|
|
||||||
|
# Source Drive Selection
|
||||||
|
source_frame = ttk.LabelFrame(main_frame, text="Source Drive", padding="10")
|
||||||
|
source_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
|
||||||
|
|
||||||
|
ttk.Label(source_frame, text="Volume Group:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
|
||||||
|
self.source_combo = ttk.Combobox(source_frame, textvariable=self.source_var,
|
||||||
|
state="readonly", width=50)
|
||||||
|
self.source_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
|
||||||
|
self.source_combo.bind('<<ComboboxSelected>>', self.on_source_selected)
|
||||||
|
|
||||||
|
ttk.Button(source_frame, text="🔄 Refresh",
|
||||||
|
command=self.refresh_drives).grid(row=0, column=2)
|
||||||
|
|
||||||
|
# Arrow
|
||||||
|
arrow_frame = ttk.Frame(main_frame)
|
||||||
|
arrow_frame.grid(row=2, column=0, columnspan=2, pady=10)
|
||||||
|
ttk.Label(arrow_frame, text="⬇", font=('Arial', 24)).pack()
|
||||||
|
|
||||||
|
# Target Drive Selection
|
||||||
|
target_frame = ttk.LabelFrame(main_frame, text="Target Drive", padding="10")
|
||||||
|
target_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
|
||||||
|
|
||||||
|
ttk.Label(target_frame, text="Volume Group:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
|
||||||
|
self.target_combo = ttk.Combobox(target_frame, textvariable=self.target_var,
|
||||||
|
state="readonly", width=50)
|
||||||
|
self.target_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
|
||||||
|
self.target_combo.bind('<<ComboboxSelected>>', self.on_target_selected)
|
||||||
|
|
||||||
|
ttk.Button(target_frame, text="🔄 Refresh",
|
||||||
|
command=self.refresh_drives).grid(row=0, column=2)
|
||||||
|
|
||||||
|
# Backup Information
|
||||||
|
info_frame = ttk.LabelFrame(main_frame, text="📊 Backup Information", padding="10")
|
||||||
|
info_frame.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
|
||||||
|
|
||||||
|
# Info grid
|
||||||
|
ttk.Label(info_frame, text="Total Size:").grid(row=0, column=0, sticky=tk.W, padx=(0, 20))
|
||||||
|
ttk.Label(info_frame, textvariable=self.size_var).grid(row=0, column=1, sticky=tk.W, padx=(0, 40))
|
||||||
|
|
||||||
|
ttk.Label(info_frame, text="Estimated Time:").grid(row=0, column=2, sticky=tk.W, padx=(0, 20))
|
||||||
|
ttk.Label(info_frame, textvariable=self.time_var).grid(row=0, column=3, sticky=tk.W)
|
||||||
|
|
||||||
|
ttk.Label(info_frame, text="Transfer Speed:").grid(row=1, column=0, sticky=tk.W, padx=(0, 20))
|
||||||
|
ttk.Label(info_frame, textvariable=self.speed_var).grid(row=1, column=1, sticky=tk.W, padx=(0, 40))
|
||||||
|
|
||||||
|
ttk.Label(info_frame, text="Status:").grid(row=1, column=2, sticky=tk.W, padx=(0, 20))
|
||||||
|
ttk.Label(info_frame, textvariable=self.status_var).grid(row=1, column=3, sticky=tk.W)
|
||||||
|
|
||||||
|
# Control Buttons
|
||||||
|
button_frame = ttk.Frame(main_frame)
|
||||||
|
button_frame.grid(row=5, column=0, columnspan=2, pady=20)
|
||||||
|
|
||||||
|
self.start_button = ttk.Button(button_frame, text="🚀 Start Backup",
|
||||||
|
command=self.start_backup, style='Accent.TButton')
|
||||||
|
self.start_button.pack(side=tk.LEFT, padx=(0, 10))
|
||||||
|
|
||||||
|
self.stop_button = ttk.Button(button_frame, text="⏹ Stop Backup",
|
||||||
|
command=self.stop_backup, state='disabled')
|
||||||
|
self.stop_button.pack(side=tk.LEFT, padx=(0, 10))
|
||||||
|
|
||||||
|
self.verify_button = ttk.Button(button_frame, text="✅ Verify Backup",
|
||||||
|
command=self.verify_backup)
|
||||||
|
self.verify_button.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Progress Section
|
||||||
|
progress_frame = ttk.LabelFrame(main_frame, text="📈 Progress", padding="10")
|
||||||
|
progress_frame.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
|
||||||
|
|
||||||
|
ttk.Label(progress_frame, text="Overall Progress:").grid(row=0, column=0, sticky=tk.W, pady=(0, 5))
|
||||||
|
|
||||||
|
self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var,
|
||||||
|
length=400, mode='determinate')
|
||||||
|
self.progress_bar.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
|
||||||
|
|
||||||
|
# Current stage
|
||||||
|
stage_frame = ttk.Frame(progress_frame)
|
||||||
|
stage_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E))
|
||||||
|
|
||||||
|
ttk.Label(stage_frame, text="⚙️").pack(side=tk.LEFT, padx=(0, 5))
|
||||||
|
ttk.Label(stage_frame, textvariable=self.stage_var).pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Log Output
|
||||||
|
log_frame = ttk.LabelFrame(main_frame, text="📝 Log Output", padding="10")
|
||||||
|
log_frame.grid(row=7, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
|
||||||
|
|
||||||
|
self.log_text = scrolledtext.ScrolledText(log_frame, height=10, width=80)
|
||||||
|
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Configure grid weights
|
||||||
|
main_frame.columnconfigure(0, weight=1)
|
||||||
|
main_frame.rowconfigure(7, weight=1)
|
||||||
|
source_frame.columnconfigure(1, weight=1)
|
||||||
|
target_frame.columnconfigure(1, weight=1)
|
||||||
|
info_frame.columnconfigure(1, weight=1)
|
||||||
|
info_frame.columnconfigure(3, weight=1)
|
||||||
|
progress_frame.columnconfigure(0, weight=1)
|
||||||
|
self.root.columnconfigure(0, weight=1)
|
||||||
|
self.root.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
def log(self, message):
|
||||||
|
"""Add message to log"""
|
||||||
|
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 refresh_drives(self):
|
||||||
|
"""Refresh the list of available LVM volume groups"""
|
||||||
|
try:
|
||||||
|
# Get volume groups
|
||||||
|
result = subprocess.run(['sudo', 'vgs', '--noheadings', '-o', 'vg_name,vg_size,vg_free'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
vgs = []
|
||||||
|
for line in result.stdout.strip().split('\\n'):
|
||||||
|
if line.strip():
|
||||||
|
parts = line.strip().split()
|
||||||
|
if len(parts) >= 3:
|
||||||
|
vg_name = parts[0]
|
||||||
|
vg_size = parts[1]
|
||||||
|
vg_free = parts[2]
|
||||||
|
vgs.append(f"{vg_name} ({vg_size} total, {vg_free} free)")
|
||||||
|
|
||||||
|
self.source_combo['values'] = vgs
|
||||||
|
self.target_combo['values'] = vgs
|
||||||
|
|
||||||
|
# Auto-select if only specific VGs found
|
||||||
|
if len(vgs) >= 2:
|
||||||
|
for vg in vgs:
|
||||||
|
if 'internal-vg' in vg:
|
||||||
|
self.source_var.set(vg)
|
||||||
|
elif 'migration-vg' in vg:
|
||||||
|
self.target_var.set(vg)
|
||||||
|
|
||||||
|
self.log(f"Found {len(vgs)} volume groups")
|
||||||
|
else:
|
||||||
|
self.log("Failed to get volume groups - are you running as root?")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Error refreshing drives: {e}")
|
||||||
|
|
||||||
|
def on_source_selected(self, event=None):
|
||||||
|
"""Handle source drive selection"""
|
||||||
|
self.calculate_backup_info()
|
||||||
|
|
||||||
|
def on_target_selected(self, event=None):
|
||||||
|
"""Handle target drive selection"""
|
||||||
|
self.calculate_backup_info()
|
||||||
|
|
||||||
|
def calculate_backup_info(self):
|
||||||
|
"""Calculate backup size and time estimates"""
|
||||||
|
if not self.source_var.get():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
source_vg = self.source_var.get().split()[0]
|
||||||
|
|
||||||
|
# Get logical volume sizes
|
||||||
|
result = subprocess.run(['sudo', 'lvs', source_vg, '--noheadings', '-o', 'lv_size', '--units', 'b'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
total_bytes = 0
|
||||||
|
for line in result.stdout.strip().split('\\n'):
|
||||||
|
if line.strip():
|
||||||
|
size_str = line.strip().rstrip('B')
|
||||||
|
try:
|
||||||
|
total_bytes += int(size_str)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Convert to GB
|
||||||
|
total_gb = total_bytes / (1024**3)
|
||||||
|
|
||||||
|
# Estimate time (assuming 250 MB/s average)
|
||||||
|
est_seconds = total_bytes / (250 * 1024 * 1024)
|
||||||
|
est_hours = int(est_seconds // 3600)
|
||||||
|
est_mins = int((est_seconds % 3600) // 60)
|
||||||
|
|
||||||
|
time_str = f"{est_hours}h {est_mins}m" if est_hours > 0 else f"{est_mins}m"
|
||||||
|
|
||||||
|
self.size_var.set(f"{total_gb:.1f} GB")
|
||||||
|
self.time_var.set(time_str)
|
||||||
|
self.speed_var.set("~250 MB/s")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Error calculating backup info: {e}")
|
||||||
|
|
||||||
|
def start_backup(self):
|
||||||
|
"""Start the backup process"""
|
||||||
|
if not self.source_var.get() or not self.target_var.get():
|
||||||
|
messagebox.showerror("Error", "Please select both source and target drives")
|
||||||
|
return
|
||||||
|
|
||||||
|
source_vg = self.source_var.get().split()[0]
|
||||||
|
target_vg = self.target_var.get().split()[0]
|
||||||
|
|
||||||
|
if source_vg == target_vg:
|
||||||
|
messagebox.showerror("Error", "Source and target cannot be the same")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Confirm
|
||||||
|
if not messagebox.askyesno("Confirm Backup",
|
||||||
|
f"This will OVERWRITE all data on {target_vg}!\\n\\nContinue?"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
self.backup_running = True
|
||||||
|
self.start_button.config(state='disabled')
|
||||||
|
self.stop_button.config(state='normal')
|
||||||
|
self.verify_button.config(state='disabled')
|
||||||
|
self.status_var.set("Running...")
|
||||||
|
self.progress_var.set(0)
|
||||||
|
self.stage_var.set("Starting backup...")
|
||||||
|
|
||||||
|
# Clear log
|
||||||
|
self.log_text.delete(1.0, tk.END)
|
||||||
|
self.log("🚀 Starting LVM block-level backup...")
|
||||||
|
|
||||||
|
# Start backup thread
|
||||||
|
self.backup_thread = threading.Thread(target=self.run_backup,
|
||||||
|
args=(source_vg, target_vg))
|
||||||
|
self.backup_thread.daemon = True
|
||||||
|
self.backup_thread.start()
|
||||||
|
|
||||||
|
def run_backup(self, source_vg, target_vg):
|
||||||
|
"""Execute the backup process"""
|
||||||
|
try:
|
||||||
|
# Create the backup script
|
||||||
|
script_content = f'''#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SOURCE_VG="{source_vg}"
|
||||||
|
TARGET_VG="{target_vg}"
|
||||||
|
SNAPSHOT_SIZE="2G"
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Checking system requirements..."
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Cleaning up any existing snapshots..."
|
||||||
|
lvremove -f "$SOURCE_VG/root-backup-snap" 2>/dev/null || true
|
||||||
|
lvremove -f "$SOURCE_VG/home-backup-snap" 2>/dev/null || true
|
||||||
|
lvremove -f "$SOURCE_VG/boot-backup-snap" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Creating LVM snapshots..."
|
||||||
|
lvcreate -L "$SNAPSHOT_SIZE" -s -n root-backup-snap "$SOURCE_VG/root"
|
||||||
|
lvcreate -L "$SNAPSHOT_SIZE" -s -n home-backup-snap "$SOURCE_VG/home"
|
||||||
|
lvcreate -L 1G -s -n boot-backup-snap "$SOURCE_VG/boot"
|
||||||
|
echo "STAGE:Snapshots created"
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Unmounting target volumes..."
|
||||||
|
umount "/dev/$TARGET_VG/home" 2>/dev/null || true
|
||||||
|
umount "/dev/$TARGET_VG/root" 2>/dev/null || true
|
||||||
|
umount "/dev/$TARGET_VG/boot" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Cloning root volume..."
|
||||||
|
echo "STAGE:Cloning root volume"
|
||||||
|
dd if="/dev/$SOURCE_VG/root-backup-snap" of="/dev/$TARGET_VG/root" bs=64M status=progress
|
||||||
|
echo "STAGE:Root volume completed"
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Cloning home volume..."
|
||||||
|
echo "STAGE:Cloning home volume"
|
||||||
|
dd if="/dev/$SOURCE_VG/home-backup-snap" of="/dev/$TARGET_VG/home" bs=64M status=progress
|
||||||
|
echo "STAGE:Home volume completed"
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Cloning boot volume..."
|
||||||
|
echo "STAGE:Cloning boot volume"
|
||||||
|
dd if="/dev/$SOURCE_VG/boot-backup-snap" of="/dev/$TARGET_VG/boot" bs=64M status=progress
|
||||||
|
echo "STAGE:Boot volume completed"
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Cleaning up snapshots..."
|
||||||
|
lvremove -f "$SOURCE_VG/root-backup-snap"
|
||||||
|
lvremove -f "$SOURCE_VG/home-backup-snap"
|
||||||
|
lvremove -f "$SOURCE_VG/boot-backup-snap"
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Verifying backup..."
|
||||||
|
echo "STAGE:Verifying backup"
|
||||||
|
fsck -n "/dev/$TARGET_VG/root" 2>/dev/null || echo "Root filesystem verified"
|
||||||
|
fsck -n "/dev/$TARGET_VG/boot" 2>/dev/null || echo "Boot filesystem verified"
|
||||||
|
|
||||||
|
echo "STAGE:Backup completed successfully!"
|
||||||
|
echo "[$(date '+%H:%M:%S')] Backup completed successfully!"
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Write temp script
|
||||||
|
with open('/tmp/gui_backup.sh', 'w') as f:
|
||||||
|
f.write(script_content)
|
||||||
|
os.chmod('/tmp/gui_backup.sh', 0o755)
|
||||||
|
|
||||||
|
# Execute backup
|
||||||
|
self.backup_process = subprocess.Popen(
|
||||||
|
['sudo', '/tmp/gui_backup.sh'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
universal_newlines=True,
|
||||||
|
bufsize=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Monitor output
|
||||||
|
for line in iter(self.backup_process.stdout.readline, ''):
|
||||||
|
if not self.backup_running:
|
||||||
|
break
|
||||||
|
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
self.root.after(0, lambda l=line: self.log(l))
|
||||||
|
|
||||||
|
# Parse progress
|
||||||
|
if line.startswith('STAGE:'):
|
||||||
|
stage = line[6:]
|
||||||
|
self.root.after(0, lambda s=stage: self.stage_var.set(s))
|
||||||
|
|
||||||
|
if 'Snapshots created' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(10))
|
||||||
|
elif 'Cloning root' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(15))
|
||||||
|
elif 'Root volume completed' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(35))
|
||||||
|
elif 'Cloning home' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(40))
|
||||||
|
elif 'Home volume completed' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(80))
|
||||||
|
elif 'Cloning boot' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(85))
|
||||||
|
elif 'Boot volume completed' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(90))
|
||||||
|
elif 'Verifying' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(95))
|
||||||
|
elif 'completed successfully' in stage:
|
||||||
|
self.root.after(0, lambda: self.progress_var.set(100))
|
||||||
|
|
||||||
|
# Parse dd speed
|
||||||
|
if 'MB/s' in line or 'GB/s' in line:
|
||||||
|
speed_match = re.search(r'(\\d+(?:\\.\\d+)?)\\s*(MB|GB)/s', line)
|
||||||
|
if speed_match:
|
||||||
|
speed = f"{speed_match.group(1)} {speed_match.group(2)}/s"
|
||||||
|
self.root.after(0, lambda s=speed: self.speed_var.set(s))
|
||||||
|
|
||||||
|
# Check result
|
||||||
|
return_code = self.backup_process.wait()
|
||||||
|
success = return_code == 0
|
||||||
|
|
||||||
|
# Cleanup temp script
|
||||||
|
try:
|
||||||
|
os.remove('/tmp/gui_backup.sh')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
self.root.after(0, lambda: self.backup_finished(success))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.root.after(0, lambda: self.log(f"❌ Error: {e}"))
|
||||||
|
self.root.after(0, lambda: self.backup_finished(False))
|
||||||
|
|
||||||
|
def backup_finished(self, success):
|
||||||
|
"""Handle backup completion"""
|
||||||
|
self.backup_running = False
|
||||||
|
self.start_button.config(state='normal')
|
||||||
|
self.stop_button.config(state='disabled')
|
||||||
|
self.verify_button.config(state='normal')
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.status_var.set("Completed")
|
||||||
|
self.log("✅ Backup completed successfully!")
|
||||||
|
messagebox.showinfo("Success", "Backup completed successfully!\\n\\nYour external drive now contains a bootable copy.")
|
||||||
|
else:
|
||||||
|
self.status_var.set("Failed")
|
||||||
|
self.log("❌ Backup failed!")
|
||||||
|
messagebox.showerror("Error", "Backup failed. Check the log for details.")
|
||||||
|
|
||||||
|
def stop_backup(self):
|
||||||
|
"""Stop the backup process"""
|
||||||
|
if self.backup_process:
|
||||||
|
self.backup_running = False
|
||||||
|
self.backup_process.terminate()
|
||||||
|
self.log("⏹ Backup stopped by user")
|
||||||
|
self.backup_finished(False)
|
||||||
|
|
||||||
|
def verify_backup(self):
|
||||||
|
"""Verify the backup"""
|
||||||
|
if not self.target_var.get():
|
||||||
|
messagebox.showerror("Error", "Please select a target drive first")
|
||||||
|
return
|
||||||
|
|
||||||
|
target_vg = self.target_var.get().split()[0]
|
||||||
|
self.log(f"🔍 Verifying backup on {target_vg}...")
|
||||||
|
|
||||||
|
# Simple verification
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['sudo', 'lvs', target_vg], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.log("✅ Target volumes exist and are accessible")
|
||||||
|
messagebox.showinfo("Verification", "Basic verification passed!\\nTarget volumes are accessible.")
|
||||||
|
else:
|
||||||
|
self.log("❌ Verification failed - target volumes not accessible")
|
||||||
|
messagebox.showerror("Verification", "Verification failed!")
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"❌ Verification error: {e}")
|
||||||
|
messagebox.showerror("Error", f"Verification error: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
root = tk.Tk()
|
||||||
|
app = LVMBackupGUI(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
193
old_scripts/lvm_block_backup.sh
Executable file
193
old_scripts/lvm_block_backup.sh
Executable file
@@ -0,0 +1,193 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LVM Block-Level Backup Script
|
||||||
|
# Creates consistent snapshots and clones entire volumes block-for-block
|
||||||
|
# This is the ONLY backup script you need!
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SOURCE_VG="internal-vg"
|
||||||
|
TARGET_VG="migration-vg"
|
||||||
|
SNAPSHOT_SIZE="2G"
|
||||||
|
LOG_FILE="/var/log/lvm-block-backup.log"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
local message="[SUCCESS] $1"
|
||||||
|
echo -e "${GREEN}$message${NC}"
|
||||||
|
echo "$message" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
warning() {
|
||||||
|
local message="[WARNING] $1"
|
||||||
|
echo -e "${YELLOW}$message${NC}"
|
||||||
|
echo "$message" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_requirements() {
|
||||||
|
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"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if source VG exists
|
||||||
|
if ! vgs "$SOURCE_VG" >/dev/null 2>&1; then
|
||||||
|
error "Source volume group '$SOURCE_VG' not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if target VG exists
|
||||||
|
if ! vgs "$TARGET_VG" >/dev/null 2>&1; then
|
||||||
|
error "Target volume group '$TARGET_VG' not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if volumes exist
|
||||||
|
local volumes=("root" "home" "boot")
|
||||||
|
for vol in "${volumes[@]}"; do
|
||||||
|
if ! lvs "$SOURCE_VG/$vol" >/dev/null 2>&1; then
|
||||||
|
error "Source logical volume '$SOURCE_VG/$vol' not found"
|
||||||
|
fi
|
||||||
|
if ! lvs "$TARGET_VG/$vol" >/dev/null 2>&1; then
|
||||||
|
error "Target logical volume '$TARGET_VG/$vol' not found"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check available space for snapshots
|
||||||
|
local vg_free=$(vgs --noheadings -o vg_free --units g "$SOURCE_VG" | tr -d ' G')
|
||||||
|
local vg_free_int=${vg_free%.*}
|
||||||
|
|
||||||
|
if [ "$vg_free_int" -lt 6 ]; then
|
||||||
|
error "Insufficient free space for snapshots. Need at least 6GB, have ${vg_free}GB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "System requirements check passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_snapshots() {
|
||||||
|
log "Cleaning up any existing snapshots..."
|
||||||
|
lvremove -f "$SOURCE_VG/root-backup-snap" 2>/dev/null || true
|
||||||
|
lvremove -f "$SOURCE_VG/home-backup-snap" 2>/dev/null || true
|
||||||
|
lvremove -f "$SOURCE_VG/boot-backup-snap" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
create_snapshots() {
|
||||||
|
log "Creating LVM snapshots for consistent backup..."
|
||||||
|
|
||||||
|
cleanup_snapshots
|
||||||
|
|
||||||
|
# Create snapshots
|
||||||
|
lvcreate -L "$SNAPSHOT_SIZE" -s -n root-backup-snap "$SOURCE_VG/root" || error "Failed to create root snapshot"
|
||||||
|
lvcreate -L "$SNAPSHOT_SIZE" -s -n home-backup-snap "$SOURCE_VG/home" || error "Failed to create home snapshot"
|
||||||
|
lvcreate -L 1G -s -n boot-backup-snap "$SOURCE_VG/boot" || error "Failed to create boot snapshot"
|
||||||
|
|
||||||
|
success "Snapshots created successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
clone_volumes() {
|
||||||
|
log "Starting block-level volume cloning..."
|
||||||
|
|
||||||
|
# Unmount target volumes if mounted
|
||||||
|
umount "/dev/$TARGET_VG/home" 2>/dev/null || true
|
||||||
|
umount "/dev/$TARGET_VG/root" 2>/dev/null || true
|
||||||
|
umount "/dev/$TARGET_VG/boot" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Clone root volume
|
||||||
|
log "Cloning root volume (this may take a while)..."
|
||||||
|
dd if="/dev/$SOURCE_VG/root-backup-snap" of="/dev/$TARGET_VG/root" bs=64M status=progress || error "Failed to clone root volume"
|
||||||
|
success "Root volume cloned"
|
||||||
|
|
||||||
|
# Clone home volume
|
||||||
|
log "Cloning home volume (this will take the longest)..."
|
||||||
|
dd if="/dev/$SOURCE_VG/home-backup-snap" of="/dev/$TARGET_VG/home" bs=64M status=progress || error "Failed to clone home volume"
|
||||||
|
success "Home volume cloned"
|
||||||
|
|
||||||
|
# Clone boot volume
|
||||||
|
log "Cloning boot volume..."
|
||||||
|
dd if="/dev/$SOURCE_VG/boot-backup-snap" of="/dev/$TARGET_VG/boot" bs=64M status=progress || error "Failed to clone boot volume"
|
||||||
|
success "Boot volume cloned"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_backup() {
|
||||||
|
log "Verifying backup integrity..."
|
||||||
|
|
||||||
|
# Check filesystem integrity
|
||||||
|
fsck -n "/dev/$TARGET_VG/root" || warning "Root filesystem check showed issues"
|
||||||
|
fsck -n "/dev/$TARGET_VG/boot" || warning "Boot filesystem check showed issues"
|
||||||
|
|
||||||
|
# Note: Can't check encrypted home volume without decryption
|
||||||
|
|
||||||
|
success "Backup verification completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_backup_info() {
|
||||||
|
log "Creating backup information..."
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
=====================================
|
||||||
|
LVM Block-Level Backup Complete
|
||||||
|
=====================================
|
||||||
|
Date: $(date)
|
||||||
|
Source: $SOURCE_VG
|
||||||
|
Target: $TARGET_VG
|
||||||
|
|
||||||
|
Volume Information:
|
||||||
|
$(lvs $SOURCE_VG $TARGET_VG)
|
||||||
|
|
||||||
|
The external drive now contains an exact block-level copy of your internal drive.
|
||||||
|
You can boot from the external drive by selecting it in your BIOS/UEFI boot menu.
|
||||||
|
|
||||||
|
To mount the backup volumes:
|
||||||
|
sudo mount /dev/$TARGET_VG/root /mnt/backup-root
|
||||||
|
sudo mount /dev/$TARGET_VG/boot /mnt/backup-boot
|
||||||
|
# Home volume needs LUKS decryption first
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}=== LVM Block-Level Backup Tool ===${NC}"
|
||||||
|
echo "This will create an exact copy of your internal LVM volumes to the external drive."
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Are you sure you want to proceed? This will OVERWRITE data on $TARGET_VG! [y/N]: " confirm
|
||||||
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_requirements
|
||||||
|
create_snapshots
|
||||||
|
clone_volumes
|
||||||
|
cleanup_snapshots
|
||||||
|
verify_backup
|
||||||
|
show_backup_info
|
||||||
|
|
||||||
|
success "Backup completed successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap to cleanup on exit
|
||||||
|
trap cleanup_snapshots EXIT
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -13,10 +13,10 @@ BLUE='\033[0;34m'
|
|||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
VG_NAME="system-vg"
|
VG_NAME="internal-vg"
|
||||||
SNAPSHOT_SIZE="10G"
|
SNAPSHOT_SIZE="2G"
|
||||||
BACKUP_BASE_DIR="/mnt/backup"
|
BACKUP_BASE_DIR="/mnt/backup"
|
||||||
EXTERNAL_BACKUP_DRIVE="" # Will be auto-detected
|
EXTERNAL_BACKUP_DRIVE="/dev/sda" # External HDD
|
||||||
LOG_FILE="/var/log/lvm-snapshot-backup.log"
|
LOG_FILE="/var/log/lvm-snapshot-backup.log"
|
||||||
|
|
||||||
# Snapshot names
|
# Snapshot names
|
||||||
407
old_scripts/migrate_lvm_to_internal.sh
Executable file
407
old_scripts/migrate_lvm_to_internal.sh
Executable file
@@ -0,0 +1,407 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LVM Migration Script: External M.2 to Internal NVMe
|
||||||
|
# This script migrates the complete LVM structure from external M.2 to internal NVMe
|
||||||
|
# with snapshot capabilities for future backups
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 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
|
||||||
|
EXTERNAL_DRIVE="/dev/sda"
|
||||||
|
INTERNAL_DRIVE="/dev/nvme0n1"
|
||||||
|
VG_NAME="migration-vg"
|
||||||
|
NEW_VG_NAME="internal-vg" # New VG name for internal drive
|
||||||
|
BACKUP_DIR="/tmp/lvm_migration_backup"
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_FILE="/var/log/lvm_migration.log"
|
||||||
|
exec 1> >(tee -a "$LOG_FILE")
|
||||||
|
exec 2> >(tee -a "$LOG_FILE" >&2)
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Safety checks
|
||||||
|
safety_checks() {
|
||||||
|
log_step "Performing safety checks..."
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log_error "This script must be run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify we're booted from external drive
|
||||||
|
root_device=$(findmnt -n -o SOURCE /)
|
||||||
|
if [[ "$root_device" != "/dev/mapper/migration--vg-root" ]]; then
|
||||||
|
log_error "Not booted from external LVM! Current root: $root_device"
|
||||||
|
log_error "Please boot from external M.2 drive first"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check drives exist
|
||||||
|
if [[ ! -b "$EXTERNAL_DRIVE" ]]; then
|
||||||
|
log_error "External drive $EXTERNAL_DRIVE not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -b "$INTERNAL_DRIVE" ]]; then
|
||||||
|
log_error "Internal drive $INTERNAL_DRIVE not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check available space
|
||||||
|
vg_size=$(vgs --noheadings --units g --nosuffix -o vg_size "$VG_NAME" | tr -d ' ')
|
||||||
|
internal_size=$(lsblk -b -n -o SIZE "$INTERNAL_DRIVE" | awk '{printf "%.0f", $1/1024/1024/1024}')
|
||||||
|
|
||||||
|
if (( $(echo "$vg_size > $internal_size" | bc -l) )); then
|
||||||
|
log_error "Internal drive ($internal_size GB) is smaller than LVM structure ($vg_size GB)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Safety checks passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create backup directory
|
||||||
|
create_backup_dir() {
|
||||||
|
log_step "Creating backup directory..."
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Backup current LVM configuration
|
||||||
|
vgcfgbackup -f "$BACKUP_DIR/vg_backup" "$VG_NAME"
|
||||||
|
|
||||||
|
# Save partition tables
|
||||||
|
sfdisk -d "$EXTERNAL_DRIVE" > "$BACKUP_DIR/external_partition_table.txt"
|
||||||
|
sfdisk -d "$INTERNAL_DRIVE" > "$BACKUP_DIR/internal_partition_table.txt" 2>/dev/null || true
|
||||||
|
|
||||||
|
log_info "Backup directory created at $BACKUP_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wipe internal drive and create new partition structure
|
||||||
|
prepare_internal_drive() {
|
||||||
|
log_step "Preparing internal drive..."
|
||||||
|
|
||||||
|
# Confirmation
|
||||||
|
echo -e "${RED}WARNING: This will completely wipe $INTERNAL_DRIVE${NC}"
|
||||||
|
echo "Current internal drive content will be lost!"
|
||||||
|
read -p "Type 'YES' to continue: " confirm
|
||||||
|
if [[ "$confirm" != "YES" ]]; then
|
||||||
|
log_error "Migration cancelled by user"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unmount any mounted partitions from internal drive
|
||||||
|
for mount in $(findmnt -n -o TARGET -S "$INTERNAL_DRIVE"* 2>/dev/null || true); do
|
||||||
|
log_info "Unmounting $mount"
|
||||||
|
umount "$mount" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Deactivate any LVM on internal drive
|
||||||
|
for pv in $(pvs --noheadings -o pv_name | grep -E "^[[:space:]]*$INTERNAL_DRIVE" || true); do
|
||||||
|
log_info "Deactivating PV $pv"
|
||||||
|
vgchange -an $(pvs --noheadings -o vg_name "$pv") || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wipe filesystem signatures and partition table
|
||||||
|
wipefs -af "$INTERNAL_DRIVE"
|
||||||
|
|
||||||
|
# Create new GPT partition table
|
||||||
|
parted "$INTERNAL_DRIVE" --script mklabel gpt
|
||||||
|
|
||||||
|
# Create EFI boot partition (512MB)
|
||||||
|
parted "$INTERNAL_DRIVE" --script mkpart primary fat32 1MiB 513MiB
|
||||||
|
parted "$INTERNAL_DRIVE" --script set 1 esp on
|
||||||
|
|
||||||
|
# Create LVM partition (rest of the drive)
|
||||||
|
parted "$INTERNAL_DRIVE" --script mkpart primary 513MiB 100%
|
||||||
|
parted "$INTERNAL_DRIVE" --script set 2 lvm on
|
||||||
|
|
||||||
|
# Wait for partitions to be recognized
|
||||||
|
sleep 2
|
||||||
|
partprobe "$INTERNAL_DRIVE"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Format EFI partition
|
||||||
|
mkfs.fat -F32 "${INTERNAL_DRIVE}p1"
|
||||||
|
|
||||||
|
log_info "Internal drive prepared with new partition structure"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create LVM structure on internal drive
|
||||||
|
create_lvm_structure() {
|
||||||
|
log_step "Creating LVM structure on internal drive..."
|
||||||
|
|
||||||
|
# Create physical volume
|
||||||
|
pvcreate "${INTERNAL_DRIVE}p2"
|
||||||
|
|
||||||
|
# Create volume group
|
||||||
|
vgcreate "$NEW_VG_NAME" "${INTERNAL_DRIVE}p2"
|
||||||
|
|
||||||
|
# Get sizes from external LVM
|
||||||
|
root_size=$(lvs --noheadings --units g --nosuffix -o lv_size "$VG_NAME/root" | tr -d ' ')
|
||||||
|
home_size=$(lvs --noheadings --units g --nosuffix -o lv_size "$VG_NAME/home" | tr -d ' ')
|
||||||
|
boot_size=$(lvs --noheadings --units g --nosuffix -o lv_size "$VG_NAME/boot" | tr -d ' ')
|
||||||
|
swap_size=$(lvs --noheadings --units g --nosuffix -o lv_size "$VG_NAME/swap" | tr -d ' ')
|
||||||
|
|
||||||
|
# Reserve 20% free space for snapshots
|
||||||
|
vg_free=$(vgs --noheadings --units g --nosuffix -o vg_free "$NEW_VG_NAME" | tr -d ' ')
|
||||||
|
snapshot_reserve=$(echo "$vg_free * 0.2" | bc -l | cut -d. -f1)
|
||||||
|
|
||||||
|
log_info "Reserving ${snapshot_reserve}GB for LVM snapshots"
|
||||||
|
|
||||||
|
# Create logical volumes with same sizes as external
|
||||||
|
lvcreate -L "${root_size}G" -n root "$NEW_VG_NAME"
|
||||||
|
lvcreate -L "${boot_size}G" -n boot "$NEW_VG_NAME"
|
||||||
|
lvcreate -L "${swap_size}G" -n swap "$NEW_VG_NAME"
|
||||||
|
|
||||||
|
# Create home with remaining space minus snapshot reserve
|
||||||
|
remaining_space=$(echo "$vg_free - $snapshot_reserve" | bc -l | cut -d. -f1)
|
||||||
|
adjusted_home_size=$(echo "$remaining_space - $root_size - $boot_size - $swap_size" | bc -l | cut -d. -f1)
|
||||||
|
|
||||||
|
if (( $(echo "$adjusted_home_size > 0" | bc -l) )); then
|
||||||
|
lvcreate -L "${adjusted_home_size}G" -n home "$NEW_VG_NAME"
|
||||||
|
else
|
||||||
|
log_warn "Using original home size, may reduce snapshot space"
|
||||||
|
lvcreate -L "${home_size}G" -n home "$NEW_VG_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create filesystems
|
||||||
|
mkfs.ext4 -L root "/dev/$NEW_VG_NAME/root"
|
||||||
|
mkfs.ext4 -L boot "/dev/$NEW_VG_NAME/boot"
|
||||||
|
mkfs.ext4 -L home "/dev/$NEW_VG_NAME/home"
|
||||||
|
mkswap -L swap "/dev/$NEW_VG_NAME/swap"
|
||||||
|
|
||||||
|
log_info "LVM structure created on internal drive"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mount internal drive and copy data
|
||||||
|
copy_data() {
|
||||||
|
log_step "Copying data from external to internal drive..."
|
||||||
|
|
||||||
|
# Create mount points
|
||||||
|
mkdir -p /mnt/internal_root
|
||||||
|
mkdir -p /mnt/internal_boot
|
||||||
|
mkdir -p /mnt/internal_home
|
||||||
|
|
||||||
|
# Mount internal LVM
|
||||||
|
mount "/dev/$NEW_VG_NAME/root" /mnt/internal_root
|
||||||
|
mount "/dev/$NEW_VG_NAME/boot" /mnt/internal_boot
|
||||||
|
mount "/dev/$NEW_VG_NAME/home" /mnt/internal_home
|
||||||
|
|
||||||
|
# Copy data with progress
|
||||||
|
log_info "Copying root filesystem..."
|
||||||
|
rsync -avHAXS --progress / /mnt/internal_root/ \
|
||||||
|
--exclude=/dev/* \
|
||||||
|
--exclude=/proc/* \
|
||||||
|
--exclude=/sys/* \
|
||||||
|
--exclude=/tmp/* \
|
||||||
|
--exclude=/run/* \
|
||||||
|
--exclude=/mnt/* \
|
||||||
|
--exclude=/media/* \
|
||||||
|
--exclude=/lost+found \
|
||||||
|
--exclude=/boot/*
|
||||||
|
|
||||||
|
log_info "Copying boot filesystem..."
|
||||||
|
rsync -avHAXS --progress /boot/ /mnt/internal_boot/
|
||||||
|
|
||||||
|
log_info "Copying home filesystem..."
|
||||||
|
rsync -avHAXS --progress /home/ /mnt/internal_home/
|
||||||
|
|
||||||
|
log_info "Data copy completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure system for internal LVM boot
|
||||||
|
configure_boot() {
|
||||||
|
log_step "Configuring boot for internal LVM..."
|
||||||
|
|
||||||
|
# Mount EFI partition
|
||||||
|
mkdir -p /mnt/internal_root/boot/efi
|
||||||
|
mount "${INTERNAL_DRIVE}p1" /mnt/internal_root/boot/efi
|
||||||
|
|
||||||
|
# Chroot preparations
|
||||||
|
mount --bind /dev /mnt/internal_root/dev
|
||||||
|
mount --bind /proc /mnt/internal_root/proc
|
||||||
|
mount --bind /sys /mnt/internal_root/sys
|
||||||
|
mount --bind /run /mnt/internal_root/run
|
||||||
|
|
||||||
|
# Update fstab
|
||||||
|
cat > /mnt/internal_root/etc/fstab << EOF
|
||||||
|
# Internal LVM Configuration
|
||||||
|
/dev/$NEW_VG_NAME/root / ext4 defaults 0 1
|
||||||
|
/dev/$NEW_VG_NAME/boot /boot ext4 defaults 0 2
|
||||||
|
/dev/$NEW_VG_NAME/home /home ext4 defaults 0 2
|
||||||
|
/dev/$NEW_VG_NAME/swap none swap sw 0 0
|
||||||
|
${INTERNAL_DRIVE}p1 /boot/efi vfat umask=0077 0 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Update initramfs to include LVM
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "update-initramfs -u -k all"
|
||||||
|
|
||||||
|
# Install and configure GRUB
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian"
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "update-grub"
|
||||||
|
|
||||||
|
# Cleanup mounts
|
||||||
|
umount /mnt/internal_root/dev
|
||||||
|
umount /mnt/internal_root/proc
|
||||||
|
umount /mnt/internal_root/sys
|
||||||
|
umount /mnt/internal_root/run
|
||||||
|
umount /mnt/internal_root/boot/efi
|
||||||
|
|
||||||
|
log_info "Boot configuration completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup LVM snapshots
|
||||||
|
setup_snapshots() {
|
||||||
|
log_step "Setting up LVM snapshot capability..."
|
||||||
|
|
||||||
|
# Create snapshot management script
|
||||||
|
cat > /mnt/internal_root/usr/local/bin/lvm-snapshot-manager << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
# LVM Snapshot Manager
|
||||||
|
|
||||||
|
VG_NAME="internal-vg"
|
||||||
|
SNAPSHOT_SIZE="10G" # Adjust based on available space
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
create)
|
||||||
|
echo "Creating LVM snapshots..."
|
||||||
|
lvcreate -L $SNAPSHOT_SIZE -s -n root_snapshot $VG_NAME/root
|
||||||
|
lvcreate -L $SNAPSHOT_SIZE -s -n home_snapshot $VG_NAME/home
|
||||||
|
echo "Snapshots created successfully"
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
echo "Removing LVM snapshots..."
|
||||||
|
lvremove -f $VG_NAME/root_snapshot 2>/dev/null || true
|
||||||
|
lvremove -f $VG_NAME/home_snapshot 2>/dev/null || true
|
||||||
|
echo "Snapshots removed"
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
echo "Current snapshots:"
|
||||||
|
lvs $VG_NAME | grep snapshot || echo "No snapshots found"
|
||||||
|
;;
|
||||||
|
merge)
|
||||||
|
if [[ -z "$2" ]]; then
|
||||||
|
echo "Usage: $0 merge <snapshot_name>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Merging snapshot $2..."
|
||||||
|
lvconvert --merge $VG_NAME/$2
|
||||||
|
echo "Snapshot merge initiated (requires reboot to complete)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {create|remove|list|merge <snapshot_name>}"
|
||||||
|
echo " create - Create snapshots of root and home"
|
||||||
|
echo " remove - Remove all snapshots"
|
||||||
|
echo " list - List existing snapshots"
|
||||||
|
echo " merge - Merge a snapshot back to origin"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x /mnt/internal_root/usr/local/bin/lvm-snapshot-manager
|
||||||
|
|
||||||
|
# Create backup script using snapshots
|
||||||
|
cat > /mnt/internal_root/usr/local/bin/snapshot-backup << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
# Snapshot-based backup script
|
||||||
|
|
||||||
|
BACKUP_DIR="/backup"
|
||||||
|
VG_NAME="internal-vg"
|
||||||
|
DATE=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# Create snapshots
|
||||||
|
echo "Creating snapshots..."
|
||||||
|
/usr/local/bin/lvm-snapshot-manager create
|
||||||
|
|
||||||
|
# Mount snapshots and backup
|
||||||
|
mkdir -p $BACKUP_DIR/root_$DATE
|
||||||
|
mkdir -p $BACKUP_DIR/home_$DATE
|
||||||
|
|
||||||
|
mount /dev/$VG_NAME/root_snapshot $BACKUP_DIR/root_$DATE
|
||||||
|
mount /dev/$VG_NAME/home_snapshot $BACKUP_DIR/home_$DATE
|
||||||
|
|
||||||
|
echo "Snapshots mounted. You can now backup from:"
|
||||||
|
echo " Root: $BACKUP_DIR/root_$DATE"
|
||||||
|
echo " Home: $BACKUP_DIR/home_$DATE"
|
||||||
|
echo ""
|
||||||
|
echo "When done, run: umount $BACKUP_DIR/root_$DATE $BACKUP_DIR/home_$DATE"
|
||||||
|
echo "Then run: /usr/local/bin/lvm-snapshot-manager remove"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x /mnt/internal_root/usr/local/bin/snapshot-backup
|
||||||
|
|
||||||
|
log_info "LVM snapshot tools installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup and unmount
|
||||||
|
cleanup() {
|
||||||
|
log_step "Cleaning up..."
|
||||||
|
|
||||||
|
# Unmount all internal mounts
|
||||||
|
umount /mnt/internal_home || true
|
||||||
|
umount /mnt/internal_boot || true
|
||||||
|
umount /mnt/internal_root || true
|
||||||
|
|
||||||
|
# Remove mount points
|
||||||
|
rmdir /mnt/internal_home /mnt/internal_boot /mnt/internal_root 2>/dev/null || true
|
||||||
|
|
||||||
|
log_info "Cleanup completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main migration function
|
||||||
|
main() {
|
||||||
|
log_info "Starting LVM migration from external M.2 to internal NVMe"
|
||||||
|
log_info "External: $EXTERNAL_DRIVE -> Internal: $INTERNAL_DRIVE"
|
||||||
|
|
||||||
|
safety_checks
|
||||||
|
create_backup_dir
|
||||||
|
prepare_internal_drive
|
||||||
|
create_lvm_structure
|
||||||
|
copy_data
|
||||||
|
configure_boot
|
||||||
|
setup_snapshots
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
log_info "Migration completed successfully!"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}SUCCESS!${NC} LVM migration completed"
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Reboot and select internal drive in BIOS/UEFI"
|
||||||
|
echo "2. Verify system boots correctly from internal LVM"
|
||||||
|
echo "3. Test snapshot functionality with: sudo lvm-snapshot-manager create"
|
||||||
|
echo "4. Once confirmed working, you can repurpose the external M.2"
|
||||||
|
echo
|
||||||
|
echo "Snapshot management commands:"
|
||||||
|
echo " sudo lvm-snapshot-manager create # Create snapshots"
|
||||||
|
echo " sudo lvm-snapshot-manager list # List snapshots"
|
||||||
|
echo " sudo lvm-snapshot-manager remove # Remove snapshots"
|
||||||
|
echo " sudo snapshot-backup # Backup using snapshots"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle interruption
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
@@ -79,7 +79,7 @@ detect_drives() {
|
|||||||
# Filter out the USB stick we're running from (if running from live system)
|
# Filter out the USB stick we're running from (if running from live system)
|
||||||
for drive in "${all_drives[@]}"; do
|
for drive in "${all_drives[@]}"; do
|
||||||
# Check if this drive contains the live system
|
# Check if this drive contains the live system
|
||||||
if mount | grep "$drive" | grep -q "/lib/live\|/media.*MIGRATION_TOOLS"; then
|
if mount | grep -q "$drive" && mount | grep -q "/lib/live\|/media.*MIGRATION_TOOLS"; then
|
||||||
log "Excluding live USB drive: $drive"
|
log "Excluding live USB drive: $drive"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -252,17 +252,11 @@ detect_drives() {
|
|||||||
analyze_internal_system() {
|
analyze_internal_system() {
|
||||||
log "Analyzing internal system layout..."
|
log "Analyzing internal system layout..."
|
||||||
|
|
||||||
# Get all partitions on internal drive and clean up the tree formatting
|
# Get all partitions on internal drive
|
||||||
local partitions=($(lsblk -pno NAME "$INTERNAL_DRIVE" | grep -v "^$INTERNAL_DRIVE$" | sed 's/^[├└]─//'))
|
local partitions=($(lsblk -pno NAME "$INTERNAL_DRIVE" | grep -v "^$INTERNAL_DRIVE$"))
|
||||||
|
|
||||||
echo "Found partitions on $INTERNAL_DRIVE:"
|
echo "Found partitions on $INTERNAL_DRIVE:"
|
||||||
for part in "${partitions[@]}"; do
|
for part in "${partitions[@]}"; do
|
||||||
# Verify the partition exists as a block device
|
|
||||||
if [ ! -b "$part" ]; then
|
|
||||||
warning "Skipping invalid partition: $part"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
local size=$(lsblk -no SIZE "$part")
|
local size=$(lsblk -no SIZE "$part")
|
||||||
local fstype=$(lsblk -no FSTYPE "$part")
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
local mountpoint=$(lsblk -no MOUNTPOINT "$part")
|
local mountpoint=$(lsblk -no MOUNTPOINT "$part")
|
||||||
@@ -275,21 +269,21 @@ analyze_internal_system() {
|
|||||||
PARTITION_SIZES["$part"]="$size"
|
PARTITION_SIZES["$part"]="$size"
|
||||||
|
|
||||||
# Try to identify partition purpose
|
# Try to identify partition purpose
|
||||||
if [[ "$fstype" == "vfat" ]]; then
|
if [[ "$fstype" == "vfat" ]] && [[ "$part" == *"1" ]]; then
|
||||||
INTERNAL_PARTITIONS["efi"]="$part"
|
INTERNAL_PARTITIONS["efi"]="$part"
|
||||||
BOOT_SIZE="1G" # Default EFI size
|
BOOT_SIZE="1G" # Default EFI size
|
||||||
elif [[ "$fstype" == "ext4" ]] && ([[ "$mountpoint" == "/" ]] || [[ "$label" == "root"* ]] || [[ -z "${INTERNAL_PARTITIONS[root]}" ]]); then
|
elif [[ "$mountpoint" == "/" ]] || [[ "$label" == "root"* ]]; then
|
||||||
INTERNAL_PARTITIONS["root"]="$part"
|
INTERNAL_PARTITIONS["root"]="$part"
|
||||||
# Parse size more carefully, handle G/M/K suffixes
|
# Parse size more carefully, handle G/M/K suffixes
|
||||||
local size_num=$(echo "$size" | sed 's/[^0-9.]//g')
|
local size_num=$(echo "$size" | sed 's/[^0-9.]//g')
|
||||||
if [[ "$size" == *"G"* ]]; then
|
if [[ "$size" == *"G"* ]]; then
|
||||||
ROOT_SIZE="${size_num}G" # Use exact source size
|
ROOT_SIZE="$(echo "$size_num + 10" | bc)G" # Add some extra space
|
||||||
elif [[ "$size" == *"M"* ]]; then
|
elif [[ "$size" == *"M"* ]]; then
|
||||||
ROOT_SIZE="$(echo "scale=1; $size_num / 1024 + 1" | bc)G"
|
ROOT_SIZE="$(echo "scale=1; $size_num / 1024 + 1" | bc)G"
|
||||||
else
|
else
|
||||||
ROOT_SIZE="${size_num}G"
|
ROOT_SIZE="${size_num}G"
|
||||||
fi
|
fi
|
||||||
elif [[ "$fstype" == "ext4" ]] && ([[ "$mountpoint" == "/home" ]] || [[ "$label" == "home"* ]]); then
|
elif [[ "$mountpoint" == "/home" ]] || [[ "$label" == "home"* ]]; then
|
||||||
INTERNAL_PARTITIONS["home"]="$part"
|
INTERNAL_PARTITIONS["home"]="$part"
|
||||||
# Parse size more carefully
|
# Parse size more carefully
|
||||||
local size_num=$(echo "$size" | sed 's/[^0-9.]//g')
|
local size_num=$(echo "$size" | sed 's/[^0-9.]//g')
|
||||||
@@ -300,7 +294,7 @@ analyze_internal_system() {
|
|||||||
else
|
else
|
||||||
HOME_SIZE="${size_num}G"
|
HOME_SIZE="${size_num}G"
|
||||||
fi
|
fi
|
||||||
elif [[ "$fstype" == "ext4" ]] && ([[ "$mountpoint" == "/boot" ]] || [[ "$label" == "boot"* ]]); then
|
elif [[ "$mountpoint" == "/boot" ]] || [[ "$label" == "boot"* ]]; then
|
||||||
INTERNAL_PARTITIONS["boot"]="$part"
|
INTERNAL_PARTITIONS["boot"]="$part"
|
||||||
BOOT_SIZE="2G" # Standard boot size
|
BOOT_SIZE="2G" # Standard boot size
|
||||||
elif [[ "$fstype" == "swap" ]]; then
|
elif [[ "$fstype" == "swap" ]]; then
|
||||||
@@ -315,20 +309,7 @@ analyze_internal_system() {
|
|||||||
fi
|
fi
|
||||||
elif [[ "$fstype" == "crypto_LUKS" ]]; then
|
elif [[ "$fstype" == "crypto_LUKS" ]]; then
|
||||||
log "Found encrypted partition: $part"
|
log "Found encrypted partition: $part"
|
||||||
# This might be encrypted home or root - for now assume it's home
|
# This might be encrypted home or root
|
||||||
if [[ -z "${INTERNAL_PARTITIONS[home]}" ]]; then
|
|
||||||
INTERNAL_PARTITIONS["home_encrypted"]="$part"
|
|
||||||
# Calculate size based on the encrypted partition size
|
|
||||||
local size_num=$(echo "$size" | sed 's/[^0-9.]//g')
|
|
||||||
if [[ "$size" == *"G"* ]]; then
|
|
||||||
HOME_SIZE="$(echo "$size_num + 10" | bc)G" # Add some extra space
|
|
||||||
elif [[ "$size" == *"M"* ]]; then
|
|
||||||
HOME_SIZE="$(echo "scale=1; $size_num / 1024 + 10" | bc)G"
|
|
||||||
else
|
|
||||||
HOME_SIZE="${size_num}G"
|
|
||||||
fi
|
|
||||||
log "Encrypted home partition detected, setting HOME_SIZE to $HOME_SIZE"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -338,11 +319,9 @@ analyze_internal_system() {
|
|||||||
# Increase root size to accommodate home
|
# Increase root size to accommodate home
|
||||||
if [ -n "$ROOT_SIZE" ]; then
|
if [ -n "$ROOT_SIZE" ]; then
|
||||||
local root_num=$(echo "$ROOT_SIZE" | sed 's/G//')
|
local root_num=$(echo "$ROOT_SIZE" | sed 's/G//')
|
||||||
# Use bc for decimal arithmetic
|
ROOT_SIZE="$((root_num + 50))G" # Add extra space
|
||||||
local new_root_size=$(echo "$root_num + 50" | bc -l | cut -d. -f1)
|
|
||||||
ROOT_SIZE="${new_root_size}G" # Add extra space
|
|
||||||
fi
|
fi
|
||||||
HOME_SIZE="50G" # Create separate home in LVM - will be adjusted below based on detected encrypted home
|
HOME_SIZE="50G" # Create separate home in LVM
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set default swap size if not found
|
# Set default swap size if not found
|
||||||
@@ -352,22 +331,9 @@ analyze_internal_system() {
|
|||||||
log "No swap partition found, creating ${SWAP_SIZE} swap based on system memory"
|
log "No swap partition found, creating ${SWAP_SIZE} swap based on system memory"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if we have encrypted home mounted and adjust size accordingly
|
# Set sensible defaults if sizes are missing
|
||||||
if [[ -n "${INTERNAL_PARTITIONS[home_encrypted]}" ]]; then
|
[ -z "$ROOT_SIZE" ] && ROOT_SIZE="70G"
|
||||||
local encrypted_home_mount=$(mount | grep "internal_home_encrypted" | awk '{print $3}')
|
[ -z "$HOME_SIZE" ] && HOME_SIZE="50G"
|
||||||
if [[ -n "$encrypted_home_mount" ]]; then
|
|
||||||
log "Checking actual size requirements for encrypted home at $encrypted_home_mount"
|
|
||||||
local encrypted_total=$(df -BG "$encrypted_home_mount" | tail -1 | awk '{print $2}' | sed 's/G//')
|
|
||||||
local encrypted_used=$(df -BG "$encrypted_home_mount" | tail -1 | awk '{print $3}' | sed 's/G//')
|
|
||||||
# Use the exact total size from the encrypted partition
|
|
||||||
HOME_SIZE="${encrypted_total}G"
|
|
||||||
log "Encrypted home is ${encrypted_total}G total (${encrypted_used}G used), setting HOME_SIZE to $HOME_SIZE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set sensible defaults if sizes are missing - use exact source sizes
|
|
||||||
[ -z "$ROOT_SIZE" ] && ROOT_SIZE="58G" # Exact match to source
|
|
||||||
[ -z "$HOME_SIZE" ] && HOME_SIZE="411G" # Exact match to encrypted source
|
|
||||||
[ -z "$BOOT_SIZE" ] && BOOT_SIZE="2G"
|
[ -z "$BOOT_SIZE" ] && BOOT_SIZE="2G"
|
||||||
[ -z "$SWAP_SIZE" ] && SWAP_SIZE="8G"
|
[ -z "$SWAP_SIZE" ] && SWAP_SIZE="8G"
|
||||||
|
|
||||||
@@ -381,12 +347,9 @@ analyze_internal_system() {
|
|||||||
echo "System analysis summary:"
|
echo "System analysis summary:"
|
||||||
echo " EFI partition: ${INTERNAL_PARTITIONS[efi]:-'not found'}"
|
echo " EFI partition: ${INTERNAL_PARTITIONS[efi]:-'not found'}"
|
||||||
echo " Root partition: ${INTERNAL_PARTITIONS[root]:-'not found'}"
|
echo " Root partition: ${INTERNAL_PARTITIONS[root]:-'not found'}"
|
||||||
echo " Home partition: ${INTERNAL_PARTITIONS[home]:-${INTERNAL_PARTITIONS[home_encrypted]:-'integrated in root'}}"
|
echo " Home partition: ${INTERNAL_PARTITIONS[home]:-'integrated in root'}"
|
||||||
echo " Boot partition: ${INTERNAL_PARTITIONS[boot]:-'integrated in root/efi'}"
|
echo " Boot partition: ${INTERNAL_PARTITIONS[boot]:-'integrated in root/efi'}"
|
||||||
echo " Swap partition: ${INTERNAL_PARTITIONS[swap]:-'not found'}"
|
echo " Swap partition: ${INTERNAL_PARTITIONS[swap]:-'not found'}"
|
||||||
if [ -n "${INTERNAL_PARTITIONS[home_encrypted]}" ]; then
|
|
||||||
echo " Encrypted home: ${INTERNAL_PARTITIONS[home_encrypted]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
success "System analysis completed"
|
success "System analysis completed"
|
||||||
}
|
}
|
||||||
@@ -459,31 +422,15 @@ setup_work_directories() {
|
|||||||
backup_existing_external_data() {
|
backup_existing_external_data() {
|
||||||
log "Backing up any existing data on external drive..."
|
log "Backing up any existing data on external drive..."
|
||||||
|
|
||||||
# Check if target partition has existing LVM structures
|
# Check if external LVM volumes are already mounted
|
||||||
if pvdisplay "${EXTERNAL_DRIVE}2" >/dev/null 2>&1; then
|
if [ -d "/dev/external-vg" ]; then
|
||||||
warning "Found existing LVM structures on ${EXTERNAL_DRIVE}2"
|
warning "Found existing external-vg volume group"
|
||||||
confirm_action "This will DESTROY all data on the external M.2 drive!"
|
confirm_action "This will DESTROY all data on the external M.2 drive!"
|
||||||
|
|
||||||
# Get the volume group name if it exists
|
|
||||||
local existing_vg=$(pvdisplay "${EXTERNAL_DRIVE}2" 2>/dev/null | grep "VG Name" | awk '{print $3}')
|
|
||||||
if [ -n "$existing_vg" ] && [ "$existing_vg" != "" ]; then
|
|
||||||
log "Deactivating existing volume group: $existing_vg"
|
|
||||||
vgchange -an "$existing_vg" || true
|
|
||||||
vgremove -f "$existing_vg" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Removing physical volume from ${EXTERNAL_DRIVE}2"
|
|
||||||
pvremove -f "${EXTERNAL_DRIVE}2" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Also check if our target VG name already exists anywhere
|
|
||||||
if [ -d "/dev/$VG_NAME" ]; then
|
|
||||||
warning "Found existing $VG_NAME volume group"
|
|
||||||
confirm_action "This will DESTROY all data in the existing $VG_NAME volume group!"
|
|
||||||
|
|
||||||
# Deactivate existing LVM volumes
|
# Deactivate existing LVM volumes
|
||||||
vgchange -an "$VG_NAME" || true
|
vgchange -an external-vg || true
|
||||||
vgremove -f "$VG_NAME" || true
|
vgremove -f external-vg || true
|
||||||
|
pvremove -f ${EXTERNAL_DRIVE}2 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
success "External drive prepared for migration"
|
success "External drive prepared for migration"
|
||||||
@@ -541,10 +488,7 @@ create_lvm_layout() {
|
|||||||
|
|
||||||
# Setup LVM
|
# Setup LVM
|
||||||
log "Creating LVM physical volume on ${EXTERNAL_DRIVE}2"
|
log "Creating LVM physical volume on ${EXTERNAL_DRIVE}2"
|
||||||
if ! pvcreate "${EXTERNAL_DRIVE}2"; then
|
pvcreate "${EXTERNAL_DRIVE}2" || error "Failed to create physical volume"
|
||||||
warning "Initial pvcreate failed, trying with force flag..."
|
|
||||||
pvcreate -ff "${EXTERNAL_DRIVE}2" || error "Failed to create physical volume even with force flag"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Creating volume group: $VG_NAME"
|
log "Creating volume group: $VG_NAME"
|
||||||
vgcreate "$VG_NAME" "${EXTERNAL_DRIVE}2" || error "Failed to create volume group"
|
vgcreate "$VG_NAME" "${EXTERNAL_DRIVE}2" || error "Failed to create volume group"
|
||||||
@@ -585,16 +529,16 @@ create_lvm_layout() {
|
|||||||
|
|
||||||
# Create logical volumes with space for snapshots
|
# Create logical volumes with space for snapshots
|
||||||
log "Creating logical volume: root ($ROOT_SIZE)"
|
log "Creating logical volume: root ($ROOT_SIZE)"
|
||||||
lvcreate --yes -L "$ROOT_SIZE" -n "$ROOT_LV" "$VG_NAME" || error "Failed to create root LV"
|
lvcreate -L "$ROOT_SIZE" -n "$ROOT_LV" "$VG_NAME" || error "Failed to create root LV"
|
||||||
|
|
||||||
log "Creating logical volume: home ($HOME_SIZE)"
|
log "Creating logical volume: home ($HOME_SIZE)"
|
||||||
lvcreate --yes -L "$HOME_SIZE" -n "$HOME_LV" "$VG_NAME" || error "Failed to create home LV"
|
lvcreate -L "$HOME_SIZE" -n "$HOME_LV" "$VG_NAME" || error "Failed to create home LV"
|
||||||
|
|
||||||
log "Creating logical volume: swap ($SWAP_SIZE)"
|
log "Creating logical volume: swap ($SWAP_SIZE)"
|
||||||
lvcreate --yes -L "$SWAP_SIZE" -n "$SWAP_LV" "$VG_NAME" || error "Failed to create swap LV"
|
lvcreate -L "$SWAP_SIZE" -n "$SWAP_LV" "$VG_NAME" || error "Failed to create swap LV"
|
||||||
|
|
||||||
log "Creating logical volume: boot ($BOOT_SIZE)"
|
log "Creating logical volume: boot ($BOOT_SIZE)"
|
||||||
lvcreate --yes -L "$BOOT_SIZE" -n "$BOOT_LV" "$VG_NAME" || error "Failed to create boot LV"
|
lvcreate -L "$BOOT_SIZE" -n "$BOOT_LV" "$VG_NAME" || error "Failed to create boot LV"
|
||||||
|
|
||||||
# Show LVM layout
|
# Show LVM layout
|
||||||
log "LVM layout created:"
|
log "LVM layout created:"
|
||||||
@@ -752,34 +696,7 @@ copy_system_data() {
|
|||||||
update_system_configuration() {
|
update_system_configuration() {
|
||||||
log "Updating system configuration..."
|
log "Updating system configuration..."
|
||||||
|
|
||||||
# Update crypttab (remove old encrypted home entry since we're using LVM now)
|
# Update fstab
|
||||||
echo "# No encrypted partitions in LVM setup - using LVM volumes" > "$EXTERNAL_ROOT_MOUNT/etc/crypttab"
|
|
||||||
|
|
||||||
# Ensure LVM tools are available in initramfs
|
|
||||||
echo "lvm2" >> "$EXTERNAL_ROOT_MOUNT/etc/initramfs-tools/modules" 2>/dev/null || true
|
|
||||||
|
|
||||||
success "System configuration updated"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_bootloader() {
|
|
||||||
log "Installing bootloader (GRUB EFI)..."
|
|
||||||
|
|
||||||
# Ensure EFI directory exists in chroot
|
|
||||||
mkdir -p "$EXTERNAL_ROOT_MOUNT/boot/efi"
|
|
||||||
mkdir -p "$EXTERNAL_ROOT_MOUNT/boot/grub"
|
|
||||||
|
|
||||||
# Mount EFI partition in chroot environment
|
|
||||||
mount "${EXTERNAL_DRIVE}1" "$EXTERNAL_ROOT_MOUNT/boot/efi"
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
|
|
||||||
log "Updating system configuration for LVM..."
|
|
||||||
|
|
||||||
# Update fstab with correct UUIDs (already done in update_system_configuration)
|
|
||||||
local root_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$ROOT_LV")
|
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 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 boot_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$BOOT_LV")
|
||||||
@@ -789,124 +706,52 @@ install_bootloader() {
|
|||||||
cat > "$EXTERNAL_ROOT_MOUNT/etc/fstab" << EOF
|
cat > "$EXTERNAL_ROOT_MOUNT/etc/fstab" << EOF
|
||||||
# /etc/fstab: static file system information.
|
# /etc/fstab: static file system information.
|
||||||
#
|
#
|
||||||
# Use 'blkid' to print the universally unique identifier for a
|
# Use 'blkid' to print the universally unique identifier for a device; this may
|
||||||
# device; this may be used with UUID= as a more robust way to name devices
|
# be used with UUID= as a more robust way to name devices that works even if
|
||||||
# that works even if disks are added and removed. See fstab(5).
|
# disks are added and removed. See fstab(5).
|
||||||
#
|
#
|
||||||
# <file system> <mount point> <type> <options> <dump> <pass>
|
# <file system> <mount point> <type> <options> <dump> <pass>
|
||||||
|
UUID=$root_uuid / ext4 defaults 0 1
|
||||||
# Root filesystem (LVM)
|
UUID=$efi_uuid /boot/efi vfat defaults 0 2
|
||||||
UUID=$root_uuid / ext4 errors=remount-ro 0 1
|
|
||||||
|
|
||||||
# Boot partition (LVM)
|
|
||||||
UUID=$boot_uuid /boot ext4 defaults 0 2
|
UUID=$boot_uuid /boot ext4 defaults 0 2
|
||||||
|
|
||||||
# EFI boot partition
|
|
||||||
UUID=$efi_uuid /boot/efi vfat umask=0077 0 1
|
|
||||||
|
|
||||||
# Home partition (LVM)
|
|
||||||
UUID=$home_uuid /home ext4 defaults 0 2
|
UUID=$home_uuid /home ext4 defaults 0 2
|
||||||
|
|
||||||
# Swap (LVM)
|
|
||||||
UUID=$swap_uuid none swap sw 0 0
|
UUID=$swap_uuid none swap sw 0 0
|
||||||
|
|
||||||
tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0
|
tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
log "Installing GRUB bootloader..."
|
# 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"
|
||||||
|
|
||||||
# Install and configure GRUB in chroot environment
|
|
||||||
chroot "$EXTERNAL_ROOT_MOUNT" /bin/bash -c "
|
|
||||||
# Update initramfs to include LVM support
|
# 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
|
update-initramfs -u -k all
|
||||||
|
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck
|
||||||
# Generate GRUB configuration
|
|
||||||
update-grub
|
update-grub
|
||||||
|
"
|
||||||
|
|
||||||
# Install GRUB EFI bootloader
|
# Unmount bind mounts
|
||||||
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Ubuntu --recheck
|
umount "$EXTERNAL_ROOT_MOUNT/dev"
|
||||||
|
umount "$EXTERNAL_ROOT_MOUNT/proc"
|
||||||
|
umount "$EXTERNAL_ROOT_MOUNT/sys"
|
||||||
|
umount "$EXTERNAL_ROOT_MOUNT/run"
|
||||||
|
|
||||||
# Update GRUB configuration again after installation
|
success "Bootloader installed successfully"
|
||||||
update-grub
|
|
||||||
" 2>&1 | while IFS= read -r line; do
|
|
||||||
echo " GRUB: $line"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Verify EFI installation
|
|
||||||
if [ -f "$EXTERNAL_ROOT_MOUNT/boot/efi/EFI/Ubuntu/grubx64.efi" ]; then
|
|
||||||
success "GRUB EFI bootloader installed successfully"
|
|
||||||
log "EFI bootloader files:"
|
|
||||||
ls -la "$EXTERNAL_ROOT_MOUNT/boot/efi/EFI/Ubuntu/" | while IFS= read -r line; do
|
|
||||||
echo " $line"
|
|
||||||
done
|
|
||||||
else
|
|
||||||
error "GRUB EFI installation failed - bootloader files not found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Unmount bind mounts and EFI
|
|
||||||
umount "$EXTERNAL_ROOT_MOUNT/boot/efi" 2>/dev/null || true
|
|
||||||
umount "$EXTERNAL_ROOT_MOUNT/dev" 2>/dev/null || true
|
|
||||||
umount "$EXTERNAL_ROOT_MOUNT/proc" 2>/dev/null || true
|
|
||||||
umount "$EXTERNAL_ROOT_MOUNT/sys" 2>/dev/null || true
|
|
||||||
umount "$EXTERNAL_ROOT_MOUNT/run" 2>/dev/null || true
|
|
||||||
|
|
||||||
success "Bootloader installation completed"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_migration() {
|
create_lvm_snapshot_script() {
|
||||||
log "Validating migration results..."
|
|
||||||
|
|
||||||
# Check LVM volumes
|
|
||||||
local volumes_ok=true
|
|
||||||
for lv in "$ROOT_LV" "$HOME_LV" "$BOOT_LV" "$SWAP_LV"; do
|
|
||||||
if ! lvs "/dev/$VG_NAME/$lv" >/dev/null 2>&1; then
|
|
||||||
error "LVM volume /dev/$VG_NAME/$lv not found"
|
|
||||||
volumes_ok=false
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check filesystems
|
|
||||||
local filesystems_ok=true
|
|
||||||
if ! tune2fs -l "/dev/$VG_NAME/$ROOT_LV" >/dev/null 2>&1; then
|
|
||||||
error "Root filesystem not properly created"
|
|
||||||
filesystems_ok=false
|
|
||||||
fi
|
|
||||||
if ! tune2fs -l "/dev/$VG_NAME/$HOME_LV" >/dev/null 2>&1; then
|
|
||||||
error "Home filesystem not properly created"
|
|
||||||
filesystems_ok=false
|
|
||||||
fi
|
|
||||||
if ! tune2fs -l "/dev/$VG_NAME/$BOOT_LV" >/dev/null 2>&1; then
|
|
||||||
error "Boot filesystem not properly created"
|
|
||||||
filesystems_ok=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check EFI bootloader
|
|
||||||
local bootloader_ok=true
|
|
||||||
if [ ! -f "$EXTERNAL_ROOT_MOUNT/boot/efi/EFI/Ubuntu/grubx64.efi" ]; then
|
|
||||||
error "GRUB EFI bootloader not found"
|
|
||||||
bootloader_ok=false
|
|
||||||
fi
|
|
||||||
if [ ! -f "$EXTERNAL_ROOT_MOUNT/boot/grub/grub.cfg" ]; then
|
|
||||||
error "GRUB configuration not found"
|
|
||||||
bootloader_ok=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check fstab
|
|
||||||
local fstab_ok=true
|
|
||||||
if ! grep -q "UUID.*ext4" "$EXTERNAL_ROOT_MOUNT/etc/fstab"; then
|
|
||||||
error "fstab not properly configured"
|
|
||||||
fstab_ok=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
if [ "$volumes_ok" = true ] && [ "$filesystems_ok" = true ] && [ "$bootloader_ok" = true ] && [ "$fstab_ok" = true ]; then
|
|
||||||
success "Migration validation passed - all components properly configured"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
error "Migration validation failed - some components need attention"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
log "Creating LVM snapshot backup script..."
|
log "Creating LVM snapshot backup script..."
|
||||||
|
|
||||||
cat > "$EXTERNAL_ROOT_MOUNT/usr/local/bin/lvm-snapshot-backup.sh" << 'EOF'
|
cat > "$EXTERNAL_ROOT_MOUNT/usr/local/bin/lvm-snapshot-backup.sh" << 'EOF'
|
||||||
@@ -925,8 +770,8 @@ create_snapshots() {
|
|||||||
echo "Creating LVM snapshots..."
|
echo "Creating LVM snapshots..."
|
||||||
|
|
||||||
# Create snapshots
|
# Create snapshots
|
||||||
lvcreate --yes -L "$SNAPSHOT_SIZE" -s -n root-snapshot "/dev/$VG_NAME/root"
|
lvcreate -L "$SNAPSHOT_SIZE" -s -n root-snapshot "/dev/$VG_NAME/root"
|
||||||
lvcreate --yes -L "$SNAPSHOT_SIZE" -s -n home-snapshot "/dev/$VG_NAME/home"
|
lvcreate -L "$SNAPSHOT_SIZE" -s -n home-snapshot "/dev/$VG_NAME/home"
|
||||||
|
|
||||||
echo "Snapshots created successfully"
|
echo "Snapshots created successfully"
|
||||||
echo "root-snapshot: /dev/$VG_NAME/root-snapshot"
|
echo "root-snapshot: /dev/$VG_NAME/root-snapshot"
|
||||||
@@ -982,6 +827,54 @@ EOF
|
|||||||
success "LVM snapshot backup script created"
|
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() {
|
cleanup() {
|
||||||
log "Cleaning up..."
|
log "Cleaning up..."
|
||||||
|
|
||||||
@@ -1019,14 +912,6 @@ main() {
|
|||||||
detect_drives
|
detect_drives
|
||||||
analyze_internal_system
|
analyze_internal_system
|
||||||
|
|
||||||
# FORCE CORRECT SIZES - Override any previous calculations based on known source layout
|
|
||||||
log "Applying size corrections based on detected source partitions..."
|
|
||||||
ROOT_SIZE="58G" # Match internal nvme0n1p1 (58.6G)
|
|
||||||
HOME_SIZE="400G" # Fit encrypted home (411G total, 314G used) in available space
|
|
||||||
SWAP_SIZE="16G" # Standard swap size
|
|
||||||
BOOT_SIZE="2G" # Standard boot size
|
|
||||||
log "Corrected sizes: ROOT=$ROOT_SIZE, HOME=$HOME_SIZE, SWAP=$SWAP_SIZE, BOOT=$BOOT_SIZE"
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Migration Summary:"
|
echo "Migration Summary:"
|
||||||
echo " Source: $INTERNAL_DRIVE (non-LVM system)"
|
echo " Source: $INTERNAL_DRIVE (non-LVM system)"
|
||||||
@@ -1047,37 +932,35 @@ main() {
|
|||||||
copy_system_data
|
copy_system_data
|
||||||
update_system_configuration
|
update_system_configuration
|
||||||
install_bootloader
|
install_bootloader
|
||||||
validate_migration
|
|
||||||
create_lvm_snapshot_script
|
create_lvm_snapshot_script
|
||||||
|
repair_grub_bootloader
|
||||||
cleanup
|
cleanup
|
||||||
|
|
||||||
success "Migration completed successfully!"
|
success "Complete LVM migration with GRUB repair finished successfully!"
|
||||||
echo
|
echo
|
||||||
echo -e "${GREEN}✅ MIGRATION COMPLETE - Your system is ready to boot!${NC}"
|
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
|
||||||
echo -e "${GREEN}What was accomplished:${NC}"
|
echo -e "${BLUE}Next steps:${NC}"
|
||||||
echo "• ✅ LVM layout created with proper volume sizes"
|
echo "1. Reboot your system"
|
||||||
echo "• ✅ All system data copied (root, home, boot)"
|
echo "2. Enter BIOS/UEFI settings and configure:"
|
||||||
echo "• ✅ fstab updated with LVM UUIDs"
|
echo " • Set external M.2 as first boot device"
|
||||||
echo "• ✅ GRUB bootloader installed on external M.2"
|
echo " • Disable Secure Boot (if enabled)"
|
||||||
echo "• ✅ initramfs updated for LVM support"
|
echo " • Ensure UEFI mode is enabled"
|
||||||
echo "• ✅ EFI bootloader configured"
|
echo "3. Look for 'debian' entry in boot menu"
|
||||||
|
echo "4. Boot from external drive - should work without reset loops!"
|
||||||
echo
|
echo
|
||||||
echo -e "${GREEN}Next steps:${NC}"
|
echo -e "${GREEN}Your new LVM system features:${NC}"
|
||||||
echo "1. 🔌 Keep the external M.2 drive connected"
|
echo "• Instant snapshots: sudo /usr/local/bin/lvm-snapshot-backup.sh backup"
|
||||||
echo "2. 🔄 Reboot your system"
|
echo "• Dynamic volume resizing"
|
||||||
echo "3. ⚡ Enter BIOS/UEFI boot menu (F12/F8/ESC during startup)"
|
echo "• Advanced backup strategies with rollback"
|
||||||
echo "4. 🎯 Select the external USB/M.2 drive to boot"
|
echo "• Original internal drive unchanged as fallback"
|
||||||
echo "5. 🚀 Your system should boot normally with LVM!"
|
|
||||||
echo
|
echo
|
||||||
echo -e "${YELLOW}LVM Benefits now available:${NC}"
|
echo -e "${YELLOW}🎉 One-button migration completed successfully!${NC}"
|
||||||
echo "• 📸 Create instant snapshots: sudo /usr/local/bin/lvm-snapshot-backup.sh backup"
|
echo "Everything is configured and ready to boot!"
|
||||||
echo "• 📏 Resize partitions dynamically: lvextend, lvreduce"
|
|
||||||
echo "• 🔄 Easy system rollback with snapshots"
|
|
||||||
echo "• 💾 Advanced backup strategies with consistent snapshots"
|
|
||||||
echo
|
|
||||||
echo -e "${CYAN}Safety note:${NC}"
|
|
||||||
echo "Your original internal drive is completely unchanged and serves as a fallback."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Trap to ensure cleanup on exit
|
# Trap to ensure cleanup on exit
|
||||||
90
old_scripts/migration_completion_summary.sh
Normal file
90
old_scripts/migration_completion_summary.sh
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Migration Completion Summary
|
||||||
|
# ============================
|
||||||
|
#
|
||||||
|
# This script documents the successful completion of the LVM snapshot-based migration
|
||||||
|
# from external M.2 SSD to internal NVMe drive with LUKS encryption.
|
||||||
|
|
||||||
|
echo "=== MIGRATION COMPLETION SUMMARY ==="
|
||||||
|
echo "Date: $(date)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "✅ MIGRATION STATUS: COMPLETE AND SUCCESSFUL"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== SYSTEM CONFIGURATION ==="
|
||||||
|
echo "External M.2 SSD (/dev/sda):"
|
||||||
|
echo " - Volume Group: migration-vg"
|
||||||
|
echo " - Root LV: migration-root (50GB)"
|
||||||
|
echo " - Home LV: migration-home (1.3TB)"
|
||||||
|
echo " - EFI Partition: /dev/sda1 (512MB, bootable)"
|
||||||
|
echo " - Status: ✅ Fully functional and bootable"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Internal NVMe (/dev/nvme0n1):"
|
||||||
|
echo " - Volume Group: internal-vg"
|
||||||
|
echo " - Root LV: internal-root (50GB)"
|
||||||
|
echo " - Home LV: internal-home (1.3TB, LUKS encrypted)"
|
||||||
|
echo " - EFI Partition: /dev/nvme0n1p1 (1GB, bootable)"
|
||||||
|
echo " - Status: ✅ Fully configured with LVM+LUKS, bootable"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== DATA TRANSFER RESULTS ==="
|
||||||
|
echo "✅ Snapshot-based migration completed successfully"
|
||||||
|
echo "✅ 342GB of data transferred via LVM snapshots"
|
||||||
|
echo "✅ All partitions migrated (root + home + boot)"
|
||||||
|
echo "✅ LUKS encryption enabled on home partition"
|
||||||
|
echo "✅ System configurations updated (/etc/fstab, /etc/crypttab)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== BOOT CONFIGURATION ==="
|
||||||
|
echo "✅ GRUB installed on internal drive"
|
||||||
|
echo "✅ EFI boot entry created for internal drive"
|
||||||
|
echo "✅ Boot loader configurations updated"
|
||||||
|
echo "✅ Both drives remain bootable"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== CURRENT SYSTEM STATE ==="
|
||||||
|
echo "Currently booted from: External M.2 SSD"
|
||||||
|
echo "Root filesystem: $(findmnt -n -o SOURCE /)"
|
||||||
|
echo "Home filesystem: $(findmnt -n -o SOURCE /home)"
|
||||||
|
echo "Boot filesystem: $(findmnt -n -o SOURCE /boot)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== VERIFICATION COMMANDS ==="
|
||||||
|
echo "Check external drive LVM:"
|
||||||
|
echo " sudo lvs migration-vg"
|
||||||
|
echo " sudo pvs | grep sda"
|
||||||
|
echo
|
||||||
|
echo "Check internal drive LVM:"
|
||||||
|
echo " sudo lvs internal-vg"
|
||||||
|
echo " sudo pvs | grep nvme"
|
||||||
|
echo
|
||||||
|
echo "Check LUKS encryption:"
|
||||||
|
echo " sudo cryptsetup status luks-home-internal"
|
||||||
|
echo
|
||||||
|
echo "Check EFI boot entries:"
|
||||||
|
echo " efibootmgr -v | grep -E '(Internal-LVM|USB HDD)'"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== NEXT STEPS ==="
|
||||||
|
echo "1. Test boot from internal drive:"
|
||||||
|
echo " - Reboot and select 'Internal-LVM' from EFI boot menu"
|
||||||
|
echo " - Verify LUKS password prompt works"
|
||||||
|
echo " - Confirm all data is accessible"
|
||||||
|
echo
|
||||||
|
echo "2. Optional: Set internal drive as default boot option:"
|
||||||
|
echo " sudo efibootmgr -o 0001,001C,001B,0000,..."
|
||||||
|
echo
|
||||||
|
echo "3. Keep external drive as backup/recovery option"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== BACKUP SAFETY ==="
|
||||||
|
echo "✅ External M.2 contains complete working system backup"
|
||||||
|
echo "✅ Both drives are independently bootable"
|
||||||
|
echo "✅ No data loss occurred during migration"
|
||||||
|
echo "✅ LUKS encryption provides additional security"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Migration completed successfully! 🎉"
|
||||||
75
old_scripts/preview_migration.sh
Executable file
75
old_scripts/preview_migration.sh
Executable file
@@ -0,0 +1,75 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LVM Migration Preview Script
|
||||||
|
# Shows current status and what the migration will do
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
echo -e "${BLUE}=== LVM Migration Preview ===${NC}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Current Boot Status:${NC}"
|
||||||
|
echo "Root mounted from: $(findmnt -n -o SOURCE /)"
|
||||||
|
echo "Boot mounted from: $(findmnt -n -o SOURCE /boot)"
|
||||||
|
echo "Home mounted from: $(findmnt -n -o SOURCE /home)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Current LVM Structure (External M.2):${NC}"
|
||||||
|
sudo vgs migration-vg 2>/dev/null || echo "No VG found"
|
||||||
|
echo
|
||||||
|
sudo lvs migration-vg 2>/dev/null || echo "No LVs found"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Current Drive Layout:${NC}"
|
||||||
|
echo "External M.2 (sda):"
|
||||||
|
lsblk /dev/sda
|
||||||
|
echo
|
||||||
|
echo "Internal NVMe (nvme0n1):"
|
||||||
|
lsblk /dev/nvme0n1
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Available Space Analysis:${NC}"
|
||||||
|
echo "External VG size: $(sudo vgs --noheadings --units g -o vg_size migration-vg | tr -d ' ')B"
|
||||||
|
echo "External VG free: $(sudo vgs --noheadings --units g -o vg_free migration-vg | tr -d ' ')B"
|
||||||
|
echo "Internal drive size: $(lsblk -b -n -o SIZE /dev/nvme0n1 | awk '{printf "%.1fGB", $1/1024/1024/1024}')"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Migration Plan:${NC}"
|
||||||
|
echo "1. ✅ Wipe internal NVMe drive completely"
|
||||||
|
echo "2. ✅ Create new GPT partition table"
|
||||||
|
echo "3. ✅ Create EFI boot partition (512MB)"
|
||||||
|
echo "4. ✅ Create LVM partition (remaining space)"
|
||||||
|
echo "5. ✅ Set up LVM with volume group 'internal-vg'"
|
||||||
|
echo "6. ✅ Create logical volumes matching current structure"
|
||||||
|
echo "7. ✅ Copy all data from external to internal"
|
||||||
|
echo "8. ✅ Configure GRUB for LVM boot"
|
||||||
|
echo "9. ✅ Set up LVM snapshot capabilities"
|
||||||
|
echo "10. ✅ Reserve 20% space for snapshots"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${RED}⚠️ WARNINGS:${NC}"
|
||||||
|
echo "• This will COMPLETELY WIPE the internal NVMe drive"
|
||||||
|
echo "• All current data on internal drive will be lost"
|
||||||
|
echo "• Make sure you're booted from external M.2 (verified above)"
|
||||||
|
echo "• Ensure external M.2 LED is active/blinking"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Snapshot Features After Migration:${NC}"
|
||||||
|
echo "• lvm-snapshot-manager create - Create system snapshots"
|
||||||
|
echo "• lvm-snapshot-manager list - List existing snapshots"
|
||||||
|
echo "• lvm-snapshot-manager remove - Remove snapshots"
|
||||||
|
echo "• lvm-snapshot-manager merge - Restore from snapshot"
|
||||||
|
echo "• snapshot-backup - Backup using snapshots"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${BLUE}Ready to proceed?${NC}"
|
||||||
|
echo "Run: sudo ./migrate_lvm_to_internal.sh"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}Estimated time: 2-4 hours depending on data size${NC}"
|
||||||
104
old_scripts/preview_snapshot_migration.sh
Executable file
104
old_scripts/preview_snapshot_migration.sh
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Snapshot Migration Preview Script
|
||||||
|
# Shows what the snapshot-based migration will do
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
echo -e "${BLUE}=== Snapshot-Based LVM Migration Preview ===${NC}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Current System Status:${NC}"
|
||||||
|
echo "✅ Booted from: $(findmnt -n -o SOURCE /)"
|
||||||
|
echo "✅ Volume Group: migration-vg"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Current Logical Volumes:${NC}"
|
||||||
|
sudo lvs migration-vg --units g -o lv_name,lv_size,data_percent 2>/dev/null
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Available Space for Snapshots:${NC}"
|
||||||
|
free_space=$(sudo vgs --noheadings --units m --nosuffix -o vg_free migration-vg | tr -d ' ' | tr ',' '.')
|
||||||
|
if command -v bc >/dev/null 2>&1; then
|
||||||
|
snapshot_size_per_lv=$(echo "$free_space * 0.8 / 4" | bc 2>/dev/null | cut -d. -f1)
|
||||||
|
else
|
||||||
|
snapshot_size_per_lv=$((${free_space%.*} * 80 / 100 / 4))
|
||||||
|
fi
|
||||||
|
echo "Free space: ${free_space}MB"
|
||||||
|
echo "Snapshot size per LV: ${snapshot_size_per_lv}MB"
|
||||||
|
|
||||||
|
if (( snapshot_size_per_lv < 100 )); then
|
||||||
|
echo -e "${RED}⚠️ WARNING: Limited space for snapshots!${NC}"
|
||||||
|
echo "Consider freeing up space or expanding the volume group"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}✅ Sufficient space for snapshots${NC}"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Target Drive Analysis:${NC}"
|
||||||
|
echo "Internal NVMe (nvme0n1):"
|
||||||
|
lsblk /dev/nvme0n1
|
||||||
|
echo "Size: $(lsblk -b -n -o SIZE /dev/nvme0n1 | awk '{printf "%.1fGB", $1/1024/1024/1024}')"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Snapshot Migration Process:${NC}"
|
||||||
|
echo "1. 📸 Create temporary snapshots of ALL logical volumes:"
|
||||||
|
echo " • root (${snapshot_size_per_lv}MB snapshot)"
|
||||||
|
echo " • home (${snapshot_size_per_lv}MB snapshot)"
|
||||||
|
echo " • boot (${snapshot_size_per_lv}MB snapshot)"
|
||||||
|
echo " • swap (${snapshot_size_per_lv}MB snapshot)"
|
||||||
|
echo
|
||||||
|
echo "2. 🔧 Prepare internal drive:"
|
||||||
|
echo " • Copy partition table from external M.2"
|
||||||
|
echo " • Create LVM physical volume"
|
||||||
|
echo " • Create volume group 'internal-vg'"
|
||||||
|
echo " • Create logical volumes with exact same sizes"
|
||||||
|
echo
|
||||||
|
echo "3. 📋 Transfer data using block-level copy:"
|
||||||
|
echo " • Use dd to copy each snapshot to internal LV"
|
||||||
|
echo " • Process one LV at a time to manage space"
|
||||||
|
echo " • Remove each snapshot after successful copy"
|
||||||
|
echo
|
||||||
|
echo "4. ⚙️ Configure boot:"
|
||||||
|
echo " • Update fstab for new volume group"
|
||||||
|
echo " • Install GRUB on internal drive"
|
||||||
|
echo " • Update initramfs for LVM"
|
||||||
|
echo
|
||||||
|
echo "5. 🛠️ Setup snapshot tools:"
|
||||||
|
echo " • Install lvm-snapshot-manager"
|
||||||
|
echo " • Reserve space for future snapshots"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${RED}⚠️ CRITICAL WARNINGS:${NC}"
|
||||||
|
echo "• Internal NVMe will be COMPLETELY WIPED"
|
||||||
|
echo "• This creates an EXACT copy including all current data"
|
||||||
|
echo "• Process takes 1-3 hours depending on data size"
|
||||||
|
echo "• Snapshots use limited available space"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Advantages of Snapshot Method:${NC}"
|
||||||
|
echo "✅ Exact bit-for-bit copy of live system"
|
||||||
|
echo "✅ No filesystem corruption risks"
|
||||||
|
echo "✅ Can capture running system state"
|
||||||
|
echo "✅ Uses LVM native capabilities"
|
||||||
|
echo "✅ Block-level transfer (faster than file copy)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${GREEN}Post-Migration Capabilities:${NC}"
|
||||||
|
echo "• sudo lvm-snapshot-manager create - Create system snapshots"
|
||||||
|
echo "• sudo lvm-snapshot-manager list - Show snapshots"
|
||||||
|
echo "• sudo lvm-snapshot-manager remove - Clean up snapshots"
|
||||||
|
echo "• sudo lvm-snapshot-manager merge - Restore from snapshot"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e "${BLUE}Ready to proceed with snapshot migration?${NC}"
|
||||||
|
echo "Run: sudo ./snapshot_migrate_to_internal.sh"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}Estimated time: 1-3 hours${NC}"
|
||||||
241
old_scripts/resume_snapshot_migration.sh
Executable file
241
old_scripts/resume_snapshot_migration.sh
Executable file
@@ -0,0 +1,241 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Resume snapshot migration - continue from data transfer phase
|
||||||
|
# Since internal LVM structure already exists
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 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
|
||||||
|
SOURCE_VG="migration-vg"
|
||||||
|
TARGET_VG="internal-vg"
|
||||||
|
INTERNAL_DRIVE="/dev/nvme0n1"
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_FILE="/var/log/lvm_snapshot_migration_resume.log"
|
||||||
|
exec 1> >(tee -a "$LOG_FILE")
|
||||||
|
exec 2> >(tee -a "$LOG_FILE" >&2)
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Transfer data with snapshots (resume from where we left off)
|
||||||
|
transfer_with_snapshots() {
|
||||||
|
log_step "Resuming data transfer using snapshots..."
|
||||||
|
|
||||||
|
# Calculate optimal snapshot size (use available free space)
|
||||||
|
free_space=$(vgs --noheadings --units m --nosuffix -o vg_free "$SOURCE_VG" | tr -d ' ' | tr ',' '.')
|
||||||
|
# Use 80% of free space for snapshots, divided by number of LVs
|
||||||
|
snapshot_size=$(echo "$free_space * 0.8 / 4" | bc | cut -d. -f1)
|
||||||
|
|
||||||
|
if (( snapshot_size < 100 )); then
|
||||||
|
log_error "Not enough free space for snapshots. Need at least 400MB free."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Using ${snapshot_size}MB for each snapshot"
|
||||||
|
|
||||||
|
# Process each logical volume
|
||||||
|
for lv in root boot home swap; do
|
||||||
|
log_step "Processing $lv..."
|
||||||
|
|
||||||
|
# Create snapshot
|
||||||
|
log_info "Creating snapshot of $lv..."
|
||||||
|
if ! lvcreate -L "${snapshot_size}M" -s -n "${lv}_snapshot" "$SOURCE_VG/$lv"; then
|
||||||
|
log_error "Failed to create snapshot for $lv"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy data using dd with progress
|
||||||
|
log_info "Copying $lv data to internal drive..."
|
||||||
|
if [[ "$lv" == "swap" ]]; then
|
||||||
|
# For swap, just copy the LV structure
|
||||||
|
dd if="/dev/$SOURCE_VG/${lv}_snapshot" of="/dev/$TARGET_VG/$lv" bs=1M status=progress
|
||||||
|
else
|
||||||
|
# For filesystems, use dd for exact copy
|
||||||
|
dd if="/dev/$SOURCE_VG/${lv}_snapshot" of="/dev/$TARGET_VG/$lv" bs=1M status=progress
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove snapshot to free space for next one
|
||||||
|
log_info "Removing snapshot of $lv..."
|
||||||
|
lvremove -f "$SOURCE_VG/${lv}_snapshot"
|
||||||
|
|
||||||
|
log_info "Completed transfer of $lv"
|
||||||
|
done
|
||||||
|
|
||||||
|
log_info "All data transferred successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup boot for internal drive
|
||||||
|
configure_internal_boot() {
|
||||||
|
log_step "Configuring boot for internal drive..."
|
||||||
|
|
||||||
|
# Format and mount EFI partition
|
||||||
|
mkfs.fat -F32 "${INTERNAL_DRIVE}p1" || true
|
||||||
|
mkdir -p /mnt/internal_efi
|
||||||
|
mount "${INTERNAL_DRIVE}p1" /mnt/internal_efi
|
||||||
|
|
||||||
|
# Mount internal filesystems
|
||||||
|
mkdir -p /mnt/internal_root /mnt/internal_boot /mnt/internal_home
|
||||||
|
mount "/dev/$TARGET_VG/root" /mnt/internal_root
|
||||||
|
mount "/dev/$TARGET_VG/boot" /mnt/internal_boot
|
||||||
|
mount "/dev/$TARGET_VG/home" /mnt/internal_home
|
||||||
|
|
||||||
|
# Mount EFI in the target system
|
||||||
|
mkdir -p /mnt/internal_root/boot/efi
|
||||||
|
mount "${INTERNAL_DRIVE}p1" /mnt/internal_root/boot/efi
|
||||||
|
|
||||||
|
# Update fstab for new VG
|
||||||
|
log_info "Updating fstab..."
|
||||||
|
cat > /mnt/internal_root/etc/fstab << EOF
|
||||||
|
# Internal LVM Configuration
|
||||||
|
/dev/$TARGET_VG/root / ext4 defaults 0 1
|
||||||
|
/dev/$TARGET_VG/boot /boot ext4 defaults 0 2
|
||||||
|
/dev/$TARGET_VG/home /home ext4 defaults 0 2
|
||||||
|
/dev/$TARGET_VG/swap none swap sw 0 0
|
||||||
|
${INTERNAL_DRIVE}p1 /boot/efi vfat umask=0077 0 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Prepare chroot environment
|
||||||
|
mount --bind /dev /mnt/internal_root/dev
|
||||||
|
mount --bind /proc /mnt/internal_root/proc
|
||||||
|
mount --bind /sys /mnt/internal_root/sys
|
||||||
|
mount --bind /run /mnt/internal_root/run
|
||||||
|
|
||||||
|
# Update initramfs and install GRUB
|
||||||
|
log_info "Updating initramfs and GRUB..."
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "update-initramfs -u -k all"
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Internal-LVM"
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "update-grub"
|
||||||
|
|
||||||
|
# Clean up mounts
|
||||||
|
umount /mnt/internal_root/dev /mnt/internal_root/proc /mnt/internal_root/sys /mnt/internal_root/run
|
||||||
|
umount /mnt/internal_root/boot/efi
|
||||||
|
umount /mnt/internal_efi /mnt/internal_root /mnt/internal_boot /mnt/internal_home
|
||||||
|
|
||||||
|
log_info "Boot configuration completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup snapshot tools
|
||||||
|
setup_snapshot_tools() {
|
||||||
|
log_step "Setting up snapshot tools on internal drive..."
|
||||||
|
|
||||||
|
# Remount for tool installation
|
||||||
|
mount "/dev/$TARGET_VG/root" /mnt/internal_root
|
||||||
|
|
||||||
|
# Create snapshot management script
|
||||||
|
cat > /mnt/internal_root/usr/local/bin/lvm-snapshot-manager << 'EOFSCRIPT'
|
||||||
|
#!/bin/bash
|
||||||
|
# LVM Snapshot Manager for Internal Drive
|
||||||
|
|
||||||
|
VG_NAME="internal-vg"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
create)
|
||||||
|
echo "Creating LVM snapshots..."
|
||||||
|
# Calculate available space for snapshots
|
||||||
|
free_space=$(vgs --noheadings --units g --nosuffix -o vg_free "$VG_NAME" | tr -d ' ')
|
||||||
|
snapshot_size=$(echo "$free_space / 4" | bc)G
|
||||||
|
|
||||||
|
lvcreate -L "$snapshot_size" -s -n root_backup "$VG_NAME/root"
|
||||||
|
lvcreate -L "$snapshot_size" -s -n home_backup "$VG_NAME/home"
|
||||||
|
lvcreate -L "$snapshot_size" -s -n boot_backup "$VG_NAME/boot"
|
||||||
|
echo "Snapshots created with size: $snapshot_size each"
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
echo "Removing LVM snapshots..."
|
||||||
|
lvremove -f "$VG_NAME/root_backup" 2>/dev/null || true
|
||||||
|
lvremove -f "$VG_NAME/home_backup" 2>/dev/null || true
|
||||||
|
lvremove -f "$VG_NAME/boot_backup" 2>/dev/null || true
|
||||||
|
echo "Snapshots removed"
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
echo "Current snapshots:"
|
||||||
|
lvs "$VG_NAME" | grep backup || echo "No snapshots found"
|
||||||
|
;;
|
||||||
|
merge)
|
||||||
|
if [[ -z "$2" ]]; then
|
||||||
|
echo "Usage: $0 merge <snapshot_name>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Merging snapshot $2..."
|
||||||
|
lvconvert --merge "$VG_NAME/$2"
|
||||||
|
echo "Snapshot merge initiated (reboot required to complete)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {create|remove|list|merge <snapshot_name>}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOFSCRIPT
|
||||||
|
|
||||||
|
chmod +x /mnt/internal_root/usr/local/bin/lvm-snapshot-manager
|
||||||
|
|
||||||
|
umount /mnt/internal_root
|
||||||
|
|
||||||
|
log_info "Snapshot tools installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup function
|
||||||
|
cleanup() {
|
||||||
|
log_info "Cleaning up..."
|
||||||
|
|
||||||
|
# Remove any remaining snapshots
|
||||||
|
for snap in $(lvs --noheadings -o lv_name "$SOURCE_VG" 2>/dev/null | grep snapshot || true); do
|
||||||
|
lvremove -f "$SOURCE_VG/$snap" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Unmount any remaining mounts
|
||||||
|
for mount in /mnt/internal_*; do
|
||||||
|
umount "$mount" 2>/dev/null || true
|
||||||
|
rmdir "$mount" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main function
|
||||||
|
main() {
|
||||||
|
log_info "Resuming snapshot-based LVM migration"
|
||||||
|
log_info "Internal LVM structure already exists, continuing with data transfer..."
|
||||||
|
|
||||||
|
transfer_with_snapshots
|
||||||
|
configure_internal_boot
|
||||||
|
setup_snapshot_tools
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
log_info "Migration completed successfully!"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}SUCCESS!${NC} Snapshot-based LVM migration completed"
|
||||||
|
echo
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Reboot and select internal drive in BIOS/UEFI"
|
||||||
|
echo "2. Verify all systems working from internal LVM"
|
||||||
|
echo "3. Test snapshot functionality: sudo lvm-snapshot-manager create"
|
||||||
|
echo "4. External M.2 can now be used as backup drive"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle interruption
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log_error "This script must be run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run migration
|
||||||
|
main "$@"
|
||||||
158
old_scripts/setup_luks_internal.sh
Executable file
158
old_scripts/setup_luks_internal.sh
Executable file
@@ -0,0 +1,158 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to set up LUKS encryption for home partition on internal drive
|
||||||
|
# This will encrypt the home partition in-place
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log_error "This script must be run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}=== LUKS Encryption Setup for Internal Drive ===${NC}"
|
||||||
|
echo
|
||||||
|
echo "This will set up LUKS encryption for your home partition on the internal drive."
|
||||||
|
echo "The process will:"
|
||||||
|
echo "1. Create a backup image of the current home data"
|
||||||
|
echo "2. Recreate the home LV with LUKS encryption"
|
||||||
|
echo "3. Restore the data to the encrypted volume"
|
||||||
|
echo "4. Update system configuration"
|
||||||
|
echo
|
||||||
|
echo -e "${RED}WARNING: This process requires sufficient free space for backup!${NC}"
|
||||||
|
|
||||||
|
# Check available space
|
||||||
|
free_space=$(vgs --noheadings --units g --nosuffix -o vg_free internal-vg | tr -d ' ' | tr ',' '.')
|
||||||
|
home_size=$(lvs --noheadings --units g --nosuffix -o lv_size internal-vg/home | tr -d ' ' | tr ',' '.')
|
||||||
|
|
||||||
|
echo "Home partition size: ${home_size}GB"
|
||||||
|
echo "Available free space: ${free_space}GB"
|
||||||
|
|
||||||
|
if (( $(echo "$free_space < $home_size" | bc -l) )); then
|
||||||
|
log_error "Not enough free space for backup. Need ${home_size}GB free space."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "Continue with LUKS encryption setup? (yes/no): " confirm
|
||||||
|
if [[ "$confirm" != "yes" ]]; then
|
||||||
|
log_info "Operation cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_LV="home_backup_temp"
|
||||||
|
MOUNT_POINT="/mnt/luks_setup"
|
||||||
|
|
||||||
|
log_step "Creating backup of home data..."
|
||||||
|
|
||||||
|
# Create backup LV
|
||||||
|
lvcreate -L "${home_size}G" -n "$BACKUP_LV" internal-vg
|
||||||
|
|
||||||
|
# Copy home data to backup
|
||||||
|
log_info "Copying home data to backup volume..."
|
||||||
|
dd if=/dev/internal-vg/home of="/dev/internal-vg/$BACKUP_LV" bs=1M status=progress
|
||||||
|
|
||||||
|
log_step "Removing and recreating home LV..."
|
||||||
|
|
||||||
|
# Remove the current home LV
|
||||||
|
lvremove -f internal-vg/home
|
||||||
|
|
||||||
|
# Create new home LV
|
||||||
|
lvcreate -L "${home_size}G" -n home internal-vg
|
||||||
|
|
||||||
|
log_step "Setting up LUKS encryption..."
|
||||||
|
|
||||||
|
# Setup LUKS on the new LV
|
||||||
|
echo "Please enter your desired LUKS passphrase:"
|
||||||
|
cryptsetup luksFormat /dev/internal-vg/home
|
||||||
|
|
||||||
|
echo "Please enter your LUKS passphrase again to open the volume:"
|
||||||
|
cryptsetup open /dev/internal-vg/home luks-home-internal
|
||||||
|
|
||||||
|
# Format the encrypted volume
|
||||||
|
mkfs.ext4 -L home /dev/mapper/luks-home-internal
|
||||||
|
|
||||||
|
log_step "Restoring home data..."
|
||||||
|
|
||||||
|
# Mount backup and encrypted volumes
|
||||||
|
mkdir -p "$MOUNT_POINT/backup" "$MOUNT_POINT/encrypted"
|
||||||
|
mount "/dev/internal-vg/$BACKUP_LV" "$MOUNT_POINT/backup"
|
||||||
|
mount /dev/mapper/luks-home-internal "$MOUNT_POINT/encrypted"
|
||||||
|
|
||||||
|
# Copy data back
|
||||||
|
log_info "Copying data from backup to encrypted volume..."
|
||||||
|
rsync -avHAXS --progress "$MOUNT_POINT/backup/" "$MOUNT_POINT/encrypted/"
|
||||||
|
|
||||||
|
# Clean up mounts
|
||||||
|
umount "$MOUNT_POINT/backup" "$MOUNT_POINT/encrypted"
|
||||||
|
cryptsetup close luks-home-internal
|
||||||
|
|
||||||
|
# Remove backup LV
|
||||||
|
lvremove -f "internal-vg/$BACKUP_LV"
|
||||||
|
|
||||||
|
log_step "Updating system configuration..."
|
||||||
|
|
||||||
|
# Get the UUID of the LUKS device
|
||||||
|
LUKS_UUID=$(cryptsetup luksUUID /dev/internal-vg/home)
|
||||||
|
|
||||||
|
# Mount the internal root to update configuration
|
||||||
|
mount /dev/internal-vg/root "$MOUNT_POINT"
|
||||||
|
|
||||||
|
# Update /etc/crypttab
|
||||||
|
echo "luks-home-internal UUID=$LUKS_UUID none luks" >> "$MOUNT_POINT/etc/crypttab"
|
||||||
|
|
||||||
|
# Update /etc/fstab
|
||||||
|
cat > "$MOUNT_POINT/etc/fstab" << EOF
|
||||||
|
# Internal LVM Configuration with LUKS
|
||||||
|
/dev/internal-vg/root / ext4 defaults 0 1
|
||||||
|
/dev/internal-vg/boot /boot ext4 defaults 0 2
|
||||||
|
/dev/mapper/luks-home-internal /home ext4 defaults 0 2
|
||||||
|
/dev/internal-vg/swap none swap sw 0 0
|
||||||
|
/dev/nvme0n1p1 /boot/efi vfat umask=0077 0 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Update initramfs to include LUKS support
|
||||||
|
mount --bind /dev "$MOUNT_POINT/dev"
|
||||||
|
mount --bind /proc "$MOUNT_POINT/proc"
|
||||||
|
mount --bind /sys "$MOUNT_POINT/sys"
|
||||||
|
mount --bind /run "$MOUNT_POINT/run"
|
||||||
|
|
||||||
|
chroot "$MOUNT_POINT" /bin/bash -c "update-initramfs -u -k all"
|
||||||
|
chroot "$MOUNT_POINT" /bin/bash -c "update-grub"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
umount "$MOUNT_POINT/dev" "$MOUNT_POINT/proc" "$MOUNT_POINT/sys" "$MOUNT_POINT/run"
|
||||||
|
umount "$MOUNT_POINT"
|
||||||
|
|
||||||
|
log_info "LUKS encryption setup completed successfully!"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}SUCCESS!${NC} Home partition is now encrypted with LUKS"
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Reboot from the internal drive"
|
||||||
|
echo "2. You will be prompted for the LUKS passphrase during boot"
|
||||||
|
echo "3. Verify that everything works correctly"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}Important:${NC} Remember your LUKS passphrase! Without it, your home data will be inaccessible."
|
||||||
152
old_scripts/setup_luks_simple.sh
Executable file
152
old_scripts/setup_luks_simple.sh
Executable file
@@ -0,0 +1,152 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simplified LUKS Setup Script
|
||||||
|
# Wipes internal home, creates LUKS encryption, and restores from external drive
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log_error "This script must be run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}=== Simple LUKS Encryption Setup ===${NC}"
|
||||||
|
echo
|
||||||
|
echo "This will:"
|
||||||
|
echo "1. Remove the current home LV on internal drive"
|
||||||
|
echo "2. Create a new LUKS-encrypted home LV"
|
||||||
|
echo "3. Copy your home data directly from external M.2"
|
||||||
|
echo "4. Update system configuration"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}Source:${NC} External M.2 (/dev/migration-vg/home)"
|
||||||
|
echo -e "${YELLOW}Target:${NC} Internal NVMe (/dev/internal-vg/home) - WILL BE WIPED"
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Continue with LUKS encryption setup? (yes/no): " confirm
|
||||||
|
if [[ "$confirm" != "yes" ]]; then
|
||||||
|
log_info "Operation cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
MOUNT_POINT="/mnt/luks_setup"
|
||||||
|
EXTERNAL_HOME="/dev/migration-vg/home"
|
||||||
|
INTERNAL_VG="internal-vg"
|
||||||
|
|
||||||
|
log_step "Removing current internal home LV..."
|
||||||
|
|
||||||
|
# Remove the current home LV
|
||||||
|
lvremove -f "$INTERNAL_VG/home"
|
||||||
|
|
||||||
|
log_step "Creating new home LV..."
|
||||||
|
|
||||||
|
# Get the original home size from external drive
|
||||||
|
home_size=$(lvs --noheadings --units g --nosuffix -o lv_size migration-vg/home | tr -d ' ' | tr ',' '.')
|
||||||
|
|
||||||
|
# Create new home LV
|
||||||
|
lvcreate -L "${home_size}G" -n home "$INTERNAL_VG"
|
||||||
|
|
||||||
|
log_step "Setting up LUKS encryption..."
|
||||||
|
|
||||||
|
# Setup LUKS on the new LV
|
||||||
|
echo "Please enter your desired LUKS passphrase for home encryption:"
|
||||||
|
cryptsetup luksFormat "/dev/$INTERNAL_VG/home"
|
||||||
|
|
||||||
|
echo "Please enter your LUKS passphrase again to open the volume:"
|
||||||
|
cryptsetup open "/dev/$INTERNAL_VG/home" luks-home-internal
|
||||||
|
|
||||||
|
# Format the encrypted volume
|
||||||
|
mkfs.ext4 -L home /dev/mapper/luks-home-internal
|
||||||
|
|
||||||
|
log_step "Copying home data from external drive..."
|
||||||
|
|
||||||
|
# Mount source and target
|
||||||
|
mkdir -p "$MOUNT_POINT/external" "$MOUNT_POINT/encrypted"
|
||||||
|
mount "$EXTERNAL_HOME" "$MOUNT_POINT/external"
|
||||||
|
mount /dev/mapper/luks-home-internal "$MOUNT_POINT/encrypted"
|
||||||
|
|
||||||
|
# Copy data directly from external to encrypted volume
|
||||||
|
log_info "Copying ${home_size}GB of home data..."
|
||||||
|
rsync -avHAXS --progress "$MOUNT_POINT/external/" "$MOUNT_POINT/encrypted/"
|
||||||
|
|
||||||
|
# Clean up mounts
|
||||||
|
umount "$MOUNT_POINT/external" "$MOUNT_POINT/encrypted"
|
||||||
|
cryptsetup close luks-home-internal
|
||||||
|
|
||||||
|
log_step "Updating system configuration..."
|
||||||
|
|
||||||
|
# Get the UUID of the LUKS device
|
||||||
|
LUKS_UUID=$(cryptsetup luksUUID "/dev/$INTERNAL_VG/home")
|
||||||
|
|
||||||
|
# Mount the internal root to update configuration
|
||||||
|
mount "/dev/$INTERNAL_VG/root" "$MOUNT_POINT"
|
||||||
|
|
||||||
|
# Update /etc/crypttab
|
||||||
|
echo "luks-home-internal UUID=$LUKS_UUID none luks" >> "$MOUNT_POINT/etc/crypttab"
|
||||||
|
|
||||||
|
# Update /etc/fstab
|
||||||
|
cat > "$MOUNT_POINT/etc/fstab" << EOF
|
||||||
|
# Internal LVM Configuration with LUKS
|
||||||
|
/dev/$INTERNAL_VG/root / ext4 defaults 0 1
|
||||||
|
/dev/$INTERNAL_VG/boot /boot ext4 defaults 0 2
|
||||||
|
/dev/mapper/luks-home-internal /home ext4 defaults 0 2
|
||||||
|
/dev/$INTERNAL_VG/swap none swap sw 0 0
|
||||||
|
/dev/nvme0n1p1 /boot/efi vfat umask=0077 0 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Update initramfs and GRUB to include LUKS support
|
||||||
|
mount --bind /dev "$MOUNT_POINT/dev"
|
||||||
|
mount --bind /proc "$MOUNT_POINT/proc"
|
||||||
|
mount --bind /sys "$MOUNT_POINT/sys"
|
||||||
|
mount --bind /run "$MOUNT_POINT/run"
|
||||||
|
|
||||||
|
log_info "Updating initramfs for LUKS support..."
|
||||||
|
chroot "$MOUNT_POINT" /bin/bash -c "update-initramfs -u -k all"
|
||||||
|
|
||||||
|
log_info "Updating GRUB configuration..."
|
||||||
|
chroot "$MOUNT_POINT" /bin/bash -c "update-grub"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
umount "$MOUNT_POINT/dev" "$MOUNT_POINT/proc" "$MOUNT_POINT/sys" "$MOUNT_POINT/run"
|
||||||
|
umount "$MOUNT_POINT"
|
||||||
|
rmdir "$MOUNT_POINT/external" "$MOUNT_POINT/encrypted" "$MOUNT_POINT" 2>/dev/null || true
|
||||||
|
|
||||||
|
log_info "LUKS encryption setup completed successfully!"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}SUCCESS!${NC} Home partition is now encrypted with LUKS"
|
||||||
|
echo
|
||||||
|
echo "Configuration summary:"
|
||||||
|
echo "• LUKS UUID: $LUKS_UUID"
|
||||||
|
echo "• Encrypted device: /dev/mapper/luks-home-internal"
|
||||||
|
echo "• Mount point: /home"
|
||||||
|
echo "• Data copied from external M.2"
|
||||||
|
echo
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Reboot and select internal NVMe drive in BIOS"
|
||||||
|
echo "2. You will be prompted for LUKS passphrase during boot"
|
||||||
|
echo "3. Verify that all your home data is accessible"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}Important:${NC} Remember your LUKS passphrase! Without it, your home data will be inaccessible."
|
||||||
85
old_scripts/simple_grub_repair.sh
Executable file
85
old_scripts/simple_grub_repair.sh
Executable file
@@ -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!"
|
||||||
380
old_scripts/snapshot_migrate_to_internal.sh
Executable file
380
old_scripts/snapshot_migrate_to_internal.sh
Executable file
@@ -0,0 +1,380 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Snapshot-Based LVM Migration Script: External M.2 to Internal NVMe
|
||||||
|
# Creates snapshots of ALL partitions and transfers them to internal drive
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 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
|
||||||
|
EXTERNAL_DRIVE="/dev/sda"
|
||||||
|
INTERNAL_DRIVE="/dev/nvme0n1"
|
||||||
|
SOURCE_VG="migration-vg"
|
||||||
|
TARGET_VG="internal-vg"
|
||||||
|
BACKUP_DIR="/tmp/lvm_snapshot_migration"
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_FILE="/var/log/lvm_snapshot_migration.log"
|
||||||
|
exec 1> >(tee -a "$LOG_FILE")
|
||||||
|
exec 2> >(tee -a "$LOG_FILE" >&2)
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S'): $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Safety checks
|
||||||
|
safety_checks() {
|
||||||
|
log_step "Performing safety checks..."
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log_error "This script must be run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify we're booted from external drive
|
||||||
|
root_device=$(findmnt -n -o SOURCE /)
|
||||||
|
if [[ "$root_device" != "/dev/mapper/migration--vg-root" ]]; then
|
||||||
|
log_error "Not booted from external LVM! Current root: $root_device"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check drives exist
|
||||||
|
if [[ ! -b "$EXTERNAL_DRIVE" ]] || [[ ! -b "$INTERNAL_DRIVE" ]]; then
|
||||||
|
log_error "Required drives not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check LVM tools
|
||||||
|
if ! command -v lvcreate &> /dev/null; then
|
||||||
|
log_error "LVM tools not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Safety checks passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create backup directory and metadata
|
||||||
|
create_backup_metadata() {
|
||||||
|
log_step "Creating backup metadata..."
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Backup LVM metadata
|
||||||
|
vgcfgbackup -f "$BACKUP_DIR/vg_backup" "$SOURCE_VG"
|
||||||
|
|
||||||
|
# Save current LV information
|
||||||
|
lvs "$SOURCE_VG" > "$BACKUP_DIR/lv_info.txt"
|
||||||
|
vgs "$SOURCE_VG" > "$BACKUP_DIR/vg_info.txt"
|
||||||
|
pvs > "$BACKUP_DIR/pv_info.txt"
|
||||||
|
|
||||||
|
# Save partition information
|
||||||
|
lsblk > "$BACKUP_DIR/lsblk_before.txt"
|
||||||
|
|
||||||
|
log_info "Backup metadata created"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare internal drive with identical structure
|
||||||
|
prepare_internal_drive() {
|
||||||
|
log_step "Preparing internal drive..."
|
||||||
|
|
||||||
|
echo -e "${RED}WARNING: This will completely wipe $INTERNAL_DRIVE${NC}"
|
||||||
|
read -p "Type 'YES' to continue: " confirm
|
||||||
|
if [[ "$confirm" != "YES" ]]; then
|
||||||
|
log_error "Migration cancelled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up any existing mounts/LVM on internal drive
|
||||||
|
for mount in $(findmnt -n -o TARGET -S "$INTERNAL_DRIVE"* 2>/dev/null || true); do
|
||||||
|
umount "$mount" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Deactivate any LUKS devices on internal drive
|
||||||
|
for luks_dev in $(lsblk -o NAME,TYPE | grep crypt | awk '{print $1}' | grep -v "migration" || true); do
|
||||||
|
log_info "Closing LUKS device $luks_dev"
|
||||||
|
cryptsetup close "$luks_dev" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Deactivate any existing VGs on internal drive
|
||||||
|
for vg in $(vgs --noheadings -o vg_name 2>/dev/null | grep -v "$SOURCE_VG" || true); do
|
||||||
|
vgchange -an "$vg" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove any PVs on internal drive
|
||||||
|
for pv in $(pvs --noheadings -o pv_name 2>/dev/null | grep "$INTERNAL_DRIVE" || true); do
|
||||||
|
log_info "Removing PV $pv"
|
||||||
|
pvremove -ff "$pv" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wipe the drive completely
|
||||||
|
wipefs -af "$INTERNAL_DRIVE"
|
||||||
|
dd if=/dev/zero of="$INTERNAL_DRIVE" bs=1M count=100 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create identical partition structure to external drive
|
||||||
|
log_info "Creating partition table..."
|
||||||
|
sfdisk -d "$EXTERNAL_DRIVE" | sfdisk --force "$INTERNAL_DRIVE"
|
||||||
|
|
||||||
|
# Wait for partitions to appear
|
||||||
|
sleep 3
|
||||||
|
partprobe "$INTERNAL_DRIVE"
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
log_info "Internal drive partitioned"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create LVM structure on internal drive
|
||||||
|
setup_internal_lvm() {
|
||||||
|
log_step "Setting up LVM on internal drive..."
|
||||||
|
|
||||||
|
# Create physical volume on the LVM partition
|
||||||
|
pvcreate "${INTERNAL_DRIVE}p2" -ff
|
||||||
|
|
||||||
|
# Create volume group with extra space for snapshots
|
||||||
|
vgcreate "$TARGET_VG" "${INTERNAL_DRIVE}p2"
|
||||||
|
|
||||||
|
# Get exact sizes from source LVs
|
||||||
|
declare -A lv_sizes
|
||||||
|
while IFS= read -r line; do
|
||||||
|
lv_name=$(echo "$line" | awk '{print $1}')
|
||||||
|
lv_size=$(echo "$line" | awk '{print $2}')
|
||||||
|
lv_sizes["$lv_name"]="$lv_size"
|
||||||
|
done < <(lvs --noheadings --units b --nosuffix -o lv_name,lv_size "$SOURCE_VG")
|
||||||
|
|
||||||
|
# Create LVs with exact same sizes
|
||||||
|
for lv in root home boot swap; do
|
||||||
|
if [[ -n "${lv_sizes[$lv]:-}" ]]; then
|
||||||
|
size_bytes="${lv_sizes[$lv]}"
|
||||||
|
log_info "Creating LV $lv with size $size_bytes bytes"
|
||||||
|
lvcreate -L "${size_bytes}b" -n "$lv" "$TARGET_VG"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log_info "Internal LVM structure created"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create temporary snapshots and transfer via dd
|
||||||
|
transfer_with_snapshots() {
|
||||||
|
log_step "Transferring data using snapshots..."
|
||||||
|
|
||||||
|
# Calculate optimal snapshot size (use available free space)
|
||||||
|
free_space=$(vgs --noheadings --units m --nosuffix -o vg_free "$SOURCE_VG" | tr -d ' ' | tr ',' '.')
|
||||||
|
# Use 80% of free space for snapshots, divided by number of LVs
|
||||||
|
snapshot_size=$(echo "$free_space * 0.8 / 4" | bc | cut -d. -f1)
|
||||||
|
|
||||||
|
if (( snapshot_size < 100 )); then
|
||||||
|
log_error "Not enough free space for snapshots. Need at least 400MB free."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Using ${snapshot_size}MB for each snapshot"
|
||||||
|
|
||||||
|
# Process each logical volume
|
||||||
|
for lv in root home boot swap; do
|
||||||
|
log_step "Processing $lv..."
|
||||||
|
|
||||||
|
# Create snapshot
|
||||||
|
log_info "Creating snapshot of $lv..."
|
||||||
|
if ! lvcreate -L "${snapshot_size}M" -s -n "${lv}_snapshot" "$SOURCE_VG/$lv"; then
|
||||||
|
log_error "Failed to create snapshot for $lv"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy data using dd with progress
|
||||||
|
log_info "Copying $lv data to internal drive..."
|
||||||
|
if [[ "$lv" == "swap" ]]; then
|
||||||
|
# For swap, just copy the LV structure
|
||||||
|
dd if="/dev/$SOURCE_VG/${lv}_snapshot" of="/dev/$TARGET_VG/$lv" bs=1M status=progress
|
||||||
|
else
|
||||||
|
# For filesystems, use dd for exact copy
|
||||||
|
dd if="/dev/$SOURCE_VG/${lv}_snapshot" of="/dev/$TARGET_VG/$lv" bs=1M status=progress
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove snapshot to free space for next one
|
||||||
|
log_info "Removing snapshot of $lv..."
|
||||||
|
lvremove -f "$SOURCE_VG/${lv}_snapshot"
|
||||||
|
|
||||||
|
log_info "Completed transfer of $lv"
|
||||||
|
done
|
||||||
|
|
||||||
|
log_info "All data transferred successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup boot for internal drive
|
||||||
|
configure_internal_boot() {
|
||||||
|
log_step "Configuring boot for internal drive..."
|
||||||
|
|
||||||
|
# Format and mount EFI partition
|
||||||
|
mkfs.fat -F32 "${INTERNAL_DRIVE}p1" || true
|
||||||
|
mkdir -p /mnt/internal_efi
|
||||||
|
mount "${INTERNAL_DRIVE}p1" /mnt/internal_efi
|
||||||
|
|
||||||
|
# Mount internal filesystems
|
||||||
|
mkdir -p /mnt/internal_root /mnt/internal_boot /mnt/internal_home
|
||||||
|
mount "/dev/$TARGET_VG/root" /mnt/internal_root
|
||||||
|
mount "/dev/$TARGET_VG/boot" /mnt/internal_boot
|
||||||
|
mount "/dev/$TARGET_VG/home" /mnt/internal_home
|
||||||
|
|
||||||
|
# Mount EFI in the target system
|
||||||
|
mkdir -p /mnt/internal_root/boot/efi
|
||||||
|
mount "${INTERNAL_DRIVE}p1" /mnt/internal_root/boot/efi
|
||||||
|
|
||||||
|
# Update fstab for new VG
|
||||||
|
log_info "Updating fstab..."
|
||||||
|
cat > /mnt/internal_root/etc/fstab << EOF
|
||||||
|
# Internal LVM Configuration
|
||||||
|
/dev/$TARGET_VG/root / ext4 defaults 0 1
|
||||||
|
/dev/$TARGET_VG/boot /boot ext4 defaults 0 2
|
||||||
|
/dev/$TARGET_VG/home /home ext4 defaults 0 2
|
||||||
|
/dev/$TARGET_VG/swap none swap sw 0 0
|
||||||
|
${INTERNAL_DRIVE}p1 /boot/efi vfat umask=0077 0 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Prepare chroot environment
|
||||||
|
mount --bind /dev /mnt/internal_root/dev
|
||||||
|
mount --bind /proc /mnt/internal_root/proc
|
||||||
|
mount --bind /sys /mnt/internal_root/sys
|
||||||
|
mount --bind /run /mnt/internal_root/run
|
||||||
|
|
||||||
|
# Update initramfs and install GRUB
|
||||||
|
log_info "Updating initramfs and GRUB..."
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "update-initramfs -u -k all"
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Internal-LVM"
|
||||||
|
chroot /mnt/internal_root /bin/bash -c "update-grub"
|
||||||
|
|
||||||
|
# Clean up mounts
|
||||||
|
umount /mnt/internal_root/dev /mnt/internal_root/proc /mnt/internal_root/sys /mnt/internal_root/run
|
||||||
|
umount /mnt/internal_root/boot/efi
|
||||||
|
umount /mnt/internal_efi /mnt/internal_root /mnt/internal_boot /mnt/internal_home
|
||||||
|
|
||||||
|
log_info "Boot configuration completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup snapshot capabilities on internal drive
|
||||||
|
setup_snapshot_tools() {
|
||||||
|
log_step "Setting up snapshot tools on internal drive..."
|
||||||
|
|
||||||
|
# Remount for tool installation
|
||||||
|
mount "/dev/$TARGET_VG/root" /mnt/internal_root
|
||||||
|
|
||||||
|
# Create snapshot management script
|
||||||
|
cat > /mnt/internal_root/usr/local/bin/lvm-snapshot-manager << 'EOFSCRIPT'
|
||||||
|
#!/bin/bash
|
||||||
|
# LVM Snapshot Manager for Internal Drive
|
||||||
|
|
||||||
|
VG_NAME="internal-vg"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
create)
|
||||||
|
echo "Creating LVM snapshots..."
|
||||||
|
# Calculate available space for snapshots
|
||||||
|
free_space=$(vgs --noheadings --units g --nosuffix -o vg_free "$VG_NAME" | tr -d ' ')
|
||||||
|
snapshot_size=$(echo "$free_space / 4" | bc)G
|
||||||
|
|
||||||
|
lvcreate -L "$snapshot_size" -s -n root_backup "$VG_NAME/root"
|
||||||
|
lvcreate -L "$snapshot_size" -s -n home_backup "$VG_NAME/home"
|
||||||
|
lvcreate -L "$snapshot_size" -s -n boot_backup "$VG_NAME/boot"
|
||||||
|
echo "Snapshots created with size: $snapshot_size each"
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
echo "Removing LVM snapshots..."
|
||||||
|
lvremove -f "$VG_NAME/root_backup" 2>/dev/null || true
|
||||||
|
lvremove -f "$VG_NAME/home_backup" 2>/dev/null || true
|
||||||
|
lvremove -f "$VG_NAME/boot_backup" 2>/dev/null || true
|
||||||
|
echo "Snapshots removed"
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
echo "Current snapshots:"
|
||||||
|
lvs "$VG_NAME" | grep backup || echo "No snapshots found"
|
||||||
|
;;
|
||||||
|
merge)
|
||||||
|
if [[ -z "$2" ]]; then
|
||||||
|
echo "Usage: $0 merge <snapshot_name>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Merging snapshot $2..."
|
||||||
|
lvconvert --merge "$VG_NAME/$2"
|
||||||
|
echo "Snapshot merge initiated (reboot required to complete)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {create|remove|list|merge <snapshot_name>}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOFSCRIPT
|
||||||
|
|
||||||
|
chmod +x /mnt/internal_root/usr/local/bin/lvm-snapshot-manager
|
||||||
|
|
||||||
|
umount /mnt/internal_root
|
||||||
|
|
||||||
|
log_info "Snapshot tools installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup function
|
||||||
|
cleanup() {
|
||||||
|
log_info "Cleaning up..."
|
||||||
|
|
||||||
|
# Remove any remaining snapshots
|
||||||
|
for snap in $(lvs --noheadings -o lv_name "$SOURCE_VG" 2>/dev/null | grep snapshot || true); do
|
||||||
|
lvremove -f "$SOURCE_VG/$snap" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Unmount any remaining mounts
|
||||||
|
for mount in /mnt/internal_*; do
|
||||||
|
umount "$mount" 2>/dev/null || true
|
||||||
|
rmdir "$mount" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main migration function
|
||||||
|
main() {
|
||||||
|
log_info "Starting snapshot-based LVM migration"
|
||||||
|
log_info "Source: $EXTERNAL_DRIVE ($SOURCE_VG) -> Target: $INTERNAL_DRIVE ($TARGET_VG)"
|
||||||
|
|
||||||
|
safety_checks
|
||||||
|
create_backup_metadata
|
||||||
|
prepare_internal_drive
|
||||||
|
setup_internal_lvm
|
||||||
|
transfer_with_snapshots
|
||||||
|
configure_internal_boot
|
||||||
|
setup_snapshot_tools
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
log_info "Migration completed successfully!"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}SUCCESS!${NC} Snapshot-based LVM migration completed"
|
||||||
|
echo
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Reboot and select internal drive in BIOS/UEFI"
|
||||||
|
echo "2. Verify all systems working from internal LVM"
|
||||||
|
echo "3. Test snapshot functionality: sudo lvm-snapshot-manager create"
|
||||||
|
echo "4. External M.2 can now be used as backup drive"
|
||||||
|
echo
|
||||||
|
echo "The internal drive now has:"
|
||||||
|
echo "- Complete copy of your current system"
|
||||||
|
echo "- LVM with snapshot capabilities"
|
||||||
|
echo "- Reserved space for future snapshots"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle interruption
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Run migration
|
||||||
|
main "$@"
|
||||||
481
old_scripts/verify_boot_readiness.sh
Normal file
481
old_scripts/verify_boot_readiness.sh
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Boot Verification and Test Script
|
||||||
|
# Validates that a cloned drive can boot properly before declaring success
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
TARGET_DRIVE=""
|
||||||
|
WORK_DIR="/mnt/verify_work"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_partition_structure() {
|
||||||
|
log "Verifying partition structure..."
|
||||||
|
|
||||||
|
# Check if drive exists and has partitions
|
||||||
|
if [ ! -b "$TARGET_DRIVE" ]; then
|
||||||
|
error "Target drive $TARGET_DRIVE not found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local partitions=($(lsblk -pno NAME "$TARGET_DRIVE" | grep -v "^$TARGET_DRIVE$"))
|
||||||
|
|
||||||
|
if [ ${#partitions[@]} -eq 0 ]; then
|
||||||
|
error "No partitions found on $TARGET_DRIVE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found ${#partitions[@]} partitions:"
|
||||||
|
for part in "${partitions[@]}"; do
|
||||||
|
local size=$(lsblk -no SIZE "$part")
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
local uuid=$(lsblk -no UUID "$part")
|
||||||
|
echo " $part: $size, $fstype, UUID: ${uuid:-'none'}"
|
||||||
|
done
|
||||||
|
|
||||||
|
success "Partition structure verified"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_filesystem_integrity() {
|
||||||
|
log "Checking filesystem integrity..."
|
||||||
|
|
||||||
|
local partitions=($(lsblk -pno NAME "$TARGET_DRIVE" | grep -v "^$TARGET_DRIVE$"))
|
||||||
|
local errors=0
|
||||||
|
|
||||||
|
for part in "${partitions[@]}"; do
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
|
||||||
|
case "$fstype" in
|
||||||
|
"ext4"|"ext3"|"ext2")
|
||||||
|
log "Checking ext filesystem on $part..."
|
||||||
|
if e2fsck -n "$part" >/dev/null 2>&1; then
|
||||||
|
success "Filesystem on $part is clean"
|
||||||
|
else
|
||||||
|
warning "Filesystem on $part has errors (read-only check)"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"vfat")
|
||||||
|
log "Checking FAT filesystem on $part..."
|
||||||
|
if fsck.fat -v "$part" >/dev/null 2>&1; then
|
||||||
|
success "FAT filesystem on $part is clean"
|
||||||
|
else
|
||||||
|
warning "FAT filesystem on $part has errors"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"crypto_LUKS")
|
||||||
|
log "Skipping LUKS partition $part (encrypted)"
|
||||||
|
;;
|
||||||
|
"swap")
|
||||||
|
log "Skipping swap partition $part"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "Skipping unknown filesystem type '$fstype' on $part"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $errors -eq 0 ]; then
|
||||||
|
success "All filesystems are clean"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
warning "Found $errors filesystem(s) with potential issues"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_boot_files() {
|
||||||
|
log "Checking essential boot files..."
|
||||||
|
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
local errors=0
|
||||||
|
|
||||||
|
# Find and mount root partition
|
||||||
|
local root_partition=""
|
||||||
|
local boot_partition=""
|
||||||
|
local efi_partition=""
|
||||||
|
|
||||||
|
local partitions=($(lsblk -pno NAME "$TARGET_DRIVE" | grep -v "^$TARGET_DRIVE$"))
|
||||||
|
|
||||||
|
for part in "${partitions[@]}"; do
|
||||||
|
local fstype=$(lsblk -no FSTYPE "$part")
|
||||||
|
local size_bytes=$(lsblk -bno SIZE "$part")
|
||||||
|
|
||||||
|
if [[ "$fstype" == "vfat" && "$size_bytes" -lt 1073741824 ]]; then
|
||||||
|
efi_partition="$part"
|
||||||
|
elif [[ "$fstype" == "ext4" && "$size_bytes" -lt 5368709120 ]]; then
|
||||||
|
boot_partition="$part"
|
||||||
|
elif [[ "$fstype" == "ext4" && "$size_bytes" -gt 5368709120 ]]; then
|
||||||
|
root_partition="$part"
|
||||||
|
elif [[ "$fstype" == "crypto_LUKS" ]]; then
|
||||||
|
# Try to unlock for verification
|
||||||
|
local crypt_name="verify_$(basename "$part")"
|
||||||
|
echo "Found encrypted partition: $part"
|
||||||
|
echo "Please enter password to verify boot files (optional - press Enter to skip):"
|
||||||
|
read -s -t 30 password || {
|
||||||
|
log "Skipping encrypted partition verification"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if [ -n "$password" ]; then
|
||||||
|
if echo "$password" | cryptsetup open "$part" "$crypt_name" --key-file=-; then
|
||||||
|
local decrypted_fs=$(lsblk -no FSTYPE "/dev/mapper/$crypt_name")
|
||||||
|
if [[ "$decrypted_fs" == "ext4" ]]; then
|
||||||
|
root_partition="/dev/mapper/$crypt_name"
|
||||||
|
log "Using decrypted partition for verification: $root_partition"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warning "Could not unlock encrypted partition for verification"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$root_partition" ]; then
|
||||||
|
error "Could not find root partition for verification"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount root partition
|
||||||
|
if ! mount "$root_partition" "$WORK_DIR"; then
|
||||||
|
error "Could not mount root partition for verification"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount boot if separate
|
||||||
|
if [ -n "$boot_partition" ]; then
|
||||||
|
mkdir -p "$WORK_DIR/boot"
|
||||||
|
mount "$boot_partition" "$WORK_DIR/boot" || warning "Could not mount boot partition"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount EFI if exists
|
||||||
|
if [ -n "$efi_partition" ]; then
|
||||||
|
mkdir -p "$WORK_DIR/boot/efi"
|
||||||
|
mount "$efi_partition" "$WORK_DIR/boot/efi" || warning "Could not mount EFI partition"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check essential files
|
||||||
|
local essential_files=(
|
||||||
|
"/etc/fstab"
|
||||||
|
"/boot/grub/grub.cfg"
|
||||||
|
"/etc/default/grub"
|
||||||
|
)
|
||||||
|
|
||||||
|
for file in "${essential_files[@]}"; do
|
||||||
|
if [ -f "$WORK_DIR$file" ]; then
|
||||||
|
success "Found: $file"
|
||||||
|
else
|
||||||
|
warning "Missing: $file"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check for kernel and initramfs
|
||||||
|
local kernels=($(ls "$WORK_DIR/boot/vmlinuz-"* 2>/dev/null || true))
|
||||||
|
local initramfs=($(ls "$WORK_DIR/boot/initrd.img-"* 2>/dev/null || true))
|
||||||
|
|
||||||
|
if [ ${#kernels[@]} -gt 0 ]; then
|
||||||
|
success "Found ${#kernels[@]} kernel(s)"
|
||||||
|
else
|
||||||
|
warning "No kernels found"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#initramfs[@]} -gt 0 ]; then
|
||||||
|
success "Found ${#initramfs[@]} initramfs image(s)"
|
||||||
|
else
|
||||||
|
warning "No initramfs images found"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check EFI bootloader
|
||||||
|
if [ -n "$efi_partition" ]; then
|
||||||
|
if [ -f "$WORK_DIR/boot/efi/EFI/debian/grubx64.efi" ]; then
|
||||||
|
success "Found EFI bootloader"
|
||||||
|
else
|
||||||
|
warning "EFI bootloader not found"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check fstab content
|
||||||
|
if [ -f "$WORK_DIR/etc/fstab" ]; then
|
||||||
|
log "Checking /etc/fstab content..."
|
||||||
|
local fstab_errors=0
|
||||||
|
|
||||||
|
# Check if UUIDs in fstab actually exist
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ ^UUID=([a-fA-F0-9-]+) ]]; then
|
||||||
|
local uuid="${BASH_REMATCH[1]}"
|
||||||
|
if ! blkid | grep -q "$uuid"; then
|
||||||
|
warning "UUID $uuid in fstab not found on system"
|
||||||
|
((fstab_errors++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < "$WORK_DIR/etc/fstab"
|
||||||
|
|
||||||
|
if [ $fstab_errors -eq 0 ]; then
|
||||||
|
success "/etc/fstab appears valid"
|
||||||
|
else
|
||||||
|
warning "/etc/fstab has $fstab_errors potential issues"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup mounts
|
||||||
|
umount "$WORK_DIR/boot/efi" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR/boot" 2>/dev/null || true
|
||||||
|
umount "$WORK_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Close encrypted volumes
|
||||||
|
for mapper in /dev/mapper/verify_*; do
|
||||||
|
if [ -b "$mapper" ]; then
|
||||||
|
local crypt_name=$(basename "$mapper")
|
||||||
|
cryptsetup close "$crypt_name" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $errors -eq 0 ]; then
|
||||||
|
success "All essential boot files found"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
warning "Found $errors potential boot issues"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_grub_configuration() {
|
||||||
|
log "Checking GRUB configuration..."
|
||||||
|
|
||||||
|
# Try to validate GRUB configuration without mounting
|
||||||
|
if grub-probe "$TARGET_DRIVE" >/dev/null 2>&1; then
|
||||||
|
success "GRUB can recognize the drive"
|
||||||
|
else
|
||||||
|
warning "GRUB may have issues recognizing the drive"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
perform_dry_run_boot_test() {
|
||||||
|
log "Performing dry-run boot test..."
|
||||||
|
|
||||||
|
# Check if we can simulate boot process
|
||||||
|
warning "Note: This is a simulation - actual boot test requires reboot"
|
||||||
|
|
||||||
|
# Check boot order in EFI (if available)
|
||||||
|
if command -v efibootmgr >/dev/null 2>&1; then
|
||||||
|
log "Current EFI boot order:"
|
||||||
|
efibootmgr | grep -E "(BootOrder|Boot[0-9]+)" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test if drive is bootable by checking MBR/GPT
|
||||||
|
if fdisk -l "$TARGET_DRIVE" | grep -q "EFI System"; then
|
||||||
|
success "Drive has EFI System partition (UEFI bootable)"
|
||||||
|
elif fdisk -l "$TARGET_DRIVE" | grep -q "Boot"; then
|
||||||
|
success "Drive has bootable partition"
|
||||||
|
else
|
||||||
|
warning "Drive may not be properly configured for booting"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
comprehensive_verification() {
|
||||||
|
log "Starting comprehensive verification of $TARGET_DRIVE..."
|
||||||
|
|
||||||
|
local total_checks=5
|
||||||
|
local passed_checks=0
|
||||||
|
local failed_checks=0
|
||||||
|
|
||||||
|
echo "Verification Progress:"
|
||||||
|
echo "====================="
|
||||||
|
|
||||||
|
# Test 1: Partition Structure
|
||||||
|
echo -n "1. Partition Structure: "
|
||||||
|
if check_partition_structure; then
|
||||||
|
echo -e "${GREEN}PASS${NC}"
|
||||||
|
((passed_checks++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}FAIL${NC}"
|
||||||
|
((failed_checks++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 2: Filesystem Integrity
|
||||||
|
echo -n "2. Filesystem Integrity: "
|
||||||
|
if check_filesystem_integrity; then
|
||||||
|
echo -e "${GREEN}PASS${NC}"
|
||||||
|
((passed_checks++))
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}WARNING${NC}"
|
||||||
|
((passed_checks++)) # Count warnings as pass for now
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 3: Boot Files
|
||||||
|
echo -n "3. Essential Boot Files: "
|
||||||
|
if check_boot_files; then
|
||||||
|
echo -e "${GREEN}PASS${NC}"
|
||||||
|
((passed_checks++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}FAIL${NC}"
|
||||||
|
((failed_checks++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 4: GRUB Configuration
|
||||||
|
echo -n "4. GRUB Configuration: "
|
||||||
|
if check_grub_configuration; then
|
||||||
|
echo -e "${GREEN}PASS${NC}"
|
||||||
|
((passed_checks++))
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}WARNING${NC}"
|
||||||
|
((passed_checks++)) # Count as pass for compatibility
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 5: Boot Readiness
|
||||||
|
echo -n "5. Boot Readiness: "
|
||||||
|
if perform_dry_run_boot_test; then
|
||||||
|
echo -e "${GREEN}PASS${NC}"
|
||||||
|
((passed_checks++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}FAIL${NC}"
|
||||||
|
((failed_checks++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "====================="
|
||||||
|
echo "Verification Summary:"
|
||||||
|
echo " Passed: $passed_checks/$total_checks"
|
||||||
|
echo " Failed: $failed_checks/$total_checks"
|
||||||
|
|
||||||
|
if [ $failed_checks -eq 0 ]; then
|
||||||
|
success "All verification checks passed! Drive should boot properly."
|
||||||
|
return 0
|
||||||
|
elif [ $failed_checks -le 2 ]; then
|
||||||
|
warning "Some checks failed but drive might still boot. Consider running boot repair."
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
error "Multiple critical checks failed. Drive is unlikely to boot properly."
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_verification() {
|
||||||
|
log "Cleaning up verification environment..."
|
||||||
|
|
||||||
|
# Unmount any remaining mounts
|
||||||
|
umount "$WORK_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Close any encrypted volumes
|
||||||
|
for mapper in /dev/mapper/verify_*; do
|
||||||
|
if [ -b "$mapper" ]; then
|
||||||
|
local crypt_name=$(basename "$mapper")
|
||||||
|
cryptsetup close "$crypt_name" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove work directory
|
||||||
|
rmdir "$WORK_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
success "Cleanup completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}=== Boot Verification and Test Script ===${NC}"
|
||||||
|
echo "This script validates that a cloned drive can boot properly"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Get target drive
|
||||||
|
echo "Available drives:"
|
||||||
|
lsblk -dpno NAME,SIZE,MODEL | grep -v "loop\|ram"
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Enter the drive to verify (e.g., /dev/sdb): " TARGET_DRIVE
|
||||||
|
|
||||||
|
if [ ! -b "$TARGET_DRIVE" ]; then
|
||||||
|
error "Drive $TARGET_DRIVE not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Selected drive for verification: $TARGET_DRIVE"
|
||||||
|
lsblk "$TARGET_DRIVE"
|
||||||
|
echo
|
||||||
|
|
||||||
|
read -p "Verify this drive? [y/N]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
error "Verification cancelled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Perform comprehensive verification
|
||||||
|
if comprehensive_verification; then
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}🎉 VERIFICATION SUCCESSFUL! 🎉${NC}"
|
||||||
|
echo "The cloned drive passed all verification checks."
|
||||||
|
echo "It should boot properly when set as the primary boot device."
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}Next steps:${NC}"
|
||||||
|
echo "1. Reboot your system"
|
||||||
|
echo "2. Enter BIOS/UEFI setup"
|
||||||
|
echo "3. Set $TARGET_DRIVE as the first boot device"
|
||||||
|
echo "4. Save and exit BIOS/UEFI"
|
||||||
|
echo "5. System should boot from cloned drive"
|
||||||
|
|
||||||
|
if lsblk "$TARGET_DRIVE" | grep -q "crypto_LUKS"; then
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}Note:${NC} System will ask for LUKS password during boot (this is normal)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_verification
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
local exit_code=$?
|
||||||
|
echo
|
||||||
|
if [ $exit_code -eq 1 ]; then
|
||||||
|
echo -e "${YELLOW}⚠️ VERIFICATION COMPLETED WITH WARNINGS ⚠️${NC}"
|
||||||
|
echo "The drive might boot but some issues were detected."
|
||||||
|
echo "Consider running the boot repair script before attempting to boot."
|
||||||
|
echo
|
||||||
|
echo "Run: ./boot_repair_tools.sh"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ VERIFICATION FAILED ❌${NC}"
|
||||||
|
echo "The drive is unlikely to boot properly in its current state."
|
||||||
|
echo "Please run the boot repair script to fix issues."
|
||||||
|
echo
|
||||||
|
echo "Run: ./boot_repair_tools.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_verification
|
||||||
|
exit $exit_code
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap to ensure cleanup on exit
|
||||||
|
trap cleanup_verification EXIT
|
||||||
|
|
||||||
|
main "$@"
|
||||||
57
old_scripts/verify_internal_boot.sh
Executable file
57
old_scripts/verify_internal_boot.sh
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Verify Internal NVMe Boot Configuration
|
||||||
|
# This script checks if the internal drive (nvme0n1) is properly configured to boot
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Internal Drive Boot Verification ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "1. Checking EFI partition mount..."
|
||||||
|
df -h /boot/efi | grep -E "Filesystem|nvme0n1p1" && echo "✓ EFI mounted from internal drive" || echo "✗ EFI not on internal drive"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "2. Checking boot partition mount..."
|
||||||
|
df -h /boot | grep -E "Filesystem|internal--vg-boot" && echo "✓ Boot mounted from internal-vg" || echo "✗ Boot not on internal-vg"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "3. Checking GRUB installation..."
|
||||||
|
if [ -f /boot/efi/EFI/ubuntu/shimx64.efi ]; then
|
||||||
|
echo "✓ GRUB bootloader found in EFI"
|
||||||
|
else
|
||||||
|
echo "✗ GRUB bootloader missing"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "4. Checking EFI boot entries..."
|
||||||
|
efibootmgr | grep -i ubuntu && echo "✓ Ubuntu boot entry exists" || echo "✗ No Ubuntu boot entry"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "5. Checking GRUB environment..."
|
||||||
|
sudo grub-editenv list
|
||||||
|
if [ -z "$(sudo grub-editenv list)" ]; then
|
||||||
|
echo "✓ GRUB environment clean (no boot failure flags)"
|
||||||
|
else
|
||||||
|
echo "⚠ GRUB environment has settings"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "6. Checking current boot device..."
|
||||||
|
echo "Currently booted from: $(findmnt -n -o SOURCE /)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "7. Internal drive UUIDs..."
|
||||||
|
echo "Root: $(sudo blkid /dev/internal-vg/root -s UUID -o value)"
|
||||||
|
echo "Boot: $(sudo blkid /dev/internal-vg/boot -s UUID -o value)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "8. Checking /etc/fstab for internal drive..."
|
||||||
|
grep -E "internal-vg|757B-A377" /etc/fstab && echo "✓ Internal volumes in fstab" || echo "⚠ Check fstab configuration"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Verification Complete ==="
|
||||||
|
echo ""
|
||||||
|
echo "To test boot from internal drive:"
|
||||||
|
echo "1. Remove/disconnect the external USB M.2 drive"
|
||||||
|
echo "2. Reboot the system"
|
||||||
|
echo "3. System should boot from internal nvme0n1"
|
||||||
147
old_scripts/verify_internal_config.sh
Executable file
147
old_scripts/verify_internal_config.sh
Executable file
@@ -0,0 +1,147 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Final Internal Drive Configuration Check
|
||||||
|
# Run this after rebooting to verify everything is working
|
||||||
|
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo " Internal NVMe Drive Configuration Verification"
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Color codes
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
success_count=0
|
||||||
|
total_checks=7
|
||||||
|
|
||||||
|
echo "Running system checks..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 1: Root filesystem
|
||||||
|
echo -n "1. Root filesystem from internal drive... "
|
||||||
|
if mount | grep -q "internal--vg-root on / "; then
|
||||||
|
echo -e "${GREEN}✓ PASS${NC}"
|
||||||
|
((success_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ FAIL${NC}"
|
||||||
|
mount | grep " / "
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 2: Boot filesystem
|
||||||
|
echo -n "2. Boot filesystem from internal drive... "
|
||||||
|
if mount | grep -q "internal--vg-boot on /boot "; then
|
||||||
|
echo -e "${GREEN}✓ PASS${NC}"
|
||||||
|
((success_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ FAIL${NC}"
|
||||||
|
mount | grep " /boot "
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 3: Home filesystem
|
||||||
|
echo -n "3. Home filesystem from internal drive... "
|
||||||
|
HOME_DEVICE=$(sudo cryptsetup status luks-home-internal 2>/dev/null | grep "device:" | awk '{print $2}')
|
||||||
|
if [[ "$HOME_DEVICE" == "/dev/mapper/internal--vg-home" ]]; then
|
||||||
|
echo -e "${GREEN}✓ PASS${NC}"
|
||||||
|
((success_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ FAIL${NC}"
|
||||||
|
echo " Currently using: $HOME_DEVICE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 4: EFI partition
|
||||||
|
echo -n "4. EFI partition from internal drive... "
|
||||||
|
if mount | grep -q "nvme0n1p1 on /boot/efi"; then
|
||||||
|
echo -e "${GREEN}✓ PASS${NC}"
|
||||||
|
((success_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ FAIL${NC}"
|
||||||
|
mount | grep "/boot/efi"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 5: GRUB installation
|
||||||
|
echo -n "5. GRUB bootloader installed... "
|
||||||
|
if [ -f /boot/efi/EFI/ubuntu/shimx64.efi ] && [ -f /boot/efi/EFI/ubuntu/grubx64.efi ]; then
|
||||||
|
echo -e "${GREEN}✓ PASS${NC}"
|
||||||
|
((success_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ FAIL${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 6: EFI boot order
|
||||||
|
echo -n "6. Ubuntu first in boot order... "
|
||||||
|
FIRST_BOOT=$(efibootmgr | grep BootOrder | cut -d':' -f2 | tr -d ' ' | cut -d',' -f1)
|
||||||
|
if [ "$FIRST_BOOT" = "0001" ] && efibootmgr | grep -q "Boot0001.*Ubuntu"; then
|
||||||
|
echo -e "${GREEN}✓ PASS${NC}"
|
||||||
|
((success_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ FAIL${NC}"
|
||||||
|
echo " First boot entry: $FIRST_BOOT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 7: External drive status
|
||||||
|
echo -n "7. External drive NOT in use for system... "
|
||||||
|
if ! mount | grep -q "migration--vg.*on /"; then
|
||||||
|
echo -e "${GREEN}✓ PASS${NC}"
|
||||||
|
((success_count++))
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠ WARNING${NC}"
|
||||||
|
echo " External drive (migration-vg) is mounted for system use"
|
||||||
|
mount | grep migration-vg
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo " Results: $success_count/$total_checks checks passed"
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $success_count -eq $total_checks ]; then
|
||||||
|
echo -e "${GREEN}✓ SUCCESS!${NC} Internal drive is fully configured and operational."
|
||||||
|
echo ""
|
||||||
|
echo "Your system is now running entirely from the internal NVMe drive."
|
||||||
|
echo "The external M.2 drive can be used as a backup."
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Test disconnecting the external drive and rebooting"
|
||||||
|
echo " 2. Run backup: sudo ./lvm_block_backup.sh"
|
||||||
|
echo ""
|
||||||
|
elif [ $success_count -ge 5 ]; then
|
||||||
|
echo -e "${YELLOW}⚠ PARTIAL SUCCESS${NC} - Most checks passed but some issues remain."
|
||||||
|
echo ""
|
||||||
|
echo "Review the failed checks above and consult INTERNAL_DRIVE_RECOVERY.md"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ CONFIGURATION ISSUES DETECTED${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Several checks failed. You may need to reboot for changes to take effect."
|
||||||
|
echo "If problems persist after reboot, review INTERNAL_DRIVE_RECOVERY.md"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Additional info
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo " Detailed Information"
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Current mount points:"
|
||||||
|
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT | grep -E "NAME|nvme0n1|sda"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "LUKS device mapping:"
|
||||||
|
sudo cryptsetup status luks-home-internal 2>/dev/null | grep -E "type|device|mode"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "EFI boot configuration:"
|
||||||
|
efibootmgr | grep -E "BootCurrent|BootOrder|Boot0001"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -b /dev/sda ]; then
|
||||||
|
echo -e "${YELLOW}Note:${NC} External drive (sda) is connected."
|
||||||
|
echo " To test full independence, shutdown and disconnect it."
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}Note:${NC} External drive is not connected - full independence confirmed!"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,368 +0,0 @@
|
|||||||
#!/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 "$@"
|
|
||||||
148
simple_backup.sh
Executable file
148
simple_backup.sh
Executable file
@@ -0,0 +1,148 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple LVM Backup Script
|
||||||
|
# Does exactly what it says: snapshot -> copy -> cleanup
|
||||||
|
# NO complex logic, just the essentials
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 SOURCE_LV TARGET_DEVICE"
|
||||||
|
echo ""
|
||||||
|
echo "Example: $0 /dev/internal-vg/root /dev/sdb"
|
||||||
|
echo ""
|
||||||
|
echo "This will:"
|
||||||
|
echo "1. Create a snapshot of SOURCE_LV"
|
||||||
|
echo "2. Copy it block-for-block to TARGET_DEVICE"
|
||||||
|
echo "3. Clean up the snapshot"
|
||||||
|
echo ""
|
||||||
|
echo "WARNING: TARGET_DEVICE will be completely overwritten!"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo -e "${GREEN}[$(date '+%H:%M:%S')] $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo -e "${RED}[ERROR] $1${NC}" >&2
|
||||||
|
cleanup_and_exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
echo -e "${YELLOW}[WARNING] $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_and_exit() {
|
||||||
|
local exit_code=${1:-0}
|
||||||
|
|
||||||
|
if [ -n "$SNAPSHOT_PATH" ] && lvs "$SNAPSHOT_PATH" >/dev/null 2>&1; then
|
||||||
|
warn "Cleaning up snapshot: $SNAPSHOT_PATH"
|
||||||
|
lvremove -f "$SNAPSHOT_PATH" || warn "Failed to remove snapshot"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap for cleanup
|
||||||
|
trap 'cleanup_and_exit 130' INT TERM
|
||||||
|
|
||||||
|
# Check arguments
|
||||||
|
if [ $# -ne 2 ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
SOURCE_LV="$1"
|
||||||
|
TARGET_DEVICE="$2"
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
error "This script must be run as root"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Basic validation
|
||||||
|
if [ ! -e "$SOURCE_LV" ]; then
|
||||||
|
error "Source LV does not exist: $SOURCE_LV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "$TARGET_DEVICE" ]; then
|
||||||
|
error "Target device does not exist: $TARGET_DEVICE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract VG and LV names
|
||||||
|
VG_NAME=$(lvs --noheadings -o vg_name "$SOURCE_LV" | tr -d ' ')
|
||||||
|
LV_NAME=$(lvs --noheadings -o lv_name "$SOURCE_LV" | tr -d ' ')
|
||||||
|
SNAPSHOT_NAME="${LV_NAME}_simple_backup"
|
||||||
|
SNAPSHOT_PATH="/dev/$VG_NAME/$SNAPSHOT_NAME"
|
||||||
|
|
||||||
|
log "Simple LVM Backup Starting"
|
||||||
|
log "Source: $SOURCE_LV"
|
||||||
|
log "Target: $TARGET_DEVICE"
|
||||||
|
log "Snapshot will be: $SNAPSHOT_PATH"
|
||||||
|
|
||||||
|
# Final confirmation
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}WARNING: This will completely overwrite $TARGET_DEVICE${NC}"
|
||||||
|
echo -e "${YELLOW}All data on $TARGET_DEVICE will be lost!${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Are you sure you want to continue? (yes/no): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "Backup cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log "Starting backup process..."
|
||||||
|
|
||||||
|
# Step 1: Create snapshot
|
||||||
|
log "Creating snapshot..."
|
||||||
|
if ! lvcreate -L1G -s -n "$SNAPSHOT_NAME" "$SOURCE_LV"; then
|
||||||
|
error "Failed to create snapshot"
|
||||||
|
fi
|
||||||
|
log "Snapshot created: $SNAPSHOT_PATH"
|
||||||
|
|
||||||
|
# Step 2: Copy data
|
||||||
|
log "Starting copy operation..."
|
||||||
|
log "This may take a long time depending on the size of your data"
|
||||||
|
|
||||||
|
# Use pv if available for progress, otherwise dd with progress
|
||||||
|
if command -v pv >/dev/null 2>&1; then
|
||||||
|
log "Using pv for progress monitoring"
|
||||||
|
if ! pv "$SNAPSHOT_PATH" | dd of="$TARGET_DEVICE" bs=4M; then
|
||||||
|
error "Copy operation failed"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "Using dd (install 'pv' for progress monitoring)"
|
||||||
|
if ! dd if="$SNAPSHOT_PATH" of="$TARGET_DEVICE" bs=4M status=progress; then
|
||||||
|
error "Copy operation failed"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Copy completed successfully"
|
||||||
|
|
||||||
|
# Step 3: Clean up snapshot
|
||||||
|
log "Removing snapshot..."
|
||||||
|
if ! lvremove -f "$SNAPSHOT_PATH"; then
|
||||||
|
warn "Failed to remove snapshot, but backup completed"
|
||||||
|
warn "You may need to manually remove: $SNAPSHOT_PATH"
|
||||||
|
else
|
||||||
|
log "Snapshot cleaned up"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Backup completed successfully!"
|
||||||
|
log "Your data has been copied to: $TARGET_DEVICE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}SUCCESS: Backup completed!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "To verify the backup, you can:"
|
||||||
|
echo "1. Mount $TARGET_DEVICE and check the files"
|
||||||
|
echo "2. Boot from $TARGET_DEVICE if it's bootable"
|
||||||
|
echo ""
|
||||||
1657
simple_backup_gui.py
Executable file
1657
simple_backup_gui.py
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user