diff --git a/localdump.sh b/localdump.sh index f9d1b11f2ae569a3a7116e8df613c3337d79bb40..f5e72fb2db36722120d1b3b1551d2f7c158c7356 100755 --- a/localdump.sh +++ b/localdump.sh @@ -32,8 +32,9 @@ # ---------------------------------------------------------------------- - . `dirname $0`/jobhelper.sh - . `dirname $0`/inc_bash.sh + . $(dirname $0)/jobhelper.sh + . $(dirname $0)/inc_bash.sh + . $(dirname $0)/vendor/ini.class.sh # if [ -r ~/.backup.conf ]; then # . ~/.backup.conf # fi @@ -50,18 +51,147 @@ BACKUP_BASEDIR= BACKUP_PLUGINDIR= + DBD_BASEDIR=plugins/localdump/profiles + declare -A DBD_PARAMS + DBD_DEBUG=0 + # Cleanup local dumps older N days typeset -i BACKUP_KEEP_DAYS=0 BACKUP_DATE= +# -------------------------------------------------------------------- +# +# -------------------------------------------------------------------- + +# ---------- GETTER + +function dbdetect._wd(){ + test "$DBD_DEBUG" -eq "1" && echo "DEBUG: $*" >&2 +} + +function dbdetect.getConfigs(){ + find ${DBD_BASEDIR} -type f -name "*.ini" +} + +function dbdetect.getType(){ + basename "$1" | cut -d "_" -f 1 | sed "s,\.ini$,," +} +function dbdetect.getProfile(){ + basename "$1" | cut -d "_" -f 1- | sed "s,\.ini$,," +} + + +function dbdetect.exists(){ + local _config="$1" + + DBD_PARAMS= + + local _found=0 + + # set file and inisection we read values from + ini.set "$_config" "detect" + + # --- check tcp + local tcpport; tcpport=$( ini.value "tcp" ) + local tcptarget; tcptarget=$( ini.value "tcp-target" ) + if { ! >/dev/tcp/$tcptarget/$tcpport; } > /dev/null 2>&1; then + # echo "No port tcp $tcpport available" + dbdetect._wd "... No port tcp $tcpport available on $tcptarget" + return 1 + fi + dbdetect._wd "... Found tcp $tcpport on $tcptarget." + + # --- check tcp process + local tcpprocess; tcpprocess=$( ini.value "tcp-process" ) + if [ -n "$tcpprocess" ]; then + if ! netstat -tulpen 2>/dev/null | grep "^tcp.*:${tcpport} .*/${tcpprocess}" >/dev/null; then + # echo "No port tcp $tcpport available" + dbdetect._wd "... $tcpprocess not found for tcp ${tcpport}" + return 1 + fi + dbdetect._wd "... tcp $tcpport is used by $tcpprocess." + fi + + # --- check binaries + local binary; binary=$( ini.value "binary" ) + if [ -n "${binary}" ]; then + for mybinary in $( echo "${binary}" | tr "," " " ); do + if ! j_requireBinary "$mybinary" 1 >/dev/null 2>&1; then + dbdetect._wd "... Missing binary: ${mybinary}" + return 1 + fi + dbdetect._wd "... Binary: ${mybinary} was found" + done + fi + + # --- check process + local process; process=$( ini.value "process" ) + if [ -n "${process}" ]; then + if ! j_requireProcess "${process}" 1 >/dev/null; then + dbdetect._wd "... Missing process: ${process}" + return 1 + fi + # if ! ps -eo command | grep -E "${process}" >/dev/null 2>&1; then + # dbdetect._wd "... Process ${process} was not found" + # return 1 + # fi + dbdetect._wd "... Process ${process} was found" + fi + + ini.set "$_config" "set" + local value + local dbuser=$( ini.value "dbuser" ) + local dbpassword=$( ini.value "dbpassword" ) + + for mykey in $( ini.keys ) + do + # params = '--port={{tcp}} --password={{dbpassword}} --user={{dbuser}} --host={{tcp-target}}' + value="$( ini.value "$mykey" )" + value="${value//\{\{tcp\}\}/$tcpport}" + value="${value//\{\{tcp-target\}\}/$tcptarget}" + value="${value//\{\{dbuser\}\}/$dbuser}" + value="${value//\{\{dbpassword\}\}/$dbpassword}" + DBD_PARAMS[$mykey]="$value" + done + + return 0 +} + +# set a profile name +function dbdetect.setProfile(){ + dbdetect.exists "${DBD_BASEDIR}/${PROFILENAME}.ini" +} + +function dbdetect.getParams(){ + echo "${DBD_PARAMS['params']}" +} + +# set variables in [set] -> env = ... +# USAGE: +# eval $( dbdetect.setenv ) +function dbdetect.setenv(){ + echo "${DBD_PARAMS['env']}" +} + +# unset variables from [set] -> env = ... +# USAGE: +# eval $( dbdetect.unsetenv ) +function dbdetect.unssetenv(){ + echo "${DBD_PARAMS['env']}" | grep -o '[a-z0-9]*=' | tr -d '=' | sed "s,^,unset ," +} + +function dbdetect.runas(){ + echo "${DBD_PARAMS['su']}" +} + # ---------------------------------------------------------------------- # FUNCTIONS 4 DB-WRAPPER # ---------------------------------------------------------------------- function db.init(){ - BACKUP_BASEDIR=`_j_getvar ${JOBFILE} "dir-localdumps"` + BACKUP_BASEDIR=$(_j_getvar ${JOBFILE} "dir-localdumps") # check if [ -z "$BACKUP_BASEDIR" ]; then @@ -71,8 +201,8 @@ color reset exit 1 fi - BACKUP_PLUGINDIR=`dirname $0`/plugins/localdump - BACKUP_KEEP_DAYS=`_j_getvar ${JOBFILE} "keep-days"` + BACKUP_PLUGINDIR=$(dirname $0)/plugins/localdump + BACKUP_KEEP_DAYS=$(_j_getvar ${JOBFILE} "keep-days") if [ $BACKUP_KEEP_DAYS -eq 0 ]; then BACKUP_KEEP_DAYS=7 @@ -88,8 +218,8 @@ # $myrc is last returncode - set in fetchrc if [ $myrc -eq 0 ]; then - echo -n "gzip $1 ... " - gzip -9 -f "${1}" + echo -n "gzip $_outfile ... " + gzip -9 -f "${_outfile}" fetchrc else cecho error "ERROR occured while dumping - no gzip of $_outfile" @@ -115,7 +245,7 @@ find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print color reset - if [ `find "${BACKUP_TARGETDIR}" -type f | wc -l` -eq 0 ]; then + if [ $(find "${BACKUP_TARGETDIR}" -type f | wc -l) -eq 0 ]; then echo "INFO: the directory is empty - deleting it" rm -rf "${BACKUP_TARGETDIR}" fi @@ -160,14 +290,23 @@ # param string name of a service function get_service_script(){ local _service=$1 - ls -1 ${BACKUP_PLUGINDIR}/${_service}.sh 2>/dev/null + local _type; _type=$( dbdetect.getType "$_service" ) + ls -1 ${BACKUP_PLUGINDIR}/${_type}.sh 2>/dev/null } # ------------------------------------------------------------ # get a list of existing dumper scripts function get_services(){ - #ls -1 `dirname $0`/localdump_* | sed "s#`dirname $0`/localdump_##" | sed "s#\.sh##" - ls -1 ${BACKUP_PLUGINDIR}/*.sh | sed "s#${BACKUP_PLUGINDIR}/##" | sed "s#\.sh##" | sort + + # + # ls -1 ${BACKUP_PLUGINDIR}/*.sh | sed "s#${BACKUP_PLUGINDIR}/##" | sed "s#\.sh##" | sort + + for config in $(dbdetect.getConfigs); do + if dbdetect.exists $config; then + echo "$( dbdetect.getProfile $config )" + fi + done + } # ------------------------------------------------------------ @@ -239,8 +378,8 @@ if [ -f $metafile ]; then cat $metafile else - sBasename=`basename $1` - sDb=`echo ${sBasename} | sed "s#__[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9].*##g"` + sBasename=$(basename $1) + sDb=$(echo ${sBasename} | sed "s#__[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9].*##g") if [ -z $sDb ]; then color error echo ERROR: db name was not detected from file $1 @@ -259,7 +398,7 @@ local _self _self=$( basename "$0" ) echo "SYNTAX: " - echo "$_self [[operation]] [Name_of_ervice] [[more services]]" + echo "$_self [[operation]] [Name_of_service] [[more services]]" echo echo "$_self backup [Name_of_service]" echo "$_self restore [Name_of_service] [[file-to-restore]]" @@ -287,7 +426,7 @@ # . /usr/local/bin/inc_cronfunctions.sh j_requireUser "root" - h1 `date` IML BACKUP :: LOCALDUMP :: $* + h1 $(date) IML BACKUP :: LOCALDUMP :: $* if [ $# -eq 0 ]; then color error @@ -321,6 +460,7 @@ # ------------------------------------------------------------ backup) if [ "$SERVICENAME" = "ALL" ]; then + services=$(get_services) echo AUTO: calling local backup scripts for all known services echo $services @@ -345,24 +485,49 @@ fi done + # ----- GO - for SERVICENAME in $services + # PROFILENAME mysql_localhost_13306 + # SERVICENAME mysql + # + for PROFILENAME in $services do - BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${SERVICENAME} - BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) + if dbdetect.setProfile "${PROFILENAME}"; then + + SERVICENAME=$( dbdetect.getType "$PROFILENAME" ) + BACKUP_PARAMS=$( dbdetect.getParams ) + + BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${PROFILENAME} + BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) + + # ----- start service specific script + h2 "START SCRIPT FOR [${PROFILENAME}] -> ${SERVICENAME}" + + # ------ set env + # dbdetect.setenv + eval $( dbdetect.setenv ) + # echo "BACKUP_PARAMS = $BACKUP_PARAMS" - # ----- start service specific script - h2 "START SCRIPT FOR ${SERVICENAME} - $BACKUP_SCRIPT" + _j_runHooks "200-before-db-service" + . $BACKUP_SCRIPT $mode - _j_runHooks "200-before-db-service" - . $BACKUP_SCRIPT $mode - test $rc -gt 0 && j_notify "db ${SERVICENAME}" "$BACKUP_SCRIPT $mode was finished with rc=$rc" $rc - _j_runHooks "230-after-db-service" "$rc" - # ----- post jobs: cleanup - cleanup_backup_target - show_info_backup_target + test $rc -gt 0 && j_notify "db ${SERVICENAME}" "$BACKUP_SCRIPT $mode was finished with rc=$rc" $rc + _j_runHooks "230-after-db-service" "$rc" + + # ----- post jobs: cleanup + cleanup_backup_target + show_info_backup_target + + # ------ unset env + eval $( dbdetect.unssetenv ) + + else + + echo "SKIP: $PROFILENAME" + + fi done ;; @@ -392,12 +557,12 @@ echo color input - sTargetDb=`guessDB ${dbfile}` + sTargetDb=$(guessDB ${dbfile}) echo -n "new database name [$sTargetDb] >" color reset read sTargetDb if [ -z $sTargetDb ]; then - sTargetDb=`guessDB ${dbfile}` + sTargetDb=$(guessDB ${dbfile}) fi echo @@ -417,16 +582,36 @@ rc=$rc+1 else - . $BACKUP_SCRIPT $mode "${sDumpfile}" "${sTargetDb}" - if [ $? -ne 0 -o $rc -ne 0 ]; then - color error - echo ERROR: $mode failed. See ouput above. :-/ - color reset - else - color ok - echo OK, $mode was successful. - color reset - fi + PROFILENAME="${sDumpfile//${BACKUP_BASEDIR}/}" + PROFILENAME="$( echo $PROFILENAME | sed "s,^/*,," | cut -f 1 -d '/')" + + if dbdetect.setProfile "${PROFILENAME}"; then + + SERVICENAME=$( dbdetect.getType "$PROFILENAME" ) + + BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${PROFILENAME} + BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) + + BACKUP_PARAMS=$( dbdetect.getParams ) + + # ------ set env + eval $( dbdetect.setenv ) + + . $BACKUP_SCRIPT $mode "${sDumpfile}" "${sTargetDb}" + + if [ $? -ne 0 -o $rc -ne 0 ]; then + color error + echo ERROR: $mode failed. See ouput above. :-/ + color reset + else + color ok + echo OK, $mode was successful. + color reset + fi + + # ------ unset env + eval $( dbdetect.unssetenv ) + fi fi ;; diff --git a/plugins/localdump/mysql.sh b/plugins/localdump/mysql.sh index 8b1c866a9449942106ac5cc58caedd373b230d65..e5a9d2d39e9dacf2fc0931142709ef1995eace91 100755 --- a/plugins/localdump/mysql.sh +++ b/plugins/localdump/mysql.sh @@ -48,14 +48,14 @@ SOURCE_DIR=/var/lib/mysql # it sets mysql_FOUND as flag function mysql._check(){ - j_requireBinary "mysql" 1 - j_requireBinary "mysqldump" 1 - j_requireProcess "mysqld|mariadb" 1 + # j_requireBinary "mysql" 1 + # j_requireBinary "mysqldump" 1 + # j_requireProcess "mysqld|mariadb" 1 - if [ ! -d $SOURCE_DIR ]; then - echo "INFO: directory $SOURCE_DIR doees not exist." - rc=$rc+1 - fi + # if [ ! -d $SOURCE_DIR ]; then + # echo "INFO: directory $SOURCE_DIR doees not exist." + # rc=$rc+1 + # fi # set flag and reset return code test $rc -eq 0 && mysql_FOUND=1 @@ -71,7 +71,7 @@ function mysql._check(){ # param string name of the dabase scheme function mysql.db.create(){ local _dbname=$1 - echo "CREATE DATABASE IF NOT EXISTS \`${_dbname}\`;" | mysql + echo "CREATE DATABASE IF NOT EXISTS \`${_dbname}\`;" | mysql ${BACKUP_PARAMS} fetchrc >/dev/null test $myrc -eq 0 && mysql_COUNT_CREATE+=1 test $myrc -eq 0 || mysql_COUNT_ERRORS+=1 @@ -89,14 +89,14 @@ function mysql.db.dump(){ local _dbname=$1 local _dumpfile=$2 - mysqldump $LD_MYSQL_DUMP_PARAMS --result-file="$_dumpfile" "$_dbname" 2>&1 + mysqldump ${BACKUP_PARAMS} $LD_MYSQL_DUMP_PARAMS --result-file="$_dumpfile" "$_dbname" 2>&1 fetchrc >/dev/null if [ $myrc -eq 0 ]; then if ! zgrep -iE "(CREATE|INSERT)" "$_dumpfile" >/dev/null then typeset -i local _iTables - _iTables=$( mysql --skip-column-names --batch -e "use $_dbname; show tables ;" | wc -l ) + _iTables=$( mysql ${BACKUP_PARAMS} --skip-column-names --batch -e "use $_dbname; show tables ;" | wc -l ) if [ $_iTables -eq 0 ]; then echo -n "EMPTY DATABASE ... " @@ -120,7 +120,7 @@ function mysql.db.dump(){ function mysql.db.import(){ local _dumpfile=$1 local _dbname=$2 - zcat "$_dumpfile" | mysql "${_dbname}" + zcat "$_dumpfile" | mysql $BACKUP_PARAMS "${_dbname}" fetchrc >/dev/null test $myrc -eq 0 && mysql_COUNT_IMPORT+=1 test $myrc -eq 0 || mysql_COUNT_ERRORS+=1 @@ -129,7 +129,7 @@ function mysql.db.import(){ # show a list of existing databases function mysql.db.list(){ # mysql -Ee "show databases ;" | grep "^Database:" | awk '{ print $2 }' - local _result=$( mysql -Ee "show databases ;" ) + local _result=$( mysql ${BACKUP_PARAMS} -Ee "show databases ;" $BACKUP_PARAMS ) fetchrc >/dev/null test $myrc -eq 0 && mysql_COUNT_DB=$( echo "$_result" | grep -c "^Database:" ) test $myrc -eq 0 && echo "$_result" | grep "^Database:" | awk '{ print $2 }' @@ -144,7 +144,8 @@ function mysql.db.list(){ # USAGE: to abort a function if not available: # mysql.available || return function mysql.available(){ - typeset -i local _rc=(1-$mysql_FOUND) + local _rc; + typeset -i _rc=(1-$mysql_FOUND) return $_rc } diff --git a/vendor/ini.class.sh b/vendor/ini.class.sh index d923606cabf675c1477895e8b4f23779d4228ba4..0392d80547e66c19456fe25c38c7954c12182a9d 100644 --- a/vendor/ini.class.sh +++ b/vendor/ini.class.sh @@ -7,6 +7,8 @@ # ---------------------------------------------------------------------- # 2024-02-04 v0.1 Initial version # 2024-02-08 v0.2 add ini.varexport; improve replacements of quotes +# 2024-02-10 v0.3 handle spaces and tabs around vars and values +# 2024-02-12 v0.4 rename local varables # ====================================================================== INI_FILE= @@ -23,7 +25,7 @@ function ini.set(){ INI_FILE= INI_SECTION= if [ ! -f "$1" ]; then - echo "ERROR: file does not exist: $1" + echo "ERROR: file does not exists: $1" exit 1 fi INI_FILE="$1" @@ -56,29 +58,31 @@ function ini.setsection(){ # Get all sections # param1 - name of the ini file function ini.sections(){ - local myfile=$1 - grep "^\[" "$myfile" | sed 's,^\[,,' | sed 's,\].*,,' + local myinifile=${1:-$INI_FILE} + grep "^\[" "$myinifile" | sed 's,^\[,,' | sed 's,\].*,,' } # Get all content inside a section # param1 - name of the ini file # param2 - name of the section in ini file function ini.section(){ - local myfile=$1 - local mysection=$2 - sed -e "0,/^\[${mysection}\]/ d" -e '/^\[/,$ d' $myfile | grep -v "^[#;]" + local myinifile=${1:-$INI_FILE} + local myinisection=${2:-$INI_SECTION} + sed -e "0,/^\[${myinisection}\]/ d" -e '/^\[/,$ d' $myinifile \ + | grep -v "^[#;]" \ + | sed -e "s/^[ \t]*//g" -e "s/[ \t]*=[ \t]*/=/g" } # Get all keys inside a section # param1 - name of the ini file # param2 - name of the section in ini file function ini.keys(){ - local myfile=${1:-$INI_FILE} - local mysection=${2:-$INI_SECTION} - - ini.section "${myfile}" "${mysection}" \ + local myinifile=${1:-$INI_FILE} + local myinisection=${2:-$INI_SECTION} + ini.section "${myinifile}" "${myinisection}" \ | grep "^[\ \t]*[^=]" \ - | cut -f 1 -d "=" + | cut -f 1 -d "=" \ + | sort -u } @@ -93,17 +97,15 @@ function ini.value(){ elif [ -z "$2" ]; then ini.value "$INI_FILE" "$INI_SECTION" "$1" else - local myfile=$1 - local mysection=$2 + local myinifile=$1 + local myinisection=$2 local myvarname=$3 local out - out=$(ini.section "${myfile}" "${mysection}" \ + out=$(ini.section "${myinifile}" "${myinisection}" \ | grep "^[\ \t]*${myvarname}[\ \t]*=.*" \ | cut -f 2- -d "=" \ | sed -e 's,^\ *,,' -e 's, *$,,' ) - - # if value does not end with [] than the last found value wins grep "\[\]$" <<< "myvarname" >/dev/null && out="echo $out | tail -1" # delete quote chars on start and end @@ -120,16 +122,16 @@ function ini.value(){ # param string ini file to read function ini.varexport(){ local myprefix="$1" - local myfile="$2" + local myinifile="$2" local var= - for mysection in $(ini.sections "$myfile"); do - var="${myprefix}${mysection}" + for myinisection in $(ini.sections "$myinifile"); do + var="${myprefix}${myinisection}" echo "declare -A ${var}; " echo "export ${var}; " - for mykey in $(ini.keys "$myfile" "$mysection"); do - value="$(ini.value "$myfile" "$mysection" "$mykey")" + for mykey in $(ini.keys "$myinifile" "$myinisection"); do + value="$(ini.value "$myinifile" "$myinisection" "$mykey")" echo ${var}[$mykey]="\"$value\"" done done