* docker-compose-v2 compatibility * MySQL/MariaDB dual comaptibility ACME client minor upgrade
		
			
				
	
	
		
			325 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #! /bin/bash
 | |
| #
 | |
| # Dumps a MySQL or MariaDB database from a native or dockerized instance running
 | |
| # on this box. This is a wrapper script to the mysqldump or mariadb-dump tool.
 | |
| #
 | |
| # If the DB engine is dockerized you need call as a Docker manager user
 | |
| # (member of the docker Linux group).
 | |
| #
 | |
| # Accepts few mysql/mariadb-dump options as well as the optional database password
 | |
| # and the optional output pathname:
 | |
| #
 | |
| # $0 [-u dbuser] [-p dbpass] [-h dbhost] [-P dbport]
 | |
| #    [-C container] [-d database] [-f dumpfile ] [--compress] [--force]
 | |
| #    [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)
 | |
| # 2024-08-24 v1.1
 | |
| # mod: MariaDB and MySQL dual compatibility (native and dockerized).
 | |
| #      Tested with Ubuntu 24.04 LTS.
 | |
| # 2023-06-18 v1.0
 | |
| # new: forked from the "SMARTERP_skeleton" repository.
 | |
| # 2023-02-15 v0.3
 | |
| # fix: Some updates to MySQL messed with our mysqldump settings.
 | |
| #      https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-31.html#mysqld-5-7-31-security
 | |
| #      --no-tablespaces (the recommended option) has been added to fix.
 | |
| #      https://dev.mysql.com/doc/refman/5.7/en/flush.html#flush-tables-with-read-lock
 | |
| #      --single-transaction=false has been added as a temporary workaround.
 | |
| # 2020-11-12 v0.2
 | |
| # fix: "$(which gzip)" instad of "$GZIP", see also:
 | |
| #      https://www.gnu.org/software/gzip/manual/html_node/Environment.html
 | |
| # mod: Accepts a dump folder name as well, instead of a dump file name.
 | |
| # 2020-09-17 v0.1 Initial release
 | |
| 
 | |
| # Accepted environment variables and their defaults.
 | |
| #
 | |
| MYCONTAINER=${MYCONTAINER-""}			# Docker container's name
 | |
| MYDATABASE=${MYDATABASE-""}			# Database name to dump
 | |
| MYDUMP=${MYDUMP-""}				# Dump file pathname
 | |
| MYHOST=${MYHOST:-"localhost"}			# Connection parameter
 | |
| MYOPTIONS=${MYOPTIONS-""}			# Options to pass to mysqldump
 | |
| MYPASSWORD=${MYPASSWORD-""}			# Credential for the MySQL user
 | |
| MYPORT=${MYPORT:-"3306"}			# Connection parameter
 | |
| MYUSER=${MYUSER:-"root"}			# MySQL user for this dump
 | |
| 
 | |
| ### Temporailly ignored! Need to sanitize.
 | |
| MYOPTIONS=""
 | |
| 
 | |
| # Other initialisations.
 | |
| #
 | |
| MYDUMPFORCED=""                                 # Dumps despite failed checks
 | |
| # Our default parameters for the mysql/mariadb-dump
 | |
| # Content of the MYOPTIONS will also appended during the actual dump.
 | |
| dumpparameters="--comments --events --routines --triggers "
 | |
| dumpparameters+="--complete-insert --dump-date --force --no-create-db "
 | |
| dumpparameters+="--opt --single-transaction "
 | |
| ## https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-31.html#mysqld-5-7-31-security
 | |
| dumpparameters+="--no-tablespaces "
 | |
| ## https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-41.html
 | |
| ## a temporary workaround only
 | |
| dumpparameters+="--single-transaction=false "
 | |
| # Technical databases which are never dumped.
 | |
| vetodatabases="information_schema mysql performance_schema sys"
 | |
| 
 | |
| # Messages.
 | |
| #
 | |
| MSG_ABORTED="aborted"
 | |
| MSG_BADCRED="Bad credentials for MySQL"
 | |
| MSG_BADOPT="Invalid option"
 | |
| MSG_DOESNOTRUN="Doesn't run the database container"
 | |
| MSG_DOCKERGRPNEED="You must be a member of the docker group."
 | |
| MSG_FAILSIZE="Failed to size the database"
 | |
| MSG_FORCED="but forced to continue"
 | |
| MSG_MISSINGDEP="Fatal: missing dependency"
 | |
| MSG_MISSINGDB="Missing database"
 | |
| MSG_NONWRITE="The target directory isn't writable"
 | |
| MSG_NOSPACE="Not enough space to dump the database"
 | |
| MSG_USAGE="Usage: $0 [options] [database [dump_pathname|-]]\n"
 | |
| MSG_USAGE+="Option:\tENVVAR:\n"
 | |
| MSG_USAGE+=" -C\tMYCONTAINER\tMySQL Docker container's name\n"
 | |
| MSG_USAGE+=" -d\tMYDATABASE\tMySQL database to dump\n"
 | |
| MSG_USAGE+=" -f\tMYDUMP\t\tDumpfile pathname\n"
 | |
| MSG_USAGE+=" -h\tMYHOST\t\tHostname or IP to connect (localhost)\n"
 | |
| MSG_USAGE+=" -p\tMYPORT\t\tTCP port to connect (3306)\n"
 | |
| MSG_USAGE+=" -P\tMYPASSWORD\tMySQL password\n"
 | |
| MSG_USAGE+=" -u\tMYUSER\t\tMySQL username (root)\n"
 | |
| MSG_USAGE+="--compress\t\tCompresses with gzip\n"
 | |
| MSG_USAGE+="--force\t\t\tForces the operation despite the failed checks\n"
 | |
| 
 | |
| # Getting options.
 | |
| #
 | |
| while getopts ":-:c:C:d:D:f:F:h:H:p:P:u:U:" option
 | |
| do
 | |
|     case ${option} in
 | |
|         "-" )
 | |
|             if   [ "$OPTARG" = "compress" ]; then compress="yes"
 | |
|             elif [ "$OPTARG" = "force" ]; then MYDUMPFORCED="yes"
 | |
|             elif [ "$OPTARG" = "help" ]; then echo -e "$MSG_USAGE" >&2; exit
 | |
|     	    else echo "$MSG_BADOPT --$OPTARG" >&2; exit 1
 | |
|             fi
 | |
|         ;;
 | |
|         "c" | "C" )
 | |
|             MYCONTAINER="$OPTARG"
 | |
|         ;;
 | |
|         "d" | "D" )
 | |
|             MYDATABASE="$OPTARG"
 | |
|         ;;
 | |
|         "f" | "F" )
 | |
|             MYDUMP="$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 are processed.
 | |
| 
 | |
| # Checks the dependencies.
 | |
| #
 | |
| # Conditional dependencies.
 | |
| if [ -n "$MYCONTAINER" ]; then
 | |
|     # Dockerized
 | |
|     additem="docker"
 | |
| else
 | |
|     # Native - MySQL or MariaDB CLI?
 | |
|     if [ -n "$(which mysql)" ]
 | |
|     then additem="mysql mysqldump"
 | |
|     else additem="mariadb mariadb-dump"; fi
 | |
| fi
 | |
| # Common dependencies.
 | |
| TR=$(which tr 2>/dev/null)
 | |
| if [ -z "$TR" ]; then echo "$MSG_MISSINGDEP tr."; exit 1 ; fi
 | |
| for item in cat cut date df dirname grep gzip hostname id pwd sed tail tee $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.
 | |
| #
 | |
| # Unifies the call of the clients in a native environment.
 | |
| if [ -z "$MYCONTAINER" ]; then
 | |
|     if [ -z "$MYSQL"     -a -n "$MARIADB"      ]; then MYSQL="$MARIADB"; fi
 | |
|     if [ -z "$MYSQLDUMP" -a -n "$MARIADB_DUMP" ]; then MYSQLDUMP="$MARIADB_DUMP"; fi
 | |
| fi
 | |
| #
 | |
| # An additional bugfix (use "$(which gzip)" instead of "$GZIP"):
 | |
| # https://www.gnu.org/software/gzip/manual/html_node/Environment.html
 | |
| GZIP=""
 | |
| 
 | |
| # 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 MySQL 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 (mandatory) database to dump.
 | |
| #
 | |
| # Lack of -d the 1st non-option parameter is the database's name.
 | |
| if [ -z "$MYDATABASE" -a -n "$1" ]; then MYDATABASE="$1"; shift; fi
 | |
| 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 MySQL internal databases.
 | |
| for veto in $vetodatabases ""
 | |
| do
 | |
|     [[ "$MYDATABASE" = "$veto" ]] && exit 0
 | |
| done
 | |
| # We've a database name to dump.
 | |
| 
 | |
| # Optional backup file pathname, defaults to ./hostname.timestamp.MYDATABASE.sql
 | |
| #
 | |
| if [ -z "$MYDUMP" -a -n "$1" ]; then MYDUMP="$1"; shift; fi
 | |
| if [ -d "$MYDUMP" ]; then
 | |
|     MYDUMP+="/$MYDATABASE.$("$DATE" '+%Y%m%d_%H%M%S').$("$HOSTNAME").sql"
 | |
| fi
 | |
| if [ -z "$MYDUMP" ]; then
 | |
|     MYDUMP="$PWD/$MYDATABASE.$("$DATE" '+%Y%m%d_%H%M%S').$("$HOSTNAME").sql"
 | |
| fi
 | |
| if [ "$MYDUMP" = "-" ]; then
 | |
|     # If '-' was given as the MYDUMP, we need output to STDOUT.
 | |
|     MYDUMP=""
 | |
|     logfile="/dev/null"
 | |
| else
 | |
|     # Adds the relevant extension to the MYDUMP and the logfile.
 | |
|     MYDUMP="${MYDUMP%.sql}.sql"
 | |
|     logfile="${MYDUMP%.sql}.log"
 | |
| fi
 | |
| # The folder to contain the new files must be writable.
 | |
| [[ -n "$MYDUMP" ]] && [[ ! -w "$("$DIRNAME" "$MYDUMP")" ]] \
 | |
| && echo "$MSG_NONWRITE \"$("$DIRNAME" "$MYDUMP")\"" >&2 && exit 1
 | |
| 
 | |
| # Prepopulates the MySQL commands.
 | |
| #
 | |
| my_connect=""
 | |
| [[ -n "$MYHOST" ]] && my_connect+=" --host=$MYHOST"
 | |
| [[ -n "$MYPORT" ]] && my_connect+=" --port=$MYPORT"
 | |
| [[ -n "$MYUSER" ]] && my_connect+=" --user=$MYUSER"
 | |
| [[ -n "$MYPASSWORD" ]] && my_connect+=" --password=$MYPASSWORD"
 | |
| 
 | |
| # Checks credentials and existence of the database given.
 | |
| #
 | |
| if [ -z "$MYCONTAINER" ]; then
 | |
|     databases=$("$MYSQL" -N --batch $my_connect --execute='show databases;' 2>/dev/null )
 | |
| else
 | |
|     # In a containerized environment we've to find out which CLIs are inside.
 | |
|     # Try the mysql first:
 | |
|     MYSQL="mysql"; MYSQLDUMP="mysqldump"
 | |
|     databases=$("$DOCKER" exec $MYCONTAINER sh -c "$MYSQL -N --batch $my_connect --execute='show databases;'" 2>/dev/null)
 | |
|     # On failure try the mariadb:
 | |
|     if [ -z "$databases" ]; then
 | |
| 	MYSQL="mariadb"; MYSQLDUMP="mariadb-dump"
 | |
| 	databases=$("$DOCKER" exec $MYCONTAINER sh -c "$MYSQL -N --batch $my_connect --execute='show databases;'" 2>/dev/null)
 | |
|     fi
 | |
| fi
 | |
| # CLI and Credentials are OK?
 | |
| [[ -z "$databases" ]] \
 | |
| && echo "$MSG_BADCRED ($MYUSER@$([[ -n "$MYCONTAINER" ]] && echo "$MYCONTAINER:")$MYHOST)." >&2 && exit 1
 | |
| # Existence?
 | |
| [[ ! "$databases" =~ (^|[[:space:]])"$MYDATABASE"($|[[:space:]]) ]] \
 | |
| && echo "$MSG_MISSINGDB \"$MYDATABASE\"." >&2 && exit 1
 | |
| # We've the database connection and existence checked.
 | |
| 
 | |
| # Do we size the database?
 | |
| #
 | |
| dbsize=0
 | |
| # It isn't relevant when we'll dump to the STDOUT.
 | |
| if [ -n "$MYDUMP" ]; then
 | |
|     # Calculates the size of the database (KB).
 | |
|     SQLVERB='SELECT table_schema, '
 | |
|     SQLVERB+='ROUND(SUM(data_length + index_length) / 1024, 0) '
 | |
|     SQLVERB+="FROM information_schema.TABLES WHERE table_schema='$MYDATABASE' "
 | |
|     SQLVERB+="GROUP BY table_schema;"
 | |
|     if [ -n "$MYCONTAINER" ]; then
 | |
| 	# Dockerized database.
 | |
| 	dbsize=$("$DOCKER" exec $MYCONTAINER sh -c "echo \"$SQLVERB\" | "$MYSQL" -N --batch $my_connect" 2>/dev/null | \
 | |
| 	         "$CUT" -d$'\t' -f2)
 | |
|     else
 | |
| 	# Self-hosted database.
 | |
| 	dbsize=$("$MYSQL" -N --batch $my_connect --execute="$SQLVERB" 2>/dev/null | \
 | |
| 	         "$CUT" -d$'\t' -f2)
 | |
|     fi
 | |
|     # Some sanitization
 | |
|     dbsize="${dbsize//[[:space:]]/}"
 | |
|     [[ -z "$dbsize" ]] && dbsize=0
 | |
|     [[ ! "$dbsize" =~ ^([[:digit:]])*$ ]] && dbsize=0
 | |
|     # On failure aborts here, except if it had forced.
 | |
|     if [ $dbsize -eq 0 ]; then echo -en "$MSG_FAILSIZE" | "$TEE" -a "$logfile"
 | |
|         if [ "$MYDUMPFORCED" ]; then
 | |
|     	    echo " - $MSG_FORCED" | "$TEE" -a "$logfile"
 | |
|         else
 | |
|     	    echo " - $MSG_ABORTED" | "$TEE" -a "$logfile"; exit 1
 | |
|         fi
 | |
|     fi
 | |
| fi
 | |
| # We've the database size.
 | |
| 
 | |
| # Checks the storage space available.
 | |
| # Note, that we'll compare the size of the running database, not the dump!
 | |
| # TODO: find a better estimate.
 | |
| #
 | |
| # It isn't relevant when we'll dump to the STDOUT or the database has no size.
 | |
| if [ -n "$MYDUMP" -a $dbsize -gt 0 ]; then
 | |
|     # KB units
 | |
|     freespace=$("$DF" --output=avail -k "$("$DIRNAME" "$MYDUMP")" | $TAIL -n1) #"
 | |
|     # It is enough?
 | |
|     if [ $freespace -lt $dbsize ]; then echo -en "$MSG_NOSPACE" | "$TEE" -a "$logfile"
 | |
| 	# On failure aborts here, except if it had forced.
 | |
|         if [ "$MYDUMPFORCED" ]; then
 | |
|     	    echo " - $MSG_FORCED" | "$TEE" -a "$logfile"
 | |
|         else
 | |
|     	    echo " - $MSG_ABORTED" | "$TEE" -a "$logfile"; exit 1
 | |
|         fi
 | |
|     fi
 | |
| fi
 | |
| # We've the space checked.
 | |
| 
 | |
| # Some cleanup.
 | |
| #
 | |
| [[ -n "$MYDUMP"  && -f "$MYDUMP"  ]] && rm "$MYDUMP"  >/dev/null
 | |
| [[ -n "$logfile" && -f "$logfile" ]] && rm "$logfile" >/dev/null
 | |
| #
 | |
| # Dumping.
 | |
| #
 | |
| if [ -n "$MYDUMP" ]; then
 | |
|     # Dumps into a file (then optionally compresses). Writes a separate log too.
 | |
|     # TODO: pipelined compress - doesn't work with Docker yet(?).
 | |
|     [[ -n "$MYCONTAINER" ]] \
 | |
|     && "$DOCKER" exec $MYCONTAINER sh -c "$MYSQLDUMP $my_connect $dumpparameters $MYOPTIONS $MYDATABASE" \
 | |
|        >"$MYDUMP" 2>>"$logfile" \
 | |
|     || "$MYSQLDUMP" $my_connect $dumpparameters $MYOPTIONS $MYDATABASE \
 | |
|        >"$MYDUMP" 2>>"$logfile"
 | |
|     # Optional compression.
 | |
|     [[ -n "$compress" ]] && "$(which gzip)" "$MYDUMP" 2>/dev/null
 | |
| else
 | |
|     # Dumps to STDOUT without logging.
 | |
|     [[ -n "$MYCONTAINER" ]] \
 | |
|     && "$DOCKER" exec $MYCONTAINER sh -c "$MYSQLDUMP $my_connect $dumpparameters $MYOPTIONS $MYDATABASE" \
 | |
|        2>/dev/null \
 | |
|     || "$MYSQLDUMP" $my_connect $dumpparameters $MYOPTIONS $MYDATABASE \
 | |
|        2>>/dev/null
 | |
| fi
 | |
| 
 | |
| # That's all, Folks! :)
 |