From 3b0bf172104bf6c39b353b98f815b9b7427346c8 Mon Sep 17 00:00:00 2001 From: The_miro Date: Fri, 26 Jun 2026 19:08:33 +0200 Subject: [PATCH] fix(archiso): document/automate block-level USB write to fix ldlinux.c32 error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "failed to load ldlinux.c32" when booting from USB is caused by writing the isohybrid ISO at file level (drag-and-drop, Rufus "ISO" mode, UNetbootin), which destroys the MBR/isolinux layout. The ISO build itself is correct — archiso's bios.syslinux mode installs isolinux.bin + isohdpfx.bin + the c32 modules and applies the isohybrid MBR, and the profile's bootmodes match upstream releng. Add archiso/write-usb.sh: a safe block-level (dd) writer that lists removable disks, refuses partitions and the system/root disk, requires an all-caps YES, unmounts the target, then writes with conv=fsync. build.sh's completion output now points at it and warns that file-level copies cause exactly this error. Co-Authored-By: Claude Opus 4.8 --- setup/archiso/build.sh | 17 ++++++++ setup/archiso/write-usb.sh | 85 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100755 setup/archiso/write-usb.sh diff --git a/setup/archiso/build.sh b/setup/archiso/build.sh index cb09a24..625d623 100755 --- a/setup/archiso/build.sh +++ b/setup/archiso/build.sh @@ -228,6 +228,23 @@ else echo "No answerfile — guided (manual) installer will start automatically on boot." fi +# ── How to write the ISO to USB ──────────────────────────────────────────────── +# This is an isohybrid image: it MUST be written block-for-block. Copying the +# file onto a mounted USB, or using Rufus "ISO" mode / UNetbootin, rewrites the +# layout and the BIOS bootloader then fails with "failed to load ldlinux.c32". +# Glob (not ls) so filenames are handled safely; date-named ISOs sort ascending, +# so the last match is the newest build. +_isos=("$OUT_DIR"/*.iso) +_built_iso="${_isos[-1]}" +if [[ -e "$_built_iso" ]]; then + echo + echo "To write it to a USB stick (block-level — required for BIOS boot):" + echo " bash $SCRIPT_DIR/write-usb.sh \"$_built_iso\" /dev/sdX" + echo " or directly: sudo dd if=\"$_built_iso\" of=/dev/sdX bs=4M conv=fsync status=progress" + echo " Do NOT drag-and-drop the .iso onto the USB, and avoid Rufus 'ISO' mode" + echo " (use 'DD' mode) — those cause 'failed to load ldlinux.c32' at boot." +fi + # ── Netboot artifacts ────────────────────────────────────────────────────────── # mkarchiso's 'netboot' build mode (set in profiledef.sh) produces a tarball # containing the kernel, initramfs, and airootfs.sfs ready for HTTP-based PXE diff --git a/setup/archiso/write-usb.sh b/setup/archiso/write-usb.sh new file mode 100755 index 0000000..89035e4 --- /dev/null +++ b/setup/archiso/write-usb.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# write-usb.sh — write the M-Archy ISO to a USB stick with a raw, block-level copy. +# +# WHY THIS EXISTS: +# The ISO produced by build.sh is an *isohybrid* image: the BIOS bootloader +# (isolinux) and its .c32 modules live at fixed offsets referenced by an MBR +# embedded in the first sectors. A block-level write (dd) reproduces those +# sectors exactly. Copying the .iso file onto a mounted USB, or using Rufus +# "ISO" mode / UNetbootin / similar "ISO-to-USB" tools, rewrites the layout and +# the firmware then fails at boot with: "failed to load ldlinux.c32". +# Writing the whole device verbatim is the only reliable method, and it boots +# on both BIOS (isolinux) and UEFI (systemd-boot) machines. +# +# USAGE: +# bash write-usb.sh +# e.g. bash write-usb.sh ~/m-archy-out/m-archy-2026.06.26-x86_64.iso /dev/sdb +# Run with the ISO but no device to list candidate removable disks. + +# -e: abort on error; -u: error on unset vars; -o pipefail: catch pipe failures. +set -euo pipefail + +ISO="${1:-}" +DEV="${2:-}" + +die() { printf '\n Error: %s\n\n' "$1" >&2; exit 1; } + +# List whole disks so the operator can identify the USB stick. RM=1 marks a +# removable device, which is almost always the stick (and never the system disk). +list_disks() { + echo "Block devices on this machine (RM=1 means removable — usually your USB):" + lsblk -dn -o NAME,SIZE,TYPE,RM,MODEL \ + | awk '$3=="disk" {model=""; for(i=5;i<=NF;i++) model=model (i>5?" ":"") $i; + printf " /dev/%-8s %7s RM=%s %s\n", $1, $2, $4, model}' +} + +[[ -n "$ISO" ]] || { echo "Usage: bash write-usb.sh "; echo; list_disks; exit 1; } +[[ -f "$ISO" ]] || die "ISO not found: $ISO" +if [[ -z "$DEV" ]]; then + echo "No target device given for ISO: $ISO"; echo; list_disks + echo; echo "Re-run as: bash write-usb.sh \"$ISO\" /dev/sdX"; exit 1 +fi + +# ── Safety checks (this operation is destructive) ───────────────────────────── +[[ -b "$DEV" ]] || die "$DEV is not a block device." +# Must be a whole disk, not a partition like /dev/sdb1 — dd to a partition would +# not produce a bootable stick and could corrupt an existing filesystem. +[[ "$(lsblk -dn -o TYPE "$DEV" 2>/dev/null)" == "disk" ]] \ + || die "$DEV is not a whole disk. Pass the disk (e.g. /dev/sdb), not a partition." +# Refuse the disk that currently backs the root filesystem — never overwrite the +# running system. PKNAME of the root source resolves the parent disk name. +_root_src=$(findmnt -no SOURCE / 2>/dev/null || true) +_root_disk=$(lsblk -no PKNAME "$_root_src" 2>/dev/null | head -n1 || true) +[[ -n "$_root_disk" && "/dev/$_root_disk" == "$DEV" ]] \ + && die "$DEV holds your root filesystem — refusing to overwrite the running system." +# A non-removable target is suspicious; warn but allow (some USB enclosures +# report RM=0). The explicit YES prompt below is the real safeguard. +[[ "$(lsblk -dn -o RM "$DEV" 2>/dev/null)" == "1" ]] \ + || echo "WARNING: $DEV is NOT flagged removable — make absolutely sure it is your USB stick." + +echo +lsblk "$DEV" +echo +_size=$(lsblk -dn -o SIZE "$DEV") +_model=$(lsblk -dn -o MODEL "$DEV" | xargs) +echo "About to ERASE $DEV (${_size}${_model:+, $_model}) and write:" +echo " $ISO" +echo +read -rp "Type YES (all caps) to proceed: " _ans +[[ "$_ans" == "YES" ]] || { echo "Aborted."; exit 1; } + +# Unmount any mounted partitions of the target so dd has exclusive access. +while read -r _part; do + sudo umount "/dev/$_part" 2>/dev/null || true +done < <(lsblk -ln -o NAME,TYPE "$DEV" | awk '$2=="part"{print $1}') + +echo "Writing (block-level)…" +# bs=4M: large blocks for throughput; conv=fsync: flush to the device before dd +# exits so "Done" really means written; status=progress: live byte counter. +sudo dd if="$ISO" of="$DEV" bs=4M conv=fsync status=progress +# Final flush of any remaining kernel buffers for the device. +sync + +echo +echo "Done — $DEV is now bootable (BIOS + UEFI)." +echo "Eject it safely (e.g. 'udisksctl power-off -b $DEV') before unplugging."