#!/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 metascript_version=v1.0.0 # if called directly if [[ "$0" =~ ^(.*)/metascript.sh$ ]] then prefix=${BASH_REMATCH[1]} case $1 in header) cat </dev/null toolsdir=\$(pwd) source metascript.sh popd >/dev/null metarun=metarun # change default to sudo if needed # metasudo=sudo while [[ \$# > 0 ]] do case "\$1" in *) parsemetaarg "\$1" ;; esac shift done usage EOF ;; version) echo $metascript_version ;; *) echo "[WARNING] only header and version arguments are supported. (prefix=$prefix), this script is intended to be included not to be directly called." >&2 exit 1 esac 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. stop after usage display. 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 "$@" } autoquoteargs() { echo -n "$1" shift while [[ $# > 0 ]] do if [[ "$1" =~ [\ \$] ]] then echo -n " '$1'" else echo -n " $1" fi shift done } echoarray() { declare -a arr=("${@}") declare -i len=${#arr[@]} # Show passed array for ((n = 0; n < len; n++)) do echo -en " \"${arr[$n]}\"" done echo } deferpipe() { cat echo "# | $@" } include_source() { if [[ -f $1 ]] then source $1 else log_fatal "Missing $1 script" exit 1 fi } 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() { fromfile=$1 shift enforcefile "$fromfile" exists if [[ -n $defer ]] then echo '```' autoquoteargs "$@" echo ' < '"$fromfile" echo '```' else "$@" < $fromfile 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"' >/dev/null' echo '```' else "$@" | sudo tee $tofile >/dev/null fi } execredirectas_to() { while [[ $# > 0 ]] do case $1 in user=*) if [[ "$1" =~ ^user=([a-z]+[a-z0-9]*)$ ]] then asuser=${BASH_REMATCH[1]} else log_fatal "execredirecttouser missing user= in '$@'" [[ -n $defer ]] || exit 1 fi ;; chroot=*) aschroot=${1/chroot=} ;; *) tofile=$1 shift break esac shift done local command=() if [[ -n $aschroot ]] then command=(sudo chroot) if [[ -n $asuser ]] then # no group yet command+=(--userspec=$asuser:$asuser) fi command+=($aschroot) elif [[ -n $asuser ]] then command=(sudo -u $asuser) fi command+=(tee $tofile) if [[ -n $defer ]] then echo '```' autoquoteargs "$@" echo ' | '"${command[@]}"' >/dev/null' echo '```' else "$@" | "${command[@]}" >/dev/null 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 ;; debug) set -ex ;; metasudo=*) metasudo=${1/metasudo=} ;; help|usage) usage exit 0 ;; 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" if [[ -n $defer ]] then [[ -z $default ]] && default="DEFAULT" log_warn "in defer/dryrun force $var=$default" eval "$var=$default" else usage exit 1 fi fi } enforce() { objecttype=$1 shift case $objecttype in file|dir) object=$1 shift constraint=$1 case $constraint in exists|does_not_exist|create_if_needed) enforce${objecttype} "$object" "$@" ;; *) log_error "enforce $objecttype '$object' $@" log_fatal "constraint '$constraint' unsupported. Please fix the code." exit 1 ;; esac ;; var|arg) enforcearg "$@" ;; *) log_error "enforce $objecttype $@" log_fatal "objectttype $objecttype unsupported" exit 1 ;; esac } enforcefile() { local file="$1" local constraint="$2" if [[ ! -f "$file" ]] then case $constraint in exists) log_error "Missing expected $file" [[ -n $defer ]] || exit 1 ;; create_if_needed) $metarun touch $file ;; esac else if [[ $constraint = does_not_exist ]] then log_error "'$file' already exists. Move it away" [[ -n $defer ]] || exit 1 fi fi } enforcedir() { local dir="$1" local constraint="$2" if [[ -e $dir ]] then case $constraint in does_not_exist) if [[ -d "$dir" ]] then log_error "'$dir' already exists" [[ -n $defer ]] || exit 1 else log_error "'$dir' already exists and is not a directory as expected" [[ -n $defer ]] || exit 1 fi ;; create_if_needed|exists) if [[ ! -d "$dir" ]] then log_error "'$dir' already exists and is not a directory as expected" [[ -n $defer ]] || exit 1 fi ;; esac else case $constraint in exists) log_error "Missing expected directory $dir" [[ -n $defer ]] || exit 1 ;; create_if_needed) if [[ ! -d "$dir" ]] then $metarun mkdir -p "$dir" fi ;; esac 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 if [[ -f $resource_var ]] then 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 if [[ -n $defer ]] then $defer : "$line" fi else log_warn "Invalid syntax in $resource_var '$line' does not match any expected expression" fi done <"$resource_var" fi } # allow to check mounted points mountpoint_get_device() { local mount_point="$1" mount_point=$(readlink -f "$mount_point") $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 mountdevice="$1" local mountpoint="$2" local param="$3" if [[ -n $defer ]] then $defer 'device=$(mountpoint_get_device '"$mountpoint"')' else device=$(mountpoint_get_device "$mountpoint") fi enforcedir "$mountpoint" exists # device is a device ... # enforcefile "$mountdevice" exists if [[ -z $device ]] then $metarun mount $param "$mountdevice" "$mountpoint" elif [[ "$device" == "$mountdevice" ]] then log_info "$device already mounted on $mountdevice" else log_warn "Another device $device is mounted on $mountpoint, not $mountdevice" fi } umount_if_needed() { local mountdevice="$1" local mountpoint="$2" local param="$3" device=$(mountpoint_get_device "$mountpoint") if [[ -n "$device" ]] then if [[ "$device" == "$mountdevice" ]] then $metarun umount "$mountpoint" else log_warn "Another device $device is mounted on $mountpoint, not $mountdevice" if [[ $param == '--bind' ]] then log_warn "Unmouning ANYWAY ( mount --bind show root device, not mounted directory )" $metarun umount "$mountpoint" fi fi else log_warn "no device found mounted for umount_if_needed $@" fi } exec_bg() { if [[ -z $defer ]] then "$@" & else $defer "$@" '&' 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