summaryrefslogtreecommitdiff
path: root/f3s/pi-netbsd/bake
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-07-07 17:26:42 +0300
committerPaul Buetow <paul@buetow.org>2026-07-07 17:26:42 +0300
commit1aa5eb37c626f48bd725578637e5db4ae870ebfe (patch)
treeed0f2507a276947dc5cb9e285c8558873e4ea186 /f3s/pi-netbsd/bake
parent213c92a36424d55f7a4650851ceb45c8a2283926 (diff)
ychat: bump image to deacb3f (fix chat color picker / POST body parsing)
Diffstat (limited to 'f3s/pi-netbsd/bake')
-rwxr-xr-xf3s/pi-netbsd/bake/bake-golden.sh81
-rwxr-xr-xf3s/pi-netbsd/bake/config.exp41
-rwxr-xr-xf3s/pi-netbsd/bake/setup.sh69
3 files changed, 191 insertions, 0 deletions
diff --git a/f3s/pi-netbsd/bake/bake-golden.sh b/f3s/pi-netbsd/bake/bake-golden.sh
new file mode 100755
index 0000000..538a8a3
--- /dev/null
+++ b/f3s/pi-netbsd/bake/bake-golden.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+# Stage A orchestrator — run on `earth`. Produces netbsd-<piN>-golden.img.gz.
+# Usage: ./bake-golden.sh piN [workdir]
+# Requires: qemu-system-aarch64, qemu-img, edk2 AAVMF, expect, ~/.ssh/id_rsa.pub.
+# EDIT bake/setup.sh (HOSTNAME/IPADDR) for the target Pi BEFORE running this.
+set -euo pipefail
+
+PIN="${1:?usage: $0 piN [workdir]}"
+HERE="$(cd "$(dirname "$0")" && pwd)"
+WORK="${2:-$HOME/Downloads}"
+BASE="$WORK/NetBSD-10.1-evbarm-aarch64-arm64.img.gz"
+URL="https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.1/evbarm-aarch64/binary/gzimg/arm64.img.gz"
+CODE=/usr/share/AAVMF/AAVMF_CODE.fd
+VARSRC=/usr/share/AAVMF/AAVMF_VARS.fd
+PUBKEY="${SSHKEY_FILE:-$HOME/.ssh/id_rsa.pub}"
+
+command -v qemu-system-aarch64 >/dev/null || { echo "install qemu-system-aarch64"; exit 1; }
+[ -f "$CODE" ] && [ -f "$VARSRC" ] || { echo "install edk2 AAVMF firmware"; exit 1; }
+[ -f "$PUBKEY" ] || { echo "missing $PUBKEY"; exit 1; }
+mkdir -p "$WORK"
+
+echo "== base image =="
+[ -f "$BASE" ] || curl -fSL "$URL" -o "$BASE"
+gzip -t "$BASE"
+
+echo "== fresh work image + varstore =="
+IMG="$WORK/netbsd-$PIN-work.img"
+VARS="$WORK/AAVMF_VARS_$PIN.fd"
+gunzip -kc "$BASE" > "$IMG"
+cp "$VARSRC" "$VARS"
+
+echo "== per-host values from the piN argument =="
+case "$PIN" in
+ pi0) IPADDR=192.168.1.125 ;;
+ pi1) IPADDR=192.168.1.126 ;;
+ pi2) IPADDR=192.168.1.127 ;;
+ pi3) IPADDR=192.168.1.128 ;;
+ *) IPADDR="${IPADDR:?unknown $PIN — set IPADDR env}" ;;
+esac
+HOSTNAME_FQDN="$PIN.lan.buetow.org"
+echo " $PIN -> $HOSTNAME_FQDN / $IPADDR"
+
+echo "== render setup.sh (inject host/ip + key + generated password) into a served dir =="
+SEED="$WORK/seed-$PIN"; mkdir -p "$SEED"
+PW=$(openssl rand -base64 9 | tr -d '/+=' | cut -c1-12)
+printf '%s\n' "$PW" > "$WORK/$PIN-cred.txt"; chmod 600 "$WORK/$PIN-cred.txt"
+KEY=$(cat "$PUBKEY")
+# substitute host/ip lines + placeholders; awk keeps the multiline key intact
+awk -v key="$KEY" -v pw="$PW" -v host="$HOSTNAME_FQDN" -v ip="$IPADDR" '
+ /^HOSTNAME=/ { print "HOSTNAME=\"" host "\""; next }
+ /^IPADDR=/ { print "IPADDR=\"" ip "\""; next }
+ { gsub(/__SSHKEY__/,key); gsub(/__PW__/,pw); print }
+' "$HERE/setup.sh" > "$SEED/setup.sh"
+
+echo "== serve setup.sh on 127.0.0.1:8000 (guest reaches it at 10.0.2.2) =="
+python3 -m http.server 8000 --bind 127.0.0.1 --directory "$SEED" >"$WORK/httpd-$PIN.log" 2>&1 &
+HTTPD=$!
+trap 'kill "$HTTPD" 2>/dev/null || true' EXIT
+until grep -q . "$WORK/httpd-$PIN.log" 2>/dev/null || curl -fsS http://127.0.0.1:8000/setup.sh -o /dev/null 2>/dev/null; do sleep 0.3; done
+
+echo "== boot + configure via qemu/expect (TCG, ~4 min) =="
+LOG="$WORK/config-$PIN.log"; : > "$LOG"
+NBIMG="$IMG" NBVARS="$VARS" NBLOG="$LOG" NBHTTP="http://10.0.2.2:8000/setup.sh" \
+ LC_ALL=C LANG=C expect -f "$HERE/config.exp" || true
+
+echo "== verify markers =="
+if ! grep -qa 'SETUP_OK' "$LOG" || ! grep -qa 'halt: halted' "$LOG"; then
+ echo "!! bake did not complete cleanly; inspect $LOG"; exit 1
+fi
+echo "-- on-disk verification block --"
+sed -n '/^VBEGIN/,/^VEND/p' <(tr -d '\r' < "$LOG")
+
+echo "== gzip -> golden =="
+GOLDEN="$WORK/netbsd-$PIN-golden.img.gz"
+gzip -c "$IMG" > "$GOLDEN"
+gzip -t "$GOLDEN"
+echo
+echo "DONE: $GOLDEN"
+echo "sha256: $(sha256sum "$GOLDEN" | awk '{print $1}')"
+echo "backup password for paul/root: $PW (also in $WORK/$PIN-cred.txt)"
+echo "Next: scp \"$GOLDEN\" paul@$PIN.lan.buetow.org:/home/paul/netbsd-pi0-golden.img.gz (see README Stage B)"
diff --git a/f3s/pi-netbsd/bake/config.exp b/f3s/pi-netbsd/bake/config.exp
new file mode 100755
index 0000000..1a3fe61
--- /dev/null
+++ b/f3s/pi-netbsd/bake/config.exp
@@ -0,0 +1,41 @@
+#!/usr/bin/expect -f
+# Blind-drive the NetBSD-in-qemu console to fetch+run setup.sh, verify, sync,
+# and power off. Paths come from the environment (set by bake-golden.sh):
+# NBIMG - raw work image (edited in place, then gzipped to the golden)
+# NBVARS - writable AAVMF (edk2) varstore
+# NBLOG - session log file
+# NBHTTP - host-side URL of setup.sh (reachable from the guest as 10.0.2.2)
+#
+# Lessons baked in: virtio-rng (entropy, else boot stalls / no ssh host keys),
+# run under LC_ALL=C (Tcl chokes on the serial's control bytes), blind login
+# with fixed sleeps + a quote-trick shell probe (expect's LIVE matching over the
+# TCG serial is unreliable), and judge success by the &&-chained command's
+# markers in NBLOG rather than by intermediate console matches.
+set timeout 700
+log_file -a $env(NBLOG)
+
+spawn qemu-system-aarch64 -M virt -cpu cortex-a72 -m 2048 -smp 2 -accel tcg \
+ -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-device,rng=rng0 \
+ -drive if=pflash,format=raw,readonly=on,file=/usr/share/AAVMF/AAVMF_CODE.fd \
+ -drive if=pflash,format=raw,file=$env(NBVARS) \
+ -drive file=$env(NBIMG),format=raw,if=virtio,cache=writeback \
+ -nic user -display none -serial stdio -monitor none
+
+# Fully blind login (root has no password on the stock image). expect's LIVE
+# console matching over the TCG serial is unreliable, so we do NOT probe for a
+# prompt — we just type, with generous settle time, and let bake-golden.sh judge
+# success from the SETUP_OK / halt markers in NBLOG afterward. Sending root twice
+# is harmless: the first logs in, a second lands as "root: not found" at a shell.
+sleep 135
+send "\r"; sleep 2
+send "root\r"; sleep 6
+send "\r"; sleep 2
+send "root\r"; sleep 6
+
+# one &&-chained command: fetch -> setup -> verify dump -> sync -> poweroff.
+# setup.sh prints SETUP_OK; the VBEGIN..VEND block is the on-disk verification.
+set timeout 600
+send "ftp -o /tmp/setup.sh $env(NBHTTP) && sh /tmp/setup.sh && echo VBEGIN && id paul && grep '^paul:' /etc/master.passwd && grep -E '^(hostname|ifconfig_mue0|defaultroute|dhcpcd|sshd)=' /etc/rc.conf && cat /home/paul/.ssh/authorized_keys && echo VEND && sync && sync && halt -p\r"
+expect { eof {} timeout { puts "\n!!TIMEOUT no poweroff" } }
+puts "\n==SESSION ENDED=="
+exit 0
diff --git a/f3s/pi-netbsd/bake/setup.sh b/f3s/pi-netbsd/bake/setup.sh
new file mode 100755
index 0000000..dc63064
--- /dev/null
+++ b/f3s/pi-netbsd/bake/setup.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+# NetBSD headless image customization. Fetched over qemu user-net and run as
+# root INSIDE the NetBSD-in-qemu VM by config.exp during a bake.
+#
+# `bake-golden.sh` substitutes __SSHKEY__ and __PW__ before serving this file.
+# EDIT the two per-host values below for the target Pi (see README "Doing pi1").
+set -eu
+
+HOSTNAME="pi0.lan.buetow.org" # <-- EDIT for the target Pi (e.g. pi1.lan.buetow.org)
+IPADDR="192.168.1.125" # <-- EDIT for the target Pi (e.g. 192.168.1.126)
+GW="192.168.1.1"
+DNS="192.168.1.1"
+
+RC=/etc/rc.conf
+
+# 1) rc.conf: static networking + hostname + sshd; strip conflicting keys first.
+tmp=$(mktemp)
+grep -vE '^(hostname|ifconfig_mue0|defaultroute|dhcpcd|sshd)=' "$RC" > "$tmp"
+mv "$tmp" "$RC"
+cat >> "$RC" <<EOF
+hostname="$HOSTNAME"
+sshd=YES
+dhcpcd=NO
+ifconfig_mue0="inet $IPADDR netmask 0xffffff00"
+defaultroute="$GW"
+EOF
+
+# 2) resolv.conf
+echo "nameserver $DNS" > /etc/resolv.conf
+
+# 3) rc.local fallback: if the LAN78xx iface is not named mue0, put the static IP
+# on the first real ethernet interface so the box stays reachable.
+cat > /etc/rc.local <<EOF
+#!/bin/sh
+if ! ifconfig mue0 >/dev/null 2>&1; then
+ iface=\$(ifconfig -l | tr ' ' '\n' | grep -E '^(mue|ure|axe|cdce|vioif)[0-9]' | head -1)
+ if [ -n "\$iface" ]; then
+ ifconfig "\$iface" inet $IPADDR netmask 0xffffff00 up
+ route add default $GW
+ fi
+fi
+EOF
+chmod 0755 /etc/rc.local
+
+# 4) user paul (+wheel) + authorized key
+if ! id paul >/dev/null 2>&1; then useradd -m -G wheel -s /bin/sh paul; fi
+mkdir -p /home/paul/.ssh
+cat > /home/paul/.ssh/authorized_keys <<'EOF'
+__SSHKEY__
+EOF
+chmod 0700 /home/paul/.ssh
+chmod 0600 /home/paul/.ssh/authorized_keys
+chown -R paul:users /home/paul/.ssh
+
+# 5) passwords (native NetBSD argon2id hash), same for paul + root (backup login)
+H=$(pwhash '__PW__')
+usermod -p "$H" paul
+usermod -p "$H" root
+
+# 6) sshd: ensure key + password auth (appended lines win over commented defaults)
+cat >> /etc/ssh/sshd_config <<'EOF'
+PubkeyAuthentication yes
+PasswordAuthentication yes
+EOF
+
+rm -f /tmp/setup.sh
+# Flush all writes to the image before qemu is stopped (soft-dep FFS).
+sync; sync
+echo SETUP_OK