| | {{infobox |
| | | title = LFS: Preparing the Build |
| | | chapter = Linux Handbook, chapter 2 — the host, the partition, the sources, the staging ground |
| | | needs = A running glibc-based Linux host · ~30 GB free · ~90 source tarballs · a weekend |
| | | hardest_part = The chicken-and-egg: a Linux must already exist to build a Linux |
| | | next = LFS I — the cross-toolchain |
| | }} |
| | |
| | **Linux From Scratch** begins before a single package compiles — with a host system, a dedicated partition, ninety source tarballs verified by checksum, and a build user wearing a sterilized environment. This chapter is the setup the LFS book assumes and newcomers most often fumble — plus the traps nobody warns you about: the musl-libc host that can't work, the bash-hash cache that poisons toolchains, the `/tools` symlink that is the build's escape hatch from its own host. Get this right and the build is stamina; get it wrong and nothing after it works. |
| | |
| | ## The chicken-and-egg problem |
| | |
| | You cannot compile an operating system without an operating system. The Linux you're building needs a **host** — an already-running Linux with a working compiler, core utilities, and disk space — to compile against, and against which the new system's early pieces link. The host isn't cheating; it's how all software births. Cross-compilation's whole art is the careful severance of host influence — chapter 13 (the cross-toolchain) is where that severance is engineered. This chapter supplies the host. |
| | |
| | ## Step 1 — The host system |
| | |
| | Three valid hosts, in ascending realism: |
| | |
| | - **A virtual machine** — QEMU/KVM or VirtualBox, 2+ CPUs, 4 GB RAM, **30+ GB disk**. The professional choice: snapshots let you rewind any mistake, and nothing you break is real. |
| | - **A dedicated partition or spare machine** — the fullest experience; real GRUB, real hardware detection, real consequences. |
| | - **A live distribution on real hardware** — possible but fragile; the live environment vanishes on reboot. |
| | |
| | We use **Slackware or Slint** as the host throughout this handbook — its installer teaches partitioning by asking you to do it, its pkgtools leave the machinery visible, and it arrives with a complete development environment (gcc, make, binutils) already aboard. Any modern Linux works; the checks below tell you if yours qualifies. |
| | |
| | ### The trap nobody warns about: your host's libc |
| | |
| | **The host must run glibc.** LFS builds against GNU libc's headers and toolchain assumptions — an Alpine/musl or other non-glibc host will fail obscurely and early, in ways that look like compiler bugs but are actually libc mismatches. Check first: |
| | |
| | ```sh |
| | ldd --version | head -1 # "ldd (GNU libc) 2.xx" = good; musl = wrong host |
| | ``` |
| | |
| | Docker images based on `alpine`, musl-based distros, and BusyBox-only environments are disqualified as hosts — fine as *targets* of curiosity, wrong as LFS mothers. WSL works for practicing tools but is a trap for the real build — no real partitions, no loop devices by default; the workaround (a loop-mounted disk *image* inside WSL) is documented below. |
| | |
| | ### Verify the host qualifies |
| | |
| | The LFS book carries a `version_check.sh` ritual — the minimum versions the build requires. On your host, prove each: |
| | |
| | ```sh |
| | bash --version | head -1 # >= 3.2 |
| | binutils: ld --version | head -1 # >= 2.13.1 |
| | bison --version | head -1 |
| | bzip2 --version |
| | coreutils: chown --version | head -1 # >= 6.9 |
| | diffutils: diff --version | head -1 |
| | findutils: find --version | head -1 # >= 4.2.31 |
| | gawk --version | head -1 # >= 4.0.1 — gawk specifically, NOT mawk |
| | gcc --version | head -1 # >= 5.2 |
| | grep --version | head -1 |
| | gzip --version |
| | uname -r # kernel >= 5.4 |
| | m4 --version | head -1 |
| | make --version | head -1 # >= 4.0 |
| | patch --version | head -1 |
| | perl -V:version # >= 5.8.8 |
| | python3 --version # >= 3.4 |
| | sed --version | head -1 |
| | tar --version | head -1 # >= 1.22 |
| | texinfo: makeinfo --version | head -1 # the one everyone forgets |
| | xz --version |
| | ``` |
| | |
| | Every check must pass — the build's earliest packages lean on all of them. Slackware ships a clean pass out of the box; on Debian-family hosts `apt install build-essential gawk bison texinfo` closes the usual gaps. Two subtleties: **gawk** is required, not mawk (Debian defaults to mawk — install gawk explicitly), and **texinfo's `makeinfo`** is the classic missing package — several builds fail silently without it, weeks of confusion for a one-package fix. |
| | |
| | ## Step 2 — The partition |
| | |
| | The new system gets its own home — a dedicated partition it will fill. Before carving, one architectural decision: **BIOS/MBR or UEFI/GPT**. UEFI hosts want GPT (`gdisk`) and eventually an EFI System Partition; BIOS-era hosts take MBR (`fdisk`) and a simpler boot. For a VM you choose when creating the machine — this handbook covers both, with the GPT path primary. |
| | |
| | ```sh |
| | # as root — find the target disk |
| | fdisk -l |
| | lsblk |
| | |
| | # carve it — gdisk on GPT, fdisk on MBR |
| | gdisk /dev/sda # new partition ~30G, type 8300 (Linux filesystem) |
| | # UEFI builders add a small ESP: ~300M, type EF00, FAT32 |
| | |
| | # format the LFS partition |
| | mkfs.ext4 /dev/sda3 |
| | # and the ESP if UEFI: mkfs.vfat /dev/sda1 |
| | |
| | # the staging ground |
| | mkdir -pv /mnt/lfs |
| | mount /dev/sda3 /mnt/lfs |
| | ``` |
| | |
| | **Why ext4 and not something exotic**: the build writes hundreds of thousands of small files — ext4's journaling and tooling (`e2fsprogs`, which you build *inside* LFS) are the boring, correct choice. Btrfs/XFS work but add variables nobody needs on a first build. |
| | |
| | **A swap partition helps** if your host is lean — an existing swap can be shared: `swapon /dev/sda2`. On a VM, a second virtual disk is cleanest — zero risk to the host's own disk. |
| | |
| | Make it persistent — the build reboots: |
| | |
| | ```sh |
| | echo '/dev/sda3 /mnt/lfs ext4 defaults 0 0' >> /etc/fstab |
| | ``` |
| | |
| | ### The no-partition path: a loop-mounted image file |
| | |
| | No spare partition? Build inside a **disk image file** — the WSL workaround and the safest experiment: |
| | |
| | ```sh |
| | dd if=/dev/zero of=lfs.img bs=1G count=32 status=progress # 32 GB sparse-able image |
| | mkfs.ext4 lfs.img |
| | mkdir -pv /mnt/lfs |
| | mount -o loop lfs.img /mnt/lfs |
| | ``` |
| | |
| | The whole LFS system lives in one file — backup it with `cp`, snapshot with `cp --reflink` on CoW filesystems, delete when done. Slightly slower than a real partition; infinitely safer. |
| | |
| + | ### The live-USB host — the trap of the ephemeral environment |
| + | |
| + | Building LFS *from* a live USB (Slackware live, a rescue distro, any bootable image) is legal and occasionally the only option — but every live environment is **ephemeral**: reboot and your `$LFS` mount, your fstab edit, your `lfs` user, your dotfiles are all gone while the *partition's contents* survive. The fix is discipline: keep `$LFS` on the persistent partition, script the mount + fstab + useradd as one repeatable `setup-host.sh`, and re-run it after every boot. Treat the live environment as disposable scaffolding around a permanent partition — never the other way round. |
| + | |
| | ## Step 3 — $LFS and the sources tree |
| | |
| | Everything hangs off one environment variable, `$LFS` — the mountpoint of the partition: |
| | |
| | ```sh |
| | export LFS=/mnt/lfs |
| | mkdir -pv $LFS/sources |
| | chmod -v a+wt $LFS/sources # world-writable + sticky, /tmp-style |
| | ``` |
| | |
| | Download the book's package manifest — `wget-list` (the ~90 tarballs) and `md5sums` (their checksums) — into `$LFS/sources`, then fetch: |
| | |
| | ```sh |
| | cd $LFS/sources |
| | wget --input-file=wget-list --continue --directory-prefix=$LFS/sources |
| | # verify every tarball against its checksum BEFORE building |
| | pushd $LFS/sources && md5sum -c md5sums && popd |
| | ``` |
| | |
| | **Check every checksum before building.** A corrupt tarball discovered three chapters in is a weekend lost; `md5sum -c` catches it now. Where upstream ships GPG signatures, `gpg --verify` them too — the sources become the system's DNA. `--continue` resumes partial downloads — the ~90-package fetch survives dropped connections. For bulk downloads `aria2c -i wget-list` parallelizes across mirrors; `wget` is the book's way and everywhere. |
| | |
| | ### The offline angle nobody plans for |
| | |
| | Download *all* tarballs before starting, and keep `$LFS/sources` — mid-build network failures are common, and an LFS build with a dead network stalls forever waiting on a tarball. `rsync -a $LFS/sources/ backups/lfs-sources/` once at the start is a two-minute insurance policy. |
| | |
| + | ### Architecture honesty |
| + | |
| + | This handbook builds **x86_64** (`LFS_TGT=x86_64-lfs-linux-gnu`). LFS supports several arches (and CLFS — Cross LFS — targets them all), but a first build has no business cross-arching: the cross-toolchain already asks gcc to build *for* a foreign target; changing the *machine* class on top doubles the failure space. Build native, on x86_64, for x86_64. 32-bit builds exist in the book's lineage but are a museum path — modern LFS assumes 64-bit, and a 32-bit host cannot produce a 64-bit kernel without serious gymnastics. Check before you start: |
| + | |
| + | ```sh |
| + | uname -m # must say x86_64 — the build assumes it everywhere |
| + | ``` |
| + | |
| | ## Step 4 — The toolchain staging area |
| | |
| | ```sh |
| | mkdir -pv $LFS/tools # where the cross-toolchain will live |
| | ln -sv $LFS/tools / # /tools symlink — the trick the whole build pivots on |
| | ``` |
| | |
| | That symlink is subtle and load-bearing — worth understanding before you make it. The cross-toolchain's early binaries are compiled with paths baked in as `/tools/...`. On the host, `/tools` resolves to `$LFS/tools` through this symlink. Later, inside the chroot — where `$LFS` *is* the root — `/tools` resolves to the *same* directory, same absolute path, no rewriting. It's how the build escapes its host's filesystem while still being built by it — a one-line filesystem judo move that makes the entire chroot transition seamless. |
| | |
| | ## Step 5 — The lfs user |
| | |
| | Build as a dedicated user, never root — one mis-typed `make install` as root overwrites your *host's* `/usr` and the host dies mid-build. This has happened to everyone who skipped the warning. |
| | |
| | ```sh |
| | groupadd lfs |
| | useradd -s /bin/bash -g lfs -m -k /dev/null lfs |
| | passwd lfs |
| | |
| | # hand the staging ground to lfs |
| | chown -v lfs $LFS/{usr{,/*},lib,var,etc,bin,sbin,tools} |
| | chown -v lfs $LFS/sources |
| | ``` |
| | |
| | Two details nobody thinks of: `-s /bin/bash` *must* be explicit — on Debian-family hosts new users default to dash, and dash is not bash (the builds require bash); `-k /dev/null` gives lfs an empty skeleton directory so no host dotfiles leak into its environment. And the UID you create now will own files inside the finished system until the book's cleanup stages normalize ownership — a small oddity you'll see in `ls -l` output later. |
| | |
| | ### The sterile environment |
| | |
| | Login as `su - lfs` (the `-` matters — a full login shell) and write the two files that make every build correct: |
| | |
| | ```sh |
| | cat > ~/.bash_profile << "EOF" |
| | exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash |
| | EOF |
| | ``` |
| | |
| | `env -i` gives lfs a *sterile* environment — every login respawns bash with only HOME, TERM, and PS1: no inherited host variables, no leaked `LD_LIBRARY_PATH`, no distro-local `CFLAGS` quietly poisoning cross-compilation. `TERM` is kept so your terminal still renders correctly. |
| | |
| | ```sh |
| | cat > ~/.bashrc << "EOF" |
| | set +h # no hashing — always re-find binaries fresh |
| | umask 022 # sane default permissions |
| | LFS=/mnt/lfs |
| | LC_ALL=POSIX # predictable locale for reproducible builds |
| | LFS_TGT=x86_64-lfs-linux-gnu # the target triplet — our system's name |
| | PATH=$LFS/tools/bin:$PATH # our toolchain first, host's second |
| | MAKEFLAGS='-j$(nproc)' # parallel builds across all cores |
| | export LFS LC_ALL LFS_TGT PATH MAKEFLAGS |
| | EOF |
| | source ~/.bash_profile |
| | ``` |
| | |
| | Each line earns its place, and three of them defend against specific ghosts: |
| | |
| | - **`set +h`** — bash caches binary locations in a hash table (`hash` shows it). Mid-build, your fresh `$LFS/tools/bin/gcc` replaces the host's gcc — but a hashed bash would keep running the *old* one. `set +h` forces PATH re-resolution every call. This single line prevents a class of "my new toolchain isn't being used" bugs that look like curses. |
| | - **`LC_ALL=POSIX`** — builds that sort, grep, and awk differently under different locales produce *different systems*. POSIX locale makes every build byte-reproducible — the difference between a toolchain that works and one whose configure probes disagree with themselves. |
| | - **`LFS_TGT`** — the GNU target triplet (`x86_64-lfs-linux-gnu`) naming your new system's architecture-vendor-OS — every `--target=$LFS_TGT` in the cross-toolchain builds *for* it; it's how `gcc` learns it is building a foreign system's compiler. |
| | - **`MAKEFLAGS='-j$(nproc)'`** — parallel builds across every core. Hours become minutes — with one honest caveat: a handful of old packages have racy Makefiles; if a build fails weirdly with `-j`, retry serial before debugging anything else. |
| | |
| | ## Step 6 — The final checks |
| | |
| | ```sh |
| | su - lfs |
| | echo $LFS $LFS_TGT # must print /mnt/lfs and the triplet |
| | which gcc # host's gcc — until tools/ exists |
| | mount | grep lfs # partition still mounted |
| | df -h $LFS # space confirmed |
| | id # you are lfs — NOT root |
| | ``` |
| | |
| | Then, and only then, the build begins. |
| | |
| + | ## The discipline nobody teaches: log everything |
| + | |
| + | The single most useful LFS habit isn't in the book — **record every command and its output**. Builds fail silently, and the forensic trail is everything: |
| + | |
| + | ```sh |
| + | # as lfs — capture everything, forever |
| + | exec > >(tee -a $LFS/build.log) 2>&1 |
| + | ``` |
| + | |
| + | or simply run each package build inside `script -a $LFS/logs/<pkg>.log`. When a configure probe fails three packages deep, the log tells you which host assumption poisoned it. Disk space for logs is trivial; the information is irreplaceable. Keep a plain-text journal of which packages installed and in what order — when the finished system misbehaves, that journal is the difference between debugging and guessing. |
| + | |
| + | ## Disk-space planning |
| + | |
| + | 30 GB is the floor; 40 GB is comfortable: |
| + | |
| + | | What | Space | |
| + | | --- | --- | |
| + | | `$LFS/sources` (~90 tarballs) | ~500 MB | |
| + | | Extracted + build trees | ~4–6 GB | |
| + | | `/tools` cross-toolchain | ~1 GB | |
| + | | The finished system | ~3–4 GB | |
| + | | Slack (logs, temp, rebuilds) | the rest | |
| + | |
| + | Running out of space mid-build is the second-most-common silent killer after wrong-user builds — `df -h $LFS` between major packages. |
| + | |
| + | ## Eyes-free LFS |
| + | |
| + | Every step in this chapter works in a **virtual console with no GUI** — the book's entire toolchain is text. Speakup users can build LFS from a talking console exactly as sighted users do from xterm; `makeinfo`-free documentation paths and `man`/`info` pages are all readable. The accessibility chapter of the finished system (BRLTTY, Speakup inside the kernel you build) is its own handbook page — but the *build* itself needs nothing graphical. See [[Speakup]]. |
| + | |
| | ## What an SBU is |
| | |
| | The book measures build costs in **SBUs** — *Standard Build Units*: the time the first package (binutils pass 1) takes on your machine, used as the unit for every other package's estimate. "This package takes 5 SBU" means five times your binutils-1 build. On a modern multi-core host an SBU is a few minutes; measure yours once and every estimate in the book becomes concrete. |
| | |
| | ## The two books — choose now |
| | |
| | LFS ships two editions: **SysVinit** and **systemd**. The choice is your new system's PID-1 — made now because later chapters fork on it. For this handbook we follow **SysVinit** — matching Slackware/Slint's BSD-style rc scripts, and because `rc.M` is a file you can read while a systemd unit graph is not — with systemd's parallel path flagged wherever the books diverge. Either teaches the same machine. |
| | |
| | ## Where builds go wrong here |
| | |
| | | Symptom | Almost always | |
| | | --- | --- | |
| | | `make install` wrote to /usr on the *host* | You were root — always build as lfs | |
| | | Tools not found after install | `set +h` missing — bash cached old paths | |
| | | Weird locale-dependent failures | `LC_ALL` unset — export `POSIX` | |
| | | Host leaked into a build | `.bash_profile` lacking `env -i` — sterile env required | |
| | | `/tools` empty inside chroot | The `/tools` symlink wasn't made on the host side | |
| | | Build can't write $LFS | Partition unmounted after reboot — fstab entry | |
| | | Tarball fails to extract | Checksum it *before* — corrupt download | |
| | | Configure can't find libc | Host is musl-based — wrong host entirely | |
| | | lfs's shell behaves oddly | Defaulted to dash — `-s /bin/bash` required | |
| | | A package fails only with `-j` | Racy Makefile — retry `make` serial | |
| | | `makeinfo` missing mid-build | texinfo — the forgotten requirement | |
| + | |
| + | ## The honest time budget |
| + | |
| + | | Phase | Wall time (modern host, `-j$(nproc)`) | |
| + | | --- | --- | |
| + | | This chapter — host, partition, sources, env | an evening | |
| + | | Cross-toolchain (ch. next) | 1–3 hours | |
| + | | Temporary system + chroot | 3–6 hours | |
| + | | Full system packages | 4–10 hours | |
| + | | Kernel + boot + first boot | 1–2 hours | |
| + | | **Whole build** | **a weekend to a week** | |
| + | |
| + | The book says LFS is not a race — and it isn't. Slow is correct; every configure probe read properly is a bug you'll never meet. |
| | |
| | ## Exercises |
| | |
| | 1. Run the full version-check roster on your host; note every version that would disqualify it — and check `ldd --version` for the glibc trap. |
| | 2. Partition a scratch VM disk by hand — `gdisk`, `mkfs.ext4`, mount, `fstab`, reboot, verify it returns. |
| | 3. Build the loop-image variant (`lfs.img`) alongside the partition — understand both paths. |
| | 4. Download five real upstream tarballs into `$LFS/sources`; `md5sum -c` them; then corrupt one deliberately and watch the check catch it. |
| | 5. As `lfs`, deliberately break `$PATH` — then explain why `set +h` exists. |
| | 6. Time a trivial `./configure && make` and compute your SBU intuition. |
| | |
| | ## Timeline |
| | |
| | | Step | What you did | |
| | | --- | --- | |
| | | Host | Verified a glibc-based Linux with dev tools | |
| | | Partition | Carved, formatted, mounted `/mnt/lfs`, persisted in fstab | |
| | | Sources | ~90 tarballs, checksum-verified, in `$LFS/sources` | |
| | | Staging | `$LFS/tools` + the `/tools` symlink | |
| | | User | `lfs` with sterile env, `LFS_TGT`, `MAKEFLAGS`, `set +h` | |
| | | Next | LFS I — the cross-toolchain: binutils → gcc → glibc | |
| | |
| | ## References |
| | |
| | - linuxfromscratch.org — LFS book (SysV & systemd editions), chapters 2–4: host requirements, preparing a partition, packages & patches, final preparations |
| | - The `version_check.sh` convention — the book's own host-qualification script |
| | - wget-list / md5sums manifests shipped with each LFS release |
| | |
| | ## See also |
| | |
| | - [[Linux]] — chapter 1: the landscape and the handbook map |
| | - [[Speakup]] — the eyes-free Linux path |
| | |
| | [[Category:Linux]] |
| | [[Category:Operating systems]] |
| | [[Category:Handbook]] |
| | [[Category:LFS]] |
| | |