Fix AutoInstaller GUI issues and improve progress tracking

- Fix AttributeError: 'network_label' -> 'interface_label' in set_defaults()
- Remove incorrect PyInstaller hidden imports (QShortcut, QKeySequence)
- Add 10-second progress re-estimation with real-time ETA display
- Update build.sh to properly handle AutoInstallerGUI binary naming
- Update README files with recent fixes and improvements
parent 42416d0f
...@@ -10,6 +10,7 @@ This project creates a custom Debian live ISO with advanced autologin, automatic ...@@ -10,6 +10,7 @@ This project creates a custom Debian live ISO with advanced autologin, automatic
-**USB boot compatible** - Works from CD/DVD or USB drives -**USB boot compatible** - Works from CD/DVD or USB drives
-**Offline installation** - No internet connection required -**Offline installation** - No internet connection required
-**Maximum WiFi support** - Comprehensive firmware packages included -**Maximum WiFi support** - Comprehensive firmware packages included
-**Graphical AutoInstaller GUI** - PyQt6-based installer with full network configuration
### **Installed System** ### **Installed System**
-**Auto-login as root** with proper password authentication -**Auto-login as root** with proper password authentication
...@@ -24,8 +25,23 @@ This project creates a custom Debian live ISO with advanced autologin, automatic ...@@ -24,8 +25,23 @@ This project creates a custom Debian live ISO with advanced autologin, automatic
-**Standalone executables** - No Python installation required for end users -**Standalone executables** - No Python installation required for end users
-**Safe USB detection** - Only shows removable devices -**Safe USB detection** - Only shows removable devices
### **AutoInstaller GUI**
-**PyQt6 fullscreen GUI** - Modern graphical installer interface
-**Automatic disk detection** - Smart USB boot device detection and target disk selection
-**Full network configuration** - Ethernet and WiFi with SSID scanning and security options
-**Timezone selection** - Graphical continent/city picker
-**Real-time progress** - Live rsync output and progress tracking
-**Command line options** - `--no-fullscreen` for testing in windowed mode
-**PyInstaller integration** - Single executable binary for live system
## 📦 What's New in This Version ## 📦 What's New in This Version
### **🔧 Recent Fixes & Improvements**
- **Fixed AutoInstaller GUI AttributeError** - Corrected `network_label` reference to `interface_label`
- **Fixed PyInstaller hidden imports** - Removed incorrect `QShortcut` and `QKeySequence` imports
- **Enhanced progress estimation** - Re-estimates copy progress every 10 seconds with real-time ETA
- **Improved build integration** - Updated build.sh to properly handle AutoInstaller GUI binary naming
### **🔧 Autologin Fixes** ### **🔧 Autologin Fixes**
- **Fixed missing autologin infrastructure** - Created required groups and PAM configurations - **Fixed missing autologin infrastructure** - Created required groups and PAM configurations
- **Complete LightDM setup** - Proper authentication for both live and installed systems - **Complete LightDM setup** - Proper authentication for both live and installed systems
...@@ -165,12 +181,18 @@ pyinstaller usb_creator.spec ...@@ -165,12 +181,18 @@ pyinstaller usb_creator.spec
- [`config/includes.chroot/root/.config/openbox/autostart`](config/includes.chroot/root/.config/openbox/autostart) - Auto-start configuration - [`config/includes.chroot/root/.config/openbox/autostart`](config/includes.chroot/root/.config/openbox/autostart) - Auto-start configuration
### **USB Creator Tools:** ### **USB Creator Tools:**
- [`usb_creator_gui.py`](usb_creator_gui.py) - PyQt6 GUI application - [`usb_creator_gui.py`](usb_creator_gui.py) - PyQt6 GUI application
- [`build_usb_creator.py`](build_usb_creator.py) - PyInstaller build script - [`build_usb_creator.py`](build_usb_creator.py) - PyInstaller build script
- [`setup_usb_creator.py`](setup_usb_creator.py) - One-click setup - [`setup_usb_creator.py`](setup_usb_creator.py) - One-click setup
- [`requirements.txt`](requirements.txt) - Python dependencies - [`requirements.txt`](requirements.txt) - Python dependencies
- [`USB_CREATOR_README.md`](USB_CREATOR_README.md) - Detailed USB creator documentation - [`USB_CREATOR_README.md`](USB_CREATOR_README.md) - Detailed USB creator documentation
### **AutoInstaller GUI:**
- [`autoinstaller_gui/`](autoinstaller_gui/) - Complete GUI installer directory
- [`autoinstaller_gui/autoinstallergui.py`](autoinstaller_gui/autoinstallergui.py) - Main PyQt6 GUI application
- [`autoinstaller_gui/build_autoinstaller_gui.py`](autoinstaller_gui/build_autoinstaller_gui.py) - PyInstaller build script
- [`autoinstaller_gui/README.md`](autoinstaller_gui/README.md) - GUI-specific documentation
### **Legacy Tools:** ### **Legacy Tools:**
- [`create_usb.sh`](create_usb.sh) / [`create_usb.bat`](create_usb.bat) - Command-line USB creation - [`create_usb.sh`](create_usb.sh) / [`create_usb.bat`](create_usb.bat) - Command-line USB creation
- [`customize_iso.sh`](customize_iso.sh) - Post-build ISO customization - [`customize_iso.sh`](customize_iso.sh) - Post-build ISO customization
......
...@@ -3,6 +3,18 @@ ...@@ -3,6 +3,18 @@
The AutoInstaller GUI is a PyQt6-based fullscreen application that provides a graphical interface to the command-line `auto-installer.sh` script. It handles all interactive parts (timezone, network) in a user-friendly way while running the installation in the background. The AutoInstaller GUI is a PyQt6-based fullscreen application that provides a graphical interface to the command-line `auto-installer.sh` script. It handles all interactive parts (timezone, network) in a user-friendly way while running the installation in the background.
## 🆕 Recent Updates
### **Bug Fixes**
- **Fixed AttributeError**: Corrected `self.network_label` reference to `self.interface_label` in `set_defaults()` method
- **Fixed PyInstaller Build**: Removed incorrect hidden imports (`QShortcut`, `QKeySequence`) that were causing build errors
- **Fixed Binary Naming**: Updated build.sh to properly detect and copy the `AutoInstallerGUI` binary
### **Performance Improvements**
- **Enhanced Progress Tracking**: Re-estimates copy progress every 10 seconds based on actual transfer rate
- **Real-time ETA Display**: Shows current speed, remaining data, and estimated completion time
- **Better Progress Accuracy**: Uses dynamic rate calculation instead of static time-based estimation
## 📋 Prerequisites ## 📋 Prerequisites
### For Development/Running the GUI ### For Development/Running the GUI
...@@ -30,7 +42,7 @@ Build first, then run the binary: ...@@ -30,7 +42,7 @@ Build first, then run the binary:
```bash ```bash
cd /working/mlivecd cd /working/mlivecd
python3 autoinstaller_gui/build_autoinstaller_gui.py python3 autoinstaller_gui/build_autoinstaller_gui.py
./dist/AutoInstallerGUI ./autoinstaller_gui/dist/AutoInstallerGUI
``` ```
### GUI Interface ### GUI Interface
...@@ -82,7 +94,7 @@ python3 autoinstaller_gui/build_autoinstaller_gui.py ...@@ -82,7 +94,7 @@ python3 autoinstaller_gui/build_autoinstaller_gui.py
- `--upx=True`: Compresses the binary for smaller size - `--upx=True`: Compresses the binary for smaller size
2. **Runs PyInstaller**: Executes `pyinstaller autoinstaller_gui.spec` 2. **Runs PyInstaller**: Executes `pyinstaller autoinstaller_gui.spec`
3. **Cleans up**: Removes the temporary .spec file 3. **Cleans up**: Removes the temporary .spec file
4. **Output**: Creates `dist/AutoInstallerGUI` (single Linux binary) 4. **Output**: Creates `autoinstaller_gui/dist/AutoInstallerGUI` (single Linux binary)
### Binary Features ### Binary Features
- **Size**: ~50-100MB (compressed with UPX) - **Size**: ~50-100MB (compressed with UPX)
...@@ -123,7 +135,7 @@ The `InstallerWorker` class has placeholder methods (`detect_usb_device`, `detec ...@@ -123,7 +135,7 @@ The `InstallerWorker` class has placeholder methods (`detect_usb_device`, `detec
### Testing the GUI ### Testing the GUI
1. **Development**: `python3 autoinstaller_gui/autoinstallergui.py` (test without sudo first) 1. **Development**: `python3 autoinstaller_gui/autoinstallergui.py` (test without sudo first)
2. **With sudo**: `sudo python3 autoinstaller_gui/autoinstallergui.py` (full functionality) 2. **With sudo**: `sudo python3 autoinstaller_gui/autoinstallergui.py` (full functionality)
3. **Standalone**: Build binary and test `./dist/AutoInstallerGUI` (requires sudo for real installation) 3. **Standalone**: Build binary and test `./autoinstaller_gui/dist/AutoInstallerGUI` (requires sudo for real installation)
### Error Logging ### Error Logging
- GUI shows real-time output from all subprocess calls - GUI shows real-time output from all subprocess calls
......
...@@ -721,9 +721,38 @@ class InstallerWorker(QThread): ...@@ -721,9 +721,38 @@ class InstallerWorker(QThread):
self.log("Target mounted") self.log("Target mounted")
def copy_live_system(self, target_mount): def copy_live_system(self, target_mount):
"""Copy live system with dynamic progress updates""" """Copy live system with dynamic progress updates based on actual data transfer"""
self.log("Starting system copy with dynamic progress...") self.log("Starting system copy with dynamic progress...")
# First, estimate total size to be copied
self.log("Estimating total size of files to copy...")
try:
# Use du to estimate total size, excluding the same directories as rsync
du_cmd = [
'du', '-sb',
'--exclude=/proc', '--exclude=/sys', '--exclude=/dev',
'--exclude=/tmp', '--exclude=/run', '--exclude=/mnt',
'--exclude=/media', '--exclude=/lost+found',
'--exclude=/lib/live', '--exclude=/cdrom',
'--exclude=/var/cache/apt/archives/*.deb',
'--exclude=/var/log/*', '--exclude=/var/log/auto-installer.log',
'/'
]
du_result = subprocess.run(du_cmd, capture_output=True, text=True, check=True)
total_bytes = int(du_result.stdout.split()[0])
self.log(f"Estimated total size to copy: {total_bytes} bytes ({total_bytes // (1024*1024)} MB)")
# Estimate copy time based on typical speeds (50-200 MB/s for modern systems)
# Use conservative estimate of 100 MB/s
estimated_copy_time = total_bytes / (100 * 1024 * 1024) # seconds
self.log(f"Estimated copy time: {estimated_copy_time:.1f} seconds")
except (subprocess.CalledProcessError, ValueError, IndexError):
# Fallback to time-based estimation if du fails
total_bytes = None
estimated_copy_time = 180 # 3 minutes default
self.log("Could not estimate total size, using default 3-minute estimation")
# Use rsync with progress output for dynamic updates # Use rsync with progress output for dynamic updates
# Exclude the target directory to prevent copying into itself # Exclude the target directory to prevent copying into itself
target_exclude = f'--exclude={target_mount}' target_exclude = f'--exclude={target_mount}'
...@@ -756,8 +785,12 @@ class InstallerWorker(QThread): ...@@ -756,8 +785,12 @@ class InstallerWorker(QThread):
copy_progress_range = copy_progress_end - copy_progress_start copy_progress_range = copy_progress_end - copy_progress_start
last_update_time = 0 last_update_time = 0
total_files = 0 start_time = time.time()
processed_files = 0 last_reestimate_time = start_time
bytes_copied = 0
estimated_copy_time = 180 # Default fallback
last_bytes_copied = 0
last_reestimate_bytes = 0
while True: while True:
# Check if process is still running # Check if process is still running
...@@ -783,29 +816,18 @@ class InstallerWorker(QThread): ...@@ -783,29 +816,18 @@ class InstallerWorker(QThread):
line = output.strip() line = output.strip()
self.copy_progress.emit(line) self.copy_progress.emit(line)
# Parse rsync progress for dynamic updates # Parse rsync progress for actual bytes copied
# Look for lines like: "12,345 100% 123.45MB/s 0:12:34 (xfr#1234, to-chk=5678/9012)" # Look for lines like: "1,234,567 100% 123.45MB/s 0:00:00 (xfr#1, to-chk=0/1)"
if '%' in line and ('xfr#' in line or 'to-chk=' in line): if 'xfr#' in line and 'to-chk=' in line:
try: try:
# Extract progress information # Extract bytes from the beginning of the line
parts = line.split() parts = line.strip().split()
for part in parts: if parts and parts[0].replace(',', '').isdigit():
if 'xfr#' in part: # Remove commas and try to parse as number
# Extract transferred files count bytes_str = parts[0].replace(',', '')
xfr_part = part.split('xfr#')[1].split(',')[0] new_bytes = int(bytes_str)
processed_files = int(xfr_part) if xfr_part.isdigit() else processed_files if new_bytes > bytes_copied: # Only update if we have more bytes
elif 'to-chk=' in part: bytes_copied = new_bytes
# Extract total files count
chk_part = part.split('to-chk=')[1].split('/')[1] if '/' in part else '0'
total_files = int(chk_part) if chk_part.isdigit() else total_files
# Calculate progress percentage within copy range
if total_files > 0:
file_progress = processed_files / total_files
current_progress = copy_progress_start + (file_progress * copy_progress_range)
current_progress = min(current_progress, copy_progress_end)
self.progress_updated.emit(int(current_progress))
except (ValueError, IndexError): except (ValueError, IndexError):
pass pass
...@@ -815,10 +837,69 @@ class InstallerWorker(QThread): ...@@ -815,10 +837,69 @@ class InstallerWorker(QThread):
# Handle non-blocking read errors gracefully # Handle non-blocking read errors gracefully
pass pass
# Send periodic status updates to keep GUI responsive # Send progress updates based on actual bytes copied or fallback to time-based
current_time = time.time() current_time = time.time()
if current_time - last_update_time > 2.0: # Every 2 seconds elapsed_time = current_time - start_time
self.copy_progress.emit("Copying system files... (please wait)")
# Re-estimate progress every 10 seconds based on actual transfer rate
if current_time - last_reestimate_time >= 10.0 and bytes_copied > last_reestimate_bytes:
bytes_since_last = bytes_copied - last_reestimate_bytes
time_since_last = current_time - last_reestimate_time
if time_since_last > 0:
current_rate = bytes_since_last / time_since_last # bytes per second
if current_rate > 0 and total_bytes:
remaining_bytes = total_bytes - bytes_copied
new_estimated_time = remaining_bytes / current_rate
estimated_copy_time = elapsed_time + new_estimated_time
# Update progress text with 10-second estimation
rate_mb_s = current_rate / (1024 * 1024)
remaining_mb = remaining_bytes / (1024 * 1024)
eta_minutes = int(new_estimated_time // 60)
eta_seconds = int(new_estimated_time % 60)
progress_text = f"Copying... {rate_mb_s:.1f} MB/s, ETA: {eta_minutes}:{eta_seconds:02d}, {remaining_mb:.0f} MB remaining"
self.copy_progress.emit(progress_text)
last_reestimate_time = current_time
last_reestimate_bytes = bytes_copied
# Use adaptive time-based progress estimation based on estimated copy time
if estimated_copy_time > 0:
# Use the estimated copy time for more accurate progression
time_progress = min(elapsed_time / estimated_copy_time, 0.95)
else:
# Fallback to adaptive time-based estimation
if elapsed_time < 60: # First minute: 0-40% of copy progress
time_progress = (elapsed_time / 60) * 0.4
elif elapsed_time < 120: # Next minute: 40-70% of copy progress
time_progress = 0.4 + ((elapsed_time - 60) / 60) * 0.3
elif elapsed_time < 240: # Next 2 minutes: 70-90% of copy progress
time_progress = 0.7 + ((elapsed_time - 120) / 120) * 0.2
else: # After 4 minutes: 90-95% of copy progress
time_progress = 0.9 + min((elapsed_time - 240) / 120, 0.05)
current_progress = copy_progress_start + (time_progress * copy_progress_range)
current_progress = min(current_progress, copy_progress_end - 2) # Leave room for final update
# Update progress more frequently during copy (every 1% change or 3 seconds)
should_update = False
if hasattr(self, '_last_progress'):
progress_diff = abs(current_progress - self._last_progress)
time_since_last_update = current_time - getattr(self, '_last_update_time', 0)
if progress_diff >= 1.0 or time_since_last_update >= 3.0:
should_update = True
else:
should_update = True
if should_update:
self.progress_updated.emit(int(current_progress))
self._last_progress = current_progress
self._last_update_time = current_time
# Send periodic status updates to keep GUI responsive (less verbose)
if current_time - last_update_time > 5.0: # Every 5 seconds instead of 2
self.copy_progress.emit("Copying system files...")
last_update_time = current_time last_update_time = current_time
# Allow GUI event processing by yielding control # Allow GUI event processing by yielding control
...@@ -838,6 +919,17 @@ class InstallerWorker(QThread): ...@@ -838,6 +919,17 @@ class InstallerWorker(QThread):
def configure_target_system(self, target_mount): def configure_target_system(self, target_mount):
"""Configure target system - same logic as auto-installer.sh""" """Configure target system - same logic as auto-installer.sh"""
# Create necessary directories for bind mounts
self.log("Creating necessary directories for bind mounts")
os.makedirs(f'{target_mount}/proc', exist_ok=True)
os.makedirs(f'{target_mount}/sys', exist_ok=True)
os.makedirs(f'{target_mount}/dev', exist_ok=True)
os.makedirs(f'{target_mount}/dev/pts', exist_ok=True)
os.makedirs(f'{target_mount}/tmp', exist_ok=True)
os.makedirs(f'{target_mount}/run', exist_ok=True)
os.makedirs(f'{target_mount}/mnt', exist_ok=True)
os.makedirs(f'{target_mount}/media', exist_ok=True)
# Bind mounts for configuration # Bind mounts for configuration
self.log("Setting up bind mounts for target configuration") self.log("Setting up bind mounts for target configuration")
subprocess.run(['mount', '-t', 'proc', 'proc', f'{target_mount}/proc'], check=True) subprocess.run(['mount', '-t', 'proc', 'proc', f'{target_mount}/proc'], check=True)
...@@ -1183,14 +1275,31 @@ class AutoInstallerGUI(QMainWindow): ...@@ -1183,14 +1275,31 @@ class AutoInstallerGUI(QMainWindow):
self.network_checkbox.setChecked(True) self.network_checkbox.setChecked(True)
self.toggle_network_config(Qt.CheckState.Checked.value) self.toggle_network_config(Qt.CheckState.Checked.value)
# Auto-detect and set default network interface
self.detect_default_network()
# Set default IP method to DHCP # Set default IP method to DHCP
self.ip_method_combo.setCurrentText("DHCP (automatic)") self.ip_method_combo.setCurrentText("DHCP (automatic)")
# Auto-detect and set default installation disk # Set initial labels to show detection is in progress
self.detect_default_disk() self.interface_label.setText("Detecting...")
self.disk_label.setText("Detecting...")
# Schedule detection to happen after GUI is fully initialized
QTimer.singleShot(100, self.perform_initial_detection)
def perform_initial_detection(self):
"""Perform initial hardware detection after GUI is fully initialized"""
try:
# Auto-detect and set default network interface
self.detect_default_network()
except Exception as e:
print(f"Network detection failed: {e}")
self.interface_label.setText("Detection failed - click to select manually")
try:
# Auto-detect and set default installation disk
self.detect_default_disk()
except Exception as e:
print(f"Disk detection failed: {e}")
self.disk_label.setText("Detection failed - click to select manually")
def detect_default_network(self): def detect_default_network(self):
"""Detect and set default network interface""" """Detect and set default network interface"""
......
...@@ -23,7 +23,7 @@ a = Analysis( ...@@ -23,7 +23,7 @@ a = Analysis(
pathex=['.'], pathex=['.'],
binaries=[], binaries=[],
datas=[], datas=[],
hiddenimports=['PyQt6.QtCore', 'PyQt6.QtGui', 'PyQt6.QtWidgets', 'PyQt6.QtGui.QShortcut', 'PyQt6.QtGui.QKeySequence', 'subprocess', 'threading', 'select', 'fcntl'], hiddenimports=['PyQt6.QtCore', 'PyQt6.QtGui', 'PyQt6.QtWidgets', 'subprocess', 'threading', 'select', 'fcntl'],
hookspath=[], hookspath=[],
hooksconfig={{}}, hooksconfig={{}},
runtime_hooks=[], runtime_hooks=[],
......
...@@ -133,11 +133,11 @@ if [ $BUILD_EXIT_CODE -eq 0 ]; then ...@@ -133,11 +133,11 @@ if [ $BUILD_EXIT_CODE -eq 0 ]; then
if [ -d "autoinstaller_gui" ] && [ -f "autoinstaller_gui/build_autoinstaller_gui.py" ]; then if [ -d "autoinstaller_gui" ] && [ -f "autoinstaller_gui/build_autoinstaller_gui.py" ]; then
cd autoinstaller_gui cd autoinstaller_gui
python3 build_autoinstaller_gui.py python3 build_autoinstaller_gui.py
if [ -f "dist/autoinstallergui" ]; then if [ -f "dist/AutoInstallerGUI" ]; then
echo "AutoInstaller GUI binary built successfully" echo "AutoInstaller GUI binary built successfully"
# Copy binary to live system # Copy binary to live system
mkdir -p ../config/includes.chroot/usr/local/bin mkdir -p ../config/includes.chroot/usr/local/bin
cp dist/autoinstallergui ../config/includes.chroot/usr/local/bin/autoinstallergui cp dist/AutoInstallerGUI ../config/includes.chroot/usr/local/bin/autoinstallergui
chmod +x ../config/includes.chroot/usr/local/bin/autoinstallergui chmod +x ../config/includes.chroot/usr/local/bin/autoinstallergui
echo "AutoInstaller GUI binary copied to live system" echo "AutoInstaller GUI binary copied to live system"
else else
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment