#!/bin/bash #/** #@file oglivecli #@brief Command line tool to manage ogLive clients. #@usage oglivecli Command [Options ...] #@param Command: #@param help show this help #@param version show script version #@param config [Parameter] show configuration parameters #@param check check system consistency #@param convert convert old ogclient to new default ogLive client #@param list list installed ogLive clients #@param show all show JSON information about all installed ogLive clients #@param show default show JSON information about ogLive client marked as default #@param show Index|Dir show JSON information about an installed ogLive client #@param search Index|Dir show corresponding index or directory #@param download show a menu to download an ogLive ISO image from the OpenGnsys website #@param download Iso download an specific ogLive ISO image from the OpenGnsys website #@param install Iso install a new ogLive client from a downloaded ISO image #@param uninstall Iso remove ISO image and uninstall its ogLive client #@param uninstall Index|Dir uninstall an ogLive client #@param get-default get index value for default ogLive client #@param set-default Index set default ogLive client #@param rebuild rebuild a lost configuration file #@param assign Iso Index assign an ISO file to a JSON entry #@param Options: #@param Index a number, starting by 0 #@param Dir directory (relative to installation directory) #@param Iso ISO file name (relative to download URL or download directory) #@warning This script needs "jq" command. #@version 1.1.0 - Initial version. #@author Ramón M. Gómez - ETSII Univ. Sevilla #@date 2016-12-05 #@version 1.1.1b - Use reduced directory names. #@author Ramón M. Gómez - ETSII Univ. Sevilla #@date 2020-01-17 #*/ ## # Global constants definition. PROG=$(basename "$(realpath "$0")") # Program name. OPENGNSYS=/opt/opengnsys # OpenGnsys main directory. DOWNLOADDIR=$OPENGNSYS/lib # Directory to store ogLive images. DOWNLOADURL="https://opengnsys.es/trac/downloads" # Download URL. TFTPDIR=$OPENGNSYS/tftpboot # TFTP directory. DEFOGLIVE="ogLive" # Default ogLive directory. MINREL=20190601 # Mininum ogLive compatibility release. INFOFILE=$OPENGNSYS/etc/ogliveinfo.json # Configuration file. # Global and secondary functions. source $OPENGNSYS/lib/ogfunctions.sh || exit 1 # Create/edit JSON file about installed ogLive clients. function addToJson() { local i DATA OGLIVEDIST="$1" OGLIVEKRNL="$2" OGLIVEARCH="$3" OGLIVEREV="$4" local OGLIVEDIR=$(basename $5 2>/dev/null) OGLIVEISO=$(basename $6 2>/dev/null) # JSON data for installed ogLive. DATA=$(cat << EOT | jq . {"distribution":"$OGLIVEDIST","kernel":"$OGLIVEKRNL","architecture":"$OGLIVEARCH","revision":"$OGLIVEREV","directory":"$OGLIVEDIR","iso":"$OGLIVEISO"} EOT ) # Check JSON file consistency. if [ "$(jq -c keys $INFOFILE 2>/dev/null)" == '["default","oglive"]' ]; then # Check if ogLive is defined into JSON file. n=$(jq ".oglive | length" $INFOFILE) for ((i=0; i/dev/null || raiseError access "Installation directory." [ ! -f $OGCLIENT/ogvmlinuz ] && raiseError notfound "\"ogclient\"." # Add entry to JSON file using ogclient kernel version. OGLIVEKRNL=$(file -bkr $OGCLIENT/ogvmlinuz | awk '/Linux/ {for(i=1;i<=NF;i++) if($i~/version/) {v=$(i+1);sub(/-.*/,"",v);print v}}') OGLIVEDIR=$DEFOGLIVE-$OGLIVEKRNL [ -r $OLDINFOFILE ] && OGLIVEISO="$(head -1 $OLDINFOFILE)" addToJson "$(echo $OGLIVEISO|cut -f2 -d-)" "$OGLIVEKRNL" "i386" "${OGLIVEISO##*-}" "$OGLIVEDIR" "$OGLIVEISO.iso" # Rename directory, link to default and clean old files. mv -v $OGCLIENT $OGLIVEDIR ln -vfs $OGLIVEDIR $DEFOGLIVE rm -f $OGCLIENT ln -vfs $DEFOGLIVE $OGCLIENT mv -v $OGCLIENT.old $OGLIVEDIR.old 2>/dev/null rm -fv {ogvmlinuz,oginitrd.img}{,.sum} $OLDINFOFILE popd >/dev/null # Delete old config file. rm -f $OLDINFOFILE } # Show script configuration parameters. function config() { local DATA DATA=$(cat << EOT [ { "param": "config-file", "description": "Configuration file", "value": "$INFOFILE" }, { "param": "download-url", "description": "ogLive download URL", "value": "$DOWNLOADURL" }, { "param": "download-dir", "description": "ogLive download directory", "value": "$DOWNLOADDIR" }, { "param": "install-dir", "description": "ogLive installation directory", "value": "$TFTPDIR" }, { "param": "default-name", "description": "Default ogLive name", "value": "$DEFOGLIVE" }, { "param": "min-release", "description": "Mainimum compatibility release", "value": "r$MINREL" } ]" EOT ) case $# in 0) # Show all parameters. echo $DATA | jq -r '.[] | .description + " (" + .param+ ")," + .value' | column -ts, ;; 1) # Show specified parameter. DATA=$(echo $DATA | jq -r ".[] | select(.param==\"$1\").value") [ "$DATA" ] || raiseError notfound "\"$1\"." echo "$DATA" ;; *) # Usage error. raiseError usage ;; esac } # Check consistency, showing configuration problems. function check() { local ERR=0 AUX INST DEF [ $# -ne 0 ] && raiseError usage # Check for old system that needs conversion. if [ -z "$(stat -c "%N" $TFTPDIR/ogclient | awk '$3~/'$DEFOGLIVE'/ {print}')" ]; then echo "This server uses old ogclient, please run \"$PROG convert\" to update." let ERR++ [ ! -f $INFOFILE ] && return $ERR fi # Check for other problems. [ ! -f $INFOFILE ] && echo "Configuration file does not exists: $INFOFILE" && let ERR++ [ -f $INFOFILE -a "$(jq -c keys $INFOFILE 2>/dev/null)" != "[\"default\",\"oglive\"]" ] && echo "Format error in configuration file: $INFOFILE" && let ERR++ [ ! -e $TFTPDIR ] && echo "TFTP directory does not exist: $TFTPDIR." && let ERR++ # Check for installed ogLive clients. INST=( $(find $TFTPDIR/ -type d -name "$DEFOGLIVE-*" -a ! -name "*.old" -printf "%f\n" | sort) ) [[ ${#INST[@]} -eq 0 ]] && echo "No ogLive clients are installed." && let ERR++ DEF=( $(jq -r .oglive[].directory $INFOFILE 2>/dev/null | sort) ) # Compare installed and defined ogLive clients. AUX=$(comm -23 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]})) [ -n "$AUX" ] && echo "Some ogLive are installed but not defined: ${AUX//$'\n'/, }" && let ERR++ AUX=$(comm -13 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]})) [ -n "$AUX" ] && echo "Some ogLive are defined but not installed: ${AUX//$'\n'/, }" && let ERR++ # Compare downloaded and defined ISO images. INST=( $(find $DOWNLOADDIR/ -type f -name "$DEFOGLIVE-*.iso" -printf "%f\n" | sort) ) DEF=( $(jq -r .oglive[].iso $INFOFILE 2>/dev/null | sort) ) AUX=$(comm -23 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]})) [ -n "$AUX" ] && echo "Some ISOs are downloaded but not defined: ${AUX//$'\n'/, }" && let ERR++ AUX=$(comm -13 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]})) [ -n "$AUX" ] && echo "Some ISOs are defined but not downloaded: ${AUX//$'\n'/, }" && let ERR++ # Check for new ISO files downloaded after installation. AUX=$(jq -r '.oglive[] as $og | $og.iso + ":" + $og.directory' $INFOFILE 2>/dev/null | \ while IFS=":" read -r DEF INST; do [ $DOWNLOADDIR/$DEF -nt $TFTPDIR/$INST ] && echo "$DEF" done) [ -n "$AUX" ] && echo "Some ISOs are downloaded after installation: ${AUX//$'\n'/, }" && let ERR++ AUX=$(jq -r '.oglive[] as $og | if ($og.revision[1:9] | tonumber) < '$MINREL' then $og.directory else "" end' $INFOFILE 2>/dev/null) [ -n "$AUX" ] && echo "Some installed ogLive aren't fully compatible: ${AUX//$'\n'/, }" && let ERR++ DEF=$(jq -r ".oglive[$(getdefault)].directory" $INFOFILE 2>/dev/null) INST=$(stat -c "%N" $TFTPDIR/$DEFOGLIVE | cut -f4 -d\') [ "$DEF" != "$INST" ] && echo "Default ogLive is not linked to right directory: $DEF <> $INST" && let ERR++ # Print result. [ $ERR -eq 0 ] && echo "OK!" || echo "Problems detected: $ERR" return $ERR } # List installed ogLive clients. function list() { [ $# -ne 0 ] && raiseError usage [ ! -r $INFOFILE ] && raiseError access "Configuration file." # List all defined indexes, directories and check if missing. jq -r .oglive[].directory $INFOFILE | nl -v 0 | \ awk '{system("echo -n "$0"; test -d '$TFTPDIR'/"$2" || echo -n \" (missing)\"; echo")}' | column -t } # Show information about an installed ogLive client. function show() { local INDEX [ $# -ne 1 ] && raiseError usage [ ! -r $INFOFILE ] && raiseError access "Configuration file." # Show JSON entries. case "$1" in default) # Default index. INDEX="[$(jq -r .default $INFOFILE)]" ;; all) # All intries. ;; [0-9]*) # Index. INDEX="[$1]" ;; *) # Directory. INDEX="[$(search "$1" 2>/dev/null)]" || raiseError notfound "Directory \"$1\"." ;; esac jq ".oglive$INDEX" $INFOFILE || raiseError notfound "Index \"$1\"." } # Show index or directory corresponding to searching parameter. function search() { [ $# -ne 1 ] && raiseError usage [ ! -r $INFOFILE ] && raiseError access "Configuration file." # Show corresponding index or directory. list | awk -v d="$1" '{if ($2==d) print $1; if ($1==d) print $2}' | grep . || raiseError notfound "Index/Directory \"$1\"." } # Show a menu to select and download an ogLive ISO image from the OpenGnsys website. function download() { local OGLIVE NISOS i HTTPCODE TARGETFILE local ISOREL [ $# -gt 1 ] && raiseError usage [ ! -d $DOWNLOADDIR ] && raiseError notfound "Download directory." [ ! -w $DOWNLOADDIR ] && raiseError access "Download directory." # Check parameter. if [ -n "$1" ]; then # ogLive to download. OGLIVEFILE="$1" else # Show download menu. OGLIVE=( $(curl --silent $DOWNLOADURL | grep "$DEFOGLIVE.*iso") ) NISOS=${#OGLIVE[@]} echo "Available downloads (+ = installed, * = full compatibility):" for i in $(seq 1 $NISOS); do [ -e $DOWNLOADDIR/${OGLIVE[i-1]} ] && OGLIVE[i-1]="(+) ${OGLIVE[i-1]}" ISOREL=${OGLIVE[i-1]##*-r}; ISOREL=${ISOREL%%.*} [ $ISOREL -ge $MINREL ] && OGLIVE[i-1]="(*) ${OGLIVE[i-1]}" done select opt in "${OGLIVE[@]}"; do [ -n "$opt" ] && OGLIVEFILE=${opt##* } && break done fi # Get download size. SOURCELENGTH=$(curl --head --silent $DOWNLOADURL/$OGLIVEFILE | awk -F: '/Content-Length:/ {print $2}') [ -n "$SOURCELENGTH" ] || raiseError download "$OGLIVEFILE" # Download ogLive. TARGETFILE=$DOWNLOADDIR/$OGLIVEFILE trap "rm -f $TARGETFILE" 1 2 3 6 9 15 curl $DOWNLOADURL/$OGLIVEFILE -o $TARGETFILE || raiseError download "\"$OGLIVEFILE\"." } # Install an ogLive client from a previously downloaded ISO image. function install() { local OGLIVEFILE OGLIVEDIST OGLIVEREV OGLIVEKRNL OGLIVEDIR OGINITRD OGSQFS OGCLIENT=ogclient local COMPRESS SAMBAPASS TMPDIR RSYNCSERV RSYNCCLNT [ $# -ne 1 ] && raiseError usage OGLIVEFILE=$(realpath $DOWNLOADDIR/$1) # Only 1 file in pathname expansion. [ $(echo $OGLIVEFILE | wc -w) -gt 1 ] && raiseError usage [ ! -f $OGLIVEFILE ] && raiseError notfound "Downloaded file: \"$1\"." [ ! -r $OGLIVEFILE ] && raiseError access "Downloaded file: \"$1\"." [ ! -w $(dirname $INFOFILE) ] && raiseError access "Configuration directory." [ ! -w $TFTPDIR ] && raiseError access "Installation directory." [ -z "$(file -b $OGLIVEFILE | grep "ISO.*ogClient")" ] && raiseError access "File is not an ogLive ISO image." # Working directory: # 64-bit: ogLive-KernelVersion-rCodeRelease # 32-bit: ogLive-KernelVersion-i386-rCodeRelease OGLIVEDIST="$(echo $OGLIVEFILE|cut -f2 -d-)" OGLIVEREV="${OGLIVEFILE##*-}"; OGLIVEREV="${OGLIVEREV%%.*}" OGLIVEKRNL="$(echo $OGLIVEFILE|cut -f3- -d-)"; OGLIVEKRNL="${OGLIVEKRNL%-$OGLIVEREV.*}" OGLIVEARCH="$(echo $OGLIVEFILE|awk -F- '{print $(NF-1)}')" case "$OGLIVEARCH" in i386|amd64) # Get architecture. OGLIVEKRNL="${OGLIVEKRNL%-$OGLIVEARCH}" ;; *) # 32-bit by default. OGLIVEARCH="i386" ;; esac OGLIVEDIR="$TFTPDIR/$DEFOGLIVE-${OGLIVEKRNL%%-*}-$OGLIVEARCH-$OGLIVEREV" OGLIVEDIR="${OGLIVEDIR/amd64-/}" # Get current or default Samba key. OGINITRD=$OGLIVEDIR/oginitrd.img [ ! -r $OGINITRD ] && OGINITRD=$TFTPDIR/$DEFOGLIVE/oginitrd.img if [ -r $OGINITRD ]; then COMPRESS=$(file -b "$OGINITRD" | awk '{print tolower($1);}') SAMBAPASS=$($COMPRESS -dc $OGINITRD | \ cpio -i --to-stdout scripts/ogfunctions 2>&1 | \ sed -n '/^[ ].*OPTIONS=/s/.*pass=\(\w*\).*/\1/p') fi # Make ogLive backup. rm -fr ${OGLIVEDIR}.old mv -fv $OGLIVEDIR ${OGLIVEDIR}.old 2>/dev/null # Mount ogLive ISO image, update its files and unmount it. TMPDIR=/tmp/${OGLIVEFILE%.iso} mkdir -p $OGLIVEDIR $TMPDIR trap "umount $TMPDIR; rm -fr $TMPDIR" 1 2 3 6 9 15 mount -o loop,ro $OGLIVEFILE $TMPDIR cp -va $TMPDIR/ogclient/* $OGLIVEDIR || raiseError access "Cannot copy files to ogLive directory." umount $TMPDIR # Link to default directory if it's the first ogLive. if [ ! -f $INFOFILE ]; then rm -f $TFTPDIR/$DEFOGLIVE $TFTPDIR/$OGCLIENT ln -vfs $(basename $OGLIVEDIR) $TFTPDIR/$DEFOGLIVE ln -vfs $DEFOGLIVE $TFTPDIR/$OGCLIENT fi # Recover or ask for a new Samba access key. if [ -n "$SAMBAPASS" ]; then echo -ne "$SAMBAPASS\n$SAMBAPASS\n" | $OPENGNSYS/bin/setsmbpass "$(basename $OGLIVEDIR)" else $OPENGNSYS/bin/setsmbpass "$(basename $OGLIVEDIR)" fi # Set permissions. find -L $OGLIVEDIR -type d -exec chmod 755 {} \; find -L $OGLIVEDIR -type f -exec chmod 644 {} \; chown -R :opengnsys $OGLIVEDIR # Mount SquashFS and check Rsync version. OGSQFS=$OGLIVEDIR/ogclient.sqfs mount -o loop,ro $OGSQFS $TMPDIR # If Rsync server version > client version, link to compiled file. RSYNCSERV=$(rsync --version 2>/dev/null | awk '/protocol/ {print $6}') RSYNCCLNT=$(chroot $TMPDIR /usr/bin/rsync --version 2>/dev/null | awk '/protocol/ {print $6}') if [ -z "$RSYNCSERV" -o ${RSYNCSERV:-0} -gt ${RSYNCCLNT:-1} ]; then [ -e $OPENGNSYS/client/bin/rsync-$RSYNCSERV ] && mv -f $OPENGNSYS/client/bin/rsync-$RSYNCSERV $OPENGNSYS/client/bin/rsync else # Else, rename compiled file using Rsync protocol number. [ -e $OPENGNSYS/client/bin/rsync ] && mv -f $OPENGNSYS/client/bin/rsync $OPENGNSYS/client/bin/rsync-$($OPENGNSYS/client/bin/rsync --version 2>/dev/null | awk '/protocol/ {print $6}') fi # Unmount SquashFS. umount $TMPDIR rmdir $TMPDIR # Update JSON file. addToJson "$OGLIVEDIST" "$OGLIVEKRNL" "$OGLIVEARCH" "$OGLIVEREV" "$OGLIVEDIR" "$OGLIVEFILE" } # Uninstall an ogLive client. function uninstall() { local ISO DIR INDEX DEFINDEX [ $# -ne 1 ] && raiseError usage [ ! -r $INFOFILE ] && raiseError access "Configuration file." [ ! -w $TFTPDIR ] && raiseError access "Installation directory." # Get index and directory for the entry. case "$1" in */*) # Error (access to other directory). raiseError access "Cannot access outside installation directory." ;; *.iso) # ISO file. ISO="$1" # Working directory (ogLive-Distribution-KernelVersion-CodeRevision). DIR="$(echo $ISO|cut -f1,3 -d-)-${ISO##*-}"; DIR=${DIR%.*} INDEX=$(search $DIR 2>/dev/null) ;; [0-9]*) # Index. INDEX=$1; DIR=$(search $INDEX 2>/dev/null) ;; *) # Directory. DIR="$1"; INDEX=$(search $DIR 2>/dev/null) ;; esac DEFINDEX=$(getdefault) [[ $INDEX = $DEFINDEX ]] && raiseError access "Cannot uninstall default ogLive." # Remove files and delete index entry. rm -vfr ${ISO:+$DOWNLOADDIR/$ISO} ${DIR:+$TFTPDIR/$DIR} ### Remove $TFTPDIR/$DIR.old ? if [ -n "$INDEX" ]; then jq "del(.oglive[$INDEX])" $INFOFILE | sponge $INFOFILE # Decrement default index if needed (removed < default). [[ $INDEX < $DEFINDEX ]] && jq ".default=$((DEFINDEX-1))" $INFOFILE | sponge $INFOFILE fi } # Get default ogLive index. function getdefault() { [ $# -ne 0 ] && raiseError usage [ ! -r $INFOFILE ] && raiseError access "Configuration file." # Read default parameter. jq -r .default $INFOFILE || raiseError notfound "Undefined default index." } # Set default ogLive index. function setdefault() { local INDEX OGLIVEDIR [ $# -ne 1 ] && raiseError usage [ ! -w $INFOFILE ] && raiseError access "Configuration file." INDEX=$1 # Check if index entry exists. jq ".oglive[$INDEX]" $INFOFILE || raiseError notfound "Index \"$INDEX\"." # Get ogLive directory. OGLIVEDIR=$(jq -r ".oglive[$INDEX].directory" $INFOFILE) || raiseError notfound "Directory for index \"$INDEX\"." # Update default parameter. jq ".default=$INDEX" $INFOFILE | sponge $INFOFILE # Link to default directory. rm -f $TFTPDIR/$DEFOGLIVE ln -vfs $(basename $OGLIVEDIR) $TFTPDIR/$DEFOGLIVE } # Rebuild a lost configuration file. function rebuild() { local i INST NF DEF [ $# -ne 0 ] && raiseError usage [ -f $INFOFILE ] && raiseError access "Configuration file exists." INST=$(find $TFTPDIR/ -type d -name "$DEFOGLIVE-*" -a ! -name "*.old" -printf "%f\n" | sort) for i in $INST; do NF=$(echo $i | awk -F- '{print NF-1}') case $NF in 1) addToJson "" "$(echo $i|cut -f2 -d-)" "i386" "" "$i" "" ;; 2) eval addToJson $(echo $i | awk -F- '{printf "\"\" %s amd64 %s %s \"\"",$2,$3,$0}') ;; 3) eval addToJson $(echo $i | awk -F- '{if ($3=="i386") printf "\"\" %s %s %s %s \"\"",$2,$3,$4,$0; else printf "%s %s i386 %s %s \"\"",$2,$3,$4,$0}') ;; 4) eval addToJson $(echo $i | awk -F- '{printf "%s %s %s %s %s \"\"",$2,$3,$4,$5,$0}') ;; esac # Check for is default oglive. [ -n "$(stat -c "%N" $TFTPDIR/$DEFOGLIVE | awk '$3~/'$i'/ {print}')" ] && DEF="$i" done # Set default ogLive. [ -n "$DEF" ] && setdefault $(search $DEF) } # Assign an ISO file to a JSON entry. function assign() { local ISOFILE DIR [ $# -ne 2 ] && raiseError usage [ ! -w $INFOFILE ] && raiseError access "Configuration file." # Check if ISO file and index directory exist. ISOFILE=$DOWNLOADFILE/$1 [ ! -f $DOWNLOADDIR/$ISOFILE ] && raiseError notfound "ISO file \"$1\"." DIR=$(search $2 2>/dev/null) [ ! -d $TFTPDIR/$DIR ] && raiseError notfound "Directory for index \"$2\"." # Assign ISO file to JSON entry. jq ".oglive[$2].iso=\"$1\"" $INFOFILE | sponge $INFOFILE && jq ".oglive[$2]" $INFOFILE } # Main progrram. # Access control. [ -r $OPENGNSYS/www/controlacceso.php ] && ACCESS="web" [ "$USER" = "root" ] && ACCESS="root" [ -z "$ACCESS" ] && raiseError access "Need to be root." # Check dependencies. which sponge &>/dev/null || raiseError notfound "Need to install \"moreutils\"." # Commands control. shopt -s extglob case "$ACCESS" in root) CMDS='+(help|version|convert|config|check|list|show|search|download|install|uninstall|get-default|set-default|rebuild|assign)' ;; web) CMDS='+(list|show|search|get-default)' ;; esac case "$1" in $CMDS) COMMAND="${1/-/}"; shift; $COMMAND "$@" ;; *) raiseError usage ;; esac exit $?