1
1

x86: add onie-installer image type

The current documentation for using OpenWRT on Mellanox Spectrum
switches (https://openwrt.org/toh/mellanox/spectrum) suggests
reflashing the entire harddrive from the recovery USB. This is not
the most friendly way to install a new OS on these switches. From
factory, they come with ONIE (Open Network Install Environment),
which is a linux-based preboot environment for fetching an OS
image from the network and installing it on disk. The installer
is a self-executing bash script that executes inside the ONIE
environment. The installer is expected to preserve the ONIE partition
for use as recovery environement. To be a better citizen on
these platforms, it would be preferrable to provide OpenWRT as
an ONIE-compatible installer.

This PR adds an ONIE_INSTALLER_IMAGES build option that produces
an ONIE compatible .bin. The generated .bin follows the ONIE demo
installer pattern [1]: it creates a new GPT partition
labelled OPENWRT-ROOT on the ONIE install device, formats ext4, extracts
the OpenWrt rootfs and kernel into it, installs GRUB into the existing
UEFI ESP under bootloader-id "OpenWrt", and adds a NVRAM boot entry via
efibootmgr.  ONIE-BOOT is preserved so ONIE rescue remains available.

Tested with the config at [2] on a Mellanox Spectrum SN3800 to produce
a booting OpenWRT install.

[1] https://github.com/opencomputeproject/onie/demo/installer/grub-arch/install.sh
[2] https://gist.github.com/Keno/abc8c5b72645e73fadd1ff0d9616b23d

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-22 14:10:37 +00:00 committed by Jonas Jelonek
parent 15593de376
commit c7e8cffcb2
No known key found for this signature in database
3 changed files with 230 additions and 0 deletions

View File

@ -309,6 +309,21 @@ menu "Target Images"
depends on GRUB_IMAGES || GRUB_EFI_IMAGES
select PACKAGE_kmod-e1000
config ONIE_INSTALLER_IMAGES
bool "Build ONIE installer image (self-extracting .bin)"
depends on TARGET_x86
depends on GRUB_EFI_IMAGES
help
Build a self-extracting ONIE installer .bin for installing
OpenWrt on switches that ship with the Open Network Install
Environment (e.g. Mellanox/NVIDIA Spectrum-based SN series).
The resulting .bin wraps a tar payload (rootfs + kernel) in a
shell script that follows the ONIE demo installer pattern:
a new GPT partition labelled OPENWRT-ROOT is created for
OpenWrt, and the existing ONIE-BOOT partition is preserved so
ONIE rescue remains bootable.
config TARGET_SERIAL
string "Serial port device"
depends on TARGET_x86 || TARGET_armsr || TARGET_loongarch64

View File

@ -79,6 +79,29 @@ define Build/grub-install
$@
endef
define Build/onie-installer
rm -rf $@.onie
mkdir -p $@.onie/payload
$(TAR) --numeric-owner --owner=0 --group=0 --sort=name \
$(if $(SOURCE_DATE_EPOCH),--mtime=@$(SOURCE_DATE_EPOCH)) \
-C $(TARGET_DIR)/ -cf - . | gzip -9n > $@.onie/payload/rootfs.tar.gz
$(CP) $(KDIR)/$(KERNEL_NAME) $@.onie/payload/vmlinuz
$(TAR) --numeric-owner --owner=0 --group=0 --sort=name \
$(if $(SOURCE_DATE_EPOCH),--mtime=@$(SOURCE_DATE_EPOCH)) \
-C $@.onie/payload -cf $@.onie/payload.tar .
sed \
-e 's#@SERIAL_CONFIG@#$(strip $(GRUB_SERIAL_CONFIG))#g' \
-e 's#@TERMINAL_CONFIG@#$(strip $(GRUB_TERMINAL_CONFIG))#g' \
-e 's#@CMDLINE@#$(strip $(BOOTOPTS) $(GRUB_CONSOLE_CMDLINE))#g' \
-e 's#@TITLE@#$(GRUB_TITLE)#g' \
-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
cat $@.onie/installer.sh $@.onie/payload.tar > $@
chmod +x $@
endef
define Build/iso
$(CP) $(KDIR)/$(KERNEL_NAME) $@.boot/boot/vmlinuz
cat \
@ -114,6 +137,7 @@ define Device/Default
IMAGE/combined-efi.vdi := grub-config efi | combined efi | grub-install efi | qemu-image vdi
IMAGE/combined-efi.vmdk := grub-config efi | combined efi | grub-install efi | qemu-image vmdk
IMAGE/combined-efi.vhdx := grub-config efi | combined efi | grub-install efi | qemu-image vhdx -o subformat=dynamic
ARTIFACT/onie-installer.bin := onie-installer
ifeq ($(CONFIG_TARGET_IMAGES_GZIP),y)
IMAGES-y := rootfs.img.gz
IMAGES-$$(CONFIG_GRUB_IMAGES) += combined.img.gz
@ -130,6 +154,7 @@ define Device/Default
ARTIFACTS-$$(CONFIG_GRUB_IMAGES) += image.iso
ARTIFACTS-$$(CONFIG_GRUB_EFI_IMAGES) += image-efi.iso
endif
ARTIFACTS-$$(CONFIG_ONIE_INSTALLER_IMAGES) += onie-installer.bin
ifeq ($(CONFIG_VDI_IMAGES),y)
IMAGES-$$(CONFIG_GRUB_IMAGES) += combined.vdi
IMAGES-$$(CONFIG_GRUB_EFI_IMAGES) += combined-efi.vdi

View File

@ -0,0 +1,190 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
#
# OpenWrt ONIE installer -- self-extracting shell archive.
# Wrapped around a tar payload that contains:
# rootfs.tar.gz -- OpenWrt root tree
# vmlinuz -- OpenWrt kernel
# Appended at PAYLOAD_OFFSET (filled in by image.mk at build time).
#
# Follows the ONIE demo installer pattern:
# https://github.com/opencomputeproject/onie/blob/master/demo/installer/grub-arch/install.sh
# The existing ESP and ONIE-BOOT partition are preserved; a new GPT
# partition labelled OPENWRT-ROOT is created for OpenWrt.
set -e
# 10-char zero-padded byte offset of the tar payload. Length-preserving
# substitution: __PYLOAD__ (10 chars) -> "NNNNNNNNNN" at build time.
PAYLOAD_OFFSET=__PYLOAD__
# Substituted by image.mk:
GRUB_CMDLINE='@CMDLINE@'
GRUB_SERIAL_CONFIG='@SERIAL_CONFIG@'
GRUB_TERMINAL_CONFIG='@TERMINAL_CONFIG@'
GRUB_TITLE='@TITLE@'
GRUB_TIMEOUT='@TIMEOUT@'
OPENWRT_VOLUME_LABEL="OPENWRT-ROOT"
OPENWRT_PART_SIZE=512 # MiB -- OpenWrt x86 default is 104; 512 gives headroom
echo ""
echo "=========================================================="
echo " OpenWrt ONIE installer"
echo " $GRUB_TITLE"
echo "=========================================================="
echo ""
cd "$(dirname "$0")"
lib_dir="/lib/onie"
# shellcheck disable=SC1090,SC1091
[ -r "$lib_dir/onie-blkdev-common" ] && . "$lib_dir/onie-blkdev-common"
[ -r /etc/machine.conf ] && . /etc/machine.conf
# Install on the same disk that holds ONIE-BOOT (demo-installer idiom)
blk_dev=$(blkid | awk -F: '/ONIE-BOOT/ {print $1; exit}' \
| sed -e 's/[0-9]*$//' -e 's/p$//')
[ -b "$blk_dev" ] || {
echo "ERROR: unable to determine ONIE install block device" >&2
exit 1
}
echo "Install device : $blk_dev"
if [ -d /sys/firmware/efi/efivars ]; then
firmware="uefi"
else
firmware="bios"
fi
echo "Firmware mode : $firmware"
blk_suffix=
case "$blk_dev" in
*mmcblk*|*nvme*) blk_suffix="p" ;;
esac
# ------------------------------------------------ extract the payload ---
payload_dir=$(mktemp -d)
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"
[ -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; }
# --------------------------------------------- create target partition ---
prev_part=$(sgdisk -p "$blk_dev" | awk -v lbl="$OPENWRT_VOLUME_LABEL" '$NF==lbl {print $1}')
if [ -n "$prev_part" ]; then
echo "Removing previous $OPENWRT_VOLUME_LABEL partition (#$prev_part)..."
sgdisk -d "$prev_part" "$blk_dev" >/dev/null
partprobe "$blk_dev" || true
fi
last_part=$(sgdisk -p "$blk_dev" | awk '/^ *[0-9]+ /{p=$1} END{print p+0}')
openwrt_part=$(( last_part + 1 ))
echo "Creating ${blk_dev}${blk_suffix}${openwrt_part} (${OPENWRT_PART_SIZE} MiB, label $OPENWRT_VOLUME_LABEL)..."
sgdisk --new=${openwrt_part}::+${OPENWRT_PART_SIZE}M \
--attributes=${openwrt_part}:=:0x0 \
--change-name=${openwrt_part}:"$OPENWRT_VOLUME_LABEL" \
"$blk_dev" >/dev/null
partprobe "$blk_dev" || blockdev --rereadpt "$blk_dev" || true
sleep 1
openwrt_dev="${blk_dev}${blk_suffix}${openwrt_part}"
mkfs.ext4 -F -q -L "$OPENWRT_VOLUME_LABEL" "$openwrt_dev"
mount -t ext4 -o defaults,rw "$openwrt_dev" "$openwrt_mnt"
# ----------------------------------------------- populate the rootfs ---
echo "Extracting OpenWrt root filesystem..."
tar -xzf "$payload_dir/rootfs.tar.gz" -C "$openwrt_mnt"
echo "Installing kernel..."
mkdir -p "$openwrt_mnt/boot"
cp "$payload_dir/vmlinuz" "$openwrt_mnt/boot/vmlinuz"
# ------------------------------------------------ install GRUB to ESP ---
if [ "$firmware" = "uefi" ]; then
uefi_part=0
for p in $(seq 1 16); do
if sgdisk -i "$p" "$blk_dev" 2>/dev/null \
| grep -q C12A7328-F81F-11D2-BA4B-00A0C93EC93B; then
uefi_part=$p; break
fi
done
[ "$uefi_part" -ne 0 ] || { echo "ERROR: cannot find UEFI ESP on $blk_dev" >&2; exit 1; }
echo "Reusing ESP : ${blk_dev}${blk_suffix}${uefi_part}"
echo "Installing GRUB (x86_64-efi)..."
grub-install \
--no-nvram \
--bootloader-id="OpenWrt" \
--efi-directory="/boot/efi" \
--boot-directory="$openwrt_mnt/boot" \
--recheck "$blk_dev" >/dev/null
for b in $(efibootmgr | awk '/OpenWrt/ {gsub("Boot",""); gsub("\\*",""); print $1}'); do
efibootmgr -b "$b" -B >/dev/null 2>&1 || true
done
efibootmgr --quiet --create \
--label "OpenWrt" \
--disk "$blk_dev" --part "$uefi_part" \
--loader "/EFI/OpenWrt/grubx64.efi"
else
echo "Installing GRUB (i386-pc, BIOS)..."
grub-install \
--target="i386-pc" \
--boot-directory="$openwrt_mnt/boot" \
--recheck "$blk_dev" >/dev/null
fi
# ---------------------------------------------- write target grub.cfg ---
# The kernel built-in root parser handles root=PARTUUID=... but not
# root=LABEL=... -- query the real PARTUUID and bake it in. We read it
# from the GPT via sgdisk since busybox blkid in ONIE doesn't support
# `-s PARTUUID -o value` and returns the device path instead.
rootpart_partuuid=$(sgdisk -i "$openwrt_part" "$blk_dev" \
| awk '/Partition unique GUID:/ {print tolower($NF)}')
[ -n "$rootpart_partuuid" ] || { echo "ERROR: cannot read PARTUUID of $openwrt_dev" >&2; exit 1; }
echo "Root PARTUUID : $rootpart_partuuid"
mkdir -p "$openwrt_mnt/boot/grub"
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
menuentry "$GRUB_TITLE" {
linux /boot/vmlinuz root=PARTUUID=$rootpart_partuuid rootwait $GRUB_CMDLINE noinitrd
}
menuentry "$GRUB_TITLE (failsafe mode)" {
linux /boot/vmlinuz failsafe=true root=PARTUUID=$rootpart_partuuid rootwait $GRUB_CMDLINE noinitrd
}
EOF
onie_grub_frag="${onie_root_dir:-/mnt/onie-boot/onie}/grub.d/50_onie_grub"
if [ -x "$onie_grub_frag" ]; then
echo "Appending ONIE boot entry to grub.cfg..."
"$onie_grub_frag" >> "$openwrt_mnt/boot/grub/grub.cfg" 2>/dev/null || true
fi
# --------------------------------------------------- finish and exit ---
sync
umount "$openwrt_mnt"
trap - EXIT
rm -rf "$payload_dir" "$openwrt_mnt"
if [ -x /bin/onie-nos-mode ]; then
/bin/onie-nos-mode -s
fi
echo ""
echo "=========================================================="
echo " OpenWrt installed. ONIE will reboot into it now."
echo "=========================================================="
exit 0