#!/usr/bin/env bash # # scripts/mksystem.sh — assemble the single dd-able rs-dos system image # # One FAT32 image holds EVERYTHING (boot medium and data medium are the # same disk, mounted by the kernel at /disk//): # # \EFI\BOOT\BOOTX64.EFI the kernel — the firmware boots it directly # \bzImage kernel copy (rsbl's expected path, real hw) # \initramfs.cpio.gz empty initramfs (rsbl requires the path) # \bin\cli\*.efi terminal binaries (invoked by the shell) # \bin\gui\*.efi windowed binaries (launched with no shell) # \etc\motd.txt seed config # \*.txt seed files (seed/) # # Freshness: an existing image is only rebuilt when a kernel/binary/seed # file is newer than it, so files written at runtime survive across # boots. --force always re-creates (wipes runtime data). # # Usage: # ./scripts/mksystem.sh [options] [output.img] default: $SYSTEM_IMG # # Options: # --force rebuild even if the image is up to date # --size NM image size in MB (default 64) # --label NAME FAT volume label (default RSDOS) # --gpt wrap the FAT volume in a GPT partition table (for # dd-ing to real USB sticks whose firmware refuses # superfloppies) # --help show this help # # Needs: dosfstools (mkfs.fat), mtools (mcopy/mmd), cargo (kernel+bins # built via scripts/build.sh when missing). # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # Absolute self path: lib.sh cd's to the project root, so a relative $0 # would no longer resolve by the time --help prints the header. SELF="$SCRIPT_DIR/$(basename "$0")" # shellcheck source=lib.sh source "$SCRIPT_DIR/lib.sh" IMG="$SYSTEM_IMG" SIZE_MB=64 FORCE=0 GPT=0 LABEL="RSDOS" while [ $# -gt 0 ]; do case "$1" in --force | --fresh) FORCE=1 ;; --size) shift SIZE_MB="$1" ;; --label) shift LABEL="$1" ;; --gpt) GPT=1 ;; --help | -h) awk 'NR > 1 && /^#/ { sub(/^# ?/, ""); print; next } NR > 1 { exit }' "$SELF" exit 0 ;; -*) echo "mksystem: unknown option: $1" >&2 exit 1 ;; *) IMG="$1" ;; esac shift done SEED_DIR="$PROJECT_DIR/seed" # ---- checks --------------------------------------------------------------- for cmd in mkfs.fat mcopy mmd; do if ! command -v "$cmd" >/dev/null 2>&1; then echo "mksystem: $cmd not found (install dosfstools + mtools)" >&2 exit 1 fi done if [ "$GPT" -eq 1 ]; then for cmd in sfdisk truncate dd; do if ! command -v "$cmd" >/dev/null 2>&1; then echo "mksystem: --gpt needs $cmd" >&2 exit 1 fi done fi # ---- build (only when artifacts are missing; build.sh is the only cargo # invocation, so an already-compiled tree costs nothing) -------------------- if [ ! -f "$KERNEL" ] || ! ls "$OUT_DIR"/*.efi >/dev/null 2>&1; then "$SCRIPT_DIR/build.sh" fi # ---- freshness stamp ------------------------------------------------------ newest="" for f in "$OUT_DIR"/*.efi "$SEED_DIR"/*; do [ -f "$f" ] || continue if [ -z "$newest" ] || [ "$f" -nt "$newest" ]; then newest="$f" fi done if [ "$FORCE" -eq 0 ] && [ -f "$IMG" ] && { [ -z "$newest" ] || [ ! "$newest" -nt "$IMG" ]; }; then echo "mksystem: up to date: $IMG (use --force to re-create)" exit 0 fi # ---- create the FAT volume ------------------------------------------------ rm -f "$IMG" SIZE_BLOCKS=$((SIZE_MB * 1024)) # mkfs.fat counts 1024-byte blocks echo "mksystem: creating $IMG (${SIZE_MB}M FAT32, label $LABEL)" mkfs.fat -C -F 32 -n "$LABEL" "$IMG" "$SIZE_BLOCKS" >/dev/null echo "mksystem: staging system tree" mmd -i "$IMG" ::/EFI ::/EFI/BOOT ::/etc ::/bin ::/bin/cli ::/bin/gui # The firmware entry point and rsbl's kernel path are the same file. mcopy -i "$IMG" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI mcopy -i "$IMG" "$KERNEL" ::/bzImage INITRD="$(mktemp -t rsdos-initrd.XXXXXX)" trap 'rm -f "$INITRD"' EXIT printf '' | gzip >"$INITRD" mcopy -i "$IMG" "$INITRD" ::/initramfs.cpio.gz echo " \\EFI\\BOOT\\BOOTX64.EFI \\bzImage \\initramfs.cpio.gz" # External binaries: everything under bins/ was compiled into $OUT_DIR. # Launch surface decides the directory (ADR 0006): GUI_BINS -> /bin/gui, # everything else -> /bin/cli. for src in "$OUT_DIR"/*.efi; do [ -f "$src" ] || continue name="$(basename "$src" .efi)" [ "$name" = "rsdos" ] && continue # the kernel is not a /bin binary case " $GUI_BINS " in *" $name "*) kind="gui" ;; *) kind="cli" ;; esac mcopy -i "$IMG" "$src" "::/bin/$kind/$name.efi" printf ' \\bin\\%s\\%s.efi\n' "$kind" "$name" done # Seed files: motd lives in \etc, everything else at the volume root. if [ -d "$SEED_DIR" ]; then for f in "$SEED_DIR"/*; do [ -f "$f" ] || continue name="$(basename "$f")" if [ "$name" = "motd.txt" ]; then mcopy -i "$IMG" "$f" ::/etc/"$name" printf ' \\etc\\%s\n' "$name" else mcopy -i "$IMG" "$f" ::/"$name" printf ' \\%s\n' "$name" fi done fi # ---- optional GPT wrapper (real-hardware dd target) ----------------------- if [ "$GPT" -eq 1 ]; then GPT_START=2048 # 1 MiB offset (standard alignment) GPT_IMG="$(mktemp)" truncate -s "${SIZE_MB}M" "$GPT_IMG" dd if="$IMG" of="$GPT_IMG" bs=512 seek="$GPT_START" conv=notrunc 2>/dev/null FINAL="$(mktemp)" truncate -s "$((SIZE_MB + 2))M" "$FINAL" sfdisk --quiet "$FINAL" </dev/null rm -f "$GPT_IMG" mv "$FINAL" "$IMG" echo "mksystem: wrapped in GPT (partition at LBA $GPT_START)" fi echo "" echo "=== Done: $IMG ===" ls -lh "$IMG" echo "Boot with: ./scripts/run.sh (or: make run)" [ "$GPT" -eq 0 ] && echo "USB dd tip: add --gpt for firmwares that refuse superfloppies" exit 0