2
0

The common mysql_restoredb script now works with dockerized engine as well.

This commit is contained in:
2025-03-05 19:29:27 +01:00
parent 39c0f07fca
commit de8bfe5137
2 changed files with 133 additions and 64 deletions

BIN
.metadata

Binary file not shown.

View File

@ -21,9 +21,13 @@
# [-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>
# Author: Kovács Zoltán <kovacsz@marcusconsulting.hu>
# Kovács Zoltán <kovacs.zoltan@smartfront.hu>
# License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html)
# 2025-03-04 v1.1
# new: Works with dockerized databases but hasn't yet been tested with natives.
# mod: Database user creation and grants rewritten. Now create user @'%'
# (instead of @'myhost') if it doesn't already exist.
# 2023-06-18 v1.0
# new: forked from the "SMARTERP_skeleton" repository.
# 2022-04-07 v0.4
@ -46,14 +50,10 @@ 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
@ -133,14 +133,21 @@ do
;;
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"
if [ -n "$MYCONTAINER" ]; then
# Dockerized
additem="docker"
else
# Native - MySQL or MariaDB CLI?
if [ -n "$(which mysql)" ]
then additem="mysql"
else additem="mariadb"; fi
fi
# Common dependencies.
TR=$(which tr 2>/dev/null)
if [ -z "$TR" ]; then echo "$MSG_MISSINGDEP tr."; exit 1 ; fi
@ -151,6 +158,12 @@ do
then export $(echo $item | "$TR" '[:lower:]' '[:upper:]')=$(which $item)
else echo "$MSG_MISSINGDEP $item." >&2; exit 1; fi
done
#
# Unifies the call of the clients in a native environment.
if [ -z "$MYCONTAINER" ]; then
if [ -z "$MYSQL" -a -n "$MARIADB" ]; then MYSQL="$MARIADB"; fi
fi
#
# All dependencies are available via "$THECOMMAND" (upper case) call.
# Sanitizing the parameters.
@ -184,6 +197,7 @@ done
#
[[ -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.
@ -213,6 +227,7 @@ for veto in $vetodatabases ""
do
[[ "$MYDATABASE" = "$veto" ]] && exit 0
done
#
# We've a database name to restore.
# Determines the dumpfile.
@ -234,6 +249,7 @@ 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.
@ -252,6 +268,7 @@ if [ -z "$MYCHARSET" ]; then
# 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.
@ -262,6 +279,7 @@ fi
&& 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.
@ -274,35 +292,64 @@ function close_log() {
"$TEE" -a "$LOGFILE"
}
trap -- 'close_log' EXIT
#
# We started logging.
# Prepopulates the SQL command skeleton (macro).
# Prepopulates two SQL command skeletons (macros).
#
# 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.
# CONNECT, DATABASE and SQLVERB clauses then eval $DO_SQLSTREAM or
# $DO_SQLVERB. Warning: the parameters must be sanitized!
#
if [ -n "$MYCONTAINER" ]; then
# When MySQL runs in the container.
#
if [ -n "$("$DOCKER" exec $MYCONTAINER which mysql)" ]; then
DO_SQLVERB="\"\$DOCKER\" exec -e MYSQL_PWD=\"\$MYSQL_PWD\" \$MYCONTAINER mysql "
# When MariaDB runs in the container.
elif [ -n "$("$DOCKER" exec $MYCONTAINER which mariadb)" ]; then
DO_SQLVERB="\"\$DOCKER\" exec -e MYSQL_PWD=\"\$MYSQL_PWD\" \$MYCONTAINER mariadb "
DO_SQLSTREAM="\"\$DOCKER\" exec -i -e MYSQL_PWD=\"\$MYSQL_PWD\" \$MYCONTAINER /bin/bash -c \"mariadb "
# Otherwise gives it up here.
else
echo -e "$MSG_BADDBTYPE in $MYCONTAINER." | "$TEE" -a "$LOGFILE" >&2
echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2
exit 1
fi
# Common parameters.
DO_SQLVERB+="\$CONNECT -sN --ssl-verify-server-cert=false \$DATABASE "
DO_SQLVERB+="-e \"\$SQLVERB\""
DO_SQLSTREAM+="\$CONNECT -sN --ssl-verify-server-cert=false \$DATABASE \""
else
# Native environment.
#
DO_SQLVERB="export \"MYSQL_PWD\"; \"\$MYSQL\" "
DO_SQLVERB+="\$CONNECT -sN --ssl-verify-server-cert=false \$DATABASE "
DO_SQLVERB+="-e \"\$SQLVERB\""
SO_SQLSTREAM="$DO_SQLVERB"
fi
#
# We've two suitable SQL macros.
# Do we connect the database as a DBA?
# Are we able to connect to the database, preferably as a DBA?
#
SQLVERB="SELECT 1;"
result=""
# Sets the default DBA username for dockerized and native RDBMS as well.
if [ -z "$MYDBAUSER" ]; then
[[ -n "$MYCONTAINER" ]] \
&& MYDBAUSER="root" \
|| MYDBAUSER="$USER"
|| MYDBAUSER="root"
fi
# In a native environment we'll try the local connection
# (Unix-domain socket) first.
if [ -z "$MYCONTAINER" ]; then
CONNECT=""
DATABASE=""
result=$(eval "$DO_SQLVERB" 2>/dev/null); excode=$?
result="${result//[[:space:]]/}"
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.
@ -327,10 +374,12 @@ if [ "$result" != "1" ]; then
fi
fi
fi
#
# We've valid MYSQL_PWD and CONNECT clauses.
# Checks the superuser privilege.
# Better check: TODO!
#
ISDBA=false
DATABASE=""
SQLVERB="SHOW GRANTS;"
@ -343,10 +392,12 @@ 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"
@ -379,41 +430,57 @@ if $ISDBA; then
&& 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.
# Database user revision.
#
echo -e "CREATE USER" | "$TEE" -a "$LOGFILE"
SQLVERB=" CREATE USER '$MYUSER'@'$MYHOST'; "
# If '$MYUSER'@'$MYHOST' exists, it will provide the necessaty privileges.
# If '$MYUSER'@'%' doesn't exist, it will create it then provide the
# necessary privileges.
#
# Checks '$MYUSER'@'$MYHOST'
SQLVERB="SELECT COUNT(1) FROM mysql.user WHERE user = '$MYUSER' AND host = '$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'; "
if [[ $excode -eq 0 && $result -eq 1 ]]; then
# It exists, let's give it privileges.
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_FAILPASS $MYUSER@$MYHOST" | "$TEE" -a "$LOGFILE" >&2 \
&& echo -e "$MSG_FAILGRANT $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'; "
# Checks '$MYUSER'@'%' as well.
SQLVERB="SELECT COUNT(1) FROM mysql.user WHERE user = '$MYUSER' AND host = '%'; "
result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
result="${result//[[:space:]]/}"
if [[ $excode -eq 0 && $result -ne 1 ]]; then
# Creates if it doesn't exist yet.
echo -e "CREATE USER %" | "$TEE" -a "$LOGFILE"
SQLVERB="CREATE USER '$MYUSER'@'%' IDENTIFIED BY '$MYPASSWORD'; "
result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
result="${result//[[:space:]]/}"
# Gives it up here if something went wrong.
[[ $excode -ne 0 ]] \
&& echo -e "$MSG_FAILUSER $MYUSER@%" | "$TEE" -a "$LOGFILE" >&2 \
&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
&& exit 1
fi
# Let's give it privileges.
echo -e "GRANT %" | "$TEE" -a "$LOGFILE"
SQLVERB="GRANT ALL PRIVILEGES ON $MYDATABASE.* TO '$MYUSER'@'%'; "
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_FAILGRANT $MYUSER@%" | "$TEE" -a "$LOGFILE" >&2 \
&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
&& exit 1
# We've the database user with the proper password.
#
# We've the database user(s) with the proper grants.
# Drops all existing connections to the database being restored.
#
@ -438,8 +505,10 @@ if $ISDBA; then
fi
done
fi
#
# Connections have eliminated (we hope).
fi
#
# Done with the superuser part.
# Drops the database.
@ -452,7 +521,7 @@ 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"
@ -463,6 +532,7 @@ 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.
@ -485,13 +555,15 @@ if [ -n "$MYCHARSET" ]; then
&& exit 1
fi
fi
#
# We've the character set adjusted.
# Restores the database from the dump.
#
# This isn't so straightforward as in PostgreSQL.
# This isn't so straightforward as in e.g 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")
@ -501,24 +573,21 @@ UNPACKER=${UNPACKER##* } # The last word is the MIME-type.
&& 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
MOD_DEFINER="s/DEFINER=.*@[^ ]*/DEFINER=CURRENT_USER/"
#
# We'll read the dump, on the fly unpack it and modify the security definer,
# then we'll pass the data stream to the MySQL client.
#
DATABASE="$MYDATABASE"
SQLVERB=""
(eval "$DO_SQLSTREAM") \
< <("$CAT" "$MYDUMPFILE" | "$UNPACKER" | "$SED" "$MOD_DEFINER") \
>/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
#
# We had a try to restore the database - the result isn't properly defined.
# Closing log entry will be handled via EXIT trap.