Monday, 16 May 2016

At last! a script to perform the update installations.

This script is intended to be run interactively - it asks the operator to answer a few questions and is quite gobby to prevent the operator from getting bored.
Also, I once had the joy of administering a system which was creakily slow and old, and provided many hundreds of NFS shares to the users, and used Veritas Storage Foundation to manage the disk storage.  The command "vxdctl enable" (used during startup) never says anything and took about 15 minutes to execute - ample time to work yourself into a panic in case it hasn't worked.  Gobby progress messages have some advantages!

Read the earlier posts to see the evolution of this update process.  If you're wondering why i don't allow the Mozilla automated updates to happen, it's because I can't:  I never run a privileged browser - doing this is like handing a loaded gun to a mugger - or X-server which would be like handing a loaded gun to any remote X-clients allowed to use it.  You can be caned enough by malevolent web sites or remote X-clients without giving them carte blanche to hijack or destroy your entire system, as many Windows users find out to their cost.  (root) Privilege is needed to perform the updates, and I try quite hard not to hand it to anybody.

Residency and invocation

The script is called mozupdate and resides in a private directory inside root's home directory /root/bin (/root as root's home directory is a linux convention that prevents the root user's scrappy files clogging up system directories).  I've added /root/bin to the end of root's PATH variable at login time.  Only the root user can execute this script.

Invocation is simple and and independent of the current working directory - it will cd into /usr/local/lib for you.  Only one parameter is required: the absolute pathname of the file containing the update you wish to install (which must have the systematised name given to it by Mozilla, so no renaming the downloaded file).

Here's what happens at runtime:

root@wideboy:~# ls -lrt ~steve/Dow* | tail -1
-rw-r--r-- 1 steve steve  36639715 Apr  3 10:33 thunderbird-31.6.0.tar.bz2

root@wideboy:~# mozupdate ~steve/Dow*/thunderbird-31.6.0.tar.bz2
Install thunderbird-31.6.0 ? : y

installing thunderbird-31.6.0
creating directory for thunderbird-31.6.0
extracting files from tar archive
Updating thunderbird symlink
Archiving installation file /home/steve/Downloads/thunderbird-31.6.0.tar.bz2 to /other/archive

Remove older versions of thunderbird ? : y
removing thunderbird-31.4.0
thunderbird updated to thunderbird-31.6.0

root@wideboy:~#

User typed entries are in bold in the above.

The Script

The script itself is listed here.  I confess openly to using some legacy syntax and commands - you never know when the urge to run ksh or sh for some perverse reason will strike.

Additional commentary I've added here is italicised.

#!/bin/bash
#=================================================
# mozupdate - a script to install new versions of firefox or thunderbird in a
# linux environment.  This script expects the installation kit file to have the
# standard mozilla name (i.e. firefox-version.tar.bz2 or
# thunderbird-version.tar.bz2) and that the directory that will contain the
# installation exists and contains a symlink called firefox or thunderbird
# pointing to the executable file in the current installation -
#       e.g. firefox -> ./firefox-37.0.1/firefox
# I expect that /usr/bin/firefox and/or /usr/bin/thunderbird will be symlink(s)
# pointing to the appropriate symlink in the installation directory.
#
# If the defined archive directory (see below) exists the install kit file will
# be moved to it after installation.
#
# Invoke the script as user root as
#
# mozupdate file
# where file is the firefox or thunderbird install kit file.
#
# sja 7oct14 + later amendments.
#================================================

# Static definitions of the installation and archive directories.
insdir=/usr/local/lib
kitarc=/other/archive

# define ABORT function for emergency exit
function ABORT {
echo
echo 'Aborting -' "$*"
exit 1
}

# Define ASKYN function for asking the user a question.
# note the function assumes "no" is the default answer.
function ASKYN {
typeset reply
echo
echo -n $* "(y/n): "
read reply
case "$reply" in
[Yy]*)  return 0
           ;;
*)        return 1
           ;;
esac
}

# define RM_OLD_VERS function for removing old installations of firefox or
# thunderbird.
# This function exists solely to remove the action code it contains from a
# control structure later in the script.
function RM_OLD_VERS {
typeset oldmoz          #should force oldmoz to be local to this function.
for oldmoz in ${mozprod}-*
do
        [[ "$oldmoz" = "$mozver" ]] && continue #retain current (new) version
        [[ "$oldmoz" = "$oldver" ]] && continue #retain last version
        echo removing $oldmoz
        rm -rf $oldmoz
done
return 0
}

# check we are running as user "root" (UID 0) before we go any further
if [ $(id -u) -ne 0 ]
then
        ABORT You must be user root to successfully run this script
fi

# check "file" exists
if ! [ -f "$1" ]
then
        ABORT "$1" not found
fi

# Get absolute pathname of install kit file
inskit=$(realpath "$1")

# extract product and version names from filename
mozver=$(basename -s .tar.bz2 "$inskit")
mozprod=$(echo "$mozver" | cut -d- -f1)

# change into installation directory
cd "$insdir"

# extract current (old) version from symlink
oldver=$(ls -l $mozprod | cut -d/ -f2)

The variables inskit, mozver, mozprod and oldver are used widely in the script.
NO changing them!

# abort if old version same as new version
if [[ "$oldver" = "$mozver" ]]
then
        ABORT $mozver has already been installed
fi

# Confirm installation
ASKYN Install $mozver || ABORT not installing $mozver

# proceed with update
echo
echo installing $mozver

# create new product directory in installation directory
echo creating directory for $mozver
mkdir $mozver || ABORT can\'t create directory $insdir/$mozver

# extract archive into the new directory
echo extracting files from tar archive
cd $mozver
tar --extract --bzip2 --preserve-permissions \
--strip-components=1 --file="$inskit"
if [ $? -ne 0 ]
then
        ABORT extract failed
fi
cd ..


I added the line wrap ("\") in the tar command; It suits the blogger page width better. Note the use of the GNU tar option --strip-components to get rid of the unwanted top level directory in the tar archive and the conditional execution operators && and || in the above.
 
# Update Symlink
echo Updating $mozprod symlink
rm $mozprod
ln -s ./$mozver/$mozprod $mozprod || ABORT symlink update failed
sync

# Archive install kit to archive directory
if [ -d "$kitarc" ]
then
        echo Archiving installation file $inskit to $kitarc
        mv "$inskit" "$kitarc"
        sync
fi

# Remove older versions (not new or previous version) if required
if ASKYN Remove unused old versions of $mozprod
then
        RM_OLD_VERS
else
        echo Not removing older versions of $mozprod
fi

# Finished!
echo $mozprod updated to $mozver
exit 0

That's it!  I've managed to avoid nesting ifs in the script - makes life easier for the maintenance crew.  Note the use of the syntax "if <command>": this can bite you if <command> doesn't return 0 for true (OK) or non-zero for false (gone horribly wrong).  As an aside, I never use the "elif" operator - I regard it as ugly as it unbalances the if command and can lead to seriously horrible things - the worst I've seen was an if...elif tree that went on for 54 printed pages and in my opinion needed to be edited using "rm".  And never forget the case statement!

This script does almost everything and should give you some idea of what's gone wrong in the event of a failure.  It doesn't yet do anything to prune the growing heap of install kits in the /other/archive directory.  I can't decide whether to do this by a maximum count or by age; both are likely to be ugly to implement.

If you use the conditional execution operators (&& and ||) take care if you use them in combination - which commands will be executed  in a command of the form "a && b || c" is not as simple as you might think (c will be executed if a or b return a false (non-zero) exit code, and the operators can also be derailed by commands which return arbitrary or unset return values.
 

Steve Austin
16 May 2016.

No comments:

Post a Comment