]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
x86: onie-installer: wire up sysupgrade via ONIE install mode 23062/head
authorKeno Fischer <keno@juliahub.com>
Sat, 25 Apr 2026 11:07:31 +0000 (11:07 +0000)
committerJonas Jelonek <jelonek.jonas@gmail.com>
Sat, 23 May 2026 15:23:54 +0000 (17:23 +0200)
This adds support for sysupgrade on ONIE-installed systems.
The install is chained through ONIE (using the ONIE installer image),
rather than attempting to manually upgrade the partition.
The idea is to allow future OpenWRT installs flexibility to use
a different partition table. By putting the installer in charge
of setting up the file system partition, the upgrade process needs
to have no knowledge of the internals of the image.

Config preservation is accomplished by appending the sysupgrade .tar.gz
to the ONIE installer image. Of course this also works for a clean
install using a sysupgrade.tar.gz created via `sysupgrade -b`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Keno Fischer <keno@juliahub.com>
Link: https://github.com/openwrt/openwrt/pull/23062
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
config/Config-images.in
target/linux/x86/base-files/lib/upgrade/platform.sh
target/linux/x86/image/Makefile
target/linux/x86/image/onie-install.sh.in

index d683e9512e542b3da8b4f872b397f228dc1ee750..6b9901b65d0766ccac3ca7fe54da27c2b9b695f8 100644 (file)
@@ -313,6 +313,7 @@ menu "Target Images"
                bool "Build ONIE installer image (self-extracting .bin)"
                depends on TARGET_x86
                depends on GRUB_EFI_IMAGES
+               select PACKAGE_grub2-editenv
                help
                  Build a self-extracting ONIE installer .bin for installing
                  OpenWrt on switches that ship with the Open Network Install
index 5dad7a538a597bfb5fd77874f5c2da174c661318..4d4706991480bfe393fc07f99e487a7258a91a4a 100644 (file)
@@ -1,9 +1,101 @@
-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) ;;
                *)
@@ -39,6 +131,9 @@ platform_check_image() {
 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
@@ -71,6 +166,11 @@ platform_do_bootloader_upgrade() {
 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
index d99b9e6e350baf07abbdc47b5751f6d9325e4e24..87256c884d34775a009cdd6c17be539493c4f12b 100644 (file)
@@ -97,7 +97,10 @@ define Build/onie-installer
                -e 's#@TIMEOUT@#$(GRUB_TIMEOUT)#g' \
                ./onie-install.sh.in > $@.onie/installer.sh
        script_len=$$(wc -c < $@.onie/installer.sh | tr -d ' '); \
-       sed -i "s/__PYLOAD__/$$(printf '%010d' $$script_len)/" $@.onie/installer.sh
+       payload_len=$$(wc -c < $@.onie/payload.tar | tr -d ' '); \
+       total_len=$$(( script_len + payload_len )); \
+       sed -i "s/__PYLOAD__/$$(printf '%010d' $$script_len)/" $@.onie/installer.sh; \
+       sed -i "s/__TOTLEN__/$$(printf '%010d' $$total_len)/" $@.onie/installer.sh
        cat $@.onie/installer.sh $@.onie/payload.tar > $@
        chmod +x $@
 endef
index cb22f0de6798c8195bc03bee5c2a36223f5e666e..d79e7ac5a2d4fab445684be0291326f21eb26280 100644 (file)
 
 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@'
@@ -69,7 +80,17 @@ openwrt_mnt=$(mktemp -d)
 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; }
 
@@ -103,6 +124,13 @@ echo "Installing kernel..."
 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
@@ -153,12 +181,21 @@ cat > "$openwrt_mnt/boot/grub/grub.cfg" <<EOF
 $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
 }