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 f63e746..86e979e 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 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 环境