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>
191 lines
6.7 KiB
Bash
191 lines
6.7 KiB
Bash
#!/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
|