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