DIY一个实用的 miniLAPP 服务器 [x86版]

作者:金步国


版权声明

本文作者是一位自由软件爱好者,所以本文虽然不是软件,但是本着 GPL 的精神发布。任何人都可以自由使用、转载、复制和再分发,但必须保留作者署名,亦不得对声明中的任何条款作任何形式的修改,也不得附加任何其它条件。您可以自由链接、下载、传播此文档,但前提是必须保证全文完整转载,包括完整的版权信息和作译者声明。

其他作品

本文作者十分愿意与他人共享劳动成果,如果你对我的其他翻译作品或者技术文章有兴趣,可以在如下位置查看现有作品的列表:

BUG报告,切磋与探讨

由于作者水平有限,因此不能保证作品内容准确无误,请在阅读中自行鉴别。如果你发现了作品中的错误,请您来信指出,哪怕是错别字也好,任何提高作品质量的建议我都将虚心接纳。如果你愿意就作品中的相关内容与我进行进一步切磋与探讨,也欢迎你与我联系。联系方式:MSN: csfrank122@hotmail.com


先决条件

本文的读者应当至少手动安装过一次 LFS ,如果你没有做过 LFS ,这篇文章很可能不适合你,请先按照《Linux彻底定制指南》做一遍 LFS 。另外,如果你对编译优化也很感兴趣,那么《GCC编译优化指南》也很值得一读。

目标

youbest兄的大作《5M大小的Apache服务器》和其续篇《600K的Apache服务器》将LFS的精神发挥到了极致,令人叹为观止!然而许多实用主义者(包括我在内)也只能叹为观止而已,因为这样的服务器由于过分追求小巧而变得不太实用,基本上不能在实践中用于生产目的。

鉴于上述原因,本文打算制作一个既实用又小巧的 Linux + Apache + PHP + PostgreSQL + OpenSSH + Iptables 服务器,并且实用优先于小巧。尽管实用优先于小巧,但是与基于普通发行版搭建的LAPP服务器相比仍然相当Mini(本文制作出来的最终系统核心部分大约15MB[其中体积较大的文件有:bzImage约1MB、postgres约3.5MB、libphp5.so约4.6MB、libc.so约1.3MB、libcrypto.so约1.1MB],总体积大约150MB),因此我把它称为"miniLAPP"服务器。当然,除了小巧,灵活和高效也重要,这在本文中主要体现在:㈠对软件包进行最大限度的自定义配置,㈡在保证稳定的前提下进行编译优化。

为了避免纠缠于各种复杂的硬件环境(它不是重点所在),本文将在 VMware Workstation 5.5.5 上建立一个虚拟机。具体如下:Intel 440BX 主板,athlon-xp处理器(如果你的CPU与我不同,那么只要将本文中所有的"athlon-xp"都替换成你的CPU型号即可,具体可以参考Gentoo Safe Cflags),512M内存(最低384MB),100M网卡(PCnet32),4G硬盘(BusLogic SCSI 0:0),CDROM(IDE 1:0 lfslivecd-x86-6.3-r2160-min.iso 仅供安装)。

网络环境:ip:192.168.10.33/24 ,broadcast:192.168.10.255 ,gateway:192.168.10.250 ,没有DNS 。如果你的环境与此不同,请自行调整相应的ip命令。

思路

基本上,小巧和实用是有冲突的,因为越要求实用就越需要各种功能,也就越无法保证小巧。为了解决这个问题,本文采用了"核心+扩展"的思路。所谓"核心"是指保证服务器正常运行必须使用的资源,比如:libc, init, httpd, postgres, libphp, sshd ... 以及各种设备文件、配置文件等等。所谓"扩展"是指非运行时必须的资源,比如:top, cat, gcc ... 等等,主要用于服务器维护。

解决方案是将"核心"部分安装在"/"目录下,使其在服务器一起动的时候就能够使用,而将"扩展"部分安装在"/usr"目录下,并且"/usr"位于独立的分区上,仅在需要使用的时候才手动挂载,使用完毕以后再手动卸载。

事实上,对于绝大多数软件包而言,所需要的只是其中的一小部分而已,因此,绝大部分软件包的主体都位于"/usr"目录下,仅将必须的某些部分安装到"/"下。

另一个问题是使用静态连接还是使用动态共享库?从小巧的目标来看,似乎应当使用静态连接,但是考虑到:

  1. 追求小巧并非第一重要,此处"Mini"的含义并非偏重于磁盘空间,而是尽可能减少不必要的程序和组件。
  2. 动态连接的程序可以在内存中共享库文件,而静态连接的程序则无法实现。考虑到此服务器可能扩展为提供 DNS, FTP, Mail, Proxy 等其它服务,静态连接将导致运行时占用更多的内存。

因此本文决定采用常规的动态连接。更多关于静态连接的害处,可以参考"Static Linking Considered Harmful"一文。


制作工具链前的准备工作

使用 LiveCD 开机,直接按回车键启动,所有设置均采用默认。

如果希望使用ssh进行远程安装,请执行如下步骤:

passwd
......
net-setup
......
/etc/rc.d/init.d/sshd start

磁盘分区与格式化(使用cfdisk的具体操作就不详细说明了,这里只给出结果):

/dev/sda1  ext2  /boot     10MB
/dev/sda2  xfs   /         50MB
/dev/sda3  xfs   none      50MB    [将来升级的备用根目录]
/dev/sda5  xfs   /usr      300MB
/dev/sda6  xfs   /root     300MB   [借用为 DESTDIR 父目录,也为将来升级的备用usr目录]
/dev/sda7  xfs   /var      200MB
/dev/sda8  xfs   /www      200MB   [ WEB 目录,借用存放临时工具链]
/dev/sda9  xfs   /data     剩余    [数据库目录,借用存放源代码和编译目录]

mke2fs -q -T news  /dev/sda1    &&
mkfs.xfs -f -q  /dev/sda2    &&
mkfs.xfs -f -q  /dev/sda3    &&
mkfs.xfs -f -q  /dev/sda5    &&
mkfs.xfs -f -q  /dev/sda6    &&
mkfs.xfs -f -q  /dev/sda7    &&
mkfs.xfs -f -q  /dev/sda8    &&
mkfs.xfs -f -q  /dev/sda9    &&

制作工具链之前的准备工作(省略了软件包和补丁的下载命令)。
[提示]如果在工具链的制作过程中中途关机,那么只需在重新开机后重新执行下面的命令即可恢复工作状态。不过,关机前一定要运行"sync"命令并umount所有挂载点,否则可能出现意外。 [说明]为了binutils-pass2测试成功,这里使用了一个rm废除LiveCD的g++和cpp,否则ld测试可能会失败。不过,没有C++编译器将会导致Glibc的 bug-atexit3-lib.os tst-cancel24.o c++-types-check 测试失败,并且无法运行dejagnu的测试程序。

rm -fr /usr/bin/*{c,g}++ /usr/include/c++ /usr/lib/lib{stdc++,supc++}.* /usr/lib/gcc/*/*/cc1plus /lib/cpp /usr/bin/cpp &&
export LFS=/mnt/lfs &&
mkdir -p $LFS       && mount -t xfs   /dev/sda2  $LFS       &&
mkdir -p $LFS/boot  && mount -t ext2  /dev/sda1  $LFS/boot  &&
mkdir -p $LFS/usr   && mount -t xfs   /dev/sda5  $LFS/usr   &&
mkdir -p $LFS/root  && mount -t xfs   /dev/sda6  $LFS/root  &&
mkdir -p $LFS/var   && mount -t xfs   /dev/sda7  $LFS/var   &&
mkdir -p $LFS/www   && mount -t xfs   /dev/sda8  $LFS/www   &&
mkdir -p $LFS/data  && mount -t xfs   /dev/sda9  $LFS/data  &&
# 这里省略了下载软件包和补丁的命令
ln -sf $LFS/www /   &&
echo 'exec env -i HOME=$HOME TERM=$TERM PS1="\\u:\\w\\\$ " /bin/bash' > ~/.bash_profile   &&
echo "set +h
umask 022
export LFS=$LFS
export SRC=$LFS/data
export PATH=/www/bin:/bin:/usr/bin
export LC_ALL=C TZ=UTC
export CFLAGS='-DNDEBUG -O3 -finline-limit=200 -fomit-frame-pointer -pipe -fno-bounds-check -freg-struct-return -march=athlon-xp'
export LDFLAGS='-s -Wl,-O1,-s,--enable-new-dtags,--hash-style=gnu'
export kLDFLAGS='-O1 --enable-new-dtags --hash-style=gnu'
alias make='make -j1' mkdir='mkdir -p' patch='patch -p1 -i' mv='mv -f' cp='cp -pf' rm='rm -fr'
cd \$SRC" > ~/.bashrc   &&
source ~/.bash_profile

为了尽可能使工具链与后面编译的最终系统兼容,这里在工具连阶段就使用相同的优化参数进行编译(pass1例外),不过由于宿主系统的影响,这样做可能会导致很多测试失败,所以工具连阶段仅对Glibc/GCC/Binutils进行测试,并且忽略Glibc/GCC测试错误(只记录测试信息)。[说明]kLDFLAGS是专供内核使用的连接器参数(直接传递给ld)。

编译过程是一个漫长的冒险,特别是在使用了一大堆configure选项和优化参数的情况下,出现错误是再正常不过的事情了。本文的所有代码都在我的CPU上测试通过,因为我没有更多的CPU,所以无法进行更多的测试,但是一个基本原则就是,一旦遇到编译或测试错误,首先想到的应当就是降低优化级别,看看能不能通过。理论上,将本文所有的"athlon-xp"都替换成"i686",并且设置 CFLAGS="-O2 -fomit-frame-pointer -pipe -march=i686" LDFLAGS="-s" kLDFLAGS="",应当可以在目前实际使用的所有 x86 CPU 上顺利完成。如果降低优化级别仍然不能解决问题,那么可以继续调整configure选项。

下面这些优化选项可能是地雷,尽量不要使用,如果你喜欢冒险,并且不小心踩到了,别怪我没提醒过你.....

CFLAGS
"-D_FILE_OFFSET_BITS=64 -fvisibility=hidden; -malign-double; -mregparm=3; -msseregparm; -ftracer; --param max-gcse-memory=100M; --param max-gcse-passes=3; -Wa,-R; -Wa,-march=athlon"至少会导致Glibc配置或编译失败。"-m128bit-long-double"会导致GCC的gcc.dg/pr19402-2.c测试失败,Glibc的math/test-misc.out测试失败,autoconf的AC_PROG_SED测试失败,tar的31/33/35号测试失败。"-freg-struct-return"虽然能让Glibc通过测试但却会间接导致GCC的gcc.dg/struct-ret-libc.c测试失败,这是二进制兼容性所致,一般可以忽略它。"-ftree-loop-linear"会导致PostgreSQL无法运行测试程序(无法初始化数据库),还会导致JPEG测试失败。

LDFLAGS
"--as-needed"将导致Glibc的stdlib,nptl,elf测试出现多处错误,"-znow"将导致Glibc的elf测试出现多处错误。"--sort-common"会导致Automake的ccnoco.test测试失败(直接和间接原因都有??)。

kLDFLAGS
"-s"会导致内核编译失败。


制作工具链

此处工具链的制作基本上与 LFS 第五章相同,只是增删几个包、改动了一些配置选项、简化了几个包的安装动作、将几个补丁的功能用 sed 进行了替代、以及其它一些小变化等等,因此下面只列出命令而不进行任何说明。

( test ! -f $SRC/tmp.log ) && [ "`pwd`" = "$SRC" ] &&
tar -xf binutils-2.18.tar.bz2 && mkdir binutils-build && cd binutils-build &&
echo 'MAKEINFO = :' >> ../binutils-2.18/Makefile.in &&
CFLAGS="-O2 -fomit-frame-pointer -pipe -march=athlon-xp" LDFLAGS="-s"  \
../binutils-2.18/configure --prefix=/www --disable-nls --disable-rpath --disable-multilib --disable-shared --enable-static --disable-werror --without-gmp --without-mpfr --without-included-gettext --without-libiconv-prefix --without-libintl-prefix --with-pic &&
make all install tooldir=/www/ && cp ../binutils-2.18/include/libiberty.h /www/include &&
make clean-ld all-ld LIB_PATH=/www/lib tooldir=/www/ && cp ld/ld-new /www/bin/ &&
cd $SRC && rm */ &&
echo '1-binutils-pass1' >> $SRC/tmp.log && sync &&

tar -xf gcc-core-4.2.3.tar.bz2 && mkdir gcc-build && cd gcc-build &&
sed -r -i 's|^LDFLAGS(_FOR_TARGET)? =|& @LDFLAGS@|' ../gcc-4.2.3/Makefile.in &&
sed -i 's/install_to_$(INSTALL_DEST) //' ../gcc-4.2.3/libiberty/Makefile.in &&
sed -i -e's@\./fixinc\.sh@-c true@' -e'/^LIBGCC2_DEBUG_CFLAGS/d' ../gcc-4.2.3/gcc/Makefile.in &&
sed -i '/MULTILIB_OSDIRNAMES/d' ../gcc-4.2.3/gcc/config/i386/t-linux64 &&
CFLAGS="-O2 -fomit-frame-pointer -pipe -march=athlon-xp" LDFLAGS="-s"  \
../gcc-4.2.3/configure --prefix=/www --with-local-prefix=/www --enable-languages=c --enable-bootstrap --disable-nls --disable-rpath --disable-multilib --enable-shared=libgcc --enable-static=libgcc --disable-libgomp --disable-libmudflap --disable-libssp --enable-linux-futex --enable-threads=posix --enable-tls --disable-symvers --with-as=/www/bin/as --with-ld=/www/bin/ld --without-included-gettext --without-libiconv-prefix --without-libintl-prefix --without-gmp --without-mpfr --without-system-libunwind --without-system-zlib --with-pic --without-x &&
make bootstrap4 install LIBCFLAGS="-O2 -fomit-frame-pointer -pipe -march=athlon-xp" BOOT_CFLAGS="-O2 -fomit-frame-pointer -pipe -march=athlon-xp" BOOT_LDFLAGS="-s" &&
ln -sf gcc /www/bin/cc &&
INC=`gcc -print-file-name=include` &&
TOL=`gcc -print-file-name=install-tools` &&
rm  $INC/* &&
cp  $TOL/include/*     $INC &&
cp  $TOL/gsyslimits.h  $INC/syslimits.h &&
unset INC TOL &&
cd $SRC && rm */ &&
echo '2-gcc-pass1' >> $SRC/tmp.log && sync &&

tar -xf linux-2.6.24.2.tar.bz2 && cd linux-2.6.24.2 &&
make distclean && make headers_check ARCH=x86 &&
make headers_install ARCH=x86 INSTALL_HDR_PATH=temp &&
cp -r temp/include/* /www/include/ &&
cd $SRC && rm */ &&
echo '3-kernel-headers' >> $SRC/tmp.log && sync &&

tar -xf glibc-2.6.1.tar.bz2 && mkdir glibc-build && cd glibc-build &&
find ../glibc-2.6.1 -name configure | xargs touch &&
LDFLAGS="`echo $LDFLAGS | sed 's/,--hash-style=gnu//'`"  \
../glibc-2.6.1/configure --prefix=/www --enable-add-ons=nptl --enable-shared --enable-kernel=2.6.22 --disable-oldest-abi --disable-profile --with-headers=/www/include --with-binutils=/www/bin --without-cvs --with-elf --without-gd --without-gmp --without-selinux --with-tls &&
echo 'MAKEINFO = :' >> configparms &&
make && ( make -k check 2> $SRC/tmp_glibc.log ; make install ) &&
cp --remove-destination /www/share/zoneinfo/UTC /www/etc/localtime &&
cd $SRC && rm */ &&
echo '4-glibc' >> $SRC/tmp.log && sync &&

mv /www/bin/{ld-new,ld} &&
gcc -dumpspecs | sed 's@/lib/ld-linux.so.2@/www/lib/ld-linux.so.2@g' > `dirname $(gcc -print-libgcc-file-name)`/specs &&
echo 'main(){}' > dummy.c && cc dummy.c && readelf -l a.out | fgrep '[Requesting program interpreter: /www/lib/ld-linux.so.2]' && rm dummy.c a.out &&
echo '5-Adjusting' >> $SRC/tmp.log && sync &&

tar -xf tcl8.4.18-src.tar.gz && cd tcl8.4.18/unix &&
./configure --prefix=/www --enable-threads --enable-shared --enable-load --enable-gcc --disable-symbols --disable-64bit --disable-64bit-vis --disable-langinfo --disable-corefoundation --disable-dtrace --disable-framework --disable-man-symlinks --disable-man-compression --disable-man-suffix &&
make && make install install-private-headers &&
ln -sf tclsh8.4 /www/bin/tclsh &&
cd $SRC && rm */ && sync &&
tar -xf expect-5.43.0.tar.bz2 && cd expect-5.43 &&
patch $SRC/expect-5.43.0-spawn-2.patch &&
sed -i 's/STTY_BIN=.*/STTY_BIN=stty/g' configure{.in,} &&
./configure --prefix=/www --enable-threads --enable-shared --enable-load --enable-gcc --with-tcl=/www/lib --with-tclinclude=/www/include --with-x=no --disable-symbols &&
make expect && make install SCRIPTS="" &&
cd $SRC && rm */ && sync &&
tar -xf dejagnu-1.4.4.tar.gz && cd dejagnu-1.4.4 &&
./configure --prefix=/www &&
make install &&
cd $SRC && rm */ &&
echo '6-tcl-expect-dejagnu' >> $SRC/tmp.log && sync &&

tar -xf gcc-core-4.2.3.tar.bz2 && tar -xf gcc-testsuite-4.2.3.tar.bz2 && cd gcc-4.2.3 &&
sed -r -i 's|^LDFLAGS(_FOR_TARGET)? =|& @LDFLAGS@|' Makefile.in &&
sed -i 's/install_to_$(INSTALL_DEST) //' libiberty/Makefile.in &&
sed -i -e's@\./fixinc\.sh@-c true@' -e'/^LIBGCC2_DEBUG_CFLAGS/d' gcc/Makefile.in &&
sed -i '/MULTILIB_OSDIRNAMES/d' gcc/config/i386/t-linux64 &&
echo '/* Remove /usr/include from end of include search path.  */
#undef STANDARD_INCLUDE_DIR
#define STANDARD_INCLUDE_DIR 0' | tee -a gcc/config/linux.h gcc/config/i386/linux{,64}.h &&
sed -i 's@/lib/ld.so@/www&@g' gcc/config/i386/gnu.h &&
sed -i 's@/lib/ld-linux.so.2@/www&@g' gcc/config/i386/linux{,64}.h &&
sed -i 's@/lib64/ld-linux-x86-64.so.2@/www&@g' gcc/config/i386/linux64.h &&
# sed -i '/LINK_SPEC/s/ "/&--hash-style=gnu /' gcc/config/i386/linux{,64}.h &&
mkdir ../gcc-build && cd ../gcc-build &&
../gcc-4.2.3/configure --prefix=/www --with-local-prefix=/www --enable-languages=c --enable-bootstrap --disable-nls --disable-rpath --disable-multilib --enable-shared=libgcc --enable-static=libgcc --disable-libgomp --disable-libmudflap --disable-libssp --enable-linux-futex --enable-threads=posix --enable-tls --disable-symvers --with-as=/www/bin/as --with-ld=/www/bin/ld --without-included-gettext --without-libiconv-prefix --without-libintl-prefix --without-gmp --without-mpfr --without-system-libunwind --without-system-zlib --with-pic --without-x &&
make profiledbootstrap LIBCFLAGS="$CFLAGS" BOOT_CFLAGS="$CFLAGS" BOOT_LDFLAGS="$LDFLAGS" &&
( make -k check | tee $SRC/tmp_gcc.log ; make install ) &&
INC=`gcc -print-file-name=include` &&
TOL=`gcc -print-file-name=install-tools` &&
rm  $INC/* &&
cp  $TOL/include/*     $INC &&
cp  $TOL/gsyslimits.h  $INC/syslimits.h &&
unset INC TOL &&
cd $SRC && rm */ && sync &&
echo 'main(){}' > dummy.c && cc dummy.c && readelf -l a.out | fgrep '[Requesting program interpreter: /www/lib/ld-linux.so.2]' && rm dummy.c a.out &&
echo '7-gcc-pass2' >> $SRC/tmp.log && sync &&

tar -xf binutils-2.18.tar.bz2 && mkdir binutils-build && cd binutils-build &&
echo 'MAKEINFO = :' >> ../binutils-2.18/Makefile.in &&
CFLAGS="$CFLAGS -fno-inline-functions" \
../binutils-2.18/configure --prefix=/www --with-lib-path=/www/lib --disable-nls --disable-rpath --disable-multilib --disable-shared --enable-static --disable-werror --without-gmp --without-mpfr --without-included-gettext --without-libiconv-prefix --without-libintl-prefix --with-pic &&
make all check install tooldir=/www/ &&
make clean-ld all-ld check-ld LIB_PATH=/lib:/usr/lib tooldir=/www/ && cp ld/ld-new /www/bin/ &&
cp ../binutils-2.18/include/libiberty.h /www/include/ &&
cd $SRC && rm */ &&
echo '8-binutils-pass2' >> $SRC/tmp.log && sync &&

tar -xf bash-3.2.tar.gz && cd bash-3.2 &&
patch $SRC/bash-3.2-fixes-7.patch &&
./configure --prefix=/www --disable-minimal-config --disable-profiling --enable-static-link --disable-nls --disable-rpath --without-lispdir --without-afs --without-bash-malloc --without-gnu-malloc --without-curses --without-termcap --disable-install-termcap --with-installed-readline=no --without-purecov --without-purify --without-libiconv-prefix --without-included-gettext --without-libintl-prefix &&
make && cp bash /www/bin/ && ln -sf bash /www/bin/sh &&
cd $SRC && rm */ &&
echo '9-bash' >> $SRC/tmp.log && sync &&

tar -xf bzip2-1.0.4.tar.gz && cd bzip2-1.0.4 &&
make bzip2 CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64" LDFLAGS="$LDFLAGS" &&
cp bzip2 /www/bin/ && ln -sf bzip2 /www/bin/bunzip2 &&
cd $SRC && rm */ &&
echo '10-bzip2' >> $SRC/tmp.log && sync &&

tar -xf coreutils-6.10.tar.gz && cd coreutils-6.10 &&
sed -i '/(setuid_root_mode)/s/(installed_su)/&;/' src/Makefile.in &&
./configure --prefix=/www --enable-option-checking --disable-acl --disable-assert --disable-rpath --disable-nls --enable-install-program="arch,hostname,su" --without-libiconv-prefix --without-included-regex --without-libintl-prefix &&
make && make install install-root &&
cd $SRC && rm */ &&
echo '11-coreutils' >> $SRC/tmp.log && sync &&

tar -xf diffutils-2.8.1.tar.gz && cd diffutils-2.8.1 &&
./configure --prefix=/www --disable-rpath --disable-nls --without-libiconv-prefix --without-included-regex --without-libintl-prefix &&
make && cp src/{cmp,diff} /www/bin/ &&
cd $SRC && rm */ &&
echo '12-diffutils' >> $SRC/tmp.log && sync &&

tar -xf findutils-4.2.33.tar.gz && cd findutils-4.2.33 &&
./configure --prefix=/www --disable-id-cache --disable-debug --disable-leaf-optimisation --disable-d_type-optimization --disable-nls --disable-rpath --without-included-regex --without-libiconv-prefix --without-libintl-prefix &&
make && cp find/find xargs/xargs /www/bin/ &&
cd $SRC && rm */ &&
echo '13-findutils' >> $SRC/tmp.log && sync &&

tar -xf gawk-3.1.6.tar.bz2 && cd gawk-3.1.6 &&
./configure --prefix=/www --disable-portals --enable-lint --enable-switch --enable-directories-fatal --disable-nls --disable-rpath --without-whiny-user-strftime --without-libiconv-prefix --without-libintl-prefix &&
make && cp gawk /www/bin/ && ln -sf gawk /www/bin/awk &&
cd $SRC && rm */ &&
echo '14-gawk' >> $SRC/tmp.log && sync &&

tar -xf grep-2.5.3.tar.bz2 && cd grep-2.5.3 &&
sed -i '/^SUBDIRS/s/doc//' Makefile.in &&
./configure --prefix=/www --disable-perl-regexp --disable-nls --without-libiconv-prefix --without-included-gettext --without-included-regex --without-included-getopt &&
make && cp src/{,e,f}grep /www/bin/ &&
cd $SRC && rm */ &&
echo '15-grep' >> $SRC/tmp.log && sync &&

tar -xf gzip-1.3.12.tar.gz && cd gzip-1.3.12 &&
sed -i 's/futimens/gl_&/' gzip.c lib/utimens.{c,h} &&
./configure --prefix=/www &&
make && cp gunzip gzip /www/bin/ &&
cd $SRC && rm */ &&
echo '16-gzip' >> $SRC/tmp.log && sync &&

tar -xf make-3.81.tar.bz2 && cd make-3.81 &&
./configure --prefix=/www --disable-nls --disable-rpath --enable-job-server --without-libiconv-prefix --without-libintl-prefix --without-customs --without-dmalloc &&
make && cp make /www/bin/ &&
cd $SRC && rm */ &&
echo '17-make' >> $SRC/tmp.log && sync &&

tar -xf patch-2.5.4.tar.gz && cd patch-2.5.4 &&
./configure --prefix=/www &&
make && cp patch /www/bin/ &&
cd $SRC && rm */ &&
echo '18-patch' >> $SRC/tmp.log && sync &&

tar -xf perl-5.8.8.tar.bz2 && cd perl-5.8.8 &&
sed -i -e's:/lib/libc.so.6:/www&:g' -e's:libc=/lib/$libc:libc=/www/lib/$libc:g' hints/linux.sh &&
echo 'locincpth="" ; loclibpth="" ; usrinc="/www/include" ; glibpth="/www/lib"' >> hints/linux.sh &&
sed -i 's/command /command[ -]/' makedepend.SH &&
./configure.gnu --prefix=/www -Dstatic_ext='Data/Dumper Fcntl IO POSIX' &&
make && make install.perl &&
cd $SRC && rm */ &&
echo '19-perl' >> $SRC/tmp.log && sync &&

tar -xf sed-4.1.5.tar.gz && cd sed-4.1.5 &&
./configure --prefix=/www --enable-i18n --enable-regex-tests --disable-html --disable-nls --without-included-regex --without-libiconv-prefix --without-included-gettext --without-libintl-prefix &&
make && cp sed/sed /www/bin/ &&
cd $SRC && rm */ &&
echo '20-sed' >> $SRC/tmp.log && sync &&

tar -xf tar-1.19.tar.bz2 && cd tar-1.19 &&
./configure --prefix=/www --disable-rpath --disable-nls --disable-backup-scripts --without-included-regex --without-libiconv-prefix --without-libintl-prefix &&
make && cp src/tar /www/bin/ &&
cd $SRC && rm */ &&
echo '21-tar' >> $SRC/tmp.log && sync &&

tar -xf util-linux-2.12r.tar.bz2 && cd util-linux-2.12r &&
sed -i 's:/usr/include:/www/include:g' configure &&
sed -i 's|CPUOPT=.*|CPUOPT=|' MCONFIG &&
LDFLAGS="$LDFLAGS -static" ./configure &&
make -C lib && make -C mount mount umount &&
cp mount/{,u}mount /www/bin/ &&
cd $SRC && rm */ &&
echo '22-util-linux' >> $SRC/tmp.log && sync &&

tar -xf linux-2.6.24.2.tar.bz2 && cd linux-2.6.24.2 &&
make distclean &&
cp $SRC/linux-2.6.24-config.txt .config &&
chown -R nobody . && su -c'make silentoldconfig ARCH=x86' -s/www/bin/bash - nobody &&
echo 'cflags-y += -march=athlon-xp' | tee arch/x86/Makefile*.cpu &&
make ARCH=x86 V=0 CC="gcc -m32 $CFLAGS" LD="ld -m elf_i386 $kLDFLAGS" &&
cp arch/x86/boot/bzImage $LFS/boot/ &&
cd $SRC && rm */ &&
echo '23-linux' >> $SRC/tmp.log && sync &&

tar -xf iproute2-2.6.24-rc7.tar.bz2 && cd iproute2-2.6.24-rc7 &&
sed -i -e's|^SBINDIR=.*|SBINDIR=/www/bin|' -e's|^CONFDIR=.*|CONFDIR=/www/etc/iproute2|' -e's|^SUBDIRS=.*|SUBDIRS=lib ip|' -e's|$(KERNEL_INCLUDE)|/www/include|' Makefile &&
make CCOPTS="-D_GNU_SOURCE $CFLAGS" LDFLAGS="$LDFLAGS" &&
cp ip/ip /www/bin/ && cp -R etc/iproute2/ /www/etc/ &&
cd $SRC && rm */ &&
echo '24-iproute2' >> $SRC/tmp.log && sync &&

[ "`ldd /www/{bin,sbin}/* /www/lib/*{,/*{,/*{,/*{,/*}}}} | egrep ' => /(usr|lib)'`" = '' ] &&
rm /www/{sbin,info,man} /www/share/{info,man,zoneinfo} &&
( strip --strip-debug /www/lib/*{,/*{,/*{,/*{,/*}}}} ; strip --strip-all /www/bin/* ; sync ) &&
echo 'tool-chain-finished!' >> $SRC/tmp.log && sync &&

到此,工具链制作完毕,可以通过查看 $SRC/tmp*.log 来了解制作过程是否顺利。如果没有很严重的错误,那么就可以准备下面的重启了。

重启工具链

与 LFS 的标准做法不同,本文为了尽可能摆脱宿主系统的影响,做完工具链之后不是chroot进入虚根环境,而是做一些必要的准备工作,然后重新启动计算机,进入一个完全与宿主系统无关的工具链环境,再继续完成目标系统的构建。

创建基础目录结构、必需的符号连接与文件、存储随机数种子、用户和组([注意]pgsql属于www组,root的密码是"123"):
[提示]如果现在就将 mtab 指向 /proc/mounts 的话,Coreutils 测试程序会将 /dev/root 挂载到 tests/rm/one-file-system.tmp/ 下的某个目录中,从而导致无法删除Coreutils 的编译目录。

mkdir $LFS/{boot,bin,etc,lib,dev/{pts,shm},share,root,usr/{bin,lib},var/log,www,data,proc} &&
ln -sf /dev/shm/tmp  $LFS/tmp     &&
ln -sf /dev/shm/run  $LFS/var/run &&
touch $LFS/etc/mtab $LFS/var/log/{btmp,lastlog,wtmp} &&
ln -sf bash $LFS/bin/sh &&
cp /www/bin/{bash,echo,mount} $LFS/bin/ &&
ln -sf /www/bin/{env,perl}    $LFS/usr/bin/ &&
ln -sf /www/lib/libgcc_s.so.1 $LFS/usr/lib/ &&
dd if=/dev/urandom of=$LFS/var/random-seed   bs=8k count=1 &&
dd if=/dev/urandom of=$LFS/var/urandom-seed  bs=8k count=1 &&
echo 'root:$1$/y21SFaU$uQ0nsncCJcYjrfgJ9qvA31:0:0:SuperUser:/root:/bin/bash
httpd:*:1001:1000:httpdDaemon:/www:/bin/false
pgsql:*:1002:1000:pgsqlDaemon:/data:/bin/false
sshd:*:3001:3000:sshdPrivSep:/share/empty:/bin/false' > $LFS/etc/passwd &&
echo 'root::0:
www::1000:
sshd::3000:' > $LFS/etc/group &&

Linux 中的设备有2种类型:字符设备(无缓冲且只能顺序存取)、块设备(有缓冲且可以随机存取)。每个字符设备和块设备都必须有主、次设备号,主设备号相同的设备是同类设备(使用同一个驱动程序)。这些设备中,有些设备是对实际存在的物理硬件的抽象,而有些设备则是内核自身提供的功能(不依赖于特定的物理硬件,又称为"虚拟设备")。每个设备在 /dev 目录下都有一个对应的文件(节点)。可以通过 cat /proc/devices 命令查看当前已经加载的设备驱动程序的主设备号。内核能够识别的所有设备都记录在原码树下的 Documentation/devices.txt 文件中。在 /dev 目录下除了字符设备和块设备节点之外还通常还会存在:FIFO管道、Socket、软/硬连接、目录。这些东西没有主/次设备号。欲了解更多有关Linux设备文件的知识,可以参考本文作者的一篇文章:《Linux设备文件简介》

由于本文不打算使用 Udev ,因此这里手动创建所有设备。

# mkfifo -m 600  $LFS/dev/initctl  &&
mknod  -m 666  $LFS/dev/null     c   1   3 &&
mknod  -m 666  $LFS/dev/zero     c   1   5 &&
mknod  -m 666  $LFS/dev/full     c   1   7 &&
mknod  -m 666  $LFS/dev/random   c   1   8 &&
mknod  -m 666  $LFS/dev/urandom  c   1   9 &&
mknod  -m 666  $LFS/dev/tty0     c   4   0 &&
mknod  -m 666  $LFS/dev/tty1     c   4   1 &&
mknod  -m 666  $LFS/dev/tty2     c   4   2 &&
mknod  -m 666  $LFS/dev/tty3     c   4   3 &&
mknod  -m 666  $LFS/dev/tty      c   5   0 &&
mknod  -m 666  $LFS/dev/console  c   5   1 &&
mknod  -m 666  $LFS/dev/ptmx     c   5   2 &&
mknod  -m 666  $LFS/dev/sda      b   8   0 &&
mknod  -m 666  $LFS/dev/sda1     b   8   1 &&
mknod  -m 666  $LFS/dev/sda2     b   8   2 &&
mknod  -m 666  $LFS/dev/sda3     b   8   3 &&
mknod  -m 666  $LFS/dev/sda4     b   8   4 &&
mknod  -m 666  $LFS/dev/sda5     b   8   5 &&
mknod  -m 666  $LFS/dev/sda6     b   8   6 &&
mknod  -m 666  $LFS/dev/sda7     b   8   7 &&
mknod  -m 666  $LFS/dev/sda8     b   8   8 &&
mknod  -m 666  $LFS/dev/sda9     b   8   9 &&
mknod  -m 666  $LFS/dev/rtc      c  10 135 &&
mknod  -m 666  $LFS/dev/hpet     c  10 228 &&
ln -sf  /proc/self/fd    $LFS/dev/fd &&
ln -sf  /proc/self/fd/0  $LFS/dev/stdin &&
ln -sf  /proc/self/fd/1  $LFS/dev/stdout &&
ln -sf  /proc/self/fd/2  $LFS/dev/stderr &&
ln -sf  /proc/kcore      $LFS/dev/core &&

由于安装 Iana-Etc 包之后将会产生巨大的 /etc/services 文件,从而对性能有不良影响(比如 getservbyname() 函数将会变慢),因此本文并未安装 Iana-Etc 包。但是 Prel 的 ext/IO/ 与 lib/Net/ 目录下的几个测试套件依赖于 Iana-Etc 包。因此为了测试的完整性,先从宿主系统复制临时的 /etc/{protocols,services} 文件,然后在 Perl 测试完毕之后再删除它们。此外,好几个 Perl 测试项目都需要 /etc/hosts 文件来解析 localhost 的名称("miniLAPP"),为了顺利运行测试套件,这里也提前创建它:

cp /etc/{protocols,services} $LFS/etc/ &&
echo '127.0.0.1 localhost miniLAPP' > $LFS/etc/hosts &&

设置Bash启动脚本,一方面是为了做编译前的准备,另一方面也是为了简化了关机后的状态回复工作。[提示]"umask 000"会导致 Coreutils 测试失败。TERM环境变量将由系统自动设置。

echo "set +h
umask 022
export HOME=/root PS1='$PS1' TZ=$TZ SRC=/data
export PATH=/bin:/usr/bin:/www/bin
export CFLAGS='$CFLAGS' LDFLAGS='$LDFLAGS' kLDFLAGS='$kLDFLAGS'
alias make='make -j1' mkdir='mkdir -p' patch='patch -p1 -i' mv='mv -f' cp='cp -pf' rm='rm -fr'
echo miniLAPP > /proc/sys/kernel/hostname
mkdir -m 1777 /dev/shm/{tmp,run,pg_socket,php_session}
touch /var/run/utmp
cd \$SRC" > $LFS/etc/profile &&

编写启动脚本。

echo '#!/bin/bash
/bin/mount -t proc    proc      /proc    &&
/bin/mount -t tmpfs   shm       /dev/shm &&
/bin/mount -t devpts  devpts    /dev/pts &&
/bin/mount -t ext2  /dev/sda1   /boot    &&
/bin/mount -t xfs   /dev/sda5   /usr     &&
/bin/mount -t xfs   /dev/sda6   /root    &&
/bin/mount -t xfs   /dev/sda7   /var     &&
/bin/mount -t xfs   /dev/sda8   /www     &&
/bin/mount -t xfs   /dev/sda9   /data    &&
/www/bin/ip -4 link set lo   txqueuelen  0       &&
/www/bin/ip -4 link set eth0 txqueuelen  4000    &&
/www/bin/ip -4 link set lo   mtu  16436          &&
/www/bin/ip -4 link set eth0 mtu  1500           &&
/www/bin/ip -4 addr add 127.0.0.1/8                               scope host    dev  lo   valid_lft forever preferred_lft forever  &&
/www/bin/ip -4 addr add 192.168.10.33/24 broadcast 192.168.10.255 scope global  dev eth0  valid_lft forever preferred_lft forever  &&
/www/bin/ip -4 link set lo   up                  &&
/www/bin/ip -4 link set eth0 up                  &&
/www/bin/ip -4 route add unicast default via 192.168.10.250 dev eth0 &&
/bin/bash --login +h' > $LFS/bin/tmpinit &&
chmod 755 $LFS/bin/tmpinit &&

由于GRUB-0.97年久失修,毛病多多,因此这里使用咱们中国人主导开发的GRUB4DOS

cp $SRC/grldr $LFS/boot/ &&
echo 'kernel (hd0,0)/bzImage root=0802 rootfstype=xfs rw init=/bin/tmpinit panic=30' > $LFS/boot/menu.lst &&
chmod +x $SRC/bootlace.com &&
$SRC/bootlace.com --no-backup-mbr --mbr-disable-floppy --mbr-disable-osbr --boot-prevmbr-last  /dev/sda &&

接下来有两个分支可选,推荐使用默认的分支㈠,因为分支㈡未经本文作者严格测试,可能会存在某些问题。

分支㈠[关机]

关机前的准备(如果没有启动ssh就省略相应的命令):

/etc/rc.d/init.d/sshd stop &&
rm ~/.bash*  &&
sync &&
exit

必须确保一直 exit 到出现"Press Enter to activate this virtual console..."为止,然后再按回车键重新登陆并按下列步骤关机:

cd / &&
rm -f /etc/mtab &&
umount /dev/sda{9,8,7,6,5,1,2} &&
poweroff

OK! 下次重新开机的时候将BIOS中的启动盘调整为硬盘,即可继续进行下面"编译最终系统"的工作。

分支㈡[chroot]

为了方便期望在编译最终系统的过程中也能使用ssh的读者,这里也介绍一下传统的chroot方法。需要说明的是,由于此分支的方法不能完全摆脱宿主系统的影响,所以本文不推荐使用。仅供那些贪图copy/paste便利的玩家参考和测试。

mount -t devpts  devpts  $LFS/dev/pts &&
mount -t tmpfs   shm     $LFS/dev/shm &&
mount -t proc    proc    $LFS/proc    &&
chroot $LFS /usr/bin/env -i TERM=$TERM /bin/bash --login +h

依赖关系分析

在正式开始编译最终系统之前,我们需要静下心来认真分析一下这个最终系统究竟需要哪些东西。

所谓"依赖性"是多方面的。一般来说,可以分为"运行时依赖"、"编译安装依赖"、"测试依赖"三个层面。为了构建一个严谨的自依赖系统,显然这三种依赖性都必须满足。运行时依赖比较简单,一般就是库的依赖;而后两种依赖则比较复杂(运行时依赖实际上取决于编译安装依赖)。比如,如果你不需要安装文档,那么 Textinfo 就不是必须的;如果你不需要国际化支持,那么 GetText 也不是必需的,等等。庆幸的是 LFS-Book 的附录部分给出了宝贵的依赖关系资料,可以提供参考,这样可以省去很多麻烦。

首先,我们来看看"核心"部分需要哪些东西。很显然,下面这些是必须的:GRUB, Kernel, Glibc, Sysklogd, Dcron, Bash, IPRoute2, Apache, PHP, PostgreSQL, OpenSSH, Iptables 。再深入思考一下,就会发现如下组件也是"核心"部分必须的:E2fsprogs(被mount和xfs_repair依赖),XFS(用于开机时的磁盘检查),Ncurses(被Readline/Bash依赖),Readline(被Bash/PostgreSQL依赖),Zlib(被Apache/PostgreSQL/OpenSSH依赖),JPEG+PNG(被PHP依赖),PCRE(被Apache/PHP依赖),OpenSSL(被Apache/OpenSSH/PostgreSQL依赖),Util-linux 与 Coreutils 中的部分程序。核心部分只需要满足运行时依赖即可。

然后,再看看"扩展"部分需要哪些东西。这部分的选择因人而异,这里就大致选择了几个系统管理相关的组件和几个实用工具:Coreutils, Procps, Psmisc, Util-linux, Bzip2, Findutils, Grep, Sed, Tar, XFS 。选择这部分组件时,暂时无需考虑依赖关系。

最后,考虑到可扩展性以及将来的组件升级与维护等,就必须构建一个自依赖的系统。也就是说,所有组件加在一起必须能构成一个完整的依赖环(3种依赖关系全部满足)。本文安装的、并且未在前面提到的软件包都属于这个用途。它们都属于"扩展"。

需要说明的是,这里的"核心"与"扩展"的划分不是绝对严格的:扩展部分的组件严格属于"扩展",而核心部分的组件通常只有某一部分属于"核心"(其他部分归入"扩展")。比如 Glibc 的基本库部分就属于"核心",而扩展库、实用程序、文档、头文件等则属于"扩展"。

包管理

其实这么简单的系统根本不需要包管理,所以本文并不使用任何一种包管理技术,只是使用了一种非常简单的 DESTDIR 方法来将每个包都复制一份到/root目录下,以方便管理员查看每个软件包究竟安装了哪些东西。当然,并不是所有软件包都遵循这个约定俗成的规则,对于这些软件包使用的命令也有所不同。


编译最终系统

配置选项

要做到最大限度的定制每一个软件包,获取完整的配置选项是必须的。当然,要想更加详细、全面的了解如何自定义安装,还需要查看 README INSTALL FAQ 之类的文档,甚至是软件包的官方手册。需要注意的是,有不少软件包的配置选项分布在多个 configure 脚本中,还有少数并不是通过 configure 脚本进行配置的,查看完整的配置信息就变成一件很吃力的事情了。因此唯一的建议就是:读文档、读文档、再读文档。当然,鸟语是免不了的...

关机与状态回复

由于编译过程漫长,下面的编译步骤被设计为"易于恢复状态的"(仅对"分支㈠"有效),意思是,你可以在编译完任意一个软件包之后关机,并且重新开机后就已经自动的恢复了工作状态。要达到这个目的,你可以使用任何你喜欢的方式关机(比如直接拔掉电源),但是你必须确保在关机前运行了"sync"命令。

对于分支㈡,恢复步骤如下:
①使用 LiveCD 开机,直接按回车键启动,所有设置均采用默认。
②如果希望使用ssh进行远程安装,步骤同前。
③挂载文件系统后进入虚根环境,即可完成状态恢复:

export LFS=/mnt/lfs && mkdir -p $LFS  &&
mount -t xfs   /dev/sda2  $LFS       &&
mount -t ext2  /dev/sda1  $LFS/boot  &&
mount -t xfs   /dev/sda5  $LFS/usr   &&
mount -t xfs   /dev/sda6  $LFS/root  &&
mount -t xfs   /dev/sda7  $LFS/var   &&
mount -t xfs   /dev/sda8  $LFS/www   &&
mount -t xfs   /dev/sda9  $LFS/data  &&
mount -t devpts  devpts  $LFS/dev/pts &&
mount -t tmpfs   shm     $LFS/dev/shm &&
mount -t proc    proc    $LFS/proc    &&
chroot $LFS /usr/bin/env -i TERM=$TERM /bin/bash --login +h

[提示]如果你和我一样使用虚拟机的暂停功能,那么重新连接ssh后只要使用最后一句chroot命令即可。甚至可以在chroot前用下面的命令偷懒到底:

echo "chroot $LFS /usr/bin/env -i TERM=\$TERM /bin/bash --login +h" > /etc/profile

内核头文件

根据 Glibc 的 FAQ ,编译 Glibc 时使用的内核头文件版本可以比实际运行 Glibc 的内核版本高。比如用于编译 Glibc 的内核头文件版本为 2.6.24 ,但是实际运行 Glibc 的可以是 2.6.16 版本的内核(编译 Glibc 时必须使用 --enable-kernel=2.6.16 而不能使用 --enable-kernel=2.6.24 )。允许这样做的好处是即使将来把内核升级到 2.6.24 也不需要重新编译 Glibc 了。另一方面,如果实际运行的内核版本比头文件版本高,那么新内核的新特性(主要是系统调用)将无法被Glibc使用。[注意]不要直接 INSTALL_HDR_PATH=/usr ,这样可能使得Glibc变得不稳定。[小提示]如果将来把内核升级为更高版本,头文件是不是也需要跟着一起升级?答案是:NO!! 关于内核头文件变迁的历史,这里有一篇《[八卦故事]内核头文件传奇》,可以当作课外读物 :)

( test ! -f $SRC/build.log ) && [ "`pwd`" = "$SRC" ] &&
R=/root/linux-headers-2.6.24.2/ &&
tar -xf linux-2.6.24.2.tar.bz2 && cd linux-2.6.24.2 &&
make distclean && make headers_check ARCH=x86 &&
make headers_install ARCH=x86 INSTALL_HDR_PATH=$R/usr &&
cp -r $R/usr/include /usr/ &&
cd $SRC && rm */ &&
echo '1-kernel-headers' >> $SRC/build.log && sync &&

Glibc

Glibc 的安装指南中说测试套件中的某些测试项目是假定以非 root 身份运行的,因此建议使用普通用户身份进行编译与安装。不过经过实践,以 root 用户进行编译和安装也没问题。这里仅安装了 zh_CN.UTF-8 的 locale 支持,如果你想支持更多的 locale 请自己添加适当的 localedef 命令。出于安全最大化的考虑还禁用了 DNS 。将时区设为UTC(相当于取消时区的概念,如果你有使用时区的需求可以设为"PRC")。更多关于安装 Glibc 的信息,请查看源码树下的 configure INSTALL FAQ 三个文件。[提示]由于没有安装C++编译器的缘故,bug-atexit3-lib.os tst-cancel24.o c++-types-check 测试将会失败,你可以安全的忽略它。touch用于阻止可能发生的Autoconf调用(当Makefile检测到一个configure文件的时间戳比它对应的configure.in文件旧的时候),这种调用有时候会导致编译失败。将"/dev/log"修改为"/dev/shm/log"是因为将来的根文件系统是只读挂载的,而syslogd会在启动的时候删除并重建此socket。Glibc GCC Binutils 三者是整个工具链的核心,因此如何对其进行定制就显得很重要。这里有一篇文章《Glibc 安装指南》,可以在漫长的编译过程中作为参考资料读一读。

R=/root/glibc-2.6.1/ && mkdir {,$R}/usr/lib/locale &&
tar -xf glibc-2.6.1.tar.bz2 && cd glibc-2.6.1 &&
sed -i 's|libs -o|libs -L/usr/lib -Wl,-dynamic-linker=/lib/ld-linux.so.2 -o|' scripts/test-installation.pl &&
sed -r -i 's:/bin/(pwd|cat):\1:g' configure{.in,} io/ftwtest-sh stdio-common/{test-popen,xbug,tstscanf}.c &&
sed -i -e's|^rootsbindir =.*|rootsbindir=/usr/bin|' -e's|^sbindir =.*|sbindir=/usr/bin|' Makeconfig &&
sed -i 's|^sbindir=.*|sbindir=/usr/bin|' {,posix/glob/}configure &&
sed -i 's|libc_cv_rootsbindir=.*|libc_cv_rootsbindir=/usr/bin|' sysdeps/unix/sysv/linux/configure &&
sed -i 's|/dev/log|/dev/shm/log|' bits/syslog-path.h &&
find . -name configure | xargs touch &&
mkdir ../glibc-build && cd ../glibc-build &&
../glibc-2.6.1/configure --prefix=/usr --sbindir=/usr/bin --enable-add-ons=nptl --enable-shared --enable-kernel=2.6.22 --disable-oldest-abi --enable-omitfp --disable-profile --with-headers=/usr/include --with-binutils=/www/bin --without-cvs --with-elf --without-gd --without-gmp --without-selinux --with-tls &&
echo 'MAKEINFO = :' >> configparms &&
make && ( make -k check 2> $SRC/build_glibc.log ; make install ) &&
make install install_root=$R &&
localedef -i zh_CN -f UTF-8 zh_CN.UTF-8 && cp /usr/lib/locale/* $R/usr/lib/locale/ &&
rm {,$R}/etc/rpc {,$R}/{,usr}/lib/*_g.* {,$R}/usr/libexec/pt_chown {,$R}/usr/share/locale &&
echo 'passwd: files
group: files
shadow: files
hosts: files
networks: files
protocols: files
services: files
ethers: files
rpc: files' | tee {,$R}/etc/nsswitch.conf &&
cp --remove-destination /usr/share/zoneinfo/UTC   /etc/localtime &&
cp --remove-destination /usr/share/zoneinfo/UTC $R/etc/localtime &&
cp /etc/{passwd,group,hosts}  $R/etc/ &&
cd $SRC && rm */ &&
echo '2-glibc' >> $SRC/build.log && sync &&

再次调整工具链

mv /www/bin/{ld-new,ld} &&
gcc -dumpspecs | sed -e'/^\*link:$/{n;s@$@ -L/usr/lib@}' -e's@/www/lib/ld-linux.so.2@/lib/ld-linux.so.2@g' -e'/^\*startfile_prefix_spec:/{n;s@.*@/usr/lib/ @}' -e'/^\*cpp:$/{n;s@$@ -isystem /usr/include@}' > `dirname $(gcc -print-libgcc-file-name)`/specs &&
echo 'main(){}' > dummy.c && cc dummy.c -v -Wl,--verbose &> dummy.log &&
readelf -l a.out | fgrep '[Requesting program interpreter: /lib/ld-linux.so.2]' &&
fgrep 'attempt to open /usr/lib/crt1.o succeeded' dummy.log &&
fgrep 'attempt to open /usr/lib/crti.o succeeded' dummy.log &&
fgrep 'attempt to open /usr/lib/crtn.o succeeded' dummy.log &&
fgrep '#include <...> search starts here:
 /usr/include
 /www/include' dummy.log &&
# fgrep 'SEARCH_DIR("/www/i686-pc-linux-gnu/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");' dummy.log &&
fgrep 'attempt to open /lib/libc.so.6 succeeded' dummy.log &&
fgrep 'found ld-linux.so.2 at /lib/ld-linux.so.2' dummy.log &&
rm dummy.c a.out dummy.log &&
echo '3-adjusting' >> $SRC/build.log && sync &&

Tcl + Expect + DejaGNU

[提示]使用 --with(out)-tk 将导致 expect 配置失败,使用了 --with-tcl 之后就不需要再详细指定 --with-tclconfig --with-tcllib --with-tcllibdir 了。使用 --with(out)-docbook 或 --with(out)-oskith 将导致 dejagnu 配置失败。sed 一定要先修改 configure.in 再修改 configure ,否则会导致 Autoconf 的调用。dejagnu 的安装指南说测试套件需要以非 root 身份运行,可是经过实践,无论 root 与否,都不能在第一次安装后立即通过测试,一般需要安装两次甚至三次才能测试成功(无论是否root都是这样),不知何故。再加上dejagnu的测试程序依赖于这里并不存在的C++编译器,因此这里跳过dejagnu测试。

R=/root/tcl8.4.18/ &&
tar -xf tcl8.4.18-src.tar.gz && cd tcl8.4.18/unix &&
sed -i 's:/bin/rm:rm:g' ../library/ldAout.tcl &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-threads --enable-shared --enable-load --enable-gcc --disable-symbols --disable-64bit --disable-64bit-vis --disable-langinfo --disable-corefoundation --disable-dtrace --disable-framework --disable-man-symlinks --disable-man-compression --disable-man-suffix &&
make && TZ=UTC make test &&
make install install-private-headers                 && ln -sf tclsh8.4   /usr/bin/tclsh &&
make install install-private-headers INSTALL_ROOT=$R && ln -sf tclsh8.4 $R/usr/bin/tclsh &&
cd $SRC && rm */ && sync &&
tar -xf expect-5.43.0.tar.bz2 && cd expect-5.43 &&
patch $SRC/expect-5.43.0-spawn-2.patch &&
sed -i 's/STTY_BIN=.*/STTY_BIN=stty/g' configure{.in,} &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-threads --enable-shared --enable-load --enable-gcc --with-tcl=/usr/lib --with-tclinclude=/usr/include --with-x=no --disable-symbols &&
make expect && make test &&
make install SCRIPTS="" && make install SCRIPTS="" INSTALL_ROOT=/root/expect-5.43.0/ &&
cd $SRC && rm */ && sync &&
tar -xf dejagnu-1.4.4.tar.gz && cd dejagnu-1.4.4 &&
./configure --prefix=/usr --sbindir=/usr/bin &&
make install && make install DESTDIR=/root/dejagnu-1.4.4/ &&
cd $SRC && rm */ &&
echo '4-tcl-expect-dejagnu' >> $SRC/build.log && sync &&

Binutils

由于"-finline-functions"会导致ld测试出现多处错误,因此这里需要专门禁用它。对Makefile.in的修改用于移除对makeinfo的依赖,否则编译将会失败(因为工具链中并未安装Texinfo)。[提示]由于 binutils-2.17 的测试套件将路径 /bin/stty 进行了硬编码,且本文作者尚未找到简便的迂回方法,因此如果你使用的是 2.17 ,那么要先建立一个符号链接来满足测试套件的需求,安装完毕后再删除它。对于如何安装 Binutils 和 GCC 可以参考一下《Binutils与GCC配置选项简介》

R=/root/binutils-2.18/ &&
tar -xf binutils-2.18.tar.bz2 && mkdir binutils-build && cd binutils-build &&
echo 'MAKEINFO = :' >> ../binutils-2.18/Makefile.in &&
CFLAGS="$CFLAGS -fno-inline-functions" \
../binutils-2.18/configure --prefix=/usr --sbindir=/usr/bin --with-lib-path=/lib:/usr/lib --disable-nls --disable-rpath --disable-multilib --enable-shared --disable-static --disable-werror --without-gmp --without-mpfr --without-included-gettext --without-libiconv-prefix --without-libintl-prefix --with-pic &&
make all check install tooldir=/usr/  && cp ../binutils-2.18/include/libiberty.h   /usr/include/ &&
make install tooldir=/usr/ DESTDIR=$R && cp ../binutils-2.18/include/libiberty.h $R/usr/include/ &&
cd $SRC && rm */ &&
echo '5-binutils' >> $SRC/build.log && sync &&

GCC

因为GCC的Makefile.in并不传递LDFLAGS,所以需要sed一下。为了得到更加高效的编译器,这里使用"make profiledbootstrap"代替"make"。对"LINK_SPEC"的修改是为了将"--hash-style=gnu"选项添加到默认的连接器选项中去,如果你希望这样的话,就去掉相应的注释。[提示]如果Glibc的CFLAGS中使用了-freg-struct-return的话,gcc.dg/struct-ret-libc.c测试将会失败,不过这是二进制兼容性所致,可以安全的忽略它。

R=/root/gcc-4.2.3/ &&
tar -xf gcc-core-4.2.3.tar.bz2 && tar -xf gcc-testsuite-4.2.3.tar.bz2 && cd gcc-4.2.3 &&
sed -r -i 's|^LDFLAGS(_FOR_TARGET)? =|& @LDFLAGS@|' Makefile.in &&
sed -i -e's@\./fixinc\.sh@-c true@' -e'/^LIBGCC2_DEBUG_CFLAGS/d' gcc/Makefile.in &&
sed -i '/MULTILIB_OSDIRNAMES/d' gcc/config/i386/t-linux64 &&
sed -i 's/install_to_$(INSTALL_DEST) //' libiberty/Makefile.in &&
# sed -i '/LINK_SPEC/s/ "/&--hash-style=gnu /' gcc/config/i386/linux{,64}.h &&
mkdir ../gcc-build && cd ../gcc-build &&
../gcc-4.2.3/configure --prefix=/usr --sbindir=/usr/bin --with-local-prefix=/usr/local --enable-languages=c --enable-bootstrap --disable-nls --disable-rpath --disable-multilib --enable-shared=libgcc --disable-static --disable-libgomp --disable-libmudflap --disable-libssp --enable-linux-futex --enable-threads=posix --enable-tls --disable-symvers --with-as=/usr/bin/as --with-ld=/usr/bin/ld --without-included-gettext --without-libiconv-prefix --without-libintl-prefix --without-gmp --without-mpfr --without-system-libunwind --without-system-zlib --with-pic --without-x &&
make profiledbootstrap LIBCFLAGS="$CFLAGS" BOOT_CFLAGS="$CFLAGS" BOOT_LDFLAGS="$LDFLAGS" &&
( make -k check | tee $SRC/build_gcc.log ; make install ) &&
make install DESTDIR=$R &&
ln -sf gcc   /usr/bin/cc &&
ln -sf gcc $R/usr/bin/cc &&
INC=`gcc -print-file-name=include` &&
TOL=`gcc -print-file-name=install-tools` &&
rm  {,$R}/$INC/* &&
cp  $TOL/include/*     $INC &&
cp  $TOL/include/*  $R/$INC &&
cp  $TOL/gsyslimits.h     $INC/syslimits.h &&
cp  $TOL/gsyslimits.h  $R/$INC/syslimits.h &&
unset INC TOL &&
cd $SRC && rm */ && sync &&
echo 'main(){}' > dummy.c && cc dummy.c -v -Wl,--verbose &> dummy.log &&
readelf -l a.out | fgrep '[Requesting program interpreter: /lib/ld-linux.so.2]' &&
fgrep 'attempt to open /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/../../../crt1.o succeeded' dummy.log &&
fgrep 'attempt to open /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/../../../crti.o succeeded' dummy.log &&
fgrep 'attempt to open /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/../../../crtn.o succeeded' dummy.log &&
fgrep '#include <...> search starts here:
 /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/include
 /usr/include
End of search list.' dummy.log &&
fgrep 'SEARCH_DIR("/usr/i686-pc-linux-gnu/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");' dummy.log &&
fgrep 'attempt to open /lib/libc.so.6 succeeded' dummy.log &&
fgrep 'found ld-linux.so.2 at /lib/ld-linux.so.2' dummy.log &&
rm dummy.c a.out dummy.log &&
echo '6-gcc' >> $SRC/build.log && sync &&

到此,最终系统的工具链核心安装完毕。

Sed

[ "`tail -1 $SRC/build.log`" = '6-gcc' ] &&
R=/root/sed-4.1.5/ &&
tar -xf sed-4.1.5.tar.gz && cd sed-4.1.5 &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-largefile --enable-i18n --enable-regex-tests --disable-html --disable-nls --without-included-regex --without-libiconv-prefix --without-included-gettext --without-libintl-prefix &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '7-sed' >> $SRC/build.log && sync &&

E2fsprogs

[提示]"--disable-htree"和"--disable-debugfs"选项都会导致许多测试错误,"--disable-swapfs"会导致lib/ext2fs测试由于找不到"tst_types"而失败,此外由于lib/ext2fs测试总是由于"Failed to allocate scratch memory!"失败,原因不明,所以这里使用sed跳过这个测试。CFLAGS和LDFLAGS必须在configure选项上设置。[依赖提示]UUID被XFS依赖,blkid,uuid 被mount/umount依赖。

R=/root/e2fsprogs-1.40.6/ && mkdir $R/{lib,etc} &&
tar -xf e2fsprogs-1.40.6.tar.gz && cd e2fsprogs-1.40.6 &&
sed -i 's:/bin/rm:rm:g' lib/blkid/test_probe.in &&
sed -i 's/^check::/cancelled-check::/' lib/ext2fs/Makefile.in &&
sed -i 's|root_sbindir=.*|root_sbindir=/usr/bin|' configure{.in,} &&
mkdir build && cd build &&
../configure --prefix=/usr --enable-elf-shlibs --disable-profile --disable-swapfs --enable-uuidd --disable-nls --disable-rpath --with-ccopts="$CFLAGS" --with-ldopts="$LDFLAGS" --with-root-prefix=/usr --without-libiconv-prefix --without-included-gettext --without-libintl-prefix &&
make && make check &&
make install install-libs && make install install-libs DESTDIR=$R &&
cp /etc/blkid.tab $R/etc/ && rm {,$R}/etc/{init.d,*.old} {,$R}/usr/etc &&
cp   /usr/lib/lib{blkid,uuid}.so.1   /lib &&
cp $R/usr/lib/lib{blkid,uuid}.so.1 $R/lib &&
cd $SRC && rm */ &&
echo '8-e2fsprogs' >> $SRC/build.log && sync &&

Coreutils

运行 make check 的非特权用户要求至少位于两个不同的组中,因此这里将 pgsql 用户临时添加到 dummy 组内。"RUN_VERY_EXPENSIVE_TESTS=yes"用于运行更多的测试项目。[说明]因为test-getaddrinfo测试总是由于缺乏DNS支持而失败,需要跳过它。更多此版本的怪脾气,请看这里。[说明]由于Findutils包内的 xargs 程序将 echo 程序硬编码为"/bin/echo",且不能在源代码中将其修改为"echo"(会导致测试失败,原因不明),另一方面echo也是bash的内置命令,所以就将它也安装在 /bin 目录下。

R=/root/coreutils-6.10/ && mkdir $R/bin/ &&
tar -xf coreutils-6.10.tar.gz && cd coreutils-6.10 &&
patch $SRC/coreutils-6.10-i18n-1.patch &&
chmod +x tests/sort/sort-mb-tests &&
sed -i '/(setuid_root_mode)/s/(installed_su)/&;/' src/Makefile.in &&
echo 'int main (void) { return 0; }' > gnulib-tests/test-getaddrinfo.c &&
sed -i 's|HOME|& /dev/shm|' tests/other-fs-tmpdir &&
sed -i 's:/bin/rmdir:rmdir:g' lib/rmdir.c &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-option-checking --disable-acl --disable-assert --disable-rpath --enable-largefile --disable-nls --enable-install-program="arch,hostname,su" --without-libiconv-prefix --without-included-regex --without-libintl-prefix &&
make && make check-root NON_ROOT_USERNAME=pgsql &&
echo "dummy::1689:pgsql" >> /etc/group && chown pgsql gnulib-tests/.deps &&
su -c'make check RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes' -s/bin/bash pgsql &&
sed -i '/dummy/d' /etc/group &&
make install install-root            && cp   /usr/bin/{echo,false,mktemp,sync}   /bin/ &&
make install install-root DESTDIR=$R && cp $R/usr/bin/{echo,false,mktemp,sync} $R/bin/ &&
cd $SRC && rm */ &&
echo '9-coreutils' >> $SRC/build.log && sync &&

M4

[依赖提示]至少被Bison/Autoconf/Automake/Flex依赖

R=/root/m4-1.4.10/ &&
tar -xf m4-1.4.10.tar.bz2 && cd m4-1.4.10 &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-changeword --without-included-regex &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '10-m4' >> $SRC/build.log && sync &&

Bison

[依赖提示]至少被Bash依赖

R=/root/bison-2.3/ &&
tar -xf bison-2.3.tar.bz2 && cd bison-2.3 &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-gcc-warnings --enable-yacc --disable-nls --disable-rpath --without-libiconv-prefix --without-libintl-prefix &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '11-bison' >> $SRC/build.log && sync &&

Libtool

[依赖提示]至少XFS的安装依赖于它。

R=/root/libtool-1.5.26/ &&
tar -xf libtool-1.5.26.tar.gz && cd libtool-1.5.26 &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-ltdl-install --enable-shared --with-pic &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '12-libtool' >> $SRC/build.log && sync &&

Ncurses

这个包的配置选项简直多如牛毛,这里没有(也没必要)涵盖全部选项。更多安装信息请查看源码树目录下的 INSTALL 文件(所有选项皆有描述,不需要查看 configure 的内容)。Ncurses 的测试套件与大多数包不同,不能用简单的非交互式"make check"来测试,因为 Ncurses 牵涉到视觉效果,所以必须是交互式的测试,更多有关如何测试的细节,请查看源码树下的 test/README 文件。更多官方补丁请查看:ftp://invisible-island.net/ncurses/5.6/。[提示]--enable/disable-database都将导致编译失败,不能使用。--disable-ext-funcs --disable-tparm-varargs 将导致 Procps 无法编译。如果你不想安装terminfo数据库可以使用"--disable-database --without-terminfo-dirs",如果你不想安装实用程序可以使用"--without-progs"。可以使用"make install.libs"仅安装共享库。

R=/root/ncurses-5.6/ && mkdir $R/lib/ &&
tar -xf ncurses-5.6.tar.gz && cd ncurses-5.6 &&
bunzip2 -kf ../ncurses-5.6-20071201-patch.sh.bz2 &&
chmod +x ../ncurses-5.6-20071201-patch.sh &&
../ncurses-5.6-20071201-patch.sh    &&
./configure --prefix=/usr --sbindir=/usr/bin --without-cxx --without-cxx-binding --with-progs --without-curses-h --disable-mixed-case --without-libtool --with-shared --without-normal --without-debug --without-profile --without-termlib --without-ticlib --without-gpm --without-dlsym --without-sysmouse --disable-rpath --enable-overwrite --with-database=misc/terminfo.src --without-hashed-db --without-fallbacks --with-terminfo-dirs=/usr/share/terminfo --with-default-terminfo-dir=/usr/share/terminfo --enable-big-core --enable-big-strings --disable-termcap --without-termpath --disable-getcap --disable-getcap-cache --disable-home-terminfo --disable-root-environ --enable-symlinks --disable-broken_linker --disable-bsdpad --enable-widec --enable-lp64 --enable-largefile --enable-tparm-varargs --without-caps --with-ospeed=int --without-rcs-ids --enable-ext-funcs --enable-const --enable-no-padding --enable-signed-char --enable-sigwinch --enable-tcap-names --without-develop --disable-hard-tabs --disable-xmc-glitch --enable-assumed-color --enable-hashmap --disable-colorfgbg --disable-ext-colors --disable-ext-mouse --disable-reentrant --disable-safe-sprintf --disable-wgetch-events --enable-echo --enable-warnings --disable-assertions --without-dmalloc --without-dbmalloc --without-valgrind --enable-leaks --disable-expanded --enable-macros --without-trace &&
make install            && cp   /usr/lib/libncursesw.so.5   /lib/ &&
make install DESTDIR=$R && cp $R/usr/lib/libncursesw.so.5 $R/lib/ &&
cd $SRC && rm */ &&
echo '13-ncurses' >> $SRC/build.log && sync &&

Procps

Procps 的测试套件与 Ncurses 类似,由于牵涉到视觉效果,因此也是交互式测试,具体细节请查看 README 文件。

R=/root/procps-3.2.7/ &&
tar -xf procps-3.2.7.tar.gz && cd procps-3.2.7 &&
sed -i 's:<curses.h>:<ncurses.h>:' {dummy,top}.c &&
sed -r -i -e'/CURSES/s/-lncurses/-lncursesw/' -e's|DESTDIR\)/s?bin/|DESTDIR)/usr/bin/|' -e's|DESTDIR\)(/usr)?/\$\(lib64\)/|DESTDIR)/usr/lib/|' Makefile &&
make install CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" &&
make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '14-procps' >> $SRC/build.log && sync &&

Perl

Perl的测试很奇怪,有时候第一次运行失败,再运行一次却能成功。

R=/root/perl-5.8.8/ &&
tar -xf perl-5.8.8.tar.bz2 && cd perl-5.8.8 &&
sed -i 's/command /command[ -]/' makedepend.SH &&
./configure.gnu --prefix=/usr -Dpager="/usr/bin/less -isR" &&
make && ( make test || make test ) &&
make install && make install DESTDIR=$R &&
rm /etc/{protocols,services} &&
cd $SRC && rm */ &&
echo '15-perl' >> $SRC/build.log && sync &&

Readline

由于只需要共享库,因此只编译和安装共享库(包括头文件)。"bash_cv_func_ctype_nonascii=yes"用于修正当en_US.ISO8859-1这个locale不存在时会出现的问题,具体细节参见这里

R=/root/readline-5.2/ && mkdir $R/{etc,lib} &&
tar -xf readline-5.2.tar.gz && cd readline-5.2 &&
patch $SRC/readline-5.2-fixes-5.patch &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-multibyte --enable-shared --disable-static --with-curses --without-purify bash_cv_func_ctype_nonascii=yes &&
make shared SHLIB_LIBS=-lncursesw SHOBJ_CFLAGS="$CFLAGS -fpic" SHOBJ_LDFLAGS="$LDFLAGS -shared" &&
make install-shared            && cp   /usr/lib/lib{history,readline}.so.5   /lib/ &&
make install-shared DESTDIR=$R && cp $R/usr/lib/lib{history,readline}.so.5 $R/lib/ &&
echo 'set horizontal-scroll-mode Off
set meta-flag On
set input-meta On
set convert-meta Off
set output-meta On
set bell-style none
"\eOd": backward-word
"\eOc": forward-word
"\e[1~": beginning-of-line
"\e[4~": end-of-line
"\e[5~": beginning-of-history
"\e[6~": end-of-history
"\e[3~": delete-char
"\e[2~": quoted-insert' | tee {,$R}/etc/inputrc &&
cd $SRC && rm */ &&
echo '16-readline' >> $SRC/build.log && sync &&

Zlib

由于只需要共享库,因此只编译和安装共享库(包括头文件)。[提示]configure脚本只传递了CFLAGS而未传递LDFLAGS,因此需要在配置完毕后(不能提前修改Makefile.in,configure会覆盖掉你的修改)再修改Makefile中的LDSHARED(作用于共享库)和LDFLAGS(作用于二进制文件)。

R=/root/zlib-1.2.3/ && mkdir $R/lib/ &&
tar -xf zlib-1.2.3.tar.bz2 && cd zlib-1.2.3 &&
sed -i 's/${CFLAGS-"-fPIC -O3"}/"${CFLAGS-"-O3"} -fPIC"/' configure &&
./configure --prefix=/usr --shared &&
sed -i -e"s|^LDSHARED=.*|& $LDFLAGS|" -e"s|^LDFLAGS=.*|& $LDFLAGS|" Makefile &&
make && make check &&
make install                && cp   /usr/lib/libz.so.1   /lib/ &&
make install prefix=$R/usr/ && cp $R/usr/lib/libz.so.1 $R/lib/ &&
cd $SRC && rm */ &&
echo '17-zlib' >> $SRC/build.log && sync &&

Flex

由于没有安装C++编译器的原因,将会有6个与C++相关的测试项目失败,不必紧张。[依赖提示]仅在安装IPRoute2的tc工具和安装Bc时需要它。

R=/root/flex-2.5.35/ &&
tar -xf flex-2.5.35.tar.bz2 && cd flex-2.5.35 &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-nls --disable-rpath --without-libiconv-prefix --without-libintl-prefix &&
make && ( make -k check | fgrep FAILED > $SRC/build_flex.log ; make install ) &&
make install prefix=$R/usr/ sbindir=$R/usr/bin &&
cd $SRC && rm */ &&
echo '18-flex' >> $SRC/build.log && sync &&

AutoconfAutomake

以root身份运行automake-1.10.1的instsh2.test测试总是会失败,所以需要使用一个sed跳过它。[提示]如果你忍受不了测试的漫长可以去掉注释。[依赖提示]至少被PHP依赖。

tar -xf autoconf-2.61.tar.bz2 && tar -xf automake-1.10.1.tar.bz2 &&
sed -i 's|#! /bin/cat|#!/usr/bin/cat|' autoconf-2.61/lib/autoconf/specific.m4 &&
# sed -i 's|$(TESTSUITE_GENERATED_AT)|$(srcdir)/acprograms.at|g' autoconf-2.61/tests/Makefile.in &&
# sed -i 's|$(TESTS)|instsh2.test|g' automake-1.10.1/tests/Makefile.in &&
sed -i '/instsh2.test/d' automake-1.10.1/tests/Makefile.in &&
cd $SRC/autoconf-2.61 && ./configure --prefix=/usr --sbindir=/usr/bin  && make && make install &&
cd $SRC/automake-1.10.1 && ./configure --prefix=/usr --sbindir=/usr/bin && make && make check && make install &&
cd $SRC/autoconf-2.61 && make check && make install DESTDIR=/root/autoconf-2.61/ &&
cd $SRC/automake-1.10.1 && make install DESTDIR=/root/automake-1.10.1/ &&
cd $SRC && rm */ &&
echo '19-autoconf-automake' >> $SRC/build.log && sync
# [ "`tail -1 $SRC/build.log`" != '19-autoconf-automake' ] && ( test -d $SRC/automake-1.10.1 ) &&
# ( cd $SRC/autoconf-2.61 ; make uninstall ; cd $SRC/automake-1.10.1 ; make uninstall ; cd $SRC ; rm */ ; cd $SRC ; sync )

Bash

Bash 的测试套件不像其他软件包那样遇到错误就停下来并返回非零的错误代码,而是始终返回表示成功的零,因此不能在脚本中根据 make tests 命令的返回状态判断测试的成败,你必须用眼睛亲自检查 make tests 的输出结果。另一方面由于 Bash 的测试套件如同 GCC 一样,错误总是不可避免的,因此如果错误不是特别多就无需太在意(可以在 http://www.linuxfromscratch.org/lfs/build-logs/ 找到参照)。更多有关安装的详细信息请查看源码树下的 INSTALL config-top.h 以及 configure --help 本身。

[ "`tail -1 $SRC/build.log`" = '19-autoconf-automake' ] &&
R=/root/bash-3.2/ && mkdir $R/{bin,etc,root,usr/bin} &&
tar -xf bash-3.2.tar.gz && cd bash-3.2 &&
patch $SRC/bash-3.2-fixes-7.patch &&
echo '#define SYS_BASHRC "/etc/bashrc"
#define SYS_BASH_LOGOUT "/etc/bash_logout"
#define NON_INTERACTIVE_LOGIN_SHELLS
#define SSH_SOURCE_BASHRC' >> config-top.h &&
sed -i 's/LANG/LC_ALL/' tests/intl.tests &&
sed -i 's|test.tests|& </dev/tty|' tests/run-test &&
sed -i -e's/-lncurses/-lncursesw/g' -e's/-lcurses/-lncursesw/g' aclocal.m4 configure support/shobj-conf &&
sed -i 's|#! /bin/cat|#!/usr/bin/cat|' configure &&
sed -r -i 's:/bin/(true|date|cat|echo):\1:g' tests/{{array,redir,rsh}.tests,varenv.sh} &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-multibyte --disable-profiling --disable-static-link --enable-largefile --disable-nls --disable-rpath --without-bash-malloc --without-gnu-malloc --with-curses --without-termcap --disable-install-termcap --with-installed-readline --without-libiconv-prefix --without-included-gettext --without-libintl-prefix &&
make && chown -R pgsql . && su -c'make tests HOME=/data' -s/bin/bash pgsql 2>&1 | tee $SRC/build_bash.log &&
cp bash   /bin/ && cp bash   /usr/bin/ &&
cp bash $R/bin/ && cp bash $R/usr/bin/ &&
touch $R/etc/profile $R/root/.bash_history &&
cd $SRC && rm */ &&
echo '20-bash' >> $SRC/build.log && sync &&
# exec /bin/bash --login +h

Bzip2

Bzip2 的两个 Makefile 文件写的相当简洁易懂,建议直接阅读以获取安装信息。

[ "`tail -1 $SRC/build.log`" = '20-bash' ] &&
R=/root/bzip2-1.0.4/ && mkdir $R/usr/bin &&
tar -xf bzip2-1.0.4.tar.gz && cd bzip2-1.0.4 &&
sed -i 's:/bin/rm:rm:g' bzdiff &&
make test CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64" LDFLAGS="$LDFLAGS" &&
cp bzip2   /usr/bin/ && ln -sf bzip2   /usr/bin/bunzip2 &&
cp bzip2 $R/usr/bin/ && ln -sf bzip2 $R/usr/bin/bunzip2 &&
cd $SRC && rm */ &&
echo '21-bzip2' >> $SRC/build.log && sync &&

Diffutils

R=/root/diffutils-2.8.1/ &&
tar -xf diffutils-2.8.1.tar.gz && cd diffutils-2.8.1 &&
patch $SRC/diffutils-2.8.1-i18n-1.patch &&
touch man/diff.1 &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-largefile --disable-rpath --disable-nls --without-included-regex --without-libiconv-prefix --without-libintl-prefix &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '22-diffutils' >> $SRC/build.log && sync &&

Findutils

[提醒] xargs 程序将 echo 程序硬编码为"/bin/echo",且不能在源代码中将其修改为"echo"(会导致测试失败,原因不明)。

R=/root/findutils-4.2.33/ &&
tar -xf findutils-4.2.33.tar.gz && cd findutils-4.2.33 &&
# sed -i 's:/bin/echo:echo:g' xargs/testsuite/xargs.sysv/{s30-t,empty_def-t}.xe xargs/xargs.c &&
./configure --prefix=/usr --sbindir=/usr/bin --localstatedir=/var/locate --disable-id-cache --disable-debug --disable-leaf-optimisation --disable-d_type-optimization --enable-largefile --disable-nls --disable-rpath --without-included-regex --without-libiconv-prefix --without-libintl-prefix &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '23-findutils' >> $SRC/build.log && sync &&

Gawk

R=/root/gawk-3.1.6/ &&
tar -xf gawk-3.1.6.tar.bz2 && cd gawk-3.1.6 &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-portals --enable-lint --enable-switch --enable-directories-fatal --enable-largefile --disable-nls --disable-rpath --without-whiny-user-strftime --without-libiconv-prefix --without-libintl-prefix &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '24-gawk' >> $SRC/build.log && sync &&

PCRE

详细的安装信息位于源码树下的 README 文件中(不需要查看 INSTALL 了),需要仔细阅读。此外,pcrebuild手册页还有更详细的说明。

R=/root/pcre-7.6/ && mkdir $R/lib/ &&
tar -xf pcre-7.6.tar.bz2 && cd pcre-7.6 &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-shared=yes --enable-static=no --disable-cpp --disable-rebuild-chartables --enable-utf8 --enable-unicode-properties --enable-newline-is-anycrlf --enable-bsr-anycrlf --disable-ebcdic --enable-stack-for-recursion --disable-pcregrep-libz --disable-pcregrep-libbz2 --disable-pcretest-libreadline --with-pic --with-posix-malloc-threshold=15 --with-link-size=2 --with-match-limit=100000 --with-match-limit-recursion=100000 &&
make && make check &&
make install            && cp   /usr/lib/libpcre.so.0   /lib/ &&
make install DESTDIR=$R && cp $R/usr/lib/libpcre.so.0 $R/lib/ &&
cd $SRC && rm */ &&
echo '25-pcre' >> $SRC/build.log && sync &&

Grep

由于grep-2.5.3的文档依赖于Texinfo,所以为了编译成功,需要用一个sed去掉doc子目录。foad1.sh 和 fmbtest.sh 测试是意料中的失败(不过我这里却能通过)。

R=/root/grep-2.5.3/ &&
tar -xf grep-2.5.3.tar.bz2 && cd grep-2.5.3 &&
patch $SRC/grep-2.5.3-debian_fixes-1.patch &&
patch $SRC/grep-2.5.3-upstream_fixes-1.patch &&
sed -i '/^SUBDIRS/s/doc//' Makefile.in &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-perl-regexp --enable-largefile --disable-nls --without-libiconv-prefix --without-included-gettext --without-included-regex --without-included-getopt &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '26-grep' >> $SRC/build.log && sync &&

Gzip

R=/root/gzip-1.3.12/ &&
tar -xf gzip-1.3.12.tar.gz && cd gzip-1.3.12 &&
sed -i 's/futimens/gl_&/' gzip.c lib/utimens.{c,h} &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-largefile &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '27-gzip' >> $SRC/build.log && sync &&

IPRoute2

这个软件包不是通过 configure 脚本而是通过直接修改 Makefile 文件进行配置的,具体细节请阅读 README 文件。[提示]由于 arpd 对这个系统没有价值而且还依赖于 Berkeley DB ,所以这里用一个 sed 去掉它。如果你不需要依赖于Flex的tc可以去掉注释。

R=/root/iproute2-2.6.24-rc7/ && mkdir $R/bin &&
tar -xf iproute2-2.6.24-rc7.tar.bz2 && cd iproute2-2.6.24-rc7 &&
sed -i -e's|^SBINDIR=.*|SBINDIR=/usr/bin|' -e's|$(KERNEL_INCLUDE)|/usr/include|' Makefile &&
# sed -i '/^SUBDIRS/s| tc | |g' Makefile &&
sed -i '/^TARGETS/s| arpd | |g' misc/Makefile &&
make CCOPTS="-D_GNU_SOURCE $CFLAGS" LDFLAGS="$LDFLAGS" &&
make install            &&
make install DESTDIR=$R &&
cd $SRC && rm */ &&
sed -i 's|/www/bin/ip|/usr/bin/ip|' /bin/tmpinit &&
echo '28-iproute2' >> $SRC/build.log && sync &&

Make

[提示]不能明确指定 --enable/disable-case-insensitive-file-system ,否则无法编译成功。

R=/root/make-3.81/ &&
tar -xf make-3.81.tar.bz2 && cd make-3.81 &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-nls --disable-rpath --enable-largefile --enable-job-server --without-libiconv-prefix --without-libintl-prefix &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '29-make' >> $SRC/build.log && sync &&

Patch

R=/root/patch-2.5.4/ &&
tar -xf patch-2.5.4.tar.gz && cd patch-2.5.4 &&
sed -r -i 's:/bin/(mkdir|rmdir):\1:g' {mkdir,rmdir}.c &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-largefile &&
make && make install &&
make install prefix=$R/usr/ sbindir=$R/usr/bin/ &&
cd $SRC && rm */ &&
echo '30-patch' >> $SRC/build.log && sync &&

Psmisc

因为 Psmisc 默认的 configure 依赖于C++编译器,而实际上编译过程并不需要C++编译器,所以这里要重新生成一个不依赖于C++编译器的 configure 。

R=/root/psmisc-22.6/ &&
tar -xf psmisc-22.6.tar.gz && cd psmisc-22.6 &&
sed -i 's:<curses.h>:<ncurses.h>:' src/pstree.c &&
sed -i -e's/ncurses/ncursesw/g' -e'/AC_PROG_CXX/d' configure.ac &&
autoconf --force &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-selinux --disable-ipv6 --disable-nls --disable-rpath --enable-largefile --without-libiconv-prefix --without-libintl-prefix &&
make &&
make install            && ln -sf killall   /usr/bin/pidof &&
make install DESTDIR=$R && ln -sf killall $R/usr/bin/pidof &&
cd $SRC && rm */ &&
echo '31-psmisc' >> $SRC/build.log && sync &&

Tar

R=/root/tar-1.19/ &&
tar -xf tar-1.19.tar.bz2 && cd tar-1.19 &&
sed -i 's:/bin/rmdir:rmdir:g' lib/rmdir.c &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-largefile --disable-rpath --disable-nls --disable-backup-scripts --without-included-regex --without-libiconv-prefix --without-libintl-prefix &&
make && make check &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '32-tar' >> $SRC/build.log && sync &&

Util-linux-ng

SUID_CFLAGS 和 SUID_LDFLAGS 用于定义几个带有 suid 位的程序(chfn, chsh, newgrp, write, mount, umount)的编译选项,出于安全考虑,这里使用了"-fpie"和"-pie"编译选项。这个包硬编码了"/bin/{login,umount}"路径,由于这个硬编码还算合理,因此未作改动,如果你要改动的话,请查看源码树下的 include/pathnames.h 文件。由于本文将使用此包中的 simpleinit 而不是 Sysvinit 中的 init ,因此 /etc/inittab 的语法有所不同。simpleinit 不但比 init 的配置简洁,而且没有所谓"运行级"的概念。更多细节可以查看simpleinit的手册页。这个包的测试程序不是通过"make check"执行的,而是通过tests子目录中的run.sh脚本执行的,不过这里的测试程序仅供开发者使用,切勿用于生产系统!具体细节请看 tests/README 文件。

R=/root/util-linux-ng-2.13.1/ && mkdir {,$R}/{bin,etc/init.d} &&
tar -xf util-linux-ng-2.13.1.tar.bz2 && cd util-linux-ng-2.13.1 &&
sed -i 's:/sbin:/bin:g' include/pathnames.h disk-utils/mkfs.c mount/{,u}mount.c login-utils/shutdown.c tests/{,expected}/ts-mount-special &&
sed -i 's:-lncurses:-lncursesw:g' {fdisk,misc-utils,text-utils}/Makefile.{in,am} &&
SUID_CFLAGS="$CFLAGS -fpie" SUID_LDFLAGS="$LDFLAGS -pie"  \
./configure --prefix=/usr --bindir=/usr/bin --sbindir=/usr/bin --enable-largefile --disable-nls --disable-rpath --disable-arch --enable-agetty --disable-cramfs --disable-elvtune --enable-init --disable-kill --enable-last --enable-mesg --disable-partx --disable-raw --enable-rdev --enable-rename --disable-reset --enable-login-utils --enable-schedutils --enable-wall --enable-write --enable-chsh-only-listed --disable-login-chown-vcs --disable-login-stat-mail --enable-pg-bell --enable-require-password --disable-use-tty-group --disable-makeinstall-chown --with-fsprobe=blkid --without-libiconv-prefix --without-libintl-prefix --without-slang --without-pam --without-selinux --without-audit &&
make install usrsbinexecdir=/usr/bin && make install DESTDIR=$R usrsbinexecdir=/usr/bin &&
mv   /usr/bin/{simpleinit,agetty,login,mount,umount,initctl,shutdown}   /bin/ &&
mv $R/usr/bin/{simpleinit,agetty,login,mount,umount,initctl,shutdown} $R/bin/ &&
echo 'bootprog=/etc/init.d/rc
INIT_PATH=/etc/init.d/
tty1:linux:/bin/agetty -iL -l/bin/login tty1 9600
tty2:linux:/bin/agetty -iL -l/bin/login tty2 9600
tty3:linux:/bin/agetty -iL -l/bin/login tty3 9600' | tee {,$R}/etc/inittab &&
echo '#!/bin/bash
/bin/shutdown -r -q now' | tee {,$R}/bin/reboot &&
touch {,$R}/etc/{nologin,securesingle,init.d/rc} &&
chmod +x {,$R}/bin/reboot {,$R}/etc/init.d/rc &&
cd $SRC && rm */ &&
sed -i 's|:/www/bin||' /etc/profile &&
export PATH=/bin:/usr/bin &&
echo '33-util-linux' >> $SRC/build.log && sync &&

Linux-Kernel

为了尽可能简单,这个内核不支持initrd也不带任何动态加载模块,甚至禁用了模块加载功能。内核的编译优化参数是直接通过 CC 和 LD 传递的(而不是通常的 CFLAGS 和 LDFLAGS)。建议在修改默认值之前先用 make -qp | egrep '^(CC|LD|CFLAGS|LDFLAGS)' 查看一下默认值[在本文情况下的默认值是:CC='gcc -m32' LD='ld -m elf_i386']。[提示]在 LD 中使用"-s"会导致编译失败;"V=1"将显示详细的命令。此外,由于本文作者觉得默认的"-mtune"不过瘾,所以还修改了 Makefile*.cpu 文件的内容。更多有关内核编译的信息可以参考内核源码树下 Documentation/kbuild 目录中的内容;对于如何配置内核,可以参考《Linux 2.6.19.x 内核编译配置选项简介》。此外,《Linux Kernel in a Nutshell》也是一本不可多得的好书。[补充说明]patch-o-matic-ng是用于扩展内核Netfilter功能的模块,可以创建更强大的防火墙规则。最新的patch-o-matic-ng可以在这里下载,如果你需要可以打上这个补丁,详细说明请阅读源码树下的 README 文件。

R=/root/linux-2.6.24.2/ && mkdir $R/boot &&
tar -xf linux-2.6.24.2.tar.bz2 && cd linux-2.6.24.2 &&
make distclean &&
cp $SRC/linux-2.6.24-config.txt .config &&
make silentoldconfig ARCH=x86 &&
echo 'cflags-y += -march=athlon-xp' | tee arch/x86/Makefile*.cpu &&
make ARCH=x86 V=0 CC="gcc -m32 $CFLAGS" LD="ld -m elf_i386 $kLDFLAGS" &&
cp arch/x86/boot/bzImage   /boot/ &&
cp arch/x86/boot/bzImage $R/boot/ &&
cd $SRC && rm */ &&
echo '34-linux' >> $SRC/build.log && sync &&

Sysklogd

如果你需要使用 klogd 的延时启动功能,可以去掉注释。由于本文不将内核消息转发到 syslogd ,而是由 klogd 直接记录到日志,因此无需延时启动。将"/dev/log"修改为"/dev/shm/log"是因为将来的根文件系统是只读挂载的,而syslogd会在启动的时候删除并重建此socket。有关Sysklogd的更多知识,可以参考《Sysklogd 系统日志记录器》

R=/root/sysklogd-1.5/ && mkdir $R/{etc,bin,/var/log} &&
tar -xf sysklogd-1.5.tar.gz && cd sysklogd-1.5 &&
# sed -i '/DKLOGD_DELAY/s/^#//' Makefile &&
sed -i 's|/dev/log|/dev/shm/log|' syslog*.c &&
make RPM_OPT_FLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE" LDFLAGS="$LDFLAGS" &&
make install_exec BINDIR=/bin &&
make install_exec BINDIR=$R/bin &&
echo 'syslog.info   -/var/log/syslog.log
auth,authpriv.info  -/var/log/auth.log
*.info;auth,authpriv,syslog.none    -/var/log/local.log
*.alert root' | tee {,$R}/etc/syslog.conf &&
touch {,$R}/var/log/{syslog,auth,local,kernel}.log &&
cd $SRC && rm */ &&
echo '35-sysklogd' >> $SRC/build.log && sync &&

Dcron

Dillon's Cron 是一种简单、严谨、安全的 cron 实现。此软件包非常简单,可以直接修改 Makefile 和 defs.h 对其进行定制。[注意]Dcron并不使用Sysklogd记录日志,一般的方法是直接将命令的结果重定向到日志文件。有关Dcron的更多知识,可以参考《简单、严谨、安全 —— Dcron 简介》

R=/root/dcron32/ && mkdir $R/bin/ {,$R}/etc/crontabs $R/var/log &&
tar -xf dcron32.tgz && cd dcron &&
sed -i -e's|/var/spool/cron/crontabs|/etc/crontabs|' -e's|/etc/cron.d|/share/empty|' defs.h &&
make CFLAGS="$CFLAGS $LDFLAGS" &&
install -o 0 -g 0 -m 0500 crond   /bin/crond   &&
install -o 0 -g 0 -m 0500 crond $R/bin/crond   &&
touch {,$R}/etc/crontabs/root {,$R}/var/log/crond-std{out,err}.log &&
cd $SRC && rm */ &&
echo '36-dcron' >> $SRC/build.log && sync &&

XFS

这个包并不是典型的"configure && make && make install"风格的软件包,但是这里用一个 sed 来达到模仿的目的,但是仍然要注意的是 configure 的目的不是为了生成 Makefile 文件,而是为了生成 include/{builddefs,platform_defs.h} 文件。要了解更多安装信息,请仔细阅读源码树下的 Makefile include/builddefs.in doc/INSTALL 文件。内核源码树下的 Documentation/filesystems/xfs.txt 文件包含了 XFS 可以使用的挂载选项。[提示]"--enable-shared=yes"会导致 libxfs.so,libxlog.so 库丢失,不知何故。

R=/root/xfsprogs_2.9.6-1/ && mkdir $R/bin/ &&
tar -xf xfsprogs_2.9.6-1.tar.gz && cd xfsprogs-2.9.6 &&
sed -i '/autoconf$/,/LOCAL_CONFIGURE_OPTIONS$/d' Makefile &&
sed -i 's:/bin/true:true:g' configure aclocal.m4 m4/package_utilies.m4 &&
sed -i -e's|^DEBUG =.*|DEBUG=-DNDEBUG|' -e's|^OPTIMIZER =.*|OPTIMIZER=@CFLAGS@|' include/builddefs.in &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-shared=no --enable-gettext=no --enable-readline=yes --enable-editline=no --enable-termcap=no --enable-lib64=no &&
make install && make install prefix=$R/usr PKG_SBIN_DIR=$R/usr/bin &&
mv   /usr/bin/xfs_repair   /bin/ &&
mv $R/usr/bin/xfs_repair $R/bin/ &&
cd $SRC && rm */ &&
echo '37-xfsprogs' >> $SRC/build.log && sync &&

Iptables

建议编译前好好读一读 Makefile 文件,然后按你的需求进行修改[这里编译的是静态版本]。当然,还有 INSTALL 也不要忘记了。此外,如果你升级了内核,那么最好把这个包重新编译一次。由于配置Iptables是一个见仁见智的工作,需要根据实际情况灵活变通,因此本文只示范其安装,而没有涉及其配置。

R=/root/iptables-1.4.0/ && mkdir $R/bin &&
tar -xf iptables-1.4.0.tar.bz2 && tar -xf linux-2.6.24.2.tar.bz2 && cd iptables-1.4.0 &&
sed -i -e'/NO_SHARED_LIBS/s/^#//' -e'/DO_IPV6/s/^#//' -e's|^KERNEL_DIR=.*|KERNEL_DIR=/usr|' -e's|^PREFIX:=.*|PREFIX=/usr|' -e's|^LIBDIR:=.*|LIBDIR=/usr/lib|' -e's|^BINDIR:=.*|BINDIR=/usr/bin|' -e's|^DO_IPV6:=.*|DO_IPV6=0|' Makefile &&
make COPT_FLAGS="$CFLAGS" LDFLAGS="$LDFLAGS -static" KERNEL_DIR=$SRC/linux-2.6.24.2 &&
make install            &&
make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '38-iptables' >> $SRC/build.log && sync &&

Bc

如果你不打算运行 OpenSSL 的测试套件就可以不安装它(不建议这么做)。BC的测试程序在遇见错误的时候也不会返回非零的结果,因此这里将测试输出记录在一个文件中。大约有10个左右的测试项目会在最末尾一位出现舍入误差,这是正常的,无需在意。

R=/root/bc-1.06/ &&
tar -xf bc-1.06.tar.gz && cd bc-1.06 &&
sed -i 's/flex -I8/flex -I/' configure &&
sed -i '/_PROTOTYPE.*readline/d' bc/scan.l &&
sed -i 's/program_counter save_adr;/static &/' bc/load.c &&
sed -i '/<stdlib.h>/a #include <string.h>' lib/number.c &&
./configure --prefix=/usr --sbindir=/usr/bin --with-readline --without-editline --without-libedit --without-pkg &&
make && echo quit | ./bc/bc -l Test/checklib.b | tee $SRC/build_bc.log &&
make install && make install DESTDIR=$R &&
cd $SRC && rm */ &&
echo '39-bc' >> $SRC/build.log && sync &&

OpenSSL

config 脚本检查系统环境并调用 Configure 完成配置,因此配置选项是通过 config 脚本向 Configure 传递的。事实上 config 脚本的作用相当于 config.guess ,所以如果你想直接调用 Configure 的话就一定要正确指定"操作系统-目标平台"(本文就是这么干的),所有可用的目标机器列表可以使用"./Configure LIST"命令获取。Configure 脚本除了根据 Makefile.org 生成 Makefile 之外,还在 crypto/opensslconf.h 中定义了许多宏(基于 crypto/opensslconf.h.in)。详细的安装信息位于源码树下的 INSTALL Configure(特别是"PROCESS_ARGS"段) Makefile.shared Makefile.org 文件中。安装后的使用与配置信息位于 doc 目录中, FAQ 文件也可以提供一些参考。

Configure第一行是全局性选项,比如:如果你的CPU是P4/K8以上,那么就可以使用"enable-sse2"。所有可用的"[no-]***"全局选项如下:zlib,zlib-dynamic,threads,shared,asm,sse2,hw,gmp,rfc3779,krb5,ssl(ssl2,ssl3),tls,dso。no-dso仅在no-shared的前提下可用。[提示]为了安装Apache的mod_ssl成功,SSLv2/SSLv3/TLS都必须开启。[TODO]测试"asm,no-asm"的速度差异。

Configure第二行的"no-***"用于禁用crypto目录下相应的子目录,为了保证能够最小安装libcrypto,libssl,openssl,其中的大部分目录都必须保留,实际可选的目录仅有如下这些:md2,md4,mdc2,ripemd,des,rc2,rc4,rc5,idea,bf,cast,camellia,ec,dsa,ecdsa,dh,ecdh,comp,store。如果你禁用了其中的某些子目录,那么在编译前必须执行make depend步骤。[提示]为了安装OpenSSH成功,ripemd,des,rc4,bf,cast,dsa,dh目录不能被禁止。

Configure第三行相当于CPPFLAGS+CFLAGS的内容(DEVRANDOM指定随机设备,SSL_FORBID_ENULL则禁止使用NULL加密算法)。由于无法通过Configure设置LDFLAGS,并且Configure会强制清空Makefile中的LDFLAGS,所以在运行完Configure之后,使用一个sed修改所有Makefile中的LDFLAGS(用于连接openssl)和SHARED_LDFLAGS(用于连接libcrypto,libssl库)。[提示]不能省略find命令内"Makefile*"两边的引号。

OpenSSH 只依赖于该软件包的加密库(libcrypto),而带有 HTTPS 支持的 Apache 则依赖于该软件包的加密库和 SSL/TLS 库(libssl)。因此,如果你不打算使用 HTTPS 的话,可以只安装加密库(no-ssl no-tls);更多介绍可以查看 README 文件的"OVERVIEW"部分。由于本文将安装一个支持 HTTPS 的 Apache 服务器,所以这两部分库都安装了。

如果你想了解更多信息,可以读一读《OpenSSL-0.9.8g 安装与配置指南》

依赖于OpenSSL的包都对OpenSSL的版本很敏感,所以即使将来只把OpenSSL进行了很小的版本升级,也建议你将所有依赖于OpenSSL的包重新编译一遍。

R=/root/openssl-0.9.8g/ && mkdir $R/{lib,root} &&
tar -xf openssl-0.9.8g.tar.gz && cd openssl-0.9.8g &&
sed -r -i 's:/bin/(cat|cp|mkdir|mv|rm):\1:g' crypto/threads/*.sh demos/ssltest-ecc/*.sh test/* util/*.sh util/pl/*.pl &&
./Configure  linux-elf  --prefix=/usr --openssldir=/etc no-zlib no-zlib-dynamic no-threads shared no-asm no-sse2 no-hw no-gmp no-rfc3779 no-krb5  \
no-md2 no-md4 no-mdc2  no-rc2 no-rc5 no-idea no-camellia  no-ec no-ecdsa no-ecdh  no-store  \
-DSSL_FORBID_ENULL -DDEVRANDOM='"/dev/urandom"' `echo $CFLAGS` &&
find . -name "Makefile*" -exec sed -r -i -e"s|^(SHARED_)?LDFLAGS=|& $LDFLAGS |" {} \; &&
make depend && make && make test &&
make install_sw && make install_sw INSTALL_PREFIX=$R &&
rm ~/.rnd {,$R}/etc/misc/ &&
cp $SRC/openssl.cnf   /etc/ && cp   /usr/lib/lib{crypto,ssl}.so.0.9.8   /lib/ &&
cp $SRC/openssl.cnf $R/etc/ && cp $R/usr/lib/lib{crypto,ssl}.so.0.9.8 $R/lib/ &&
cd $SRC && rm */ &&
echo '40-openssl' >> $SRC/build.log && sync &&

OpenSSH

详细的安装信息可以从 configure INSTALL README.privsep 文件中获取,测试相关的信息可以从 regress/README.regress 文件中获取。TEST_SSH_QUIET=yes表示抑制测试成功的输出信息(仅输出失败信息),将TEST_SSH_TRACE设为"yes"可以输出详细且罗嗦的测试信息。TEST_SSH_PORT将用于测试的TCP监听端口改为非22以避免可能的端口冲突。如果 $PATH 中不包含"scp",那么 multiplex scp 测试将会失败,因此这里在测试前先建立一个符号链接。[提示]测试套件要求 /bin 目录下至少有两个"l"打头的文件,否则 sftp-badcmds 等测试会失败,诡异的要求!

更多有关OpenSSH的知识可以阅读《OpenSSH 安装指南》,以及一系列的《OpenSSH 中文手册》

R=/root/openssh-4.7p1/ && mkdir {,$R}/share/empty $R/bin $R/var/log &&
tar -xf openssh-4.7p1.tar.gz && cd openssh-4.7p1 &&
sed -i 's:/bin/ls:/usr/bin/ls:g' regress/*.sh &&
sed -i 's:/bin/passwd:passwd:g' openbsd-compat/bsd-cray.c &&
sed -i 's:/bin/false:false:g' configure{.ac,} &&
chown 0:0 /share/empty && chmod 1000 /share/empty &&
./configure --prefix=/usr --sysconfdir=/etc/ssh --sbindir=/usr/bin --enable-largefile --disable-etc-default-login --disable-lastlog --disable-utmp --disable-utmpx --disable-wtmp --disable-wtmpx --enable-libutil --disable-pututline --disable-pututxline --without-rpath --without-Werror --without-solaris-contracts --without-osfsia --with-zlib=/lib --with-zlib-version-check --without-skey --without-tcp-wrappers --without-libedit --without-audit --with-ssl-dir=/lib --with-openssl-header-check --without-ssl-engine --without-pam --with-random=/dev/urandom --without-rand-helper --without-prngd-port --without-prngd-socket --with-entropy-timeout=5000 --with-privsep-user=sshd --with-privsep-path=/share/empty --without-sectok --without-opensc --without-selinux --without-kerberos5 --without-xauth --with-mantype=man --with-md5-passwords --without-shadow --with-ipaddr-display --with-default-path=/bin:/usr/bin --with-superuser-path=/bin:/usr/bin:/sbin:/usr/sbin --without-4in6 --without-bsd-auth --with-pid-dir=/var/run --without-lastlog &&
make && ln -sf `pwd`/scp /usr/bin/ && touch /bin/ldummy{tjhftgs,zdeufsd} &&
make tests TEST_SSH_QUIET=yes TEST_SSH_PORT=967 &&
rm /usr/bin/scp /bin/ldummy{tjhftgs,zdeufsd} &&
make install && make install DESTDIR=$R &&
rm ~/.ssh {,$R}/etc/ssh/ssh* &&
ssh-keygen -b 4096 -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key &&
chmod 000 /etc/ssh/ssh_host_rsa_key && cp /etc/ssh/ssh_host_rsa_key* $R/etc/ssh/ &&
cp $SRC/sshd_config   /etc/ssh/ && mv   /usr/{libexec/sftp-server,bin/sshd}   /bin/ &&
cp $SRC/sshd_config $R/etc/ssh/ && mv $R/usr/{libexec/sftp-server,bin/sshd} $R/bin/ &&
touch {,$R}/var/log/sshd.log &&
cd $SRC && rm */ &&
echo '41-openssh' >> $SRC/build.log && sync &&

PostgreSQL

configure脚本的主要任务是生成 src/Makefile.global 文件,阅读它可以得到不少信息。更多安装信息可以查看《PostgreSQL 8.2.3 中文文档》,这里就不多罗嗦了。该文档位于PostgreSQL 中文之家。此外,本文还安装了非常有用的加密模块。要了解如何添加模块,可以阅读 contrib/README 来选择你想要的模块,然后进入相应的子目录阅读 README 文件。此外,contrib/start-scripts 目录下有非常棒的启动脚本示范。由于对咱中国人来说,只需要utf8与gbk的编码转换即可,为了"减负",这里删除了许多在initdb时默认创建的转换规则,仅保留了utf8与gbk之间的转换规则。此外,加载库的搜索路径也被调整了。[TODO]测试-fprefetch-loop-arrays的效果。

R=/root/postgresql-8.3.0/ && mkdir $R/dev/shm/pg_socket {,$R}/{bin,{etc,lib,var/log}/postgresql} {,$R}/share/postgresql/{timezone,timezonesets} &&
tar -xf postgresql-8.3.0.tar.bz2 && cd postgresql-8.3.0 &&
sed -i 's:/bin/pwd:pwd:g' configure{.in,} &&
LDFLAGS_SL="$LDFLAGS" \
./configure --prefix=/usr --sbindir=/usr/bin --enable-integer-datetimes --disable-nls --enable-shared --disable-rpath --enable-spinlocks --disable-debug --disable-profiling --disable-dtrace --disable-cassert --disable-thread-safety --disable-thread-safety-force --enable-largefile --without-docdir --with-pgport=5432 --without-tcl --without-perl --without-python --without-gssapi --without-krb5 --without-pam --without-ldap --without-bonjour --without-openssl --with-readline --without-ossp-uuid --without-libxml --without-libxslt --with-zlib &&
make && chown -R pgsql . && su -c'make check' -s/bin/bash pgsql &&
make install && make install DESTDIR=$R &&
sed -i -e'/gbk/!d' -e's|$libdir/utf8_and_gbk|utf8_and_gbk.so|' {,$R}/usr/share/postgresql/conversion_create.sql &&
make distclean &&
LDFLAGS_SL="$LDFLAGS" \
./configure --prefix=/usr --sbindir=/usr/bin --enable-integer-datetimes --disable-nls --enable-shared --disable-rpath --enable-spinlocks --disable-debug --disable-profiling --disable-dtrace --disable-cassert --disable-thread-safety --disable-thread-safety-force --enable-largefile --without-docdir --with-pgport=5432 --without-tcl --without-perl --without-python --without-gssapi --without-krb5 --without-pam --without-ldap --without-bonjour --with-openssl --without-readline --without-ossp-uuid --without-libxml --without-libxslt --without-zlib &&
cd contrib/pgcrypto && make && make install && make install DESTDIR=$R && cd ../../ &&
sed -i 's|$libdir/pgcrypto|pgcrypto.so|' {,$R}/usr/share/postgresql/contrib/pgcrypto.sql &&
cp   /usr/bin/postgres   /bin/ && cp   /usr/lib/libpq.so.5   /lib/ && cp   /usr/lib/postgresql/{utf8_and_gbk,pgcrypto}.so   /lib/postgresql/ &&
cp $R/usr/bin/postgres $R/bin/ && cp $R/usr/lib/libpq.so.5 $R/lib/ && cp $R/usr/lib/postgresql/{utf8_and_gbk,pgcrypto}.so $R/lib/postgresql/ &&
cp   /usr/share/postgresql/timezone/UTC   /share/postgresql/timezone/ && echo 'UTC 0' >   /share/postgresql/timezonesets/Default &&
cp $R/usr/share/postgresql/timezone/UTC $R/share/postgresql/timezone/ && echo 'UTC 0' > $R/share/postgresql/timezonesets/Default &&
mkdir -m 700 /data/tmp && chown pgsql /data/tmp && sync &&
su -c'initdb -Atrust -D/data/tmp -EUTF-8 --locale=C -Upgsql &&
pg_ctl start -p/bin/postgres -w -D/data/tmp &&
make -C contrib/pgcrypto installcheck &&
pg_ctl stop -D/data/tmp -msmart' -s/bin/bash pgsql &&
cp $SRC/postgresql.conf   /etc/postgresql/ &&
cp $SRC/postgresql.conf $R/etc/postgresql/ &&
echo '' | tee {,$R}/etc/postgresql/pg_ident.conf &&
echo 'local all all md5
hostnossl all all 127.0.0.1/32 md5' | tee {,$R}/etc/postgresql/pg_hba.conf &&
cd $SRC && rm */ &&
echo '42-postgresql' >> $SRC/build.log && sync &&

Apache

详细的安装信息可以查看《Apache 2.2 中文手册》,这里就不详细解说了。[提示]--without-libtool 会导致apr编译失败;--disable-ipv6 会导致httpd和其他二进制程序编译失败。--without-ldap --without-dbm 会导致apr-util配置失败。

APR的configure脚本在--disable/enable-profile的时候都在CFLAGS中添加"-pg"(这应当是一个bug),而如果不明确指定则添加"-g",由于"-fomit-frame-pointer"与"-pg"是冲突的,所以在CFLAGS中含有-fomit-frame-pointer的情况下使用--disable-profile会导致apr配置和编译失败。为了可以使用"-fomit-frame-pointer",这里用一个sed来确保不向CFLAGS中(实际上是不向EXTRA_CFLAGS中)添加"-pg",另外,由于看"-g"也不顺眼,顺手也去掉它。

R=/root/httpd-2.2.8/ && mkdir {,$R}/{bin,lib,var/log/apache} &&
tar -xf httpd-2.2.8.tar.bz2 && cd httpd-2.2.8 &&
patch $SRC/httpd-2.2.8-layout.patch &&
sed -i 's|temp_create_method=.*|temp_create_method=mktemp|' support/check_forensic &&
sed -i -e's|"-pg"||g' -e's|"-g"||g' srclib/apr/configure &&
cd srclib/apr &&
./configure --enable-layout=apr --enable-shared --disable-static --enable-nonportable-atomics --disable-threads --with-pic --with-sendfile --with-devrandom=/dev/urandom  &&
make && make install && make install DESTDIR=$R &&
cd ../apr-util &&
./configure --enable-layout=apr-util --with-apr=/usr --enable-shared --disable-static --with-pic --without-pgsql --without-mysql --with-expat=builtin &&
make && make install && make install DESTDIR=$R &&
cd $SRC/httpd-2.2.8 &&
./configure --enable-layout=LAPP --with-apr=/usr --with-apr-util=/usr --with-mpm=prefork \
--enable-modules="auth-digest authn-default authn-file authz-default authz-user authz-host deflate dir env setenvif expires log-config mime status rewrite so" --enable-mods-shared="info ssl" --disable-actions --disable-alias --disable-asis --disable-auth-basic --disable-authz-groupfile --disable-autoindex --disable-cgi --disable-cgid --disable-filter --disable-imagemap --disable-include --disable-isapi --disable-negotiation --disable-userdir  \
--disable-v4-mapped --with-ssl=/lib --with-z=/lib --with-perl=/usr --with-pcre=/usr --disable-static-support --disable-suexec  &&
make && make install && make install DESTDIR=$R &&
rm {,$R}/etc/apache/* {,$R}/usr/lib/{,apache}/*.exp {,$R}/www/{build*,cgi*,error,htdocs,icons,manual} &&
cp $SRC/{authn.md5,httpd.conf,mime.types}   /etc/apache/ && mv   /usr/lib/apache/   /lib/ &&
cp $SRC/{authn.md5,httpd.conf,mime.types} $R/etc/apache/ && mv $R/usr/lib/apache/ $R/lib/ &&
cp   /usr/bin/{httpd,rotatelogs}   /bin/ && cp   /usr/lib/lib{apr-1,aprutil-1,expat}.so.0   /lib/ &&
cp $R/usr/bin/{httpd,rotatelogs} $R/bin/ && cp $R/usr/lib/lib{apr-1,aprutil-1,expat}.so.0 $R/lib/ &&
cd $SRC && rm */ &&
echo '43-httpd' >> $SRC/build.log && sync &&

JPEG + PNG

R=/root/jpeg-png/ && mkdir $R/{lib,usr/{lib,include}} &&
tar -xf jpegsrc.v6b.tar.gz && tar -xf libpng-1.2.25.tar.bz2 &&
cd $SRC/jpeg-6b &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-static --enable-shared --enable-maxmem=4 &&
make && make test &&
make install-lib && make install-lib prefix=$R/usr sbindir=$R/usr/bin &&
cp   /usr/lib/libjpeg.so.62   /lib/ &&
cp $R/usr/lib/libjpeg.so.62 $R/lib/ &&
cd $SRC/libpng-1.2.25 && sync &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-static --enable-shared --with-pic --without-binconfigs --without-libpng-compat &&
make && make check &&
make install            && cp   /usr/lib/libpng12.so.0   /lib/ &&
make install DESTDIR=$R && cp $R/usr/lib/libpng12.so.0 $R/lib/ &&
rm {,$R}/usr/bin/libpng-config &&
cd $SRC && rm */ &&
echo '44-jpeg-png' >> $SRC/build.log && sync &&

PHP[含APC模块]

PHP的安装比较复杂,好在官方文档提供了详尽的安装指导,请到http://www.php.net/manual/zh/查看。这里安装的PHP除了核心模块(date,standard)之外,还包括下列PECL扩展:PCRE Session APC GD mbstring PostgresSQL 。PHP的测试程序不像其他软件包那样遇到错误就停下来并返回非零的错误代码,而是始终返回表示成功的零,因此不能在脚本中根据 make test 命令的返回状态判断测试的成败,你必须用眼睛亲自检查输出结果。[提示]只有使用了--enable-cli选项后才能运行测试程序。[TODO]测试"-minline-all-stringops"的效果。

如果你想了解更多有关如何配置PHP的知识,可以看一看《适用于 php-5.2 的 php.ini 中文版》

R=/root/php-5.2.5/ && mkdir $R/{etc,lib/apache,var/log} {,$R}/dev/shm/php_session {,$R}/www/upload  &&
tar -xf php-5.2.5.tar.bz2 && tar -xf APC-3.0.16.tgz &&
cd php-5.2.5 && mv ../APC-3.0.16 ext/apc &&
rm configure && ./buildconf --force &&
sed -i 's:/bin/ls:ls:g' build/shtool &&
sed -i 's:/bin/cat:/usr/bin/cat:g' ext/mbstring/tests/mb_send_mail*.phpt ext/standard/tests/general_functions/{bug34794,proc_open}.phpt &&
sed -i -e's:/bin/sleep:/usr/bin/sleep:g' -e's:string(28):string(32):' -e's:string(14):string(18):' ext/standard/tests/general_functions/{bug39322,proc_open02}.phpt &&
./configure --prefix=/usr --sbindir=/usr/bin --with-layout=GNU --with-apxs2=/usr/bin/apxs --enable-cli --disable-cgi --disable-mod-charset --without-pear  \
--disable-debug --disable-rpath --disable-libgcc --disable-ipv6 --enable-fd-setsize=4096 --disable-versioning --with-config-file-path=/etc  \
--enable-shared --disable-static --with-pic --with-tsrm-pthreads --enable-memory-limit --enable-zend-multibyte --disable-all --disable-reflection  \
--with-gd --with-jpeg-dir=/usr --with-png-dir=/usr --with-zlib-dir=/usr --enable-gd-native-ttf --disable-gd-jis-conv  \
--enable-mbstring --enable-mbregex --enable-mbregex-backtrack  --with-pcre-regex=/usr  --with-pgsql=/usr  --enable-session  \
--enable-apc --enable-apc-filehits --disable-apc-mmap --enable-apc-futex   &&
make && echo n | make test | tee $SRC/build_php.log &&
cp libs/libphp5.so   /lib/apache/ && cp $SRC/php.ini   /etc/ &&
cp libs/libphp5.so $R/lib/apache/ && cp $SRC/php.ini $R/etc/ &&
touch {,$R}/var/log/php_error.log &&
cd $SRC && rm */ &&
echo '45-php' >> $SRC/build.log && sync &&

现在所有软件包都已经安装完毕,距离完成最终系统只剩一步之遥了。可以通过查看 $SRC/build*.log 来了解前面的编译过程是否顺利。如果没有严重错误,那么就可以继续下面的操作。

Kbd[暂时不需要进行键盘设置,通过SSH一样可以在命令行上使用UTF-8中文]

由于官方的包好久都没有人维护了,因此这里使用非官方的 legion 版本。

R=/root/kbd-1.13/ && mkdir $R/bin &&
tar -xf kbd-1.13.tar.bz2 && cd kbd-1.13 &&
sed -i '/LOADKEYS_BINDIR/s|$(DESTDIR)/bin|$(DESTDIR)$(PREFIX)/bin|' src/Makefile.in &&
./configure --prefix=/usr --datadir=/usr/share/kbd --mandir=/usr/share/man --disable-nls &&
make RPM_OPT_FLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" &&
make install            && mv   /usr/bin/unicode_start   /bin/ &&
make install DESTDIR=$R && mv $R/usr/bin/unicode_start $R/bin/ &&
cd $SRC && rm */ &&
echo '??-kbd' >> $SRC/build.log && sync &&

(尚未调试好)Memcached[含libevent]

Memcached是一个基于内存的高性能对象缓存系统。常用于加速动态web应用(本文的PHP将配置为使用它来存储会话数据)。libevent是一个执行事件通知的库,Memcache依赖于它。[提示]内核必须支持事件轮循的系统调用(CONFIG_EPOLL=y),否则该库的运行效率将大大降低。[说明]无论测试成功与否,libevent的测试程序总是返回表示成功的"0",所以这里记录下测试输出(regress: FAILED 是正常的)。memcached的测试程序总是无法启动,原因不明,所以这里跳过测试步骤。

R=/root/libevent-1.4.2-rc/ && mkdir $R/lib &&
tar -xf libevent-1.4.2-rc.tar.gz && cd libevent-1.4.2-rc &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-shared --disable-static --with-pic &&
make && make verify > $SRC/build_libevent.log &&
make install            && cp   /usr/lib/libevent-1.4.so.2   /lib/ &&
make install DESTDIR=$R && cp $R/usr/lib/libevent-1.4.so.2 $R/lib/ &&
cd $SRC && rm */ &&
R=/root/memcached-1.2.4/ && mkdir $R/bin $R/dev/shm &&
tar -xf memcached-1.2.4.tar.gz && cd memcached-1.2.4 &&
./configure --prefix=/usr --sbindir=/usr/bin --disable-threads --with-libevent=/usr &&
make && # make test &&
cp memcached   /bin/ &&
cp memcached $R/bin/ &&
touch {,$R}/dev/shm/memcached.sock &&
cd $SRC && rm */ &&
echo '??-memcached[libevent]' >> $SRC/build.log && sync &&

FreeType2 + 中文字库[尚未调试好,且与本文主题关系不大,故而暂时取消它]

除了查看 builds/unix/configure 之外,对包的定制还需要通过修改 modules.cfg 和 include/freetype/config/ftoption.h 文件来完成的,详细的信息可以阅读 doc/CUSTOMIZE 文件。[说明]考虑到效率问题,这里的字体都是来自文泉驿的点阵字体。

R=/root/freetype-2.3.5/ && mkdir /share/fonts $R/{lib,share} &&
tar -xf freetype-2.3.5.tar.bz2 && cd freetype-2.3.5 &&
rm objs/* include/freetype/config/ftmodule.h &&
echo 'FONT_MODULES = pcf
HINTING_MODULES =
RASTER_MODULES =
AUX_MODULES = cache
BASE_EXTENSIONS =' > modules.cfg &&
echo '#define __FTOPTION_H__
#include <ft2build.h>
FT_BEGIN_HEADER
#undef  FT_CONFIG_OPTION_FORCE_INT64
#define FT_RENDER_POOL_SIZE  0
#define FT_MAX_MODULES  4
#undef FT_CONFIG_OPTION_USE_MODULE_ERRORS
FT_END_HEADER' > include/freetype/config/ftoption.h &&
./configure --prefix=/usr --sbindir=/usr/bin --enable-shared --disable-static --without-zlib --without-old-mac-fonts --without-fsspec --without-fsref --without-quickdraw-toolbox --without-quickdraw-carbon --without-ats --with-pic &&
make && make install && make install prefix=$R/usr sbindir=$R/usr/bin &&
cp /usr/lib/libfreetype.so.* /lib/ && cp $R/usr/lib/libfreetype.so.* $R/lib/ &&
cd $SRC && tar -xf wqy-bitmapfont-pcf-0.8.1-7.tar.gz &&
cp wqy-bitmapfont/wenquanyi_{10,12}pt.pcf /share/fonts/ &&
cp -R /share/fonts $R/share/ &&
cd $SRC && rm */ &&
echo '??-freetype+fonts' >> $SRC/build.log && sync &&

结尾工作

系统清理

为了最大限度避免不必要的麻烦,这里首先简单的删除一些无用文件,存储随机数种子,然后重新启动,再使用临时工具链进行 strip 操作。

cd / &&
ln -sf /proc/mounts /etc/mtab &&
rm /bin/tmpinit /etc/{ld.so.cache,profile} ~/.bash* /tmp/* /usr{,/share}/{doc,info,man} &&
cp /www/bin/{bash,mount} / &&
echo 'kernel (hd0,0)/bzImage root=0802 rootfstype=xfs rw init=/bash panic=30' > /boot/menu.lst &&
dd if=/dev/urandom of=/var/random-seed  bs=8k count=1 &&
dd if=/dev/urandom of=/var/urandom-seed bs=8k count=1 &&
sync &&
umount -l /dev/sda{9,8,7,6,5,1} &&
# 此命令一般不会成功,不过你可以等待30秒(panic=30)后由内核自动重启。
shutdown -r -q now

重启完毕,现在可以使用下面的命令进行 strip 操作。[提示]设置 lo 网络接口是为了在初始化 PostgreSQL 数据库后,启动数据库服务时可以使用 INET socket ,否则无法安装加密模块。设置 eth0 网络接口是为了便于使用SSH。

cd / &&
export PATH=/bin:/usr/bin HISTFILESIZE=0 &&
/mount -t proc    proc     /proc     &&
/mount -t tmpfs   shm      /dev/shm  &&
/mount -t devpts  devpts   /dev/pts  &&
/mount -t ext2  /dev/sda1  /boot &&
/mount -t xfs   /dev/sda5  /usr  &&
/mount -t xfs   /dev/sda6  /root &&
/mount -t xfs   /dev/sda7  /var  &&
/mount -t xfs   /dev/sda8  /www  &&
/mount -t xfs   /dev/sda9  /data  &&
/www/bin/mkdir -p -m 1777 /dev/shm/{tmp,run,pg_socket,php_session} &&
( /www/bin/strip --strip-all {,/root/*}/{,usr/}bin/*  {,/root/*}/usr/libexec/{*,*/*,*/*/*,*/*/*/*,*/*/*/*/*} ;
/www/bin/strip --strip-debug {,/root/*}/{,usr}/lib/{*,*/*,*/*/*,*/*/*/*,*/*/*/*/*} ;
rm -f /{bash,mount} /root/.bash* ) &&
sync  &&
ip -4 link set lo   txqueuelen  0       &&
ip -4 link set eth0 txqueuelen  4000    &&
ip -4 link set lo   mtu  16436          &&
ip -4 link set eth0 mtu  1500           &&
ip -4 addr add 127.0.0.1/8                               scope host    dev  lo   valid_lft forever preferred_lft forever  &&
ip -4 addr add 192.168.10.33/24 broadcast 192.168.10.255 scope global  dev eth0  valid_lft forever preferred_lft forever  &&
ip -4 link set lo   up                  &&
ip -4 link set eth0 up                  &&
ip -4 route add unicast default via 192.168.10.250 dev eth0 &&

创建站点目录、安装 phpPgAdmin APC-monitor

更多有关 phpPgAdmin 的知识,可以参考《phpPgAdmin 中文文档》

umount /www &&
mkfs.xfs -f -q  /dev/sda8   &&
mount -t xfs /dev/sda8 /www &&
mkdir -p /www/{oklaoshi/{htdocs,secret,www},phppgadmin,upload} &&
tar -xf /data/phpPgAdmin-4.2.tar.bz2 -C /data &&
tar -xf /data/APC-3.0.16.tgz         -C /data &&
mv /data/phpPgAdmin/*        /www/phppgadmin/ &&
mv /data/APC-3.0.16/apc.php  /www/phppgadmin/ &&
mv /data/config.inc.php /www/phppgadmin/conf/ &&
chown -R httpd: /www &&
chmod -R 1500   /www &&
find /www ! -type d ! -type l | xargs chmod 1400 &&
chmod 1700 /www/upload &&
sync &&
umount /www &&

初始化 PostgreSQL 数据库集群

[提示]数据库的超级用户名是:pgsql,密码是:123

umount /data &&
mkfs.xfs -f -q  /dev/sda9  &&
mount -t xfs /dev/sda9 /data &&
chown pgsql: /data &&
chmod 1700   /data &&
su -c'echo 123 > /dev/shm/pass.txt &&
initdb -Atrust -D/data -EUTF-8 --locale=C -Upgsql --pwfile=/dev/shm/pass.txt &&
pg_ctl start -w -D/data -p/bin/postgres &&
psql -d template1 -f /usr/share/postgresql/contrib/pgcrypto.sql -Upgsql -1 &&
pg_ctl stop -D/data -msmart' -s/bin/bash pgsql &&
chown -R pgsql: /data &&
chmod -R og=- /data &&
rm -f /data/*.{conf,opts} /dev/shm/pass.txt && sync &&
umount /data &&

配置 Bash

启动登陆(交互) shell 时会执行 /etc/profile 和 ~/.bash_profile 文件(后者的内容会覆盖前者),通常在其中定义环境变量。
启动非登录(非交互) shell 时会执行 /etc/bashrc 和 ~/.bashrc 文件(后者的内容会覆盖前者),通常在其中定义别名和函数。
习惯上一般要求 profile 额外执行 bashrc 的内容。
退出 shell 时会执行 /etc/bash_logout 和 ~/.bash_logout 文件。

umount /root &&
echo 'umask 077
export HISTFILESIZE=0 PATH=/bin:/usr/bin PS1="[\\u:\\w]"
export INPUTRC=/etc/inputrc TZ=UTC LC_ALL=zh_CN.UTF-8
. /etc/bashrc' > /etc/profile &&
echo "alias make='make -j1' mkdir='mkdir -p' patch='patch -p1 -i' mv='mv -f' cp='cp -af' rm='rm -fr' ls='ls -AFh' mount='mount -n' umount='umount -n'" > /etc/bashrc &&
echo 'umount -nl /usr' > /etc/bash_logout &&
echo 'mount -n -t xfs -o async,noatime,nodiratime,nodev,nosuid,ro  /dev/sda5  /usr' > /root/musr &&
chmod 1000 /root/musr && chown 0:0 /root/musr &&

配置 Grub4dos

出于安全考虑,需要使用md5密码(这里是"123")保护启动菜单。密码字符串可以使用宿主系统的 grub-md5-crypt 程序得到。[提示]因为simpleinit会在运行'bootprog'之前首先创建 /dev/initctl 这个FIFO,所以要先"rw"挂载根然后再在启动脚本里面remount成"ro"。

Grub 的一个重要作用是向内核传递引导参数,如果你想了解更多,可以参考《Linux内核引导参数精选》

echo 'timeout 5
default=0
password --md5 $1$A6dtK$96kvTFeAhOiK524zH2U260
title miniLAPP
kernel (hd0,0)/bzImage root=0802 rootfstype=xfs rw panic=30 init=/bin/simpleinit
title miniLAPP (single mode)
lock
kernel (hd0,0)/bzImage root=0802 rootfstype=xfs rw panic=30 init=/bin/simpleinit single' > /boot/menu.lst &&

编写启动脚本/关机脚本

下面的脚本相当于"有网络多用户模式"的运行级别。一般来说,启动时至少要(大体上按这个顺序)做这些事情:⒈设置掩码与环境变量。⒉挂载内核文件系统。⒊加载内核模块。⒋启动Udev守护进程。⒌挂载交换分区。⒍设置各项内核参数(控制台日志等级、主机名、ctrl-alt-del、硬件时钟……)。⒎磁盘文件系统检查与挂载。⒏清理文件系统中的垃圾、创建所必要的文件和目录。⒐还原上次关机时保存的随机数。⒑设置控制台特性。⒒启动网络接口。⒓开启防火墙。⒔启动日志守护进程。⒕启动其它各项守护进程。而关闭时的动作基本上就是上述步骤的逆过程。

[提示]PostgreSQL的自动清理功能依赖于 INET socket 和 /etc/hosts ,所以必须要启动本地回环接口。

echo '#!/bin/bash
umask 077
export PATH=/bin:/usr/bin
if [ "$1" = "start" ]; then
  echo "Server Starting ..."
  echo "Only root can login" > /etc/nologin
  sync
  mount -n -t xfs -o remount,noatime,nodiratime,nosuid,ro  /dev/sda2  /  && echo "remount / ok"      || echo "remount / failed"
  mount -n -t proc  proc  /proc                                          && echo "mount /proc ok"    || echo "mount /proc failed"
  mount -n -t devpts -o uid=0,gid=0,mode=1000       devpts  /dev/pts     && echo "mount /dev/pts ok" || echo "mount /dev/pts failed"
  mount -n -t tmpfs -o nodev,noexec,mode=1777,size=128m shm /dev/shm     && echo "mount /dev/shm ok" || echo "mount /dev/shm failed"
  echo "4 4 0 4" > /proc/sys/kernel/printk &&
  echo miniLAPP > /proc/sys/kernel/hostname &&
  echo 0 > /proc/sys/kernel/ctrl-alt-del &&
  echo 1073741824 > /proc/sys/kernel/shmmax &&
  echo 16777216 > /proc/sys/kernel/shmall &&
  echo 1 > /proc/sys/net/ipv4/tcp_syncookies &&
  echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all &&
  echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects &&
  echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects &&
  echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter && echo "setting kernel ok" || echo "setting kernel failed"
  # e2fsck -pf /dev/sda1 ; xfs_repair /dev/sda2
  xfs_repair /dev/sda3 2>/dev/null  && echo "/dev/sda3 check ok" || echo "/dev/sda3 check failed"
  xfs_repair /dev/sda5 2>/dev/null  && echo "/dev/sda5 check ok" || echo "/dev/sda5 check failed"
  xfs_repair /dev/sda6 2>/dev/null  && echo "/dev/sda6 check ok" || echo "/dev/sda6 check failed"
  xfs_repair /dev/sda7 2>/dev/null  && echo "/dev/sda7 check ok" || echo "/dev/sda7 check failed"
  xfs_repair /dev/sda8 2>/dev/null  && echo "/dev/sda8 check ok" || echo "/dev/sda8 check failed"
  xfs_repair /dev/sda9 2>/dev/null  && echo "/dev/sda9 check ok" || echo "/dev/sda9 check failed"
  mount -n -t xfs -o async,noatime,nodiratime,nodev,nosuid,ro  /dev/sda5  /usr 2>/dev/null  && echo "mount /usr ok"  || echo "mount /usr failed"
  mount -n -t xfs -o async,noatime,nodiratime,nodev,noexec     /dev/sda7  /var 2>/dev/null  && echo "mount /var ok"  || echo "mount /var failed"
  mount -n -t xfs -o async,noatime,nodiratime,nodev,noexec     /dev/sda8  /www 2>/dev/null  && echo "mount /www ok"  || echo "mount /www failed"
  mount -n -t xfs -o async,noatime,nodiratime,nodev,noexec     /dev/sda9  /data 2>/dev/null && echo "mount /data ok" || echo "mount /data failed"
  rm -fr /www/upload/*  && echo "clean /www/upload/ ok" || echo "clean /www/upload/ failed"
  mkdir -p -m 1777 /dev/shm/{tmp,run}  && echo "create /dev/shm/{tmp,run} ok" || echo "create /dev/shm/{tmp,run} failed"
  mkdir -p -m 1770 /dev/shm/pg_socket  && echo "create /dev/shm/pg_socket ok" || echo "create /dev/shm/pg_socket failed"
  chown   pgsql:   /dev/shm/pg_socket  && echo "chown /dev/shm/pg_socket ok" || echo "chown /dev/shm/pg_socket failed"
  mkdir -p -m 1700 /dev/shm/php_session  && echo "create /dev/shm/php_session ok" || echo "create /dev/shm/php_session failed"
  chown   httpd:   /dev/shm/php_session  && echo "chown /dev/shm/php_session ok" || echo "chown /dev/shm/php_session failed"
  touch /var/run/utmp  && echo "create /var/run/utmp ok" || echo "create /var/run/utmp failed"
  dd if=/var/random-seed  of=/dev/random  2>/dev/null  && echo "restore /dev/random ok"  || echo "restore /dev/random failed"
  dd if=/var/urandom-seed of=/dev/urandom 2>/dev/null  && echo "restore /dev/urandom ok" || echo "restore /dev/urandom failed"
  ip -4 link set lo   txqueuelen  0       &&
  ip -4 link set eth0 txqueuelen  4000    &&
  ip -4 link set lo   mtu  16436          &&
  ip -4 link set eth0 mtu  1500           &&
  ip -4 addr add 127.0.0.1/8                               scope host    dev  lo   valid_lft forever preferred_lft forever  &&
  ip -4 addr add 192.168.10.33/24 broadcast 192.168.10.255 scope global  dev eth0  valid_lft forever preferred_lft forever  &&
  ip -4 link set lo   up                  &&
  ip -4 link set eth0 up                  &&
  ip -4 route add unicast default via 192.168.10.250 dev eth0 && echo "start Network Interface ok" || echo "start Network Interface failed"
  #iptables here
  /bin/klogd -f/var/log/kernel.log -x && echo "start klogd ok" || echo "start klogd failed"
  /bin/syslogd -p/dev/shm/log  && echo "start syslogd ok" || echo "start syslogd failed"
  /bin/crond -c/etc/crontabs -s/share/empty 1>>/var/log/crond-stdout.log 2>>/var/log/crond-stderr.log  && echo "start crond ok" || echo "start crond failed"
  /bin/sshd -4 -e -u0 2>> /var/log/sshd.log  && echo "start sshd ok" || echo "start sshd failed"
  su -c"/bin/postgres -c config_file=/etc/postgresql/postgresql.conf" -s/usr/bin/bash pgsql  && echo "start postgres ok" || echo "start postgres failed"
  ENCRYPT_KEY=enc-key /bin/httpd -f/etc/apache/httpd.conf -k start  && echo "start httpd ok" || echo "start httpd failed"
  umount -n /usr  && echo "umount /usr ok" || echo "umount /usr failed"
  sync
fi
if [ "$1" = "stop" ]; then
  mount -n -t xfs -o async,noatime,nodiratime,nodev,nosuid,ro  /dev/sda5  /usr 2>/dev/null  && echo "mount /usr ok"  || echo "mount /usr failed"
  kill -WINCH `cat /var/run/httpd.pid`  && echo "stop httpd ok"  || echo "stop httpd failed"
  sync ; echo "waiting 10 sec for Apache to quit ..." ; sleep 10 ; sync
  kill -TERM  `cat /var/run/postgresql.pid`  && echo "stop postgres ok"  || echo "stop postgres failed"
  sync ; echo "waiting 5 sec for PostgreSQL to quit ..." ; sleep 5 ; sync
  kill -TERM  `cat /var/run/sshd.pid`  && echo "stop sshd ok"  || echo "stop sshd failed"
  killall /bin/crond  && echo "stop crond ok"  || echo "stop crond failed"
  kill -TERM  `cat /var/run/syslogd.pid` && echo "stop syslogd ok"  || echo "stop syslogd failed"
  kill -TERM  `cat /var/run/klogd.pid` && echo "stop klogd ok"  || echo "stop klogd failed"
  sync ; echo "waiting 20 sec for Other Daemons to quit ..." ; sleep 20 ; sync
  ps -ef | grep -P "/bin/(httpd|rotatelogs|postgres|sshd|sftp-server|crond|syslogd|klogd)" &&
  ( echo "The above Daemons are still running, waiting 5 min for them to quit ..." ;
  killall /bin/{httpd,rotatelogs,postgres,sshd,sftp-server,crond,syslogd,klogd} ; sleep 300 ; sync )
  ps -ef | grep -P "/bin/(httpd|rotatelogs|postgres|sshd|sftp-server|crond|syslogd|klogd)" &&
  ( echo "The above Daemons will be killed immediately" ;
  killall -s KILL /bin/{httpd,rotatelogs,postgres,sshd,sftp-server,crond,syslogd,klogd} )
  ip link set eth0 down  && echo "stop eth0 ok" || echo "stop eth0 failed"
  rm -fr /www/upload/*  && echo "clean /www/upload/ ok" || echo "clean /www/upload/ failed"
  dd if=/dev/urandom of=/var/random-seed  bs=8k count=1 2>/dev/null && echo "store /dev/random ok"  || echo "store /dev/random failed"
  dd if=/dev/urandom of=/var/urandom-seed bs=8k count=1 2>/dev/null && echo "store /dev/urandom ok"  || echo "store /dev/urandom failed"
  sync
  umount -nl /{www,data,var}  && echo "umount /{www,data,var} ok" || echo "umount /{www,data,var} failed"
  umount -nl /usr /dev/shm && echo "umount /usr /dev/shm ok" || echo "umount /usr /dev/shm failed"
fi
exit 0' > /etc/init.d/rc &&
chmod 1100 /etc/init.d/rc &&

设置文件和目录权限

先关机:

sync &&
umount -l /dev/sda{7,5,1} &&
shutdown -h -q now

重新开机后,将BIOS调整为从 LFS LiveCD 启动,所有设置均采用默认。首先进行磁盘检查,然后再设置权限:

cd /
e2fsck -pf /dev/sda1 2>/dev/null  && echo "/dev/sda1 check ok" || echo "/dev/sda1 check failed" &&
xfs_repair /dev/sda2 2>/dev/null  && echo "/dev/sda2 check ok" || echo "/dev/sda2 check failed" &&
xfs_repair /dev/sda3 2>/dev/null  && echo "/dev/sda3 check ok" || echo "/dev/sda3 check failed" &&
xfs_repair /dev/sda5 2>/dev/null  && echo "/dev/sda5 check ok" || echo "/dev/sda5 check failed" &&
xfs_repair /dev/sda6 2>/dev/null  && echo "/dev/sda6 check ok" || echo "/dev/sda6 check failed" &&
xfs_repair /dev/sda7 2>/dev/null  && echo "/dev/sda7 check ok" || echo "/dev/sda7 check failed" &&
xfs_repair /dev/sda8 2>/dev/null  && echo "/dev/sda8 check ok" || echo "/dev/sda8 check failed" &&
xfs_repair /dev/sda9 2>/dev/null  && echo "/dev/sda9 check ok" || echo "/dev/sda9 check failed" &&
# Default Permission
mount /dev/sda2 /mnt &&
touch /mnt/etc/nologin &&
chown -R  0:0 /mnt &&
chmod -R 1555 /mnt &&
find /mnt ! -type d ! -type l | xargs chmod 1000 &&
mount /dev/sda1 /mnt/boot &&
mount /dev/sda5 /mnt/usr &&
mount /dev/sda6 /mnt/root &&
mount /dev/sda7 /mnt/var &&
chown -R  0:0 /mnt &&
chmod -R 1555 /mnt &&
find /mnt ! -type d ! -type l | xargs chmod 1000 &&
# /bin
chmod 1100      /mnt/bin/* &&
chmod 1555      /mnt/bin/{false,mktemp,sync} &&
chown 1002:1000 /mnt/bin/postgres &&
chmod 1500      /mnt/bin/postgres &&
# /boot
chmod -R 1000   /mnt/boot &&
# /data
chown 1002:1000 /mnt/data &&
chmod 1700      /mnt/data &&
# /dev
chmod 1100 /mnt/dev/pts &&
chmod 1777 /mnt/dev/shm &&
chmod 1666 /mnt/dev/{full,null} &&
chmod 1444 /mnt/dev/{random,rtc,urandom,zero} &&
# /etc
chmod 1100 /mnt/etc/{certs,crontabs,iproute2,private,ssh} &&
chown 1001:1000 /mnt/etc/apache/{,authn.md5} &&
chmod 1500      /mnt/etc/apache/             &&
chmod 1400      /mnt/etc/apache/authn.md5    &&
chmod -R 1100 /mnt/etc/init.d &&
chown -R 1002:1000 /mnt/etc/postgresql   &&
chmod  1400        /mnt/etc/postgresql/* &&
chmod  1500        /mnt/etc/postgresql   &&
chmod 1444 /mnt/etc/{hosts,localtime} &&
# /lib
chmod 1444 /mnt/lib/lib*.* &&
chmod 1111 /mnt/lib/ld-linux.so.2 &&
chmod 1100 /mnt/lib/apache &&
chown -R 1002:1000 /mnt/lib/postgresql   &&
chmod  1400        /mnt/lib/postgresql/* &&
chmod  1500        /mnt/lib/postgresql   &&
# /proc /root
chmod 1100 /mnt/{proc,root} &&
find /mnt/root -type d | xargs chmod 1100 &&
# /share
chmod 1000 /mnt/share/empty &&
chown -R 1002:1000 /mnt/share/postgresql      &&
chmod -R 1500      /mnt/share/postgresql      &&
chmod    1400      /mnt/share/postgresql/*/*  &&
# /usr
find /mnt/usr -type d | xargs chmod 1100 &&
chmod -R 1100 /mnt/usr/{bin,libexec} &&
chmod 1555 /mnt/usr{,/bin,/bin/bash} &&
# /var
chmod 1100 /mnt/var/{locate,log/apache} &&
chown 1002:1000 /mnt/var/log/postgresql &&
chmod 1700      /mnt/var/log/postgresql &&
chown 1001:1000 /mnt/var/log/php_error.log &&
chmod 1200      /mnt/var/log/php_error.log &&
# /www
chown 1001:1000 /mnt/www &&
chmod 1500      /mnt/www &&
# umount
sync &&
umount /dev/sda{7,6,5,1,2} &&
echo "Permission finished"

大功告成!

到此为止,这个 MiniLAPP 系统全部制作完毕。现在可以关闭计算机,拿掉CDROM。

poweroff

重新开机后,将BIOS设置为从硬盘启动,进行最后的"冒烟测试",如果不出意外的话,应当一切正常。具体说来也就是:

  1. 使用 SSH2 客户端(PuTTY SecureCRT SecureFX ssh sftp ...)连接到 192.168.10.33:22 并使用 root 账号和密码("123")应当可以正常登陆。
  2. 登陆后,首先执行"source /root/musr"将 /dev/sda5 挂载到 /usr ,然后使用 pstree 应当可以看到所有的守护进程都在运行当中。
  3. 将 SSH2 客户端的字符集设为 UTF-8 ,应当可以在命令行上正常使用中文。比如创建和删除中文名称的文件和目录。
  4. 在 /etc/hosts 或 C:\WINDOWS\system32\drivers\etc\hosts 中添加一条"192.168.10.33 dbadmin.oklaoshi.com"记录之后应当就可以使用 http://dbadmin.oklaoshi.com 来访问phpPgAdmin[HTTP认证的用户名和密码都是"DB_Admin"],然后应当也可以使用"pgsql"用户(密码:"123")登陆数据库。
  5. 可以使用 /bin/reboot 正常重启,使用 /bin/shutdown -q -h now 正常关闭。