#!/usr/bin/env bash # Build the boot-contract-v2 disk image (docs/boot-contract.md): ONE disk, # GPT, three partitions, each file system made as its own file first and # then copied into place (no root, no loop mounts): # # p1 ROOT_MIB Linux root (x86-64) ext2 mke2fs -d A: / # p2 DATA_MIB Microsoft basic data FAT32 mkfs.vfat+mtools B: /mnt/disk0 # p3 SHARE_MIB Microsoft basic data exFAT mkfs.exfat + scripts/exfat.py # C: /mnt/share # # Rustbelt 0.21 mounts the first Linux-root partition holding ext2 at /, # the first basic-data exFAT partition at /mnt/share, and numbers the # others /mnt/disk0, /mnt/disk1, ... -- so the FAT32 one is /mnt/disk0. # # Usage: scripts/mkimage-v2.sh STAGING IMAGE "B:/bin order..." # STAGING/root, STAGING/data, STAGING/share: the three trees. set -euo pipefail STAGING=$1 IMAGE=$2 ORDER=$3 ROOT_MIB=${ROOT_MIB:-32} DATA_MIB=${DATA_MIB:-40} SHARE_MIB=${SHARE_MIB:-16} PATH=$PATH:/usr/sbin:/sbin export MTOOLS_SKIP_CHECK=1 E2FSPROGS_FAKE_TIME=1767225600 # 2026-01-01, for repeatable ext2 times HERE=$(cd "$(dirname "$0")" && pwd) MIB=2048 # sectors per MiB P1=$MIB P2=$((P1 + ROOT_MIB * MIB)) P3=$((P2 + DATA_MIB * MIB)) END=$((P3 + SHARE_MIB * MIB)) # then 1 MiB for the backup GPT rm -f "$IMAGE" "$IMAGE".p? # p1: ext2. -b 1024 like the kernel's own test root; -m 0: no blocks # reserved for root; root_owner: files owned by root, not by whoever builds. mke2fs -q -t ext2 -b 1024 -m 0 -L foundry-root -U 466f756e-6472-794f-5300-726f6f740001 \ -E root_owner=0:0,hash_seed=466f756e-6472-794f-5300-726f6f740002 \ -d "$STAGING/root" "$IMAGE.p1" $((ROOT_MIB * 1024)) # mke2fs -d copies each file's owner from the build machine (root_owner only # covers the top directory), so everything would belong to whoever ran # `make` (uid 1000 here). Make every file root's (uid 0, gid 0), and give # /home/tyler to tyler (1000:1000, as in /etc/passwd), with debugfs. ( cd "$STAGING/root" && find . -mindepth 1 | LC_ALL=C sort | sed 's|^\.||' | while read -r f; do case $f in /home/tyler) u=1000 ;; *) u=0 ;; esac echo "sif $f uid $u"; echo "sif $f gid $u" done ) > "$IMAGE.owners" debugfs -w -f "$IMAGE.owners" "$IMAGE.p1" > /dev/null 2>&1 rm -f "$IMAGE.owners" # p2: FAT32, filled with mtools: the top level, then bin/ in ORDER. mkfs.vfat -F 32 -s 1 -n FOUNDRY -i 466f6e64 -C "$IMAGE.p2" $((DATA_MIB * 1024)) >/dev/null for entry in $(cd "$STAGING/data" && ls -A | LC_ALL=C sort); do [ "$entry" = bin ] && continue mcopy -s -i "$IMAGE.p2" "$STAGING/data/$entry" ::/ done mmd -i "$IMAGE.p2" ::/bin for p in $ORDER; do [ -f "$STAGING/data/bin/$p" ] || { echo "[mkimage-v2] missing $STAGING/data/bin/$p"; exit 1; } mcopy -i "$IMAGE.p2" "$STAGING/data/bin/$p" "::/bin/$p" done # p3: exFAT, one FAT (two would be TexFAT, which Rustbelt mounts read-only). truncate -s $((SHARE_MIB * 1024 * 1024)) "$IMAGE.p3" mkfs.exfat -q -L SHARE "$IMAGE.p3" >/dev/null python3 "$HERE/exfat.py" "$IMAGE.p3" put "$STAGING/share" fsck.exfat -n "$IMAGE.p3" >/dev/null || { echo "[mkimage-v2] fsck.exfat failed on the share"; exit 1; } # The disk: zeros + GPT, then the three file systems. truncate -s $(((END + MIB) * 512)) "$IMAGE" sfdisk --quiet "$IMAGE" <