Fix AutoInstaller GUI post-installation finalization

- Implement proper root password configuration matching auto-installer.sh:
  * Support preseed file passwords (both plain and crypted)
  * Copy live system root password hash when available
  * Set default password as fallback
  * Proper shadow file management and password aging

- Fix mbetterclient user creation with complete autologin setup:
  * Create user with proper home directory and bash shell
  * Remove password for autologin
  * Configure .bashrc for automatic X session launch on tty1
  * Create .xinitrc for proper X session with MBetterClient
  * Set proper file ownership

- Implement complete inittab configuration for autologin:
  * Configure getty for mbetterclient autologin on tty1
  * Maintain normal getty for other terminals
  * Proper runlevel configuration

- Add system cleanup functionality:
  * Remove live-specific systemd configurations
  * Remove live directories and profile configurations
  * Remove live initramfs hooks

- Enhance post-install cleanup:
  * Remove auto-installer script from installed system
  * Remove auto-installer log files
  * Run post-installation setup scripts if available

These changes ensure the installed system boots correctly with:
- Proper network configuration using selected interface
- Working root password (from live system or default)
- Automatic mbetterclient user login and MBetterClient startup
- Clean system without live-specific configurations
parent 1c403584
...@@ -749,16 +749,16 @@ class InstallerWorker(QThread): ...@@ -749,16 +749,16 @@ class InstallerWorker(QThread):
total_bytes = int(du_result.stdout.split()[0]) total_bytes = int(du_result.stdout.split()[0])
self.log(f"Estimated total size to copy: {total_bytes} bytes ({total_bytes // (1024*1024)} MB)") self.log(f"Estimated total size to copy: {total_bytes} bytes ({total_bytes // (1024*1024)} MB)")
# Estimate copy time based on typical speeds (20-100 MB/s for USB drives) # Estimate copy time based on typical speeds (10-100 MB/s for USB drives)
# Use very conservative estimate of 20 MB/s for realistic estimation # Use very conservative estimate of 5 MB/s for realistic estimation
# This accounts for slower USB drives and system overhead # This accounts for slower USB drives and system overhead
estimated_copy_time = total_bytes / (20 * 1024 * 1024) # seconds estimated_copy_time = total_bytes / (5 * 1024 * 1024) # seconds
self.log(f"Estimated copy time: {estimated_copy_time:.1f} seconds (conservative estimate)") self.log(f"Estimated copy time: {estimated_copy_time:.1f} seconds (conservative estimate)")
except (subprocess.CalledProcessError, ValueError, IndexError): except (subprocess.CalledProcessError, ValueError, IndexError):
# Fallback to time-based estimation if du fails # Fallback to time-based estimation if du fails
total_bytes = None total_bytes = None
estimated_copy_time = 180 # 3 minutes default estimated_copy_time = 1800 # 30 minutes default
self.log("Could not estimate total size, using default 3-minute estimation") self.log("Could not estimate total size, using default 3-minute estimation")
# Use rsync with optimized progress output for better performance # Use rsync with optimized progress output for better performance
...@@ -1087,6 +1087,37 @@ class InstallerWorker(QThread): ...@@ -1087,6 +1087,37 @@ class InstallerWorker(QThread):
self.log(f"Warning: GRUB installation failed: {str(e)}") self.log(f"Warning: GRUB installation failed: {str(e)}")
# Continue with installation despite GRUB failure # Continue with installation despite GRUB failure
# Remove live-specific configurations
self.log("Removing live-specific configurations...")
try:
# Remove live-specific systemd service override
live_config_path = f"{target_mount}/etc/systemd/system/getty@tty1.service.d/live-config.conf"
if os.path.exists(live_config_path):
os.remove(live_config_path)
self.log("Removed live systemd getty override")
# Remove live directory
live_dir = f"{target_mount}/lib/live"
if os.path.exists(live_dir):
import shutil
shutil.rmtree(live_dir)
self.log("Removed live directory")
# Remove live profile configuration
live_profile = f"{target_mount}/etc/profile.d/zz-live-config_xinit.sh"
if os.path.exists(live_profile):
os.remove(live_profile)
self.log("Removed live profile configuration")
# Remove live initramfs hooks
live_hooks_dir = f"{target_mount}/usr/share/initramfs-tools/hooks/live"
if os.path.exists(live_hooks_dir):
shutil.rmtree(live_hooks_dir)
self.log("Removed live initramfs hooks")
except Exception as e:
self.log(f"Warning: Could not remove some live configurations: {e}")
# Clean up bind mounts # Clean up bind mounts
self.log("Cleaning up bind mounts") self.log("Cleaning up bind mounts")
...@@ -1129,7 +1160,7 @@ class InstallerWorker(QThread): ...@@ -1129,7 +1160,7 @@ class InstallerWorker(QThread):
self.log(f"Applying network configuration for {selected_interface} ({interface_type})") self.log(f"Applying network configuration for {selected_interface} ({interface_type})")
# Create interfaces configuration # Create interfaces configuration - same format as auto-installer.sh
interfaces_content = "# Network configuration applied during installation\nauto lo\niface lo inet loopback\n\nauto {selected_interface}\n" interfaces_content = "# Network configuration applied during installation\nauto lo\niface lo inet loopback\n\nauto {selected_interface}\n"
if ip_method == "DHCP (automatic)": if ip_method == "DHCP (automatic)":
...@@ -1187,13 +1218,262 @@ class InstallerWorker(QThread): ...@@ -1187,13 +1218,262 @@ class InstallerWorker(QThread):
self.log("Network configuration applied to target system") self.log("Network configuration applied to target system")
def _configure_root_password(self, target_mount): def _configure_root_password(self, target_mount):
"""Configure root password from preseed or live system""" """Configure root password from preseed or live system - same logic as auto-installer.sh"""
# Implementation similar to original script try:
# Try to get password from preseed file first
preseed_pass = None
preseed_pass_crypted = None
if self.config.get('preseed_file') and os.path.exists(self.config['preseed_file']):
try:
with open(self.config['preseed_file'], 'r') as f:
for line in f:
if 'passwd/root-password' in line and 'password' in line:
if 'crypted' in line:
preseed_pass_crypted = line.split()[-1]
else:
preseed_pass = line.split()[-1]
except Exception as e:
self.log(f"Warning: Could not read preseed file: {e}")
if preseed_pass_crypted:
self.log("Setting root password from crypted preseed file...")
# Extract the crypted password (remove "password " prefix if present)
if preseed_pass_crypted.startswith('password '):
crypt_pass = preseed_pass_crypted[9:] # Remove "password " prefix
else:
crypt_pass = preseed_pass_crypted
# Ensure we have a clean shadow file first - remove any duplicate root entries
shadow_path = f"{target_mount}/etc/shadow"
if os.path.exists(shadow_path):
with open(shadow_path, 'r') as f:
lines = f.readlines()
with open(shadow_path, 'w') as f:
for line in lines:
if not line.startswith('root:'):
f.write(line)
# Add the root entry with the crypted password
with open(shadow_path, 'a') as f:
f.write(f"root:{crypt_pass}:19453:0:99999:7:::\n")
elif preseed_pass:
self.log("Setting root password from preseed file...")
# Set the password using chpasswd
cmd = ['chroot', target_mount, 'chpasswd']
clean_env = self._clean_env()
process = subprocess.Popen(cmd, stdin=subprocess.PIPE, text=True, env=clean_env)
process.communicate(f"root:{preseed_pass}\n")
if process.returncode == 0:
self.log("Root password set from preseed file")
else:
self.log("Warning: Failed to set root password from preseed")
else:
# Try to copy password from live system
live_shadow_path = "/etc/shadow"
if os.path.exists(live_shadow_path):
try:
with open(live_shadow_path, 'r') as f:
for line in f:
if line.startswith('root:'):
live_root_hash = line.split(':')[1]
if live_root_hash and live_root_hash not in ['*', '!', 'x']:
# Copy the live system's root password hash directly to target shadow file
self.log("Copying live system root password to installed system...")
shadow_path = f"{target_mount}/etc/shadow"
if os.path.exists(shadow_path):
with open(shadow_path, 'r') as f:
lines = f.readlines()
with open(shadow_path, 'w') as f:
for line in lines:
if not line.startswith('root:'):
f.write(line)
# Add the live system's root entry
with open(shadow_path, 'a') as f:
f.write(line)
if os.path.exists(shadow_path):
self.log("Live system root password copied successfully")
else:
self.log("Warning: Failed to copy live password, using default")
cmd = ['chroot', target_mount, 'chpasswd']
clean_env = self._clean_env()
process = subprocess.Popen(cmd, stdin=subprocess.PIPE, text=True, env=clean_env)
process.communicate("root:mbetter123\n")
break
except Exception as e:
self.log(f"Warning: Could not copy live password: {e}")
# Set default password if none available
if not preseed_pass and not preseed_pass_crypted:
self.log("No root password available, using default password: mbetter123")
cmd = ['chroot', target_mount, 'chpasswd']
clean_env = self._clean_env()
process = subprocess.Popen(cmd, stdin=subprocess.PIPE, text=True, env=clean_env)
process.communicate("root:mbetter123\n")
# Verify password was set and ensure account is properly configured
self.log("Ensuring root account is properly configured...")
# Make sure the root account is unlocked and has proper password aging
try:
subprocess.run(['chroot', target_mount, 'passwd', '-u', 'root'],
capture_output=True, text=True, env=self._clean_env())
except subprocess.CalledProcessError:
self.log("Warning: Could not unlock root account")
try:
subprocess.run(['chroot', target_mount, 'chage', '-d', '99999', 'root'],
capture_output=True, text=True, env=self._clean_env())
except subprocess.CalledProcessError:
self.log("Warning: Could not remove password expiration")
try:
subprocess.run(['chroot', target_mount, 'chage', '-E', '-1', 'root'],
capture_output=True, text=True, env=self._clean_env())
except subprocess.CalledProcessError:
self.log("Warning: Could not remove account expiration")
try:
subprocess.run(['chroot', target_mount, 'chage', '-m', '0', 'root'],
capture_output=True, text=True, env=self._clean_env())
except subprocess.CalledProcessError:
self.log("Warning: Could not set minimum password age")
try:
subprocess.run(['chroot', target_mount, 'chage', '-M', '99999', 'root'],
capture_output=True, text=True, env=self._clean_env())
except subprocess.CalledProcessError:
self.log("Warning: Could not set maximum password age")
# Verify the shadow entry is correct
shadow_path = f"{target_mount}/etc/shadow"
if os.path.exists(shadow_path):
with open(shadow_path, 'r') as f:
for line in f:
if line.startswith('root:'):
root_pass_field = line.split(':')[1]
if root_pass_field and root_pass_field not in ['x', '*', '!']:
self.log("Root password verification successful")
else:
self.log(f"Warning: Root password appears to be disabled or invalid: {root_pass_field}")
break
else:
self.log("Warning: No shadow file found!")
except Exception as e:
self.log(f"Warning: Root password configuration failed: {e}")
# Try to set a default password as fallback
try:
cmd = ['chroot', target_mount, 'chpasswd']
clean_env = self._clean_env()
process = subprocess.Popen(cmd, stdin=subprocess.PIPE, text=True, env=clean_env)
process.communicate("root:mbetter123\n")
self.log("Set default root password as fallback")
except Exception as e2:
self.log(f"Warning: Even fallback password failed: {e2}")
self.log("Root password configuration completed") self.log("Root password configuration completed")
def _configure_mbetterclient_user(self, target_mount): def _configure_mbetterclient_user(self, target_mount):
"""Create and configure mbetterclient user""" """Create and configure mbetterclient user for autologin - same logic as auto-installer.sh"""
# Implementation similar to original script try:
self.log("Creating mbetterclient user for autologin...")
# Create mbetterclient user with no password for autologin
try:
cmd = ['chroot', target_mount, 'useradd', '-m', '-s', '/bin/bash', 'mbetterclient']
clean_env = self._clean_env()
result = subprocess.run(cmd, capture_output=True, text=True, env=clean_env)
if result.returncode != 0 and 'already exists' not in result.stderr:
self.log(f"Warning: useradd failed: {result.stderr}")
except subprocess.CalledProcessError as e:
self.log(f"Warning: Could not create mbetterclient user: {e}")
# Remove password for autologin
try:
cmd = ['chroot', target_mount, 'passwd', '-d', 'mbetterclient']
clean_env = self._clean_env()
result = subprocess.run(cmd, capture_output=True, text=True, env=clean_env)
if result.returncode != 0:
self.log(f"Warning: Could not remove mbetterclient password: {result.stderr}")
except subprocess.CalledProcessError as e:
self.log(f"Warning: Could not remove mbetterclient password: {e}")
# Configure mbetterclient user to launch startx on login
self.log("Configuring mbetterclient user to launch startx...")
# Create .bashrc for mbetterclient
bashrc_content = '''# X session autolaunch configuration
if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then
# SSH session - don't autolaunch
return
fi
# Check if we're on tty1 (autologin terminal)
if [ "$(tty)" = "/dev/tty1" ]; then
# Set XKB environment variables to fix keyboard issues
export XKB_DEFAULT_LAYOUT="us"
export XKB_DEFAULT_MODEL="pc105"
export XKB_DEFAULT_VARIANT=""
export XKB_DEFAULT_OPTIONS=""
# Launch X session (MBetterClient will be started by .xinitrc)
exec startx
fi
'''
bashrc_path = f"{target_mount}/home/mbetterclient/.bashrc"
try:
with open(bashrc_path, 'w') as f:
f.write(bashrc_content)
self.log("Created .bashrc for mbetterclient")
except Exception as e:
self.log(f"Warning: Could not create .bashrc: {e}")
# Create .xinitrc for proper X session startup
xinitrc_content = '''#!/bin/sh
# X session initialization for mbetterclient
# Set keyboard layout
setxkbmap us
# Launch Openbox window manager
exec openbox-session &
# Give Openbox a moment to start
sleep 1
# Launch MBetterClient in a terminal
xterm -e '/usr/local/bin/MbetterClient_wrapper.sh --ssl --web-host 0.0.0.0'
'''
xinitrc_path = f"{target_mount}/home/mbetterclient/.xinitrc"
try:
with open(xinitrc_path, 'w') as f:
f.write(xinitrc_content)
# Make .xinitrc executable
os.chmod(xinitrc_path, 0o755)
self.log("Created .xinitrc for mbetterclient")
except Exception as e:
self.log(f"Warning: Could not create .xinitrc: {e}")
# Set proper ownership
try:
cmd = ['chroot', target_mount, 'chown', 'mbetterclient:mbetterclient',
'/home/mbetterclient/.bashrc', '/home/mbetterclient/.xinitrc']
clean_env = self._clean_env()
result = subprocess.run(cmd, capture_output=True, text=True, env=clean_env)
if result.returncode != 0:
self.log(f"Warning: Could not set ownership: {result.stderr}")
else:
self.log("Set proper ownership for mbetterclient files")
except subprocess.CalledProcessError as e:
self.log(f"Warning: Could not set ownership: {e}")
except Exception as e:
self.log(f"Warning: mbetterclient user configuration failed: {e}")
self.log("Mbetterclient user configuration completed") self.log("Mbetterclient user configuration completed")
def _create_fstab(self, target_mount): def _create_fstab(self, target_mount):
...@@ -1202,8 +1482,82 @@ class InstallerWorker(QThread): ...@@ -1202,8 +1482,82 @@ class InstallerWorker(QThread):
self.log("Fstab created") self.log("Fstab created")
def _configure_inittab(self, target_mount): def _configure_inittab(self, target_mount):
"""Configure inittab for autologin""" """Configure inittab for mbetterclient autologin - same logic as auto-installer.sh"""
# Implementation similar to original script try:
self.log("Configuring inittab for mbetterclient autologin...")
inittab_content = '''# /etc/inittab: init(8) configuration.
# $Id: inittab,v 1.91 2002/01/25 13:35:21 miquels Exp $
# The default runlevel.
id:2:initdefault:
# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS
# What to do in single-user mode.
~~:S:wait:/sbin/sulogin
# /etc/init.d executes the S and K scripts upon change
# of runlevel.
#
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevel 2-5 are multi-user.
# Runlevel 6 is reboot.
l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6
# Normally not reached, but fallthrough in case of emergency.
z6:6:respawn:/sbin/sulogin
# /sbin/getty invocations for the runlevels.
#
# The "id" field MUST be the same as the last
# characters of the device (after "tty").
#
# Format:
# <id>:<runlevels>:<action>:<process>
#
# Autologin for mbetterclient on tty1
1:2345:respawn:/sbin/getty --autologin mbetterclient --noclear 38400 tty1
# Normal getty for other terminals
2:23:respawn:/sbin/getty 38400 tty2
3:23:respawn:/sbin/getty 38400 tty3
4:23:respawn:/sbin/getty 38400 tty4
5:23:respawn:/sbin/getty 38400 tty5
6:23:respawn:/sbin/getty 38400 tty6
# Example how to put a getty on a serial line (for a terminal)
#
#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
#T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100
# Example how to put a getty on a modem line.
#
#T3:23:respawn:/sbin/mgetty -x0 -s 57600 ttyS3
'''
inittab_path = f"{target_mount}/etc/inittab"
try:
with open(inittab_path, 'w') as f:
f.write(inittab_content)
self.log("Configured inittab for mbetterclient autologin")
except Exception as e:
self.log(f"Warning: Could not configure inittab: {e}")
except Exception as e:
self.log(f"Warning: inittab configuration failed: {e}")
self.log("Inittab configured") self.log("Inittab configured")
def _generate_ssh_keys(self, target_mount): def _generate_ssh_keys(self, target_mount):
...@@ -1263,8 +1617,40 @@ class InstallerWorker(QThread): ...@@ -1263,8 +1617,40 @@ class InstallerWorker(QThread):
self.log("Continuing installation despite GRUB installation failure") self.log("Continuing installation despite GRUB installation failure")
def run_post_install(self, target_mount): def run_post_install(self, target_mount):
# Run post-install script if available # Run post-install cleanup - remove auto-installer from installed system
self.log("Post-install completed") self.log("Running post-installation cleanup...")
try:
# Remove auto-installer script from installed system
auto_installer_path = f"{target_mount}/usr/local/bin/auto-installer.sh"
if os.path.exists(auto_installer_path):
os.remove(auto_installer_path)
self.log("Removed auto-installer script from installed system")
# Remove auto-installer log from installed system
auto_installer_log = f"{target_mount}/var/log/auto-installer.log"
if os.path.exists(auto_installer_log):
os.remove(auto_installer_log)
self.log("Removed auto-installer log from installed system")
# Run our post-install script if available
post_install_script = "/cdrom/setup-installed-system.sh"
if os.path.exists(post_install_script):
self.log("Running post-installation setup script...")
try:
cmd = ['bash', post_install_script, target_mount]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
self.log("Post-installation setup script completed successfully")
else:
self.log(f"Warning: Post-installation setup script failed: {result.stderr}")
except Exception as e:
self.log(f"Warning: Could not run post-installation setup script: {e}")
except Exception as e:
self.log(f"Warning: Post-install cleanup failed: {e}")
self.log("Post-install cleanup completed")
class AutoInstallerGUI(QMainWindow): class AutoInstallerGUI(QMainWindow):
def __init__(self): def __init__(self):
......
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