- chronyd (makestep 1 -1) from a shepherd service + chrony account; the Pi has no RTC. ntp doesn't cross-build, openntpd won't step late. - wireless-regdb-signed: upstream regulatory.db + .p7s, so the kernel accepts it (country US instead of world 00). - lan-eee-off one-shot: EEE between the Pi's PHY and the workstation's e1000e dropped ~90% of LAN frames. - Drop console-font service (setfont fails on the Pi framebuffer). - deploy-rpi.sh: ssh -n so the generation loop sees every generation. - openwrt.scm: missing (guix git-download) import. Generation 6 verified after a cold boot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
3.8 KiB
Bash
Executable File
99 lines
3.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Update the running Pi over SSH without reflashing.
|
|
#
|
|
# ./deploy-rpi.sh build rpi4.scm, copy, switch, reboot
|
|
# ./deploy-rpi.sh --no-reboot ... activate only (services restart on reboot)
|
|
# ./deploy-rpi.sh switch N make existing generation N the default
|
|
# (roll back), activate, reboot
|
|
# ./deploy-rpi.sh list show the Pi's system generations
|
|
#
|
|
# The system is cross-built here (--target, like the image) and sent with
|
|
# `guix copy'; extlinux.conf is generated here too (scripts/extlinux-conf.scm),
|
|
# because `guix system reconfigure/switch-generation' on the Pi would need
|
|
# native aarch64 builds (PITFALLS #28). Old generations stay in the U-Boot
|
|
# menu: pick one from the console if a new one doesn't boot.
|
|
#
|
|
# Needs: ~/.ssh/config entry for $PI, /etc/guix/signing-key.* on this host,
|
|
# and that key in the Pi's guix-service-type authorized-keys (rpi4.scm).
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
export PATH=$HOME/.local/bin:$PATH
|
|
export GUIX_PACKAGE_PATH="$PWD/packages:$HOME/channels/nonguix"
|
|
PI=${PI:-pirouter}
|
|
PROFILES=/var/guix/profiles
|
|
|
|
# -n: never read stdin (it would swallow `while read` loop input).
|
|
pi() { ssh -n -o BatchMode=yes "$PI" "$@"; }
|
|
|
|
# "N /gnu/store/…-system" for each generation, ascending.
|
|
generations() {
|
|
pi "cd $PROFILES && for l in system-*-link; do n=\${l#system-}; echo \"\${n%-link} \$(readlink \$l)\"; done" |
|
|
sort -n
|
|
}
|
|
|
|
current() { pi "readlink -f $PROFILES/system"; }
|
|
|
|
# Write extlinux.conf with generation $1 (store path) as default and every
|
|
# other generation as fallback entries, newest first; install it on the Pi.
|
|
install_bootcfg() {
|
|
local default=$1 tmp params=() n path conf
|
|
tmp=$(mktemp -d)
|
|
pi "cat $default/parameters" > "$tmp/default"
|
|
while read -r n path; do
|
|
[ "$path" = "$default" ] && continue
|
|
pi "cat $path/parameters" > "$tmp/$n"
|
|
params=("$path" "$tmp/$n" "${params[@]}") # newest first
|
|
done < <(generations)
|
|
conf=$(guix repl -L packages -L "$HOME/channels/nonguix" -- \
|
|
scripts/extlinux-conf.scm rpi4.scm \
|
|
"$default" "$tmp/default" "${params[@]}" | tail -1)
|
|
rm -rf "$tmp"
|
|
[ -f "$conf" ] || { echo "extlinux.conf generation failed" >&2; exit 1; }
|
|
scp -q -o BatchMode=yes "$conf" "$PI:/boot/extlinux/extlinux.conf.new"
|
|
pi "mv /boot/extlinux/extlinux.conf.new /boot/extlinux/extlinux.conf && sync"
|
|
echo "extlinux.conf: default $default, $(( ${#params[@]} / 2 )) fallback(s)"
|
|
}
|
|
|
|
# Point the system profile at generation $1, activate it.
|
|
switch_to() {
|
|
local n=$1 path
|
|
path=$(pi "readlink $PROFILES/system-$n-link")
|
|
install_bootcfg "$path"
|
|
pi "ln -sfn system-$n-link $PROFILES/system.new && mv -T $PROFILES/system.new $PROFILES/system"
|
|
echo "activating generation $n ($path)"
|
|
# activate.scm picks the system from GUIX_NEW_SYSTEM (set by reconfigure
|
|
# and at boot); without it, it re-activates /run/current-system.
|
|
pi "GUIX_NEW_SYSTEM=$path $path/activate" ||
|
|
echo "warning: activation reported errors" >&2
|
|
}
|
|
|
|
reboot_pi() {
|
|
echo "rebooting $PI"
|
|
pi "sync; (sleep 1; reboot) >/dev/null 2>&1 &" || true
|
|
}
|
|
|
|
case "${1:-deploy}" in
|
|
list)
|
|
cur=$(current)
|
|
generations | while read -r n path; do
|
|
echo "$n $path$( [ "$path" = "$cur" ] && echo ' (current)')"
|
|
done ;;
|
|
switch)
|
|
n=${2:?usage: deploy-rpi.sh switch N}
|
|
switch_to "$n"
|
|
[ "${3:-}" = --no-reboot ] || reboot_pi ;;
|
|
deploy|--no-reboot)
|
|
sys=$(guix system build --target=aarch64-linux-gnu rpi4.scm | tail -1)
|
|
echo "built $sys"
|
|
if [ "$sys" = "$(current)" ]; then echo "already running $sys"; exit 0; fi
|
|
guix copy --to="$PI" "$sys"
|
|
last=$(generations | tail -1 | cut -d' ' -f1)
|
|
n=$((last + 1))
|
|
pi "ln -sfn $sys $PROFILES/system-$n-link"
|
|
echo "registered generation $n"
|
|
switch_to "$n"
|
|
[ "${1:-deploy}" = --no-reboot ] || reboot_pi ;;
|
|
*)
|
|
sed -n '2,10p' "$0"; exit 1 ;;
|
|
esac
|