diff --git a/Why a Spare Router.md b/Why a Spare Router.md new file mode 100644 index 0000000..8d0138c --- /dev/null +++ b/Why a Spare Router.md @@ -0,0 +1,144 @@ +# Why a "Spare Router" Configuration? + +Many people who use OpenWrt wind up with unused routers when +they retire one for a newer device. +These are perfectly functioning devices that are perhaps older +or missing a certain function. + +They could be easily re-used and passed along to friends, family or neighbors. +BUT... you have to solve a couple problems: + +1. The router retains all your personal info: + passwords, certificates, idiosyncratic packages. etc. +2. You can't remember how it was configured, so you + can't even connect to it. + +**The remedy:** A "spare router" configuration script that +you can use when you take a router out of service. +It leaves the router with current OpenWrt firmware +configured for Wifi access, and +a known useful set of package to make it easy to reuse. +The script also prints a label that you can attach to +the router so that you can get started quickly the next +time you get it out. + +## Usage + +When you retire a router from service, run this script. +It is available at +[config-spare-router.sh](https://github.com/richb-hanover/OpenWrtScripts/blob/master/config-spare-router.sh) +To use it: + +* Connect your laptop via Ethernet to a LAN port +* Connect the router's WAN Ethernet to the Internet + (perhaps the LAN port of the new router). +* Use the LuCI GUI to reset settings to + factory default (**System -> Backup/Flash firmware**) +* Use the [Firmware Selector](https://firmware-selector.openwrt.org/) + to find and flash the latest firmware +* SSH into the router using the Ethernet connection +* Run the attached script (see the instructions within it) +* The script prints a label: cut it out and tape it to the router +* _Pro tip:_ Printing the label in 12-point type produces a + "business card" size label. +* _Pro tip:_ Snip the model number from the paper + and tape it directly to the power brick. + (Use the text from the "Power Brick Label" section.) +* _Pro tip:_ Place the router and its power brick in a Ziploc bag + to keep them together. + +The `config-spare-router.sh` script may be run multiple times without bad effect. +When the script completes, it displays configuration similar to this, +suitable for printing and taping to the router. + +```text +# ======= Printed with: print-router-label.sh ======= +# Device: Linksys E8450 (UBI) +# OpenWrt: OpenWrt 23.05.5 r24106-10cc5fcd00 +# Connect to: http://SpareRouter.local +# or: ssh root@SpareRouter.local +# LAN: 172.30.42.1 +# User: root +# Login PW: SpareRouter +# Wifi SSID: SpareRouter +# Wifi PW: +# Configured: 2024-11-28 +# === See github.com/richb-hanover/OpenWrtScripts === +# +# Label for Power Brick: Linksys E8450 (UBI) +``` + +## When you (re)deploy the router + +The default settings are (intentionally) insecure. +Remember to change the following: + +* Root password (**System -> Administration**) +* Wifi credentials (**Network -> Wireless**) +* Enable other Wifi radios (**Network -> Wireless**) +* Change the LAN interface IP address and other settings as needed (**Network -> Interfaces**) +* (Optional) Configure SQM (**Network -> SQM QoS**) +* (Optional) Change the hostname (**System -> System**) +* (Optional) Install other packages as needed +* (Optional) Travelmate (**Services -> Travelmate**) + Click the **Interface Wizard** button one time +* (Optional) Re-run the `print-router-label.sh` to create + a new label and tape it to the router, so you don't + have to fuss the next time you work on it. + +## Rationale for the configuration choices + +This script was designed for ease of use. +It presumes that it is being installed on a modern (post-2021) +router that has plenty of RAM and Flash storage, so that size +was not a consideration. + +* **Root password:** To make it easy to re-use the router, + the `root` password is set to `SpareRouter`. + There is no need for strong security here, as you will be changing + the password when you set it up in its new location. +* **LAN Address:** The LAN IP address is set to `172.30.42.1`. + This is a + [valid private IP address range](https://en.wikipedia.org/wiki/Private_network) + (like `10...` and `192.168...` subnets) but it is less commonly used. + This means that you can bring the router into virtually any + network environment without concern for IP address conflicts, + then use the LuCI GUI to configure the LAN. +* **Hostname:** is set to "SpareRouter". + Because `umdns` is installed, you can connect using + `http://SpareRouter.local` or `ssh root@SpareRouter.local` + no matter what the LAN IP address is. +* **Wifi settings:** The SSID is of the _first_ radio is set + to `SpareRouter` without encryption. + No other radios are enabled. + As with the root password, there is no need for a strong password, + because you will be changing it immediately. +* **Time Zone:** As a convenience, the time zone is set to `Americas/New York`. + You can use the LuCI GUI to re-configure as needed. +* **Software packages:** The script installs a minimal set of useful + packages that are required to bootstrap a new router. + + * **luci** Released versions of OpenWrt already install `luci`. + Re-installing does no harm. + * **umdns** Allows the router to advertise its name as "SpareRouter" + (e.g., connect using `ssh root@sparerouter.local`) + * **luci-app-sqm** All OpenWrt routers ought to have the SQM package + installed to minimize bufferbloat. Just do it. + * **travelmate** _and_ + * **luci-app-travelmate** This packages allow a router to + act as a Wifi repeater by making a wireless "uplink" + to an existing network + Even if there's no Ethernet connection for the spare routers's WAN port, + you can use the wireless uplink to download additional packages. + +## Modifications + +This script provides a stable platform for re-deploying old routers. +Feel free to make suggestions (create an Issue) for _minimal_ tweaks that +would improve the script. Enjoy! + +## Old information + +The script also has a large number of lines that are commented out. +These were steps for other packages that are not essential for a "Spare Router". +Feel free to experiment with these sections in your own copy of the script. diff --git a/opkgscript.sh b/opkgscript.sh index d46bbb0..11fddb4 100755 --- a/opkgscript.sh +++ b/opkgscript.sh @@ -11,21 +11,27 @@ # Thanks, too, to hnyman for important comments on this script # # Version history +# 0.2.3 - added support for selective IPK caching with version retention control for offline recovery installs # 0.2.2 - editorial tweaks to help text -richb-hanvover # 0.2.1 - fixed typo in awk script for dependency detection # 0.2.0 - command interface # 0.1.0 - Initial release -PCKGLIST=/etc/config/opkg.installed # default package list -SCRIPTNAME=$(basename $0) # name of this script -COMMAND="" # command to execute +PCKGLIST=/etc/config/opkg.installed # Default package list +IPK_CACHE_LIST="/etc/config/opkg.ipk_cache_list" # List of packages to cache IPKs for +IPK_CACHE_DIR="/etc/opkg/ipk_cache" # Directory to store cached IPK files +IPK_CACHE_ALL_VERSIONS=false # Cache only the latest IPK per package (default). Set to true to keep all versions. +SCRIPTNAME=$(basename $0) # Name of this script +COMMAND="" # Command to execute -INSTLIST=$(mktemp) # list of packages to install -PREQLIST=$(mktemp) # list of prerequisite packages +INSTLIST=$(mktemp) # List of packages to install +PREQLIST=$(mktemp) # List of prerequisite packages + +UPDATE=false # Update the package database +OPKGOPT="" # Options for opkg calls +VERBOSE=false # Be verbose +DEBUG_TRACE=false # Set to true to enable shell debug tracing (set -x) -UPDATE=false # update the package database -OPKGOPT="" # options for opkg calls -VERBOSE=false # be verbose cleanup () { rm -f $INSTLIST $PREQLIST @@ -40,11 +46,18 @@ Available commands: write write a list of currently installed packages install install packages on list not currently installed script output a script to install missing packages - + cache-ipks download .ipk files for specific packages to a local cache + Options: -u update the package database -t test only, execute opkg commands with --noaction -v be verbose + -x enable shell debug tracing (set -x) + +Configuration: + IPK_CACHE_ALL_VERSIONS in script controls whether to cache all .ipk versions or just the latest. + Default is to cache only the latest. Set IPK_CACHE_ALL_VERSIONS=true to retain all versions. + Warning: caching all versions may consume significant storage and must be cleaned manually. $SCRIPTNAME can be used to re-install those packages that were installed before a firmware upgrade but are not part of the new firmware image. @@ -70,35 +83,33 @@ Alternatively, you can execute to output a shell script that will contain calls to opkg to install those missing packages. This might be useful if you want to check which packages -would be installed of if you want to edit that list. +would be installed or if you want to edit that list. In order for this script to work after a firmware upgrade or reboot, the opkg database must have been updated. You can use the option -u to do this. You can specify the option -t to test what $SCRIPTNAME would do. All calls to opkg will be made with the option --noaction. This does not influence -the call to opkg to write the list of installed packages, though. +the call to opkg to write the list of installed packages, though. " } trap cleanup SIGHUP SIGINT SIGTERM EXIT # parse command line options -while getopts "htuvw" OPTS; do +while getopts "htuvx" OPTS; do case $OPTS in - t ) - OPKGOPT="$OPKGOPT --noaction";; - u ) - UPDATE=true;; - v ) - VERBOSE=true;; - [h\?*] ) - echo_usage - exit 0;; + t ) OPKGOPT="$OPKGOPT --noaction";; + u ) UPDATE=true;; + v ) VERBOSE=true;; + x ) DEBUG_TRACE=true; VERBOSE=true;; + [h\?*] ) echo_usage; exit 0;; esac done shift $(($OPTIND - 1)) +[ "$DEBUG_TRACE" = true ] && set -x + # Set the command COMMAND=$1 @@ -110,7 +121,6 @@ fi # # Help # - if [ "x$COMMAND" == "x" ]; then echo "No command specified." echo "" @@ -125,20 +135,113 @@ fi # # Write # - -if [ $COMMAND = "write" ] ; then - if $VERBOSE; then - echo "Saving package list to $PCKGLIST" - fi +if [ $COMMAND = "write" ]; then + $VERBOSE && echo "Saving package list to $PCKGLIST" # NOTE: option --noaction not valid for list-installed opkg list-installed > "$PCKGLIST" exit 0 fi # -# Update +# Cache IPKs # +if [ $COMMAND = "cache-ipks" ]; then + if [ ! -f "$IPK_CACHE_LIST" ]; then + echo "Error: IPK cache list '$IPK_CACHE_LIST' not found." + echo "Please create this file and list the packages you want to cache (one per line)." + exit 1 + fi + + if [ ! -d "$IPK_CACHE_DIR" ]; then + $VERBOSE && echo "Creating IPK cache directory: $IPK_CACHE_DIR" + mkdir -p "$IPK_CACHE_DIR" || { + echo "Error: Could not create IPK cache directory '$IPK_CACHE_DIR'." + exit 1 + } + fi + + $VERBOSE && echo "Caching IPK files from list: $IPK_CACHE_LIST to $IPK_CACHE_DIR" + $VERBOSE && echo "Checking freshness of opkg cache..." + + # Only update if lists are older than 5 minutes + CACHE_AGE_LIMIT=300 # seconds + needs_update=false + for list in /var/opkg-lists/*; do + if [ ! -f "$list" ] || [ "$(($(date +%s) - $(date -r "$list" +%s)))" -gt "$CACHE_AGE_LIMIT" ]; then + needs_update=true + break + fi + done + + if $needs_update; then + $VERBOSE && echo "Running opkg update..." + opkg update + $VERBOSE && echo "Finished opkg update" + else + echo "Skipping opkg update: cache is fresh" + fi + TOTAL=0 + DOWNLOADED=0 + SKIPPED=0 + REMOVED=0 + + while read PACKAGE; do + PACKAGE=$(echo "$PACKAGE" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + [ -z "$PACKAGE" ] && continue + TOTAL=$((TOTAL + 1)) + + IPK_LATEST_FILENAME=$(opkg info "$PACKAGE" | grep "^Filename:" | awk '{print $2}') + [ -z "$IPK_LATEST_FILENAME" ] && { + echo "Warning: Could not find filename for package '$PACKAGE'. Skipping." + continue + } + + BASE_PACKAGE_NAME=$(opkg info "$PACKAGE" | grep "^Package:" | awk '{print $2}') + IPK_LATEST_PATH="$IPK_CACHE_DIR/$IPK_LATEST_FILENAME" + + if ! $IPK_CACHE_ALL_VERSIONS && [ -n "$BASE_PACKAGE_NAME" ]; then + CACHED_FILES=$(find "$IPK_CACHE_DIR" -maxdepth 1 -type f -name "${BASE_PACKAGE_NAME}_*.ipk") + for CACHED_FILE in $CACHED_FILES; do + CACHED_FILENAME=$(basename "$CACHED_FILE") + if [ "$CACHED_FILENAME" != "$IPK_LATEST_FILENAME" ]; then + $VERBOSE && echo "Removing old version of $BASE_PACKAGE_NAME: $CACHED_FILENAME" + rm -f "$CACHED_FILE" || echo "Warning: Failed to remove $CACHED_FILE" + REMOVED=$((REMOVED + 1)) + fi + done + fi + + if [ -f "$IPK_LATEST_PATH" ]; then + echo "$PACKAGE is up to date ($IPK_LATEST_FILENAME)" + SKIPPED=$((SKIPPED + 1)) + else + $VERBOSE && echo "Downloading $PACKAGE → $IPK_LATEST_FILENAME" + opkg $OPKGOPT download "$PACKAGE" + if [ $? -eq 0 ] && [ -f "./$IPK_LATEST_FILENAME" ]; then + mv "./$IPK_LATEST_FILENAME" "$IPK_LATEST_PATH" || echo "Error: Failed to move downloaded IPK" + $VERBOSE && echo "Moved to $IPK_CACHE_DIR" + DOWNLOADED=$((DOWNLOADED + 1)) + else + echo "Warning: Download failed or file missing for '$PACKAGE'" + fi + fi + done < "$IPK_CACHE_LIST" + + echo "" + echo "IPK Cache Summary:" + printf " %-25s %d\n" "Packages scanned:" $TOTAL + printf " %-25s %d\n" "New downloads:" $DOWNLOADED + printf " %-25s %d\n" "Older versions removed:" $REMOVED + printf " %-25s %d\n" "Already up to date:" $SKIPPED + echo "" + + exit 0 +fi + +# +# Update +# if $UPDATE; then opkg $OPKGOPT update fi @@ -146,12 +249,9 @@ fi # # Check # - if [ $COMMAND == "install" ] || [ $COMMAND == "script" ]; then # detect uninstalled packages - if $VERBOSE && [ $COMMAND != "script" ]; then - echo "Checking packages... " - fi + $VERBOSE && [ $COMMAND != "script" ] && echo "Checking packages... " cat "$PCKGLIST" | while read PACKAGE SEP VERSION; do # opkg status is much faster than opkg info # it only returns status of installed packages @@ -162,10 +262,10 @@ if [ $COMMAND == "install" ] || [ $COMMAND == "script" ]; then # collect prerequisites opkg info "$PACKAGE" | awk "/^Depends: / { - sub(\"Depends: \", \"\"); \ - gsub(\", \", \"\\n\"); \ - print >> \"$PREQLIST\"; \ - }" + sub(\"Depends: \", \"\"); + gsub(\", \", \"\\n\"); + print >> \"$PREQLIST\"; + }" fi done fi @@ -173,15 +273,12 @@ fi # # Install or script # - if [ $COMMAND == "install" ]; then # install packages cat "$INSTLIST" | while read PACKAGE; do if grep -q "^$PACKAGE\$" "$PREQLIST"; then # prerequisite package, will be installed automatically - if $VERBOSE; then - echo "$PACKAGE installed automatically" - fi + $VERBOSE && echo "$PACKAGE installed automatically" else # install package opkg $OPKGOPT install $PACKAGE