Skip to contentSkip to Braille Studio
ALB Wiki

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

  1. 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.
  2. Partition (GPT) — UEFI: a 512 MiB ESP + the rest ext4 root. BIOS-on-GPT: a 1 MiB EF02 bios-boot partition for core.img + the rest ext4 root (the chapter 6 BIOS-on-GPT note explains why).
  3. Format + copy — mkfs.vfat the ESP, mkfs.ext4 the root, then tar-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 /media mount loops.
  4. fstab — generated from blkid filesystem UUIDs (fstab is userspace; filesystem UUIDs are correct here even though the kernel line needs partition UUIDs — see below).
  5. Bootloader, in chroot — mount --bind the virtual filesystems into the target, grub-install for the matching platform, grub-mkconfig.
  6. Identity — regenerate machine-id and 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 -A in 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 with GRUB_CMDLINE_LINUX_DEFAULT carrying your required flags (ima_appraise=off, serial consoles) — otherwise the next grub-mkconfig on 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

  1. Boot the live env twice and compare lsblk output — find which letter your install target took each time. Then explain why PARTUUID survives what the letter cannot.
  2. Read do_mounts in init/do_mounts.c and find where rootwait sleeps versus where root=/dev/sdXN parses.
  3. Install to a scratch disk, then delete /etc/machine-id and diff the first boot's journal against a boot that kept the file.
  4. Write the BIOS-on-GPT variant: which single partition-type GUID changes, and which grub-install target replaces x86_64-efi?

Timeline

  • 1998 — Debian boot-floppies / dbootstrap: the guided-text-installer template still recognizable today
  • 2004 — debian-installer modularizes the same idea; partman makes 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

This page was last edited 2026-11-02 14:19:57 UTC by ALB Team. What links here