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