#!/bin/sh set -eu ### User-configurable vars IMG="./out/monok8s-dev.img.gz" DEFAULT_DISK="/dev/disk7" # whole disk, not a partition CONFIG_PART_SUFFIX="s1" # config partition after flashing ENV_SEARCH_DIR="./out" # optional; leave empty to use image dir MNT="/tmp/monok8s-usb-config.$$" cleanup() { if mount | grep -q "on ${MNT} "; then diskutil unmount "$MNT" >/dev/null 2>&1 || true fi rmdir "$MNT" >/dev/null 2>&1 || true } log() { echo "[*] $*" } warn() { echo "[!] $*" >&2 } die() { echo "[!] ERROR: $*" >&2 exit 1 } require_cmd() { command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" } wait_for_disk() { dev="$1" timeout="${2:-15}" i=0 while [ "$i" -lt "$timeout" ]; do [ -e "$dev" ] && return 0 i=$((i + 1)) sleep 1 done return 1 } wait_for_mountable_partition() { dev="$1" timeout="${2:-20}" i=0 while [ "$i" -lt "$timeout" ]; do if [ -e "$dev" ] && diskutil info "$dev" >/dev/null 2>&1; then return 0 fi i=$((i + 1)) sleep 1 done return 1 } ### Sanity checks require_cmd diskutil require_cmd gunzip require_cmd dd require_cmd sync require_cmd mount require_cmd find require_cmd cp require_cmd sed require_cmd stat require_cmd pv printf "disk ($DEFAULT_DISK): " read -r TARGET_DISK if [ -z "$TARGET_DISK" ]; then TARGET_DISK="$DEFAULT_DISK" fi TARGET_RAW_DISK="/dev/r$(basename "$TARGET_DISK")" TARGET_BASENAME="$(basename "$TARGET_DISK")" ### Derived vars TARGET_CONFIG_PART="/dev/${TARGET_BASENAME}${CONFIG_PART_SUFFIX}" IMG_DIR="$(cd "$(dirname "$IMG")" && pwd)" SEARCH_DIR="${ENV_SEARCH_DIR:-$IMG_DIR}" IMG_SIZE_BYTES="$(stat -f%z "$IMG")" [ -f "$IMG" ] || die "Image not found: $IMG" [ -b "$TARGET_DISK" ] || die "Target disk not found: $TARGET_DISK" [ "$(id -u)" -eq 0 ] || die "Run this script with sudo" case "$TARGET_DISK" in /dev/disk[0-9]*) ;; *) die "TARGET_DISK must look like /dev/diskN" ;; esac mkdir -p "$MNT" trap cleanup EXIT log "Image: $IMG" log "Compressed image size: $IMG_SIZE_BYTES bytes" log "Target disk: $TARGET_DISK" log "Raw target disk: $TARGET_RAW_DISK" log "Expected config partition after flash: $TARGET_CONFIG_PART" log "Target disk info:" diskutil info "$TARGET_DISK" || die "Unable to inspect target disk" echo printf "Type 'YES' to continue: " read -r CONFIRM [ "$CONFIRM" = "YES" ] || die "Aborted." log "Unmounting target disk..." diskutil unmountDisk force "$TARGET_DISK" || die "Failed to unmount $TARGET_DISK" log "Flashing image with progress..." pv -s "$IMG_SIZE_BYTES" "$IMG" \ | gunzip \ | dd of="$TARGET_RAW_DISK" bs=1m log "Syncing writes..." sync log "Ejecting and re-reading disk..." diskutil eject "$TARGET_DISK" >/dev/null 2>&1 || true sleep 2 log "Waiting for disk to come back..." if ! wait_for_disk "$TARGET_DISK" 15; then warn "Disk node did not reappear quickly. You may need to replug the drive." fi log "Disk layout after flashing:" diskutil list "$TARGET_DISK" || warn "Could not list flashed disk yet" ENV_FILES="$(find "$SEARCH_DIR" -maxdepth 1 -type f -name '*.env' | sort || true)" if [ -n "$ENV_FILES" ]; then log "Found env files:" echo "$ENV_FILES" | sed 's/^/ /' log "Waiting for config partition..." wait_for_mountable_partition "$TARGET_CONFIG_PART" 20 || die "Config partition not ready: $TARGET_CONFIG_PART" log "Mounting flashed config partition..." diskutil mount -mountPoint "$MNT" "$TARGET_CONFIG_PART" >/dev/null || die "Failed to mount $TARGET_CONFIG_PART" log "Copying env files to config partition..." echo "$ENV_FILES" | while IFS= read -r f; do [ -n "$f" ] || continue base="$(basename "$f")" cp "$f" "$MNT/$base" echo " copied: $base" done log "Syncing copied config..." sync log "Config partition contents:" ls -la "$MNT" || true log "Unmounting config partition..." diskutil unmount "$MNT" >/dev/null || warn "Unmount failed for $MNT" else log "No .env files found in $SEARCH_DIR" fi log "Done."