#!/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."