diff --git a/AGENTS.md b/AGENTS.md index 32b626c..0b8a052 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,7 +65,33 @@ BootRepairTool/ - Arch/Manjaro: `pacman -S linux` 3. **重新生成 initramfs** - 使用 dracut/mkinitcpio/update-initramfs -### 5. GRUB 修复 (`backend.py:chroot_and_repair_grub`) +### 5. BLS 配置恢复 (`backend.py:restore_bls_entries`) + +**NEW v2.2**: 恢复 Boot Loader Specification (BLS) 启动条目 + +#### 适用系统 +- CentOS/RHEL 8+ +- Fedora 30+ +- Rocky Linux / AlmaLinux + +#### 问题场景 +当 `/boot` 分区被清空时,BLS 配置文件(`/boot/loader/entries/*.conf`)会丢失,导致 GRUB 菜单为空(`blscfg` 命令加载不到条目)。 + +#### 恢复方法 +1. **使用 kernel-install** - 调用 `kernel-install add` 重新生成 BLS 条目 +2. **手动创建 BLS 文件** - 从 `/etc/os-release` 读取系统信息,生成标准格式的 `.conf` 文件 +3. **重新生成 grub.cfg** - 确保 BLS 支持配置正确 + +#### BLS 文件格式示例 +``` +title CentOS Linux (4.18.0-348.el8.x86_64) 8 +version 4.18.0-348.el8.x86_64 +linux /vmlinuz-4.18.0-348.el8.x86_64 +initrd /initramfs-4.18.0-348.el8.x86_64.img +options root=/dev/mapper/cl-root ro crashkernel=auto rd.lvm.lv=cl/root rhgb quiet +``` + +### 6. GRUB 修复 (`backend.py:chroot_and_repair_grub`) #### BIOS 模式 - `grub-install --target=i386-pc --recheck --force /dev/sdX` diff --git a/__pycache__/backend.cpython-39.pyc b/__pycache__/backend.cpython-39.pyc index 86e979e..1156655 100644 Binary files a/__pycache__/backend.cpython-39.pyc and b/__pycache__/backend.cpython-39.pyc differ diff --git a/backend.py b/backend.py index d41777a..faa8b51 100644 --- a/backend.py +++ b/backend.py @@ -1421,6 +1421,215 @@ def check_and_restore_kernel(mount_point: str, distro_type: str, has_separate_bo return False, f"检查 /boot 目录失败: {e}" +def restore_bls_entries(mount_point: str, distro_type: str) -> Tuple[bool, str]: + """ + 恢复 BLS (Boot Loader Specification) 配置条目。 + CentOS/RHEL/Fedora 等使用 BLS 格式,启动项在 /boot/loader/entries/*.conf + + 返回: (是否成功, 错误信息) + """ + # 检查是否使用 BLS + loader_dir = os.path.join(mount_point, "boot/loader/entries") + grub_cfg_path = os.path.join(mount_point, "boot/grub2/grub.cfg") + + # 检查 grub.cfg 是否包含 blscfg + uses_bls = False + try: + if os.path.exists(grub_cfg_path): + with open(grub_cfg_path, 'r') as f: + content = f.read() + if 'blscfg' in content or 'BootLoaderSpec' in content: + uses_bls = True + log_info("检测到系统使用 BLS (Boot Loader Specification)") + except Exception as e: + log_debug(f"检查 BLS 失败: {e}") + + if not uses_bls: + log_debug("系统不使用 BLS,跳过 BLS 恢复") + return True, "" + + log_step("恢复 BLS 启动条目") + + chroot_cmd_prefix = ["sudo", "chroot", mount_point] + + # 方法1: 使用 kernel-install 重新生成 BLS 条目(推荐) + log_info("尝试使用 kernel-install 重新生成 BLS 条目...") + + # 获取已安装的内核版本 + boot_dir = os.path.join(mount_point, "boot") + kernel_versions = [] + + try: + for f in os.listdir(boot_dir): + if f.startswith("vmlinuz-"): + kernel_ver = f.replace("vmlinuz-", "") + kernel_versions.append(kernel_ver) + except Exception as e: + log_warning(f"读取 /boot 目录失败: {e}") + + if not kernel_versions: + log_error("没有找到内核版本,无法生成 BLS 条目") + return False, "没有内核版本" + + log_info(f"发现内核版本: {kernel_versions}") + + # 创建 loader 目录 + os.makedirs(loader_dir, exist_ok=True) + + for kernel_ver in kernel_versions: + # 检查是否已有 BLS 条目 + entry_file = os.path.join(loader_dir, f"{kernel_ver}.conf") + if os.path.exists(entry_file): + log_info(f"BLS 条目已存在: {entry_file}") + continue + + log_info(f"为内核 {kernel_ver} 生成 BLS 条目...") + + # 尝试使用 kernel-install + success, _, stderr = run_command( + chroot_cmd_prefix + ["kernel-install", "add", kernel_ver, f"/boot/vmlinuz-{kernel_ver}"], + f"生成 BLS 条目 for {kernel_ver}", + timeout=30 + ) + + if success: + log_success(f"✓ kernel-install 成功: {kernel_ver}") + else: + log_warning(f"kernel-install 失败,尝试手动创建 BLS 条目...") + + # 方法2: 手动创建 BLS 条目 + machine_id = "" + try: + machine_id_path = os.path.join(mount_point, "etc/machine-id") + if os.path.exists(machine_id_path): + with open(machine_id_path, 'r') as f: + machine_id = f.read().strip() + except: + pass + + if not machine_id: + machine_id = "unknown" + + # 读取 /etc/os-release 获取标题 + os_name = "Linux" + os_version = "" + try: + os_release_path = os.path.join(mount_point, "etc/os-release") + if os.path.exists(os_release_path): + with open(os_release_path, 'r') as f: + for line in f: + if line.startswith('NAME='): + os_name = line.split('=')[1].strip().strip('"') + elif line.startswith('VERSION_ID='): + os_version = line.split('=')[1].strip().strip('"') + except: + pass + + title = f"{os_name}" + if os_version: + title += f" {os_version}" + + # 获取根分区 UUID 或路径 + root_device = "/dev/mapper/cl-root" # 默认,应该根据实际检测 + + # 尝试从 /etc/fstab 获取根分区 + try: + fstab_path = os.path.join(mount_point, "etc/fstab") + if os.path.exists(fstab_path): + with open(fstab_path, 'r') as f: + for line in f: + if line.startswith('/') and ' / ' in line: + parts = line.split() + if len(parts) >= 2 and parts[1] == '/': + root_device = parts[0] + break + except: + pass + + # 创建 BLS 条目文件 + bls_content = f"""title {title} ({kernel_ver}) +version {kernel_ver} +linux /vmlinuz-{kernel_ver} +initrd /initramfs-{kernel_ver}.img +options root={root_device} ro +""" + + # 对于 CentOS/RHEL,添加特定的 LVM 和 resume 参数 + if distro_type in ["centos", "rhel", "fedora", "rocky", "almalinux"]: + bls_content = f"""title {title} ({kernel_ver}) +version {kernel_ver} +linux /vmlinuz-{kernel_ver} +initrd /initramfs-{kernel_ver}.img +options root={root_device} ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet +""" + + entry_filename = f"{machine_id}-{kernel_ver}.conf" + entry_path = os.path.join(loader_dir, entry_filename) + + try: + with open(entry_path, 'w') as f: + f.write(bls_content) + log_success(f"✓ 手动创建 BLS 条目: {entry_path}") + except Exception as e: + log_error(f"创建 BLS 条目失败: {e}") + return False, f"创建 BLS 条目失败: {e}" + + # 方法3: 重新运行 grub2-mkconfig 确保 BLS 支持正确 + log_info("重新生成 grub.cfg 以确保 BLS 支持...") + + # 找到 grub2-mkconfig 或 grub-mkconfig + mkconfig_cmd = None + for cmd in ["/usr/sbin/grub2-mkconfig", "/sbin/grub2-mkconfig", "/usr/bin/grub2-mkconfig"]: + if os.path.exists(os.path.join(mount_point, cmd.lstrip('/'))): + mkconfig_cmd = cmd + break + + if not mkconfig_cmd: + for cmd in ["/usr/sbin/grub-mkconfig", "/sbin/grub-mkconfig", "/usr/bin/grub-mkconfig"]: + if os.path.exists(os.path.join(mount_point, cmd.lstrip('/'))): + mkconfig_cmd = cmd + break + + if mkconfig_cmd: + # 找到 grub.cfg 路径 + grub_cfg = "/boot/grub2/grub.cfg" + for cfg_path in ["/boot/grub2/grub.cfg", "/boot/grub/grub.cfg"]: + if os.path.exists(os.path.join(mount_point, cfg_path.lstrip('/'))): + grub_cfg = cfg_path + break + + success, _, stderr = run_command( + chroot_cmd_prefix + [mkconfig_cmd, "-o", grub_cfg], + "重新生成 grub.cfg", + timeout=60 + ) + + if success: + log_success("✓ grub.cfg 重新生成成功") + else: + log_warning(f"grub.cfg 重新生成失败: {stderr}") + + # 最终检查 + try: + if os.path.exists(loader_dir): + entries = [f for f in os.listdir(loader_dir) if f.endswith('.conf')] + log_info(f"BLS 条目数量: {len(entries)}") + for entry in entries: + log_info(f" - {entry}") + + if len(entries) > 0: + log_success("✓ BLS 配置恢复完成") + return True, "" + else: + log_warning("没有 BLS 条目生成") + return False, "BLS 条目生成失败" + else: + log_error("loader/entries 目录不存在") + return False, "BLS 目录不存在" + except Exception as e: + return False, f"检查 BLS 条目失败: {e}" + + def chroot_and_repair_grub(mount_point: str, target_disk: str, is_uefi: bool = False, distro_type: str = "unknown", install_hybrid: bool = False, @@ -1473,6 +1682,12 @@ def chroot_and_repair_grub(mount_point: str, target_disk: str, log_error(f"内核恢复失败: {kernel_err}") return False, f"内核恢复失败: {kernel_err}" + # 恢复 BLS 启动条目(CentOS/RHEL/Fedora 使用 BLS) + bls_ok, bls_err = restore_bls_entries(mount_point, distro_type) + if not bls_ok: + log_warning(f"BLS 恢复失败: {bls_err}") + # BLS 失败不终止,继续尝试传统菜单 + chroot_cmd_prefix = ["sudo", "chroot", mount_point] # 检测 Live 环境