527 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			527 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/bin/bash
 | |
| #
 | |
| # Restores a MySQL/MariaDB database from a native or dockerized RDBMS instance
 | |
| # accessible from this box. Also creates an owner user for this database
 | |
| # (if it doesn't exist) and grants the appropriate privileges.
 | |
| #
 | |
| # Needs MySQL v5.7.6 or MariaDB 10.1.3 (or later).
 | |
| # To restore a database with the necessary user management and grants,
 | |
| # needs the superuser privileges on RDBMS.
 | |
| # * If the RDBMS runs dockerized you need call this script as a Docker manager
 | |
| #  user (member of the docker Linux group).
 | |
| # * If we're using a native MySQL/MariaDB, you need call this script as a
 | |
| #   Linux user whom the superuser role has been already granted within RDBMS
 | |
| #   (via unix_socket authentication) or you need provide the superuser's
 | |
| #   credentials as well.
 | |
| # Lack of this the script will skip the user management and grant steps.
 | |
| #
 | |
| # Usage:
 | |
| # $0 [-U dbuser] [-P dbpass] [-h dbhost] [-p dbport]
 | |
| #    [-A dbadminuser] [-a dbadminpass] [-c characterset]
 | |
| #    [-C container] [-d database] [-f dumpfile ]
 | |
| #    [database (if not in -d)] [dumpfile (if not in -f)]
 | |
| #
 | |
| # Author: Kovács Zoltán <kovacs.zoltan@smartfront.hu>
 | |
| #         Kovács Zoltán <kovacsz@marcusconsulting.hu>
 | |
| # License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html)
 | |
| # 2023-06-18 v1.0
 | |
| # new: forked from the "SMARTERP_skeleton" repository.
 | |
| # 2022-04-07 v0.4
 | |
| # new: An option and a guess mechanism has added to set the default character set
 | |
| #      of the restored database.
 | |
| # 2021-08-30 v0.3
 | |
| # fix: Uses the defaults when MYDBA* variables aren't set.
 | |
| # 2021-03-22 v0.2
 | |
| # fix: A duplicated SQLVERB has blocked setting password for a newly created user.
 | |
| #      The unnecessary PASSWORD() call has been removed as well.
 | |
| # fix: Typos.
 | |
| # 2021-02-18 v0.1 Initial release
 | |
| 
 | |
| # Accepted environment variables and their defaults.
 | |
| #
 | |
| MYCONTAINER=${MYCONTAINER-""}                   # Docker container's name
 | |
| MYCHARSET=${MYCHARSET-""}                       # Default character set for DB
 | |
| MYDATABASE=${MYDATABASE-""}                     # Database name to restore
 | |
| MYDBAUSER=${MYDBAUSER:-""}			# Database admin superuser
 | |
| MYDBAPASSWORD=${MYDBAPASSWORD:-""}		# Credential for the DBA user
 | |
| MYDUMP=${MYDUMP-""}                             # Dump file pathname
 | |
| MYHOST=${MYHOST:-"localhost"}                   # Connection parameter
 | |
| MYOPTIONS=${MYOPTIONS-""}                       # Options to pass to pg_dump
 | |
| MYPASSWORD=${MYPASSWORD-""}                     # Credential for the DB owner
 | |
| MYPORT=${MYPORT:-"3306"}                        # Connection parameter
 | |
| MYUSER=${MYUSER:-"root"}			# Owner of the restored DB
 | |
| 
 | |
| ### Temporailly ignored! Need to sanitize.
 | |
| MYOPTIONS=""
 | |
| 
 | |
| # Basic environment settings.
 | |
| #
 | |
| LANG=C
 | |
| LC_ALL=C
 | |
| # We need also the sbin directories.
 | |
| if ! [[ "$PATH" =~ '/sbin:' ]]; then
 | |
| PATH="$PATH:/usr/local/sbin:/usr/sbin:/sbin"; fi
 | |
| 
 | |
| # Other initialisations.
 | |
| #
 | |
| LOGSTAMP="\"\$DATE\" +%Y-%m-%d\ %H:%M:%S"	# Timestamp format for logs
 | |
| MARIADBMIN="010001003"				# MariaDB minimum version
 | |
| MYSQLMIN="005007006"				# MySQL minimum version
 | |
| vetodatabases="information_schema mysql performance_schema sys"
 | |
| 
 | |
| # Messages.
 | |
| #
 | |
| MSG_AMBIGOUS="The character set used within the dump is ambigous."
 | |
| MSG_BADDBTYPE="Unknown database type"
 | |
| MSG_BADDUMP="Doesn't exist or doesn't a dumpfile:"
 | |
| MSG_BADOPT="Invalid option"
 | |
| MSG_BADPARAM="Doubtful parameter:"
 | |
| MSG_BLOCKING="This is a fatal error - restore has been aborted."
 | |
| MSG_CONNTERM="DB connection(s) have forced to terminate"
 | |
| MSG_DOCKERGRPNEED="You must be a member of the docker group."
 | |
| MSG_DOESNOTRUN="Doesn't run the database container"
 | |
| MSG_EXISTING="did not create exisiting object"
 | |
| MSG_FAILCONN="Failed to connect the RDBMS."
 | |
| MSG_FAILGRANT="Failet to grant privileges to user"
 | |
| MSG_FAILKILL="Failed to kill active connection"
 | |
| MSG_FAILPASS="Failed to set password to user"
 | |
| MSG_FAILTOKILL="Failed to retrieve the active connections."
 | |
| MSG_FAILVER="Failed to get the RDBMS version."
 | |
| MSG_FAILUSER="Failed to create RDBMS user"
 | |
| MSG_MISSINGDEP="Fatal: missing dependency"
 | |
| MSG_NONBLOCKING="Recoverable error - restore is continuing."
 | |
| MSG_NONSUPER="DB user hasn't DBA (database superuser) privileges."
 | |
| MSG_NONZERO="The result code is non zero"
 | |
| MSG_OLDRDBMS="RDBMS version is too old"
 | |
| MSG_PERCENT="Hint: you may use percent-encoding (e.g %40 instead of @)"
 | |
| MSG_SUPERNEED="user must have DBA (database superuser) privileges."
 | |
| 
 | |
| MSG_USAGE="Usage: $0 [options] [database [dump_pathname]]\n"
 | |
| MSG_USAGE+="Option:\tENVVAR:\n"
 | |
| MSG_USAGE+=" -A\tMYDBAUSER     \tMySQL/MariaDB DB admin superuser\n"
 | |
| MSG_USAGE+=" -a\tMYDBAPASSWORD \tMySQL/MariaDB DB admin password\n"
 | |
| MSG_USAGE+=" -c\tMYCHARSET     \tMySQL/MariaDB DB character set\n"
 | |
| MSG_USAGE+=" -C\tMYCONTAINER   \tMySQL/MariaDB Docker container's name\n"
 | |
| MSG_USAGE+=" -d\tMYDATABASE    \tMySQL/MariaDB database to restore\n"
 | |
| MSG_USAGE+=" -f\tMYDUMPFILE    \tDumpfile pathname\n"
 | |
| MSG_USAGE+=" -h\tMYHOST        \tHostname or IP to connect (localhost)\n"
 | |
| MSG_USAGE+=" -p\tMYPORT        \tTCP port to connect (3306)\n"
 | |
| MSG_USAGE+=" -P\tMYPASSWORD    \tMySQL/MariaDB password\n"
 | |
| MSG_USAGE+=" -U\tMYUSER        \tMySQL/MariaDB username ($MYUSER)\n"
 | |
| 
 | |
| # Getting options.
 | |
| #
 | |
| while getopts ":-:a:A:c:C:d:D:f:h:H:p:P:u:U:" option
 | |
| do
 | |
|     case ${option} in
 | |
|         "-" )
 | |
|             if   [ "$OPTARG" = "help" ]; then echo -e "$MSG_USAGE" >&2; exit
 | |
|             else echo "$MSG_BADOPT --$OPTARG" >&2; exit 1
 | |
|             fi
 | |
|         ;;
 | |
|         "A" ) MYDBAUSER="$OPTARG" ;;
 | |
|         "a" ) MYDBAPASSWORD="$OPTARG" ;;
 | |
|         "c" ) MYCHARSET="$OPTARG" ;;
 | |
|         "C" ) MYCONTAINER="$OPTARG" ;;
 | |
|         "d" | "D" ) MYDATABASE="$OPTARG" ;;
 | |
|         "f" ) MYDUMPFILE="$OPTARG" ;;
 | |
|         "h" | "H" ) MYHOST="$OPTARG" ;;
 | |
|         "P" ) MYPASSWORD="$OPTARG" ;;
 | |
|         "p" ) MYPORT="$OPTARG" ;;
 | |
|         "u" | "U" ) MYUSER="$OPTARG" ;;
 | |
|         \? ) echo "$MSG_BADOPT -$OPTARG" >&2; exit 1
 | |
| 	;;
 | |
|     esac
 | |
| done; shift $((OPTIND -1))
 | |
| # All options have been processed.
 | |
| 
 | |
| # Checks the dependencies.
 | |
| #
 | |
| # Conditional dependencies (according to native or dockerized environment).
 | |
| [[ -z "$MYCONTAINER" ]] \
 | |
| && additem="mysql" \
 | |
| || additem="docker"
 | |
| # Common dependencies.
 | |
| TR=$(which tr 2>/dev/null)
 | |
| if [ -z "$TR" ]; then echo "$MSG_MISSINGDEP tr."; exit 1 ; fi
 | |
| for item in basename cat cut date dirname file grep gunzip head id locale \
 | |
|             readlink printf sed sort tail tee wc $additem
 | |
| do
 | |
|     if [ -n "$(which $item)" ]
 | |
|     then export $(echo $item | "$TR" '[:lower:]' '[:upper:]')=$(which $item)
 | |
|     else echo "$MSG_MISSINGDEP $item." >&2; exit 1; fi
 | |
| done
 | |
| # All dependencies are available via "$THECOMMAND" (upper case) call.
 | |
| 
 | |
| # Sanitizing the parameters.
 | |
| # Most of them are only arbitrary restrictions (reliable source: TODO!)
 | |
| #
 | |
| [[ -n "$MYDBAUSER" ]] && [[ ! "$MYDBAUSER" =~ ^([[:alnum:]]|[.-_\\+])*$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYDBAUSER\n$MSG_USAGE" >&2 && exit 1
 | |
| #
 | |
| [[ -n "$MYDBAPASSWORD" ]] && [[ ! "$MYDBAPASSWORD" =~ ^([[:alnum:]]|[ !~&#$<>()%+-_.])*$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYDBAPASSWORD\n$MSG_PERCENT\n$MSG_USAGE" >&2 && exit 1
 | |
| #
 | |
| [[ -n "$MYCONTAINER" ]] && [[ ! "$MYCONTAINER" =~ ^([[:alnum:]]|[-_])*$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYCONTAINER\n$MSG_USAGE" >&2 && exit 1
 | |
| #
 | |
| [[ -n "$MYDATABASE" ]] && [[ ! "$MYDATABASE" =~ ^([[:alnum:]]|[_])*$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYDATABASE\n$MSG_USAGE" >&2 && exit 1
 | |
| #
 | |
| [[ -n "$MYDUMPFILE" ]] && [[ ! "$MYDUMPFILE" =~ ^([[:alnum:]]|[ .-_/])*$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYDUMPFILE\n$MSG_USAGE" >&2 && exit 1
 | |
| # https://tools.ietf.org/html/rfc1123#page-13 (relaxed)
 | |
| [[ -z "$MYHOST" ]] && MYHOST="localhost"
 | |
| [[ -n "$MYHOST" ]] && [[ ! "$MYHOST" =~ ^([[:alnum:]]|[.-])*$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYHOST\n$MSG_USAGE" >&2 && exit 1
 | |
| # https://tools.ietf.org/html/rfc6056 (relaxed)
 | |
| [[ -z "$MYPORT" ]] && MYPORT=3306
 | |
| [[ -n "$MYPORT" ]] && [[ ! "$MYPORT" =~ ^[1-9]([[:digit:]]){0,4}$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYPORT\n$MSG_USAGE" >&2 && exit 1
 | |
| #
 | |
| [[ -n "$MYPASSWORD" ]] && [[ ! "$MYPASSWORD" =~ ^([[:alnum:]]|[ !~&#$<>()%+-_.])*$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYPASSWORD\n$MSG_PERCENT\n$MSG_USAGE" >&2 && exit 1
 | |
| #
 | |
| [[ -n "$MYUSER" ]] && [[ ! "$MYUSER" =~ ^([[:alnum:]]|[.-_\\+])*$ ]] \
 | |
| && echo -e "$MSG_BADPARAM $MYUSER\n$MSG_USAGE" >&2 && exit 1
 | |
| # We've at least a minimally checked parameters.
 | |
| 
 | |
| # Need to be root or a Docker manager user if the DB runs in a container.
 | |
| #
 | |
| [[ -n "$MYCONTAINER" ]] && [[ "$USER" != 'root' ]] \
 | |
| && [[ -z "$(echo "$("$ID" -Gn "$USER") " | "$GREP" ' docker ')" ]] \
 | |
| && echo "$MSG_DOCKERGRPNEED" >&2 && exit 1 #"
 | |
| 
 | |
| # If the RDBMS is dockerized the container must be running.
 | |
| #
 | |
| [[ -n "$MYCONTAINER" ]] \
 | |
| && [[ -z "$("$DOCKER" ps -q -f name=$MYCONTAINER)" ]] \
 | |
| && echo "$MSG_DOESNOTRUN $MYCONTAINER" >&2 && exit 1
 | |
| 
 | |
| # Determines the database to restore.
 | |
| #
 | |
| # Lack of -d the 1st non-option parameter is the database's name.
 | |
| if [ -z "$MYDATABASE" -a -n "$1" ]; then MYDATABASE="$1"; shift; fi
 | |
| # The database's name is mandatory.
 | |
| if [ -z "$MYDATABASE" ]
 | |
| then echo -e "$MSG_USAGE" >&2; exit 1; fi
 | |
| # A humble sanitization.
 | |
| if [[ ! "$MYDATABASE" =~ ^([[:alnum:]]|[_])*$ ]]
 | |
| then echo -e "$MSG_USAGE" >&2; exit 1; fi
 | |
| # Silently refuses the MySQL/MariaDB internal databases.
 | |
| for veto in $vetodatabases ""
 | |
| do
 | |
|     [[ "$MYDATABASE" = "$veto" ]] && exit 0
 | |
| done
 | |
| # We've a database name to restore.
 | |
| 
 | |
| # Determines the dumpfile.
 | |
| #
 | |
| # Lack of -f the 2nd non-option parameter is the database's name.
 | |
| if [ -z "$MYDUMPFILE" -a -n "$1" ]; then MYDUMPFILE="$1"; shift; fi
 | |
| # The dumpfile is mandatory.
 | |
| if [ -z "$MYDUMPFILE" ]
 | |
| then echo -e "$MSG_USAGE" >&2; exit 1; fi
 | |
| # The MYDUMPFILE must point to a readable file.
 | |
| # If it is an existing symlink dereferences it to ensure, it points to a file.
 | |
| if [ -h "$MYDUMPFILE" ]; then
 | |
|     if [[ "$("$READLINK" "$MYDUMPFILE")" != /* ]]
 | |
|     # relative path in symlink
 | |
|     then MYDUMPFILE="$("$DIRNAME" "$MYDUMPFILE")/$("$READLINK" "$MYDUMPFILE")"
 | |
|     # absolute path in symlink
 | |
|     else MYDUMPFILE="$("$READLINK" "$MYDUMPFILE")"; fi
 | |
| fi
 | |
| # Let's check it!
 | |
| if [ ! -r "$MYDUMPFILE" -o  ! -f "$MYDUMPFILE" ]
 | |
| then echo -e "$MSG_BADDUMP $MYDUMPFILE"; exit 1; fi
 | |
| # We've an existing dumpfile.
 | |
| 
 | |
| # Tries to get the locale settings (actually CHARACTER SET) of this dump.
 | |
| #
 | |
| if [ -z "$MYCHARSET" ]; then
 | |
|     # Let's identify the file is gzipped or not.
 | |
|     UNPACKER=$("$FILE" --mime-type "$MYDUMPFILE")
 | |
|     UNPACKER=${UNPACKER##* }	# The last word is the MIME-type.
 | |
|     # We'll use gunzip or cat (a dummy unzipper), according to the MIME type.
 | |
|     [[ "$UNPACKER" = 'application/gzip' ]] \
 | |
|     && UNPACKER="$GUNZIP" \
 | |
|     || UNPACKER="$CAT"
 | |
|     # Collects all character set adjustments from the dumpfile.
 | |
|     MYCHARSET=$("$CAT" "$MYDUMPFILE" | "$UNPACKER" | "$GREP" -B2 -i 'CREATE TABLE' | \
 | |
|                 "$GREP" -i 'character_set_client =' | "$SORT" -u)
 | |
|     # Trims the character set's name itself (the first word after the equal sign).
 | |
|     [[ -n "$MYCHARSET" ]] && MYCHARSET=$(echo -e "$MYCHARSET" | "$SED" 's/^.*= \(.*\) .*$/\1/') #'
 | |
| fi
 | |
| # We've a raw guess about the character sets used.
 | |
| 
 | |
| # Finds the LOGFILE to use.
 | |
| #
 | |
| # If the folder containing the MYDUMPFILE is writable, we will use a
 | |
| # logfile with the same name as the dumpfile but with .log extension.
 | |
| [[ -w "$("$DIRNAME" "$MYDUMPFILE")" ]] \
 | |
| && LOGFILE="${MYDUMPFILE%.gz}" \
 | |
| && LOGFILE="${LOGFILE%.*}.log" \
 | |
| || LOGFILE="/dev/null"
 | |
| # We've a suitable logfile.
 | |
| 
 | |
| # Opens the log and takes care to close it when finish.
 | |
| #
 | |
| echo "$(eval $LOGSTAMP) Starting job #$$ $("$TR" '\0' ' ' < /proc/$$/cmdline)" | \
 | |
|      "$TEE" -a "$LOGFILE"
 | |
| # Sets a trap to make always a corresponding exit log entry as well.
 | |
| function close_log() {
 | |
|     echo -e "$(eval $LOGSTAMP) Finished job #$$ $("$TR" '\0' ' ' < /proc/$$/cmdline)\n" | \
 | |
| 	 "$TEE" -a "$LOGFILE"
 | |
| }
 | |
| trap -- 'close_log' EXIT
 | |
| # We started logging.
 | |
| 
 | |
| # Prepopulates the SQL command skeleton (macro).
 | |
| #
 | |
| # This skeleton makes the SQL calls independent to the environment
 | |
| # (native or dockerized) and credentials. We need only actualize the
 | |
| # CONNECT, DATABASE and SQLVERB clauses then eval $DO_SQLVERB.
 | |
| # Warning: the parameters must had been sanitized!
 | |
| DO_SQLVERB=""
 | |
| DO_SQLVERB+="export MYSQL_PWD; "
 | |
| DO_SQLVERB+="\"\$MYSQL\" \$CONNECT -N \$DATABASE "
 | |
| DO_SQLVERB+="-e \"\$SQLVERB\""
 | |
| # We've a suitable SQL macro.
 | |
| 
 | |
| # Do we connect the database as a DBA?
 | |
| #
 | |
| SQLVERB="SELECT 1;"
 | |
| # Sets the default DBA username for dockerized and native RDBMS as well.
 | |
| if [ -z "$MYDBAUSER" ]; then
 | |
|     [[ -n "$MYCONTAINER" ]] \
 | |
|     && MYDBAUSER="root" \
 | |
|     || MYDBAUSER="$USER"
 | |
| fi
 | |
| #
 | |
| # We'll try the local connection (Unix-domain socket) first.
 | |
| CONNECT=""
 | |
| DATABASE=""
 | |
| #result=$(eval "$DO_SQLVERB" 2>/dev/null); excode=$?
 | |
| result="${result//[[:space:]]/}"
 | |
| if [ "$result" != "1" ]; then
 | |
|     #
 | |
|     # On failure we'll try the TCP connection.
 | |
|     MYSQL_PWD="$MYDBAPASSWORD"
 | |
|     CONNECT="-u $MYDBAUSER -h $MYHOST -P $MYPORT"
 | |
|     result=$(eval "$DO_SQLVERB" 2>/dev/null); excode=$?
 | |
|     result="${result//[[:space:]]/}"
 | |
|     if [ "$result" != "1" ]; then
 | |
| 	#
 | |
| 	# On failure we'll try the TCP connection with non-DBA credentials.
 | |
| 	MYSQL_PWD="$MYPASSWORD"
 | |
| 	CONNECT="-u $MYUSER -h $MYHOST -P $MYPORT"
 | |
| 	result=$(eval "$DO_SQLVERB" 2>/dev/null); excode=$?
 | |
| 	result="${result//[[:space:]]/}"
 | |
| 	if [ "$result" != "1" ]; then
 | |
| 	    #
 | |
| 	    # On failure we'll give up here.
 | |
| 	    [[ "$result" != "1" ]] \
 | |
| 	    && echo -e "$MSG_FAILCONN" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 	    && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 	    && exit 1
 | |
| 	fi
 | |
|     fi
 | |
| fi
 | |
| # We've valid MYSQL_PWD and CONNECT clauses.
 | |
| 
 | |
| # Checks the superuser privilege.
 | |
| # Better check: TODO!
 | |
| ISDBA=false
 | |
| DATABASE=""
 | |
| SQLVERB="SHOW GRANTS;"
 | |
| result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
| result="${result//[[:space:]]/}"
 | |
| if [[ $excode -eq 0 && \
 | |
|      "$result" =~ ^GRANTALLPRIVILEGESON\*\.\*.*WITHGRANTOPTION$ ]]; then
 | |
|     ISDBA=true
 | |
| else
 | |
|     echo -e "$MSG_NONSUPER" | "$TEE" -a "$LOGFILE" >&2
 | |
|     echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
 | |
| fi
 | |
| # We know we're a DB superuser or not.
 | |
| 
 | |
| # Following steps need the superuser privileges.
 | |
| # Lack of this we'll skip them.
 | |
| if $ISDBA; then
 | |
|     DATABASE="mysql"
 | |
| 
 | |
|     # Checks the minimal MySQL/MariaDB version.
 | |
|     #
 | |
|     SQLVERB="SELECT version();"
 | |
|     result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
|     result="${result//[[:space:]]/}"
 | |
|     [[ $excode -ne 0 ]] \
 | |
|     && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
|     && exit 1
 | |
|     [[ -z "$result" ]] \
 | |
|     && echo -e "$MSG_FAILVER" | "$TEE" -a "$LOGFILE" >&2 \
 | |
|     && echo -e "$MSG_BLOCKING"  | "$TEE" -a "$LOGFILE" >&2 \
 | |
|     && exit 1
 | |
|     # Let's extract a comparable RDBMS version from the result.
 | |
|     dbversion=${result%%-*}		# strips anyone after 1st dash (including)
 | |
|     dbversion=(${dbversion//./ })	# converts to an array
 | |
|     dbversion=$("$PRINTF" '%03d%03d%03d' ${dbversion[@]}) # 3 times 3 digits 0-padded
 | |
|     if [ -n "$(echo "$result" | "$GREP" -i "mariadb")" ]; then
 | |
| 	# MariaDB version check.
 | |
|         (( dbversion < MARIADBMIN )) \
 | |
| 	&& echo -e "$MSG_OLDRDBMS: $result" | "$TEE" -a "$LOGFILE" >&2 \
 | |
|         && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 	&& exit 1
 | |
|     else
 | |
| 	# MySQL version check.
 | |
| 	(( dbversion < MYSQLMIN )) \
 | |
| 	&& echo -e "$MSG_OLDRDBMS: $result" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 	&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 	&& exit 1
 | |
|     fi
 | |
|     # RDBMS version is proper.
 | |
| 
 | |
|     # Creates the database user (owner) if it doesn't exist.
 | |
|     #
 | |
|     echo -e "CREATE USER" | "$TEE" -a "$LOGFILE"
 | |
|     SQLVERB=" CREATE USER '$MYUSER'@'$MYHOST'; "
 | |
|     result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
|     result="${result//[[:space:]]/}"
 | |
|     if [[ $excode -ne 0 ]]; then
 | |
| 	# Already exists (or something went wrong).
 | |
| 	echo -e "$MSG_FAILUSER $MYUSER@$MYHOST" | "$TEE" -a "$LOGFILE" >&2
 | |
| 	echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
 | |
|     else
 | |
| 	# Sets the password only if the user has just created.
 | |
| 	echo -e "SET PASSWORD" | "$TEE" -a "$LOGFILE"
 | |
| 	SQLVERB="SET PASSWORD FOR '$MYUSER'@'$MYHOST' = '$MYPASSWORD'; "
 | |
| 	result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
| 	result="${result//[[:space:]]/}"
 | |
| 	[[ $excode -ne 0 ]] \
 | |
| 	&& echo -e "$MSG_FAILPASS $MYUSER@$MYHOST" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 	&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 	&& exit 1
 | |
|     fi
 | |
|     #
 | |
|     # Grants all privileges on the database to the user.
 | |
|     #
 | |
|     echo -e "GRANT" | "$TEE" -a "$LOGFILE"
 | |
|     SQLVERB="GRANT ALL PRIVILEGES ON $MYDATABASE.* TO '$MYUSER'@'$MYHOST'; "
 | |
|     result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
|     result="${result//[[:space:]]/}"
 | |
|     [[ $excode -ne 0 ]] \
 | |
|     && echo -e "$MSG_FAILGRANT $MYUSER@$MYHOST" | "$TEE" -a "$LOGFILE" >&2 \
 | |
|     && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
|     && exit 1
 | |
|     # We've the database user with the proper password.
 | |
| 
 | |
|     # Drops all existing connections to the database being restored.
 | |
|     #
 | |
|     echo -e "KILL CONNECTIONS" | "$TEE" -a "$LOGFILE"
 | |
|     # List of the active connections.
 | |
|     SQLVERB="SELECT id FROM information_schema.processlist "
 | |
|     SQLVERB+="WHERE db = '$MYDATABASE';"
 | |
|     result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
|     if [[ $excode -ne 0 ]]; then
 | |
| 	echo -e "$MSG_FAILTOKILL" | "$TEE" -a "$LOGFILE" >&2
 | |
| 	echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
 | |
|     else
 | |
| 	# Enumerates and tries to kill these connections.
 | |
| 	for connection in $result ""
 | |
| 	do
 | |
| 	    if [ -n "$connection" ]; then
 | |
| 		SQLVERB="KILL $connection;"
 | |
| 		eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2); excode=$?
 | |
| 		[[ $excode -ne 0 ]] \
 | |
| 		&& echo -e "$MSG_FAILKILL $connection" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 		&& echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
 | |
| 	    fi
 | |
| 	done
 | |
|     fi
 | |
|     # Connections have eliminated (we hope).
 | |
| fi
 | |
| # Done with the superuser part.
 | |
| 
 | |
| # Drops the database.
 | |
| #
 | |
| echo -e "DROP DATABASE" | "$TEE" -a "$LOGFILE"
 | |
| DATABASE=""
 | |
| SQLVERB="DROP DATABASE IF EXISTS $MYDATABASE;"
 | |
| result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
| result="${result//[[:space:]]/}"
 | |
| [[ $excode -ne 0 ]] \
 | |
| && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| && exit 1
 | |
| 
 | |
| # Recreates the database.
 | |
| #
 | |
| echo -e "CREATE DATABASE" | "$TEE" -a "$LOGFILE"
 | |
| DATABASE=""
 | |
| SQLVERB="CREATE DATABASE $MYDATABASE;"
 | |
| result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
| result="${result//[[:space:]]/}"
 | |
| [[ $excode -ne 0 ]] \
 | |
| && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| && exit 1
 | |
| # We've an empty database.
 | |
| 
 | |
| # Sets the default character set.
 | |
| #
 | |
| if [ -n "$MYCHARSET" ]; then
 | |
|     echo -e "ALTER CHARACTER SET" | "$TEE" -a "$LOGFILE"
 | |
|     # If it is ambigous, we'll ignore it.
 | |
|     if [ "$(echo -e "$MYCHARSET" | "$WC" -l)" -ne 1 ]; then
 | |
| 	echo -e "$MSG_AMBIGOUS" | "$TEE" -a "$LOGFILE" >&2
 | |
| 	echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
 | |
| 	MYCHARSET=""
 | |
|     else
 | |
| 	# Let's set it.
 | |
| 	DATABASE="$MYDATABASE"
 | |
| 	SQLVERB="ALTER DATABASE $MYDATABASE CHARACTER SET $MYCHARSET;"
 | |
| 	result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
 | |
| 	result="${result//[[:space:]]/}"
 | |
| 	[[ $excode -ne 0 ]] \
 | |
| 	&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
 | |
| 	&& exit 1
 | |
|     fi
 | |
| fi
 | |
| # We've the character set adjusted.
 | |
| 
 | |
| # Restores the database from the dump.
 | |
| #
 | |
| # This isn't so straightforward as in PostgreSQL.
 | |
| # We'll use the database user's credentials, not the superuser's
 | |
| # to mitigate the effect of an unsanitized dump.
 | |
| echo -e "RESTORE" | "$TEE" -a "$LOGFILE"
 | |
| # Let's identify the file is gzipped or not.
 | |
| UNPACKER=$("$FILE" --mime-type "$MYDUMPFILE")
 | |
| UNPACKER=${UNPACKER##* }	# The last word is the MIME-type.
 | |
| # We'll use gunzip or cat (a dummy unzipper), according to the MIME type.
 | |
| [[ "$UNPACKER" = 'application/gzip' ]] \
 | |
| && UNPACKER="$GUNZIP" \
 | |
| || UNPACKER="$CAT"
 | |
| # This is a sed expression to modify the security definers within the dump.
 | |
| MOD_DEFINER="s/DEFINER=.*@[^ ]*/DEFINER=\`$MYUSER\`@\`$MYHOST\`/"
 | |
| # Considers the RDBMS environment.
 | |
| if [ -n "$MYCONTAINER" ]; then
 | |
|     # Dockerized RDBMS.
 | |
|     echo "MySQL dockerized - TODO!" | "$TEE" -a "$LOGFILE" >&2
 | |
| else
 | |
|     # Native RDBMS.
 | |
|     # Reads the dump, on the fly unpacks it and modifies the scurity definer,
 | |
|     # then passes the data stream to the MySQL client.
 | |
|     "$CAT" "$MYDUMPFILE" | "$UNPACKER" | "$SED" "$MOD_DEFINER" | \
 | |
|     "$MYSQL" -u "$MYUSER" -p$MYPASSWORD -h "$MYHOST" -P "$MYPORT" \
 | |
|              -f -D "$MYDATABASE" \
 | |
|              >/dev/null 2> >("$TEE" -a "$LOGFILE" >&2); excode=$?
 | |
|     # Unfortunately the result code doesn't differentiate the
 | |
|     # blocking and non-blocking states.
 | |
|     [[ $excode -ne 0 ]] \
 | |
|     && echo -e "$MSG_NONZERO: $excode" | "$TEE" -a "$LOGFILE" >&2
 | |
| fi
 | |
| # We had a try to restore the database - the result isn't properly defined.
 | |
| 
 | |
| # Closing log entry will be handled via EXIT trap.
 | |
| #
 | |
| # That's all, Folks! :)
 |