From 6a08d19fd80fc5b0cf40c43ced866749ca8da346 Mon Sep 17 00:00:00 2001 From: zj <1052308357@qq.com> Date: Thu, 12 Feb 2026 01:16:16 +0800 Subject: [PATCH] 4 --- AGENTS.md | 19 ++- __pycache__/backend.cpython-39.pyc | Bin 41877 -> 46078 bytes backend.py | 199 +++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 599101a..32b626c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,10 +49,27 @@ BootRepairTool/ - **NEW**: Gentoo - **NEW**: NixOS -### 4. GRUB 修复 (`backend.py:chroot_and_repair_grub`) +### 4. 内核恢复功能 (`backend.py:check_and_restore_kernel`) + +**NEW v2.1**: 自动检测和恢复缺失的内核文件 + +#### 检测逻辑 +- 检查 `/boot` 目录下的 `vmlinuz-*` 和 `initramfs-*.img` 文件 +- 如果缺失,自动尝试恢复 + +#### 恢复方法 +1. **从 /usr/lib/modules 复制** - 如果根分区包含内核模块目录 +2. **重新安装内核包** - 使用包管理器重新安装内核: + - CentOS/RHEL/Rocky/Alma: `yum reinstall kernel-core kernel-modules` + - Debian/Ubuntu: `apt-get install --reinstall linux-image-generic` + - Arch/Manjaro: `pacman -S linux` +3. **重新生成 initramfs** - 使用 dracut/mkinitcpio/update-initramfs + +### 5. GRUB 修复 (`backend.py:chroot_and_repair_grub`) #### BIOS 模式 - `grub-install --target=i386-pc --recheck --force /dev/sdX` +- **NEW**: 独立 `/boot` 分区支持(添加 `--boot-directory=/boot`) #### UEFI 模式 (参考 Calamares 实现) - 自动检测系统架构 (x86_64/i386/arm64/loongarch64) diff --git a/__pycache__/backend.cpython-39.pyc b/__pycache__/backend.cpython-39.pyc index f63e746f1b1cd5f30c9af18d05f97b7a57fd14c2..86e979eca72ebf073813743695494adf9939235a 100644 GIT binary patch delta 7456 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 diff --git a/backend.py b/backend.py index 893c8f4..d41777a 100644 --- a/backend.py +++ b/backend.py @@ -1228,6 +1228,199 @@ def install_efi_fallback(mount_point: str, efi_target: str, efi_grub_file: str, return False +def check_and_restore_kernel(mount_point: str, distro_type: str, has_separate_boot: bool) -> Tuple[bool, str]: + """ + 检查并恢复 /boot 目录下的内核文件。 + 如果内核文件缺失,尝试从 /usr/lib/modules 复制或重新安装内核包。 + + 返回: (是否成功, 错误信息) + """ + log_step("检查内核文件", f"独立 /boot 分区: {has_separate_boot}") + + boot_dir = os.path.join(mount_point, "boot") + + # 检查现有的内核文件 + vmlinuz_files = [] + initramfs_files = [] + + try: + if os.path.exists(boot_dir): + for f in os.listdir(boot_dir): + if f.startswith("vmlinuz-"): + vmlinuz_files.append(f) + elif f.startswith("initramfs-") and f.endswith(".img"): + initramfs_files.append(f) + except Exception as e: + log_warning(f"检查 /boot 目录失败: {e}") + + log_info(f"发现 {len(vmlinuz_files)} 个内核文件, {len(initramfs_files)} 个 initramfs 文件") + + # 如果内核文件存在,检查是否完整 + if vmlinuz_files and initramfs_files: + log_success("✓ 内核文件已存在") + return True, "" + + log_warning("内核文件缺失,尝试恢复...") + + chroot_cmd_prefix = ["sudo", "chroot", mount_point] + + # 方法1: 从 /usr/lib/modules 复制内核(如果存在) + usr_lib_modules = os.path.join(mount_point, "usr/lib/modules") + if os.path.exists(usr_lib_modules): + log_info("检查 /usr/lib/modules 中的内核...") + try: + kernel_versions = [] + for d in os.listdir(usr_lib_modules): + # 跳过 rescue 和普通目录,查找版本号格式的目录 + if os.path.isdir(os.path.join(usr_lib_modules, d)) and not d.endswith("kdump"): + kernel_versions.append(d) + + if kernel_versions: + log_info(f"找到内核版本: {kernel_versions}") + + for kernel_ver in kernel_versions: + # 检查该版本的内核文件是否存在 + kernel_source = os.path.join(usr_lib_modules, kernel_ver, "vmlinuz") + if not os.path.exists(kernel_source): + # 某些系统使用不同的路径 + kernel_source = os.path.join(mount_point, f"usr/lib/modules/{kernel_ver}/vmlinuz") + + if os.path.exists(kernel_source): + kernel_target = os.path.join(boot_dir, f"vmlinuz-{kernel_ver}") + log_info(f"复制内核: {kernel_source} -> {kernel_target}") + try: + shutil.copy2(kernel_source, kernel_target) + log_success(f"✓ 复制内核成功: vmlinuz-{kernel_ver}") + except Exception as e: + log_error(f"复制内核失败: {e}") + else: + log_warning("/usr/lib/modules 中没有找到内核") + except Exception as e: + log_warning(f"检查 /usr/lib/modules 失败: {e}") + + # 重新检查内核文件 + vmlinuz_files = [] + try: + for f in os.listdir(boot_dir): + if f.startswith("vmlinuz-"): + vmlinuz_files.append(f) + except: + pass + + # 方法2: 重新安装内核包(如果方法1失败) + if not vmlinuz_files: + log_info("尝试重新安装内核包...") + + # 根据发行版选择正确的包名 + kernel_packages = { + "centos": ["kernel-core", "kernel-modules"], + "rhel": ["kernel-core", "kernel-modules"], + "fedora": ["kernel-core", "kernel-modules"], + "rocky": ["kernel-core", "kernel-modules"], + "almalinux": ["kernel-core", "kernel-modules"], + "debian": ["linux-image-generic"], + "ubuntu": ["linux-image-generic"], + "arch": ["linux"], + "manjaro": ["linux"], + } + + packages = kernel_packages.get(distro_type, ["kernel"]) + + if distro_type in ["centos", "rhel", "fedora", "rocky", "almalinux"]: + success, _, stderr = run_command( + chroot_cmd_prefix + ["yum", "reinstall", "-y"] + packages, + f"重新安装内核包", + timeout=300 + ) + elif distro_type in ["debian", "ubuntu"]: + success, _, stderr = run_command( + chroot_cmd_prefix + ["apt-get", "install", "--reinstall", "-y"] + packages, + f"重新安装内核包", + timeout=300 + ) + elif distro_type in ["arch", "manjaro"]: + success, _, stderr = run_command( + chroot_cmd_prefix + ["pacman", "-S", "--noconfirm"] + packages, + f"重新安装内核包", + timeout=300 + ) + else: + log_warning(f"不支持的发行版 '{distro_type}',无法自动安装内核") + return False, "无法自动恢复内核文件" + + if not success: + log_error("内核包安装失败") + return False, "内核包安装失败" + + # 方法3: 重新生成 initramfs + log_info("重新生成 initramfs...") + + # 获取当前存在的内核版本 + vmlinuz_files = [] + try: + for f in os.listdir(boot_dir): + if f.startswith("vmlinuz-"): + vmlinuz_files.append(f) + except: + pass + + if not vmlinuz_files: + log_error("无法找到内核文件,无法生成 initramfs") + return False, "内核文件缺失" + + for vmlinuz in vmlinuz_files: + kernel_ver = vmlinuz.replace("vmlinuz-", "") + log_info(f"为内核 {kernel_ver} 生成 initramfs...") + + if distro_type in ["centos", "rhel", "fedora", "rocky", "almalinux"]: + # CentOS/RHEL 使用 dracut + success, _, stderr = run_command( + chroot_cmd_prefix + ["dracut", "-f", f"/boot/initramfs-{kernel_ver}.img", kernel_ver], + f"生成 initramfs for {kernel_ver}", + timeout=120 + ) + elif distro_type in ["debian", "ubuntu"]: + # Debian/Ubuntu 使用 update-initramfs + success, _, stderr = run_command( + chroot_cmd_prefix + ["update-initramfs", "-c", "-k", kernel_ver], + f"生成 initramfs for {kernel_ver}", + timeout=120 + ) + elif distro_type in ["arch", "manjaro"]: + # Arch 使用 mkinitcpio + success, _, stderr = run_command( + chroot_cmd_prefix + ["mkinitcpio", "-p", kernel_ver], + f"生成 initramfs for {kernel_ver}", + timeout=120 + ) + else: + # 通用方法:尝试 dracut + success, _, stderr = run_command( + chroot_cmd_prefix + ["dracut", "-f", f"/boot/initramfs-{kernel_ver}.img", kernel_ver], + f"生成 initramfs for {kernel_ver}", + timeout=120 + ) + + if success: + log_success(f"✓ initramfs 生成成功: {kernel_ver}") + else: + log_warning(f"initramfs 生成失败: {stderr}") + + # 最终检查 + try: + vmlinuz_count = sum(1 for f in os.listdir(boot_dir) if f.startswith("vmlinuz-")) + initramfs_count = sum(1 for f in os.listdir(boot_dir) if f.startswith("initramfs-") and f.endswith(".img")) + + log_info(f"恢复完成: {vmlinuz_count} 个内核, {initramfs_count} 个 initramfs") + + if vmlinuz_count > 0: + return True, "" + else: + return False, "内核恢复失败" + except Exception as e: + return False, f"检查 /boot 目录失败: {e}" + + def chroot_and_repair_grub(mount_point: str, target_disk: str, is_uefi: bool = False, distro_type: str = "unknown", install_hybrid: bool = False, @@ -1274,6 +1467,12 @@ def chroot_and_repair_grub(mount_point: str, target_disk: str, has_separate_boot = True log_info("检测到独立的 /boot 分区") + # 检查并恢复内核文件 + kernel_ok, kernel_err = check_and_restore_kernel(mount_point, distro_type, has_separate_boot) + if not kernel_ok: + log_error(f"内核恢复失败: {kernel_err}") + return False, f"内核恢复失败: {kernel_err}" + chroot_cmd_prefix = ["sudo", "chroot", mount_point] # 检测 Live 环境