LFS 编译记录 (VirtualBox + Void)

前言

LFS 编译记录 更新了两年,从 11.2 到 12.1,算上 rc 我一共构建了五版。 编译 5 遍之后很多东西都是驾轻就熟,文中的很多内容对未来的我自己而言参考价值一直在减少 1

另一方面,由于一直没购入 线程撕裂者,不想测试 ALFS 和 CLFS2,我对 LFS 的兴趣也在日益下滑。 特别是我已经不用 WSL,很多软件都直接在 Void 编译,LFS 作为离线编译环境的作用也是微乎其微, 何况真遇到这种情况我还可以用 void-vtoyboot 在 LiveCD 编译或者直接 VirtualBox/QEMU + immutable disk,对磁盘的修改重启就失效,连 WSL 导出 Zstd 压缩备份 + wsldl 导入的步骤都省了。

由于不再使用 WSL,内核部分有不小的差异,这次也会编译和配置 Grub,尝试用 Ventoy 启动。 除此之外的部分变化不大,很多就是改改版本号的复制粘贴。 Anyway,后续如果还会编译 LFS,都会更新到这篇文章,原文大概率不再更新。

💡 Tip

由于每次更新版本号都要改链接实在麻烦,下文没有超链接的软件包默认为 LFS/BLFS 自带。

准备

知识

下载

依赖

version-check.sh 见 Chapter 2.2. Host System Requirements

迁移至 void-mklive,使用自定义 base 镜像 3,已集成全部依赖, 只需要修改 /bin/sh 即可(出于性能考虑,Void 默认软链接到 dash), 参考依赖见 LFS#依赖汇总

sudo ln -s /bin/bash /bin/sh

分区

  • 本部分即 Chapter 2.4. Creating a New Partition
  • root partition: 10G 起步,20G 够用,30G 很奢侈
  • swap: SSD 不需要,next

由于这回切换到 VirtualBox/QEMU,随便建个上限 15G,动态分配的 VDI/QCOW2 即可。 如果后续考虑用 Ventoy 引导启动,需要直接初始化一个固定大小的 VDI/VHD/RAW。 直接在虚拟机中挂载,然后 cfdisk 创建分区 4,如果在桌面环境也可以使用 GParted,但没必要。

$ sudo fdisk -l
Disk /dev/sdb: 15 GiB, 16106127360 bytes, 31457280 sectors
Disk model: VBOX HARDDISK   
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 byte

$ sudo cfdisk /dev/sdb
# gpt, new, 15G Linux filesystem, write, yes, quit

$ sudo fdisk -l
Device     Start      End  Sectors Size Type
/dev/sdb1   2048 31455231 31453184  15G Linux filesystem

参考 2.5. Creating a File System on the Partition 用 mkfs 创建 ext4, 然后参考 2.7. Mounting the New Partition 挂载即可。

sudo mkfs -v -t ext4 /dev/sdb1
sudo mkdir /mnt/lfs
sudo mount /dev/sdb1 /mnt/lfs

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1        15G  2.1M   14G   1% /mnt/lfs

参考 2.6. Setting The $LFS Variable,设置 $LFS,在普通用户和 root 的 .bashrc 里都加上 export LFS=/mnt/lfs,并且确保全程都在用 bash。

echo "export LFS=/mnt/lfs" >> $HOME/.bashrc
chsh --shell /bin/bash

echo "export LFS=/mnt/lfs" | sudo tee -a /etc/bash/bashrc
sudo chsh --shell /bin/bash root

准备构建

直接解压下载好的软件包,同时查看 security advisories ,以确认是否需要升级。 也可以到 patches database 下载需要的 patch(3.3. Needed Patches)。

# Run as ROOT
mkdir -v $LFS/sources
# Make this directory writable and sticky
chmod -v a+wt $LFS/sources

sha1sum -c SHA1SUMS
tar xf lfs-packages-12.2.tar
rm lfs-packages-12.2.tar

cd 12.2
md5sum -c md5sums

接着按照指南走,标注一下我用的用户名和密码:

root: toor
lfs: lfs

lfs 用户创建 .bashrc 时,如果挂载位置不同,记得修改 $LFS

Export MAKEFLAGS 这里我给虚拟机分配了 4C16G,不怕物理机被搞死,-j5 拉满。 线程撕裂者 -j128 有多快我都不敢想,当然对应内存也得跟上。

export MAKEFLAGS='-j5'

Test suite 的问题见 Part II. 4.6. About the Test Suites 里提供的两个链接:

构建 Toolchain 和 Chroot 环境

只介绍第一步 binutils 的编译过程:

# Make sure $LFS is set one more time
echo $LFS

# Extract tarball
cd $LFS/sources
tar xf binutils-2.43.1.tar.xz
cd binutils-2.43.1

# Dedicated build directory recommended
mkdir -v build
cd build

# Menchmark SBU
time {
../configure --prefix=$LFS/tools \
             --with-sysroot=$LFS \
             --target=$LFS_TGT   \
             --disable-nls       \
             --enable-gprofng=no \
             --disable-werror    \
             --enable-new-dtags  \
             --enable-default-hash-style=gnu && make && make install;
}

# Clean up
cd $LFS/sources
rm -rf binutils-2.43.1

编译用时 49s,为估计后面编译时间的基准,即 SBU。 比 LFS#构建 Toolchain 和 Chroot 环境 的 13s 慢了三倍,虚拟机确实不行……

real    0m49.378s
user    1m59.720s
sys     0m22.891s

5.3. GCC-14.2.0 - Pass 1 这里的坑遇到几次都记不住,也难怪每次都会写 note。 手册写的这些命令,都要在 $LFS/sources/gcc-14.2.0 目录执行:

tar -xf ../mpfr-4.2.1.tar.xz
mv -v mpfr-4.2.1 mpfr
tar -xf ../gmp-6.3.0.tar.xz
mv -v gmp-6.3.0 gmp
tar -xf ../mpc-1.3.1.tar.gz
mv -v mpc-1.3.1 mpc

完成 Chapter 7 时耗时最久的应该是两次 gcc, 6.18. GCC-14.2.0 - Pass 2 耗时没有统计,根据 SBU 和旧文估算是半小时以内。

进入 Chroot

此时 $LFS,即 Chroot 主目录的文件结构:

(lfs chroot) I have no name!:/# ls -l 
total 40
lrwxrwxrwx   1 0 0     7 Sep 15 11:25 bin -> usr/bin
drwxr-xr-x  14 0 0  3240 Sep 15 11:15 dev
drwxr-xr-x   2 0 0  4096 Sep 15 12:15 etc
lrwxrwxrwx   1 0 0     7 Sep 15 11:25 lib -> usr/lib
drwxr-xr-x   2 0 0  4096 Sep 15 12:10 lib64
drwx------   2 0 0 16384 Sep 15 11:02 lost+found
dr-xr-xr-x 209 0 0     0 Sep 15 12:50 proc
drwxrwxrwt   2 0 0    40 Sep 15 12:50 run
lrwxrwxrwx   1 0 0     8 Sep 15 11:25 sbin -> usr/sbin
drwxrwxrwt   2 0 0  4096 Sep 15 12:46 sources
dr-xr-xr-x  13 0 0     0 Sep 15 11:14 sys
drwxr-xr-x   9 0 0  4096 Sep 15 12:09 tools
drwxr-xr-x   9 0 0  4096 Sep 15 12:31 usr
drwxr-xr-x   3 0 0  4096 Sep 15 12:15 var

只需注意以 root(而不是 lfs)运行命令即可。

I have no name!LFS 提过,遇到好多次都没新鲜感了,但这次注意到 FHS 规范, 前几天尝试 Peropesis 的时候也见过,记录在 Linux Distro。 要时不时检查 /usr/lib64,为了保证 LFS/BLFS 正常运行,这个目录不应该存在。

后文就没什么问题,关于 t64 和 Y2038 的问题稍微了解一下即可,不影响构建 LFS。

备份

完成之后来到 7.13. Cleaning up and Saving the Temporary System, 这里先退出 chroot 环境做一下备份,避免出错了得全部重头再来。

🚨 Warning

注意完成之后要重新加载 virtual kernel file system 并进入 chroot 环境。

手册里提到备份是压缩的,所以要挺久的。 如果觉得太慢,其实可以用支持多核/多线程的 pigz 替代 gzip,实测只要 1/4 的时间。 后文 #留念 阶段也可以使用这个来加速。

# 备份 LFS
cd $LFS
tar cf - . | pigz > $HOME/lfs-temp-tools-12.2.tar.xz

安装参考 #BLFS,当然这里是安装到挂载 LFS 的环境,而不是 LFS Chroot, 我写在那部分是完成 LFS 之后需要用到,LFS 其实不安装只用 Zstd 也行。

如果是虚拟机构建 LFS,备份完成后直接挪到 /home/lfs + chown, 然后 rsync 即可,1.4G 同步只需要 13s。

还原

迄今为止的 6 次编译用上过 1-2 次,希望再也用不上……

一定要以 root 身份运行,并且确保 $LFS 环境变量已经正确设置, 不然 $LFS 不存在默认 fallback 到 /,配合 root 用户的 rm -rf ./*,系统就🈚️了。

cd $LFS
rm -rf ./*
tar -xpf $HOME/lfs-temp-tools-12.2.tar.xz

构建

注意 chroot 里没有设置 MAKEFLAGS,参考 4.5. About SBUs 和 7.4. Entering the Chroot Environment 的内容,在 chroot 里输入下面的命令可以设置:

declare -x MAKEFLAGS='-j5'

如果在 chroot 里编译的时候不小心删了某个软件包的源码,没必要退出卸载全部环境再重新进 chroot,可以直接在外层系统移动文件到 $LFS/sources/,确保 $LFS 变量设置正确即可。

在 8.5. Glibc-2.40 需要注意,不要安装全部 locales,不过其实手册里的 minimum set of locales necessary for the optimal coverage of tests 已经 cover 了我的常用 locales:

# This is BAD (time-consuming)
# make localedata/install-locales

# This is good
# Required by test suite
localedef -i C -f UTF-8 C.UTF-8
localedef -i ja_JP -f SHIFT_JIS ja_JP.SJIS 2> /dev/null || true
# Additonal locale
localedef -i en_US -f UTF-8 en_US.UTF-8 2> /dev/null || true
localedef -i zh_CN -f UTF-8 zh_CN.UTF-8 2> /dev/null || true
localedef -i zh_CN -f GB18030 zh_CN.GB18030
localedef -i zh_HK -f BIG5-HKSCS zh_HK.BIG5-HKSCS
localedef -i zh_TW -f UTF-8 zh_TW.UTF-8

8.5.2.2. Adding time zone data 跳过 tzselect,直接输入:

ln -sfv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

8.21. GMP-6.3.0 需要注意,GMP 会根据编译 CPU 进行高度优化, 在其他 CPU 运行很可能会出错,届时在编译加入 --host=none-linux-gnu 重新编译。 不过我在开了 PAE/NX 的虚拟机运行测试还是提示 Illegal instruction,所以也没办法, 只能舍弃 CPU 的编译优化,注意需要删除源码文件夹重新解压,不然 make 直接不构建。 (虽然大概也能 make clean

$ ./configure --prefix=/usr    \
            --enable-cxx     \
            --disable-static \
            --host=none-linux-gnu \
            --docdir=/usr/share/doc/gmp-6.3.0

configure: summary of build options:

  Version:           GNU MP 6.3.0
  Host type:         none-none-linux-gnu
  ABI:               standard
  Install prefix:    /usr
  Compiler:          gcc
  Static libraries:  no
  Shared libraries:  yes

8.25. Acl-2.32 的 test suite 需要 Coreutils 支持,不过那个在 8.57. Coreutils-9.4。

8.27. Libxcrypt-4.4.36 需要注意二进制程序的 ABI Version 1 要求,但如果只在 LFS 使用源码编译的程序就无所谓。

在编译 LFS 的 8.28. Shadow-4.16.0 前可以先编译 BLFS 里的 CrackLib-2.10.12,然后在 configure 前参考指南多输一条命令,并额外加一则配置项:

touch /usr/bin/passwd
./configure --sysconfdir=/etc   \
            --disable-static    \
            --with-{b,yes}crypt \
            --without-libbsd    \
            --with-libcrack     \
            --with-group-name-max-length=32

8.29. GCC-14.2.0 的 make test 很耗时,但可以多核运行:

# Test the results as a non-privileged user, but do not stop at errors
chown -Rv tester .
# Use 4 cores
su tester -c "PATH=$PATH make -j4 -k check"

由于构建过很多次,跳过,手册似乎更新了,现在会移除/修复在 LFS 必定失败的测试。 我过往的 GCC 完整报告见 LFS#GCC Make Check,后面不再更新,参考 log 在 LFS 手册也有就不贴了。 此外 GCC 本身支持很多语言,但那些又要安装其他依赖,我就没有折腾,详见 BLFS

8.30. Ncurses-6.5 也有和 8.27. Libxcrypt-4.4.36 类似的 API 问题,这里重新编译一次。

8.51. Libffi-3.4.6 和上文提到的 GMP 类似,如果有跨 CPU 运行的需求,需要把 --with-gcc-arch=native 改成 --with-gcc-arch=x86-64GCC Manual 中合适的选项。但这里 make check 虚拟机并没有报错,就不管了。

8.63. Groff-1.23.0 注意修改 PAGE :

PAGE=A4 ./configure --prefix=/usr

这里先跳过 8.64. GRUB-2.12 的安装,到 BLFS 阶段再进行安装也可以。

系统配置

历经千辛万苦,来到 Chapter 9. System Configuration。

9.4. Managing Devices 以前因为在 WSL 操作不需要用到,都是随便看看,这回仔细看了感觉还是挺有帮助的, 特别是 udev-rules 的部分,如果在写 八位堂手柄的 udev-rules 之前看到可能就不用那么辛苦了。

9.5.1. Creating Network Interface Configuration Files,我虚拟机用的 NAT 网卡 + DHCP, 比静态 IP 多一步,需要参考 BLFS Chapter 14. Connecting to a Network 配置 DHCP 服务器。

9.6.4. Configuring the System Clock,运行下面的命令,确定硬件时间是 UTC(和实际时间不同),所以保持原文:

# Check hardware lock setting
hwclock --localtime --show

cat > /etc/sysconfig/clock << "EOF"
# Begin /etc/sysconfig/clock

UTC=1

# Set this to any options you might need to give to hwclock,
# such as machine hardware clock type for Alphas.
CLOCKPARAMS=

# End /etc/sysconfig/clock
EOF

9.6.5. Configuring the Linux Console 自行了解,没有配置文件则 console bootscript 不会做任何操作,而且 Linux console(注意这玩意儿不是通常理解的 terminal)其实并不实现 CJK 字符的完全显示,所以留空即可。

9.6.8. The rc.site File 可以取消默认设置中 distro 和 colored prefix 的相关注释。

9.7. Configuring the System Locale

cat > /etc/profile << "EOF"
# Begin /etc/profile

for i in $(locale); do
  unset ${i%=*}
done

if [[ "$TERM" = linux ]]; then
  export LANG=C.UTF-8
else
  export LANG=en_US.utf8
fi

# End /etc/profile
EOF

启动配置

制作分区表

10.2. Creating the /etc/fstab File

cat > /etc/fstab << "EOF"
# Begin /etc/fstab

# file system  mount-point    type     options             dump  fsck
#                                                                order

/dev/sdb1      /              ext4     defaults            1     1
/dev/sdb2      /boot/efi vfat codepage=437,iocharset=iso8859-1 0 1
# no swap for  SSD
# /dev/<yyy>   swap           swap     pri=1               0     0
proc           /proc          proc     nosuid,noexec,nodev 0     0
sysfs          /sys           sysfs    nosuid,noexec,nodev 0     0
devpts         /dev/pts       devpts   gid=5,mode=620      0     0
tmpfs          /run           tmpfs    defaults            0     0
devtmpfs       /dev           devtmpfs mode=0755,nosuid    0     0
tmpfs          /dev/shm       tmpfs    nosuid,nodev        0     0
cgroup2        /sys/fs/cgroup cgroup2  nosuid,noexec,nodev 0     0
efivarfs       /sys/firmware/efi/efivars efivarfs defaults 0     0

# End /etc/fstab
EOF

注意根据 df -h 输出修改 /dev/<xxx>,UEFI 启动需要 vfat 和那个奇怪的 efivarfs。 MS-DOS / Windows 格式的磁盘还需要额外配置,但和我虚拟机又有什么关系呢╮(╯_╰)╭

编译 Linux 内核

10.3. Linux-6.10.5 开始编译 Linux 内核,注意这需要 0.4-32 SBU(通常是 2,5 SBU)的时间并预计占用 1.7-14 GB(通常是 2.3 GB)的硬盘空间。

因为我隔天才继续,编译前务必进入 chroot 环境,并确保 $LFSMAKEFLAGS 设置正确

由于折腾过 Custom WSL2-Linux-Kernel,现在内核配置起码算是一知半解, 但是 WSL2-Linux-Kernel 毕竟和 mainline kernel 不一样(新版倒是用上了 LTS 6.6 内核), 这里就先不多魔改,基本就按照手册和默认的来,只在配置后额外启用了几个算法:

vim .config
-CONFIG_CRYPTO_CAMELLIA is not set
+CONFIG_CRYPTO_CAMELLIA=y
-CONFIG_CRYPTO_SERPENT is not set
+CONFIG_CRYPTO_SERPENT=y
-CONFIG_CRYPTO_TWOFISH is not set
+CONFIG_CRYPTO_TWOFISH=y

需要添加 UEFI 支持可以参考 BLFS 的 Grub 安装内核配置 。 准备在实机使用,并且需要闭源内核的话,可以参考 BLFS Firmwarelinux-firmware 镜像。 由于虚拟机自带 USB 支持,这部分仍需参考上文提到的 Custom-WSL2-Kernel。

下面是 USB 支持的部分配置,只针对 VirtualBox 和 6.10 内核进行调整,没有实际使用。 说实话我很多选项都是瞎选的,看着有用但实际上可能会影响 LFS 系统的稳定性, 起码我之前用 Custom-WSL2-Kernel 的时候偶尔会闪退,内核深坑还是不建议轻易尝试。 而且 menuconfig 和寻宝一样,常常要在这里挖个坑,那里摆面镜子,隐藏神庙才会出现= =

-> Device Drivers
	-> [*] USB support
		[*] Support for Host-side USB
	    [*] Enable USB persist by default
	    [*] OTG support
	    [M] USB Modem (CDC ACM) support
		[*] USB Mass Storage support
		[M] USB/IP support
		[M] VHCI hcd
		[M] USB Serial Converter support
			[M] USB FTDI Single Port Serial Driver
		[M] USB Gagdget Support
			-> USB Peripheral Controller

同理,无线网络连接也要修改内核配置(Configuring the Linux Kernel for Wireless), 并安装 Wireless Tools-29 和可能需要的固件,自己看文档吧。

以及更新/精简过的内核设置参考文档:

搞定 make menuconfig.config 修改后的实际编译流程:

# Compile kernel
declare -x MAKEFLAGS='-j5'
make
make modules_install
# NO NEED to make install
#make install
# Backup compiled kernel
# Note it's in x86 dir even on x86_64 platform
# $(pwd)-bzImage == /sources/linux-6.10.6-bzImagee if you follow LFS-12.2
cp arch/x86/boot/bzImage $(pwd)-bzImage

如果闲着没事干配置了 USB:

# Compile USBIP
cd tools/usb/usbip
./autogen.sh
./configure
make install
# Make USBIP toolchain accessible by USBIP
cp libsrc/.libs/libusbip.so.0 /lib/libusbip.so.0
# Backup USBIP & config
cp libsrc/.libs/libusbip.so.0 /sources/libusbip.so.0
cd ../../../
# same as above, /sources/linux-6.10.6.config if you follow LFS-12.2
cp .config $(pwd).config

切换到宿主机备份编译出的内核、配置和库:

cd ~/projects/lfs-12.2
mkdir build && cd build
rsync -avz void:/mnt/lfs/sources/linux-6.10.5-bzImage ./
rsync -avz void:/mnt/lfs/sources/linux-6.10.5.config ./
rsync -avz void:/mnt/lfs/sources/libusbip.so.0 ./

创建 Grub 引导

由于前面跳过了 8.64. GRUB-2.12 的安装,这里先安装 BLFS 的 GRUB-2.12 for EFI, 注意需要提前安装 FreeType-2.13.3,不然 configure 的时候会报错。

同时如果需要「救援模式」,又得参考 BLFS 安装 libisoburn-1.5.6 和它的两个依赖= = 但我安装完运行命令报错:

grub-mkrescue: error: `mformat` invocation failed

搜了下需要安装 GNU 的 mtools,但扫了眼 BLFS 目录没有(GParted 里可能有相关信息,没细看), 而 BLFS Using GRUB to Set Up the Boot Process with UEFI 中的救援 USB 又要安装另一个 dosfstools-4.2,实在是麻烦,遂弃。

其他参考 BLFS 提示即可,由于搞不懂 LVM,也没有安装 Fuse,这里我没有安装 grub-mount。 然后继续跟着 BLFS 走,在下面这里又受挫:

$ grub-install --bootloader-id=LFS --recheck
Installing for x86_64-efi platform.
grub-install: error: efibootmgr: not found.

仔细检查发现没有安装 efivar 和 efibootmgr,于是装上(顺带安装 popt),正常输出,继续。

10.4. Using GRUB to Set Up the Boot Process 需要注意,这里要根据 df -h 输出修改分区。 对于 UEFI 启动记得确保 EFI 分区存在,并且 /etc/fstab 中挂载的是正确的。

/boot/grub/grub.cfg 也需要同步修改:5

cat > /boot/grub/grub.cfg << "EOF"
# Begin /boot/grub/grub.cfg
set default=0
set timeout=5

insmod part_gpt
insmod ext2
set root=(hd0,2)

menuentry "GNU/Linux, Linux 6.10.5-lfs-12.2" {
        linux   /boot/vmlinuz-6.10.5-lfs-12.2 root=/dev/sdb1 ro
}
EOF

创建 Release 信息很简单,没啥好说的:

echo 12.2 > /etc/lfs-release

# Used by Linux Standards Base
cat > /etc/lsb-release << "EOF"
DISTRIB_ID="Linux From Scratch"
DISTRIB_RELEASE="12.2"
DISTRIB_CODENAME="vinfall"
DISTRIB_DESCRIPTION="Linux From Scratch"
EOF

# Used by systemd & GUI components
cat > /etc/os-release << "EOF"
NAME="Linux From Scratch"
VERSION="12.2"
ID=lfs
PRETTY_NAME="Linux From Scratch 12.2"
VERSION_CODENAME="vinfall"
HOME_URL="https://www.linuxfromscratch.org/lfs/"
EOF

排查

退出 chroot,卸载,重启,进 Grub,发现 LFS,进入失败,于是又重回 chroot 排查= = 由于 Grub 提示 /boot/vmlinuz-6.10.5-lfs-12.2 不存在,尝试导入内核备份, 但我复制的时候又提示是否要覆盖,所以内核还是在的。

继续排查,发现可能是 grub.cfg 的分区写岔了,修改重启还是一样,这才发现这块磁盘好像没 EFI 分区。 而且在 bootloader 阶段 VirtualBox 似乎只会挂载第一块硬盘,进 boot manager 切换还是一样的报错。

为了避免修好引导但内核 panic 的悲惨局面 6,决定在虚拟机的 /boot/ 测试编译好的 LFS 内核:

# 在 host OS 正常挂载硬盘
su - 
cp -iv $LFS/boot/vmlinuz-6.10.5-lfs-12.2 /boot/
update-grub
reboot

然后发现引导正常,进入的也是 LFS 系统,测试了其他数据也没问题,所以内核和系统整体都正常。

后面尝试运行下面这俩命令居然提示 command not found ,这才恍然大悟,Grub 根本没正常安装 并不是,单纯是我多插了块硬盘,所以装错分区了……

-bash-5.2# update-grub
-bash: update-grub: command not found
-bash-5.2# grub
-bash: grub: command not found

但因为现在是依靠 host OS 的引导进入了真正的 LFS,不再是 chroot,连 OpenSSH 都没有, LFS/BLFS 指南里的命令只能手动输入,对打字速度又是一大考验…… 而且不知道为什么 LFS 不会响应 VirtualBox 的 ACPI shutdown,shutdow now 又是 maintenance mode,一路 Ctrl + D 十次还自动风控了,WTF?估计是 SysVinit 的配置哪里写错了。 反正这样搞得我每次都要强制关机,总觉得不安全。

最后我选择扩容 VDI,再把 LFS 磁盘挂载到 Kali Live 用 GParted 划分个 100M 的 EFI 分区。 然后重新 grub-install,接着修改 /etc/fstab 分区表中的 EFI 路径,重启,看起来一切正常,先备份一手。 随后取消挂载原先用 void-mklive 制作的自定义镜像,重启,启动失败,关闭 vbox 的 UEFI,启动失败。 所以排查下来这 LFS 除了 Grub 其他部分都是正常的,但少了 void-mklive 的 EFI 分区就无法启动。

于是按图索骥,重新翻看 BLFS UEFI 启动部分,感觉可能是安装 efibootmgr-18 过程的问题, 当时错误的分区表中写的是 /dev/sdb1(即 host OS 的 EFI 分区 7),所以其实安装到了错误的分区:

# BLFS efibootmgr-18
make EFIDIR=LFS EFI_LOADER=grubx64.efi
make install EFIDIR=LFS
#  /boot/efi/EFI -> /dev/sdb1/efi/EFI

同时 BLFS Find or Create the EFI System Partition 部分提到,有的 UEFI 实现要求 ESP(即 EFI 分区)在磁盘的第一个分区,我这后面扩容创建的可能还不行。

在重装了 efibootmgr,重新走了一遍 BLFS 中的 UEFI 相关内容后,efibootmgr | cut -f 1 的输出里 LFS 总算成为了最后一个(启动优先级最高)。

然后自信 sudo reboot😎,看报错想起来根本没装 sudo😅,尴尬 reboot, 进 Firmware Setup,终于是大大的 LFS,进入,正常启动,排查完结🎆 并没有,还差最后一步,记得上面说 /boot/efi 分区的问题吗,取消挂载另一块硬盘后内核又 404 了= = 经过艰苦卓绝的努力,最终发现原因竟然是…… 分区表和 `grub.cfg` 都写的 `/dev/sdb`,移除另一块硬盘就变成 `/dev/sda` 了!非常蠢的设计,当然也可以改用 UUID,这样大概就能恢复正常。 Anyway,排查半天结果就这么点破事,我直接 创建一块 4M 大小啥也不干的 VDI解决。 然后发现真的可以,于是屁颠屁颠地修复了这个 bug,LFS 部分至此完结。

扩展

留念

本来要参考 #备份 章节导出 LFS 完成体留念,这次因为已经搞定单一 VDI,所以无所谓。 对于虚拟机而言,除了 tarball 还可以 Clonezilla 或者直接导出 OVA/OVF 镜像。

# Clonezilla
/usr/sbin/ocs-sr -q2 -c -j2 -z9p -i 0 -sfsck -senc -p choose savedisk lfs-12.2 nvme0n1

LFS 已安装 Zstd,这里用 Zstd/Pigz(上文的 pigz 是宿主机安装)都行。

# Exit Chroot
exit
# Make sure we are root with $LFS properly set
cd && pwd && ls
whoami
echo $LFS
# Export rootfs
cd $LFS

# SLOW backup
tar czf $HOME/lfs-rootfs-12.2.tar.gz .

# SPEEDY backup, change 4 to preferred core numbers
# tar cf - . | pigz -p 4 > $HOME/lfs-12.2-rootfs.tar.gz
tar cf - . | pigz -p 4 > $HOME/lfs-rootfs-12.2.tar.xz
# Or use zstd
tar cf - . | zstd -T4 > $HOME/lfs-rootfs-12.2.tar.zst

然后到 The Linux From Scratch Counter 登记(11.2. Get Counted)。

BLFS

传输源码和 #备份 结尾一样,用 rsync 同步即可,如果之前没有边编译边删除 tarball8,可以用 --delete 删除 $LFS 中已经不需要的部分软件包:

# 宿主机
cd ~/projects/lfs-12.2
rsync -avz blfs-packages/ user@void:/home/user/sources/
# 需要要删除已经用不着的 lfs-packages
# rsync -avz --delete blfs-packages/ user@void:/home/user/sources/
rsync -avz custom-packages/ user@void:/home/user/sources/

# 虚拟机(还没挂载进 chroot 环境时,单独开个 SSH 也行)
# 确保 $LFS 正常设置
echo $LFS
# 移动源码
mv sources/* $LFS/sources/

如果已完成 Grub 部分且无法进入 chroot,可以先安装 BLFS 的 OpenSSH-9.8p1,减少手动输入。

Please follow the manual against the version you’re building. Also, order matters since LFS gets no package manager to solve dependencies for you. Some packages even require each other (as hard/runtime dependency)…

  • BLFS Boot Scripts
  • rsync-3.3.0
  • tree-2.1.3
  • p11-kit-0.25.5
  • make-ca-1.14 (touch /usr/bin/trust to suppress warnings)
  • Wget-1.24.5 (go back to make-ca to add additional CA certs if you ever need PEM cert)
  • Fcron-3.2.1 (go back to make-ca to enable periodic jobs and setup Python3 to use system CA by the way)
  • cURL-8.9.1,额外安装 wcurl
  • Git-2.46.0
  • lsof-4.99.0
  • Lynx-2.9.2 (do NOT configure it to save cookies, I prefer W3M tbh)
  • zsh-5.9
  • Screen-4.9.1
  • Sudo-1.9.15p5
  • cpio-2.15
  • Lua-5.4.7
  • CMake-3.30.2 本身还好,但推荐的 libarchive 要装上又得装 libxml2 和升级很头疼的 ICU,遂弃

bash-completion:

./configure --prefix=/usr --sysconfdir=/etc
make
make install

Which-2.21 and Alternatives:

# Run the command as root
cat > /usr/bin/which << "EOF"
#!/bin/bash
type -pa "$@" | head -n 1 ; exit ${PIPESTATUS[0]}
EOF
chmod -v 755 /usr/bin/which
chown -v root:root /usr/bin/which

以及其他一些自定义软件包:

  • screenFetch (neofetch manual removed as it’s now archived, the same goes to fff)
wget -O screenfetch-dev https://raw.githubusercontent.com/KittyKatt/screenFetch/master/screenfetch-dev
chmod +x screenfetch-dev
mv screenfetch-dev /usr/bin/screenfetch
make
mv pigz /usr/local/bin/pigz
  • ncdu-1.20:
    • 本来还想试试编译 Zig 写的 ndcu2,但 BLFS 完全没有 Zig 的内容,遂作罢
    • 只能退而求其次用 C + Ncurses 写的初代
./configure --prefix=/usr
make
make install
  • nnn-5.0
    • nnn 依赖 LFS 缺少 xdg-open(xdg-utils-1.2.1),但这个又依赖于大部头 Xorg,于是不管,并不影响正常使用,只是不能使用默认打开方式而已
    • 编译见 Developer Guide
      • 由于我没装 PCRE2-10.44,直接使用默认的 POSIX regex
      • 配置项很多,但看 Makefile 默认值都比较合理,很多功能需要额外依赖我就不没有开启
    • 由于我只用 Bash/Zsh,没有装 Fish 的补全,不过照猫画虎也很简单
make PREFIX=/usr
make install

install -m644 misc/auto-completion/bash/nnn-completion.bash \
			/usr/share/bash-completion/completions/nnn
install -m644 misc/auto-completion/zsh/_nnn \
			/usr/share/zsh/site-functions/_nnn
  • LuaRocks,需要安装 UnZip-6.0,这里牵涉到 UnZip(错误的)压缩包编码处理,在 BLFS 范围内可以不管,记得别用来解压 CJK 压缩包即可(否则会文件名乱码)
./configure --prefix=/usr
make
make install
  • LuaJIT
    • 使用 rolling release,并且只分发源码,不提供二进制文件,所以只能用 GitHub mirror 的 v2.1.ROLLING tarball
    • 但是做到一半想到 Lua-5.4.7 提到一个 shared-library patch(虽然针对的是仍然使用旧版 Lua-5.2.4 的 WireShark/VLC),懒得手动改 Makefile,遂弃

Vtoyboot

有了 void-vtoyboot 的经验,这次是真的准备做成 Linux vDisk 用 Ventoy 启动。 于是尝试在 LFS 挂载 vtoyboot,并使用 Ventoy 启动 VDI。

mount /dev/cdrom /mnt/
cp /mnt/vtoyboot-*.tar.gz /sources/
umount /mnt

BLFS 阶段安装了 tree 所以这里直接查看 /boot/,发现不需要重新创建 EFI,比 Void 少一步:

tree /boot/efi/EFI/
/boot/efi/EFI/
├── BOOT
│   └── BOOTX64.EFI
├── grub
│   └── grubx64.efi
└── LFS
    └── grubx64.efi

4 directories, 3 files

然后 sh vtoyboot.sh,提示不支持 LFS,仔细看脚本发现是缺少 initramfs:

# vtoyboot.sh
if ! [ -f ./distros/$initrdtool/vtoy.sh ]; then
    echo 'Current OS is not supported!'
    exit 1
fi

# distros/initramfstool/check.sh
vtoy_check_initramfs_tool() {
    if which update-initramfs >/dev/null 2>&1; then
    ...
    else
        vtoy_false
    fi
}

BLFS 搜索关键词,找到 About initramfs 章节,文中提到 initramfs 最大的作用就是 dkms, 但在 LFS 内核是自己编译的,默认都是用 root,显然只有 root filesystem,确实用不上。 巧就巧在 vtoyboot 的实现原理就需要这个,还能怎么办,生成吧。

文中提到在 LFS 需要用到 initramfs 的几种可能:

  1. 网络启动,和 iVentoy 类似,也可以说和 Ventoy 的条件符合
  2. LVM,没有安装,与我无关
  3. 加密 rootfs,没有,next
  4. 使用 LABEL/UUID 标记 rootfs,即我在 #排查 结尾提到的方案,与 Ventoy 无关 9
  5. 其他情况下,更可能的是内核配置有误

当然,这里也可以安装 dracut,但既然 BLFS 没有,精疲力尽的我没动力自己动手。 先复制俩脚本,然后照例少不了多余的依赖 cpio-2.15,然后 mkinitramfs。 做到这里才想起来,除了内核固件还有 Microcode updates for CPUs, 我这 iGPU 似乎还需要 mesa/vulkan 一堆有的没的,又得改 Grub,瞬间失去兴趣。 而且配置完成后再次尝试运行 vtoyboot.sh,依旧提示不支持,懒得魔改了,继续 #有生之年。

未经测试的参考配置,注意因为我在 #排查 中删除了 LFS 的 grub.cfg,(用 grub-mkconfig?) 重新生成过, 所以才在 /etc/grub.d/40_custom 修改,正常情况下 LFS 应该直接修改 /boot/grub/grub.cfg

# /etc/grub.d/40_custom
menuentry 'LFS-12.2 initrd, Linux 6.10.5'
{  
        insmod part_gpt
        insmod ext2
        set root='hd1,gpt1'
        linux   /boot/vmlinuz-6.10.5-lfs-12.2 root=/dev/sda1 ro
        initrd  /boot/initrd.img-6.10.5
}

压缩

这一步的话其实没 LFS 什么事,方法应该都通用。

继续前想先压缩一下 15G 动态分配的 VDI,因为上面编译的各种缓存和中间产物,扩展到了已经 13.6G。 Clonezilla -z9p(即 zstd 压缩)备份只有 600M,硬盘实际使用就 2.2G,显然用不了 13.6G。

这时就不得不夸 WSL,虽然不会自动回收 VHDX 空间的设计让人很无语,各种 bug 也满天飞, 但手动运行 Optimize-VHD -Path ".\ext4.vhdx" -Mode Retrim -Confirm 基本都能够腾出不少空间。

经过一番搜索,发现 expert mode 选择 -icds 可以跳过磁盘大小的检测(即在容量较小的磁盘恢复容量较大磁盘的备份,当然,肯定得保证目标磁盘也能容纳下全部数据= =),但实测老是提示检测不到分区。 GParted, cfdisk, Clonezilla 来回切换,就是还原不上,很难受。 原因很无语,单纯是 Kali Live 沿用了上游 Debian 的软件包,没有把 part_to_local_part 操作需要的 fatresize 软件包设置为「推荐」的锅。 因为输出太多我前面直接忽略了,安装后再次运行就一切正常。

sudo apt install clonezilla fatresize dracut-core

备份 ext4 分区到末尾可能会提示在 restored OS 不能更新 initramfs,这是正常的, 毕竟 LFS 本来就没有╮( ̄▽ ̄)╭,详见 #Vtoyboot

随后在 VirtualBox 的 LFS 虚拟机设置里替换成小号磁盘,启动,运行正常,完美💅 这回算是解决了从 WSL 切换到 QEMU/vbox 最大的痛点,VDI/VHD/QCOW2 大概都能压缩。 重新导出的 OVA 镜像大小也从 6G+ 锐减到 670M+。

xorriso -as mkisofs -r -J -V "LFS" -o lfs-12.2.iso -b /boot/efi/EFI/BOOT/BOOTx64.EFI -no-emul-boot -boot-load-size 4 -boot-info-table /mnt/shared/lfs-12.2.img

Live CD

鉴于 vtoyboot 出师不利,依赖都搞不定,决定换成制作 Live CD,和 vtoyboot 的目的一样。 但是这里自己上手尝试了 xorriso, genisoimage, dd 都没搞定, LFS hints 中和 bootable CD 相关的都是十年没更新,其中一个 remastered 甚至是 07 年的, LFS LiveCD Project 也是停更状态,所以这里折腾半天还是决定暂缓,有生之年再继续。

Misc

ALFS 见 #前言,CLFS 更不敢想,哪天买了 RISC-V 板子可能考虑一下,但我估计 toolchain 编译都悬:

鼓捣过 cosmopolitan libc 之后感觉可以尝试 Cosmo-LFS,即创建在 AMD64/ARM64 Linux + Mac + Windows + BSD + BIOS 均可运行的单文件/系统,具体怎么玩还没想好,慢慢构思。


  1. 边际效益递减了属于是 ↩︎

  2. 归根结底,还是编译时间的锅。我在 Void(在不使用 sccache 的情况下)编译个 Firefox ESR 都需要 40-60 分钟,Chromium 更是根本没勇气尝试,感觉从早编译到晚不是梦。这也是我远离 Gentoo 的原因,当然现在 Gentoo 提供二进制包,但没本机优化的 Gentoo 有什么意义,Portage/Emerge 也算不上多好用<(  ̄^ ̄)> ↩︎

  3. 如果你打算在 Void LiveCD 完全离线编译,那么建议生成带 DE/WM 的镜像,而不是 base。当然不管怎么样都需要 gcc 和 glibc,所以 musl 版本肯定是不行的= =虽然有 Musl-LFS,但那个已经两年没更新,而且只在 LFS-9.1 上经过测试,LFS-10.x 都没有测试,不建议轻易入坑 ↩︎

  4. cfdisk 真的挺简单的,实在不会可以参考 How to Install Void Linux (Step by Step) ↩︎

  5. 注意这里正常的分区应该是第二个,我因为前面的错误修正后才是 /dev/sdb1,详见 #排查 ↩︎

  6. 虽然从马后炮的角度看这里多此一举,但对没有内核编译经验的新手而言,还是很容易 panic 的 ↩︎

  7. 注意这里要分清楚目的,我想实现的是单独启动的 LFS 虚拟机,所以说分区表是「错误的」。对于想在已经装有 GNU/Linux 的本机,额外安装 LFS 的系统而言,我原本的设置反而是正确的! ↩︎

  8. 虽然我只保留 lfs/blfs-bootscripts, gmp 和 linux(其他可以通过 wget-list 下载和 md5sum 校检),但如果磁盘空间足够(参考本文设置的 15G 是肯定够的),还是建议保留源码,后续修改也比较方便。而且大多数包管理器也是这么干的,甚至还会多保留几个版本方便回滚(虽然因为依赖问题大多数时候其实无法回滚……) ↩︎

  9. 这里写「无关」是因为我并不打算把包含 LFS 的 VDI 虚拟机集成进 Grub,如果要 loopback 那就有关了 ↩︎

Vinfall's Geekademy

Sine īrā et studiō