-RAMFS_COPY_BIN='grub-bios-setup'
+RAMFS_COPY_BIN='grub-bios-setup grub-editenv'
+
+find_partname_dev() {
+ local partname="$1"
+ local uevent dev
+ for uevent in /sys/class/block/*/uevent; do
+ grep -q "^PARTNAME=$partname\$" "$uevent" 2>/dev/null || continue
+ dev=$(sed -n 's/^DEVNAME=//p' "$uevent")
+ [ -n "$dev" ] && { echo "/dev/$dev"; return 0; }
+ done
+ return 1
+}
+
+# True when the boot disk hosts ONIE: both ONIE-BOOT and OPENWRT-ROOT
+# GPT-labelled partitions present.
+is_onie_install() {
+ grep -q PARTNAME=ONIE-BOOT /sys/class/block/*/uevent 2>/dev/null || return 1
+ grep -q PARTNAME=OPENWRT-ROOT /sys/class/block/*/uevent 2>/dev/null || return 1
+ return 0
+}
+
+# Reinstall via ONIE: append the preserved-config tarball to the installer
+# image (installer detects it via size check), drop the combined file on
+# OPENWRT-ROOT as onie-installer-x86_64, and flip both grubenv files so the
+# next boot chainloads ONIE in install mode. ONIE re-runs our installer,
+# which recreates OPENWRT-ROOT, extracts the new rootfs, and stashes
+# /sysupgrade.tgz for OpenWrt's preinit to restore.
+platform_do_upgrade_onie() {
+ local image="$1"
+ local backup="$UPGRADE_BACKUP"
+
+ local root_dev onie_dev
+ root_dev=$(find_partname_dev OPENWRT-ROOT) || {
+ v "ONIE upgrade: OPENWRT-ROOT not found"; return 1;
+ }
+ onie_dev=$(find_partname_dev ONIE-BOOT) || {
+ v "ONIE upgrade: ONIE-BOOT not found"; return 1;
+ }
+
+ local root_mnt=/tmp/upgrade-root
+ local onie_mnt=/tmp/upgrade-onie
+ mkdir -p "$root_mnt" "$onie_mnt"
+
+ mount -t ext4 -o rw,noatime "$root_dev" "$root_mnt" || {
+ v "ONIE upgrade: mount $root_dev failed"
+ return 1
+ }
+
+ local bundle="$root_mnt/onie-installer-x86_64"
+ v "ONIE upgrade: writing bundle to $bundle"
+ if [ -s "$backup" ]; then
+ cat "$image" "$backup" > "$bundle"
+ else
+ cp "$image" "$bundle"
+ fi
+ chmod +x "$bundle"
+
+ # One-shot: OpenWrt's grub.cfg reads next_entry and chainloads ONIE.
+ grub-editenv "$root_mnt/boot/grub/grubenv" set next_entry=ONIE || {
+ v "ONIE upgrade: failed to set next_entry in OPENWRT-ROOT grubenv"
+ sync; umount "$root_mnt"
+ return 1
+ }
+ sync
+ umount "$root_mnt"
+
+ mount -o rw,noatime "$onie_dev" "$onie_mnt" || {
+ v "ONIE upgrade: mount $onie_dev failed"
+ return 1
+ }
+ grub-editenv "$onie_mnt/grub/grubenv" set onie_mode=install || {
+ v "ONIE upgrade: failed to set onie_mode in ONIE-BOOT grubenv"
+ sync; umount "$onie_mnt"
+ return 1
+ }
+ sync
+ umount "$onie_mnt"
+
+ v "ONIE upgrade: ready; stage2 will reboot"
+ return 0
+}
platform_check_image() {
local diskdev partdev diff
[ "$#" -gt 1 ] && return 1
+ if is_onie_install; then
+ [ "$(get_magic_word "$1")" = "2321" ] || {
+ v "Invalid image: expected ONIE installer (shell script)"
+ return 1
+ }
+ head -c 4096 "$1" | grep -q '^PAYLOAD_OFFSET=' || {
+ v "Invalid image: no PAYLOAD_OFFSET header"
+ return 1
+ }
+ return 0
+ fi
+
case "$(get_magic_word "$1")" in
eb48|eb63) ;;
*)
platform_copy_config() {
local partdev parttype=ext4
+ # ONIE upgrade bundles sysupgrade.tgz into the installer itself.
+ is_onie_install && return 0
+
if export_partdevice partdev 1; then
part_magic_fat "/dev/$partdev" && parttype=vfat
mount -t $parttype -o rw,noatime "/dev/$partdev" /mnt
platform_do_upgrade() {
local diskdev partdev diff
+ if is_onie_install; then
+ platform_do_upgrade_onie "$1"
+ return $?
+ fi
+
export_bootdevice && export_partdevice diskdev 0 || {
v "Unable to determine upgrade device"
return 1
set -e
-# 10-char zero-padded byte offset of the tar payload. Length-preserving
-# substitution: __PYLOAD__ (10 chars) -> "NNNNNNNNNN" at build time.
+# 10-char zero-padded byte offsets, substituted length-preserving at build:
+# __PYLOAD__ -> byte offset where payload.tar starts (== script size)
+# __TOTLEN__ -> total file size at build time (script + payload)
+# Sysupgrade appends the preserved-config tarball to the tail of the file;
+# the installer detects it by comparing actual file size to BUILD_SIZE.
PAYLOAD_OFFSET=__PYLOAD__
+BUILD_SIZE=__TOTLEN__
+# Strip leading zeros: shells (dash/ash) parse a leading 0 as octal,
+# which breaks on digits >=8. Parameter expansion keeps us clear of `expr`
+# (which returns exit 1 for "0" and trips `set -e`).
+PAYLOAD_OFFSET=${PAYLOAD_OFFSET#${PAYLOAD_OFFSET%%[!0]*}}
+BUILD_SIZE=${BUILD_SIZE#${BUILD_SIZE%%[!0]*}}
+: "${PAYLOAD_OFFSET:=0}"
+: "${BUILD_SIZE:=0}"
# Substituted by image.mk:
GRUB_CMDLINE='@CMDLINE@'
trap 'cd /; umount "$openwrt_mnt" 2>/dev/null; rm -rf "$payload_dir" "$openwrt_mnt"' EXIT
echo "Extracting installer payload..."
-dd if="$0" bs="$PAYLOAD_OFFSET" skip=1 2>/dev/null | tar -x -C "$payload_dir"
+actual_size=$(wc -c < "$0")
+if [ "$actual_size" -gt "$BUILD_SIZE" ]; then
+ # Sysupgrade has appended sysupgrade.tgz past the build-time tail.
+ payload_size=$(( BUILD_SIZE - PAYLOAD_OFFSET ))
+ tail -c "+$(( PAYLOAD_OFFSET + 1 ))" "$0" | head -c "$payload_size" \
+ | tar -x -C "$payload_dir"
+ tail -c "+$(( BUILD_SIZE + 1 ))" "$0" > "$payload_dir/sysupgrade.tgz"
+ echo "Preserved config : $(wc -c < "$payload_dir/sysupgrade.tgz") bytes"
+else
+ dd if="$0" bs="$PAYLOAD_OFFSET" skip=1 2>/dev/null | tar -x -C "$payload_dir"
+fi
[ -f "$payload_dir/rootfs.tar.gz" ] || { echo "ERROR: rootfs.tar.gz missing" >&2; exit 1; }
[ -f "$payload_dir/vmlinuz" ] || { echo "ERROR: vmlinuz missing" >&2; exit 1; }
mkdir -p "$openwrt_mnt/boot"
cp "$payload_dir/vmlinuz" "$openwrt_mnt/boot/vmlinuz"
+# Stash preserved config at rootfs root; OpenWrt's preinit
+# (80_mount_root) extracts it on first boot and /etc/init.d/done
+# removes it.
+if [ -f "$payload_dir/sysupgrade.tgz" ]; then
+ cp "$payload_dir/sysupgrade.tgz" "$openwrt_mnt/sysupgrade.tgz"
+fi
+
# ------------------------------------------------ install GRUB to ESP ---
if [ "$firmware" = "uefi" ]; then
uefi_part=0
$GRUB_SERIAL_CONFIG
$GRUB_TERMINAL_CONFIG
-set default="0"
-set timeout="$GRUB_TIMEOUT"
-
# GRUB finds this filesystem by ext4 label (set by mkfs.ext4 -L).
search --no-floppy --label --set=root $OPENWRT_VOLUME_LABEL
+# One-shot boot selector: sysupgrade writes next_entry=ONIE into
+# /boot/grub/grubenv to chainload ONIE (in install mode) on the next boot.
+load_env
+if [ -n "\$next_entry" ]; then
+ set default="\$next_entry"
+ set next_entry=
+ save_env next_entry
+else
+ set default="0"
+fi
+set timeout="$GRUB_TIMEOUT"
+
menuentry "$GRUB_TITLE" {
linux /boot/vmlinuz root=PARTUUID=$rootpart_partuuid rootwait $GRUB_CMDLINE noinitrd
}