From 70d8ac41b45bd3869c202cb023ff53fe31f5ee68 Mon Sep 17 00:00:00 2001 From: zj <1052308357@qq.com> Date: Tue, 10 Feb 2026 15:00:20 +0800 Subject: [PATCH] two --- README.md | 4 +- backend.py | 322 ++++++++++++++++++++++++++++++++++++++++++++++++++++ frontend.py | 289 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 614 insertions(+), 1 deletion(-) create mode 100644 backend.py create mode 100644 frontend.py diff --git a/README.md b/README.md index 060f24a..b8be241 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -### GURB引导修复 \ No newline at end of file +### GURB引导修复 + +提供图形化界面 \ No newline at end of file diff --git a/backend.py b/backend.py new file mode 100644 index 0000000..8dad89d --- /dev/null +++ b/backend.py @@ -0,0 +1,322 @@ +# backend.py + +import subprocess +import os +import json +import time # 用于模拟耗时操作,实际可移除 + +def run_command(command, description="执行命令", cwd=None, shell=False): + """ + 运行一个系统命令,并捕获其输出和错误。 + :param command: 要执行的命令列表 (例如 ["sudo", "lsblk"]) + :param description: 命令的描述,用于日志 + :param cwd: 更改工作目录 + :param shell: 是否使用shell执行命令 (通常不推荐,除非命令需要shell特性) + :return: (True/False, stdout, stderr) 表示成功、标准输出、标准错误 + """ + try: + print(f"[Backend] {description}: {' '.join(command)}") + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, # 如果命令返回非零退出码,将抛出CalledProcessError + cwd=cwd, + shell=shell + ) + print(f"[Backend] 命令成功: {description}") + return True, result.stdout, result.stderr + except subprocess.CalledProcessError as e: + print(f"[Backend] 命令失败: {description}") + print(f"[Backend] 错误输出: {e.stderr}") + return False, e.stdout, e.stderr + except FileNotFoundError: + print(f"[Backend] 命令未找到: {' '.join(command)}") + return False, "", f"命令未找到: {command[0]}" + except Exception as e: + print(f"[Backend] 发生未知错误: {e}") + return False, "", str(e) + +def scan_partitions(): + """ + 扫描系统中的所有磁盘和分区,并返回结构化的信息。 + :return: (success, disks, partitions, efi_partitions, error_message) + disks: list of {"name": "/dev/sda"} + partitions: list of {"name": "/dev/sda1", "fstype": "ext4", "size": "100G", "mountpoint": "/"} + efi_partitions: list of {"name": "/dev/sda1", "fstype": "vfat", "size": "512M"} + """ + success, stdout, stderr = run_command( + ["sudo", "lsblk", "-J", "-o", "NAME,FSTYPE,SIZE,MOUNTPOINT,UUID,PARTLABEL,LABEL,TYPE"], + "扫描分区" + ) + + if not success: + return False, [], [], [], stderr + + try: + data = json.loads(stdout) + all_disks = [] + all_partitions = [] + all_efi_partitions = [] + + for block_device in data.get("blockdevices", []): + dev_name = f"/dev/{block_device['name']}" + dev_type = block_device.get("type") + + if dev_type == "disk": + all_disks.append({"name": dev_name}) + elif dev_type == "part": + # 过滤掉Live系统自身的分区(基于常见的Live环境挂载点) + mountpoint = block_device.get("mountpoint") + if mountpoint and (mountpoint == "/" or mountpoint.startswith("/run/media") or mountpoint.startswith("/cdrom")): + continue + + part_info = { + "name": dev_name, + "fstype": block_device.get("fstype", "unknown"), + "size": block_device.get("size", "unknown"), + "mountpoint": mountpoint, + "uuid": block_device.get("uuid"), + "partlabel": block_device.get("partlabel"), + "label": block_device.get("label") + } + all_partitions.append(part_info) + + # 识别EFI系统分区 (FAT32文件系统, 且通常有特定标签或名称) + if part_info["fstype"] == "vfat" and (part_info["partlabel"] == "EFI System Partition" or part_info["label"] == "EFI"): + all_efi_partitions.append(part_info) + + return True, all_disks, all_partitions, all_efi_partitions, "" + + except json.JSONDecodeError as e: + return False, [], [], [], f"解析lsblk输出失败: {e}" + except Exception as e: + return False, [], [], [], f"处理分区数据时发生未知错误: {e}" + +def mount_target_system(root_partition, boot_partition=None, efi_partition=None): + """ + 挂载目标系统的根分区、/boot分区和EFI分区到临时目录。 + :param root_partition: 目标系统的根分区设备路径 (例如 /dev/sda1) + :param boot_partition: 目标系统的独立 /boot 分区设备路径 (可选) + :param efi_partition: 目标系统的EFI系统分区设备路径 (可选,仅UEFI) + :return: (True/False, mount_point, error_message) + """ + mount_point = "/mnt_grub_repair_" + str(int(time.time())) # 确保唯一性 + if not os.path.exists(mount_point): + os.makedirs(mount_point) + + # 1. 挂载根分区 + success, _, stderr = run_command(["sudo", "mount", root_partition, mount_point], f"挂载根分区 {root_partition}") + if not success: + return False, "", f"挂载根分区失败: {stderr}" + + # 2. 如果有独立 /boot 分区 + if boot_partition: + boot_mount_point = os.path.join(mount_point, "boot") + if not os.path.exists(boot_mount_point): + os.makedirs(boot_mount_point) + success, _, stderr = run_command(["sudo", "mount", boot_partition, boot_mount_point], f"挂载 /boot 分区 {boot_partition}") + if not success: + unmount_target_system(mount_point) # 挂载失败,尝试清理 + return False, "", f"挂载 /boot 分区失败: {stderr}" + + # 3. 如果有EFI分区 (UEFI系统) + if efi_partition: + efi_mount_point = os.path.join(mount_point, "boot/efi") + if not os.path.exists(efi_mount_point): + os.makedirs(efi_mount_point) + success, _, stderr = run_command(["sudo", "mount", efi_partition, efi_mount_point], f"挂载 EFI 分区 {efi_partition}") + if not success: + unmount_target_system(mount_point) # 挂载失败,尝试清理 + return False, "", f"挂载 EFI 分区失败: {stderr}" + + # 4. 绑定必要的伪文件系统 + bind_paths = ["/dev", "/dev/pts", "/proc", "/sys", "/run"] + for path in bind_paths: + target_path = os.path.join(mount_point, path.lstrip('/')) + if not os.path.exists(target_path): + os.makedirs(target_path) + success, _, stderr = run_command(["sudo", "mount", "--bind", path, target_path], f"绑定 {path} 到 {target_path}") + if not success: + unmount_target_system(mount_point) # 绑定失败,尝试清理 + return False, "", f"绑定 {path} 失败: {stderr}" + + return True, mount_point, "" + +def detect_distro_type(mount_point): + """ + 尝试检测目标系统发行版类型。 + :param mount_point: 目标系统根分区的挂载点 + :return: "arch", "centos", "debian", "ubuntu", "unknown" + """ + os_release_path = os.path.join(mount_point, "etc/os-release") + if not os.path.exists(os_release_path): + return "unknown" + + try: + with open(os_release_path, "r") as f: + content = f.read() + if "ID=arch" in content: + return "arch" + elif "ID=centos" in content or "ID=rhel" in content: + return "centos" + elif "ID=debian" in content: + return "debian" + elif "ID=ubuntu" in content: + return "ubuntu" + else: + return "unknown" + except Exception as e: + print(f"[Backend] 无法读取os-release文件: {e}") + return "unknown" + +def chroot_and_repair_grub(mount_point, target_disk, is_uefi=False, distro_type="unknown"): + """ + Chroot到目标系统并执行GRUB修复命令。 + :param mount_point: 目标系统根分区的挂载点 + :param target_disk: GRUB要安装到的物理磁盘 (例如 /dev/sda) + :param is_uefi: 目标系统是否使用UEFI启动 + :param distro_type: 目标系统发行版类型 (用于选择正确的GRUB命令) + :return: (True/False, error_message) + """ + chroot_cmd_prefix = ["sudo", "chroot", mount_point] + + # 1. 安装GRUB到目标磁盘 + if is_uefi: + # UEFI系统,需要确保EFI分区已挂载到 /boot/efi + # grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=GRUB + # 注意:某些系统可能需要指定 --removable 选项,以便在某些固件上更容易启动 + # 统一使用 --bootloader-id=GRUB,如果已存在会更新 + success, _, stderr = run_command( + chroot_cmd_prefix + ["grub-install", "--target=x86_64-efi", "--efi-directory=/boot/efi", "--bootloader-id=GRUB"], + "安装UEFI GRUB" + ) + else: + # BIOS系统,安装到MBR + success, _, stderr = run_command( + chroot_cmd_prefix + ["grub-install", target_disk], + "安装BIOS GRUB" + ) + if not success: + return False, f"GRUB安装失败: {stderr}" + + # 2. 更新GRUB配置文件 + grub_update_cmd = [] + if distro_type in ["debian", "ubuntu"]: + grub_update_cmd = ["update-grub"] # 这是一个脚本,实际是调用grub-mkconfig + elif distro_type == "arch": + grub_update_cmd = ["grub-mkconfig", "-o", "/boot/grub/grub.cfg"] + elif distro_type == "centos": + grub_update_cmd = ["grub2-mkconfig", "-o", "/boot/grub2/grub.cfg"] # CentOS 8/9 + else: + # 尝试通用命令 + grub_update_cmd = ["grub-mkconfig", "-o", "/boot/grub/grub.cfg"] + + success, _, stderr = run_command( + chroot_cmd_prefix + grub_update_cmd, + "更新GRUB配置文件" + ) + if not success: + return False, f"GRUB配置文件更新失败: {stderr}" + + return True, "" + +def unmount_target_system(mount_point): + """ + 卸载所有挂载的分区。 + :param mount_point: 目标系统根分区的挂载点 + :return: (True/False, error_message) + """ + print(f"[Backend] 正在卸载分区 from {mount_point}...") + success = True + error_msg = "" + + # 逆序卸载绑定 + bind_paths = ["/run", "/sys", "/proc", "/dev/pts", "/dev"] + for path in reversed(bind_paths): # 逆序 + target_path = os.path.join(mount_point, path.lstrip('/')) + if os.path.ismount(target_path): # 检查是否真的挂载了 + s, _, stderr = run_command(["sudo", "umount", target_path], f"卸载绑定 {target_path}") + if not s: + success = False + error_msg += f"卸载绑定 {target_path} 失败: {stderr}\n" + + # 卸载 /boot/efi (如果存在) + efi_mount_point = os.path.join(mount_point, "boot/efi") + if os.path.ismount(efi_mount_point): + s, _, stderr = run_command(["sudo", "umount", efi_mount_point], f"卸载 {efi_mount_point}") + if not s: + success = False + error_msg += f"卸载 {efi_mount_point} 失败: {stderr}\n" + + # 卸载 /boot (如果存在) + boot_mount_point = os.path.join(mount_point, "boot") + if os.path.ismount(boot_mount_point): + s, _, stderr = run_command(["sudo", "umount", boot_mount_point], f"卸载 {boot_mount_point}") + if not s: + success = False + error_msg += f"卸载 {boot_mount_point} 失败: {stderr}\n" + + # 卸载根分区 + if os.path.ismount(mount_point): + s, _, stderr = run_command(["sudo", "umount", mount_point], f"卸载根分区 {mount_point}") + if not s: + success = False + error_msg += f"卸载根分区 {mount_point} 失败: {stderr}\n" + + # 清理临时挂载点目录 + if os.path.exists(mount_point) and not os.path.ismount(mount_point): + try: + os.rmdir(mount_point) + print(f"[Backend] 清理临时目录 {mount_point}") + except OSError as e: + print(f"[Backend] 无法删除临时目录 {mount_point}: {e}") + error_msg += f"无法删除临时目录 {mount_point}: {e}\n" + + + return success, error_msg + +# 示例:如果直接运行backend.py,可以进行一些测试 +if __name__ == "__main__": + print("--- 运行后端测试 ---") + + print("\n--- 扫描分区测试 ---") + success, disks, partitions, efi_partitions, err = scan_partitions() + if success: + print("磁盘:", [d['name'] for d in disks]) + print("分区:", [p['name'] for p in partitions]) + print("EFI分区:", [p['name'] for p in efi_partitions]) + else: + print("扫描分区失败:", err) + + # 假设有一些分区可供测试,实际运行时需要用户输入 + # 例如: + # test_root_partition = "/dev/sdaX" + # test_target_disk = "/dev/sda" + # test_efi_partition = "/dev/sdY" + # + # if test_root_partition and test_target_disk: + # print(f"\n--- 挂载系统测试 (模拟 {test_root_partition}) ---") + # mount_ok, mnt_pt, mount_err = mount_target_system(test_root_partition, efi_partition=test_efi_partition) + # if mount_ok: + # print(f"系统挂载成功到: {mnt_pt}") + # distro = detect_distro_type(mnt_pt) + # print(f"检测到发行版: {distro}") + # + # print("\n--- 修复GRUB测试 ---") + # repair_ok, repair_err = chroot_and_repair_grub(mnt_pt, test_target_disk, is_uefi=True, distro_type=distro) + # if repair_ok: + # print("GRUB修复成功!") + # else: + # print("GRUB修复失败:", repair_err) + # + # print("\n--- 卸载系统测试 ---") + # unmount_ok, unmount_err = unmount_target_system(mnt_pt) + # if unmount_ok: + # print("系统卸载成功!") + # else: + # print("系统卸载失败:", unmount_err) + # else: + # print("系统挂载失败:", mount_err) + # else: + # print("\n请在代码中设置 test_root_partition 和 test_target_disk 进行完整测试。") diff --git a/frontend.py b/frontend.py new file mode 100644 index 0000000..5de3f97 --- /dev/null +++ b/frontend.py @@ -0,0 +1,289 @@ +# frontend.py + +import tkinter as tk +from tkinter import ttk, messagebox +import threading +import backend # 导入后端逻辑 + +class GrubRepairApp: + def __init__(self, master): + self.master = master + master.title("Linux GRUB 引导修复工具") + master.geometry("850x650") # 调整窗口大小 + master.resizable(True, True) # 允许窗口大小调整 + + self.mount_point = None + self.selected_root_partition_info = None # 存储字典信息 + self.selected_boot_partition_info = None + self.selected_efi_partition_info = None + self.selected_target_disk_info = None + self.is_uefi_mode = False + + self.all_disks_data = {} # {display_name: {"name": "/dev/sda"}} + self.all_partitions_data = {} # {display_name: {"name": "/dev/sda1", ...}} + self.all_efi_partitions_data = {} # {display_name: {"name": "/dev/sda1", ...}} + + self.create_widgets() + self.scan_partitions_gui() + + def create_widgets(self): + # Frame for partition selection + self.partition_frame = ttk.LabelFrame(self.master, text="1. 选择目标系统分区", padding="10") + self.partition_frame.pack(padx=10, pady=5, fill="x") + self.partition_frame.columnconfigure(1, weight=1) # 让 Combobox 填充宽度 + + ttk.Label(self.partition_frame, text="根分区 (/):").grid(row=0, column=0, padx=5, pady=2, sticky="w") + self.root_partition_combo = ttk.Combobox(self.partition_frame, state="readonly") + self.root_partition_combo.grid(row=0, column=1, padx=5, pady=2, sticky="ew") + self.root_partition_combo.bind("<>", self.on_root_partition_selected) + + ttk.Label(self.partition_frame, text="独立 /boot 分区 (可选):").grid(row=1, column=0, padx=5, pady=2, sticky="w") + self.boot_partition_combo = ttk.Combobox(self.partition_frame, state="readonly") + self.boot_partition_combo.grid(row=1, column=1, padx=5, pady=2, sticky="ew") + + ttk.Label(self.partition_frame, text="EFI 系统分区 (ESP, 仅UEFI):").grid(row=2, column=0, padx=5, pady=2, sticky="w") + self.efi_partition_combo = ttk.Combobox(self.partition_frame, state="readonly") + self.efi_partition_combo.grid(row=2, column=1, padx=5, pady=2, sticky="ew") + + # Frame for target disk selection + self.disk_frame = ttk.LabelFrame(self.master, text="2. 选择GRUB安装目标磁盘", padding="10") + self.disk_frame.pack(padx=10, pady=5, fill="x") + self.disk_frame.columnconfigure(1, weight=1) + + ttk.Label(self.disk_frame, text="目标磁盘 (例如 /dev/sda):").grid(row=0, column=0, padx=5, pady=2, sticky="w") + self.target_disk_combo = ttk.Combobox(self.disk_frame, state="readonly") + self.target_disk_combo.grid(row=0, column=1, padx=5, pady=2, sticky="ew") + + self.uefi_mode_var = tk.BooleanVar(value=False) + self.uefi_check = ttk.Checkbutton(self.disk_frame, text="目标系统使用UEFI启动 (Live环境检测为UEFI)", variable=self.uefi_mode_var) + self.uefi_check.grid(row=1, column=0, columnspan=2, padx=5, pady=2, sticky="w") + + # 尝试自动检测Live环境是否为UEFI + if os.path.exists("/sys/firmware/efi"): + self.uefi_mode_var.set(True) + self.uefi_check.config(state="disabled") # 如果Live是UEFI,通常目标系统也是,且不建议更改 + self.is_uefi_mode = True # 直接设置 + else: + self.uefi_check.config(state="normal") + self.is_uefi_mode = False # 直接设置 + self.uefi_check.config(text="目标系统使用UEFI启动 (Live环境检测为BIOS)") + + self.uefi_mode_var.trace_add("write", self.on_uefi_check_changed) + + + # Start Repair Button + self.start_button = ttk.Button(self.master, text="3. 开始修复 GRUB", command=self.start_repair_thread) + self.start_button.pack(padx=10, pady=10, fill="x") + + # Progress and Log + self.log_frame = ttk.LabelFrame(self.master, text="修复日志", padding="10") + self.log_frame.pack(padx=10, pady=5, fill="both", expand=True) + + self.log_text = tk.Text(self.log_frame, wrap="word", height=15, state="disabled", font=("Monospace", 10)) + self.log_text.pack(padx=5, pady=5, fill="both", expand=True) + self.log_scrollbar = ttk.Scrollbar(self.log_text, command=self.log_text.yview) + self.log_scrollbar.pack(side="right", fill="y") + self.log_text.config(yscrollcommand=self.log_scrollbar.set) + + def log_message(self, message, level="info"): + """ + 线程安全的日志记录函数。 + """ + self.master.after(0, self._append_log, message, level) + + def _append_log(self, message, level): + self.log_text.config(state="normal") + + tag = "info" + if level == "error": + tag = "error" + self.log_text.tag_config("error", foreground="red") + elif level == "warning": + tag = "warning" + self.log_text.tag_config("warning", foreground="orange") + elif level == "success": + tag = "success" + self.log_text.tag_config("success", foreground="green") + + self.log_text.insert(tk.END, message + "\n", tag) + self.log_text.see(tk.END) + self.log_text.config(state="disabled") + + def scan_partitions_gui(self): + self.log_message("正在扫描分区...", "info") + + success, disks, partitions, efi_partitions, err = backend.scan_partitions() + + if not success: + self.log_message(f"分区扫描失败: {err}", "error") + messagebox.showerror("错误", f"无法扫描分区: {err}\n请确保您有sudo权限并已安装lsblk。") + return + + # 清空旧数据 + self.all_disks_data = {} + self.all_partitions_data = {} + self.all_efi_partitions_data = {} + + disk_display_names = [] + for d in disks: + display_name = d["name"] + self.all_disks_data[display_name] = d + disk_display_names.append(display_name) + self.target_disk_combo['values'] = disk_display_names + + partition_display_names = [] + for p in partitions: + display_name = f"{p['name']} ({p['fstype']} - {p['size']})" + self.all_partitions_data[display_name] = p + partition_display_names.append(display_name) + self.root_partition_combo['values'] = partition_display_names + self.boot_partition_combo['values'] = [""] + partition_display_names # 允许不选择独立/boot + + efi_partition_display_names = [] + for p in efi_partitions: + display_name = f"{p['name']} ({p['fstype']} - {p['size']})" + self.all_efi_partitions_data[display_name] = p + efi_partition_display_names.append(display_name) + self.efi_partition_combo['values'] = [""] + efi_partition_display_names # 允许不选择EFI分区 + + self.log_message("分区扫描完成。请选择目标系统分区。", "info") + + def on_root_partition_selected(self, event): + selected_display = self.root_partition_combo.get() + self.selected_root_partition_info = self.all_partitions_data.get(selected_display) + + if self.selected_root_partition_info: + self.log_message(f"已选择根分区: {self.selected_root_partition_info['name']}", "info") + # 尝试根据根分区所在的磁盘自动选择目标磁盘 + # /dev/sda1 -> /dev/sda + disk_name_from_root = "/".join(self.selected_root_partition_info['name'].split('/')[:3]) + if disk_name_from_root in self.target_disk_combo['values']: + self.target_disk_combo.set(disk_name_from_root) + self.selected_target_disk_info = self.all_disks_data.get(disk_name_from_root) + self.log_message(f"自动选择目标磁盘: {self.selected_target_disk_info['name']}", "info") + else: + self.target_disk_combo.set("") + self.selected_target_disk_info = None + self.log_message("无法自动选择目标磁盘,请手动选择。", "warning") + else: + self.selected_root_partition_info = None + self.target_disk_combo.set("") + self.selected_target_disk_info = None + + def on_uefi_check_changed(self, *args): + self.is_uefi_mode = self.uefi_mode_var.get() + self.log_message(f"UEFI模式选择: {'启用' if self.is_uefi_mode else '禁用'}", "info") + + def start_repair_thread(self): + # 禁用按钮防止重复点击 + self.start_button.config(state="disabled") + # 清空日志 + self.log_text.config(state="normal") + self.log_text.delete(1.0, tk.END) + self.log_text.config(state="disabled") + self.log_message("--- GRUB 修复过程开始 ---", "info") + + # 获取用户选择 + self.selected_root_partition_info = self.all_partitions_data.get(self.root_partition_combo.get()) + self.selected_boot_partition_info = self.all_partitions_data.get(self.boot_partition_combo.get()) + self.selected_efi_partition_info = self.all_efi_partitions_data.get(self.efi_partition_combo.get()) + self.selected_target_disk_info = self.all_disks_data.get(self.target_disk_combo.get()) + self.is_uefi_mode = self.uefi_mode_var.get() + + if not self.selected_root_partition_info: + messagebox.showerror("错误", "请选择目标系统的根分区!") + self.log_message("未选择根分区,修复中止。", "error") + self.start_button.config(state="normal") + return + if not self.selected_target_disk_info: + messagebox.showerror("错误", "请选择GRUB要安装到的目标磁盘!") + self.log_message("未选择目标磁盘,修复中止。", "error") + self.start_button.config(state="normal") + return + + # 再次确认UEFI模式下是否选择了EFI分区 + if self.is_uefi_mode and not self.selected_efi_partition_info: + response = messagebox.askyesno("警告", "您选择了UEFI模式,但未选择EFI系统分区 (ESP)。这可能导致修复失败。是否继续?") + if not response: + self.log_message("用户取消,未选择EFI分区。", "warning") + self.start_button.config(state="normal") + return + + + # 启动新线程进行修复,避免UI卡死 + repair_thread = threading.Thread(target=self._run_repair_process) + repair_thread.start() + + def _run_repair_process(self): + root_part_path = self.selected_root_partition_info['name'] + boot_part_path = self.selected_boot_partition_info['name'] if self.selected_boot_partition_info else None + efi_part_path = self.selected_efi_partition_info['name'] if self.selected_efi_partition_info else None + target_disk_path = self.selected_target_disk_info['name'] + + self.log_message(f"选择的根分区: {root_part_path}", "info") + self.log_message(f"选择的独立 /boot 分区: {boot_part_path if boot_part_path else '无'}", "info") + self.log_message(f"选择的EFI分区: {efi_part_path if efi_part_path else '无'}", "info") + self.log_message(f"GRUB安装目标磁盘: {target_disk_path}", "info") + self.log_message(f"UEFI模式: {'启用' if self.is_uefi_mode else '禁用'}", "info") + + # 1. 挂载目标系统 + self.log_message("正在挂载目标系统分区...", "info") + mount_ok, self.mount_point, mount_err = backend.mount_target_system( + root_part_path, + boot_part_path, + efi_part_path if self.is_uefi_mode else None # 只有UEFI模式才挂载EFI分区 + ) + if not mount_ok: + self.log_message(f"挂载失败,修复中止: {mount_err}", "error") + messagebox.showerror("修复失败", f"无法挂载目标系统分区。请检查分区选择和权限。\n错误: {mount_err}") + self.start_button.config(state="normal") + return + + # 2. 尝试识别发行版类型 + self.log_message("正在检测目标系统发行版...", "info") + distro_type = backend.detect_distro_type(self.mount_point) + self.log_message(f"检测到目标系统发行版: {distro_type}", "info") + + # 3. Chroot并修复GRUB + self.log_message("正在进入Chroot环境并修复GRUB...", "info") + repair_ok, repair_err = backend.chroot_and_repair_grub( + self.mount_point, + target_disk_path, + self.is_uefi_mode, + distro_type + ) + if not repair_ok: + self.log_message(f"GRUB修复失败: {repair_err}", "error") + messagebox.showerror("修复失败", f"GRUB修复过程中发生错误。\n错误: {repair_err}") + # 即使失败,也要尝试卸载 + else: + self.log_message("GRUB修复命令执行成功!", "success") + + + # 4. 卸载分区 + self.log_message("正在卸载目标系统分区...", "info") + unmount_ok, unmount_err = backend.unmount_target_system(self.mount_point) + if not unmount_ok: + self.log_message(f"卸载分区失败: {unmount_err}", "error") + messagebox.showwarning("卸载警告", f"部分分区可能未能正确卸载。请手动检查并卸载。\n错误: {unmount_err}") + else: + self.log_message("所有分区已成功卸载。", "success") + + self.log_message("--- GRUB 修复过程结束 ---", "info") + + # 最终结果提示 + if repair_ok and unmount_ok: + messagebox.showinfo("修复成功", "GRUB引导修复已完成!请重启系统以验证。") + elif repair_ok and not unmount_ok: + messagebox.showwarning("修复完成,但有警告", "GRUB引导修复已完成,但部分分区卸载失败。请重启系统以验证。") + else: + messagebox.showerror("修复失败", "GRUB引导修复过程中发生错误。请检查日志并尝试手动修复。") + + self.start_button.config(state="normal") + + +if __name__ == "__main__": + root = tk.Tk() + app = GrubRepairApp(root) + root.mainloop() +