#!/usr/bin/env bash # # Copyright 2022 Bryan C. Roessler # # Build and flash/upgrade OpenWRT devices # # Apache 2.0 License # Set default release : "${RELEASE:="22.03.2"}" printHelp() { debug "${FUNCNAME[0]}" cat <<-'EOF' Run and deploy OpenWRT images using the Image Builder. USAGE: openwrtbuilder [OPTION [VALUE]]... OPTIONS --profile, -p PROFILE --info, -i PROFILE --list-profiles, -l --release, -r, --version, -v RELEASE_VERSION ("snapshot", "22.03.3", etc.) --builddir, -b PATH --ssh-upgrade HOST Example: root@192.168.1.1 --ssh-backup SSH_PATH (Enabled by default for --ssh-upgrade) --flash, -f DEVICE Example: /dev/sdX --reset Cleanup all source and output files --debug, -d --help, -h EOF } readInput() { debug "${FUNCNAME[0]}" unset RESET if _input=$(getopt -o +r:v:p:i:lb:f:dh -l release:,version:,profile:,info:,list-profiles,builddir:,ssh-upgrade:,ssh-backup:,flash:,reset,debug,help -- "$@"); then eval set -- "$_input" while true; do case "$1" in --release|-r|--version|-v) shift && RELEASE="$1" ;; --profile|-p) shift && PROFILE="$1" ;; --info|-i) shift && PROFILE="$1" && PROFILE_INFO=1 && exit $? ;; --list-profiles|-l) listProfiles && exit $? ;; --builddir|-b) shift && BUILDDIR="$1" ;; --ssh-upgrade) shift && SSH_UPGRADE_PATH="$1" ;; --ssh-backup) shift && SSH_BACKUP_PATH="$1" ;; --flash|-f) shift && FLASH_DEV="$1" ;; --reset) RESET=1 ;; --debug|-d) echo "Debugging on" DEBUG=1 ;; --help|-h) printHelp && exit 0 ;; --) shift break ;; esac shift done else echo "Incorrect options provided" printHelp && exit 1 fi } listProfiles() { debug "${FUNCNAME[0]}" grep "declare -Ag" "$PFILE" | cut -d" " -f3 } installHostDependencies() { debug "${FUNCNAME[0]}" local -a _pkg_list local _pkg_cmd source /etc/os-release if [[ "$ID" =~ ^(fedora)$ ]]; then _pkg_list=(\ "@c-development" \ "@development-tools" \ "@development-libs" \ "perl-FindBin" \ "zlib-static" \ "elfutils-libelf-devel" \ "gawk" \ "unzip" \ "file" \ "wget" \ "python3" \ "python2" \ "axel" \ ) _pkg_cmd="dnf" elif [[ "$ID" =~ ^(debian|ubuntu)$ ]]; then _pkg_list=(\ "build-essential" \ "libncurses5-dev" \ "libncursesw5-dev" \ "zlib1g-dev" \ "gawk" \ "git" \ "gettext" \ "libssl-dev" \ "xsltproc" \ "wget" \ "unzip" \ "python" \ "axel" \ ) _pkg_cmd="apt-get" fi debug "Installing dependencies" debug "sudo $_pkg_cmd -y install ${_pkg_list[*]}" if ! sudo "$_pkg_cmd" -y install "${_pkg_list[@]}" > /dev/null 2>&1; then echo "Warning: Problem installing dependencies" return 1 fi } getImageBuilder() { debug "${FUNCNAME[0]}" local _url _filename _dl_tool if [[ "${P_ARR[release]}" == "snapshot" ]]; then _filename="openwrt-imagebuilder-${P_ARR[target]//\//-}.Linux-x86_64.tar.xz" _url="https://downloads.openwrt.org/snapshots/targets/${P_ARR[target]}/$_filename" if [[ -f "${P_ARR[source_archive]}" ]]; then if askOk "Update ImageBuilder snapshot?"; then rm -f "${P_ARR[source_archive]}" else return 0 fi fi else _filename="openwrt-imagebuilder-${P_ARR[release]}-${P_ARR[target]//\//-}.Linux-x86_64.tar.xz" _url="https://downloads.openwrt.org/releases/${P_ARR[release]}/targets/${P_ARR[target]}/$_filename" [[ -f "${P_ARR[source_archive]}" ]] && return 0 # Reuse existing ImageBuilders fi # Make sources directory if it does not exist [[ ! -d "$BUILDDIR/sources" ]] && mkdir -p "$BUILDDIR/sources" if hash axel &>/dev/null; then _dl_tool="axel" elif hash curl &>/dev/null; then _dl_tool="curl" else echo "Downloading the ImageBuilder requires axel or curl!" return 1 fi #_dl_tool="curl" # TODO remove echo "Downloading imagebuilder archive using $_dl_tool" debug "$_dl_tool -o ${P_ARR[source_archive]} $_url" if ! "$_dl_tool" -o "${P_ARR[source_archive]}" "$_url"; then echo "Could not download imagebuilder archive" exit 1 fi if [[ ! -f "${P_ARR[source_archive]}" ]]; then echo "Archive missing" exit 1 fi echo "Extracting image archive" [[ ! -d "${P_ARR[source_dir]}" ]] && mkdir -p "${P_ARR[source_dir]}" debug "tar -xf ${P_ARR[source_archive]} -C ${P_ARR[source_dir]} --strip-components 1" if ! tar -xf "${P_ARR[source_archive]}" -C "${P_ARR[source_dir]}" --strip-components 1; then echo "Extraction failed" exit 1 fi } addRepos() { debug "${FUNCNAME[0]}" if [[ -v P_ARR[repo] ]]; then if ! grep -q "${P_ARR[repo]}" "${P_ARR[source_dir]}/repositories.conf"; then echo "${P_ARR[repo]}" >> "${P_ARR[source_dir]}/repositories.conf" fi sed -i '/option check_signature/d' "${P_ARR[source_dir]}/repositories.conf" fi } sshBackup() { debug "${FUNCNAME[0]}" local _date _hostname _backup_fname [[ -d "$FILESDIR" ]] || mkdir -p "$FILESDIR" printf -v _date '%(%Y-%m-%d-%H-%M-%S)T' _hostname=$(ssh -qt "$SSH_BACKUP_PATH" echo -n \$HOSTNAME) _backup_fname="backup-$_hostname-$_date.tar.gz" # Make backup archive on remote debug "ssh -t $SSH_BACKUP_PATH sysupgrade -b /tmp/$_backup_fname" if ! ssh -t "$SSH_BACKUP_PATH" "sysupgrade -b /tmp/$_backup_fname"; then echo "SSH backup failed" exit 1 fi # Move backup archive locally debug "rsync -avz --remove-source-files $SSH_BACKUP_PATH:/tmp/$_backup_fname $BUILDDIR/" if ! rsync -avz --remove-source-files "$SSH_BACKUP_PATH":"/tmp/$_backup_fname" "$BUILDDIR/"; then echo "Could not copy SSH backup" exit 1 fi # Extract backup archive debug "tar -C $FILESDIR -xzf $BUILDDIR/$_backup_fname" if ! tar -C "$FILESDIR" -xzf "$BUILDDIR/$_backup_fname"; then echo "Could not extract SSH backup" exit 1 fi rm "$BUILDDIR/$_backup_fname" } makeImage() { debug "${FUNCNAME[0]}" # Reuse the existing output if [[ -d "${P_ARR[out_bin_dir]}" ]]; then if askOk "${P_ARR[out_bin_dir]} exists. Rebuild?"; then rm -rf "${P_ARR[out_bin_dir]}" else return 0 fi fi [[ ! -d "${P_ARR[out_bin_dir]}" ]] && mkdir -p "${P_ARR[out_bin_dir]}" if ! make image \ BIN_DIR="${P_ARR[out_bin_dir]}" \ PROFILE="${P_ARR[profile]}" \ PACKAGES="${P_ARR[packages]}" \ FILES="$FILESDIR" \ --directory="${P_ARR[source_dir]}" \ --jobs=$(( $(nproc) - 1 )) \ > make.log; then echo "Make image failed!" exit 1 fi } flashImage() { debug "${FUNCNAME[0]}" local _umount if [[ ! -e "$FLASH_DEV" ]]; then echo "The device specified by --flash could not be found" exit 1 fi # TODO Roughly chooses the correct image if [[ -f "${P_ARR[factory_img_gz]}" ]]; then img_gz="${P_ARR[factory_img_gz]}" img="${P_ARR[factory_img]}" elif [[ -f "${P_ARR[sysupgrade_img_gz]}" ]]; then img_gz="${P_ARR[sysupgrade_img_gz]}" img="${P_ARR[sysupgrade_img]}" else return 1 fi debug "$img_gz $img" debug "gunzip -qfk $img_gz" gunzip -qfk "$img_gz" echo "Unmounting target device $FLASH_DEV partitions" _umount=( "$FLASH_DEV"?* ) debug "umount ${_umount[*]}" sudo umount "${_umount[@]}" debug "sudo dd if=\"$img\" of=\"$FLASH_DEV\" bs=2M conv=fsync" if sudo dd if="$img" of="$FLASH_DEV" bs=2M conv=fsync; then sync echo "Image flashed sucessfully!" else echo "dd failed!" exit 1 fi } sshUpgrade() { debug "${FUNCNAME[0]}" echo "Copying \"${P_ARR[sysupgrade_bin_gz]}\" to $SSH_UPGRADE_PATH/tmp/" debug "scp \"${P_ARR[sysupgrade_bin_gz]}\" \"$SSH_UPGRADE_PATH\":\"/tmp/${P_ARR[sysupgrade_bin_gz_fname]}\"" # shellcheck disable=SC2140 if ! scp "${P_ARR[sysupgrade_bin_gz]}" "$SSH_UPGRADE_PATH":"/tmp/${P_ARR[sysupgrade_bin_gz_fname]}"; then echo "Could not access the --ssh-upgrade PATH" exit 1 fi echo "Executing remote sysupgrade" debug "ssh \"$SSH_UPGRADE_PATH\" \"sysupgrade -F /tmp/${P_ARR[sysupgrade_bin_gz_fname]}\"" # shellcheck disable=SC2029 ssh "$SSH_UPGRADE_PATH" "sysupgrade -F /tmp/${P_ARR[sysupgrade_bin_gz_fname]}" } debug() { (( DEBUG )) && echo "Running: $*"; } askOk() { local _response read -r -p "$* [y/N]" _response _response=${_response,,} [[ ! "$_response" =~ ^(yes|y)$ ]] && return 1 return 0 } reset() { debug "${FUNCNAME[0]}" askOk "Remove $FILESDIR $BUILDDIR/sources $BUILDDIR/bin?" || exit $? debug "rm -rf $FILESDIR $BUILDDIR/sources $BUILDDIR/bin" rm -rf "$FILESDIR" "${BUILDDIR:?}/sources" "${BUILDDIR:?}/bin" } loadProfiles() { debug "${FUNCNAME[0]}" declare -g SCRIPTDIR PFILE # https://stackoverflow.com/a/4774063 SCRIPTDIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 || exit $? ; pwd -P )" PFILE="$SCRIPTDIR/profiles" # shellcheck source=./profiles ! source "$PFILE" && echo "profiles file missing!" && return 1 } main() { debug "${FUNCNAME[0]}" loadProfiles readInput "$@" [[ ! ${!PROFILE@a} = A ]] && echo "Profile does not exist" && return 1 declare -gn P_ARR="$PROFILE" declare _out_prefix : "${BUILDDIR:=$SCRIPTDIR}" : "${FILESDIR:=$BUILDDIR/files}" : "${P_ARR[release]:=$RELEASE}" : "${P_ARR[source_archive]:=$BUILDDIR/sources/${P_ARR[profile]}-${P_ARR[release]}.tar.xz}" : "${P_ARR[source_dir]:=${P_ARR[source_archive]%.tar.xz}}" : "${P_ARR[out_bin_dir]:=$BUILDDIR/bin/${P_ARR[profile]}-${P_ARR[release]}}" if [[ "${P_ARR[release]}" == "snapshot" ]]; then _out_prefix="${P_ARR[out_bin_dir]}/openwrt-${P_ARR[target]//\//-}-${P_ARR[profile]}" else _out_prefix="${P_ARR[out_bin_dir]}/openwrt-${P_ARR[release]}-${P_ARR[target]//\//-}-${P_ARR[profile]}" fi : "${P_ARR[factory_img]:=$_out_prefix-${P_ARR[filesystem]}-factory.img}" : "${P_ARR[factory_img_gz]:=${P_ARR[factory_img]}.gz}" : "${P_ARR[sysupgrade_img]:=$_out_prefix-${P_ARR[filesystem]}-sysupgrade.img}" : "${P_ARR[sysupgrade_img_gz]:=${P_ARR[sysupgrade_img]}.gz}" : "${P_ARR[sysupgrade_bin]:=$_out_prefix-${P_ARR[filesystem]}-sysupgrade.img}" : "${P_ARR[sysupgrade_bin_fname]:=${P_ARR[sysupgrade_bin]##*/}}" : "${P_ARR[sysupgrade_bin_gz]:=${P_ARR[sysupgrade_bin]}.gz}" : "${P_ARR[sysupgrade_bin_gz_fname]:=${P_ARR[sysupgrade_bin_gz]##*/}}" (( RESET )) && reset if (( DEBUG )) || (( PROFILE_INFO )); then for x in "${!P_ARR[@]}"; do printf "[%s]=%s\n" "$x" "${P_ARR[$x]}"; done fi installHostDependencies getImageBuilder addRepos #copyFiles [[ -v SSH_BACKUP_PATH ]] && sshBackup if makeImage; then [[ -v SSH_UPGRADE_PATH ]] && sshUpgrade [[ -v FLASH_DEV ]] && flashImage fi } main "$@" exit