5
This commit is contained in:
28
AGENTS.md
28
AGENTS.md
@@ -65,7 +65,33 @@ BootRepairTool/
|
|||||||
- Arch/Manjaro: `pacman -S linux`
|
- Arch/Manjaro: `pacman -S linux`
|
||||||
3. **重新生成 initramfs** - 使用 dracut/mkinitcpio/update-initramfs
|
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 模式
|
#### BIOS 模式
|
||||||
- `grub-install --target=i386-pc --recheck --force /dev/sdX`
|
- `grub-install --target=i386-pc --recheck --force /dev/sdX`
|
||||||
|
|||||||
Binary file not shown.
215
backend.py
215
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}"
|
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,
|
def chroot_and_repair_grub(mount_point: str, target_disk: str,
|
||||||
is_uefi: bool = False, distro_type: str = "unknown",
|
is_uefi: bool = False, distro_type: str = "unknown",
|
||||||
install_hybrid: bool = False,
|
install_hybrid: bool = False,
|
||||||
@@ -1473,6 +1682,12 @@ def chroot_and_repair_grub(mount_point: str, target_disk: str,
|
|||||||
log_error(f"内核恢复失败: {kernel_err}")
|
log_error(f"内核恢复失败: {kernel_err}")
|
||||||
return False, 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]
|
chroot_cmd_prefix = ["sudo", "chroot", mount_point]
|
||||||
|
|
||||||
# 检测 Live 环境
|
# 检测 Live 环境
|
||||||
|
|||||||
Reference in New Issue
Block a user