#!/bin/bash # SPDX-FileCopyrightText: 2025 artlog@l0g.eu # SPDX-License-Identifier: AGPL-3.0-or-later # # metascript to include # # to generate file header for caller script do : # bash /metascript.sh header # ex: # bash lib/metascript.sh header # if called directly if [[ "$0" =~ ^(.*)/metascript.sh$ ]] then prefix=${BASH_REMATCH[1]} if [[ $1 == header ]] then cat </dev/null toolsdir=\$(pwd) source metascript.sh popd >/dev/null while [[ \$# > 0 ]] do case "\$1" in *) parsemetaarg "\$1" ;; esac shift done exit 0 EOF else echo "[WARNING] only header argument is supported. (prefix=$prefix)" >&2 fi exit 0 fi if [[ "$metascript_included" == "yes" ]] then log_warn "metascript already included ($0)" else metascript_included=yes # at this step $(pwd) should be this of metascript.sh # while $0 is this of caller script using metascript if [[ -z $toolsdir ]] then # assume all tools are in lib/ toolsdir=$(dirname $(readlink -f $0))/lib # all tools resources are relative to this directory fi if [[ -z $log_functions ]] then log_functions=$toolsdir/log_functions.sh [[ -f $log_functions ]] || { echo "[FATAL] Missing $log_functions , toolsdir=$toolsdir script=$0" >&2 ; exit 1 ;} source $log_functions fi metascript_usage() { cat <&2 metascript commands : help|usage help or usage of this {$0} tool dryrun|show|showdoc display what should/will be done defersource= script file defining defer() non standard function defer= defer function to use, default is showdoc toolsresourcesdir= where to pick resource default to parent of script $toolsresourcesdir mostly used with dryrun apply default : will actual do work without defer EOF } usage() { echo "[WARNING} no specific usage function for {$0}, to improve by developer" >&2 echo >&2 metascript_usage } showinfo() { echo "$@" } metarun() { $defer $metasudo "$@" } showdoc() { if [[ $1 =~ ^tools/ ]] then # assumes it handles ENV_METASCRIPT_DEFER "$@" else echo '```' autoquoteargs "$@" echo echo '```' fi } redirectto() { tofile=$1 if [[ -n $defer ]] then echo "Copy to $tofile" echo '```' cat echo '```' else cat > $tofile fi } execredirectfrom() { tofile=$1 shift if [[ -n $defer ]] then echo '```' autoquoteargs "$@" echo ' < '"$tofile" echo '```' else "$@" < $tofile fi } execredirectto() { tofile=$1 shift if [[ -n $defer ]] then echo '```' autoquoteargs "$@" echo ' > '"$tofile" echo '```' else $@ > $tofile fi } execredirecttoroot() { tofile=$1 shift if [[ -n $defer ]] then echo '```' autoquoteargs "$@" echo ' | sudo tee '"$tofile" echo '```' else "$@" | sudo tee $tofile fi } pipeto() { if [[ -n $defer ]] then echo '```' echo -n 'cat << EOF| ' autoquoteargs "$@" echo cat echo "EOF" echo '```' else cat | "$@" fi } query_ext() { local prompt="$1" local var=$2 if [[ -n $defer ]] then # uppercase it eval $var=${var^^} $defer query $prompt "$(eval echo \$$var)" else echo -ne $prompt read $var fi } # two arguments first prompt, second name of var query_password() { local prompt="$1" local var=$2 if [[ -n $defer ]] then # uppercase it eval $var=${var^^} $defer query_password "\"$prompt\"" "$(eval echo \$$var)" else read -sp "$prompt" $var echo fi } # echo yes if reply match ^[Yy]([eE][sS]|)$ or no if does not match. query_yesno() { local prompt="$1" local yesno=no read -p "$prompt (Yes/No) " yesno if [[ $yesno =~ ^[Yy]([eE][sS]|)$ ]] then echo yes else echo no fi } # through hardcoded 'secret' variable create_secret() { # global secret # declare -g secret # export -n secret local -i length=$1 [[ -z $length ]] && length = 32 if (( length < 8 )) then log_warn "secret length $length < 8. very small" fi # secret=$(echo $RANDOM | md5sum | head -c $length) secret=$(tr -dc A-Za-z0-9 &2 from=${from//$s/\\$s} fi if [[ $from =~ \[ ]] then from=${from//\[/\\\[} fi if [[ $from =~ \* ]] then from=${from//\*/\\\*} fi if [[ $from =~ ^(.*)\$$ ]] then from=${BASH_REMATCH[1]}'\$' fi if [[ $from =~ ^\^(.*)$ ]] then from='\^'${BASH_REMATCH[1]} fi if [[ $to =~ [\\] ]] then # escape char \ should be doubled to=${to//\\/\\\\} fi if [[ $to =~ $s ]] then # echo "[ERROR] character $s is prohibited due to sed usage" >&2 # echo "This is a limitation of metascript.sh script, replaced by \$s" >&2 to=${to//$s/\\$s} fi if [[ $to =~ [\&] ]] then # echo "[ERROR] character & is prohibited due to sed usage" >&2 to=${to//\&/\\\&} fi # replace it globaly echo "s$s$from$s$to${s}g" } sedreplacefromto() { local from="$1" local to="$2" shift 2 local sedexpr="$1" execredirectto $to sed "$sedexpr" $from shift while [[ $# > 0 ]] do sedexpr="$1" $defer sed -i "$sedexpr" $to shift done } replacefromto() { local from="$1" local to="$2" shift 2 if [[ -n $defer ]] then $defer "replace $@ from '$from' into '$to'" else local sedexpr=$(sed_substitute_expr "$1" "$2") execredirectto $to sed "$sedexpr" $from shift 2 while [[ $# > 0 ]] do sedexpr=$(sed_substitute_expr "$1" "$2") $defer sed -i "$sedexpr" $to shift 2 done fi } sedreplacein() { local file=$1 shift while [[ $# > 0 ]] do $defer sed -i "$1" $file shift done } replacein() { local infile=$1 shift if [[ -n $defer ]] then $defer "replace $@ into '$infile'" else while [[ $# > 0 ]] do sedexpr=$(sed_substitute_expr "$1" "$2") $defer sed -i "$sedexpr" $infile shift 2 done fi } parsemetaarg() { case $1 in apply) defer= ;; defersource=*) defersource=${1/defersource=/} ;; defer=*) defer=${1/defer=/} ;; dryrun|show|showdoc) defer=showdoc ;; metasudo=*) metasudo=${1/metasudo=} ;; help|usage) usage ;; toolsresourcesdir=*) toolsresourcesdir=${1/toolsresourcesdir=/} ;; scl_enable=*) scl_args=(scl enable ${1/scl_enable=/} --) ;; *) log_error "unrecognized argument '$1'" usage exit 1 ;; esac } enforcearg() { local var="$1" local default="$2" eval value='$'"$var" if [[ -z $value ]] then log_error "{0} expect '$var' to be set ex $var=$default" usage exit 1 fi } enforcefile() { file="$1" constraint="$2" if [[ ! -f "$file" ]] then if [[ $constraint = exists ]] then log_error "[ERROR] Missing expected $file" [[ -n $defer ]] || exit 1 fi else if [[ $constraint = does_not_exists ]] then log_error "[ERROR] '$file' already exists. Move it away" [[ -n $defer ]] || exit 1 fi fi } enforcedir() { dir="$1" constraint="$2" if [[ $constraint = does_not_exist ]] then if [[ -e $dir ]] then log_error "'$dir' already exists. Move it away" [[ -n $defer ]] || exit 1 fi fi if [[ $constraint = exists ]] then if [[ ! -d "$dir" ]] then if [[ -e "$dir" ]] then log_error "'$dir' already exists but is not a directory as expected" [[ -n $defer ]] || exit 1 fi log_error "[ERROR] Missing expected directory '$dir'" [[ -n $defer ]] || exit 1 fi fi } applymetaargs() { if [[ -n $defer ]] then if [[ -n $defersource ]] then if [[ -f $defersource ]] then log_any "source $defersource" source $defersource else exit_fatal "defersource $defersource provided but not a file" fi fi # $showinfo "generated with $0 $allparms" export ENV_METASCRIPT_DEFER="$defer" export ENV_METASCRIPT_RESOURCESDIR="$toolsresourcesdir" fi } defaultmetainit() { while [[ $# > 0 ]] do parsemetaarg "$1" shift done applymetaargs } read_organisation() { organisation_file=$(find organisation -name '*.conf') if [[ -f $organisation_file ]] then while read line do case $line in name=*) organisation_name=${line/name=/} ;; domain=*) organisation_domain=${line/domain=/} ;; ldap_base=*) organisation_ldap_base=${line/ldap_base=/} ;; image_keyword=*) organisation_image_keyword=${line/image_keyword=/} ;; *) log_warn "'$line' not recognized as an organisation parameter" ;; esac done < $organisation_file fi } check_variable_match() { local var="$1" local match="$2" local default="$3" local value="" eval value='$'"$var" if [[ -z "$value" ]] then echo "set $var to default value $default" eval "$var=$default" eval value='$'"$var" fi if [[ ! $value =~ $match ]] then log_error "$var $value should match $match" exit 1 fi } check_variable_in() { local var="$1" local default="$2" shift 2 local value="" local values="$@" eval value='$'"$var" if [[ -z "$value" ]] then echo "set $var to default value $default" eval "$var=$default" eval value='$'"$var" fi while [[ $# > 0 ]] do if [[ "$value" = "$1" ]] then return fi shift done log_error "'$var' should be within $values it is '$value'" exit 1 } check_root() { if [[ -n $defer ]] then $defer "{$0} script to run run as root or with sudo" else [[ $EUID -eq 0 ]] || { log_error "{$0} You have to be root or use sudo to run this script" exit 1; } fi } get_timestamp_second() { echo "$(date +"%Y%m%d%H%M%S")" } todo() { log_any TODO "$@" } get_resource_var() { local varname="$1" local default_value="$2" eval value='$'"$varname" if [[ -z $value ]] then echo "# ($0:metascript.sh:$LINENO) $(date)" >>$collect_context if [[ -z $default_value ]] then log_error "resource $varname does not exists and no non empty default provided" echo "# $varname=" >>$collect_context exit 1 fi log_warn "($0) Using default value '$varname'='$default_value' HARDCODED in script : should be fixed with proper default file .resources.var, see traces in $collect_context" read "$varname" <<<"$default_value" echo "$varname=$default_value" >>$collect_context fi } setup_resources_var() { local resource_var="$1" enforcefile "$resource_var" exists while read line do if [[ $line =~ ^([a-zA-Z0-9_]+)=(.+)$ ]] then varname=${BASH_REMATCH[1]} value=${BASH_REMATCH[2]} read "$varname" <<<"$value" elif [[ $line =~ ^# ]] then echo "$line" else log_warn "Invalid syntax in $resource_var '$line' does not match any expected expression" fi done <"$resource_var" } # allow to check mounted points mountpoint_get_device() { local mount_point="$1" $defer awk "{ if (\$2 == \""$mount_point"\") print \$1 ;}" /proc/mounts } device_get_mountpoints() { local device="$1" $defer awk "{ if (\$1 == \""$device"\") print \$2 ;}" /proc/mounts } mount_if_needed() { local mountpoint="$1" local mountdevice="$2" local param="$3" device=$(mountpoint_get_device "$mountpoint") enforcedir "$mountpoint" exists # device is a device ... # enforcefile "$mountdevice" exists if [[ -z $device ]] then $metarun mount $param "$mountdevice" "$device" elif [[ "$device" == "$mountdevice" ]] then log_info "$device already mounted on $mountdevice" else log_warn "Another device $device is mounted on $mountdevice, not $mountdevice" fi } umount_if_needed() { local mountpoint="$1" local mountdevice="$2" device=$(mountpoint_get_device "$mountpoint") if [[ -n "$device" ]] then if [[ "$device" == "$mountpoint" ]] then $metarun umount "$mountpoint" else log_warn "Another device $device is mounted on $mountdevice, not $mountdevice" fi fi } # collect all hardcoded values. mkdir -p ~/.artlog collect_context=~/.artlog/collect_context.var if [[ -z $toolsresourcesdir ]] then # project directory # from current $(pwd) will follow parent dir hierarchy to find .resources.var dir="$(pwd)" while [[ -n $dir ]] && [[ -d $dir ]] && [[ ! -f $dir/.resources.var ]] do new_dir=$(dirname "$dir") if [[ $new_dir == $dir ]] then # protect against infinite loop break fi dir="$new_dir" done if [[ -f $dir/.resources.var ]] then toolsresourcesdir=$dir else if [[ -z $ENV_METASCRIPT_RESOURCESDIR ]] then toolsresourcesdir=$toolsparentdir else toolsresourcesdir=$ENV_METASCRIPT_RESOURCESDIR fi fi fi resources_var=$toolsresourcesdir/.resources.var if [[ -f $resources_var ]] then setup_resources_var "$resources_var" else log_warn "No $resources_var found" fi # quick way to give scl patches to fill scl_arg array if [[ -f $toolsresourcesdir/.scl_env ]] then source $toolsresourcesdir/.scl_env fi # empty defer means doit defer=$ENV_METASCRIPT_DEFER showinfo=showinfo allparms="$@" applymetaargs=applymetaargs metarun=$defer # metascript included fi