LFS Installing
An LFS install has no anaconda, no calamares, no installer framework — and needs none. The live environment you just built is the installer: it contains the complete system, compressed, plus every tool required to write that system to a disk. What remains is a guided script doing five honest steps — partition, format, copy, bootloader, identity — and three field-hardened lessons about why the naive version of each step fails on real hardware. Measured end-to-end: live env → blank 30 GB disk → powered-off, media-removed boot → login → network → SSH.
The contract — what the installer actually does
- Detect the boot mode —
[ -d /sys/firmware/efi ]distinguishes a UEFI-booted live env from BIOS. The installed layout follows the firmware the media booted under. - Partition (GPT) — UEFI: a 512 MiB ESP + the rest ext4 root. BIOS-on-GPT: a 1 MiB
EF02bios-boot partition forcore.img+ the rest ext4 root (the chapter 6 BIOS-on-GPT note explains why). - Format + copy —
mkfs.vfatthe ESP,mkfs.ext4the root, thentar-copy the mounted squashfs contents onto the target. Copying the squashfs (not the live overlay) is what guarantees the installed system is clean — no runtime writes, no/mediamount loops. - fstab — generated from
blkidfilesystem UUIDs (fstab is userspace; filesystem UUIDs are correct here even though the kernel line needs partition UUIDs — see below). - Bootloader, in chroot —
mount --bindthe virtual filesystems into the target,grub-installfor the matching platform,grub-mkconfig. - Identity — regenerate
machine-idand SSH host keys: an installed system cloned from an image must not carry the image's identity into the world.
Add the polish later — hostname, user, timezone prompts are installer UX, not installer plumbing. The six steps are the load-bearing contract.
Field lesson 1 — never trust a device letter
The single most dangerous assumption in a naive installer: that /dev/sdb today is /dev/sdb tomorrow. The kernel assigns SCSI disk letters by probe-completion order — whichever controller finishes first wins the letter, and completion order varies boot to boot. Measured: a 30 GB install target presented as /dev/sdc in one boot and /dev/sdb in the next; a root=/dev/sdc2 kernel line then panicked unknown-block(8,34) trying to mount the build disk's 1 MiB partition.
The fix is root=PARTUUID= — the kernel parses GPT partition UUIDs natively, no initramfs required (chapter 6's root= table has the full matrix; root=UUID= — the filesystem UUID — still requires userspace and does not work without one). Install-time fix: after grub-mkconfig, rewrite root=/dev/... to root=PARTUUID=<uuid> in the generated grub.cfg, or patch the generator's defaults.
Field lesson 2 — ro without an initramfs stays ro forever
Distribution installers emit ro on the kernel line because their initramfs remounts the root rw after checking it. A no-initramfs system has no remount step — and if systemd-remount-fs.service doesn't fire (or the fstab ordering defeats it), / stays read-only for the life of the session. The symptom cascades silently: journald degrades to volatile-only (logs vanish at reboot — this exact effect hid a real diagnosis for an hour), no unit can write state, network leases can't persist. Fix: rw on the installed kernel line; the kernel mounts writable directly and no remount is owed. Pair it with rootwait — async SCSI probing can lose the race to do_mounts on slower controllers.
Field lesson 3 — Name=e* matches more than Ethernet
A systemd-networkd [Match] Name=e* looks like it means "every Ethernet" — predictable-interface names are en*. But the glob is a character match, not a semantic one: the kernel's obsolete serial-line equalizer driver creates a dead eql interface, and e* matches it. Measured: the same address bound to eql and ens33; outbound traffic blackholed through the dead link while ARP resolved normally — layer 2 healthy, layer 3 gone, the most misleading failure signature in the whole build. The match is Name=en* — one character narrower, meaningfully correct.
Identity — what a clone must not keep
Copying a filesystem copies its identity. Three regenerations separate "the image" from "the machine":
/etc/machine-id—systemd-machine-id-setup --root=<target>(or truncate to empty and let first boot assign it). D-Bus, journald, and anything DHCP-adjacent treats machine-id as the host's fingerprint.- SSH host keys —
ssh-keygen -Ain the target chroot. Ships-in-image host keys mean every installed machine shares a private key. /etc/default/grub— may not exist in the image at all. Create it withGRUB_CMDLINE_LINUX_DEFAULTcarrying your required flags (ima_appraise=off, serial consoles) — otherwise the nextgrub-mkconfigon the installed system silently drops them.
The EFI removable path — NVRAM-free booting
grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable writes EFI/BOOT/BOOTX64.EFI — the firmware fallback path, loaded without any boot entry ever being registered in NVRAM. Removable is the right default for an installer: it boots on machines whose NVRAM is unwritable, on disks moved between machines, and in VMs with no EFI boot configuration at all — which is exactly how VMware found it during measurement.
What an installer owes the user
The v0.1 is deliberately guided: list the candidate disks with sizes and models, make the user pick one, print in full sentences that the chosen disk will be erased, require typed confirmation, then work. Whole-disk-only is honest scope — partitioning is the most irreversible operation a normal user will ever run, and a script that wipes the wrong disk has no apology worth hearing. Automation comes later, behind a flag, after the guided path is proven — not before.
Exercises
- Boot the live env twice and compare
lsblkoutput — find which letter your install target took each time. Then explain why PARTUUID survives what the letter cannot. - Read
do_mountsininit/do_mounts.cand find whererootwaitsleeps versus whereroot=/dev/sdXNparses. - Install to a scratch disk, then delete
/etc/machine-idand diff the first boot's journal against a boot that kept the file. - Write the BIOS-on-GPT variant: which single partition-type GUID changes, and which
grub-installtarget replacesx86_64-efi?
Timeline
- 1998 — Debian
boot-floppies/dbootstrap: the guided-text-installer template still recognizable today - 2004 —
debian-installermodularizes the same idea;partmanmakes partitioning semi-sane - 2006 — Ubuntu's Ubiquity puts a live-env installer on the desktop: run the system, install it from inside itself
- Present — the pattern this chapter follows: live environment as installer, no separate install image
See also
- LFS Live ISO — chapter 7 of the arc: the media this installer rides on
- LFS Kernel and Boot — the
root=identifier table, BIOS-on-GPT, serial debugging - GRUB — both platform installs and the removable path
- Disks and Storage — partitioning, filesystems, fstab grammar
- Inside a Linux System — what the installed system does at its first boot
- Linux Security — machine-id, host keys, and why identity isn't copied