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
-**USB boot compatible** - Works from CD/DVD or USB drives
-**Offline installation** - No internet connection required
-**Maximum WiFi support** - Comprehensive firmware packages included
-**Graphical AutoInstaller GUI** - PyQt6-based installer with full network configuration
### **Installed System**
-**Auto-login as root** with proper password authentication
......@@ -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
-**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
### **🔧 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**
- **Fixed missing autologin infrastructure** - Created required groups and PAM configurations
- **Complete LightDM setup** - Proper authentication for both live and installed systems
......@@ -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
### **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
- [`setup_usb_creator.py`](setup_usb_creator.py) - One-click setup
- [`requirements.txt`](requirements.txt) - Python dependencies
- [`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:**
- [`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
......
......@@ -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.
## 🆕 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
### For Development/Running the GUI
......@@ -30,7 +42,7 @@ Build first, then run the binary:
```bash
cd /working/mlivecd
python3 autoinstaller_gui/build_autoinstaller_gui.py
./dist/AutoInstallerGUI
./autoinstaller_gui/dist/AutoInstallerGUI
```
### GUI Interface
......@@ -82,7 +94,7 @@ python3 autoinstaller_gui/build_autoinstaller_gui.py
- `--upx=True`: Compresses the binary for smaller size
2. **Runs PyInstaller**: Executes `pyinstaller autoinstaller_gui.spec`
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
- **Size**: ~50-100MB (compressed with UPX)
......@@ -123,7 +135,7 @@ The `InstallerWorker` class has placeholder methods (`detect_usb_device`, `detec
### Testing the GUI
1. **Development**: `python3 autoinstaller_gui/autoinstallergui.py` (test without sudo first)
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
- GUI shows real-time output from all subprocess calls
......
......@@ -721,9 +721,38 @@ class InstallerWorker(QThread):
self.log("Target mounted")
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...")
# 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
# Exclude the target directory to prevent copying into itself
target_exclude = f'--exclude={target_mount}'
......@@ -756,8 +785,12 @@ class InstallerWorker(QThread):
copy_progress_range = copy_progress_end - copy_progress_start
last_update_time = 0
total_files = 0
processed_files = 0
start_time = time.time()
last_reestimate_time = start_time
bytes_copied = 0
estimated_copy_time = 180 # Default fallback
last_bytes_copied = 0
last_reestimate_bytes = 0
while True:
# Check if process is still running
......@@ -783,29 +816,18 @@ class InstallerWorker(QThread):
line = output.strip()
self.copy_progress.emit(line)
# Parse rsync progress for dynamic updates
# Look for lines like: "12,345 100% 123.45MB/s 0:12:34 (xfr#1234, to-chk=5678/9012)"
if '%' in line and ('xfr#' in line or 'to-chk=' in line):
# Parse rsync progress for actual bytes copied
# Look for lines like: "1,234,567 100% 123.45MB/s 0:00:00 (xfr#1, to-chk=0/1)"
if 'xfr#' in line and 'to-chk=' in line:
try:
# Extract progress information
parts = line.split()
for part in parts:
if 'xfr#' in part:
# Extract transferred files count
xfr_part = part.split('xfr#')[1].split(',')[0]
processed_files = int(xfr_part) if xfr_part.isdigit() else processed_files
elif 'to-chk=' in part:
# 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))
# Extract bytes from the beginning of the line
parts = line.strip().split()
if parts and parts[0].replace(',', '').isdigit():
# Remove commas and try to parse as number
bytes_str = parts[0].replace(',', '')
new_bytes = int(bytes_str)
if new_bytes > bytes_copied: # Only update if we have more bytes
bytes_copied = new_bytes
except (ValueError, IndexError):
pass
......@@ -815,10 +837,69 @@ class InstallerWorker(QThread):
# Handle non-blocking read errors gracefully
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()
if current_time - last_update_time > 2.0: # Every 2 seconds
self.copy_progress.emit("Copying system files... (please wait)")
elapsed_time = current_time - start_time
# 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
# Allow GUI event processing by yielding control
......@@ -838,6 +919,17 @@ class InstallerWorker(QThread):
def configure_target_system(self, target_mount):
"""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
self.log("Setting up bind mounts for target configuration")
subprocess.run(['mount', '-t', 'proc', 'proc', f'{target_mount}/proc'], check=True)
......@@ -1183,14 +1275,31 @@ class AutoInstallerGUI(QMainWindow):
self.network_checkbox.setChecked(True)
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
self.ip_method_combo.setCurrentText("DHCP (automatic)")
# Auto-detect and set default installation disk
self.detect_default_disk()
# Set initial labels to show detection is in progress
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):
"""Detect and set default network interface"""
......
......@@ -23,7 +23,7 @@ a = Analysis(
pathex=['.'],
binaries=[],
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=[],
hooksconfig={{}},
runtime_hooks=[],
......
......@@ -133,11 +133,11 @@ if [ $BUILD_EXIT_CODE -eq 0 ]; then
if [ -d "autoinstaller_gui" ] && [ -f "autoinstaller_gui/build_autoinstaller_gui.py" ]; then
cd autoinstaller_gui
python3 build_autoinstaller_gui.py
if [ -f "dist/autoinstallergui" ]; then
if [ -f "dist/AutoInstallerGUI" ]; then
echo "AutoInstaller GUI binary built successfully"
# Copy binary to live system
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
echo "AutoInstaller GUI binary copied to live system"
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