For reference:
- The management server runs this script to prepare offline sysupgrade and then upgrade remotely
Script name: /root/openwrt/remote_update.sh
Last updated: 2020-10-29
Verified working: 2020-10-29 on TP-Link Archer C7 v5 snapshot
#!/bin/sh
#
# Command line
# sh "/root/openwrt/remote_update.sh" "IP" "19.07.4"
# sh "/root/openwrt/remote_update.sh" "IP" "19.07.4" dry
# ssh -oStrictHostKeyChecking=no -i "/etc/ssh/ssh_host_rsa_key" "root@IP" "/bin/sh /root/opkg_reinstall.sh; reboot"
#
# Script configuration.
DRY_RUN=0
REMOTE_PACKAGE_CACHE_PATH="/root/packages"
SSH_PRIVATE_KEY="/etc/ssh/ssh_host_rsa_key"
UPLOAD_PACKAGE_CACHE=0
#
#########################
# FUNCTIONS BLOCK START #
#########################
fetchPackageList () {
# Syntax:
# fetchPackageList "[WEBSERVER_DIRECTORY]"
#
# Global vars.
# [IN] FP_PACKAGES
#
# Called By:
# fetchPackageLists
#
# Variables.
TMP_FPL_URL="${1}"
#
echo "[INFO] Downloading ${TMP_FPL_URL}/Packages.gz"
TMP_FPL_RESULT="$(wget -q -O - -c "${TMP_FPL_URL}/Packages.gz" | gzip -d 2> /dev/null)"
if [ -z "${TMP_FPL_RESULT}" ]; then
echo "[ERROR] fetchPackageList: Failed to download and unpack ${TMP_FPL_URL##*/}/Packages.gz"
return 1
fi
echo "${TMP_FPL_RESULT}" | egrep "^Package:|^Filename:" | sed -E "s^\^Filename:\s+^URL:"${TMP_FPL_URL}"/^" >> "${FP_PACKAGES}"
return 0
}
fetchPackageLists () {
# Syntax:
# fetchPackageLists
#
# Global vars.
# [IN] ARCH
# [IN] FP_PACKAGES
# [IN] TARGET
# [IN] UPDATE_TO_VERSION
#
# Called By:
# fetchPackages
#
# Check prerequisites.
if [ -z "${FP_PACKAGES}" ]; then
return 1
fi
#
echo "[INFO] fetchPackageLists"
#
# Get dir listing and extract kmods/[KMOD_DIR_NAME], e.g. "4.14.195-1-b84a5a29b1d5ae1dc33ccf9ba292ca1d"
# This information is also available from "/etc/opkg/distfeeds.conf" if we would unpack the new firmware image.
KMOD_DIR_NAME="$(wget -q -O - "http://downloads.openwrt.org/releases/${UPDATE_TO_VERSION}/targets/${TARGET}/generic/kmods" | grep -o '<a .*href=.*>' | sed -e 's/<a /\n<a /g' | sed -e 's/<a .*href=['"'"'"]//' -e 's/["'"'"'].*$//' -e '/^$/ d' | grep -v "^/" | sed -e "s/\/$//")"
if [ -z "${KMOD_DIR_NAME}" ]; then
echo "[ERROR] fetchPackageLists: Failed to determine kmods dir name on server."
return 1
fi
#
rm -f "${FP_PACKAGES}"
PACKAGE_LISTS_REL_PATHS="targets/${TARGET}/generic/packages targets/${TARGET}/generic/kmods/${KMOD_DIR_NAME} packages/${ARCH}/base packages/${ARCH}/luci packages/${ARCH}/packages packages/${ARCH}/routing packages/${ARCH}/telephony"
for packagelistrelpath in ${PACKAGE_LISTS_REL_PATHS}; do
fetchPackageList "http://downloads.openwrt.org/releases/${UPDATE_TO_VERSION}/${packagelistrelpath}"
if [ ! "$?" = 0 ]; then
return 1
fi
done
#
return 0
}
fetchPackage () {
# Syntax:
# fetchPackage "[PACKAGE_NAME]"
#
# Global vars.
# [IN] DEVICE_IP
# [IN] DOWNLOAD_DIR
# [IN] FP_PACKAGES
#
# Called By:
# fetchPackages
#
# Variables.
TMP_FP_PACKAGE_NAME="${1}"
#
# Check prerequisites.
if [ -z "${FP_PACKAGES}" ] || [ -z "${TMP_FP_PACKAGE_NAME}" ] ; then
return 1
fi
#
TMP_FP_URL="$(cat "${FP_PACKAGES}" 2> /dev/null | grep "^URL:" | grep "/"${TMP_FP_PACKAGE_NAME}"_.*\.ipk$" | sed -e "s/^URL://" | head -n 1)"
if [ -z "${TMP_FP_URL}" ]; then
echo "[ERROR] fetchPackage: Package [${TMP_FP_PACKAGE_NAME}] not found."
return 1
fi
#
echo "[INFO] Downloading ${TMP_FP_URL}"
wget -q -O "${DOWNLOAD_DIR}/${TMP_FP_URL##*/}" "${TMP_FP_URL}"
if [ ! "$?" = "0" ]; then
echo "[ERROR] fetchPackage: Failed to download ${TMP_FP_URL##*/}"
return 1
fi
#
return 0
}
fetchPackages () {
# Syntax:
# fetchPackages
#
# Global vars.
# [IN] ARCH
# [IN] DOWNLOAD_DIR
# [IN] TARGET
# [IN] UPDATE_TO_VERSION
#
# Called By:
# MAIN
#
# Variables.
FP_PACKAGES="${DOWNLOAD_DIR}/packages.txt"
#
echo "[INFO] fetchPackages"
fetchPackageLists
if [ ! "$?" = 0 ]; then
return 1
fi
#
echo "[INFO] fetchPackages: Downloading IPK files for offline update"
PACKAGE_NAMES="ath10k-firmware-qca988x batctl-full kmod-ath10k kmod-batman-adv kmod-crypto-crc32c kmod-crypto-hash kmod-lib-crc16 kmod-lib-crc32c libopenssl1.1 librt wpad-mesh-openssl"
for package in ${PACKAGE_NAMES}; do
fetchPackage "${package}"
if [ ! "$?" = 0 ]; then
return 1
fi
done
#
return 0
}
scpDownloadFile () {
# Syntax:
# scpDownloadFile "[DEVICE_IP]" "[SSH_PRIVATE_KEY]" "[REMOTE_FILE]" "[LOCAL_FILE]"
#
# Called By:
# MAIN
#
# Variables.
TMP_SDF_DEVICE_IP="${1}"
TMP_SDF_SSH_PRIVATE_KEY="${2}"
TMP_SDF_REMOTE_FILE="${3}"
TMP_SDF_LOCAL_FILE="${4}"
#
# Create local parent directory.
mkdir -p "${TMP_SDF_LOCAL_FILE%/*}"
#
# Download file.
scp -oStrictHostKeyChecking=no -i "${TMP_SDF_SSH_PRIVATE_KEY}" "root@${TMP_SDF_DEVICE_IP}:${TMP_SDF_REMOTE_FILE}" "${TMP_SDF_LOCAL_FILE}"
#
return $?
}
scpUploadFile () {
# Syntax:
# scpDownloadFile "[DEVICE_IP]" "[SSH_PRIVATE_KEY]" "[LOCAL_FILE]" "[REMOTE_FILE]"
#
# Called By:
# MAIN
#
# Variables.
TMP_SDF_DEVICE_IP="${1}"
TMP_SDF_SSH_PRIVATE_KEY="${2}"
TMP_SDF_LOCAL_FILE="${3}"
TMP_SDF_REMOTE_FILE="${4}"
#
# Download file.
scp -oStrictHostKeyChecking=no -i "${TMP_SDF_SSH_PRIVATE_KEY}" "${TMP_SDF_LOCAL_FILE}" "root@${TMP_SDF_DEVICE_IP}:${TMP_SDF_REMOTE_FILE}"
#
return $?
}
#########################
# FUNCTIONS BLOCK END #
#########################
#
# Verify SSH prerequisites.
if [ ! -f "${SSH_PRIVATE_KEY}" ]; then
echo "[ERROR] SSH_PRIVATE_KEY=[${SSH_PRIVATE_KEY}] is missing."
exit 99
fi
chmod 0600 "${SSH_PRIVATE_KEY}"
#
# Get command line params.
DEVICE_IP="${1}"
UPDATE_TO_VERSION="${2}"
if ( echo "${*}" | grep -q "dry" ); then
DRY_RUN="1"
fi
if [ "${DRY_RUN}" = "1" ]; then
echo "[INFO] DRY_RUN set."
fi
#
# Verify command line params.
if [ -z "${DEVICE_IP}" ]; then
echo "[ERROR] Param #1 DEVICE_IP missing."
exit 99
fi
## echo "[INFO] DEVICE_IP=[${DEVICE_IP}]"
#
if [ -z "${UPDATE_TO_VERSION}" ]; then
echo "[ERROR] Param #1 UPDATE_TO_VERSION missing."
exit 99
fi
## echo "[INFO] UPDATE_TO_VERSION=[${UPDATE_TO_VERSION}]"
#
# Firmware download dir
DOWNLOAD_DIR="/tmp/device_${DEVICE_IP}_${UPDATE_TO_VERSION}"
#
echo "[INFO] Getting device info from [${DEVICE_IP}] ..."
BOARD_JSON="${DOWNLOAD_DIR}/board.json"
scpDownloadFile "${DEVICE_IP}" "${SSH_PRIVATE_KEY}" "/etc/board.json" "${BOARD_JSON}"
#
OPENWRT_RELEASE="${DOWNLOAD_DIR}/openwrt_release"
scpDownloadFile "${DEVICE_IP}" "${SSH_PRIVATE_KEY}" "/etc/openwrt_release" "${OPENWRT_RELEASE}"
#
# Generate firmware download URL.
#
## Example: MODEL="archer-c7-v5"
MANUFACTURER="$(cat "${BOARD_JSON}" | grep "\"id\"" | cut -d '"' -f 4 | cut -d "," -f 1)"
echo "[INFO] MANUFACTURER=[${MANUFACTURER}]"
#
## Example: MODEL="archer-c7-v5"
MODEL="$(cat "${BOARD_JSON}" | grep "\"id\"" | cut -d '"' -f 4 | cut -d "," -f 2)"
echo "[INFO] MODEL=[${MODEL}]"
#
## Example: ARCH="mips_24kc"
ARCH="$(cat "${OPENWRT_RELEASE}" | grep "^DISTRIB_ARCH=" | cut -d "'" -f 2 | cut -d "/" -f 1)"
echo "[INFO] ARCH=[${ARCH}]"
#
## Example: TARGET="ath79"
TARGET="$(cat "${OPENWRT_RELEASE}" | grep "^DISTRIB_TARGET=" | cut -d "'" -f 2 | cut -d "/" -f 1)"
echo "[INFO] TARGET=[${TARGET}]"
#
## Example: http://downloads.openwrt.org/releases/19.07.3/targets/ath79/generic/openwrt-19.07.3-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin
FIRMWARE_DOWNLOAD_URL="http://downloads.openwrt.org/releases/${UPDATE_TO_VERSION}/targets/${TARGET}/generic/openwrt-${UPDATE_TO_VERSION}-${TARGET}-generic-${MANUFACTURER}_${MODEL}-squashfs-sysupgrade.bin"
SYSUPGRADE_IMAGE="${DOWNLOAD_DIR}/openwrt-${UPDATE_TO_VERSION}-${TARGET}-generic-${MANUFACTURER}_${MODEL}-squashfs-sysupgrade.bin"
#
SHASUMS_DOWNLOAD_URL="http://downloads.openwrt.org/releases/${UPDATE_TO_VERSION}/targets/${TARGET}/generic/sha256sums"
SHASUMS="${DOWNLOAD_DIR}/sha256sums"
if [ ! -f "${SHASUMS}" ]; then
echo "[INFO] Downloading [${SHASUMS_DOWNLOAD_URL}] to [${SHASUMS}]..."
wget -qq -O "${SHASUMS}" "${SHASUMS_DOWNLOAD_URL}"
fi
#
echo "[INFO] Checking for existing firmware image ..."
CHECKSUM_RESULT="$(cd "${SYSUPGRADE_IMAGE%/*}"; cat "${SHASUMS}" | grep "${SYSUPGRADE_IMAGE##*/}" | sha256sum -c 2> /dev/null)"
if ( ! echo "${CHECKSUM_RESULT}" | grep -q "OK" ); then
if [ -f "${SYSUPGRADE_IMAGE}" ]; then
echo "[WARN] Firmware image FAILED checksum test."
fi
echo "[INFO] Downloading firmware image from [${FIRMWARE_DOWNLOAD_URL}] to [${SYSUPGRADE_IMAGE}] ..."
wget -qq -O "${SYSUPGRADE_IMAGE}" "${FIRMWARE_DOWNLOAD_URL}"
CHECKSUM_RESULT="$(cd "${SYSUPGRADE_IMAGE%/*}"; cat "${SHASUMS}" | grep "${SYSUPGRADE_IMAGE##*/}" | sha256sum -c 2> /dev/null)"
fi
#
if ( ! echo "${CHECKSUM_RESULT}" | grep -q "OK" ); then
echo "[ERROR] Firmware image FAILED checksum test."
exit 99
fi
echo "[INFO] Firmware image PASSED checksum test."
#
rm -f ${DOWNLOAD_DIR}/*.ipk
fetchPackages
if [ ! "$?" = 0 ]; then
echo "[ERROR] fetchPackages FAILED."
exit 99
fi
#
if [ "${DRY_RUN}" = "0" ] && [ "${UPLOAD_PACKAGE_CACHE}" = "1" ]; then
ssh -oStrictHostKeyChecking=no -i "${SSH_PRIVATE_KEY}" "root@${DEVICE_IP}" "rm -rf ${REMOTE_PACKAGE_CACHE_PATH}; mkdir -p ${REMOTE_PACKAGE_CACHE_PATH}"
for ipk in ${DOWNLOAD_DIR}/*.ipk; do
echo "[INFO] Uploading ${ipk##*/}"
scpUploadFile "${DEVICE_IP}" "${SSH_PRIVATE_KEY}" "${ipk}" "${REMOTE_PACKAGE_CACHE_PATH}/${ipk##*/}"
if [ ! "$?" = "0" ]; then
echo "[ERROR] Upload FAILED."
exit 99
fi
done
else
echo "[INFO] DRY_RUN flag set. Skipping package upload."
fi
#
if [ "${DRY_RUN}" = "0" ]; then
echo "[INFO] Uploading firmware image to device ..."
scpUploadFile "${DEVICE_IP}" "${SSH_PRIVATE_KEY}" "${SYSUPGRADE_IMAGE}" "/tmp/firmware.bin"
if [ ! "$?" = "0" ]; then
echo "[ERROR] Upload FAILED."
exit 99
fi
else
echo "[INFO] DRY_RUN flag set. Skipping image upload."
fi
#
if [ "${DRY_RUN}" = "0" ]; then
echo "[INFO] Flashing image ..."
ssh -oStrictHostKeyChecking=no -i "${SSH_PRIVATE_KEY}" "root@${DEVICE_IP}" "sync; echo 3 > /proc/sys/vm/drop_caches; sysupgrade -v /tmp/firmware.bin"
else
echo "[INFO] DRY_RUN flag set. Skipping flash."
fi
#
echo "[INFO] Done."
#
exit 0
The OpenWrt device (TP-Link Archer c7 v2 / v5 run the following
/etc/rc.local
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.
( /bin/sleep 30; /bin/sh /root/opkg_reinstall.sh ) &
exit 0
/etc/sysupgrade.conf
/root
Script name: /root/opkg_reinstall.sh
Last update: 2020-10-29
Verified working: 2020-10-29 on TP-Link Archer c7 v2/v5
#!/bin/sh
trap "" SIGHUP
#
#
# Command line
## sh /root/opkg_reinstall.sh force
#
# Notes
## opkg remove ath10k-firmware-qca988x kmod-ath10k kmod-batman-adv wpad-mesh-openssl; opkg remove batctl-full;
## opkg install ath10k-firmware-qca988x-ct kmod-ath10k-ct
#
# Consts
PATH=/usr/bin:/usr/sbin:/sbin:/bin
#
# Note: If we log to "/root/" during "insmod", "opkg", "rmmod" commands affecting kernel modules, the kernel will panic and the device will reboot.
LOGFILE="/root/opkg_reinstall.log"
LOG_MAX_LINES="1000"
#
LOG_COLLECTOR_HOSTNAME="HST-WifiAP-01"
INSTALL_BATMANADV="1"
INSTALL_OPENVPN="1"
INSTALL_RELAYD="0"
INSTALL_WIFI_NON_CT_DRIVERS="1"
PACKAGE_CACHE="/root/packages"
#
# Runtime vars
INSTALL_ONLINE="1"
REBOOT_REQUIRED="0"
#
#
# -----------------------------------------------------
# -------------- START OF FUNCTION BLOCK --------------
# -----------------------------------------------------
logAdd ()
{
TMP_DATETIME="$(date '+%Y-%m-%d [%H-%M-%S]')"
TMP_LOGSTREAM="$(tail -n ${LOG_MAX_LINES} ${LOGFILE} 2>/dev/null)"
echo "${TMP_LOGSTREAM}" > "$LOGFILE"
echo "${TMP_DATETIME} $*" | tee -a "${LOGFILE}"
return
}
opkgInstall () {
# Syntax:
# opkgInstall "[PACKAGE_NAME]"
#
# Online mode.
if [ "${INSTALL_ONLINE}" = "1" ]; then
RESULT="$(eval "opkg install ${@}" 2>&1)"
logAdd "[INFO] opkgInstall: - ${RESULT}"
if ( echo "${RESULT}" | grep -q "Collected errors:"); then
return 1
fi
return 0
fi
#
# Offline mode
IPKG_FULLFN=""
for package_name in $@; do
if ( ! ls -1 ${PACKAGE_CACHE} | grep -q "^${package_name}_" ); then
logAdd "[ERROR] opkgInstall: Package missing in cache - [${package_name}]"
continue
fi
IPKG_FULLFN="${IPKG_FULLFN} ${PACKAGE_CACHE}/$(ls -1 ${PACKAGE_CACHE} | grep "^${package_name}_")"
done
if [ -z "${IPKG_FULLFN}" ]; then
return 0
fi
RESULT="$(eval "opkg --cache ${PACKAGE_CACHE} install ${IPKG_FULLFN}" 2>&1)"
if ( echo "${*}" | grep -q "kmod-" ); then
REBOOT_REQUIRED="1"
# logAdd "[INFO] opkgInstall: Kernel module added. Will sleep a bit to avoid crash."
sleep 10
fi
logAdd "[INFO] opkgInstall: - ${RESULT}"
if ( echo "${RESULT}" | grep -q "Collected errors:"); then
return 1
fi
return 0
}
opkgRemove () {
# Syntax:
# opkgRemove "[PACKAGE_NAME]"
#
for package_name in $@; do
logAdd "[INFO] opkgRemove: Removing ${package_name}"
RESULT="$(eval "opkg remove ${package_name}" 2>&1)"
if ( echo "${package_name}" | grep -q "^kmod-" ); then
REBOOT_REQUIRED="1"
# logAdd "[INFO] opkgRemove: Kernel module removed. Will sleep a bit to avoid crash."
sleep 10
fi
logAdd "[INFO] opkgRemove: - ${RESULT}"
if ( echo "${RESULT}" | grep -q "Collected errors:"); then
return 1
fi
done
return 0
}
runInstall () {
# Syntax:
# runInstall
#
# Global vars
# [IN] INSTALL_ONLINE
#
# Variables
PKG_TO_INSTALL=""
PKG_TO_REMOVE=""
#
# If we are offline, check if we have a package cache available.
if [ "${INSTALL_ONLINE}" = "0" ] && [ ! -d "${PACKAGE_CACHE}" ]; then
logAdd "[ERROR] runInstall: We are offline but don't have a package cache available at ${PACKAGE_CACHE}"
return 1
fi
#
if [ "${INSTALL_ONLINE}" = "1" ]; then
logAdd "[INFO] Downloading package information ..."
RESULT="$(opkg update | grep "http://" 2>&1)"
logAdd "[INFO] opkg update: - ${RESULT}"
fi
#
# Add packages to the install and remove queue.
PKG_TO_REMOVE="${PKG_TO_REMOVE} wpad wpad-basic wpad-basic-wolfssl"
PKG_TO_INSTALL="${PKG_TO_INSTALL} libopenssl1.1 wpad-mesh-openssl"
#
# Replace ct with non-ct drivers
if [ "${INSTALL_WIFI_NON_CT_DRIVERS}" = "1" ]; then
# Remove order is important
PKG_TO_REMOVE="${PKG_TO_REMOVE} kmod-ath10k-ct ath10k-firmware-qca988x-ct"
PKG_TO_INSTALL="${PKG_TO_INSTALL} ath10k-firmware-qca988x kmod-ath10k"
fi
#
# batman-adv
if [ "${INSTALL_BATMANADV}" = "1" ]; then
PKG_TO_INSTALL="${PKG_TO_INSTALL} batctl-full kmod-batman-adv kmod-crypto-crc32c kmod-lib-crc16 kmod-lib-crc32c kmod-crypto-hash librt"
fi
#
# Only install these packages if we are online
if [ "${INSTALL_ONLINE}" = "1" ]; then
#
# Base system
PKG_TO_INSTALL="${PKG_TO_INSTALL} bash curl htop lua luafilesystem mailsend nano terminfo tcpdump wget"
#
# FTP service
PKG_TO_INSTALL="${PKG_TO_INSTALL} vsftpd"
#
# Relayd
if [ "${INSTALL_RELAYD}" = "1" ]; then
PKG_TO_INSTALL="${PKG_TO_INSTALL} luci-proto-relay relayd"
fi
#
# OpenVPN
if [ "${INSTALL_OPENVPN}" = "1" ]; then
PKG_TO_INSTALL="${PKG_TO_INSTALL} luci-app-openvpn openvpn-easy-rsa openvpn-openssl"
fi
#
# Syslog-ng, logd
if ( echo "${HOSTNAME}" | grep -q "^${LOG_COLLECTOR_HOSTNAME}" ); then
PKG_TO_REMOVE="${PKG_TO_REMOVE} logd"
PKG_TO_INSTALL="${PKG_TO_INSTALL} syslog-ng"
else
PKG_TO_REMOVE="${PKG_TO_REMOVE} syslog-ng"
PKG_TO_INSTALL="${PKG_TO_INSTALL} logd"
fi
#
# USB storage drivers
PKG_TO_INSTALL="${PKG_TO_INSTALL} block-mount e2fsprogs kmod-fs-ext4 kmod-fs-msdos kmod-scsi-core kmod-usb-storage libncurses libpcre"
fi
#
logAdd "[INFO] runInstall: Remove packages"
opkgRemove "${PKG_TO_REMOVE}"
if [ ! "$?" = "0" ]; then
return $?
fi
#
logAdd "[INFO] runInstall: Install packages"
opkgInstall "${PKG_TO_INSTALL}"
if [ ! "$?" = "0" ]; then
return $?
fi
#
return 0
}
waitForInternetConnection () {
# Syntax:
# waitForInternetConnection
#
# Assume we can wait for internet connection if hostapd started fine.
# If it did not start, we maybe miss internet connectivity via mesh.
#
if [ -z "$(iw dev)" ]; then
logAdd "[INFO] WiFi is not initialized"
return 1
fi
#
# Give WiFi mesh interface time to connect and get internet connectivity.
logAdd "[INFO] Waiting for WiFi to initialize"
UPTIME_IN_SECONDS="$(cat /proc/uptime | cut -d "." -f 1)"
SECONDS_TO_SLEEP="$((120-${UPTIME_IN_SECONDS}))"
sleep "${SECONDS_TO_SLEEP}"
#
return 0
}
# ---------------------------------------------------
# -------------- END OF FUNCTION BLOCK --------------
# ---------------------------------------------------
#
#
# Check command line.
if ( echo "${*}" | grep -q "force" ); then
runInstall
exit 0
fi
#
# Check if the script should run on boot.
if [ -f "/etc/_OPKG_INSTALL_COMPLETE" ]; then
echo "[INFO] /etc/_OPKG_INSTALL_COMPLETE exists."
exit 99
fi
#
waitForInternetConnection
#
logAdd "[INFO] Checking internet connection ..."
if ( ! echo -e "GET / HTTP/1.1\r\nHost: downloads.openwrt.org\r\n" | nc downloads.openwrt.org 80 > /dev/null ); then
logAdd "[INFO] No internet connection. Switching to offline mode."
INSTALL_ONLINE="0"
fi
#
runInstall
if [ ! "$?" = "0" ]; then
logAdd "[ERROR] One or more packages FAILED to install"
else
logAdd "[INFO] All packages installed successfully"
touch "/etc/_OPKG_INSTALL_COMPLETE"
fi
#
logAdd "[INFO] Cleanup"
if [ ! -z "${PACKAGE_CACHE}" ]; then
rm -rf "${PACKAGE_CACHE}"
fi
#
if [ "${REBOOT_REQUIRED}" = "1" ]; then
logAdd "[INFO] Rebooting device"
reboot -d 3
exit 0
fi
#
logAdd "[INFO] Done."
exit 0