x86: onie-installer: wire up sysupgrade via ONIE install mode

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>
This commit is contained in:
Keno Fischer
2026-04-25 11:07:31 +00:00
committed by Jonas Jelonek
parent c7e8cffcb2
commit 9a143bf7ff
4 changed files with 149 additions and 8 deletions
+1
View 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
@@ -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
+4 -1
View 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
+43 -6
View File
@@ -14,9 +14,20 @@
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
}