diff --git a/iridium_installer/backend/disk.py b/iridium_installer/backend/disk.py index 14b27ae..30a3759 100644 --- a/iridium_installer/backend/disk.py +++ b/iridium_installer/backend/disk.py @@ -4,23 +4,28 @@ import os logger = logging.getLogger(__name__) +# Import network logging for critical disk operations +from .network_logging import log_to_discord + + +def log_disk_operation(operation, status="start", details=""): + log_to_discord("INFO", f"DISK_{operation}_{status}: {details}", module="disk") + + class CommandResult: def __init__(self, stdout, stderr, returncode): self.stdout = stdout self.stderr = stderr self.returncode = returncode + def run_command(cmd, check=True): logger.info(f"Running command: {' '.join(cmd)}") - + process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - bufsize=1 + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1 ) - + stdout_lines = [] stderr_lines = [] @@ -32,25 +37,33 @@ def run_command(cmd, check=True): line_list.append(line) import threading - t1 = threading.Thread(target=read_stream, args=(process.stdout, stdout_lines, logger.info)) - t2 = threading.Thread(target=read_stream, args=(process.stderr, stderr_lines, logger.error)) - + + t1 = threading.Thread( + target=read_stream, args=(process.stdout, stdout_lines, logger.info) + ) + t2 = threading.Thread( + target=read_stream, args=(process.stderr, stderr_lines, logger.error) + ) + t1.start() t2.start() - + t1.join() t2.join() - + returncode = process.wait() - + stdout_str = "".join(stdout_lines) stderr_str = "".join(stderr_lines) - + if check and returncode != 0: - raise subprocess.CalledProcessError(returncode, cmd, output=stdout_str, stderr=stderr_str) - + raise subprocess.CalledProcessError( + returncode, cmd, output=stdout_str, stderr=stderr_str + ) + return CommandResult(stdout_str, stderr_str, returncode) + def get_partition_device(disk_device, partition_number): """ Returns the partition device path. @@ -60,6 +73,7 @@ def get_partition_device(disk_device, partition_number): return f"{disk_device}p{partition_number}" return f"{disk_device}{partition_number}" + def get_total_memory(): """Returns total system memory in bytes.""" try: @@ -72,6 +86,7 @@ def get_total_memory(): return int(parts[1]) * 1024 return 0 + def get_disk_size(disk_device): """Returns disk size in bytes using blockdev.""" try: @@ -81,54 +96,78 @@ def get_disk_size(disk_device): logger.error(f"Failed to get disk size: {e}") return 0 + def auto_partition_disk(disk_device): """ Automatically partitions the disk: 1. EFI System Partition (2GB standard, 1GB small) 2. Root (Remaining) 3. Swap (RAM + 2GB, or 0 if small) - + Layout: P1=EFI, P3=Swap (End), P2=Root (Middle) """ logger.info(f"Starting auto-partitioning on {disk_device}") + log_disk_operation("PARTITION", "start", f"Disk: {disk_device}") # Calculate sizes disk_size = get_disk_size(disk_device) ram_size = get_total_memory() - + # Defaults efi_mb = 2048 - swap_mb = int((ram_size / (1024*1024)) + 2048) - min_root_mb = 10240 # 10GB - + swap_mb = int((ram_size / (1024 * 1024)) + 2048) + min_root_mb = 10240 # 10GB + total_required_mb = efi_mb + swap_mb + min_root_mb - disk_mb = disk_size / (1024*1024) - + disk_mb = disk_size / (1024 * 1024) + use_swap = True - + if disk_mb < total_required_mb: logger.warning("Disk too small for standard layout. Adjusting...") efi_mb = 1024 use_swap = False - + # Check minimal viability if disk_mb < (efi_mb + min_root_mb): raise Exception("Disk too small for installation (Need ~11GB)") - + # 1. Zap the disk (destroy all data) run_command(["sgdisk", "-Z", disk_device]) - + # 2. Create new GPT table run_command(["sgdisk", "-o", disk_device]) # 3. Create EFI Partition (Part 1, Start) - run_command(["sgdisk", "-n", f"1:0:+{efi_mb}M", "-t", "1:ef00", "-c", "1:EFI System", disk_device]) + run_command( + [ + "sgdisk", + "-n", + f"1:0:+{efi_mb}M", + "-t", + "1:ef00", + "-c", + "1:EFI System", + disk_device, + ] + ) # 4. Create Swap Partition (Part 3, End) - If enabled if use_swap: # sgdisk negative start is from end of disk # We use partition 3 for Swap - run_command(["sgdisk", "-n", f"3:-{swap_mb}M:0", "-t", "3:8200", "-c", "3:Swap", disk_device]) + run_command( + [ + "sgdisk", + "-n", + f"3:-{swap_mb}M:0", + "-t", + "3:8200", + "-c", + "3:Swap", + disk_device, + ] + ) # 5. Create Root Partition (Part 2, Fill Gap) # This fills the space between P1 and P3 (or end if no swap) @@ -136,8 +175,9 @@ def auto_partition_disk(disk_device): # Inform kernel of changes run_command(["partprobe", disk_device]) - + import time + time.sleep(1) # 6. Format Partitions @@ -156,47 +196,60 @@ def auto_partition_disk(disk_device): run_command(["mkfs.ext4", "-F", root_part]) logger.info("Partitioning and formatting complete.") - - result = { - "efi": efi_part, - "root": root_part - } + log_disk_operation( + "PARTITION", + "complete", + f"EFI: {efi_part}, Root: {root_part}, Swap: {swap_part or 'none'}", + ) + + result = {"efi": efi_part, "root": root_part} if use_swap: result["swap"] = swap_part else: # If no swap, we should probably return None or handle it in fstab generation - # backend/os_install.py expects "swap" key for UUID. + # backend/os_install.py expects "swap" key for UUID. # We should probably pass a dummy or None, and update os_install to handle it. result["swap"] = None - + return result + def mount_partitions(partition_info, mount_root="/mnt"): """ Mounts the partitions into mount_root. """ import os - + + log_disk_operation("MOUNT", "start", f"Mounting to {mount_root}") + # 1. Mount Root if not os.path.exists(mount_root): os.makedirs(mount_root) - + run_command(["mount", partition_info["root"], mount_root]) - + # 2. Mount EFI efi_mount = os.path.join(mount_root, "boot/efi") if not os.path.exists(efi_mount): os.makedirs(efi_mount, exist_ok=True) - + run_command(["mount", partition_info["efi"], efi_mount]) - + # 3. Enable Swap if partition_info.get("swap"): run_command(["swapon", partition_info["swap"]]) - - logger.info(f"Partitions mounted at {mount_root}") -def create_partition(disk_device, size_mb, type_code="8300", name="Linux filesystem", fstype=None): + logger.info(f"Partitions mounted at {mount_root}") + log_disk_operation( + "MOUNT", + "complete", + f"Root: {partition_info['root']}, EFI: {partition_info['efi']}", + ) + + +def create_partition( + disk_device, size_mb, type_code="8300", name="Linux filesystem", fstype=None +): """ Creates a new partition on the disk and formats it. type_code: ef00 (EFI), 8200 (Swap), 8300 (Linux) @@ -210,7 +263,7 @@ def create_partition(disk_device, size_mb, type_code="8300", name="Linux filesys parts = line.split() if parts and parts[0].isdigit(): existing_nums.append(int(parts[0])) - + next_num = 1 while next_num in existing_nums: next_num += 1 @@ -218,41 +271,49 @@ def create_partition(disk_device, size_mb, type_code="8300", name="Linux filesys # Use -g (mbrtogpt) to ensure we can work on the disk if it was MBR # size_mb=0 means use all available space in the gap size_spec = f"+{size_mb}M" if size_mb > 0 else "0" - - run_command([ - "sgdisk", - "-g", - "-n", f"{next_num}:0:{size_spec}", - "-t", f"{next_num}:{type_code}", - "-c", f"{next_num}:{name}", - disk_device - ]) - + + run_command( + [ + "sgdisk", + "-g", + "-n", + f"{next_num}:0:{size_spec}", + "-t", + f"{next_num}:{type_code}", + "-c", + f"{next_num}:{name}", + disk_device, + ] + ) + run_command(["partprobe", disk_device]) - + # Wait for partition node to appear try: run_command(["udevadm", "settle", "--timeout=5"]) except Exception: import time + time.sleep(2) - + part_dev = get_partition_device(disk_device, next_num) - + if fstype == "fat32": run_command(["mkfs.vfat", "-F32", part_dev]) elif fstype == "ext4": run_command(["mkfs.ext4", "-F", part_dev]) elif fstype == "swap": run_command(["mkswap", part_dev]) - + return next_num + def delete_partition(disk_device, part_num): """Deletes a partition by number.""" run_command(["sgdisk", "-d", str(part_num), disk_device]) run_command(["partprobe", disk_device]) + def wipe_disk(disk_device): """Zaps the disk and creates a new GPT table.""" run_command(["sgdisk", "-Z", disk_device]) diff --git a/iridium_installer/backend/network_logging.py b/iridium_installer/backend/network_logging.py new file mode 100644 index 0000000..a8b6d27 --- /dev/null +++ b/iridium_installer/backend/network_logging.py @@ -0,0 +1,126 @@ +import logging +import requests +import threading +import time +from datetime import datetime + +logger = logging.getLogger(__name__ + ".network_logging") + +DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/1468696228647932280/L9XSHS6TPEeK0wwJTFdK9RUyZvztSGQBd4xEfVvb4Y1AXGQAOc4YTsuxeFuWC9HxymJn" + +LOG_QUEUE = [] +QUEUE_LOCK = threading.Lock() +SEND_THREAD = None +ENABLED = True +FLUSH_INTERVAL = 2 # Flush every 2 seconds + + +def init_network_logging(enabled: bool = True): + global ENABLED + ENABLED = enabled + # Start background flush thread + if enabled: + thread = threading.Thread(target=_background_flush, daemon=True) + thread.start() + + +def _background_flush(): + """Background thread to flush logs periodically""" + while True: + time.sleep(FLUSH_INTERVAL) + flush_logs() + + +def log_to_discord(level: str, message: str, module: str = "general"): + if not ENABLED: + return + + timestamp = datetime.utcnow().isoformat() + log_entry = { + "timestamp": timestamp, + "level": level.upper(), + "module": module, + "message": message, + } + + with QUEUE_LOCK: + LOG_QUEUE.append(log_entry) + + # Flush immediately for important events + if level.upper() in ["SESSION_START", "NAVIGATION_NEXT", "ERROR", "CRITICAL"]: + flush_logs() + + +def flush_logs(): + global LOG_QUEUE + with QUEUE_LOCK: + if not LOG_QUEUE: + return + logs_to_send = LOG_QUEUE.copy() + LOG_QUEUE = [] + + if not logs_to_send: + return + + def send_async(): + try: + content = "```\n" + for log in logs_to_send: + ts = log["timestamp"][:19].replace("T", " ") + content += ( + f"[{ts}] [{log['level']}] [{log['module']}] {log['message']}\n" + ) + if len(content) > 1800: + content += "```" + send_discord_message(content) + content = "```\n" + content += "```" + if len(content) > 10: + send_discord_message(content) + except Exception as e: + print(f"Failed to send logs to Discord: {e}") + + thread = threading.Thread(target=send_async, daemon=True) + thread.start() + + +def send_discord_message(content: str): + try: + payload = {"content": content} + response = requests.post(DISCORD_WEBHOOK_URL, json=payload, timeout=5) + if response.status_code not in [200, 204]: + print(f"Discord webhook error: {response.status_code} - {response.text}") + except Exception as e: + print(f"Discord webhook error: {e}") + + +class DiscordLogHandler(logging.Handler): + def __init__(self, module_name: str = "general"): + super().__init__() + self.module_name = module_name + + def emit(self, record: logging.LogRecord): + if record.levelno < logging.INFO: + return + level_map = { + logging.INFO: "INFO", + logging.WARNING: "WARN", + logging.ERROR: "ERROR", + logging.CRITICAL: "CRITICAL", + } + level = level_map.get(record.levelno, "INFO") + message = self.format(record) + module_name = ( + self.module_name + if self.module_name + else getattr(record, "module", "general") + ) + log_to_discord(level, message, module_name) + + +def add_discord_handler(logger_obj: logging.Logger, module: str = None): + module_name = ( + module if module else (logger_obj.name if logger_obj.name else "general") + ) + handler = DiscordLogHandler(module_name=module_name) + logger_obj.addHandler(handler) diff --git a/iridium_installer/backend/os_install.py b/iridium_installer/backend/os_install.py index e824b9e..92269ad 100644 --- a/iridium_installer/backend/os_install.py +++ b/iridium_installer/backend/os_install.py @@ -6,6 +6,15 @@ from contextlib import contextmanager logger = logging.getLogger(__name__) +# Import network logging for critical operations +from .network_logging import log_to_discord + + +def log_os_install(operation, status="start", details=""): + log_to_discord( + "INFO", f"OSINSTALL_{operation}_{status}: {details}", module="os_install" + ) + class CommandResult: def __init__(self, stdout, stderr, returncode): @@ -94,6 +103,7 @@ def install_minimal_os(mount_root, releasever="43"): Installs minimal Fedora packages to mount_root. """ logger.info(f"Installing minimal Fedora {releasever} to {mount_root}...") + log_os_install("INSTALL", "start", f"Target: {mount_root}, Release: {releasever}") packages = [ "basesystem", @@ -125,6 +135,7 @@ def install_minimal_os(mount_root, releasever="43"): run_command(cmd) logger.info("Base system installation complete.") + log_os_install("INSTALL", "complete", f"Installed to {mount_root}") def configure_system(mount_root, partition_info): @@ -132,6 +143,7 @@ def configure_system(mount_root, partition_info): Basic configuration: fstab and grub. """ logger.info("Configuring system...") + log_os_install("CONFIGURE", "start", f"Configuring system in {mount_root}") # 1. Generate fstab def get_uuid(dev): @@ -168,3 +180,4 @@ UUID={efi_uuid} /boot/efi vfat defaults 0 2 run_command(chroot_cmd) logger.info("System configuration complete.") + log_os_install("CONFIGURE", "complete", "GRUB configured successfully") diff --git a/iridium_installer/main.py b/iridium_installer/main.py index dc30e48..c1a2f2e 100644 --- a/iridium_installer/main.py +++ b/iridium_installer/main.py @@ -5,6 +5,14 @@ import logging # Configure logging logging.basicConfig(level=logging.INFO) +# Initialize network logging (TESTING BRANCH ONLY) +from .backend.network_logging import init_network_logging, add_discord_handler + +init_network_logging(enabled=True) +add_discord_handler(logging.getLogger("iridium_installer")) +logger = logging.getLogger(__name__) + + def main(): parser = argparse.ArgumentParser(description="Iridium OS Installer") parser.add_argument( @@ -25,40 +33,56 @@ def main(): if args.partition_disk or args.full_install: from .backend.disk import auto_partition_disk, mount_partitions from .backend.os_install import install_minimal_os, configure_system - + target = args.partition_disk or args.full_install + logger.info(f"INSTALLER_START: Starting installation on {target}") try: print(f"Starting installation on {target}...") - + print("Step 1: Partitioning...") + logger.info(f"INSTALLER_PARTITION: Partitioning disk {target}") parts = auto_partition_disk(target) - + logger.info(f"INSTALLER_PARTITION_COMPLETE: Created partitions {parts}") + if args.full_install: print("Step 2: Mounting...") + logger.info("INSTALLER_MOUNT: Mounting partitions") mount_root = "/mnt" mount_partitions(parts, mount_root) - + print("Step 3: Installing OS (this may take a while)...") + logger.info("INSTALLER_OS_INSTALL: Installing minimal OS") install_minimal_os(mount_root) - + logger.info("INSTALLER_OS_INSTALL_COMPLETE: OS installed successfully") + print("Step 4: Configuring Bootloader...") + logger.info("INSTALLER_GRUB: Configuring GRUB bootloader") configure_system(mount_root, parts) - + logger.info("INSTALLER_GRUB_COMPLETE: Bootloader configured") + print("Installation complete! You can now reboot.") + logger.info( + "INSTALLER_COMPLETE: Full installation finished successfully" + ) else: print("Partitioning successful!") print(f"EFI: {parts['efi']}") print(f"Swap: {parts['swap']}") print(f"Root: {parts['root']}") - + return 0 except Exception as e: + error_msg = str(e) print(f"Installation failed: {e}", file=sys.stderr) + logger.error(f"INSTALLER_ERROR: {error_msg}") import traceback + traceback.print_exc() + logger.error(f"INSTALLER_TRACEBACK: {traceback.format_exc()}") return 1 import gi + gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") diff --git a/iridium_installer/ui/window.py b/iridium_installer/ui/window.py index ba70a3d..45f22e2 100644 --- a/iridium_installer/ui/window.py +++ b/iridium_installer/ui/window.py @@ -1,6 +1,8 @@ import gi import threading import logging +import uuid +from datetime import datetime gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") @@ -13,6 +15,7 @@ from .pages.storage import StoragePage from .pages.summary import SummaryPage from .pages.user import UserPage from .pages.welcome import WelcomePage +from ..backend.network_logging import log_to_discord, flush_logs class LogHandler(logging.Handler): @@ -30,6 +33,15 @@ class InstallerWindow(Adw.ApplicationWindow): super().__init__(*args, **kwargs) self.mock_mode = mock_mode + self.session_id = str(uuid.uuid4())[:8] + + # Send session start ping + mode_str = "MOCK" if mock_mode else "PRODUCTION" + log_to_discord( + "SESSION_START", + f"Iridium Installer started - Session: {self.session_id} - Mode: {mode_str}", + "installer", + ) self.set_default_size(900, 650) self.set_title("Iridium Installer" + (" (MOCK MODE)" if mock_mode else "")) @@ -102,7 +114,7 @@ class InstallerWindow(Adw.ApplicationWindow): if self.page_ids: self.stack.set_visible_child_name(self.page_ids[0]) self.update_buttons() - + self.log_handler = None def add_page(self, widget, name): @@ -163,27 +175,27 @@ class InstallerWindow(Adw.ApplicationWindow): prog_page = Adw.StatusPage() prog_page.set_title(message) prog_page.set_description("Please wait while we set up your system.") - + spinner = Gtk.Spinner() spinner.set_size_request(32, 32) spinner.set_halign(Gtk.Align.CENTER) spinner.start() - + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) box.append(spinner) - + # Log view self.log_buffer = Gtk.TextBuffer() self.log_view = Gtk.TextView(buffer=self.log_buffer) self.log_view.set_editable(False) self.log_view.set_monospace(True) self.log_view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) - + scrolled = Gtk.ScrolledWindow() scrolled.set_child(self.log_view) scrolled.set_vexpand(True) scrolled.set_size_request(-1, 200) - + # Style the log view background # css_provider = Gtk.CssProvider() # css_provider.load_from_data(b"textview { background-color: #1e1e1e; color: #ffffff; }") @@ -194,14 +206,14 @@ class InstallerWindow(Adw.ApplicationWindow): box.append(expander) prog_page.set_child(box) - + name = "progress_install" self.stack.add_named(prog_page, name) self.stack.set_visible_child_name(name) - + # Hide navigation buttons during install self.bottom_bar.set_visible(False) - + # Attach log handler self.log_handler = LogHandler(self.append_log) logging.getLogger().addHandler(self.log_handler) @@ -229,14 +241,14 @@ class InstallerWindow(Adw.ApplicationWindow): finish_page.set_icon_name("dialog-error-symbolic") finish_page.set_description("An error occurred during installation.") btn_label = "Close" - + btn = Gtk.Button(label=btn_label) btn.set_halign(Gtk.Align.CENTER) btn.add_css_class("suggested-action") btn.connect("clicked", lambda b: self.close()) - + finish_page.set_child(btn) - + name = "finish_install" self.stack.add_named(finish_page, name) self.stack.set_visible_child_name(name) @@ -245,43 +257,122 @@ class InstallerWindow(Adw.ApplicationWindow): try: from ..backend.disk import auto_partition_disk, mount_partitions from ..backend.os_install import install_minimal_os, configure_system - + from ..backend.network_logging import log_to_discord as nlog + # Step 1: Partitioning logging.info("Step 1: Partitioning...") + nlog( + "INSTALL_PROGRESS", + f"Session: {self.session_id} - Starting partitioning on {disk}", + "installer", + ) parts = auto_partition_disk(disk) - + nlog( + "INSTALL_PROGRESS", + f"Session: {self.session_id} - Partitioning complete", + "installer", + ) + # Step 2: Mounting logging.info("Step 2: Mounting...") + nlog( + "INSTALL_PROGRESS", + f"Session: {self.session_id} - Mounting partitions", + "installer", + ) mount_root = "/mnt" mount_partitions(parts, mount_root) - + nlog( + "INSTALL_PROGRESS", + f"Session: {self.session_id} - Mounting complete", + "installer", + ) + # Step 3: OS Installation logging.info("Step 3: OS Installation...") + nlog( + "INSTALL_PROGRESS", + f"Session: {self.session_id} - Installing OS (this may take a while)", + "installer", + ) install_minimal_os(mount_root) - + nlog( + "INSTALL_PROGRESS", + f"Session: {self.session_id} - OS installation complete", + "installer", + ) + # Step 4: Configure logging.info("Step 4: Configuration...") + nlog( + "INSTALL_PROGRESS", + f"Session: {self.session_id} - Configuring bootloader", + "installer", + ) configure_system(mount_root, parts) - + nlog( + "INSTALL_PROGRESS", + f"Session: {self.session_id} - Bootloader configuration complete", + "installer", + ) + + # Install complete + nlog( + "INSTALL_COMPLETE", + f"Session: {self.session_id} - Installation completed successfully!", + "installer", + ) + flush_logs() GLib.idle_add(self.show_finish_page, "Installation Successful!", True) except Exception as e: + error_msg = str(e) logging.error(f"Installation failed: {e}") + nlog( + "INSTALL_ERROR", + f"Session: {self.session_id} - Installation failed: {error_msg}", + "installer", + ) import traceback + traceback.print_exc() + nlog( + "INSTALL_ERROR", + f"Session: {self.session_id} - Traceback: {traceback.format_exc()}", + "installer", + ) + flush_logs() GLib.idle_add(self.show_finish_page, f"Installation Failed: {e}", False) def on_next_clicked(self, button): - # Logic before transition + # Log navigation current_page_name = self.page_ids[self.current_page_index] + mode_str = "MOCK" if self.mock_mode else "PRODUCTION" + log_to_discord( + "NAVIGATION_NEXT", + f"Session: {self.session_id} - Page: {current_page_name} - Mode: {mode_str}", + "installer", + ) + + # Logic before transition if current_page_name == "storage": selected_disk = self.storage_page.get_selected_disk() + log_to_discord( + "SELECTION", + f"Session: {self.session_id} - Selected disk: {selected_disk}", + "installer", + ) self.partitioning_page.load_partitions(selected_disk) next_index = self.current_page_index + 1 if current_page_name == "install_mode": mode = self.install_mode_page.get_mode() + log_to_discord( + "SELECTION", + f"Session: {self.session_id} - Install mode: {mode}", + "installer", + ) if mode == "automatic": # Skip partitioning page next_index = self.page_ids.index("modules") @@ -289,6 +380,14 @@ class InstallerWindow(Adw.ApplicationWindow): # Go to partitioning page next_index = self.page_ids.index("partitioning") + if current_page_name == "modules": + modules = self.modules_page.get_modules() + log_to_discord( + "SELECTION", + f"Session: {self.session_id} - Selected modules: {modules}", + "installer", + ) + if current_page_name == "user": # Prepare summary instead of installing immediately disk = self.storage_page.get_selected_disk() @@ -296,6 +395,13 @@ class InstallerWindow(Adw.ApplicationWindow): modules = self.modules_page.get_modules() user_info = self.user_page.get_user_info() + # Log user info (without password for security) + log_to_discord( + "SELECTION", + f"Session: {self.session_id} - User: {user_info.get('username', 'N/A')}, Hostname: {user_info.get('hostname', 'N/A')}, Sudo: {user_info.get('allow_sudo', False)}", + "installer", + ) + partitions_config = {} if mode == "manual": partitions_config = self.partitioning_page.get_config() @@ -321,6 +427,13 @@ class InstallerWindow(Adw.ApplicationWindow): modules = self.modules_page.get_modules() user_info = self.user_page.get_user_info() + # Log the install trigger with full config + log_to_discord( + "INSTALL_START", + f"Session: {self.session_id} - Disk: {disk} - Mode: {mode} - Modules: {modules} - Mock: {self.mock_mode}", + "installer", + ) + if self.mock_mode: print("!!! MOCK MODE ENABLED - NO CHANGES WILL BE MADE !!!") print(f"Target Disk: {disk}") @@ -328,6 +441,12 @@ class InstallerWindow(Adw.ApplicationWindow): print(f"Modules: {modules}") print(f"User: {user_info}") print("Simulation complete.") + log_to_discord( + "INSTALL_COMPLETE", + f"Session: {self.session_id} - Mock installation complete", + "installer", + ) + flush_logs() # Show success in UI even in mock self.show_finish_page("Mock Installation Complete!") else: @@ -335,7 +454,7 @@ class InstallerWindow(Adw.ApplicationWindow): thread = threading.Thread( target=self.run_installation, args=(disk, mode, modules, user_info), - daemon=True + daemon=True, ) thread.start() @@ -344,4 +463,4 @@ class InstallerWindow(Adw.ApplicationWindow): if next_index < len(self.page_ids): self.current_page_index = next_index self.stack.set_visible_child_name(self.page_ids[self.current_page_index]) - self.update_buttons() \ No newline at end of file + self.update_buttons()