From 56b798afb54ca9bbdae40a04dd29e05261ccbc26 Mon Sep 17 00:00:00 2001 From: "Hahn Axel (hahn)" <axel.hahn@unibe.ch> Date: Thu, 14 Mar 2024 13:18:28 +0100 Subject: [PATCH] reformat code; fix restore permissions --- localdump.sh | 1078 +++++++++++++++++++++++++------------------------- 1 file changed, 544 insertions(+), 534 deletions(-) diff --git a/localdump.sh b/localdump.sh index 585c553..6230bef 100755 --- a/localdump.sh +++ b/localdump.sh @@ -16,9 +16,11 @@ # 2022-02-18 ..... WIP: use class like functions # 2022-03-17 ..... WIP: add lines with prefix __DB__ # 2022-11-04 ah rename hooks +# 2024-03-14 ah v2.0: use profiles for local and remote databases # ====================================================================== # --- variables: +# ARCHIVE_BASEDIR {string} base directory for db archive (couchdb2 only) # BACKUP_BASEDIR {string} base directory for db dumps # BACKUP_DATE {string} string with current timestamp; will be part of filename for backups # BACKUP_KEEP_DAYS {int} count of days how long to keep db dumps below $BACKUP_BASEDIR @@ -31,606 +33,614 @@ # CONFIG VARS # ---------------------------------------------------------------------- - . $(dirname $0)/vendor/ini.class.sh || exit 1 - . $(dirname $0)/vendor/color.class.sh || exit 1 + . $(dirname $0)/vendor/ini.class.sh || exit 1 + . $(dirname $0)/vendor/color.class.sh || exit 1 - . $(dirname $0)/includes/jobhelper.sh || exit 1 - . $(dirname $0)/includes/inc_bash.sh || exit 1 + . $(dirname $0)/includes/jobhelper.sh || exit 1 + . $(dirname $0)/includes/inc_bash.sh || exit 1 - . $(dirname $0)/includes/dbdetect.class.sh || exit 1 + . $(dirname $0)/includes/dbdetect.class.sh || exit 1 - if [ ! -r "${JOBFILE}" ]; then - color.echo error "ERROR: missing config file ${JOBFILE}." - exit 1 - fi + if [ ! -r "${JOBFILE}" ]; then + color.echo error "ERROR: missing config file ${JOBFILE}." + exit 1 + fi - LOCALDUMP_LOADED=1 + LOCALDUMP_LOADED=1 - ARCHIVE_BASEDIR= - BACKUP_BASEDIR= - BACKUP_PLUGINDIR= + ARCHIVE_BASEDIR= + BACKUP_BASEDIR= + BACKUP_PLUGINDIR= - DBD_DEBUG=0 + DBD_DEBUG=0 - # Cleanup local dumps older N days - typeset -i BACKUP_KEEP_DAYS=0 + # Cleanup local dumps older N days + typeset -i BACKUP_KEEP_DAYS=0 - BACKUP_DATE= + BACKUP_DATE= + LASTINPUT= # ---------------------------------------------------------------------- # FUNCTIONS 4 DB-WRAPPER # ---------------------------------------------------------------------- - # helpfer function for SERVICENAME.backup - # it is called after the service specific dump was done. - # param {string} filename of created dump file - function db._compressDumpfile(){ - local _outfile=$1 + # helpfer function for SERVICENAME.backup + # it is called after the service specific dump was done. + # param {string} filename of created dump file + function db._compressDumpfile(){ + local _outfile=$1 - # $myrc is last returncode - set in fetchrc - if [ $myrc -eq 0 ]; then - echo -n "gzip $_outfile ... " - gzip -9 -f "${_outfile}" - fetchrc - else - color.echo error "ERROR occured while dumping - no gzip of $_outfile" - fi - # echo -n "__DB__$SERVICENAME INFO: backup to " - # ls -l "$_outfile"* 2>&1 - # echo - } + # $myrc is last returncode - set in fetchrc + if [ $myrc -eq 0 ]; then + echo -n "gzip $_outfile ... " + gzip -9 -f "${_outfile}" + fetchrc + else + color.echo error "ERROR occured while dumping - no gzip of $_outfile" + fi + # echo -n "__DB__$SERVICENAME INFO: backup to " + # ls -l "$_outfile"* 2>&1 + # echo + } # ---------------------------------------------------------------------- # FUNCTIONS 4 BACKUP # ---------------------------------------------------------------------- - # ------------------------------------------------------------ - # cleanup a backup dir: remove old files and delete empty dirs - function cleanup_backup_target(){ - if [ -d "${BACKUP_TARGETDIR}" ]; then - h3 "CLEANUP ${BACKUP_TARGETDIR} older $BACKUP_KEEP_DAYS days ..." - - echo find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print - color.preset cmd - find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print - color.reset - - if [ $(find "${BACKUP_TARGETDIR}" -type f | wc -l) -eq 0 ]; then - echo "INFO: the directory is empty - deleting it" - rm -rf "${BACKUP_TARGETDIR}" - fi - fi - } - - # ------------------------------------------------------------ - # compress a file - # shared function in localdump_* - # param string filename of uncompressed output file - function compress_file(){ - echo -n compressing $1 ... - gzip -9 -f "${1}" - fetchrc - } - - # ------------------------------------------------------------ - # create a backup directory with name of service - # shared function in localdump_* - function create_targetdir(){ - mkdir -p "${BACKUP_TARGETDIR}" 2>/dev/null - if [ ! -d "${BACKUP_TARGETDIR}" ]; then - color.echo "error" "FATAL ERROR: directory ${BACKUP_TARGETDIR} was not created" - exit 1 - fi - } - - - # ------------------------------------------------------------ - # generate a base filename for backup dump based on on db name - # ... and added timestamp - # param string name of database schema - # --> see listBackupedDBs() and guessDB() - these function must be able to split this - function get_outfile(){ - echo $*__${BACKUP_DATE} - } - - # ------------------------------------------------------------ - # get name of a service script - # param string name of a service - function get_service_script(){ - local _service=$1 - local _type; _type=$( dbdetect.getType "$_service" ) - ls -1 ${BACKUP_PLUGINDIR}/${_type}.sh 2>/dev/null - } - - # ------------------------------------------------------------ - # get a list of existing database profiles - function get_database_profiles(){ - for config in $(dbdetect.getConfigs); do - if dbdetect.exists "$config"; then - echo "$( dbdetect.getProfile $config )" + # ------------------------------------------------------------ + # cleanup a backup dir: remove old files and delete empty dirs + function cleanup_backup_target(){ + if [ -d "${BACKUP_TARGETDIR}" ]; then + h3 "CLEANUP ${BACKUP_TARGETDIR} older $BACKUP_KEEP_DAYS days ..." + + echo find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print + color.preset cmd + find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print + color.reset + + if [ $(find "${BACKUP_TARGETDIR}" -type f | wc -l) -eq 0 ]; then + echo "INFO: the directory is empty - deleting it" + rm -rf "${BACKUP_TARGETDIR}" + fi fi - done - } - - # ------------------------------------------------------------ - # show directory infos with count of files and used space - # show used space and count of files and dirs - function show_info_backup_target(){ - if [ -d "${BACKUP_TARGETDIR}" ]; then - h3 "INFO about backup target ${BACKUP_TARGETDIR}" - - echo -n "used space: " - du -hs "${BACKUP_TARGETDIR}" - - echo -n "subdirs : " - find "${BACKUP_TARGETDIR}" -type d | wc -l - - echo -n "files : " - find "${BACKUP_TARGETDIR}" -type f | wc -l - - echo -n "free space: " - df -h "${BACKUP_TARGETDIR}" | tail -1 | awk '{ print $4 }' - echo - fi - } - -# ---------------------------------------------------------------------- -# FUNCTIONS 4 RESTORE -# ---------------------------------------------------------------------- - - # ------------------------------------------------------------ - # restore: show profiles from that exist backups - # global string BACKUP_BASEDIR base directory of all backups - function listBackupedServices(){ - ( - find "${BACKUP_BASEDIR}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; - test -n "${ARCHIVE_BASEDIR}" && find "${ARCHIVE_BASEDIR}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; - ) | sort -u - } - - # ------------------------------------------------------------ - # restore: show databases or dumps of a given database that can be restored - # global string BACKUP_BASEDIR base directory of all backups of selected dbprofile - # param string optional: DB-Name for file filter to select from existing dumps; - function listBackupedDBs(){ - if [ -d "${BACKUP_TARGETDIR}" ]; then - - if [ -z $1 ]; then - # list all databases - find "${BACKUP_TARGETDIR}" -mindepth 1 -maxdepth 1 -type f -exec basename {} \; \ - | sed "s#__[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9].*##g" \ - | sed "s#\..*##g" \ - | sort -ud| sed "s#^\./##g" - else - # list dumps of a database - ls -ltr ${BACKUP_TARGETDIR}/${1}*gz | sed "s,${BACKUP_TARGETDIR}/,,g" - fi + } - else - color.echo error "ERROR: ${BACKUP_TARGETDIR} does not exist - here are no backups to restore." - echo - echo "You can try to restore dumps:" - echo "1) Restore dump files from a backup set" - echo " $(dirname $0)/restore.sh $BACKUP_BASEDIR" - echo "2) Copy restored dumps into $BACKUP_TARGETDIR" - echo "3) Start database restore again" - echo " $(dirname $0)/localdump.sh restore [profile]" - echo - - exit 1 - fi - } - - - # ------------------------------------------------------------ - # guess name of the database file - # param string filename of db dump; can be full path or not - function guessDB(){ - dumpfile=$1 - - # the metafile is written in sqlite backup to store full path - metafile=${BACKUP_TARGETDIR}/${dumpfile}.meta - if [ -f $metafile ]; then - grep "^/" "$metafile" || grep "^ File: " "$metafile" | cut -c 9- - 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") - if [ -z $sDb ]; then - color.echo error "ERROR: db name was not detected from file $1" - exit 1 - fi - echo $sDb - fi - } - - - # read .meta file (that contains output of stats) and restore last owner and file permissions - # param string filename of db dump - # param string restored database file - function restorePermissions(){ - local sMyMeta="${1}.meta" - local sTargetfile="$2" - if [ -f "${sMyMeta}" ]; then - - # Access: (0674/-rw-rwxr--) Uid: ( 1000/ axel) Gid: ( 1000/ axel) - # ^ ^ ^ - # _sPerm _sUser _sGroup - local _sPerm=$( grep "^Access: (" "${sMyMeta}" | cut -f 2 -d '(' | cut -f 1 -d '/') - if [ -n "$_sPerm" ]; then - local _sUser=$( grep "^Access: (" "${sMyMeta}" | cut -f 3 -d '/' | cut -f 1 -d ')' | tr -d ' ') - local _sGroup=$( grep "^Access: (" "${sMyMeta}" | cut -f 4 -d '/' | cut -f 1 -d ')' | tr -d ' ') - - echo -n "Restoring file owner $_sUser:$_sGroup and permissions $_sPerm ... " - chown "$_sUser:$_sGroup" "${sTargetfile}" && chmod "$_sPerm" "${sTargetfile}" + # ------------------------------------------------------------ + # compress a file + # shared function in localdump_* + # param string filename of uncompressed output file + function compress_file(){ + echo -n compressing $1 ... + gzip -9 -f "${1}" fetchrc - fi - fi - } - - # ------------------------------------------------------------ - # show help - # ------------------------------------------------------------ - function showhelp(){ - local _self - _self=$( basename "$0" ) - echo - echo "LOCALDUMP detects existing local databases and dumps them locally." - echo "It is included in the backup.sh to dump all before a file backup will store them." - echo - echo "It can be started seperately for manual database backups or for restore" - echo - echo "SYNTAX:" - echo " $_self [OPTIONS] <operation> <profile [more_profiles]>" - echo - echo "OPTIONS:" - echo " -h|--help show this help" - echo - echo "PARAMETERS:" - echo " operation - one of check|backup|restore; optional parameter" - echo " backup dump all databases/ schemes of a given service" - echo " check show info only if the service is available" - echo " restore import a dump into same or new database" - echo " Without a filename it starts an interactive mode" - echo " profile - name of database profiles" - echo " You get a list of all available services without parameter" - echo " Use ALL for bulk command" - echo " file - filename of db dump to restore to origin database scheme" - echo - echo "EXAMPLES:" - echo " $_self backup" - echo " $_self backup ALL" - echo " Backup all databases of all found services" - echo - echo " $_self backup mysql" - echo " Backup all Mysql databases." - echo - echo " $_self restore" - echo " Start interactive restore of a database of any service." - echo - echo " $_self restore sqlite" - echo " Start interactive restore of an sqlite database." - echo - echo " $_self restore <file-to-restore> [<database-name>]" - echo " Restore a given dump file to the origin database scheme or" - echo " to a new/ other database with the given name." - } + } - -# ---------------------------------------------------------------------- -# INIT -# ---------------------------------------------------------------------- - - while [[ "$#" -gt 0 ]]; do case $1 in - -h|--help) showhelp; exit 0;; - *) if grep "^-" <<< "$1" >/dev/null ; then - echo; echo "ERROR: Unknown parameter: $1"; echo; showhelp; exit 2 + # ------------------------------------------------------------ + # create a backup directory with name of service + # shared function in localdump_* + function create_targetdir(){ + mkdir -p "${BACKUP_TARGETDIR}" 2>/dev/null + if [ ! -d "${BACKUP_TARGETDIR}" ]; then + color.echo "error" "FATAL ERROR: directory ${BACKUP_TARGETDIR} was not created" + exit 1 fi - break; - ;; - esac; done - - mode="" - case "$1" in - backup|check|restore|shell) - mode=$1 - shift 1 - ;; - esac - - if [ -z "$mode" ]; then - color.echo error "ERROR: missing parameter for operation." - echo - showhelp - echo - echo "Hint: On this machine working profiles:" - get_database_profiles | nl - echo - exit 1 - fi - - - # ----- init vars - BACKUP_BASEDIR=$(_j_getvar "${JOBFILE}" "dir-localdumps") - - # check - if [ -z "$BACKUP_BASEDIR" ]; then - color.echo error "ERROR: missing config for backup target." - echo There must be an entry dir-localdumps in ${JOBFILE} - exit 1 - fi - ARCHIVE_BASEDIR=$(_j_getvar "${JOBFILE}" dir-dbarchive) - - BACKUP_PLUGINDIR=$(dirname $0)/plugins/localdump - DBD_BASEDIR=$BACKUP_PLUGINDIR/profiles - - BACKUP_KEEP_DAYS=$(_j_getvar ${JOBFILE} "keep-days") - - if [ $BACKUP_KEEP_DAYS -eq 0 ]; then - BACKUP_KEEP_DAYS=7 - fi - BACKUP_DATE=$(/bin/date +%Y%m%d-%H%M) - - # ----- checks - - # . /usr/local/bin/inc_cronfunctions.sh - j_requireUser "root" - - h1 $(date) IML BACKUP :: LOCALDUMP :: $* + } - export SERVICENAME=$1 - # BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${SERVICENAME} - # BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) + # ------------------------------------------------------------ + # generate a base filename for backup dump based on on db name + # ... and added timestamp + # param string name of database schema + # --> see listBackupedDBs() and guessDB() - these function must be able to split this + function get_outfile(){ + echo $*__${BACKUP_DATE} + } - case "$mode" in # ------------------------------------------------------------ - check) - DBD_DEBUG=1 - for PROFILENAME in $(dbdetect.getConfigs) - do - echo "----- $PROFILENAME" - dbdetect.exists "${PROFILENAME}" - echo - done - # . $BACKUP_SCRIPT $mode - ;; + # get name of a service script + # param string name of a service + function get_service_script(){ + local _service=$1 + local _type; _type=$( dbdetect.getType "$_service" ) + ls -1 ${BACKUP_PLUGINDIR}/${_type}.sh 2>/dev/null + } + # ------------------------------------------------------------ - backup) - if [ "$1" = "ALL" ] || [ -z "$1" ]; then + # get a list of existing database profiles + function get_database_profiles(){ + for config in $(dbdetect.getConfigs); do + if dbdetect.exists "$config"; then + echo "$( dbdetect.getProfile $config )" + fi + done + } - profiles2run=$(get_database_profiles) - echo AUTO: calling local backup scripts for all active profiles - echo "$profiles2run" | nl + # ------------------------------------------------------------ + # show directory infos with count of files and used space + # show used space and count of files and dirs + function show_info_backup_target(){ + if [ -d "${BACKUP_TARGETDIR}" ]; then + h3 "INFO about backup target ${BACKUP_TARGETDIR}" + + echo -n "used space: " + du -hs "${BACKUP_TARGETDIR}" + + echo -n "subdirs : " + find "${BACKUP_TARGETDIR}" -type d | wc -l + + echo -n "files : " + find "${BACKUP_TARGETDIR}" -type f | wc -l + + echo -n "free space: " + df -h "${BACKUP_TARGETDIR}" | tail -1 | awk '{ print $4 }' echo - else - profiles2run=$* fi + } - # ----- GO - # PROFILENAME mysql_localhost_13306 - # SERVICENAME mysql - # - for PROFILENAME in $profiles2run - do - - - if dbdetect.setProfile "${PROFILENAME}"; then - h2 "START PROFILE [${PROFILENAME}]" - - SERVICENAME=$( dbdetect.getType "$PROFILENAME" ) - BACKUP_PARAMS=$( dbdetect.getParams ) - - BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${PROFILENAME} - ARCHIVE_DIR=${ARCHIVE_BASEDIR}/${PROFILENAME} - BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) - - - # ------ set env - # echo "BACKUP_PARAMS = $BACKUP_PARAMS" - # dbdetect.setenv - eval $( dbdetect.setenv ) - - _j_runHooks "200-before-db-service" - - h3 "BACKUP [${PROFILENAME}] -> ${SERVICENAME}" - . $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" - - # ------ unset env - eval $( dbdetect.unssetenv ) - - # ----- post jobs: cleanup - cleanup_backup_target - show_info_backup_target +# ---------------------------------------------------------------------- +# FUNCTIONS 4 RESTORE +# ---------------------------------------------------------------------- + # ------------------------------------------------------------ + # restore: show profiles from that exist backups + # global string BACKUP_BASEDIR base directory of all backups + function listBackupedServices(){ + ( + find "${BACKUP_BASEDIR}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; + test -n "${ARCHIVE_BASEDIR}" && find "${ARCHIVE_BASEDIR}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; + ) | sort -u + } + # ------------------------------------------------------------ + # restore: show databases or dumps of a given database that can be restored + # global string BACKUP_BASEDIR base directory of all backups of selected dbprofile + # param string optional: DB-Name for file filter to select from existing dumps; + function listBackupedDBs(){ + if [ -d "${BACKUP_TARGETDIR}" ]; then + if [ -z $1 ]; then + # list all databases + find "${BACKUP_TARGETDIR}" -mindepth 1 -maxdepth 1 -type f -exec basename {} \; \ + | sed "s#__[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9].*##g" \ + | sed "s#\..*##g" \ + | sort -ud| sed "s#^\./##g" + else + # list dumps of a database + ls -ltr ${BACKUP_TARGETDIR}/${1}*gz | sed "s,${BACKUP_TARGETDIR}/,,g" + fi else - - echo "SKIP: profile '$PROFILENAME' " - - # see why it is not active - DBD_DEBUG=1; dbdetect.setProfile "${PROFILENAME}"; echo; DBD_DEBUG=0 - + color.echo error "ERROR: ${BACKUP_TARGETDIR} does not exist - here are no backups to restore." + echo + echo "You can try to restore dumps:" + echo "1) Restore dump files from a backup set" + echo " $(dirname $0)/restore.sh $BACKUP_BASEDIR" + echo "2) Copy restored dumps into $BACKUP_TARGETDIR" + echo "3) Start database restore again" + echo " $(dirname $0)/localdump.sh restore [profile]" + echo + exit 1 fi + } - # just to have it in the output - dbdetect.validate - - done - ;; # ------------------------------------------------------------ - restore) - - h1 "RESTORE DATABASE" - - if ! listBackupedServices | grep -q . ; then - color.echo error "ERROR: No database dump was found in [${BACKUP_BASEDIR}] nor [${ARCHIVE_BASEDIR}]." - exit 1 - fi - - if [ -z $1 ] || [ ! -f "$1" ]; then - - parService="$1" - - # ----- interactive selections - - h2 "Select profile that has a dump" - - if [ -z "${parService}" ]; then - if [ "$( listBackupedServices | wc -l )" -eq 1 ]; then - parService="$( listBackupedServices )" - echo "Selecting the only existing profile: $parService" - fi - fi - - if [ -z "${parService}" ]; then - listBackupedServices - color.print input "Restore for profile name >" - read -r parService - test -z "$parService" && exit 1 + # guess name of the database file + # param string filename of db dump; can be full path or not + function guessDB(){ + dumpfile=$1 + + # the metafile is written in sqlite backup to store full path + metafile=${BACKUP_TARGETDIR}/${dumpfile}.meta + if [ -f $metafile ]; then + grep "^/" "$metafile" || grep "^ File: " "$metafile" | cut -c 9- else - echo "Taken from command line: $parService" + 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" | sed "s#\.couchdbdump\.gz$##g" ) + # ^ ^ + # timestamp in backup file __/ for couchdb2 restore from archive __/ + if [ -z $sDb ]; then + color.echo error "ERROR: db name was not detected from file $1" + exit 1 + fi + echo $sDb fi + } - # ----- check if profile exists - if ! dbdetect.setProfile "${parService}"; then - color.echo error "ERROR: profile [${parService}] is not known here (or database service is stopped)." - echo - echo "Existing database profiles:" - get_database_profiles | nl - echo + # ------------------------------------------------------------ + # show a selection + a prompt and read the input + # - If the selection is just 1 line it will be returned + # - If the user presses just return the script will exit + # param string selection of items to select from + # param string prompt to show + function showSelectAndInput(){ + local _selection="$1" + local _prompt="$2" + + local _lines + typeset -i _lines; _lines=$( grep -c "." <<< "$_selection" ) + + if [ $_lines -eq "1" ]; then + echo "INFO: No interaction on a single choice. Using '$_selection'" + LASTINPUT="$_selection" + return 0 + else + + echo "$_selection" + color.print input "${_prompt} >" + read -r LASTINPUT + if [ -z "$LASTINPUT" ]; then + echo "No input given. Aborting." exit 1 fi + fi + } - # ----- check if dump exists in archive and in backup - if [ -d "${BACKUP_BASEDIR}/${parService}" ] && [ -d "${ARCHIVE_BASEDIR}/${parService}" ]; then - echo - echo "Backup ${BACKUP_BASEDIR}" - echo "Archive ${ARCHIVE_BASEDIR}" - color.print input "Select a source directory >" - read -r backupsource - test -z "$backupsource" && exit 1 - BACKUP_BASEDIR="${backupsource}" - else - # just one test needed because BACKUP_BASEDIR is BACKUP_BASEDIR - test -d "${ARCHIVE_BASEDIR}/${parService}" && BACKUP_BASEDIR="${ARCHIVE_BASEDIR}" + # ------------------------------------------------------------ + # read .meta file (that contains output of stats) and restore last owner and file permissions + # param string filename of db dump + # param string restored database file + function restorePermissions(){ + local sMyMeta="${1}.meta" + local sTargetfile="$2" + if [ -f "${sMyMeta}" ]; then + + # Access: (0674/-rw-rwxr--) Uid: ( 1000/ axel) Gid: ( 1000/ axel) + # ^ ^ ^ + # _sPerm _sUser _sGroup + local _sPerm=$( grep "^Access: (" "${sMyMeta}" | cut -f 2 -d '(' | cut -f 1 -d '/') + if [ -n "$_sPerm" ]; then + + local _sUser=$( grep "^Access: (" "${sMyMeta}" | cut -f 3 -d '(' | cut -f 1 -d '/' | tr -d ' ') + local _sGroup=$( grep "^Access: (" "${sMyMeta}" | cut -f 4 -d '(' | cut -f 1 -d '/' | tr -d ' ') + + echo -n "Restoring file owner $_sUser:$_sGroup and permissions $_sPerm ... " + chown "$_sUser:$_sGroup" "${sTargetfile}" && chmod "$_sPerm" "${sTargetfile}" + fetchrc + fi fi + } - # ----- check if target dir with profile exists - if [ ! -d "${BACKUP_BASEDIR}/${parService}" ]; then - color.echo error "ERROR: Directory does not exist '${BACKUP_BASEDIR}/${parService}'." - exit 1 - fi + # ------------------------------------------------------------ + # show help + # ------------------------------------------------------------ + function showhelp(){ + local _self + _self=$( basename "$0" ) + cat <<EOH + +LOCALDUMP detects existing local databases and dumps them locally. +It is included in the backup.sh to dump all before a file backup will store +them. It can be started seperately for manual database backups or for restore. + +SYNTAX: + $_self [OPTIONS] <operation> <profile [more_profiles]> + +OPTIONS: + -h|--help show this help + +PARAMETERS:" + operation - one of check|backup|restore; optional parameter + backup dump all databases/ schemes of a given service + check show info only if the service is available + restore import a dump into same or new database + Without a filename it starts an interactive mode + profile - name of database profiles + You get a list of all available services without parameter + Use ALL for bulk command + file - filename of db dump to restore to origin database scheme + +EXAMPLES: + $_self backup + $_self backup ALL + Backup all databases of all found services + $_self backup mysql + Backup all Mysql databases. + + $_self restore + Start interactive restore of a database of any service. + $_self restore sqlite + Start interactive restore of an sqlite database. + $_self restore <file-to-restore> [<database-name>] + Restore a given dump file to the origin database scheme or + to a new/ other database with the given name. + +EOH + } - BACKUP_TARGETDIR="${BACKUP_BASEDIR}/${parService}" - h2 "Select database" - listBackupedDBs - color.print input "name of db to restore >" - read -r fileprefix - test -z "$fileprefix" && exit 1 - echo +# ---------------------------------------------------------------------- +# INIT +# ---------------------------------------------------------------------- - h2 "Select a specific dump for that database" - listBackupedDBs $fileprefix - color.print input "backupset to import >" - read -r dbfile - test -z "$dbfile" && exit 1 + while [[ "$#" -gt 0 ]]; do case $1 in + -h|--help) showhelp; exit 0;; + *) if grep "^-" <<< "$1" >/dev/null ; then + echo; echo "ERROR: Unknown parameter: $1"; echo; showhelp; exit 2 + fi + break; + ;; + esac; done + + mode="" + case "$1" in + backup|check|restore|shell) + mode=$1 + shift 1 + ;; + esac + + if [ -z "$mode" ]; then + color.echo error "ERROR: missing parameter for operation." echo - - sTargetDb=$(guessDB ${dbfile}) - color.print input "New database name [$sTargetDb] >" - read -r sTargetDb - if [ -z $sTargetDb ]; then - sTargetDb=$(guessDB ${dbfile}) - fi + showhelp + echo + echo "Hint: On this machine working profiles:" + get_database_profiles | nl echo + exit 1 + fi - sDumpfile="${BACKUP_TARGETDIR}/${dbfile}" - else - sDumpfile=$1 - sTargetDb=$2 - fi - shift 2 + # ----- init vars + BACKUP_BASEDIR=$(_j_getvar "${JOBFILE}" "dir-localdumps") - # ----- start restore + # check + if [ -z "$BACKUP_BASEDIR" ]; then + color.echo error "ERROR: missing config for backup target." + echo There must be an entry dir-localdumps in ${JOBFILE} + exit 1 + fi + ARCHIVE_BASEDIR=$(_j_getvar "${JOBFILE}" dir-dbarchive) - if [ ! -f "${sDumpfile}" ]; then - color.echo error "ERROR: [${sDumpfile}] is not a file" - rc=$rc+1 - else + BACKUP_PLUGINDIR=$(dirname $0)/plugins/localdump + DBD_BASEDIR=$BACKUP_PLUGINDIR/profiles - PROFILENAME="${sDumpfile//${BACKUP_BASEDIR}/}" - PROFILENAME="$( echo $PROFILENAME | sed "s,^/*,," | cut -f 1 -d '/')" + BACKUP_KEEP_DAYS=$(_j_getvar ${JOBFILE} "keep-days") - if dbdetect.setProfile "${PROFILENAME}"; then + if [ $BACKUP_KEEP_DAYS -eq 0 ]; then + BACKUP_KEEP_DAYS=7 + fi + BACKUP_DATE=$(/bin/date +%Y%m%d-%H%M) + + # ----- checks - SERVICENAME=$( dbdetect.getType "$PROFILENAME" ) + # . /usr/local/bin/inc_cronfunctions.sh + j_requireUser "root" - BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${PROFILENAME} - BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) - ARCHIVE_DIR=${ARCHIVE_BASEDIR}/${PROFILENAME} + h1 $(date) IML BACKUP :: LOCALDUMP :: $* - BACKUP_PARAMS=$( dbdetect.getParams ) - eval $( dbdetect.setenv ) - . $BACKUP_SCRIPT $mode "${sDumpfile}" "${sTargetDb}" - if [ $? -ne 0 -o $rc -ne 0 ]; then - color.echo error "ERROR: $mode failed. See ouput above. :-/" + export SERVICENAME=$1 + # BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${SERVICENAME} + # BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) + + case "$mode" in + # ------------------------------------------------------------ + check) + DBD_DEBUG=1 + for PROFILENAME in $(dbdetect.getConfigs) + do + echo "----- $PROFILENAME" + dbdetect.exists "${PROFILENAME}" + echo + done + # . $BACKUP_SCRIPT $mode + ;; + # ------------------------------------------------------------ + backup) + if [ "$1" = "ALL" ] || [ -z "$1" ]; then + + profiles2run=$(get_database_profiles) + echo AUTO: calling local backup scripts for all active profiles + echo "$profiles2run" | nl + echo + else + profiles2run=$* + fi + + # ----- GO + # PROFILENAME mysql_localhost_13306 + # SERVICENAME mysql + # + for PROFILENAME in $profiles2run + do + + + if dbdetect.setProfile "${PROFILENAME}"; then + h2 "START PROFILE [${PROFILENAME}]" + + SERVICENAME=$( dbdetect.getType "$PROFILENAME" ) + BACKUP_PARAMS=$( dbdetect.getParams ) + + BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${PROFILENAME} + ARCHIVE_DIR=${ARCHIVE_BASEDIR}/${PROFILENAME} + BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) + + + # ------ set env + # echo "BACKUP_PARAMS = $BACKUP_PARAMS" + # dbdetect.setenv + eval $( dbdetect.setenv ) + + _j_runHooks "200-before-db-service" + + h3 "BACKUP [${PROFILENAME}] -> ${SERVICENAME}" + . $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" + + # ------ unset env + eval $( dbdetect.unssetenv ) + + # ----- post jobs: cleanup + cleanup_backup_target + show_info_backup_target + else - color.echo ok "OK, $mode was successful." + + echo "SKIP: profile '$PROFILENAME' " + + # see why it is not active + DBD_DEBUG=1; dbdetect.setProfile "${PROFILENAME}"; echo; DBD_DEBUG=0 + fi + + # just to have it in the output + dbdetect.validate + + done + ;; + + # ------------------------------------------------------------ + restore) + + h1 "RESTORE DATABASE" + + if ! listBackupedServices | grep -q . ; then + color.echo error "ERROR: No database dump was found in [${BACKUP_BASEDIR}] nor [${ARCHIVE_BASEDIR}]." + exit 1 + fi + + if [ -z $1 ] || [ ! -f "$1" ]; then + + parService="$1" + + # ----- interactive selections + + h2 "Select profile that has a dump" + + if [ -z "${parService}" ]; then + showSelectAndInput "$( listBackupedServices )" "Restore for profile name" + parService="$LASTINPUT" + else + echo "Taken from command line: $parService" + fi + + # ----- check if profile exists + if ! dbdetect.setProfile "${parService}"; then + color.echo error "ERROR: profile [${parService}] is not known here (or database service is stopped)." + echo + exit 1 + fi + + # ----- check if dump exists in archive and in backup + if [ -d "${BACKUP_BASEDIR}/${parService}" ] && [ -d "${ARCHIVE_BASEDIR}/${parService}" ]; then + echo + showSelectAndInput "$(echo "${BACKUP_BASEDIR}"; echo "${ARCHIVE_BASEDIR}")" "Select a source directory" + BACKUP_BASEDIR="$LASTINPUT" + else + # just one test needed because BACKUP_BASEDIR is BACKUP_BASEDIR + test -d "${ARCHIVE_BASEDIR}/${parService}" && BACKUP_BASEDIR="${ARCHIVE_BASEDIR}" + fi + + # ----- check if target dir with profile exists + if [ ! -d "${BACKUP_BASEDIR}/${parService}" ]; then + color.echo error "ERROR: Directory does not exist '${BACKUP_BASEDIR}/${parService}'." + exit 1 + fi + + BACKUP_TARGETDIR="${BACKUP_BASEDIR}/${parService}" + + h2 "Select a database schema" + showSelectAndInput "$(listBackupedDBs)" "Name of database to restore" + fileprefix="$LASTINPUT" + echo + + h2 "Select a specific dump for that database" + showSelectAndInput "$(listBackupedDBs $fileprefix)" "Backupset to import" + dbfile="$LASTINPUT" + echo + + sTargetDb=$(guessDB ${dbfile}) + color.print input "New database name [$sTargetDb] >" + read -r sTargetDb + if [ -z $sTargetDb ]; then + sTargetDb=$(guessDB ${dbfile}) + fi + echo + + sDumpfile="${BACKUP_TARGETDIR}/${dbfile}" + else + sDumpfile=$1 + sTargetDb=$2 + fi + shift 2 + + # ----- start restore + + if [ ! -f "${sDumpfile}" ]; then + color.echo error "ERROR: [${sDumpfile}] is not a file" + rc=$rc+1 + else + + PROFILENAME="${sDumpfile//${BACKUP_BASEDIR}/}" + PROFILENAME="$( echo $PROFILENAME | sed "s,^/*,," | cut -f 1 -d '/')" + + if dbdetect.setProfile "${PROFILENAME}"; then + + SERVICENAME=$( dbdetect.getType "$PROFILENAME" ) - # ------ unset env - eval $( dbdetect.unssetenv ) - else - color.echo error "ERROR: Profile $PROFILENAME was detected but its database service is not available." - fi - - fi - ;; - # ------------------------------------------------------------ - # shell) - - # export BACKUP_TARGETDIR - # . $BACKUP_SCRIPT - # ( - # mycmd= - # echo - # echo "Starting interactive shell..." - # echo - # echo "STATUS: STILL ALPHA as long existing db plugins are not rewritten." - # echo - # echo "INFO: Try ${SERVICENAME}.help to see database specific commands." - # echo "INFO: Type exit and return to leave the shell." - # echo - # while [ ! "$mycmd" = "exit" ]; do - # echo -n "[${SERVICENAME}]" - # color.print input " $( pwd )" - # echo -n " % " - # read -r mycmd - # if [ ! "$mycmd" = "exit" ];then - # color.preset cmd - # eval $mycmd - # color.reset - # fi - # done - # ) - # ;; + BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${PROFILENAME} + BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} ) + ARCHIVE_DIR=${ARCHIVE_BASEDIR}/${PROFILENAME} + + BACKUP_PARAMS=$( dbdetect.getParams ) + eval $( dbdetect.setenv ) + . $BACKUP_SCRIPT $mode "${sDumpfile}" "${sTargetDb}" + + if [ $? -ne 0 -o $rc -ne 0 ]; then + color.echo error "ERROR: $mode failed. See ouput above. :-/" + else + color.echo ok "OK, $mode was successful." + fi + + # ------ unset env + eval $( dbdetect.unssetenv ) + else + color.echo error "ERROR: Profile $PROFILENAME was detected but its database service is not available." + fi + + fi + ;; + # ------------------------------------------------------------ + # shell) + + # export BACKUP_TARGETDIR + # . $BACKUP_SCRIPT + # ( + # mycmd= + # echo + # echo "Starting interactive shell..." + # echo + # echo "STATUS: STILL ALPHA as long existing db plugins are not rewritten." + # echo + # echo "INFO: Try ${SERVICENAME}.help to see database specific commands." + # echo "INFO: Type exit and return to leave the shell." + # echo + # while [ ! "$mycmd" = "exit" ]; do + # echo -n "[${SERVICENAME}]" + # color.print input " $( pwd )" + # echo -n " % " + # read -r mycmd + # if [ ! "$mycmd" = "exit" ];then + # color.preset cmd + # eval $mycmd + # color.reset + # fi + # done + # ) + # ;; + + # ----- start restore + *) + color.echo error "ERROR: unknown command [$mode]" + ;; + esac + + echo _______________________________________________________________________________ + echo STATUS $0 exit with final returncode rc=$rc + exit $rc - # ----- start restore - *) - color.echo error "ERROR: unknown command [$mode]" - ;; - esac - - echo _______________________________________________________________________________ - echo STATUS $0 exit with final returncode rc=$rc - exit $rc - # ---------------------------------------------------------------------- + \ No newline at end of file -- GitLab