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! :)
|