Browse Source

I heard you like rewrites

bryan 2 years ago
parent
commit
35a48627b6
6 changed files with 533 additions and 490 deletions
  1. 1 0
      .gitignore
  2. 1 1
      .vscode/settings.json
  3. 1 1
      .vscode/tasks.json
  4. 0 488
      openwrtBuild
  5. 517 0
      openwrtbuilder
  6. 13 0
      openwrtbuilder.code-workspace

+ 1 - 0
.gitignore

@@ -1,4 +1,5 @@
 sources/
 bin/
 files/
+patches/
 .lock

+ 1 - 1
.vscode/settings.json

@@ -1,5 +1,5 @@
 {
-    "window.title": "openwrtBuild",
+    "window.title": "openwrtbuilder",
     "cSpell.words": [
         "infile",
         "isfile",

+ 1 - 1
.vscode/tasks.json

@@ -6,7 +6,7 @@
         {
             "label": "Build RPi4 snapshot in toolbox",
             "type": "shell",
-            "command": "toolboxRun -c openwrt ${file} --version snapshot --profile rpi-4",
+            "command": "toolbox ${file} -p r4s -d",
             "problemMatcher": []
         }
     ]

+ 0 - 488
openwrtBuild

@@ -1,488 +0,0 @@
-#!/usr/bin/env bash
-#
-# This script/function will build and flash/upgrade OpenWRT based on user-defined custom profiles
-# For Fedora/Debian/Ubuntu only
-#
-# MIT License
-# Copyright (c) 2020 Bryan Roessler
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-printHelpAndExit() {
-
-    debug "${FUNCNAME[0]}"
-
-    cat <<-'EOF'
-USAGE:
-openwrtBuild [[OPTION] [VALUE]]...
-
-If PROFILE is set and TARGET is not, buildOpenwrt can use a custom profile specified in DEFAULTS
-
-OPTIONS
-    --profile, -p PROFILE
-    --version, -v OPENWRT_VERSION
-    --builddir, -b PATH
-    --ssh-upgrade SSH_PATH
-        Example: root@192.168.1.1
-    --ssh-backup SSH_PATH
-        Enabled by default for --ssh-upgrade
-    --flash, -f DEVICE
-        Example: /dev/sdX
-    --debug, -d
-    --help, -h
-EOF
-    # Exit using passed exit code
-    [[ -z $1 ]] && exit 0 || exit "$1"
-}
-
-
-setDefaults() {
-
-    debug "${FUNCNAME[0]}"
-
-    export _target _factory_suffix _sysupgrade_suffix _profile _builddir _debug _filesroot
-    declare -ag _packages
-
-    [[ -z $_debug ]] && _debug="false" # Set to true to enable debugging by default
-    [[ -z $_builddir ]] && _builddir="$PWD"
-    [[ -z $_filesroot ]] && _filesroot="$_builddir/files/"
-
-    # Additional packages for all profiles
-    _packages+=("luci" "luci-ssl" "nano" "htop" "tcpdump" "diffutils" "tar" "iperf")
-
-    # Exit if no profile specified
-    [[ -z $_profile ]] && echo "You must specify a target profile (device)" && printHelpAndExit 1
-
-    # By default use latest release
-    [[ -z $_version ]] && _version="21.02.1"
-
-    # Custom profiles
-    # TP-Link Archer C7v2 WAP (dumb AP) w/ legacy drivers for better performance
-    if [[ "$_profile" == "archer" ]]; then
-        _profile="tplink_archer-c7-v2"
-        _target="ath79/generic"
-        _filesystem="squashfs"
-        _packages+=("-dnsmasq" \
-                    "-odhcpd" \
-                    "-iptables" \
-                    "-ath10k-firmware-qca988x-ct" \
-                    "ath10k-firmware-qca988x-ct-full-htt")
-    # Linksys EA8300 (dumb AP)
-    elif [[ "$_profile" == "linksys" ]]; then
-        _profile="linksys_ea8300"
-        _target="ipq40xx/generic"
-        _filesystem="squashfs"
-        _packages+=("-dnsmasq" \
-                    "-odhcpd" \
-                    "-iptables" \
-                    )
-    # Raspberry Pi 4B router with USB->Ethernet dongle
-    elif [[ "$_profile" == "rpi-4" ]]; then
-        _target="bcm27xx/bcm2711"
-        _filesystem="ext4"
-        _packages+=("kmod-usb-net-asix-ax88179" \
-                    "kmod-usb-net-rtl8152" \
-                    "luci-app-upnp" \
-                    "luci-app-wireguard" \
-                    "luci-app-vpn-policy-routing" \
-                    "-dnsmasq" \
-                    "dnsmasq-full" \
-                    "luci-app-ddns" \
-                    "luci-app-sqm")
-    # NanoPi R2S router
-    elif [[ "$_profile" == "r2s" ]]; then
-        _profile="friendlyarm_nanopi-r2s"
-        _target="rockchip/armv8"
-        _filesystem="ext4"
-        _packages+=("luci-app-upnp" \
-                    "luci-app-wireguard" \
-                    "luci-app-vpn-policy-routing" \
-                    "-dnsmasq" \
-                    "dnsmasq-full" \
-                    "luci-app-ddns" \
-                    "luci-app-sqm" \
-                    "luci-app-statistics" \
-                    "collectd-mod-sensors" \
-                    "collectd-mod-thermal" \
-                    "collectd-mod-conntrack" \
-                    "smcroute" \
-                    "curl" \
-                    "ethtool")
-    elif [[ "$_profile" == "r4s" ]]; then
-        _version="snapshot"
-        _profile="friendlyarm_nanopi-r4s"
-        _target="rockchip/armv8"
-        _filesystem="ext4"
-        _packages+=("luci-app-upnp" \
-                    "luci-app-wireguard" \
-                    "luci-app-vpn-policy-routing" \
-                    "-dnsmasq" \
-                    "dnsmasq-full" \
-                    "luci-app-ddns" \
-                    "luci-app-sqm" \
-                    "luci-app-statistics" \
-                    "collectd-mod-sensors" \
-                    "collectd-mod-thermal" \
-                    "collectd-mod-conntrack" \
-                    "smcroute" \
-                    "curl" \
-                    "ethtool")
-    fi
-}
-
-
-parseInput() {
-
-    debug "${FUNCNAME[0]}"
-
-    if _input=$(getopt -o +v:p:b:f:dh -l version:,profile:,builddir:,ssh-upgrade:,ssh-backup:,flash:,debug,help -- "$@"); then
-        eval set -- "$_input"
-        while true; do
-            case "$1" in
-                --version|-v)
-                    shift && _version="$1"
-                    ;;
-                --profile|-p)
-                    shift && _profile="$1"
-                    ;;
-                --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"
-                    ;;
-                --debug|-d)
-                    echo "Debugging on"
-                    _debug="true"
-                    ;;
-                --help|-h)
-                    printHelpAndExit 0
-                    ;;
-                --)
-                    shift
-                    break
-                    ;;
-            esac
-            shift
-        done
-    else
-        echo "Incorrect options provided"
-        printHelpAndExit 1
-    fi
-}
-
-
-debug () { [[ "$_debug" == "true" ]] && echo "Running: " "$@" ; }
-
-
-setVars() {
-
-    debug "${FUNCNAME[0]}"
-
-    getOS () {
-
-        debug "${FUNCNAME[0]}"
-
-        if [[ -f /etc/os-release ]]; then
-            # shellcheck disable=SC1091
-            source /etc/os-release
-            export ID="$ID"
-            echo "Detected platform: $ID"
-        else
-            echo "Cannot detect OS!"
-            exit 1
-        fi
-    }
-    getOS
-
-    export _source_archive="$_builddir/sources/$_profile-$_version.tar.xz"
-    export _source_dir="${_source_archive%.tar.xz}"
-    export _out_bin_dir="$_builddir/bin/$_profile-$_version/"
-
-    export _patches_dir="$_builddir/patches/"
-    export _files_dir="$_builddir/files/"
-
-    if [[ "$_version" == "snapshot" ]]; then
-        local _out_prefix="$_out_bin_dir/openwrt-${_target//\//-}-$_profile"
-    else
-        local _out_prefix="$_out_bin_dir/openwrt-$_version-${_target//\//-}-$_profile"
-    fi
-
-
-    export _factory_bin="$_out_prefix-$_filesystem-factory.bin"
-    export _factory_bin_fname="${_factory_bin##*/}"
-    export _factory_bin_gz="$_factory_bin.gz"
-    export _factory_bin_gz_fname="${_factory_bin_gz##*/}"
-
-    export _sysupgrade_bin="$_out_prefix-$_filesystem-sysupgrade.bin"
-    export _sysupgrade_bin_fname="${_sysupgrade_bin##*/}"
-    export _sysupgrade_bin_gz="$_sysupgrade_bin.gz"
-    export _sysupgrade_bin_gz_fname="${_sysupgrade_bin_gz##*/}"
-}
-
-
-installPrerequisites() {
-
-    debug "${FUNCNAME[0]}"
-
-    local -a _pkg_list
-    local _pkg_cmd
-
-    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
-
-    echo "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 prerequisites"
-        return 1
-    fi
-}
-
-
-acquireImageBuilder() {
-
-    debug "${FUNCNAME[0]}"
-
-    local _url _filename
-
-    if [[ "$_version" == "snapshot" ]]; then
-        # Remove existing ImageBuilders
-        [[ -f "$_source_archive" ]] && rm "$_source_archive"
-        _filename="openwrt-imagebuilder-${_target//\//-}.Linux-x86_64.tar.xz"
-        _url="https://downloads.openwrt.org/snapshots/targets/$_target/$_filename"
-    else
-        # Reuse existing ImageBuilders
-        [[ -f "$_source_archive" ]] && return 0
-        _filename="openwrt-imagebuilder-$_version-${_target//\//-}.Linux-x86_64.tar.xz"
-        _url="https://downloads.openwrt.org/releases/$_version/targets/$_target/$_filename"
-    fi
-
-    # Make sources directory if it does not exist
-    [[ ! -d "$_builddir/sources" ]] && mkdir -p "$_builddir/sources"
-
-    echo "Downloading image archive"
-    debug "axel -o $_source_archive $_url"
-    if ! axel -o "$_source_archive" "$_url" > /dev/null 2>&1; then
-        echo "Could not download Image Builder"
-        exit 1
-    fi
-}
-
-
-extractImageBuilder() {
-
-    debug "${FUNCNAME[0]}"
-
-    [[ ! -d "$_source_dir" ]] && mkdir -p "$_source_dir"
-
-    if [[ ! -f "$_source_archive" ]]; then
-        echo "Archive missing"
-        exit 1
-    fi
-
-    echo "Extracting image archive"
-    debug "tar -xf $_source_archive -C $_source_dir --strip-components 1"
-    if ! tar -xf "$_source_archive" -C "$_source_dir" --strip-components 1; then
-        echo "Extraction failed"
-        exit 1
-    fi
-}
-
-
-# copyFiles() {
-
-#     debug "${FUNCNAME[0]}"
-
-#     declare -l _this_files_dir="$_files_dir/$_profile"
-
-#     [[ ! -d "$_files_dir" ]] && return
-
-#     $_profile == "r2s"
-
-
-# }
-
-
-makeImage() {
-
-    debug "${FUNCNAME[0]}"
-
-    # move to extracted source directory
-    if ! pushd "$_source_dir" > /dev/null 2>&1; then
-        exit 1
-    fi
-
-    # Make bin dir
-    [[ ! -d "$_out_bin_dir" ]] && mkdir -p "$_out_bin_dir"
-
-    # build image
-    echo "Running make -j4 image BIN_DIR=$_out_bin_dir PROFILE=$_profile PACKAGES=${_packages[*]} FILES=$_filesroot"
-    debug "make -j4 image BIN_DIR=$_out_bin_dir PROFILE=$_profile PACKAGES=${_packages[*]} FILES=$_filesroot > make.log"
-    if ! make image BIN_DIR="$_out_bin_dir" PROFILE="$_profile" PACKAGES="${_packages[*]}" FILES="$_filesroot" > make.log; then
-        echo "Make image failed!"
-        exit 1
-    fi
-
-    if ! popd > /dev/null 2>&1; then
-        exit 1
-    fi
-}
-
-
-extractImage() {
-
-    debug "${FUNCNAME[0]}" "$@"
-
-    local _gz
-
-    [[ $# -lt 1 ]] && echo "extractImage() requires at least one argument" && exit 1
-
-    for _gz in "$@"; do
-        [[ ! -f "$_gz" ]] && return 1
-        debug "gunzip -qfk $_gz"
-        if ! gunzip -qfk "$_gz"; then
-            echo "$_gz extraction failed!"
-        fi
-    done
-}
-
-
-flashImage() {
-
-    debug "${FUNCNAME[0]}"
-
-    if [[ -z $_factory_bin && -f "$_factory_bin_gz" ]]; then
-        extractImage "$_factory_bin_gz"
-    fi
-
-    if [[ ! -e "$_flash_dev" ]]; then
-        echo "The device specified by --flash could not be found"
-        exit 1
-    fi
-
-    echo "Unmounting target device $_flash_dev partitions"
-    debug "umount $_flash_dev?*"
-    sudo umount "$_flash_dev"?*
-
-    debug "sudo dd if=\"$_factory_bin\" of=\"$_flash_dev\" bs=2M conv=fsync"
-    if sudo dd if="$_factory_bin" of="$_flash_dev" bs=2M conv=fsync; then
-        sync
-        echo "Image flashed sucessfully!"
-    else
-        echo "dd failed!"
-        exit 1
-    fi
-}
-
-
-sshBackup() {
-
-    debug "${FUNCNAME[0]}"
-
-    local _source="$1"
-    local _random="$RANDOM"
-
-    if ! ssh -t "$_source" "sysupgrade -b /tmp/backup-${_random}.tar.gz"; then
-        echo "SSH backup failed"
-        exit 1
-    fi
-    if ! scp "$_source":/tmp/backup-"${_random}".tar.gz "$_builddir"; then
-        echo "Could not copy SSH backup"
-        exit 1
-    fi
-
-    if ! ssh -t "$_source" "rm -f /tmp/backup-${_random}.tar.gz"; then
-        echo "Could not remove /tmp/backup-${_random}.tar.gz from $_source"
-    fi
-
-    [[ -d "$_filesroot" ]] && rm -rf "$_filesroot"
-    mkdir -p "$_filesroot"
-
-    if ! tar xzf "$_builddir/backup-${_random}.tar.gz" etc/ -C "$_filesroot"; then
-        "Could not extract SSH backup"
-        exit 1
-    fi
-
-    rm "$_builddir/backup-${_random}.tar.gz"
-
-}
-
-
-sshUpgrade() {
-
-    debug "${FUNCNAME[0]}"
-
-    if [[ -f "$_sysupgrade_bin_gz" ]]; then
-        local _source="$_sysupgrade_bin_gz"
-        local _source_fname="$_sysupgrade_bin_gz_fname"
-    elif [[ -f "$_sysupgrade_bin" ]]; then
-        local _source="$_sysupgrade_bin"
-        local _source_fname="$_sysupgrade_bin_fname"
-    else
-        echo "Could not find upgrade file"
-        exit 1
-    fi
-
-    echo "Copying \"$_source\" to $_ssh_upgrade_path/tmp/"
-    debug "scp \"$_source\" \"$_ssh_upgrade_path\":\"/tmp/$_source_fname\""
-    # shellcheck disable=SC2140
-    if ! scp "$_source" "$_ssh_upgrade_path":"/tmp/$_source_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/$_source_fname\""
-    # shellcheck disable=SC2029
-    ssh "$_ssh_upgrade_path" "sysupgrade -F /tmp/$_source_fname"
-}
-
-
-
-
-__main() {
-
-    parseInput "$@"
-    setDefaults
-    setVars
-    installPrerequisites
-    acquireImageBuilder
-    extractImageBuilder
-    #copyFiles
-    rm -rf "$_ssh_backup_path"
-    [[ -v _ssh_backup_path ]] && sshBackup "$_ssh_backup_path"
-    if makeImage; then
-        [[ -v _ssh_upgrade_path ]] && sshUpgrade
-        [[ -v _flash_dev ]] && flashImage
-    fi
-}
-
-__main "$@"
-exit $?

+ 517 - 0
openwrtbuilder

@@ -0,0 +1,517 @@
+#!/usr/bin/env bash
+#
+# Build and flash/upgrade OpenWRT devices
+#
+# Apache 2.0 License
+
+printHelpAndExit() {
+
+    debug "${FUNCNAME[0]}"
+
+    cat <<-'EOF'
+USAGE:
+openwrtbuilder [[OPTION] [VALUE]]...
+
+If PROFILE is set and TARGET is not, openwrtbuild can use a custom profile specified in DEFAULTS
+
+OPTIONS
+    --profile, -p PROFILE
+    --profile-info, -i PROFILE
+    --list-profiles, -l
+    --release, -r RELEASE
+    --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
+    --debug, -d
+    --help, -h
+EOF
+    # Exit using passed exit code
+    [[ -z $1 ]] && exit 0 || exit "$1"
+}
+
+
+input() {
+
+    debug "${FUNCNAME[0]}"
+
+    if _input=$(getopt -o +v:p:i:lb:f:dh -l release:,profile:,profile-info:,list-profiles,builddir:,ssh-upgrade:,ssh-backup:,flash:,debug,help -- "$@"); then
+        eval set -- "$_input"
+        while true; do
+            case "$1" in
+                --release|-r)
+                    shift && RELEASE="$1"
+                    ;;
+                --profile|-p)
+                    shift && PROFILE="$1"
+                    ;;
+                --profile-info|-i)
+                    shift && profileInfo "$1" && exit $?
+                    ;;
+                --list-profile|-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"
+                    ;;
+                --debug|-d)
+                    echo "Debugging on"
+                    DEBUG=true
+                    ;;
+                --help|-h)
+                    printHelpAndExit 0
+                    ;;
+                --)
+                    shift
+                    break
+                    ;;
+            esac
+            shift
+        done
+    else
+        echo "Incorrect options provided"
+        printHelpAndExit 1
+    fi
+}
+
+
+profiles() {
+
+    debug "${FUNCNAME[0]}"
+
+    [[ -z $DEBUG ]] && DEBUG=false # Set to true to enable debugging by default
+    [[ -z $BUILDDIR ]] && BUILDDIR="$PWD"
+    [[ -z $FILESDIR ]] && FILESDIR="$BUILDDIR/files/"
+
+    # Additional packages to install for all profiles
+    default_packages="\
+        luci \
+        luci-ssl \
+        nano \
+        htop \
+        tcpdump \
+        diffutils \
+        tar \
+        iperf "
+
+    # Set the default release
+    [[ -z $RELEASE ]] && RELEASE="21.02.1"
+
+    # Use these tools to add and parse profiles
+    declare -ag PROFILES
+    add_profile() { 
+        declare -Ag "$1"
+        PROFILES+=("$1")
+    }
+
+    add_profile archer
+    archer['profile']="tplink_archer-c7-v2"
+    archer['target']="ath79/generic"
+    archer['filesystem']="squashfs"
+    archer['packages']="\
+        $default_packages \
+        -dnsmasq \
+        -odhcpd \
+        -iptables \
+        -ath10k-firmware-qca988x-ct \
+        ath10k-firmware-qca988x-ct-full-htt"
+
+    add_profile linksys
+    linksys['profile']="linksys_ea8300"
+    linksys['target']="ipq40xx/generic"
+    linksys['filesystem']="squashfs"
+    linksys['packages']="\
+        $default_packages \
+        -dnsmasq \
+        -odhcpd \
+        -iptables"
+
+    add_profile rpi4
+    rpi4['profile']="rpi-4"
+    rpi4['target']="bcm27xx/bcm2711"
+    rpi4['filesystem']="ext4"
+    rpi4['packages']="\
+        $default_packages \
+        kmod-usb-net-asix-ax88179 \
+        kmod-usb-net-rtl8152 \
+        luci-app-upnp \
+        luci-app-wireguard \
+        luci-app-vpn-policy-routing \
+        -dnsmasq \
+        dnsmasq-full \
+        luci-app-ddns \
+        luci-app-sqm"
+
+    add_profile r2s
+    r2s['profile']="friendlyarm_nanopi-r2s"
+    r2s['target']="rockchip/armv8"
+    r2s['filesystem']="ext4"
+    r2s['packages']="\
+        $default_packages \
+        luci-app-upnp \
+        luci-app-wireguard \
+        luci-app-vpn-policy-routing \
+        -dnsmasq \
+        dnsmasq-full \
+        luci-app-ddns \
+        luci-app-sqm \
+        luci-app-statistics \
+        collectd-mod-sensors \
+        collectd-mod-thermal \
+        collectd-mod-conntrack \
+        smcroute \
+        curl \
+        ethtool"
+
+    add_profile r4s
+    r4s['release']="snapshot"
+    r4s['profile']="friendlyarm_nanopi-r4s"
+    r4s['target']="rockchip/armv8"
+    r4s['filesystem']="ext4"
+    r4s['packages']="\
+        $default_packages \
+        luci-app-upnp \
+        luci-app-wireguard \
+        luci-app-vpn-policy-routing \
+        -dnsmasq \
+        dnsmasq-full \
+        luci-app-ddns \
+        luci-app-sqm \
+        luci-app-statistics \
+        collectd-mod-sensors \
+        collectd-mod-thermal \
+        collectd-mod-conntrack \
+        smcroute \
+        curl \
+        ethtool"
+
+    for PNAME in "${PROFILES[@]}"; do
+        declare -n ARR="$PNAME"
+        local _out_prefix
+
+        [[ ! -v ARR['release'] ]] && ARR['release']="$RELEASE"
+        ARR['source_archive']="$BUILDDIR/sources/${ARR[profile]}-${ARR[release]}.tar.xz"
+        ARR['source_dir']="${ARR[source_archive]%.tar.xz}"
+        ARR['out_bin_dir']="$BUILDDIR/bin/${ARR[profile]}-${ARR[release]}"
+
+        #_patches_dir="$BUILDDIR/patches/"
+        #_files_dir="$BUILDDIR/files/"
+
+        if [[ "${ARR[release]}" == "snapshot" ]]; then
+            _out_prefix="${ARR[out_bin_dir]}/openwrt-${ARR[target]//\//-}-${ARR[profile]}"
+        else
+            _out_prefix="${ARR[out_bin_dir]}/openwrt-${ARR[release]}-${ARR[target]//\//-}-${ARR[profile]}"
+        fi
+
+        ARR['factory_img']="$_out_prefix-${ARR[filesystem]}-factory.img"
+        ARR['factory_img_gz']="${ARR[factory_img]}.gz"
+
+        ARR['sysupgrade_img']="$_out_prefix-${ARR[filesystem]}-sysupgrade.img"
+        ARR['sysupgrade_img_gz']="${ARR[sysupgrade_img]}.gz"
+
+        ARR['sysupgrade_bin']="$_out_prefix-${ARR[filesystem]}-sysupgrade.bin"
+        ARR['sysupgrade_bin_fname']="${ARR[sysupgrade_bin]##*/}"
+        ARR['sysupgrade_bin_gz']="${ARR[sysupgrade_bin]}.gz"
+        ARR['sysupgrade_bin_gz_fname']="${ARR[sysupgrade_bin_gz]##*/}"
+    done
+}
+
+
+listProfiles() {
+    debug "${FUNCNAME[0]}"
+    [[ ! -v PROFILES ]] && profiles
+    echo "Available profiles:"
+    for PNAME in "${PROFILES[@]}"; do
+        declare -n ARR2="$PNAME"
+        echo "$PNAME: ${ARR2[profile]}"
+    done
+}
+
+
+profileInfo() {
+    debug "${FUNCNAME[0]}"
+    local _profile 
+    _profile="$1"
+    [[ ! -v PROFILES ]] && profiles
+    declare -n ARR3="$_profile"
+    for i in "${!ARR3[@]}"; do
+        echo "$i: ${ARR3[i]}"
+    done
+}
+
+
+prerequisites() {
+
+    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
+
+    echo "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 prerequisites"
+        return 1
+    fi
+}
+
+
+getImageBuilder() {
+
+    debug "${FUNCNAME[0]}"
+
+    declare -n ARR4="$PROFILE"
+
+    local _url _filename
+
+    if [[ "${ARR4[release]}" == "snapshot" ]]; then
+        _filename="openwrt-imagebuilder-${ARR4[target]//\//-}.Linux-x86_64.tar.xz"
+        _url="https://downloads.openwrt.org/snapshots/targets/${ARR4[target]}/$_filename"
+        if [[ -f "${ARR4[source_archive]}" ]]; then
+            if askOk "Update ImageBuilder snapshot?"; then
+                rm -f "${ARR4[source_archive]}"
+            else
+                return 0
+            fi
+        fi
+    else
+        _filename="openwrt-imagebuilder-${ARR4[release]}-${ARR4[target]//\//-}.Linux-x86_64.tar.xz"
+        _url="https://downloads.openwrt.org/releases/${ARR4[release]}/targets/${ARR4[target]}/$_filename"
+        [[ -f "${ARR4[source_archive]}" ]] && return 0 # Reuse existing ImageBuilders
+    fi
+    
+    # Make sources directory if it does not exist
+    [[ ! -d "$BUILDDIR/sources" ]] && mkdir -p "$BUILDDIR/sources"
+
+    echo "Downloading imagebuilder archive"
+    debug "axel -o ${ARR4[source_archive]} $_url"
+    if ! axel -o "${ARR4[source_archive]}" "$_url" > /dev/null 2>&1; then
+        echo "Could not download imagebuilder archive"
+        exit 1
+    fi
+
+    if [[ ! -f "${ARR4[source_archive]}" ]]; then
+        echo "Archive missing"
+        exit 1
+    fi
+
+    echo "Extracting image archive"
+    debug "tar -xf ${ARR4[source_archive]} -C ${ARR4[source_dir]} --strip-components 1"
+    if ! tar -xf "${ARR4[source_archive]}" -C "${ARR4[source_dir]}" --strip-components 1; then
+        echo "Extraction failed"
+        exit 1
+    fi
+}
+
+
+# copyFiles() {
+#     debug "${FUNCNAME[0]}"
+#     declare -l _this_files_dir="$_files_dir/$PROFILE"
+#     [[ ! -d "$_files_dir" ]] && return
+#     $PROFILE == "r2s"
+# }
+
+
+makeImage() {
+
+    debug "${FUNCNAME[0]}"
+
+    declare -n ARR5="$PROFILE"
+
+    # Reuse the existing output
+    if [[ -d "${ARR5[out_bin_dir]}" ]]; then
+        if askOk "${ARR5[out_bin_dir]} exists. Rebuild?"; then
+            rm -rf "${ARR5[out_bin_dir]}"
+        else
+            return 0
+        fi
+    fi
+
+    [[ ! -d "${ARR5[out_bin_dir]}" ]] && mkdir -p "${ARR5[out_bin_dir]}"
+
+    # build image
+    echo "Running make -j4 image BIN_DIR=${ARR5[out_bin_dir]} PROFILE=${ARR5[profile]} PACKAGES=${ARR5[packages]} FILES=$FILESDIR"
+    debug "make -j4 image BIN_DIR=${ARR5[out_bin_dir]} PROFILE=${ARR5[profile]} PACKAGES=${ARR5[packages]} FILES=$FILESDIR --directory=${ARR5[source_dir]} > make.log"
+    if ! make image BIN_DIR="${ARR5[out_bin_dir]}" PROFILE="${ARR5[profile]}" PACKAGES="${ARR5[packages]}" FILES="$FILESDIR" --directory="${ARR5[source_dir]}"  > make.log; then
+        echo "Make image failed!"
+        exit 1
+    fi
+}
+
+
+flashImage() {
+
+    debug "${FUNCNAME[0]}"
+
+    declare -n ARR6="$PROFILE"
+
+    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 "${ARR6[factory_img_gz]}" ]]; then
+        img_gz="${ARR6[factory_img_gz]}"
+        img="${ARR6[factory_img]}"
+    elif [[ -f "${ARR6[sysupgrade_img_gz]}" ]]; then
+        img_gz="${ARR6[sysupgrade_img_gz]}"
+        img="${ARR6[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
+}
+
+
+sshBackup() {
+
+    debug "${FUNCNAME[0]}"
+
+    local _random="$RANDOM"
+
+    if ! ssh -t "$SSH_BACKUP_PATH" "sysupgrade -b /tmp/backup-${_random}.tar.gz"; then
+        echo "SSH backup failed"
+        exit 1
+    fi
+    if ! scp "$SSH_BACKUP_PATH":/tmp/backup-"${_random}".tar.gz "$BUILDDIR"; then
+        echo "Could not copy SSH backup"
+        exit 1
+    fi
+
+    if ! ssh -t "$SSH_BACKUP_PATH" "rm -f /tmp/backup-${_random}.tar.gz"; then
+        echo "Could not remove /tmp/backup-${_random}.tar.gz from $SSH_BACKUP_PATH"
+    fi
+
+    [[ -d "$FILESDIR" ]] && rm -rf "$FILESDIR"
+    mkdir -p "$FILESDIR"
+
+    if ! tar xzf "$BUILDDIR/backup-${_random}.tar.gz" etc/ -C "$FILESDIR"; then
+        "Could not extract SSH backup"
+        exit 1
+    fi
+
+    rm "$BUILDDIR/backup-${_random}.tar.gz"
+
+}
+
+
+sshUpgrade() {
+
+    debug "${FUNCNAME[0]}"
+
+    declare -n ARR7="$PROFILE"
+
+    echo "Copying \"${ARR7[sysupgrade_bin_gz]}\" to $SSH_UPGRADE_PATH/tmp/"
+    debug "scp \"${ARR7[sysupgrade_bin_gz]}\" \"$SSH_UPGRADE_PATH\":\"/tmp/${ARR7[sysupgrade_bin_gz_fname]}\""
+    # shellcheck disable=SC2140
+    if ! scp "${ARR7[sysupgrade_bin_gz]}" "$SSH_UPGRADE_PATH":"/tmp/${ARR7[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/${ARR7[sysupgrade_bin_gz_fname]}\""
+    # shellcheck disable=SC2029
+    ssh "$SSH_UPGRADE_PATH" "sysupgrade -F /tmp/${ARR7[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
+}
+
+
+main() {
+
+    input "$@"
+    profiles
+    prerequisites
+    getImageBuilder
+    copyFiles
+    [[ -v SSH_BACKUP_PATH ]] && sshBackup
+    if makeImage; then
+        [[ -v SSH_UPGRADE_PATH ]] && sshUpgrade
+        [[ -v flash_dev ]] && flashImage
+    fi
+}
+
+main "$@"
+exit $?

+ 13 - 0
openwrtbuilder.code-workspace

@@ -0,0 +1,13 @@
+{
+	"folders": [
+		{
+			"path": "."
+		}
+	],
+	"settings": {
+		"window.title": "openwrtbuilder",
+		"cSpell.words": [
+			"padx"
+		]
+	}
+}