From add8a9643a1f0a899ab5fcfbe920e03e470e806d Mon Sep 17 00:00:00 2001 From: zj <1052308357@qq.com> Date: Thu, 12 Feb 2026 01:37:27 +0800 Subject: [PATCH] 5 --- AGENTS.md | 28 +++- __pycache__/backend.cpython-39.pyc | Bin 46078 -> 50636 bytes backend.py | 215 +++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 1 deletion(-) 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 86e979eca72ebf073813743695494adf9939235a..1156655fe47161ccde3e7f6f5bd8f2d484917788 100644 GIT binary patch delta 8717 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 }B?5zdAAH|gwc>0sU~ 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 Z2;V?Sf)xS$r+ky53%bx~{70d7R!myDVsuQ#jn6 zx%Zp^D1Wfgh{KfokM==>YKNe+9dP-U|FWdJjmz4&4cE?4pC9*SA2gr@MRAfQ700LdyMw zDDY`SKKJN$zjB{)M2+lyx;5A^GL^GeFPefV_A47y=OX7~&@rShx=*3|^u=)AEYBDC z6=$Q~7%;P8ko)z<`xIYP^?nH{0R4W+*JVQyV;t>DDs;zQZ#w~3F?$Aa{`9k6d9ccPQ zBX5NKD_%8|!BFSw57oH00mcx_-W8i#OSOOD@*Q!gb%u5km+y%at;KQw#^ryI{8wCP zEzO#8O5%~olNUun+pEMGGOgK6$fZ!%+Kc46upo;209PL(k!Gc;cX9hZlJ}5=&51kK z+sS2dYJGeB=fHKC*jW@_4%ymQSdy+jEZjH$feecJjw+z;j&h(!JA$@wJa|rq6E-ed zH|-^#ix)QC2>J^3z(E7R;jED2b24 zz=|RQMpl{yO{y$fQ79V`PZ)|t5wk{9* zQV~tQL!?lg_npWoLMz2c_JZ+Mp!0roEEhRtBDnV$$rX)5b}gI_v2Cb0PA0Yh0r?_0 zR8bZSOK0MMhjsMzZ0^|BPY3#Rk25hGM#m1Fd3yR?9u~sj#O0y+Hd%_9XvU9ZBN9ib zWnUK|l_LFc9<)pC;TlpP+=p!mRUnOnzef1;JSD{8zxikaP2@_q$%yw(_bWyMPhg*V zHJXICo+cZKTn!LU3^xY|tMkSKB9SKoCxfQ6Dm-z6$w)GijTAVM%9G$(N#n^tZ9D}Y z7ftHWjMQ$$NRxV2gB}w*?p5dvlzFYU_+o<7xI)uMRKo__m|z08jSw()9#m#<+r%3; znC-O&6H#W&07)jFF(Fx}xaOJ_Z&HBl`V&!%bfOGqd6Q`tPoG4zV<_o+(p|CP&q}3I zaU}q1rf<>&kN28sHb!wor8#sK*W_w;wrNO9MVWyFpZY* zY;jS1FBM8U2+JRei;b ziB&|*R7+xdM9h|E3 zNpTLOXn{;ItT@3HR5FLwb0T}->Ux@4G1PKlSSmHB0kdZ%I`6W3RoXC_U~mT{GLS`A z1Y-8=q>F>TX*>;jdKMgNjpB*ud2uk4 zwvG^R(h&|9sV|tt9l*p$PV-^|o^bH(BK)1^MPTA%x*AlOXxk1Yps{(-74dWpUE8g~ zm0d^INAG!wQ3Cf|VCI8~;b%ni3`3uEOg0zv$)@lw(kGoV8~BbwZ}6gM#AV@#c^L$1Wqop&fjgh9 z(q>toEk{gveDEgkOkPH*U%k6pRyun`G0J&)Fo)_;)e3O8i&sEByTkZ(@VJ^4v*L$; zvaXaWC1CXwS1LsQwgZhm(AHgBr<8fCEVEDc`#R+tU+5lQHjx>)N>cByvS4tXvYLMWJG_Lmdj+=DG*mJ^sGD~H#qUvN$+>}95 zw4eN7x-(`=S9gcI)4P?uCR$IGkx#_lQ){(=1%{sI#Ve;uG&R&IdtjKS4jB<$o3=FEEaElu2gri(O&K0dRMm|p;~7!AxXpAEq|NMumNXp8Wm zB*m~AkFD$L@p!lNZ*$i#vWk0uaj>u)Q8MY(2t{iSYdDW{Q1o}?{zJ5!U7=Q6#ShPx zktLzAvqePv0tIQ}yME-sk5|MuqdSrBE;XUL$6qEY z%(UE&20>~&5$<9 zUe26oN_OlcxWZyE9CV=om(GomSHu&)YFPRPGA<#(FKFQ}JMyd0WarC^AX~W--Kj^? zh-3*8%ne&6TE+@Vo!CBBM&^s7uvWw~W1~5Zk!O!6pFw{SRsK%@Y`RbJDedCeGbR56 DMIM}f 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 环境