diff --git a/.metadata b/.metadata index d0ee0b0..24fd331 100644 Binary files a/.metadata and b/.metadata differ diff --git a/.templates/bin/mysql_restoredb b/.templates/bin/mysql_restoredb index 03c3caa..19e8ae1 100755 --- a/.templates/bin/mysql_restoredb +++ b/.templates/bin/mysql_restoredb @@ -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 -# Kovács Zoltán +# Author: Kovács Zoltán +# Kovács Zoltán # 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.