OpenWrt Forum Archive

Topic: Custom /etc/rc.button/reset script for config backup/restores

The content of this topic has been archived on 9 Apr 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

Hi everyone

I want to share my work here, in case anyone has any feedback, has done this before, or might look into doing it in the future.

I am using OpenWRT r45574 on TP-Link TL-WR710N hardware, and need to write a custom /etc/rc.button/reset script to allow for the rollback of configuration changes to a previous, safe, state.

I use these small devices as inexpensive networked serial/RS232 probes.  A USB-to-serial adapter is plugged into the USB and either the 802.11 wireless radio or the built-in ethernet interface is used to connect to the local network.

I mail these devices out to remote sites in far-away places and usually never see them again.

By default, I leave the network interfaces configured as DHCP clients, but sometimes I need to do crazy stuff like set a static IP, route between interfaces, or VPN/NAT between interfaces.

When I make such changes and then reboot to apply them, there is a chance that I will permanently lose access to the device if mistakes are made.

A solution to mitigate this risk is to make the reset button restore a previously saved configuration state, which will allow the client to be accessed once again.

I imagine that I would write a script, like "saveconfig" or "savesafe", which would then tar up /etc and other critical files to some location on the local flash. Then, when the reset button is pressed for less than 2 seconds, the reset-button script would restore this safe configuration and reboot the device. Theoretically, I could then regain control of the device since the old config was known to be working.

I can't use the default reset behavior, because that totally wipes the overlay flash via a jffs2reset. I would lose required local customization which would break the device.

So, I need a custom solution.

Has anyone done this before, and do you have any feedback?

I will be working on this today and will post a script or whatever I figure out once I do it.

I started thinking about how I would back up files and restore them.

What tool would I use? tar? What files do I need to backup and restore? Where would I get my list of files?

Fortunately, I realized that most of the work has already been done. The "sysupgrade" script already does this! I can use the /etc/sysupgrade.conf file as a list of the files I want to back up, and sysupgrade already has options to backup and restore a file.

Hmm. Off the top of my head: Backing up the current state is easy, just tar up /overlay. I'm just not sure about restoring, i.e. how well the system takes replacing /overlay while running. Maybe setting a flag and have a custom script very early in startup to (selectively) wipe and repopulate overlay before it's mounted? And maybe that's already overkill and there's a much easier solution.

However, that would actually generally be a pretty great addition towards a backup/restore functionality, a simple way to restore overlay by just putting a tar file in a special location and rebooting.

metai wrote:

Hmm. Off the top of my head: Backing up the current state is easy, just tar up /overlay. I'm just not sure about restoring, i.e. how well the system takes replacing /overlay while running. Maybe setting a flag and have a custom script very early in startup to (selectively) wipe and repopulate overlay before it's mounted? And maybe that's already overkill and there's a much easier solution.

However, that would actually generally be a pretty great addition towards a backup/restore functionality, a simple way to restore overlay by just putting a tar file in a special location and rebooting.

I thought about this, and I don't think that simply backing up the overlay is enough. It is possible to install packages or files in the right place and break networking or booting on the system. The sysupgrade config backup system already does exactly what I want, so I am using sysupgrade -b to create the backup file.

The problem I am having right now is wiping the overlay prior to restoration. Obviously we can't just do that because critical files are on the overlay, and doing an rm -rf breaks the system. This is why sysupgrade calls a bunch of functions in the /lib/upgrade/ directory which handles creating a temporary ramfs and doing a pivot-root onto it.

Okay, I've got it. With the assistance of remote hands to press the reset button, I can remotely restore from a bad configuration.

Here is my /etc/rc.button/reset script:

#!/bin/sh

PATH="$PATH:/usr/local/sbin/:/usr/local/bin/"

# Only run on button release.
[[ "${ACTION}" = "released" ]] || exit 0

# logger "$BUTTON pressed for $SEEN seconds"
# env >> /tmp/reset-button

if [[ "$SEEN" -le 1 ]] ; then
    MESSAGE="RESET BUTTON: Retain current overlay, restore safebackup, and reboot"
    echo "$MESSAGE" > /dev/console
    logger "$MESSAGE"
    safebackup restore && reboot &
elif [[ "$SEEN" -ge 3 -a "$SEEN" -le 5 ]] ; then
    MESSAGE="RESET BUTTON: Wipe overlay, restore safebackup, and reboot"
    echo "$MESSAGE" > /dev/console
    logger "$MESSAGE"
    safebackup fullrestore
elif [[ "$SEEN" -ge 9 -a "$SEEN" -le 12 ]] ; then
    MESSAGE="RESET BUTTON: Wipe overlay (factory reset) and reboot"
    echo "$MESSAGE" > /dev/console
    logger "$MESSAGE"
    jffs2reset -y && reboot &
else
    MESSAGE="RESET BUTTON: Error. Pressed for $SEEN seconds. Do nothing."
    echo "$MESSAGE" > /dev/console
    logger "$MESSAGE"
fi

And here is my safebackup script:

#!/bin/sh
# This script was written to run under the busybox ash shell.
#
# Save and restore a safe configuration backup state for the host.

export BAKDIR="/safebackups"
export SAVEFILE="/safebackups/safebackup-$(hostname)-$(date +%Y%m%d%H%M%S)-$$.tar.gz"
export RESTOREFILE=$(find /safebackups/ -maxdepth 1 -mindepth 1 -type f -name 'safebackup-*.tar.gz' | head -n 1 2> /dev/null)
export SYSUPGRADE_CONF_TAR="/tmp/sysupgrade.tgz"

PIDFILE="/tmp/$(basename $0.pid)"
MYNAME=$(basename $0)

#--

echoerr() {
    # Print errors to stderr.
    echo "$@" 1>&2;
}

f_validate_restore() {
    # Validate before a restoration.
    # If there is no restore file, there is nothing we can do.
    if ! [[ -f "$RESTOREFILE" ]] ; then
        echoerr ""
        echoerr "ERROR: No restore file could be found."
        echoerr ""
        exit 1
    fi
}

f_save() {
    # Save a backup.
    echo ""
    # Make sure the BAKDIR exists. If not, create it.
    if ! [[ -d "$BAKDIR" ]] ; then
        echo "$BAKDIR does not exist, so creating it."
        mkdir "$BAKDIR"
        chmod o-rwx "$BAKDIR"
    fi
    # Remove old backups prior to the new save. We only keep one backup at a time.
    echo -n "Removing old backups..."
    rm -rf /safebackups/safebackup-*.tar.gz
    echo " Done"
    # Save the new backup.
    sysupgrade -b "$SAVEFILE"
    echo "sysupgrade backup saved to $SAVEFILE"
    echo ""
}

f_restore() {
    # Restore the backup.
    f_validate_restore
    echo ""
    echo "Restoring sysupgrade backup from file: $RESTOREFILE"
    sysupgrade -r "$RESTOREFILE"
    echo " Done"
    echo ""
}

f_fullrestore() {
    # Wipe the overlay flash, and restore from backup.
    # This is a complicated and dangerous process. This is mostly based on what the sysupgrade script does.
    # FIXME: ext-root problems? Reference notes.
    f_validate_restore
    echo ""
    echo "Wiping overlay and restoring sysupgrade backup from file: $RESTOREFILE"
    echoerr "WARNING: Network access will be lost during this process and the host will be rebooted."
    echo ""
    # Copy the safe backup to /tmp
    cp "$RESTOREFILE" "$SYSUPGRADE_CONF_TAR" || { echoerr "ERROR: Unable cp restore file to /tmp" ; exit 1 ; }
    #
    # Source required functions.
    source /lib/functions.sh
    for EACH in /lib/upgrade/*.sh ; do source "$EACH" ; done
    #
    # run_hooks will disable the process watchdog and do other important tasks.
    run_hooks "" $sysupgrade_pre_upgrade
    #
    # No idea what this does, and may not be needed at all.
    ubus call system upgrade
    #
    # Kill off all non-critical processes.
    kill_remaining TERM ; sleep 3 ; kill_remaining KILL
    #
    # This is the important part. This runs the ramfs, pivots root, erases the overlay, and restores the config backup.
    # WARNING: Remember that when we do run_ramfs, we lose access to the old filesystem, possibly functions, envrionment, etc.
    run_ramfs 'mtd -e rootfs_data jffs2write $SYSUPGRADE_CONF_TAR rootfs_data ; reboot -f'
    #
    # Nothing from here on our matters. We've already rebooted.
    # SYSUPGRADE_CONF_TAR is restored on reboot by /lib/preinit/80_mount_root and erased by /etc/init.d/done
    #
    echo " Done"
    echo ""
}
#--

case "$1" in
    save )
        f_save
    ;;
    restore )
        f_restore
    ;;
    fullrestore )
        f_fullrestore
    ;;
    *)
        echo ""
        echo "Usage: $MYNAME save|restore|fullrestore"
        echo "  WARNING: fullrestore implies a overlay wipe and reboot"
        echo ""
        exit 1
    ;;
esac

It is important to note that you must do a "safebackup save" before any restoration can occur. No backup, no restore.

For the reset button:

0-1 seconds    Restore saved configuration and reboot. Overlay is NOT wiped.
3-5 seconds    Overlay is wiped, saved configuration is restored, host is rebooted.
9-12 seconds    Full factory reset. Overlay is wiped. No config restore.



WARNING: This works for me. Might not work for you. If you don't have a serial console to recover from, you should probably not try this at all.

My platform here is ar71xx generic, specifically ar9331 on TP-Link TL-WR710N. The reset button is recessed, so there is no chance of it being pressed accidentally. If your reset button could be accidentally triggered, you should probably take that into consideration and re-work that reset script.

(Last edited by jmomo on 3 May 2015, 01:56)

Pretty nice.
I haven't needed exactly this functionality, but nice work in any case.

One idea for further development:
I save my "good config" in a tar.gz file in my firmware binary as a custom file (<buildroot>/files/... ). I also include the scripts to unpack that archive. This makes good settings available even after a firstboot cleanup as long as I can reach the console. I can unpack good settings with one simple command. (I publish my firmware as a community firmware, so I include the settings in encrypted format. See https://forum.openwrt.org/viewtopic.php … 96#p142896 ). The unpacking might even be automated via uci-defaults files.

uci-defaults might be interesting also for you. They are normally located in /etc/uci-defaults directory, but they get deleted after being run successfully, so normally the directory seems empty after a boot. The scripts gets deleted if it exits with code 0. If it exits with 1 (or something else), the script remains there for the next boot.

http://wiki.openwrt.org/doc/uci#defaults

That provides the possibility to have something run automatically once after new flash or after firstboot cleanup. In a live router you can see the existing uci-defaults scripts in /rom/etc/uci-defaults, as /etc/uci-defaults is typically empty (after all scripts have been run ok).

uci-defaults might offer you a way to include several "good settings" in the firmware and after firstboot get one of the archives expanded based on the MAC address or something. (Two years ago I sniffed router MAC and set a few settings based on that. Check the end of https://forum.openwrt.org/viewtopic.php … 18#p185418 )

(Last edited by hnyman on 3 May 2015, 08:40)

Thanks for the advice Hnyman. I will check that out.

The discussion might have continued from here.