Files
openwrt-x86-upgrade/openwrt-x86-upgrade-script.sh

267 lines
10 KiB
Bash

#!/bin/ash
# Exit script on non-zero exit code (this was causing the script to exit prematurely at e2fsck)
#set -e
# install dependencies
opkg update && opkg install lsblk curl rsync
# Set mount point for second OpenWrt installation
mount_pt=/tmp/mnt
mkdir ${mount_pt}
# Check current release vs new release
current_dist=$(grep DISTRIB_RELEASE /etc/openwrt_release | cut -d "'" -f 2)
## TODO - Perhaps use the https://sysupgrade.openwrt.org/ api to get new release versions
new_release=$(wget -qO- https://downloads.openwrt.org | grep releases | awk -F'/' '{print $2}' | tr '\n' ' ' | awk '{print $1}')
if [[ $current_dist == $new_release ]]
then echo -e "Already on newest release: /n/t Current: ${current_dist} = Newest: ${new_release}"; exit 1
fi
# Which device/partition is the currently mounted, which is the target
boot_dev=$(lsblk -pPo LABEL,PATH | grep kernel | sed -E 's/.*PATH="(.*)".*/\1/')
current_dev=$(lsblk -pPo MOUNTPOINTS,PATH | grep 'MOUNTPOINTS="/"' | sed -E 's/.*PATH="(.*)".*/\1/')
if [[ $current_dev =~ 2 ]]
then target_dev=$(lsblk -pPo PATH | grep '3"' | sed -E 's/.*PATH="(.*)".*/\1/')
else target_dev=$(lsblk -pPo PATH | grep '2"' | sed -E 's/.*PATH="(.*)".*/\1/')
fi
# Mount the target device, check old version
mount ${target_dev} ${mount_pt}
old_dist=`grep DISTRIB_RELEASE ${mount_pt}/etc/openwrt_release | cut -d "'" -f 2`
echo "Current OpenWRT release: ${current_dist} on ${current_dev}"
echo "New OpenWRT release: ${new_release} to replace ${old_dist} on target ${target_dev}"
# Ask user to confirm continuation of upgrade process
read -n1 -p "Continue with upgrade? (WARNING: THIS WILL OVERWRITE ${target_dev}) [y/N]: " doit
if [[ ! $doit =~ [yY] ]]; then
umount ${mount_pt}
echo -e "\nExiting...\n"
exit 1
fi
### Slow but perhaps more accurate installed packages list
# From user: spence
# https://forum.openwrt.org/t/detecting-user-installed-pkgs/161588/8
##############################################################
#myDeviceName=$(ubus call system board | jsonfilter -e '@.board_name' | tr ',' '_')
#myDeviceTarget=$(ubus call system board | jsonfilter -e '@.release.target')
#myDeviceVersion=$(ubus call system board | jsonfilter -e '@.release.version')
#myDeviceJFilterString=@[\"profiles\"][\"$myDeviceName\"][\"device_packages\"]
#myDefaultJFilterString=@[\"default_packages\"]
#
####myDeviceProfilesURL="https://downloads.openwrt.org/releases/$myDeviceVersion/targets/$myDeviceTarget/profiles.json"
#if [ "$myDeviceVersion" = 'SNAPSHOT' ] ; then
# myDeviceProfilesURL="https://downloads.openwrt.org/snapshots/targets/$myDeviceTarget/profiles.json"
#else
# myDeviceProfilesURL="https://downloads.openwrt.org/releases/$myDeviceVersion/targets/$myDeviceTarget/profiles.json"
#fi
#
##### 2023-10-17: Potential better way to get URL:
#myDeviceProfilesURL=$(grep openwrt_core /etc/opkg/distfeeds.conf / | grep -o "https.*[/]")profiles.json
#
#wget -O /tmp/profiles.json "$myDeviceProfilesURL"
#
#jsonfilter -i /tmp/profiles.json -e $myDeviceJFilterString | sed s/\"/''/g | tr '[' ' ' | tr ']' ' ' | sed s/\ /''/g | tr ',' '\n' > /tmp/my-def-pkgs
#
#jsonfilter -i /tmp/profiles.json -e $myDefaultJFilterString | sed s/\"/''/g | tr '[' ' ' | tr ']' ' ' | sed s/\ /''/g | tr ',' '\n' >> /tmp/my-def-pkgs
#
###############################################################
### OR
# From user: efahl
# https://forum.openwrt.org/t/detecting-user-installed-pkgs/161588/16
printf "\n---Getting list of user-installed packages for Image Builder---\n"
package_list=./installed-packages
rm -f $package_list
examined=0
for pkg in $(opkg list-installed | awk '{print $1}') ; do
examined=$((examined + 1))
printf '%5d - %-40s\r' "$examined" "$pkg"
#deps=$(opkg whatdepends "$pkg" | awk '/^\t/{printf $1" "}')
deps=$(
cd /usr/lib/opkg/info/ &&
grep -lE "Depends:.* ${pkg}([, ].*|)$" -- *.control | awk -F'\.control' '{printf $1" "}'
)
count=$(echo "$deps" | wc -w)
if [ "$count" -eq 0 ] ; then
printf '%s\t%s\n' "$pkg" "$deps" >> $package_list
fi
done
n_logged=$(wc -l < $package_list)
printf 'Done, logged %d of %d entries\n' "$n_logged" "$examined"
####################################################################
# Build json for Image Builder request
awk -v new_release=${new_release} '{
items[NR] = $1
}
END {
printf "{\n"
printf " \"packages\": [\n"
for (i = 1; i <= NR; i++) {
printf " \"%s\"", items[i]
if (i < NR) {
printf ","
}
printf "\n"
}
printf " ],\n"
printf " \"filesystem\": \"ext4\",\n"
printf " \"profile\": \"generic\",\n"
printf " \"target\": \"x86/64\",\n"
printf " \"version\": \"%s\"\n", new_release
printf "}\n"
}' installed-packages > json_data
printf "---Requesting build from https://sysupgrade.openwrt.org/api/v1/build---\n"
curl -H 'accept: application/json' -H 'Content-Type: application/json' --data-binary '@json_data' 'https://sysupgrade.openwrt.org/api/v1/build' > build_reply
build_status=$(cat build_reply | jsonfilter -e '@.status')
if [ $build_status == 202 ] || [ $build_status == 200 ]; then
build_hash=$(cat build_reply | jsonfilter -e '@.request_hash')
printf "Request OK. Request hash: %s\n" "${build_hash}"
else
echo "Error requesting Image build:"
cat build_reply
umount ${mount_pt}
exit 1
fi
i=0
spin='-\|/'
build_time=0
while [ true ]; do
# Sleep to comply with API rules
sleep 6
building=$(curl -s "https://sysupgrade.openwrt.org/api/v1/build/${build_hash}")
build_status=$(echo $building | jsonfilter -e '@.status')
# 202 = in-progress
if [ $build_status == 202 ]; then
i=$(( (i+1) %4 ))
build_time=$(( build_time + 6 ))
printf "\rWaiting for build to complete ${spin:$i:1}"
continue
# 200 = build complete
elif [ $build_status == 200 ]; then
image=$(echo $building | jsonfilter -e '@.images[@.filesystem="ext4" && @.type="rootfs"].name')
hash=$(echo $building | jsonfilter -e '@.images[@.filesystem="ext4" && @.type="rootfs"].sha256')
image_hash="${hash} ${image}"
printf "\nBuild finished in %d seconds\n" "${build_time}"
break
else
# Sleep an extra 5 seconds to not hit the API back-to-back just to report an error
sleep 5
printf "\nError with Image Builder:\n"
curl "https://sysupgrade.openwrt.org/api/v1/build/${build_hash}"
exit 1
fi
done
echo -e "\n---Downloading the rootfs image and copying to ${target_dev}---\n"
cd /tmp
# Download new release
wget "https://sysupgrade.openwrt.org/store/${build_hash}/${image}"
# Check sha256 hash against file downloaded
csum=$(echo $image_hash | sha256sum -c | awk '{ print $2 }')
printf "Checksum %s!\n" "${csum}"
if [ $csum != "OK" ]; then
# If hash doesn't match, exit
printf "Downloaded image doesn't match sha256sum! Exiting...\n\n"
umount ${mount_pt}
exit 1
else
printf "---Image downloaded and hash OK. Installing---\n"
fi
# Unzip and write directly to partition
gzip -d -c ${image} | dd of=${target_dev}
# Unmount partition to resize without error
umount ${target_dev}
# Check filesystem for errors
e2fsck -fp ${target_dev}
# Resize filesystem to partition size
resize2fs ${target_dev}
# Check partition for errors
fsck.ext4 ${target_dev}
# Remount target device
mount ${target_dev} ${mount_pt}
echo "---Removing old kernel(s)---"
mkdir -p /tmp/boot
# Mount /boot into a tmp directory
mount ${boot_dev} /tmp/boot
# Check if more that one kernel exists
num_kernels=$(find /tmp/boot/boot/ -name *vmlinuz* | wc -l)
if [[ $num_kernels > 1 ]]; then
current_root_partuuid=$(lsblk -pPo MOUNTPOINTS,PARTUUID | grep 'MOUNTPOINTS="/"' | sed -E 's/.*PARTUUID="(.*)".*/\1/')
current_kernel=$(grep ${current_root_partuuid} /tmp/boot/boot/grub/grub.cfg | sed -E 's/.*linux \/boot\/(.*) .*/\1/g' | cut -d " " -f 1 | uniq)
find /tmp/boot/boot -name *vmlinuz* ! -name ${current_kernel} -exec mv {} /tmp \;
else
echo "One existing kernel found, not deletion required"
fi
echo "---Downloading new kernel---"
new_kernel=vmlinuz-${new_release}
wget https://downloads.openwrt.org/releases/${new_release}/targets/x86/64/openwrt-${new_release}-x86-64-generic-kernel.bin -O /tmp/boot/boot/${new_kernel}
echo "---Updating Grub---"
# Get new partition UUID
new_partuuid=`lsblk -pPo PATH,PARTUUID | grep ${target_dev} | sed -E 's/.*PARTUUID="(.*)".*/\1/'`
# Create a backup copy of grub in case something fails
cp /tmp/boot/boot/grub/grub.cfg /tmp/boot/boot/grub/grub.cfg.bak
# Copy the first menu entry to create an additional entry
sed -i '1,/menuentry/{/menuentry/{N;N;p;N}}' /tmp/boot/boot/grub/grub.cfg
## Update the first menu entry
# Name
sed -i "1,/menuentry/s/\"OpenWrt.*\"/\"OpenWrt-${new_release}\"/" /tmp/boot/boot/grub/grub.cfg
# Kernel
sed -i "1,/linux/s/vmlinuz[-0-9.]*/${new_kernel}/" /tmp/boot/boot/grub/grub.cfg
# Partition
sed -i "1,/linux/s/PARTUUID=[-0-9a-f]*/PARTUUID=${new_partuuid}/" /tmp/boot/boot/grub/grub.cfg
# Leave the second entry as is - the current working (old) version
# If there are now 4 menu entries, delete the 3rd (oldest version)
grub_entries=`grep menuentry /tmp/boot/boot/grub/grub.cfg | wc -l`
if [[ grub_entries == 4 ]]; then
awk 'BEGIN {count=0} /menuentry/ {count++} count!=3' /tmp/boot/boot/grub/grub.cfg > tmp && mv tmp /tmp/boot/boot/grub/grub.cfg
fi
## Update failsafe entry
# Copy (new) first entry to the end of grub, add failsafe
sed -n '1,/menuentry/{/menuentry/{N;N;p}}' /tmp/boot/boot/grub/grub.cfg | sed -E 's/(\"OpenWrt-.*)\"/\1 \(failsafe\)"/' | sed -E 's/(^.*)(root=PARTUUID=.*$)/\1failsafe=true \2/' >> /tmp/boot/boot/grub/grub.cfg
# Delete the old failsafe entry
sed -i '1,/failsafe/{/failsafe/{N;N;d}}' /tmp/boot/boot/grub/grub.cfg
# Since we used awk to replace the file, restore original permissions
chmod 755 /tmp/boot/boot/grub/grub.cfg
echo "---Copying /etc files to new OpenWRT---"
rsync -aAXP --exclude banner* --exclude openwrt_* --exclude opkg/ --exclude os_release /etc/. /tmp/mnt/etc
echo "---Copying files in sysupgrade.conf---"
for file in $(awk '!/^[ \t]*#/&&NF' /etc/sysupgrade.conf); do
directory=$(dirname ${file})
if [ ! -d $directory ]; then
mkdir -p "/tmp/mnt${directory}"
fi
rsync -aAXP $file /tmp/mnt$file
done
echo "---Finished!---"
umount /tmp/boot
umount ${mount_pt}
echo "Reboot to start new OpenWrt version!"