From 0a31e558bef9b9f42aabbc50ee43ba0b06894e93 Mon Sep 17 00:00:00 2001 From: rajames Date: Thu, 29 Oct 2026 06:22:26 -0400 Subject: [PATCH] feat(build): make write_stick DEVICE=/dev/sdX -- the PC stick onto a USB stick, checked and read back scripts/write_stick.sh refuses a device that is not a whole, removable USB disk, that holds the running system, that is smaller than the image, or that has a mounted partition (it prints the udisksctl lines); shows the stick and asks for its name typed back; writes with conv=fsync, flushes, reads the stick back and compares it with the image byte for byte; powers it off. On Windows/macOS it points to balenaEtcher or Rufus. Captain Bob, 2026-10-28, after a terminal line break dropped dd's bs=4M conv=fsync on the first SER5 stick. Co-Authored-By: Claude Opus 5.5 --- Makefile | 12 +++++++- scripts/write_stick.sh | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100755 scripts/write_stick.sh diff --git a/Makefile b/Makefile index bab14091..b12d422e 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,9 @@ # (build/boards//*.spdx*) and a fresh # source SBOM -- needs syft # make boot_images every board that has a kernel port +# make write_stick DEVICE=/dev/sdX write the PC stick image to a USB stick, checked +# first, read back and compared after +# (scripts/write_stick.sh; Linux, sudo) # make docs [TARGET=] the book; a board adds its appendix # # make clean all of build/ except build/cache/ @@ -59,7 +62,7 @@ BOARD_DIR := $(if $(TARGET),$(BOARD_DIR_$(TARGET))) SUBMAKE := $(MAKE) --no-print-directory .DEFAULT_GOAL := all -.PHONY: all boot_image boot_images docs clean clean-docs kernel boards help help-hosted sbom sbom-board \ +.PHONY: all boot_image boot_images write_stick docs clean clean-docs kernel boards help help-hosted sbom sbom-board \ qemu qemu-esp qemu-gdb # ARCH given on the command line and no TARGET: `clean` is the kernel's for that @@ -222,6 +225,13 @@ boards: @$(foreach b,$(BOARDS),printf ' %-9s %-8s %s\n' "$(b)" \ "$(if $(call board_ready,$(b)),ready,no port)" "$(call board_field,$(b),BOARD_DESC)";) +# write_stick -- the PC stick image (make boot_image TARGET=PC) onto a USB stick, safely: +# scripts/write_stick.sh refuses anything but a whole, removable, unmounted USB disk big enough, +# asks for the device to be typed back, writes, flushes, reads back and compares. +write_stick: + @[ -n "$(DEVICE)" ] || { echo "Usage: make write_stick DEVICE=/dev/sdX (lsblk lists the disks)"; exit 1; } + @bash scripts/write_stick.sh build/boards/pc/lithos-pc.img $(DEVICE) + help: @sed -n '/^# TARGET is/,/^# =====/p' Makefile | sed '$$d; s/^# \{0,1\}//' diff --git a/scripts/write_stick.sh b/scripts/write_stick.sh new file mode 100755 index 00000000..ffc510d5 --- /dev/null +++ b/scripts/write_stick.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# write_stick.sh -- write a StarshipOS stick image to a USB stick, safely, and prove it. +# +# scripts/write_stick.sh IMAGE DEVICE e.g. build/boards/pc/lithos-pc.img /dev/sdb +# make write_stick DEVICE=/dev/sdX (the PC stick; root Makefile) +# +# Linux only (lsblk, udisksctl, sudo). Refuses a device that is not a whole, removable USB disk, +# that holds the running system, that is smaller than the image, or that has a mounted partition. +# Shows the device and asks for its name to be typed back before writing. Writes with +# conv=fsync, flushes, reads the stick back and compares it with the image byte for byte, then +# powers the stick off so it can be pulled. Everything on the stick is erased. +# +# On Windows or macOS: balenaEtcher (https://etcher.balena.io) writes and verifies an image; +# on Windows, Rufus in "DD Image" mode does too. +set -euo pipefail +PATH="$PATH:/usr/sbin:/sbin" + +die() { echo "write_stick: $*" >&2; exit 1; } + +[ $# -eq 2 ] || die "usage: scripts/write_stick.sh IMAGE DEVICE (e.g. /dev/sdb; see lsblk)" +IMG="$1" DEV="$2" +[ -f "$IMG" ] || die "no image at $IMG -- build it first: make boot_image TARGET=PC" +[ -b "$DEV" ] || die "$DEV is not a block device (lsblk lists the disks)" + +name=$(basename "$DEV") +[ "$(lsblk -dno TYPE "$DEV")" = disk ] || die "$DEV is a partition, not a whole disk: give the disk (e.g. /dev/sdb, not /dev/sdb1)" +[ "$(lsblk -dno RM "$DEV" | tr -d ' ')" = 1 ] || die "$DEV is not a removable disk -- refusing" +[ "$(lsblk -dno TRAN "$DEV" | tr -d ' ')" = usb ] || die "$DEV is not a USB disk -- refusing" +root_src=$(findmnt -no SOURCE / || true) +if [ -n "$root_src" ] && lsblk -no PKNAME "$root_src" 2>/dev/null | grep -qx "$name"; then + die "$DEV holds the running system -- refusing" +fi +img_bytes=$(stat -c %s "$IMG") +dev_bytes=$(lsblk -bdno SIZE "$DEV") +[ "$dev_bytes" -ge "$img_bytes" ] || die "$DEV ($dev_bytes bytes) is smaller than the image ($img_bytes bytes)" +mounted=$(lsblk -nro NAME,MOUNTPOINTS "$DEV" | awk 'NF > 1 {print "/dev/" $1}') +if [ -n "$mounted" ]; then + echo "write_stick: $DEV has mounted partitions. Unmount them first:" >&2 + for p in $mounted; do echo " udisksctl unmount -b $p" >&2; done + exit 1 +fi + +echo "About to ERASE this whole stick and write the StarshipOS image to it:" +lsblk -o NAME,SIZE,TRAN,MODEL,VENDOR,FSTYPE,LABEL "$DEV" +echo +echo " image: $IMG ($img_bytes bytes)" +read -r -p "Type the device name ($DEV) to go on, anything else to stop: " answer +[ "$answer" = "$DEV" ] || die "stopped; nothing was written" + +echo "== writing (sudo asks for your password)" +sudo dd if="$IMG" of="$DEV" bs=4M conv=fsync status=progress +echo "== flushing to the stick" +sudo sync +sudo blockdev --flushbufs "$DEV" +echo "== reading the stick back and comparing it with the image" +if sudo cmp -n "$img_bytes" "$IMG" "$DEV"; then + echo "== the stick matches the image" +else + die "the stick does NOT match the image -- do not boot it; try again, or another stick" +fi +if command -v udisksctl >/dev/null 2>&1 && udisksctl power-off -b "$DEV" >/dev/null 2>&1; then + echo "== done: the stick is powered off and can be pulled" +else + echo "== done: the stick can be pulled" +fi +echo "Boot it: Secure Boot off in the firmware setup, then choose the stick in the boot menu."