
1. 为什么需要重新打包ISO批量装机的真实痛点与双引导差异1.1 从手动点盘到全自动安装中间差一个preseed单台装机时手动安装完全没问题无非就是点几轮对话框、等一会儿进度条。可一旦要面对一批机器比如实验室里的十几台设备、机房里的存量机器重装手动安装的成本就会成倍放大。每台机器都要重复选语言、配置键盘、分区、设置用户、等镜像下载任何一步点错都会让整个流程中断。我的经验是交给我一个人手动装十台机器比配好一台“插上就能自动装”的镜像再复制到十台机器要慢得多也更容易出错。所谓无人应答安装在 Debian 里就是 preseed 机制。安装器从启动参数里读取一个答案文件把所有交互问题一次性回答掉然后直接跑完整套安装流程。实现方式可以有好几种通过 PXE 网络引导时传preseed/url...内核启动时传preseed/file...或者干脆把答案文件封进安装介质内部。标题里的“重新打包DebianISO”指的是最后一种把 preseed 文件放进 ISO让镜像本身自带答案开机后无需任何网络依赖就能静默完成安装。这个方案的缺点是每次打包需要重新生成 ISO优点是离线环境尤其受用。你不能指望每台机器都连得上同一个 HTTP 服务也不能保证现场网络足够稳定。把答案固化在镜像里光盘、U 盘随便写走到哪装到哪。1.2 BIOS 与 UEFI 两套引导体系不能只改一边很多第一次做定制镜像的人容易犯一个错误改完了一处引导菜单就觉得完事了结果拿到老机器上能用新机器上却停在引导菜单不动。根源在于一份 ISO 里其实同时存在两套引导链。BIOS 时代机器固件直接执行光盘/U 盘上的 MBR 引导代码随后进入 ISOLINUX 菜单。Debian 镜像里对应/isolinux/isolinux.cfg、menu.cfg、txt.cfg这一组文件。UEFI 时代固件先找 ESP 分区里的 EFI 引导程序对 CD 介质来说是通过 El Torito 记录的 EFI 启动镜像再由 GRUB 读取配置。Debian 镜像里对应/boot/grub/grub.cfg以及 EFI 启动镜像。因此要让一份镜像同时适应两种固件必须同时处理两套菜单文件并且在重新生成 ISO 时把 BIOS 启动镜像和 EFI 启动镜像都写进启动信息。这正好是本文标题“UEFIBIOS”想强调的部分。1.3 哪些场景值得重新打包哪些场景不必要重新打包 ISO 不是银弹。我的建议是先判断场景再动手如果只有两三台机器且能接受现场交互式操作官方 ISO 足够。如果网络环境很干净所有机器都能访问同一个 HTTP 服务直接传preseed/url更轻量不需要动 ISO。如果要做大量离线部署、统一加固基线、固化分区和密码策略、或者现场网络不稳定把 preseed 封进 ISO 重新打包是更稳妥的选择。搞清楚边界后再做决定能省掉很多白费的时间。2. 开工前的准备解包ISO、认识结构、备齐工具链2.1 解包ISO先看清自带的文件布局拿到官方 ISO 后第一件事不是急着改文件而是把它完整解包。用xorriso解包比较顺手mkdir -p /tmp/build cd /tmp/build xorriso -osirrox on -indev debian.iso -extract / custom-iso/ chmod -R w custom-iso/解包后建议先把关键目录列出来看看find custom-iso -maxdepth 2 -type d | sort正常情况下你会看到类似这样的结构路径作用/isolinux/BIOS 启动菜单文件ISOLINUX 引导要读这里/boot/grub/UEFI 引导时 GRUB 的配置目录/install.amd/内核vmlinuz与初始化内存盘initrd.gz安装器本体/pool/软件包仓库离线安装大部分流量从这里来/efi/部分镜像里存放 EFI 引导程序和转发配置/dists/、/doc/发行版元数据和文档一般不用动熟悉这个布局以后你就知道 preseed 文件该放哪、启动菜单该改哪。核心其实只有两处一个用于装载答案文件的preseed.cfg以及两条用于传递参数的菜单项。2.2 必备工具xorriso 是主角QEMU 是检验员重新打包一个同时支持 BIOS 和 UEFI 的 ISO核心工具是xorriso。它的-as mkisofs兼容模式可以直接生成混合型 ISO同时写入 MBR、Bios Boot 记录和 EFI El Torito 记录这正是双引导 ISO 最需要的。另外还需要isolinux包提供的 MBR 模板文件比如/usr/lib/ISOLINUX/isohdpfx.bin。测试阶段可以用 QEMU 分别模拟 BIOS 和 UEFI 启动避免每次都刻盘甚至烧坏 U 盘。apt install xorriso isolinux p7zip-full ovmf qemu-system-x86工具链不需要太复杂就这几个就够。装完后可以先用xorriso -version确认版本我一般会选 1.5 以上太旧对 EFI 启动镜像的处理容易出问题。2.3 preseed 文件的基本原理Debian 安装器基于 debconf 机制工作大多数安装步骤背后都是一个一个的配置模板。preseed 文件做的事情就是预先回答这些模板问题。安装器在启动时读取到preseed/file/cdrom/preseed.cfg或preseed/url...之后会在执行到对应步骤时直接采用答案不再跳交互界面。这解释了一个常见现象当你看见安装卡在某个问题上往往不是因为 preseed 没加载而是因为那个问题在 preseed 里没有被回答。反过来只要 preseed 写了某个字段安装器基本不会有再询问的余地。所以写出完整、正确的 preseed 是整个流程的核心。3. 编写可用的preseed配置分区、网络、用户一个都不能漏3.1 一份能跑通的最小答案文件下面这份 preseed 是我反复用过的骨架可以覆盖大部分服务器或桌面安装场景。把它保存为preseed.cfg放到解包目录/tmp/build/custom-iso/的根目录。# 语言与时区 d-i debian-installer/locale string en_US.UTF-8 d-i debian-installer/country string CN d-i debian-installer/language string en d-i keyboard-configuration/xkb-keymap select us # 网络优先 DHCP自动选择接口 d-i netcfg/choose_interface select auto d-i netcfg/get_hostname string debian-host d-i netcfg/get_domain string local # 镜像源选择 d-i mirror/country string manual d-i mirror/http/hostname string deb.debian.org d-i mirror/http/directory string /debian d-i mirror/http/proxy string # 分区使用自动分区里的 atomic 方案整块盘一个根分区加一个 swap d-i partman-auto/method string regular d-i partman-auto/choose_recipe select atomic d-i partman-partitioning/confirm_write_new_label boolean true d-i partman/choose_partition select finish d-i partman/confirm boolean true d-i partman/confirm_nooverwrite boolean true # 引导器安装 d-i grub-installer/only_debian boolean true d-i grub-installer/bootdev string /dev/sda # 用户账户 d-i passwd/root-login boolean false d-i passwd/user-fullname string Admin d-i passwd/username string admin d-i passwd/user-password password admin123 d-i passwd/user-password-again password admin123 d-i user-setup/allow-password-weak boolean true d-i user-setup/encrypt-home boolean false # 任务与软件包 tasksel tasksel/first multiselect standard d-i pkgsel/include string openssh-server vim-tiny curl d-i pkgsel/upgrade select none # 安装结束后自动重启 d-i finish-install/reboot_in_progress note这份文件里的atomic分区方案会自动把一整块盘划成一个可启动根分区和 swap。如果你需要更严格的分区布局比如/和/home分开那就要写expert_recipe不过那属于进阶用法第一版先跑通比较重要。3.2 最容易踩的三个坑第一磁盘设备名不一定是/dev/sda。传统 SATA 盘通常是/dev/sda但 NVMe 盘会是/dev/nvme0n1虚拟机里可能是/dev/vda。grub-installer/bootdev一旦写死错设备名安装会在最后一步卡住。更稳妥的做法是先把答案写死到你实际部署的磁盘型号上测试时专门用一个相同接口的虚拟机。第二没有 DHCP 的环境会一直卡在网络配置。如果目标网络是静态 IP必须在 preseed 里补这么几行d-i netcfg/disable_dhcp boolean true d-i netcfg/get_ipaddress string 192.168.1.100 d-i netcfg/get_netmask string 255.255.255.0 d-i netcfg/get_gateway string 192.168.1.1 d-i netcfg/get_nameservers string 192.168.1.1 d-i netcfg/confirm_static boolean true第三软件包源配置不当会导致安装后半段很长。默认情况下安装器会尝试访问网络镜像如果离线环境里网络不通最好把apt-setup相关项也预置好或者干脆接受只安装 ISO 自带的软件包集合避免卡死在这种非关键环节上。3.3 用“只预置一半”的方式排查配置错误preseed 文件语法错误不会像编译错误那样直接报出来常见表现是安装器某个步骤停住然后又重新弹出交互界面。排查时我有一个很实用的习惯第一次先只预置语言、键盘、分区、用户这几项启动参数里先不传prioritycritical或者干脆用普通优先级让没预置到的项自己问。这样你能一眼看出哪些步骤没被覆盖而不是跑到一半才发现某条选项写错了。等到基础项都确认没问题再用autotrue prioritycritical preseed/file...做完整无人值守测试。记住先少再全是避免调试时被细节淹没的最好办法。4. 把preseed塞进启动参数BIOS侧与UEFI侧分别怎么改4.1 BIOS 侧ISOLINUX 菜单加自动安装项把preseed.cfg放到 ISO 根目录之后开始修改 BIOS 菜单。打开/tmp/build/custom-iso/isolinux/txt.cfg它通常包含一个install标签。按下面的格式增加autoinstall标签default autoinstall label autoinstall menu label ^Automated install (preseed) kernel /install.amd/vmlinuz append initrd/install.amd/initrd.gz autotrue prioritycritical preseed/file/cdrom/preseed.cfg --- label install menu label ^Install kernel /install.amd/vmlinuz append initrd/install.amd/initrd.gz quiet ---其中autotrue让安装器进入自动模式prioritycritical表示只询问优先级为 critical 的问题而这些问题已经被 preseed 回答过了因此理论上不会出现任何对话框。preseed/file/cdrom/preseed.cfg是核心它告诉安装器去读镜像根目录下的答案文件。如果你想让开机后默认就执行自动安装把default autoinstall这一行保留如果还想给人保留手动安装的入口可以把default改回install只是在图形菜单里多出一个“Automated install”选项。4.2 UEFI 侧GRUB 菜单同样加一项UEFI 启动时读取的是 GRUB 配置。打开/tmp/build/custom-iso/boot/grub/grub.cfg在现有Install菜单项后面追加一个新的Automated install菜单项menuentry Automated install { linux /install.amd/vmlinuz autotrue prioritycritical preseed/file/cdrom/preseed.cfg --- initrd /install.amd/initrd.gz }有两点值得注意。首先vmlinuz和initrd.gz的路径必须与原始菜单保持一致不要凭记忆写路径。其次部分镜像里还存在/efi/boot/grub.cfg这类的转发配置它通常在启动时把控制权交给/boot/grub/grub.cfg大概率不需要重复修改。稳妥起见你可以在解包后对比两个文件内容看看哪个才是真正包含菜单定义的。4.3 两套引导共用同一个 preseed 文件的好处我见过有人为 BIOS 和 UEFI 分别写两个 preseed但这完全没必要。两个引导菜单只是启动参数不同读到的还是同一个/cdrom/preseed.cfg。共用文件的好处很直接修改一份两套引导自动生效不会出现 BIOS 装出来的系统和 UEFI 装出来的系统配置不一致的问题。需要留神的是两个菜单里追加的参数必须完全一致。尤其是preseed/file/cdrom/preseed.cfg这段少写一个等号、多写一个空格都会让安装器忽略答案文件然后继续交互式询问。我在最初做这一块时就是因为 BIOS 侧多写了quietUEFI 侧没写导致两个平台表现不一致排查了很久才发现。5. 使用xorriso重新生成双引导ISO参数逐条拆解5.1 一条命令构造可引导镜像所有文件改动完成后进入/tmp/build目录执行下面的命令cd /tmp/build xorriso -as mkisofs \ -V Custom Debian \ -o debian-custom.iso \ -J -joliet-long -cache-inodes \ -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \ -b isolinux/isolinux.bin \ -c isolinux/boot.cat \ -no-emul-boot -boot-load-size 4 -boot-info-table \ -eltorito-alt-boot \ -e boot/grub/efi.img \ -no-emul-boot \ -isohybrid-gpt-basdat \ custom-iso/这条命令每个参数都有明确任务-V Custom Debian设置 ISO 卷标相当于给光盘起名字。-J -joliet-long生成 Joliet 扩展让长文件名兼容性更好。-cache-inodes避免因为文件 inode 重复导致部分文件被跳过。-isohybrid-mbr 文件把 ISOLINUX 提供的 MBR 模板写入 ISO这是 USB 启动能正常工作的基础。-b isolinux/isolinux.bin、-c isolinux/boot.cat登记 BIOS 启动用的 El Torito 条目告诉固件从哪儿启动 ISOLINUX。-no-emul-boot -boot-load-size 4 -boot-info-tableBIOS 启动的兼容选项ISOLINUX 会依赖boot-info-table写入偏移信息。-eltorito-alt-boot -e boot/grub/efi.img登记第二个 El Torito 条目指向 EFI 启动镜像。-isohybrid-gpt-basdat让混合 ISO 在 GPT 分区表中正确标记 EFI 系统分区UEFI 固件才能识别。如果解包后发现efi.img不在boot/grub/下而是直接放在根目录那把-e boot/grub/efi.img改成-e efi.img即可。不同 Debian 版本的放置位置不完全一致我建议执行前跑一句find custom-iso -name efi.img用实际路径为准不用猜。5.2 校验新的ISO别等烧录才发现问题命令跑完后别急着烧 U 盘先在本地做几项快速校验。xorriso -indev debian-custom.iso -toc fdisk -l debian-custom.iso md5sum debian-custom.isofdisk -l输出里应该同时能看到 MBR 分区信息和 GPT 相关标记。如果只有普通分区而没有混合标记说明-isohybrid-mbr或-isohybrid-gpt-basdat没生效。md5sum则是给镜像本身留一个指纹后续批量拷贝时可以用来确认写盘结果是否完整。5.3 QEMU里分别模拟BIOS和UEFI启动校验通过后用 QEMU 做真实启动测试。BIOS 模式qemu-system-x86_64 -enable-kvm -m 2048 -cdrom debian-custom.iso -boot dUEFI 模式要额外指定 OVMF 固件qemu-system-x86_64 -enable-kvm -m 2048 -bios /usr/share/ovmf/OVMF.fd -cdrom debian-custom.iso -boot d观察点有两个虚拟机能进入安装菜单且菜单里出现了Automated install选择该项后整个安装流程能跑完中间不再弹出任何交互式对话框。如果卡住优先检查 preseed 文件里是否还有没覆盖到的问题其次再查启动参数拼写。6. 把整套流程沉淀成可复用方案与经验6.1 用模板生成 preseed实现批量差异化到了这一步单台机器的无人应答安装已经没问题。但真正到批量部署时每台机器的 hostname、IP、用户密码可能都不一样用同一份 preseed 显然不现实。我的做法是把preseed.cfg拆成一个模板用 Shell 脚本填充变量后生成最终答案文件再复制进解包目录重新打包。比如HOSTNAMEnode-01 IP_ADDR192.168.1.101 GATEWAY192.168.1.1 DNS192.168.1.1 sed -e s/{HOSTNAME}/$HOSTNAME/ \ -e s/{IP_ADDR}/$IP_ADDR/ \ -e s/{GATEWAY}/$GATEWAY/ \ -e s/{DNS}/$DNS/ \ preseed.template custom-iso/preseed.cfg配合一个循环脚本几十台机器的定制镜像一次性就能生成完。模板化之后preseed 文件就变成了一种“配置代码”可以放到版本管理系统里跟踪避免每次批量装机都要从零回忆。6.2 用 late_command 实现系统安装后的自动定制preseed 不只解决安装问题还能在安装收尾阶段注入自定义操作。late_command在文件系统已经部署完成后执行适合做一些初始化动作。比如给所有目标机器添加一个运维账户或者关闭不需要的默认服务d-i preseed/late_command string \ in-target useradd -m ops -s /bin/bash; \ in-target bash -c echo ops ALL(ALL) NOPASSWD:ALL /etc/sudoers.d/ops; \ in-target systemctl enable ssh每一句前面的in-target表示进入目标系统执行而不是在安装器环境里执行。这个字段是排除故障时的高价值工具也经常被用来注入 SSH 公钥省去装机后手动登录配置的成本。6.3 我踩过并且希望你提前避开的几个坑最后分享几条实操经验每条都是真金白银换来的preseed 文件的空行和缩进不要乱加。某些解析阶段对行首空格敏感第一版最好保证每行顶格写。修改启动菜单时不要把多个 menuentry 嵌套或漏掉结尾的右花括号。GRUB 语法错误在 UEFI 模式下会直接回到固件菜单而不是给你报错。每次重新打包前最好先清空旧的解包目录重新解压官方 ISO避免残留文件影响判断。虚拟机里能跑通不代表实机一定能跑通。实机上的磁盘控制器、网卡型号、固件版本都会带来差异批量部署前务必至少在一台有代表性的真机上验证一次。如果你把 preseed 放在/cdrom/preseed.cfg却没生效先检查是否使用了正确的启动菜单项。很多所谓的“preseed 失效”其实只是启动时选错了菜单根本没走上自动安装路径。把这套流程固定成一个脚本或笔记下次再用就是复制粘贴几分钟的事。无人应答安装的核心并不神秘一份完整的答案文件、两条启动参数、一次正确的双引导封装剩下的就是重复享受“开机不看屏幕”的畅快感。