penguin/monok8s

k8s image for Mono Gateway Dev Kit

macos/flashusb.sh

raw ยท 6988 bytes

#!/bin/sh
set -eu

### User-configurable vars
IMG_DIR="./out"
CONFIG_PART_SUFFIX="s1"           # config partition after flashing
ENV_SEARCH_DIR="./out"            # optional; leave empty to use selected 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
}

select_image() {
    dir="$1"

    [ -d "$dir" ] || die "Image directory not found: $dir"

    OLD_IFS="${IFS}"
    IFS='
'
    set -- $(find "$dir" -maxdepth 1 -type f -name '*.img.gz' | sort)
    IFS="${OLD_IFS}"

    [ "$#" -gt 0 ] || die "No .img.gz images found in $dir"

    echo "Available images:"
    n=1
    for img in "$@"; do
        size="$(stat -f%z "$img" 2>/dev/null || echo "?")"
        echo "  [$n] $(basename "$img") (${size} bytes)"
        n=$((n + 1))
    done

    echo
    printf "Select image number: "
    read -r choice

    case "$choice" in
        ''|*[!0-9]*)
            die "Invalid selection: $choice"
            ;;
    esac

    n=1
    for img in "$@"; do
        if [ "$n" -eq "$choice" ]; then
            SELECTED_IMG="$img"
            return 0
        fi
        n=$((n + 1))
    done

    die "Selection out of range: $choice"
}

list_candidate_disks() {
    found=0
    for dev in /dev/disk*; do
        base="$(basename "$dev")"

        case "$base" in
            disk[0-9])
                ;;
            *)
                continue
                ;;
        esac

        info="$(diskutil list "$dev" 2>/dev/null || true)"
        [ -n "$info" ] || continue

        # Skip Apple disks
        echo "$info" | grep -q "APFS" && continue

        info="$(diskutil info "$dev" 2>/dev/null || true)"
        found=1

        media_name="$(echo "$info" | sed -n 's/^ *Device \/ Media Name: *//p' | head -n1)"
        protocol="$(echo "$info" | sed -n 's/^ *Protocol: *//p' | head -n1)"
        size="$(echo "$info" | sed -n 's/^ *Disk Size: *//p' | head -n1)"

        echo "$dev|$media_name|$protocol|$size"
    done

    [ "$found" -eq 1 ] || return 1
    return 0
}

select_disk() {
    candidates="$(list_candidate_disks || true)"

    if [ -z "$candidates" ]; then
        warn "No obvious external candidate disks found."
        printf "Enter target disk manually (example: /dev/disk7): "
        read -r TARGET_DISK
        [ -n "$TARGET_DISK" ] || die "No disk entered"
        return 0
    fi

    echo "Candidate target disks:"
    n=1
    echo "$candidates" | while IFS='|' read -r dev media protocol size; do
        echo "  [$n] $dev - $media - $protocol - $size"
        n=$((n + 1))
    done

    echo
    printf "Select disk number (or type a full /dev/diskN path): "
    read -r choice

    case "$choice" in
        /dev/disk[0-9]*)
            TARGET_DISK="$choice"
            return 0
            ;;
        ''|*[!0-9]*)
            die "Invalid selection: $choice"
            ;;
    esac

    n=1
    OLD_IFS="${IFS}"
    IFS='
'
    for line in $candidates; do
        if [ "$n" -eq "$choice" ]; then
            TARGET_DISK="$(echo "$line" | cut -d'|' -f1)"
            IFS="${OLD_IFS}"
            return 0
        fi
        n=$((n + 1))
    done
    IFS="${OLD_IFS}"

    die "Selection out of range: $choice"
}

### 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
require_cmd cut
require_cmd grep
require_cmd head

select_image "$IMG_DIR"
IMG="$SELECTED_IMG"

select_disk

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_ABS="$(cd "$(dirname "$IMG")" && pwd)"
SEARCH_DIR="${ENV_SEARCH_DIR:-$IMG_DIR_ABS}"

[ -f "$IMG" ] || die "Image not found: $IMG"
IMG_SIZE_BYTES="$(stat -f%z "$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=4m

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."