diff --git a/rebuild.sh b/rebuild.sh index a862465..c5b9e55 100755 --- a/rebuild.sh +++ b/rebuild.sh @@ -1,8 +1,15 @@ #!/usr/bin/env bash set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" IMG="$HOME/Documents/toes-image-builder/toes-ubuntu-base.img" USBBOOT="$HOME/apps/forks/usbboot" +PROVISION_SCRIPT="${PROVISION_SCRIPT:-$SCRIPT_DIR/scripts/provision-device.sh}" +PROVISION_DEVICE_ID="${PROVISION_DEVICE_ID:-${DEVICE_ID:-device-001}}" +DEFAULT_SERIAL_LOGIN="toes" +DEFAULT_SERIAL_PASSWORD="nicetrywiseguy" +REBOOT_SETTLE_SECS="${REBOOT_SETTLE_SECS:-45}" +SERIAL_BOOT_TIMEOUT_SECS="${SERIAL_BOOT_TIMEOUT_SECS:-180}" list_external_disks() { diskutil list external physical 2>/dev/null | awk '/^\/dev\/disk[0-9]+ / { sub("^/dev/", "", $1); print $1 }' | sort || true @@ -12,6 +19,145 @@ disk_bytes() { diskutil info "/dev/$1" | awk -F'[()]' '/Disk Size:/ { sub(/ Bytes.*/, "", $2); print $2; exit }' } +detect_serial_port() { + local pattern port + local -a matches + + matches=() + for pattern in /dev/cu.usbserial\* /dev/cu.usbmodem\*; do + for port in $pattern; do + [[ -e "$port" ]] || continue + matches+=("$port") + done + done + + if (( ${#matches[@]} == 1 )); then + printf '%s\n' "${matches[0]}" + return 0 + fi + + if (( ${#matches[@]} > 1 )); then + echo "multiple serial ports detected; set SERIAL_PORT to one of:" >&2 + printf ' %s\n' "${matches[@]}" >&2 + return 2 + fi + + matches=() + for pattern in /dev/tty.usbserial\* /dev/tty.usbmodem\*; do + for port in $pattern; do + [[ -e "$port" ]] || continue + matches+=("$port") + done + done + + if (( ${#matches[@]} == 1 )); then + printf '%s\n' "${matches[0]}" + return 0 + fi + + if (( ${#matches[@]} > 1 )); then + echo "multiple serial ports detected; set SERIAL_PORT to one of:" >&2 + printf ' %s\n' "${matches[@]}" >&2 + return 2 + fi + + return 1 +} + +resolve_serial_port() { + local port status + + if [[ -n "${SERIAL_PORT:-}" ]]; then + [[ -e "$SERIAL_PORT" ]] || { echo "serial port does not exist: $SERIAL_PORT" >&2; return 1; } + printf '%s\n' "$SERIAL_PORT" + return 0 + fi + + if port="$(detect_serial_port)"; then + printf '%s\n' "$port" + return 0 + fi + + status=$? + if (( status == 1 )); then + echo "no serial port detected; set SERIAL_PORT=/dev/cu.*" >&2 + fi + return 1 +} + +wait_for_serial_port() { + local timeout="$1" + local deadline=$((SECONDS + timeout)) + local port status + + if [[ -n "${SERIAL_PORT:-}" ]]; then + while (( SECONDS < deadline )); do + [[ -e "$SERIAL_PORT" ]] && { printf '%s\n' "$SERIAL_PORT"; return 0; } + sleep 1 + done + echo "timed out waiting for serial port: $SERIAL_PORT" >&2 + return 1 + fi + + while (( SECONDS < deadline )); do + if port="$(detect_serial_port 2>/dev/null)"; then + printf '%s\n' "$port" + return 0 + fi + + status=$? + if (( status == 2 )); then + detect_serial_port >/dev/null || true + return 1 + fi + + sleep 1 + done + + echo "timed out waiting for serial port; set SERIAL_PORT=/dev/cu.*" >&2 + return 1 +} + +stop_rpiboot_mass_storage() { + pkill -f 'rpiboot.*mass-storage-gadget' 2>/dev/null || true +} + +reboot_target_over_serial() { + local port="$1" + + echo "Requesting target reboot over $port" + stty -f "$port" 115200 raw -echo clocal 2>/dev/null || true + { + sleep 0.2 + printf '\r' + sleep 0.5 + printf 'root\r' + sleep 0.5 + printf '\r' + sleep 0.5 + printf 'sync\r' + sleep 0.5 + printf 'reboot -f\r' + } >"$port" +} + +provision_device() { + local port="$1" + local provision_login="${SERIAL_LOGIN:-$DEFAULT_SERIAL_LOGIN}" + local provision_password="${SERIAL_PASSWORD:-$DEFAULT_SERIAL_PASSWORD}" + local provision_prompt="${SERIAL_PROMPT:-${provision_login}@}" + + [[ -x "$PROVISION_SCRIPT" ]] || { echo "provision script is not executable: $PROVISION_SCRIPT" >&2; return 1; } + + echo "Provisioning $PROVISION_DEVICE_ID over $port" + SERIAL_PORT="$port" \ + SERIAL_LOGIN="$provision_login" \ + SERIAL_PASSWORD="$provision_password" \ + SERIAL_PROMPT="$provision_prompt" \ + SERIAL_LOGIN_TIMEOUT_SECS="${SERIAL_LOGIN_TIMEOUT_SECS:-$SERIAL_BOOT_TIMEOUT_SECS}" \ + "$PROVISION_SCRIPT" "$PROVISION_DEVICE_ID" +} + if [[ "${SKIP_BUILD:-0}" != 1 ]]; then ssh toes-image-builder 'cd toes-image-builder && ./build.sh && cp build/toes-ubuntu-base.img /mnt/utm/toes-image-builder/' fi @@ -64,3 +210,20 @@ diskutil unmountDisk "/dev/$target" sudo dd if="$IMG" of="/dev/r$target" bs=16m status=progress conv=fsync sync diskutil eject "/dev/$target" + +if [[ "${SKIP_REBOOT:-0}" != 1 ]]; then + reboot_port="$(resolve_serial_port)" + stop_rpiboot_mass_storage + reboot_target_over_serial "$reboot_port" + echo "Waiting ${REBOOT_SETTLE_SECS}s for target to start booting..." + sleep "$REBOOT_SETTLE_SECS" +else + echo "Skipping target reboot because SKIP_REBOOT=1" +fi + +if [[ "${SKIP_PROVISION:-0}" != 1 ]]; then + provision_port="$(wait_for_serial_port "$SERIAL_BOOT_TIMEOUT_SECS")" + provision_device "$provision_port" +else + echo "Skipping provisioning because SKIP_PROVISION=1" +fi