#!/usr/bin/env bash # Update the running GL-MT6000 over SSH without reflashing. # # ./deploy-mt6000.sh build mt6000.scm, copy, switch, reboot # ./deploy-mt6000.sh --no-reboot ... activate only (the new kernel and # services start on the next boot) # ./deploy-mt6000.sh switch N make existing generation N the default # (roll back), activate, reboot # ./deploy-mt6000.sh list show the router's system generations # # As deploy-rpi.sh: the system is cross-built here and sent with `guix copy'. # GL's U-Boot boots one FIT from the `kernel' partition (p6), with no menu, # so switching to a generation means writing its FIT (scripts/mt6000-fit.sh, # built here) to p6. If a new generation doesn't come up, roll back from # ssh if it gets that far, else through U-Boot's TFTP recovery # (docs/PITFALLS.md #51) -- an interrupted p6 write needs that too. # # Needs: ~/.ssh/config entry for $ROUTER, /etc/guix/signing-key.* on this # host, and that key in the router's guix-service-type authorized-keys # (mt6000.scm). set -euo pipefail cd "$(dirname "$0")" export PATH=$HOME/.local/bin:$PATH export GUIX_PACKAGE_PATH="$PWD/packages:$HOME/channels/nonguix" ROUTER=${ROUTER:-mt6000} PROFILES=/var/guix/profiles KERNEL_PART=/dev/mmcblk0p6 # -n: never read stdin (it would swallow `while read` loop input). router() { ssh -n -o BatchMode=yes "$ROUTER" "$@"; } # "N /gnu/store/…-system" for each generation, ascending. generations() { router "cd $PROFILES && for l in system-*-link; do n=\${l#system-}; echo \"\${n%-link} \$(readlink \$l)\"; done" | sort -n } current() { router "readlink -f $PROFILES/system"; } # Write the FIT for system $1 to p6 and check it. The system's kernel and # initrd must be in this host's store (they are: it built them). install_fit() { local sys=$1 tmp sum [ -d "$sys" ] || guix build "$sys" >/dev/null tmp=$(mktemp -d) scripts/mt6000-fit.sh "$sys" "$tmp/boot.itb" ssh -o BatchMode=yes "$ROUTER" "dd of=$KERNEL_PART bs=64k conv=fsync 2>/dev/null" < "$tmp/boot.itb" sum=$(router "head -c $(stat -c %s "$tmp/boot.itb") $KERNEL_PART | sha256sum" | cut -c1-64) if [ "$sum" != "$(sha256sum < "$tmp/boot.itb" | cut -c1-64)" ]; then rm -rf "$tmp" echo "FIT on $KERNEL_PART does not match: rewrite it before rebooting" >&2 exit 1 fi rm -rf "$tmp" echo "FIT written to $KERNEL_PART" } # Point the system profile at generation $1, write its FIT, activate it. switch_to() { local n=$1 path path=$(router "readlink $PROFILES/system-$n-link") install_fit "$path" router "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. router "GUIX_NEW_SYSTEM=$path $path/activate" || echo "warning: activation reported errors" >&2 } reboot_router() { echo "rebooting $ROUTER" router "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-mt6000.sh switch N} switch_to "$n" [ "${3:-}" = --no-reboot ] || reboot_router ;; deploy|--no-reboot) sys=$(guix system build --target=aarch64-linux-gnu mt6000.scm | tail -1) echo "built $sys" if [ "$sys" = "$(current)" ]; then echo "already running $sys"; exit 0; fi guix copy --to="$ROUTER" "$sys" last=$(generations | tail -1 | cut -d' ' -f1) n=$((last + 1)) router "ln -sfn $sys $PROFILES/system-$n-link" echo "registered generation $n" switch_to "$n" [ "${1:-deploy}" = --no-reboot ] || reboot_router ;; *) sed -n '2,10p' "$0"; exit 1 ;; esac