From 5d08facafed764e39a743db32ed9dd5c9f8e58f8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 2 Aug 2026 13:24:02 -0500 Subject: [PATCH] firmware: do not reboot into a half-applied major upgrade Two spots treated destructive upgrade stages as if they cannot fail: upgrade.sh checked for a pending kernel with 'opnsense-update -K -c', installed it, discarded the result and rebooted no matter what. The kernel install moves /boot/kernel out of the way, consumes the pending marker and extracts the new kernel in place, so a failed extraction (disk full, I/O error) leaves no bootable default kernel and nothing to retry. Rebooting at that point strands the machine at the loader prompt, which on a remote firewall means a console visit. Now the reboot only happens when no kernel was pending or the apply succeeded; a failed apply falls through to the existing abort path, which also clears the deferred sets so the boot-time hook will not pile base and packages on top. The early syshook looped K, B and P, rebooting after the first stage that reported success. Since a stage exits 1 both when nothing is pending and when the apply blows up, a broken kernel stage was indistinguishable from a no-op and the loop happily applied base or packages on top of it and rebooted. Keep exit 1 as "nothing pending" and treat any other failure as an aborted apply and stop. Older opnsense-update versions only exit 0 or 1, so behaviour there is unchanged; the companion opnsense/update change makes failed applies exit 2 (and restores the previous kernel). Co-Authored-By: Claude Fable 5 --- src/etc/rc.syshook.d/early/05-upgrade | 11 ++++++++++- src/opnsense/scripts/firmware/upgrade.sh | 8 ++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/etc/rc.syshook.d/early/05-upgrade b/src/etc/rc.syshook.d/early/05-upgrade index fdc508260f5..c75ec2fcfe2 100755 --- a/src/etc/rc.syshook.d/early/05-upgrade +++ b/src/etc/rc.syshook.d/early/05-upgrade @@ -3,8 +3,17 @@ # Perform major updates for STAGE in K B P; do - if opnsense-update -${STAGE}; then + opnsense-update -${STAGE} + STATUS=${?} + + if [ "${STATUS}" -eq 0 ]; then echo "Rebooting now." reboot + break + elif [ "${STATUS}" -ne 1 ]; then + # the stage was pending but could not be applied; the + # stages behind it must not run on top of the damage + echo "Upgrade stage ${STAGE} failed (${STATUS}), aborting." + break fi done diff --git a/src/opnsense/scripts/firmware/upgrade.sh b/src/opnsense/scripts/firmware/upgrade.sh index aea4e5733fe..149f4ef4424 100755 --- a/src/opnsense/scripts/firmware/upgrade.sh +++ b/src/opnsense/scripts/firmware/upgrade.sh @@ -31,11 +31,11 @@ REQUEST="UPGRADE" if output_cmd opnsense-update -u; then if output_cmd /usr/local/etc/rc.syshook upgrade; then - # pending kernel applies before reboot - if output_cmd opnsense-update -K -c; then - output_cmd opnsense-update -K + # pending kernel applies before reboot, but if that apply + # fails rebooting would only make matters worse + if ! output_cmd opnsense-update -K -c || output_cmd opnsense-update -K; then + output_reboot keep-log fi - output_reboot keep-log fi output_txt "The upgrade was aborted due to an error."