#!/bin/bash ## Sequencer script is doing nothing on its own. It is included by a squence script ## which uses the sequencer.sh to provide sequencial operations with or without ## user interaction (see seqTemplate.sh) ## Version information VERSION_REV=3 VERSION_MAJOR=0 VERSION_MINOR=0 ## Start of generic script part QUIET=0 DRY=0 VERBOSE=0 ERNO=0 MAX_STEP=255 ALIAS= TEMPLATE_NAME=seqTemplateExample.sh MISSING_CONF=missingConf.log VERSION_STRING="${VERSION_REV}.${VERSION_MAJOR}.${VERSION_MINOR}" helpSequencer() { echo "Usage: ${0##*/} [OPTIONS] [STEP NUMBER(s) or ALIAS]" echo echo " [OPTIONS]" echo " --dry-run, -d : Only print to console what would be done" echo " ! Attention - Sequence must support this" echo " --help, -h : Display help" echo " --quiet, -q : Don't ask for permission to execute steps" echo " If called without starting step number, only this help is shown" echo " --verbose, -v : Verbose output (use exe() function to call shell commands in seqs)" echo " ( e.g.: exe apt update )" echo " --version : Display version of sequencer and revision of sequence" echo echo " [STEP NUMBER(s) 1-${MAX_STEP} or ALIAS]" echo " Single STEP or ALIAS : starting point of process" echo " Multiple STEPS or ALIAS : execute only given steps" echo " execute only one step with using special step 0" echo " ( e.g. only execute step 4: $0 4 0 )" } # endCheckEmpty [VariableName] [DESCRIPTION] # DESCRIPTION : Optional text for error endCheckEmpty() { local errorText=$1 eval 'local ref=$'$1 if [ ! -z "$2" ] ; then errorText=$2 fi if [ -z $ref ] ; then echo -e "[Error] $errorText must not be empty.\nAborting installation." exit 666 fi } existsFunction() { local NOTFOUND=0 declare -F $1 &>>/dev/null || NOTFOUND=1 return $NOTFOUND } saveReturn() { if [ $1 -ne 0 ] ; then ERNO=$1 fi } # endReturn [-f] # -f : force exit with $ERNO without user input endReturn() { if [[ ( $ERNO -ne 0 && $QUIET -ne 0 ) || ( $ERNO -ne 0 && ! -z $1 && $1 == "-f" ) ]] ; then echo echo -e "[Error] Return value $ERNO detected.\nAborting installation." exit $ERNO fi if [ $ERNO -ne 0 ] ; then echo echo "[Error] Return value $ERNO detected." read -p "End installation: y(default)/n? " answer case $answer in [nN]) echo echo Continuing installation... ;; *) echo echo Installation aborted exit $ERNO; ;; esac fi } # addConf # trying to write a file # if exists, one attempt is made to create bck file of it # if all fails, a log file is created with the conflicts to be resolved by the user addConf() { local confMode="" case "$1" in -c) # create a new file confMode="-c" ;; -a) # append to existing file confMode="-a" ;; *) # default echo "Parameter 1 (-a|-c) missing for addConf()" exit 0; ;; esac echo -n "Writing $3 ... " if [ "$DRY" -ne 0 ] ; then echo "dry-run" return 0; fi # try writing config directly if [ ! -f "$3" ] ; then if [ $confMode == "-c" ] ; then echo "$2" > "$3" else echo "$2" >> "$3" fi echo "ok" return 0 fi # try backup existing config if [ ! -f "$3".bck ] ; then cp -ar "$3" "$3".bck if [ $confMode == "-c" ] ; then echo "$2" > "$3" else echo "$2" >> "$3" fi echo "[WARN] Existing config saved to ${3}.bck" return 0 fi # add configuration to missingConf file if [ "$missingDate" = "" ] ; then missingDate=set echo -n "### " >> "$MISSING_CONF" date >> "$MISSING_CONF" fi echo "#--- $3 ---" >> "$MISSING_CONF" echo "$2" >> "$MISSING_CONF" echo >> "$MISSING_CONF" echo "[WARN] Check $(realpath "$missingConf") for configuration conflicts ($3)" return 1 } # execute [-q] # -q: don't stop and don't report step functions which cannot be found # execute given step_ function execute() { local NOTFOUND=0 local NOREPORT=0 if [ $1 == "-q" ] ; then NOREPORT=1 shift fi # check if step function exists declare -F step_$1 &>>/dev/null || NOTFOUND=1 if [ $NOTFOUND -eq 1 ] && [ $NOREPORT -ne 1 ] ; then echo "Step $1 not found" exit 1; fi # don't execute step functions which are not available if [ $NOTFOUND -eq 1 ] ; then return $NOTFOUND fi echo -en "\n[STEP $1] " existsFunction step_${1}_info if [ $? -eq 0 ] ; then step_${1}_info $1 fi if [ $QUIET -ne 1 ] ; then read -p "Start: y/n(default)? " answer case $answer in [yY]) step_$1 $1 ;; *) echo Aborting installation at step $1 exit 1; ;; esac else step_$1 $1 fi } # checkStep # return 0 - for invalid step # Check sanitiy of step number or # Check if alias exists checkStep() { if (( $1 < 1 || $1 > $MAX_STEP )) ; then eval 'local ref=$alias_'$1 if [ -z $ref ] || [ "$ref" == "$1" ] ; then return 0 else return $ref fi else return $1 fi } # continous # (max $MAX_STEP) # execute installation continously from given starting step continous() { local step=0 checkStep $1 step=$? if [[ $step == 0 ]] ; then return 1 fi for ((i=$step; i<=${MAX_STEP}; i++)); do execute -q $i local res=$? if [ $res -ne 0 ] ; then break; fi done } # selection # execute given step list # e.g.: selection -q 1 4 12 selection() { local step=0 for i in $@ ; do checkStep $i step=$? # stop on step 0 if [ $step -eq 0 ] ; then break else execute $step fi done } # Creating a minimal step definition template createTemplate() { if [ -f $TEMPLATE_NAME ] ; then return 1 fi echo "#!/bin/bash" > $TEMPLATE_NAME echo >> $TEMPLATE_NAME echo "# Step 1 is mandatory" >> $TEMPLATE_NAME echo "step_1_info() { echo \"My custom step \$1\"; }" >> $TEMPLATE_NAME echo "step_1_alias() { ALIAS=\"begin\"; }" >> $TEMPLATE_NAME echo "step_1() {" >> $TEMPLATE_NAME echo " echo \"Doing something...\"" >> $TEMPLATE_NAME echo "}" >> $TEMPLATE_NAME echo >> $TEMPLATE_NAME echo "VERSION_SEQREV=${VERSION_REV}" >> $TEMPLATE_NAME echo ". $0" >> $TEMPLATE_NAME chmod +x $TEMPLATE_NAME return 0 } # Parse alias functions "step_[STEP NUBER]_alias" to create # back reference variable of schema: # alias_[ALIAS]=[STEP NUMBER] parseAlias() { for ((i=1; i<=${MAX_STEP}; i++)); do # Check for alias definition existsFunction step_${i}_alias if [ $? -ne 0 ] ; then continue fi # Function writes global ALIAS variable step_${i}_alias eval 'alias_'$ALIAS'='$i done } # Always display sequencer help and, if available, sequence help displayHelp() { helpSequencer # check if step definition exists by looking for step_1() existsFunction step_1 if [ $? -ne 0 ] ; then echo -e "\n It seems ${0##*/} was called directly." echo -e " Please create a sequence script first.\n" read -p " Create a template now? y/n(default)? " answer case $answer in [yY]) createTemplate if [ $? -eq 0 ] ; then echo -e "\n $TEMPLATE_NAME created." else echo -e "\n $TEMPLATE_NAME exists...Nothing to do!" fi ;; *) echo -e "\n Nothing to do!" ;; esac exit 1; else echo " Step (= alias) documentation:" for ((i=1; i<=${MAX_STEP}; i++)); do # Display step reference in help if step function exists existsFunction step_${i} if [ $? -ne 0 ] ; then continue fi printf ' Step %3s ' $i # Display alias if exists existsFunction step_${i}_alias if [ $? -eq 0 ] ; then step_${i}_alias echo " = $ALIAS" printf ' : ' else echo -n " : " fi # Display step help only if info function exists existsFunction step_${i}_info if [ $? -eq 0 ] ; then step_${i}_info $i else echo " - step_${i}_info() missing" fi done fi } # showVersion showVersion() { echo "Sequencer ${VERSION_STRING}" echo -n "Seq Revision " if [ ! -z "${VERSION_SEQREV}" ] ; then echo "${VERSION_SEQREV}" else echo "-" fi } exe() { local arr=("$@") if [ $DRY -ne 0 ] ; then echo -e "\n-- ${arr[@]}" elif [ $VERBOSE -eq 1 ] ; then (set -x; "${arr[@]}") else "${arr[@]}" fi } # Handle dry run and verbose output for commands containing pipe and/or redirects # exep exep() { if [ $DRY -ne 0 ] || [ $VERBOSE -eq 1 ] ; then echo -e "\n-- $1" fi if [ $DRY -eq 0 ] ; then bash -c "$1" fi } main() { local START=0 # options check for arg in "$@" ; do case "$arg" in --dry-run|-d) # shows what would be done DRY=1 QUIET=1 shift ;; --help|-h) # show only help displayHelp exit 0; ;; --quiet|-q) # detect if option quiet is available QUIET=1 shift ;; --verbose|-v) # set verbose flag VERBOSE=1 shift ;; --version) # version request showVersion exit 0; ;; esac done if [ -z $1 ] || [ "$1" == "" ] ; then # Empty -> show help displayHelp # Assume starting at one for interactive mode START=1 fi # compatibility check of sequence if [ ! -z $VERSION_SEQREV ] && [ $VERSION_SEQREV -gt $VERSION_REV ] ; then echo "[ERROR] Unsupported sequence revision" showVersion exit 1 fi # exclude older versions if needed if [ ! -z $VERSION_SEQREV ] && [ $VERSION_SEQREV -lt 3 ] ; then echo "[ERROR] Unsupported sequence revision (addConf)" showVersion exit 1 fi if [ -z $VERSION_SEQREV ] ; then echo -e "[WARNING] No sequence revision found. Trying anyway...\n"; fi # check for starting step if [ ! -z "$1" ] ; then START=$1 else # End here on quiet mode and no step was given if [ $QUIET -eq 1 ] ; then exit 1; fi fi if [ $DRY -ne 0 ] ; then echo echo "[WARN] Dry run active." echo " Printed commands may not be accurate (e.g. quotation incorrect)" echo " Sequence may ignore dry run" read -p "Press enter to continue or Ctrl + c to abort" fi parseAlias # check if more than one step is given and select execution mode if [ ! -z $2 ] ; then selection $@ else continous $START fi echo echo "${0##*/} finished" } main "$@" exit 0;