Topic: Identify packages to be re-installed after system upgrade

Hello,

I'm looking for an easy way to re-install all packages after a sysupgrade that were installed before the upgrade.

I guess I could save a list of all packages installed before the upgrade. After the upgrade, I would then install all packages on that list that are not already installed. That could be handled by a simple script.

Is it possible to identify the installed packages that were not part of the firmware image but installed later on?

How do you do this? Wouldn't it nice to have a script like that, or a command in LuCI?

Thanks, Malte

2 (edited by lizby 2013-03-07 19:27:02)

Re: Identify packages to be re-installed after system upgrade

opkg list-installed

To save a file which will be preserved over sysupgrade:

opkg list-installed > /etc/config/opkg_installed

This doesn't differentiate between files in the orignal image and those installed later, but you can compare that what you have in the new installation.

Re: Identify packages to be re-installed after system upgrade

I have written a small script that can be used to
  * save a list of the currently installed packages before a firmware upgrade, and
  * use that list to reinstall all those packages after a firmware upgrade.
The script takes care not to directly install packages that will be installed automatically in order to fulfill dependencies.

Ideally, a script like this should be installed as part of the firmware image. May be, one the default packages could adopt it?

I'd happily provide my script if there is any interest. Unfortenately, this forum does not seem to support attachments. What is the proper way to make the script available?

4 (edited by hnyman 2013-03-23 13:03:30)

Re: Identify packages to be re-installed after system upgrade

You can include the script here inside [ code] and [ /code] tags  (but of course without space after [ that I included to prevent action.)

5 (edited by mforkel 2013-03-23 09:50:21)

Re: Identify packages to be re-installed after system upgrade

Ok, here's my script. Comments are welcome.

#! /bin/sh

# Write a list of packages currently installed or read that list,
# presumably after a firmware upgrade, in order to reinstall all packages
# on that list not currently installed
#
# (c) 2013 Malte Forkel <malte.forkel@berlin.de>
#
# Version history
#    0.1.0 - Initial release

PCKGLIST=/etc/config/opkg.installed  # default package list
INSTLIST=$(mktemp)                   # list of packages to install
PREQLIST=$(mktemp)                   # list of prerequisite packages

WRITE=false                          # write a package list
UPDATE=false                         # update the package database
ACTOPT=""                            # test only, no action
VERBOSE=false                        # be verbose

cleanup () {
    rm -f $INSTLIST $PREQLIST
}

echo_usage () {
    echo \
"Usage: $(basename $0) [options] [packagelist]

The default package list is $PCKGLIST.

Options:
  -h     print this help message
  -t     test only, execute opkg commands with --noaction
  -u     update the package database
  -v     be verbose
  -w     write the list of currently installed packages"
}

trap cleanup SIGHUP SIGINT SIGTERM EXIT

# parse command line options
while getopts "htuvw" OPTS; do
    case $OPTS in
        t )
            ACTOPT="--noaction";;
        u )
            UPDATE=true;;
        v )
            VERBOSE=true;;
        w )
            WRITE=true;;
        [h\?*] )
            echo_usage
            exit 0;;
    esac
done
shift $(($OPTIND - 1))

# Set name of the package list
if [ "x$1" != "x" ]; then
    PCKGLIST="$1"
fi

#
# Write
#

if $WRITE; then
    if $VERBOSE; then
        echo "Saving package list to $PCKGLIST"
    fi
    # NOTE: option --noaction not valid for list-installed
    opkg list-installed > "$PCKGLIST"
    exit 0
fi

#
# Update 
#

if $UPDATE; then
    opkg $ACTOPT update
fi

#
# Install
#

# detect uninstalled packages
if $VERBOSE; then
    echo "Checking packages... "
fi
cat "$PCKGLIST" | while read PACKAGE SEP VERSION; do
    # opkg status is much faster than opkg info
    # it only returns status of installed packages
    #if ! opkg status $PACKAGE | grep -q "^Status:.* installed"; then
    if [ "x$(opkg status $PACKAGE)" == "x" ]; then
        # collect uninstalled packages
        echo $PACKAGE >> $INSTLIST
        # collect prerequisites
        opkg info "$PACKAGE" |
        awk "/^Depends: / {
                            sub(\"Depends: \", \"\");   \
                            gsub(\", \", \"\\n\");      \
                            print >> \"$PREQLIST\";      \
                          }"
    fi
done

# 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
    else
        # install package
        opkg $ACTOPT install $PACKAGE
    fi
done

# clean up and exit
exit 0

EDIT: You'll find an improved version further down

6 (edited by hnyman 2013-03-10 12:56:26)

Re: Identify packages to be re-installed after system upgrade

Interesting.

I would make the script more foolproof in order to avoid mistakes.
Default action (with no arguments) should be -h = help text.

Currently the install process seems to be the default action, if no argument is given.
Install process should always require an active argument.

The help text might also include explanation of the workflow. If I got it right, you have to "write list" before sysupgrade to the config directory, so that the list survives sysupgrade. After the flash you have to update opkg package list, determine packages to be installed and finally install them.

I would also separate the "determine packages to be installed" and the actual installation step. Most users would probably like to first see the list of packages to be installed, in any case. IT seems to be possible also now, if the -t option is used, but even then it also proceeds to the install itself.


Interesting script in any case. I am publishing a community build here for wndr3700, and might include a polished version of this script to it.

EDIT:
Additional functionality: there might also be an option to echo/print the list of the final opkg install commands. The user might want to remove unneeded packages from the list, and thus having the script to produce a command list might be useful.

7 (edited by mforkel 2013-03-23 09:47:09)

Re: Identify packages to be re-installed after system upgrade

Thanks for your suggestions! I think, those are valid points. I tried to adapt my script accordingly.

Feel free to change the script according to your needs. But I would like it even better if we cooperated and came up with one version that we both like.

Here is my new version:

#! /bin/sh

# Write a list of packages currently installed or read that list,
# presumably after a firmware upgrade, in order to reinstall all packages
# on that list not currently installed
#
# (c) 2013 Malte Forkel <malte.forkel@berlin.de>
#
# Version history
#    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

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

cleanup () {
    rm -f $INSTLIST $PREQLIST
}

echo_usage () {
    echo \
"Usage: $(basename $0) [options...] command [packagelist]

Available commands:
    help                print this help text
    write               write a list of currently installed packages
    install             install packages on list not currently installed
    script              output a script to install missing packages
    
Options:
    -u                  update the package database
    -t                  test only, execute opkg commands with --noaction
    -v                  be verbose

$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.

Before the firmware upgrade, execute

    $SCRIPTNAME [options...] write [packagelist]
    
to save the list of currently installed packages. The default package list
is '$PCKGLIST'. Save the package list in a place that will
not be wiped out by the firmware upgrade or copy it to another computer 
before the upgrade.

After the firmware upgrade, execute

    $SCRIPTNAME [options...] install [packagelist]
    
to re-install all packages that were not part of the firmware image.
Alternatively, you can execute

    $SCRIPTNAME [options...] script [packagelist]
    
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.

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. 
"
}

trap cleanup SIGHUP SIGINT SIGTERM EXIT

# parse command line options
while getopts "htuvw" OPTS; do
    case $OPTS in
        t )
            OPKGOPT="$OPKGOPT --noaction";;
        u )
            UPDATE=true;;
        v )
            VERBOSE=true;;
        [h\?*] )
            echo_usage
            exit 0;;
    esac
done
shift $(($OPTIND - 1))

# Set the command
COMMAND=$1

# Set name of the package list
if [ "x$2" != "x" ]; then
    PCKGLIST="$2"
fi

#
# Help
#

if [ "x$COMMAND" == "x" ]; then
    echo "No command specified."
    echo ""
    COMMAND="help"
fi

if [ $COMMAND == "help" ]; then
    echo_usage
    exit 0
fi

#
# Write
#

if [ $COMMAND = "write" ] ; then
    if $VERBOSE; then
        echo "Saving package list to $PCKGLIST"
    fi
    # NOTE: option --noaction not valid for list-installed
    opkg list-installed > "$PCKGLIST"
    exit 0
fi

#
# Update 
#

if $UPDATE; then
    opkg $OPKGOPT update
fi

#
# Check
#

if [ $COMMAND == "install" ] || [ $COMMAND == "script" ]; then
    # detect uninstalled packages
    if $VERBOSE && [ $COMMAND != "script" ]; then
        echo "Checking packages... "
    fi
    cat "$PCKGLIST" | while read PACKAGE SEP VERSION; do
        # opkg status is much faster than opkg info
        # it only returns status of installed packages
        #if ! opkg status $PACKAGE | grep -q "^Status:.* installed"; then
        if [ "x$(opkg status $PACKAGE)" == "x" ]; then
            # collect uninstalled packages
            echo $PACKAGE >> $INSTLIST
            # collect prerequisites
            opkg info "$PACKAGE" |
            awk "/^Depends: / {
                                sub(\"Depends: \", \"\");   \
                                gsub(\", \", \"\\n\");      \
                                print >> \"$PREQLIST\";      \
                              }"
        fi
    done
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
        else
            # install package
            opkg $OPKGOPT install $PACKAGE
        fi
    done
elif [ $COMMAND == "script" ]; then
    # output install script
    echo "#! /bin/sh"
    cat "$INSTLIST" | while read PACKAGE; do
        if ! grep -q "^$PACKAGE\$" "$PREQLIST"; then
            echo "opkg install $PACKAGE"
        fi
    done
else
    echo "Unknown command '$COMMAND'."
    echo ""
    echo_usage
    exit 1
fi

# clean up and exit
exit 0

EDIT: Fixed awk script, thanks to hnyman

8 (edited by hnyman 2013-03-10 21:34:04)

Re: Identify packages to be re-installed after system upgrade

Thanks,
I included that in my newest firmware build and flashed it.

It seems to identify the packages ok, but does not determine dependencies correctly.
I tested with openvpn-openssl and it does not recognize that liblzo is a dependency and wants to install that too.

But otherwise it seems to work.

EDIT:
I am no awk specialist, but I think that your script expects spaces before "Depends:" string, right?
Trunk opkg info does not have spaces, instead the line starts with Depends:

root@OpenWrt:/etc# opkg info openvpn-openssl
Package: openvpn-openssl
Version: 2.3.0-1
Depends: libc, kmod-tun, liblzo, libopenssl
            opkg info "$PACKAGE" |
            awk "/    Depends: / {
                                sub(\"Depends: \", \"\");   \

If I remove the spaces before the first Depends, the dependency detection seems to work.

Re: Identify packages to be re-installed after system upgrade

Sorry for the late reply. Even though I'm subscribed this topic, I didn't receive a notification about your post...

Thanks for pointing out the awk script bug. You're right: The leading spaces are wrong. Actually, that line should read

        awk "/^Depends: / {

I remember doing some editing like 's/^\t/    /', not realizing that OpenWrt's vi does not recognize '^' as a special character in regular expressions. I'm sorry.

Your pattern should work as well.

Re: Identify packages to be re-installed after system upgrade

This is a very nice script :-)

Thanks for the hard work. It shoud go into the Wiki.

Re: Identify packages to be re-installed after system upgrade

I did get a notification for your post, written_direcon smile! Glad you like the script.

Where do you think it would fit into the wiki? To my surprise, I haven't even found a page for backup & restore of the configuration files using LuCI.

Ideally, I think, some functionality like this should be included in the firmware. And made accessible through System > Backup / Flash Firmware > Backup / Restore. Because its probably most useful if its available after you have (re)flashed your device and want to restore your configuration.

I could put the script in a separate package. But I'd rather like to see it included in the existing stuff. Unfortenately, I don't know enough about LuCI to prepare a patch like that. Whom would I have to bribe?

Re: Identify packages to be re-installed after system upgrade

Thanks. After fixing that awk string, the dependency checking seems to work ok.

As editing old messages is possible, I suggest that you correct the awk string in your message with the script, so that the script there is correct and the final version. (You might maybe also remove the first version of the script.)

13 (edited by mforkel 2013-03-23 09:51:17)

Re: Identify packages to be re-installed after system upgrade

Good idea! I fixed the script and added a note to the inital version.

Re: Identify packages to be re-installed after system upgrade

I'd remove the scripts in all posts in this thread here and just put the current script into the first post. Makes more sense :-)

Re: Identify packages to be re-installed after system upgrade

mforkel wrote:

Where do you think it would fit into the wiki?

http://wiki.openwrt.org/doc/howto/snippets
Then a link to there in every other place you fell fit wink

Netgear WNR854T (ARM Marvell Orion CPU 500MHz, Marvell 88W8361P mini-PCI STA only, 8/32MB) - trunk r17427 since 09/09/09 to 06-06-12 GLOD
TP-LINK TL-WR741ND v1.9 (Atheros AR7240 CPU 350MHz, Atheros AR9285 Chipset, 4/32MB) - trunk r23281 since 10/10/10
TP-LINK TL-MR3420 v1.1, TL-MR3220 v1.2 - trunk r25302 since 11/11/11
TP-LINK TL-WR842ND v1.0, TL-WR1043ND v1.8 - 12.09-rc1 since 12/12/12

16 (edited by mforkel 2013-03-28 17:37:53)

Re: Identify packages to be re-installed after system upgrade

Again, I didn't get a notification for you post. :-(

Thanks for your suggestion. I wrote Reinstalling Packages after Firmware Upgrade in Snippets.

17 (edited by Rafciq 2014-03-07 20:12:57)

Re: Identify packages to be re-installed after system upgrade

Hi,
I wrote a script to automate the process of updating the system (for OpenWrt Attitude Adjustment). With this script updating the firmware with the packages installation it is just simply one command 'install.sh sysupgrade'.

#!/bin/sh
# Install or download packages and/or sysupgrade.
# Script version 1.33 Rafal Drzymala 2013
#
# Changelog
#
#    1.00    RD    First stable code
#    1.04    RD    Change code sequence
#    1.05    RD    Code tune up
#    1.06    RD    Code tune up
#    1.07    RD    ExtRoot code improvements
#    1.08    RD    Add image check sum control
#    1.09    RD    Add command line switch on/off-line package post-install
#                Add command line switch to disable configuration backup 
#    1.10    RD    Preparation scripts code improvements
#    1.11    RD    Preparation scripts code improvements (2)
#    1.12    RD    Preparation scripts code improvements (3)
#    1.13    RD    Preparation scripts code improvements (4)
#    1.14    RD    Extroot scripts code improvements
#    1.15    RD    Help improvements
#    1.16    RD    Help improvements (2), Preparation scripts code improvements (5)
#    1.17    RD    Extroot scripts code improvements (2)
#    1.18    RD    Include installed packages options
#    1.19    RD    Extroot scripts code improvements (3)
#    1.20    RD    Add status led toggle
#    1.21    RD    Correct rc.local manipulation code
#    1.22    RD    Add packages disabling to sysupgrade process
#                Preparation scripts code improvements (5)
#    1.23    RD    Extroot scripts code improvements
#    1.24    RD    Added recurrence of checking of package dependences
#                Changed packages initialization script name convention
#    1.25    RD    Preparation scripts code improvements (6)
#    1.26    RD    Preparation scripts code improvements (7)
#    1.27    RD    Code tune up
#    1.28    RD    Code tune up
#    1.29    RD    Dependency check code improvements
#    1.30    RD    Added post install file removing
#                Added external script
#    1.31    RD    Added backup command
#    1.32    RD    Removed I/O control after post install file removing
#    1.33    RD    Added variables to image source path
#
# Destination /sbin/install.sh
#
. /etc/openwrt_release

local CMD=""
local OFFLINE_POST_INSTALL="1"
local INCLUDE_INSTALLED="1"
local HOST_NAME=""
local BACKUP_ENABLE="1"
local BACKUP_PATH=""
local BACKUP_FILE=""
local INSTALL_PATH="/tmp"
local PACKAGES=""
local IMAGE_SOURCE=""
local IMAGE_FILENAME="sysupgrade.bin"
local POST_INSTALL_SCRIPT="post-installer"
local POST_INSTALLER="/bin/$POST_INSTALL_SCRIPT.sh"
local POST_INSTALLER_LOG="/usr/$POST_INSTALL_SCRIPT.log"
local INSTALLER_KEEP_FILE="/lib/upgrade/keep.d/$POST_INSTALL_SCRIPT"
local RC_LOCAL="/etc/rc.local"
local POST_INSTALL_REMOVE="/etc/config/*-opkg"
local RUN_SCRIPT=""

check_exit_code() {
    local CODE=$?
    if [ $CODE != 0 ]; then 
        echo "Abort, error ($CODE) detected!"
        exit $CODE
    fi
}

get_mount_device() { # <Path to check>
    local CHECK_PATH=$1
    [ -L $CHECK_PATH ] && CHECK_PATH=$($BIN_LS -l $CHECK_PATH | $BIN_AWK -F " -> " '{print $2}')
    $BIN_AWK -v path="$CHECK_PATH" 'BEGIN{FS=" ";device=""}path~"^"$2{if($2>point){device=$1;point=$2}}END{print device}' /proc/mounts
    check_exit_code
}

which_binary() { # <Name of Binary> [<Name of Binary> [...]]
    while [ -n "$1" ]; do
        local WHICH=$(which $1)
        if [ "$WHICH" == "" ]; then
            echo "Binary $1 not found in system!"
            exit 1
        else
            eval "export -- \"BIN_$(echo $1 | tr '[a-z]' '[A-Z]')=$WHICH\""
        fi
        shift
    done
}

add_to_keep_file() { # <Content to save> <Root path>
    local CONTENT="$1"
    local ROOT_PATH="$2"
    $BIN_ECHO "$1">>$ROOT_PATH$INSTALLER_KEEP_FILE
    check_exit_code
}

run_script() { # <Event>
    if [ "$RUN_SCRIPT" != "" ] && [ -x $RUN_SCRIPT ]; then
        $BIN_ECHO "Run script $RUN_SCRIPT $1 ..."
        $RUN_SCRIPT $1
        check_exit_code
        $BIN_ECHO "Script $RUN_SCRIPT exited."
    fi
}

add_to_post_installer_log() { # <Content to save>
    $BIN_ECHO "$($BIN_DATE) $1">>$POST_INSTALLER_LOG
}

package_script_execute() { # <Package> <Script name> <Command>
    local PACKAGE="$1"
    local SCRIPT="$2"
    local CMD="$3"
    if [ -x $SCRIPT ]; then
        $BIN_ECHO "Executing $SCRIPT $CMD for package $PACKAGE"
        if [ "$CMD" == "enable" ] || [ "$CMD" == "stop" ]; then
            $SCRIPT $CMD
        else
            $SCRIPT $CMD
            check_exit_code
        fi
    fi
}

update_path_vars() { # <String to update>
    local PATH_VARS="$1"
    local TARGET=$(echo "$DISTRIB_TARGET" | cut -d "/" -f 1)
    local SUBTARGET=$(echo "$DISTRIB_TARGET" | cut -d "/" -f 2)
    local BOARD_NAME=$($BIN_CAT /tmp/sysinfo/model | $BIN_TR '[A-Z]' '[a-z]')
    local BOARD_VER=$($BIN_ECHO "$BOARD_NAME" | $BIN_CUT -d " " -f 3)
    BOARD_NAME=$($BIN_ECHO "$BOARD_NAME" | $BIN_CUT -d " " -f 2)
    [ -n "$BOARD_VER" ] && BOARD_NAME="$BOARD_NAME-$BOARD_VER"
    [ -n "$DISTRIB_CODENAME" ] && PATH_VARS=${PATH_VARS//\<CODENAME\>/$DISTRIB_CODENAME}
    [ -n "$TARGET" ] && PATH_VARS=${PATH_VARS//\<TARGET\>/$TARGET}
    [ -n "$SUBTARGET" ] && PATH_VARS=${PATH_VARS//\<SUBTARGET\>/$SUBTARGET}
    [ -n "$BOARD_NAME" ] && PATH_VARS=${PATH_VARS//\<HARDWARE\>/$BOARD_NAME}
    $BIN_ECHO "$PATH_VARS"
}

caution_alert() {
    local KEY
    $BIN_ECHO "Caution!"
    $BIN_ECHO "You can damage the system or hardware. You perform this operation at your own risk."
    read -t 60 -n 1 -p "Press Y to continue " KEY
    $BIN_ECHO ""
    [ "$KEY" != "Y" ] && exit 0
}

print_help() {
    $BIN_ECHO -e "Usage:"\
            "\n\t$0 [install|download|sysupgrade] [-h|--help] [-o|--online] [-b|--backup-off] [-i|--exclude-installed]"\
            "\n\nCommands:"\
            "\n\t\tdownload\tdownload all packages and system image do install directory,"\
            "\n\t\tinstall\t\tbackup configuration,"\
            "\n\t\t\t\tstop and disable packages,"\
            "\n\t\t\t\tinstall packages,"\
            "\n\t\t\t\trestore configuration,"\
            "\n\t\t\t\tenable and start packages."\
            "\n\t\tsysupgrade\tbackup configuration,"\
            "\n\t\t\t\tdownload all packages and system image do install directory (in off-line mode),"\
            "\n\t\t\t\tprepare post upgrade package installer,"\
            "\n\t\t\t\tsystem upgrade,"\
            "\n\t\t\t\t... reboot system ...,"\
            "\n\t\t\t\tif extroot exist, clean check sum and reboot system,"\
            "\n\t\t\t\tinstall packages,"\
            "\n\t\t\t\trestore configuration,"\
            "\n\t\t\t\tcleanup installation,"\
            "\n\t\t\t\t... reboot system ..."\
            "\n\t\tbackup\t\tbackup configuration"\
            "\n\nOptions:"\
            "\n\t\t-h\t\tThis help,"\
            "\n\t\t-b\t\tDisable configuration backup and restore during installation or system upgrade process."\
            "\n\t\t\t\tBy default, backup and restore configuration are enabled."\
            "\n\t\t\t\tPath to backup have to on external device otherwise during system upgrade can be lost."\
            "\n\t\t-o\t\tOnline packages installation by post-installer."\
            "\n\t\t\t\tInternet connection is needed after system restart and before packages installation."\
            "\n\t\t-i\t\tExclude installed packages. Only packages from configuration can be processed."\
            "\n\nCurrent configuration:"\
            "\n\tLocal install directory : '$($BIN_UCI -q get system.@sysupgrade[0].localinstall)'"\
            "\n\tConfiguration backup direcory : '$($BIN_UCI -q get system.@sysupgrade[0].backupconfig)'"\
            "\n\tImage source URL : '$($BIN_UCI -q get system.@sysupgrade[0].imagesource)'"\
            "\n\tRun external script : '$($BIN_UCI -q get system.@sysupgrade[0].runscript)'"\
            "\n\tPackages: '$($BIN_UCI -q get system.@sysupgrade[0].opkg)'"\
            "\n\nExamples configuration in /etc/config/system"\
            "\n\tconfig sysupgrade"\
            "\n\t\toption localinstall '/install'"\
            "\n\t\toption backupconfig '/backup'"\
            "\n\t\toption imagesource 'http://ecco.selfip.net/<CODENAME>/<TARGET>/openwrt-<TARGET>-<SUBTARGET>-<HARDWARE>-squashfs-sysupgrade.bin'"\
            "\n\t\tlist opkg libusb"\
            "\n\t\tlist opkg kmod-usb-serial-option"\
            "\n\t\tlist opkg kmod-usb-net-cdc-ether"\
            "\n"
    exit 0
}

initialize() { # <Script parametrs>
    which_binary echo basename dirname logger chmod uci date ls cat cut tr wc rm mv sync reboot awk grep wget opkg sysupgrade md5sum ping logread gzip
    while [ -n "$1" ]; do
        case "$1" in
            install|download|sysupgrade|backup) CMD="$1";; 
            -h|--help) print_help;;
            -b|--backup-off) BACKUP_ENABLE="";;
            -o|--online) OFFLINE_POST_INSTALL="";;
            -i|--exclude-installed) INCLUDE_INSTALLED="";;
            -*) $BIN_ECHO "Invalid option: $1";print_help;;
            *) $BIN_ECHO "Invalid command: $1";print_help;;
        esac
        shift
    done
    [ "$CMD" == "" ] && CMD=install
    [ "$CMD" == "backup" ] && BACKUP_ENABLE="1"
    HOST_NAME=$($BIN_UCI -q get system.@system[0].hostname)
    if [ "$HOST_NAME" == "" ]; then 
        $BIN_ECHO "Error while getting host name!"
        exit 1
    fi
    if [ "$CMD" == "download" ] || ([ "$CMD" == "sysupgrade" ] && [ "$OFFLINE_POST_INSTALL" != "" ]); then
        INSTALL_PATH=$($BIN_UCI -q get system.@sysupgrade[0].localinstall)
        if [ "$INSTALL_PATH" == "" ]; then
            $BIN_ECHO "Install path is empty!"
            exit 1
        fi    
        if [ ! -d "$INSTALL_PATH" ]; then
            $BIN_ECHO "Install path not exist!"
            exit 1
        fi    
    fi
    if [ "$BACKUP_ENABLE" != "" ]; then
        BACKUP_PATH=$($BIN_UCI -q get system.@sysupgrade[0].backupconfig)
        BACKUP_FILE="$BACKUP_PATH/backup-$HOST_NAME-$($BIN_DATE +%Y-%m-%d-%H-%M-%S).tar.gz"        
        if [ ! -d "$BACKUP_PATH" ]; then
            $BIN_ECHO "Backup path not exist!"
            exit 1
        fi
        local MOUNT_DEVICE=$(get_mount_device $BACKUP_PATH)
        if [ "$MOUNT_DEVICE" == "rootfs" ] || [ "$MOUNT_DEVICE" == "sysfs" ] || [ "$MOUNT_DEVICE" == "tmpfs" ]; then
            $BIN_ECHO "Backup path ($BACKUP_PATH) must be on external device. Now is mounted on $MOUNT_DEVICE."
            exit 1
        fi
    fi
    if [ "$CMD" == "download" ] || [ "$CMD" == "sysupgrade" ]; then
        IMAGE_SOURCE=$($BIN_UCI -q get system.@sysupgrade[0].imagesource)
        local IMAGE_PREFIX=$($BIN_UCI -q get system.@sysupgrade[0].imageprefix)
        local IMAGE_SUFFIX=$($BIN_UCI -q get system.@sysupgrade[0].imagesuffix)
        if [ -n "$IMAGE_PREFIX" ] || [ -n "$IMAGE_SUFFIX" ]; then
            IMAGE_SOURCE="$IMAGE_SOURCE/$IMAGE_PREFIX<HARDWARE>$IMAGE_SUFFIX"
        fi
    fi
    RUN_SCRIPT=$($BIN_UCI -q get system.@sysupgrade[0].runscript)
    PACKAGES=$($BIN_UCI -q get system.@sysupgrade[0].opkg)
    if [ "$CMD" == "sysupgrade" ] && [ "$OFFLINE_POST_INSTALL" != "" ]; then
        local MOUNT_DEVICE=$(get_mount_device $INSTALL_PATH)
        if [ "$MOUNT_DEVICE" == "rootfs" ] || [ "$MOUNT_DEVICE" == "sysfs" ] || [ "$MOUNT_DEVICE" == "tmpfs" ]; then
            $BIN_ECHO "Install path ($INSTALL_PATH) must be on external device. Now is mounted on $MOUNT_DEVICE."
            exit 1
        fi
    fi
    $BIN_ECHO "Operation $CMD on $HOST_NAME - $DISTRIB_ID $DISTRIB_RELEASE ($DISTRIB_REVISION)"
}

update_repository() {
    run_script before_opkg_update
    $BIN_ECHO "Updating packages repository ..."
    $BIN_OPKG update
    check_exit_code
    $BIN_ECHO "Packages repository updated."
}

check_installed() {
    if [ "$INCLUDE_INSTALLED" != "" ]; then
        $BIN_ECHO "Checking installed packages ..."
        local INSTALLED=$($BIN_AWK -v PKG="$PACKAGES " 'BEGIN{FS=": ";ORS=" "}/^Package\: /{Package=$2}/^Status\: / && /user installed/{if(index(PKG,Package" ")==0)print Package}' /usr/lib/opkg/status)
        check_exit_code
        INSTALLED=${INSTALLED%% }
        if [ "$INSTALLED" != "" ]; then
            $BIN_ECHO "Installed packages not in configuration: $INSTALLED."
            PACKAGES="$PACKAGES $INSTALLED"
        else
            $BIN_ECHO "All packages from configuration."
        fi
    fi
}

check_dependency() {
    if [ "$PACKAGES" != "" ]; then 
        $BIN_ECHO "Checking packages dependency ..."
        $BIN_ECHO "Main packages: $PACKAGES."
        local PACKAGES_COUNT=-1
        while [ "$($BIN_ECHO $PACKAGES | $BIN_WC -w)" != "$PACKAGES_COUNT" ]; do
            PACKAGES_COUNT=$($BIN_ECHO $PACKAGES | $BIN_WC -w)
            local DEPENDS
            local DEPENDS_COUNT=-1
            while [ "$($BIN_ECHO $DEPENDS | $BIN_WC -w)" != "$DEPENDS_COUNT" ]; do
                DEPENDS_COUNT=$($BIN_ECHO $DEPENDS | $BIN_WC -w)
                DEPENDS=$DEPENDS$($BIN_OPKG depends -A $DEPENDS $PACKAGES | $BIN_AWK -v PKG="$DEPENDS $PACKAGES " 'BEGIN{ORS=" "}{if($2=="" && !seen[$1]++ && index(PKG,$1" ")==0)print $1}')
                check_exit_code
            done
            DEPENDS=${DEPENDS%% }
            [ "$DEPENDS" != "" ] && PACKAGES="$DEPENDS $PACKAGES"
            PACKAGES=$($BIN_OPKG whatprovides -A $PACKAGES | $BIN_AWK -v PKG="$PACKAGES " 'function Select(){if(CNT<1)return;SEL=0;for(ITEM in LIST)if(index(PKG,LIST[ITEM]" ")!=0)SEL=ITEM;if(!seen[LIST[SEL]]++)print LIST[SEL];delete LIST;CNT=0}BEGIN{ORS=" "}{if($3!="")Select();else LIST[CNT++]=$1}END{Select()}')
            PACKAGES=${PACKAGES%% }
        done
        $BIN_ECHO "All packages: $PACKAGES."
    fi
}

config_backup() {
    if [ "$BACKUP_ENABLE" != "" ]; then
        if [ ! -d "$BACKUP_PATH" ]; then
            $BIN_ECHO "Backup path not exist."
            exit 1
        fi
        if [ "$BACKUP_FILE" == "" ]; then
            $BIN_ECHO "Backup file name is empty."
            exit 1
        fi
        $BIN_ECHO "Making configuration backup to $BACKUP_FILE ..."
        $BIN_SYSUPGRADE --create-backup $BACKUP_FILE
        check_exit_code
        $BIN_CHMOD 640 $BACKUP_FILE
        check_exit_code
        $BIN_ECHO "Configuration backuped."
    fi
}

config_restore() {
    if [ "$BACKUP_ENABLE" != "" ]; then
        if [ "$BACKUP_FILE" == "" ]; then
            $BIN_ECHO "Backup file name is empty."
            exit 1
        else
            $BIN_ECHO "Restoring configuration from backup $BACKUP_FILE ..."
            $BIN_SYSUPGRADE --restore-backup $BACKUP_FILE
            check_exit_code
            $BIN_ECHO "Configuration restored."
        fi
    fi
}

packages_disable() {
    if [ "$PACKAGES" != "" ]; then 
        $BIN_ECHO "Disabling packages ..."
        local SCRIPT
        for PACKAGE in $PACKAGES; do
            for SCRIPT in $($BIN_OPKG files $PACKAGE | $BIN_GREP /etc/init.d/); do
                package_script_execute $PACKAGE $SCRIPT disable
                package_script_execute $PACKAGE $SCRIPT stop
            done
        done
        $BIN_ECHO "Packages are disabled."
    fi
}

packages_enable() {
    if [ "$PACKAGES" != "" ]; then 
        $BIN_ECHO "Enabling packages ..."
        local SCRIPT
        for PACKAGE in $PACKAGES; do
            for SCRIPT in $($BIN_OPKG files $PACKAGE | $BIN_GREP /etc/init.d/); do
                package_script_execute $PACKAGE $SCRIPT enable
                package_script_execute $PACKAGE $SCRIPT start
            done
        done
        $BIN_ECHO "Packages are enabled."
    fi
}

packages_install() {
    if [ "$PACKAGES" != "" ]; then 
        run_script before_opkg_install
        $BIN_ECHO "Installing packages ..."
        $BIN_OPKG $CMD $PACKAGES
        check_exit_code
        $BIN_RM $POST_INSTALL_REMOVE
        $BIN_ECHO "Packages are installed."
        run_script after_opkg_install
    fi
}

packages_download() {
    if [ "$PACKAGES" != "" ]; then 
        local PACKAGES_FILE="Packages"
        local PACKAGES_LIST="$PACKAGES_FILE.gz"
        $BIN_ECHO "Downloading packages to $INSTALL_PATH ..."
        cd $INSTALL_PATH
        $BIN_RM -f *.ipk
        $BIN_OPKG download $PACKAGES
        check_exit_code
        $BIN_ECHO "Building packages information ..."
        [ -f $INSTALL_PATH/$PACKAGES_FILE ] && $BIN_RM -f $INSTALL_PATH/$PACKAGES_FILE
        [ -f $INSTALL_PATH/$PACKAGES_LIST ] && $BIN_RM -f $INSTALL_PATH/$PACKAGES_LIST
        for PACKAGE in $PACKAGES; do
            $BIN_ECHO "Getting information for package $PACKAGE."
            $BIN_OPKG info $PACKAGE >>$INSTALL_PATH/$PACKAGES_FILE
            check_exit_code
        done 
        $BIN_ECHO "Compressing packages information as $INSTALL_PATH/$PACKAGES_LIST ..."
        $BIN_AWK '{if($0!~/^Status\:|^Installed-Time\:/)print $0}' $INSTALL_PATH/$PACKAGES_FILE | $BIN_GZIP -c9 >$INSTALL_PATH/$PACKAGES_LIST
        check_exit_code
        $BIN_RM -f $INSTALL_PATH/$PACKAGES_FILE
        check_exit_code
        $BIN_ECHO "Packages are downloaded."
    fi
}

image_download() {
    if [ "$IMAGE_SOURCE" == "" ]; then 
        $BIN_ECHO "Image source information is empty."
        exit 1
    fi
    local IMAGE_REMOTE_NAME="$(update_path_vars $IMAGE_SOURCE)"
    local IMAGE_LOCAL_NAME="$INSTALL_PATH/$IMAGE_FILENAME"
    local SUMS_REMOTE_NAME="$($BIN_DIRNAME $IMAGE_REMOTE_NAME)/md5sums"
    local SUMS_LOCAL_NAME="$INSTALL_PATH/md5sums"
    [ -f $IMAGE_LOCAL_NAME ] && $BIN_RM -f $IMAGE_LOCAL_NAME
    $BIN_ECHO "Downloading system image as $IMAGE_LOCAL_NAME from $IMAGE_REMOTE_NAME ..."    
    $BIN_WGET -O $IMAGE_LOCAL_NAME $IMAGE_REMOTE_NAME
    check_exit_code
    $BIN_ECHO "Downloading images sums as $SUMS_LOCAL_NAME from $SUMS_REMOTE_NAME ..."    
    $BIN_WGET -O $SUMS_LOCAL_NAME $SUMS_REMOTE_NAME
    check_exit_code
    $BIN_ECHO "Checking system image control sum ..."    
    local SUM_ORG=$($BIN_GREP $($BIN_BASENAME $IMAGE_REMOTE_NAME) $SUMS_LOCAL_NAME | $BIN_CUT -d " " -f 1)
    check_exit_code
    local SUM_FILE=$($BIN_MD5SUM $IMAGE_LOCAL_NAME | $BIN_CUT -d " " -f 1)
    check_exit_code
    if [ "$SUM_ORG" == "" ]; then
        $BIN_ECHO "Can't get original control sum!"
        exit 1
    elif [ "$SUM_FILE" == "" ]; then
        $BIN_ECHO "Can't calculate system image control sum!"
        exit 1
    elif [ "$SUM_ORG" != "$SUM_FILE" ]; then
        $BIN_ECHO "Downloaded system image is damaged!"
        exit 1
    else
        $BIN_ECHO "System image is downloaded and checksum is correct."
    fi
    run_script after_image_downloaded
}

installer_prepare() {
    $BIN_ECHO "Preparing packages installer in $POST_INSTALLER ..."
    $BIN_ECHO -e "#!/bin/sh"\
            "\n# Script auto-generated by $0"\
            "\n. /etc/diag.sh"\
            "\nget_status_led"\
            "\nset_state preinit"\
            "\nif [ -d /tmp/overlay-disabled ]; then"\
            "\n\t$BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT \"Removing overlay-rootfs checksum and force reboot\""\
            "\n\t$BIN_RM -f /tmp/overlay-disabled/.extroot.md5sum"\
            "\n\t$BIN_RM -f /tmp/overlay-disabled/etc/extroot.md5sum"\
            "\nelif [ -d /tmp/whole_root-disabled ]; then"\
            "\n\t$BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT \"Removing whole-rootfs checksum and force reboot\""\
            "\n\t$BIN_RM -f /tmp/whole_root-disabled/.extroot.md5sum"\
            "\n\t$BIN_RM -f /tmp/whole_root-disabled/etc/extroot.md5sum"\
            "\nelse"\
            "\n\t$BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT \"Start instalation of packages\"">$POST_INSTALLER
    check_exit_code
    if [ "$OFFLINE_POST_INSTALL" != "" ]; then
        $BIN_ECHO -e "\t$BIN_CAT /etc/opkg.conf | $BIN_AWK 'BEGIN{print \"src/gz local file:/$INSTALL_PATH\"}!/^src/{print \$0}' >/etc/opkg.conf">>$POST_INSTALLER
        check_exit_code
    else
        $BIN_ECHO -e "\tuntil $BIN_PING -q -W 30 -c 1 8.8.8.8 &>/dev/null; do"\
                "\t\t$BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT \"Wait for internet connection\""\
                "\n\tdone">>$POST_INSTALLER
        check_exit_code
    fi
    if [ "$RUN_SCRIPT" != "" ] && [ -x $RUN_SCRIPT ]; then
        $BIN_ECHO -e "\t$RUN_SCRIPT before_opkg_update | $BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT">>$POST_INSTALLER
        check_exit_code
    fi
    $BIN_ECHO -e "\t$BIN_OPKG update | $BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT">>$POST_INSTALLER
    if [ "$RUN_SCRIPT" != "" ] && [ -x $RUN_SCRIPT ]; then
        $BIN_ECHO -e "\t$RUN_SCRIPT before_opkg_install | $BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT">>$POST_INSTALLER
        check_exit_code
    fi
    $BIN_ECHO -e "\tlocal PACKAGES=\"$PACKAGES\""\
            "\n\tlocal PACKAGE"\
            "\n\tlocal SCRIPT"\
            "\n\tfor PACKAGE in \$PACKAGES; do"\
            "\n\t\t$BIN_OPKG install \$PACKAGE | $BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT"\
            "\n\t\tfor SCRIPT in \$($BIN_OPKG files \$PACKAGE | $BIN_GREP /etc/init.d/); do"\
            "\n\t\t\tif [ -x \$SCRIPT ]; then"\
            "\n\t\t\t\t$BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT \"Executing \$SCRIPT enable for package \$PACKAGE\""\
            "\n\t\t\t\t\$SCRIPT enable | $BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT"\
            "\n\t\t\tfi"\
            "\n\t\tdone"\
            "\n\tdone">>$POST_INSTALLER
    check_exit_code
    if [ "$RUN_SCRIPT" != "" ] && [ -x $RUN_SCRIPT ]; then
        $BIN_ECHO -e "\t$RUN_SCRIPT after_opkg_install | $BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT">>$POST_INSTALLER
        check_exit_code
    fi
    if [ "$BACKUP_ENABLE" != "" ]; then
        $BIN_ECHO -e "\t$BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT \"Restoring configuration backup from $BACKUP_FILE\""\
                "\n\t$BIN_SYSUPGRADE --restore-backup $BACKUP_FILE">>$POST_INSTALLER
        check_exit_code
    fi
    $BIN_ECHO -e "\t$BIN_LOGGER -p user.notice -t $POST_INSTALL_SCRIPT \"Stop installation of packages, cleaning and force reboot\""\
            "\n\t$BIN_RM $POST_INSTALL_REMOVE"\
            "\n\t$BIN_RM -f $INSTALLER_KEEP_FILE"\
            "\n\t$BIN_AWK -v installer=\"$POST_INSTALLER\" '\$0!~installer' $RC_LOCAL>$RC_LOCAL.tmp"\
            "\n\t$BIN_MV -f $RC_LOCAL.tmp $RC_LOCAL"\
            "\n\t$BIN_RM -f $POST_INSTALLER"\
            "\nfi"\
            "\n$BIN_LOGREAD >>$POST_INSTALLER_LOG"\
            "\n$BIN_SYNC"\
            "\n$BIN_REBOOT -f"\
            "\n# Done.">>$POST_INSTALLER
    check_exit_code
    $BIN_CHMOD 777 $POST_INSTALLER
    check_exit_code
    add_to_keep_file $POST_INSTALLER
    [ "$RUN_SCRIPT" != "" ] && [ -x $RUN_SCRIPT ] && add_to_keep_file $RUN_SCRIPT
    $BIN_ECHO "Setting autorun packages installer on next boot in $RC_LOCAL ..."
    add_to_keep_file $RC_LOCAL
    $BIN_ECHO -e "[ -x $POST_INSTALLER ] && $POST_INSTALLER\n$($BIN_CAT $RC_LOCAL)">$RC_LOCAL
    check_exit_code
    add_to_post_installer_log "Packages installer prepared"
    $BIN_ECHO "Packages installer prepared."
}

sysupgrade_execute() {
    $BIN_ECHO "Upgrading system from image $INSTALL_PATH/$IMAGE_FILENAME ..."
    add_to_keep_file $0
    add_to_post_installer_log "Running system upgrade"
    cd $INSTALL_PATH
    $BIN_SYSUPGRADE $IMAGE_FILENAME
}

# Main routine
initialize $@
[ "$CMD" == "backup" ] && config_backup && exit
[ "$CMD" == "sysupgrade" ] && caution_alert
update_repository
check_installed
check_dependency
([ "$CMD" == "install" ] || [ "$CMD" == "sysupgrade" ]) && config_backup
[ "$CMD" == "install" ] && packages_disable && packages_install
([ "$CMD" == "download" ] || ([ "$CMD" == "sysupgrade" ] && [ "$OFFLINE_POST_INSTALL" != "" ])) && packages_download
[ "$CMD" == "install" ] && config_restore && packages_enable
([ "$CMD" == "download" ] || [ "$CMD" == "sysupgrade" ]) && image_download
[ "$CMD" == "sysupgrade" ] && installer_prepare && packages_disable && sysupgrade_execute
$BIN_ECHO "Done."
# Done.

Latest script code is here https://raw.github.com/Rafciq/openwrt/master/misc/install.sh.

Before first use, put the scrpit code into file /sbin/install.sh on your router. Next step: you have to add configuration section to file /etc/config/system .
Example configuration for install.sh script:

config sysupgrade
    option localinstall '/install'
    option backupconfig '/backup'
    option imagesource 'http://ecco.selfip.net/<CODENAME>/<TARGET>/openwrt-<TARGET>-<SUBTARGET>-<HARDWARE>-squashfs-sysupgrade.bin'
    list opkg libusb
    list opkg kmod-usb-serial-option
    list opkg kmod-usb-net-cdc-ether

For typical system upgrade proces they are required two folders. One for save backup of router configuration (backupconfig) and second for download file firmware image and packeges files (localinstall). Both folders must be outside of router flash. I recomend pendrive plugged to router USB and auto-monted via fstab.
Option imagesource defines from the image file will be downloaded. Obligatory packages are defined by list opkg.
Variable used in imagesource path:

  • <CODENAME> name of milestone (ex. "attitude_adjustment")

  • <TARGET> platform name (ex. "ar71xx")

  • <SUBTARGET> sub-platform name (ex. "generic")

  • <HARDWARE> hardware name with version (ex. "tl-wdr4300-v1")

The install.sh script performs one of the four operations:

  1. install.sh sysupgrade

    • backup configuration,

    • download all packages and system image do install directory (in off-line mode),

    • prepare post upgrade package installer,

    • system upgrade,

    • ... reboot system ...,

    • if extroot exist, clean check sum and reboot system,

    • install packages,

    • restore configuration,

    • cleanup installation,

    • ... reboot system ...

  2. install.sh install

    • backup configuration,

    • stop and disable packages,

    • install packages,

    • restore configuration,

    • enable and start packages.

  3. install.sh download

    • download all packages and system image do install directory,

  4. install.sh backup

    • backup configuration

Other install.sh script command line options:

  • -b
    Disable configuration backup and restore during installation or system upgrade process.
    By default, backup and restore configuration are enabled.
    Path to backup have to on external device otherwise during system upgrade can be lost.

  • -o
    Online packages installation by post-installer.
    Internet connection is needed after system restart and before packages installation.

  • -i
    Exclude installed packages. Only packages from configuration can be processed.

Script Install.sh can execute your commands during the installation process.
You have to create your script, eg in the file /bin/extra_install.sh and add options to /etc/config/system.
Example configuration in file /etc/config/system:

config sysupgrade
    . . .
option runscript '/bin/extra_install.sh'

Example user script file /bin/extra_install.sh

#!/bin/sh

after_image_downloaded() {
    echo "after-image-downloaded"
}

before_opkg_update() {
    echo "before-opkg-update"
}

before_opkg_install() {
    echo "before-opkg-install"
}

after_opkg_install() {
    echo "after-opkg-install"
}

while [ -n "$1" ]; do 
    if type $1 | grep -q ' function'; then
        $1
    else
        echo "Invalid argument $1"
    fi
    shift 
done

* This install.sh script was tested with OpenWrt Attitude Adjustment 12.09.x but not with Gargoyle.

install.sh - System Upgrade, sysinfo.sh - System Info, openvpn-auth.sh - OpenVPN - Login
My scripts on GitHub