main
  1#!/usr/bin/env bash
  2# Flash an Alpine appliance to an SD card for Raspberry Pi.
  3# Usage: ./alpine/flash.sh <hostname> <device>
  4#
  5# Example: ./alpine/flash.sh athena /dev/sda
  6#
  7# This will:
  8#   1. Partition the SD card (256MB FAT32 boot + 512MB ext4 persist)
  9#   2. Copy boot files (kernel, initramfs, firmware, DTBs, U-Boot)
 10#   3. Optionally copy secrets from the running host
 11set -euo pipefail
 12
 13SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
 14HOST="${1:?Usage: $0 <hostname> <device>}"
 15DEVICE="${2:?Usage: $0 <hostname> <device>}"
 16BOOT_DIR="$SCRIPT_DIR/images/$HOST/boot"
 17
 18if [ ! -d "$BOOT_DIR" ]; then
 19    echo "Error: no boot image found for $HOST at $BOOT_DIR"
 20    echo "Run ./alpine/build.sh $HOST first."
 21    exit 1
 22fi
 23
 24if [ ! -b "$DEVICE" ]; then
 25    echo "Error: $DEVICE is not a block device"
 26    exit 1
 27fi
 28
 29# Safety check
 30echo "========================================"
 31echo "  WARNING: This will ERASE $DEVICE"
 32echo "========================================"
 33echo ""
 34echo "  Host:    $HOST"
 35echo "  Device:  $DEVICE"
 36lsblk "$DEVICE" 2>/dev/null || true
 37echo ""
 38read -rp "Are you sure? Type 'yes' to continue: " CONFIRM
 39[ "$CONFIRM" = "yes" ] || { echo "Aborted."; exit 0; }
 40
 41echo ""
 42echo "==> Unmounting existing partitions on $DEVICE..."
 43sudo umount "${DEVICE}"* 2>/dev/null || true
 44
 45echo "==> Partitioning $DEVICE..."
 46sudo sfdisk "$DEVICE" <<EOF
 47label: dos
 48size=256M, type=c, bootable
 49size=512M, type=83
 50EOF
 51
 52# Detect partition naming (sda1 vs mmcblk0p1)
 53if [[ "$DEVICE" == *"mmcblk"* ]] || [[ "$DEVICE" == *"nvme"* ]]; then
 54    PART1="${DEVICE}p1"
 55    PART2="${DEVICE}p2"
 56else
 57    PART1="${DEVICE}1"
 58    PART2="${DEVICE}2"
 59fi
 60
 61# Stop udiskie to prevent auto-mounting during flash
 62UDISKIE_WAS_RUNNING=0
 63if systemctl --user is-active --quiet udiskie 2>/dev/null; then
 64    systemctl --user stop udiskie
 65    UDISKIE_WAS_RUNNING=1
 66    echo "    (stopped udiskie service to prevent auto-mount)"
 67    sleep 1
 68elif pkill -x udiskie 2>/dev/null; then
 69    UDISKIE_WAS_RUNNING=1
 70    echo "    (stopped udiskie to prevent auto-mount)"
 71    sleep 1
 72fi
 73
 74sudo umount "${DEVICE}"* 2>/dev/null || true
 75
 76echo "==> Formatting partitions..."
 77sudo mkfs.vfat -n ALPINE "$PART1"
 78sudo mkfs.ext4 -L persist -q "$PART2"
 79
 80# Mount partitions
 81BOOT_MNT=$(mktemp -d)
 82PERSIST_MNT=$(mktemp -d)
 83trap 'sudo umount "$BOOT_MNT" 2>/dev/null; sudo umount "$PERSIST_MNT" 2>/dev/null; rmdir "$BOOT_MNT" "$PERSIST_MNT" 2>/dev/null; [ "$UDISKIE_WAS_RUNNING" -eq 1 ] && systemctl --user start udiskie 2>/dev/null || true' EXIT
 84
 85echo "==> Mounting partitions..."
 86sudo mount "$PART1" "$BOOT_MNT"
 87sudo mount "$PART2" "$PERSIST_MNT"
 88
 89echo "==> Copying boot files..."
 90sudo cp -v "$BOOT_DIR"/*.bin "$BOOT_MNT/" 2>/dev/null || true
 91sudo cp -v "$BOOT_DIR"/*.elf "$BOOT_MNT/" 2>/dev/null || true
 92sudo cp -v "$BOOT_DIR"/*.dat "$BOOT_MNT/" 2>/dev/null || true
 93sudo cp -v "$BOOT_DIR"/*.dtb "$BOOT_MNT/" 2>/dev/null || true
 94sudo cp -v "$BOOT_DIR"/vmlinuz-rpi "$BOOT_MNT/" 2>/dev/null || true
 95sudo cp -v "$BOOT_DIR"/initramfs "$BOOT_MNT/"
 96sudo cp -v "$BOOT_DIR"/config.txt "$BOOT_MNT/"
 97[ -d "$BOOT_DIR/overlays" ] && sudo cp -rv "$BOOT_DIR/overlays" "$BOOT_MNT/"
 98[ -d "$BOOT_DIR/extlinux" ] && sudo cp -rv "$BOOT_DIR/extlinux" "$BOOT_MNT/"
 99
100echo "==> Preparing persist partition..."
101sudo mkdir -p "$PERSIST_MNT/ssh"
102sudo mkdir -p "$PERSIST_MNT/wireguard"
103
104# Try to copy secrets from the running host
105echo ""
106echo "==> Fetching secrets from running $HOST..."
107FETCHED_SECRETS=0
108
109if ssh -o ConnectTimeout=5 "root@${HOST}.vpn" true 2>/dev/null; then
110    echo "    Connected to ${HOST}.vpn"
111
112    # SSH host key
113    if ssh "root@${HOST}.vpn" "cat /etc/ssh/ssh_host_ed25519_key" | sudo tee "$PERSIST_MNT/ssh/ssh_host_ed25519_key" >/dev/null 2>/dev/null; then
114        ssh "root@${HOST}.vpn" "cat /etc/ssh/ssh_host_ed25519_key.pub" | sudo tee "$PERSIST_MNT/ssh/ssh_host_ed25519_key.pub" >/dev/null 2>/dev/null
115        sudo chmod 600 "$PERSIST_MNT/ssh/ssh_host_ed25519_key"
116        echo "    ✓ SSH host key copied"
117        FETCHED_SECRETS=1
118    else
119        echo "    ✗ Could not fetch SSH host key"
120    fi
121
122    # WireGuard private key
123    if ssh "root@${HOST}.vpn" "cat /etc/wireguard/private.key" | sudo tee "$PERSIST_MNT/wireguard/private.key" >/dev/null 2>/dev/null; then
124        sudo chmod 600 "$PERSIST_MNT/wireguard/private.key"
125        echo "    ✓ WireGuard private key copied"
126        FETCHED_SECRETS=1
127    else
128        echo "    ✗ Could not fetch WireGuard private key"
129    fi
130else
131    echo "    Could not connect to ${HOST}.vpn"
132fi
133
134if [ "$FETCHED_SECRETS" -eq 0 ]; then
135    echo ""
136    echo "    ⚠ No secrets fetched. You'll need to manually copy:"
137    echo "      - SSH host key → $PERSIST_MNT/ssh/ssh_host_ed25519_key"
138    echo "      - WireGuard key → $PERSIST_MNT/wireguard/private.key"
139fi
140
141echo ""
142echo "==> Syncing and unmounting..."
143sync
144
145echo ""
146echo "========================================"
147echo "  ✓ SD card flashed for $HOST"
148echo "========================================"
149echo ""
150echo "  Boot partition:    $PART1 (FAT32, label=ALPINE)"
151echo "  Persist partition: $PART2 (ext4, label=persist)"
152echo ""
153echo "  Next steps:"
154echo "    1. Insert SD card into $HOST RPi"
155echo "    2. Power on"
156echo "    3. Verify: ssh root@${HOST}.vpn (or ssh root@<lan-ip>)"
157echo ""
158echo "  Keep the old NixOS SD card as rollback!"