|
@@ -1,7 +1,6 @@
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
# {{{ License and Copyright
|
|
|
-# PostgreSQL Backup Script
|
|
|
# https://github.com/k0lter/autopostgresqlbackup
|
|
|
# Copyright (c) 2005 Aaron Axelsen <axelseaa@amadmax.com>
|
|
|
# 2005 Friedrich Lobenstock <fl@fl.priv.at>
|
|
@@ -40,83 +39,58 @@ CONFIG="/etc/default/autopostgresqlbackup"
|
|
|
# Email Address to send errors to. If empty errors are displayed on stdout.
|
|
|
MAILADDR="root"
|
|
|
|
|
|
-# By default, on Debian systems (and maybe others), only postgres user is
|
|
|
-# allowed to access PostgreSQL databases without password.
|
|
|
-#
|
|
|
-# In order to dump databases we need to run pg_dump/psql commands as postgres
|
|
|
-# with su. This setting makes possible to run backups with a substitute user
|
|
|
-# using su. If
|
|
|
-#
|
|
|
-# empty, su usage will be disabled)
|
|
|
-SU_USERNAME=
|
|
|
+# Database engines supported: postgresql, mysql
|
|
|
+DBENGINE="postgresql"
|
|
|
|
|
|
-# Username to access the PostgreSQL server
|
|
|
-USERNAME="postgres"
|
|
|
+# Only while using PostgreSQL DB Engine
|
|
|
+SU_USERNAME=""
|
|
|
|
|
|
-# Password settings
|
|
|
-# In order to use a password to connect to database create a file
|
|
|
-# ${HOME}/.pgpass containing a line like this
|
|
|
-#
|
|
|
-# hostname:*:*:dbuser:dbpass
|
|
|
-#
|
|
|
-# replace hostname with the value of ${DBHOST}, dbuser with the value of
|
|
|
-# ${USERNAME} and dbpass with the password.
|
|
|
+# Username to access the Database server
|
|
|
+USERNAME=""
|
|
|
|
|
|
-# Host name (or IP address) of PostgreSQL server.
|
|
|
-# Use 'localhost' for socket connection or '127.0.0.1' to force TCP connection
|
|
|
+# Password to access then Database server
|
|
|
+PASSWORD=""
|
|
|
+
|
|
|
+# Host name (or IP address) of the Database server.
|
|
|
DBHOST="localhost"
|
|
|
|
|
|
-# Port of PostgreSQL server.
|
|
|
-# It is also used if ${DBHOST} is localhost (socket connection) as socket name
|
|
|
-# contains port
|
|
|
-DBPORT="5432"
|
|
|
+# Port of Database server.
|
|
|
+DBPORT=""
|
|
|
|
|
|
-# List of database(s) names(s) to backup If you would like to backup all
|
|
|
-# databases on the server set DBNAMES="all".
|
|
|
-#
|
|
|
-# If set to "all" then any new databases will automatically be backed up
|
|
|
-# without needing to modify this settings when a new database is created.
|
|
|
-#
|
|
|
-# If the database you want to backup has a space in the name replace the space
|
|
|
-# with a % ("data base" will become "data%base").
|
|
|
+# List of database(s) names(s) to backup.
|
|
|
DBNAMES="all"
|
|
|
|
|
|
-# List of databases to exclude if DBNAMES is not set to all.
|
|
|
+# List of databases to exclude
|
|
|
DBEXCLUDE=""
|
|
|
|
|
|
-# Pseudo database name used to dump global objects (users, roles, tablespaces)
|
|
|
+# Virtual database name used to dump global objects (users, roles, tablespaces)
|
|
|
GLOBALS_OBJECTS="postgres_globals"
|
|
|
|
|
|
# Backup directory
|
|
|
BACKUPDIR="/var/backups"
|
|
|
|
|
|
-# Include CREATE DATABASE in backups?
|
|
|
+# Include CREATE DATABASE statement
|
|
|
CREATE_DATABASE="yes"
|
|
|
|
|
|
-# Which day do you want weekly backups? (1 to 7 where 1 is Monday)
|
|
|
-# When set to 0, weekly backups are disabled
|
|
|
+# Which day do you want weekly backups?
|
|
|
DOWEEKLY=7
|
|
|
|
|
|
# Which day do you want monthly backups?
|
|
|
-# When set to 0, monthly backups are disabled
|
|
|
DOMONTHLY=1
|
|
|
|
|
|
-# Backup retention count for daily backups, older backups are removed.
|
|
|
+# Backup retention count for daily backups.
|
|
|
BRDAILY=14
|
|
|
|
|
|
-# Backup retention count for weekly backups, older backups are removed.
|
|
|
+# Backup retention count for weekly backups.
|
|
|
BRWEEKLY=5
|
|
|
|
|
|
-# Backup retention count for monthly backups, older backups are removed.
|
|
|
+# Backup retention count for monthly backups.
|
|
|
BRMONTHLY=12
|
|
|
|
|
|
-# Compression tool. It could be gzip, pigz, bzip2, xz, zstd or any compression
|
|
|
-# tool that supports to read data to be compressed from stdin and outputs them
|
|
|
-# to stdout).
|
|
|
-# If the tool is not in ${PATH}, the absolute path can be used.
|
|
|
+# Compression tool.
|
|
|
COMP="gzip"
|
|
|
|
|
|
-# Compression tools options to be used with COMP
|
|
|
+# Compression tools options.
|
|
|
COMP_OPTS=
|
|
|
|
|
|
# Options string for use with pg_dump (see pg_dump manual page).
|
|
@@ -125,6 +99,9 @@ PGDUMP_OPTS=
|
|
|
# Options string for use with pg_dumpall (see pg_dumpall manual page).
|
|
|
PGDUMPALL_OPTS=
|
|
|
|
|
|
+# Options string for use with mysqldump (see myqldump manual page).
|
|
|
+MYDUMP_OPTS=
|
|
|
+
|
|
|
# Backup files extension
|
|
|
EXT="sql"
|
|
|
|
|
@@ -132,22 +109,12 @@ EXT="sql"
|
|
|
PERM=600
|
|
|
|
|
|
# Minimum size (in bytes) for a dump/file (compressed or not).
|
|
|
-# File size below this limit will raise an warning.
|
|
|
MIN_DUMP_SIZE=256
|
|
|
|
|
|
# Enable encryption (asymmetric) with GnuPG.
|
|
|
ENCRYPTION="no"
|
|
|
|
|
|
# Encryption public key (path to the key)
|
|
|
-# Export your public key=""
|
|
|
-# gpg --export 0xY0URK3Y1D --output mypubkey.gpg or \
|
|
|
-# gpg --export --armor 0xY0URK3Y1D --output mypubkey.asc
|
|
|
-# then copy mypubkey.asc or mypubkey.gpg to the path pointed by the
|
|
|
-# ${ENCRYPTION_PUBLIC_KEY}.
|
|
|
-#
|
|
|
-# Decryption
|
|
|
-# gpg --decrypt --output backup.sql.gz backup.sql.gz.enc
|
|
|
-#
|
|
|
ENCRYPTION_PUBLIC_KEY=
|
|
|
|
|
|
# Suffix for encyrpted files
|
|
@@ -238,6 +205,46 @@ log_warn() {
|
|
|
}
|
|
|
# }}}
|
|
|
|
|
|
+# {{{ arg_encode()
|
|
|
+arg_encode() {
|
|
|
+ while read -r arg ; do
|
|
|
+ echo "${arg}" | sed \
|
|
|
+ -e 's/%/%25/g' \
|
|
|
+ -e 's/ /%20/g' \
|
|
|
+ -e 's/\$/%24/g' \
|
|
|
+ -e 's/`/%60/g' \
|
|
|
+ -e 's/"/%22/g' \
|
|
|
+ -e "s/'/%27/g" \
|
|
|
+ -e "s/#/%23/g" \
|
|
|
+ -e 's/=/%3D/g' \
|
|
|
+ -e 's/\[/%5B/g' \
|
|
|
+ -e 's/\]/%5D/g' \
|
|
|
+ -e 's/!/%21/g' \
|
|
|
+ -e 's/>/%3E/g' \
|
|
|
+ -e 's/</%3C/g' \
|
|
|
+ -e 's/|/%7C/g' \
|
|
|
+ -e 's/;/%3B/g' \
|
|
|
+ -e 's/{/%7B/g' \
|
|
|
+ -e 's/}/%7D/g' \
|
|
|
+ -e 's/(/%28/g' \
|
|
|
+ -e 's/)/%29/g' \
|
|
|
+ -e 's/\*/%2A/g' \
|
|
|
+ -e 's/:/%3A/g' \
|
|
|
+ -e 's/\?/%3F/g' \
|
|
|
+ -e 's/&/%26/g' \
|
|
|
+ -e 's/\//%2F/g'
|
|
|
+ done
|
|
|
+}
|
|
|
+# }}}
|
|
|
+
|
|
|
+# {{{ arg_decode()
|
|
|
+arg_decode() {
|
|
|
+ while read -r arg ; do
|
|
|
+ echo -e "${arg//%/\\x}"
|
|
|
+ done
|
|
|
+}
|
|
|
+# }}}
|
|
|
+
|
|
|
# {{{ gpg_setup()
|
|
|
gpg_setup() {
|
|
|
GPG_HOMEDIR="$(mktemp --quiet --directory -t "${NAME}.XXXXXX")"
|
|
@@ -267,20 +274,24 @@ compression () {
|
|
|
}
|
|
|
# }}}
|
|
|
|
|
|
-# {{{ pgdb_init()
|
|
|
-pgdb_init () {
|
|
|
+# {{{ postgresqldb_init()
|
|
|
+postgresqldb_init () {
|
|
|
+ if [ -z "${DBPORT}" ]; then
|
|
|
+ DBPORT="5432"
|
|
|
+ fi
|
|
|
CONN_ARGS=(--port "${DBPORT}")
|
|
|
if [ "${DBHOST}" != "localhost" ]; then
|
|
|
CONN_ARGS+=(--host "${DBHOST}")
|
|
|
fi
|
|
|
- if [ -n "${USERNAME}" ]; then
|
|
|
- CONN_ARGS+=(--username "${USERNAME}")
|
|
|
+ if [ -z "${USERNAME}" ]; then
|
|
|
+ USERNAME="postgres"
|
|
|
fi
|
|
|
+ CONN_ARGS+=(--username "${USERNAME}")
|
|
|
}
|
|
|
# }}}
|
|
|
|
|
|
-# {{{ pgdb_list()
|
|
|
-pgdb_list () {
|
|
|
+# {{{ postgresqldb_list()
|
|
|
+postgresqldb_list () {
|
|
|
local cmd_prog cmd_args raw_dblist dblist dbexcl databases
|
|
|
|
|
|
cmd_prog="psql"
|
|
@@ -304,6 +315,7 @@ pgdb_list () {
|
|
|
read -r -a dblist <<< "$(
|
|
|
printf "%s" "${raw_dblist}" | \
|
|
|
sed -E -n 's/^([^:]+):.+$/\1/p' | \
|
|
|
+ arg_encode | \
|
|
|
tr '\n' ' '
|
|
|
)"
|
|
|
log_debug "Automatically found databases: ${dblist[*]}"
|
|
@@ -328,8 +340,8 @@ pgdb_list () {
|
|
|
}
|
|
|
# }}}
|
|
|
|
|
|
-# {{{ pgdb_dump()
|
|
|
-pgdb_dump () {
|
|
|
+# {{{ postgresqldb_dump()
|
|
|
+postgresqldb_dump () {
|
|
|
local db_name cmd_prog cmd_args pg_args
|
|
|
|
|
|
db_name="${1}"
|
|
@@ -353,7 +365,11 @@ pgdb_dump () {
|
|
|
pg_args=("${PGDUMPALL_ARGS[@]}")
|
|
|
else
|
|
|
cmd_prog="pg_dump"
|
|
|
- cmd_args=("${db_name}")
|
|
|
+ if [ -n "${SU_USERNAME}" ]; then
|
|
|
+ cmd_args=("'${db_name}'")
|
|
|
+ else
|
|
|
+ cmd_args=("${db_name}")
|
|
|
+ fi
|
|
|
pg_args=("${PGDUMP_ARGS[@]}")
|
|
|
if [ "${CREATE_DATABASE}" = "yes" ]; then
|
|
|
pg_args+=(--create)
|
|
@@ -378,6 +394,156 @@ pgdb_dump () {
|
|
|
}
|
|
|
# }}}
|
|
|
|
|
|
+# {{{ mysqldb_init()
|
|
|
+mysqldb_init () {
|
|
|
+ CONN_ARGS=()
|
|
|
+ if [ -z "${DBPORT}" ]; then
|
|
|
+ DBPORT="3306"
|
|
|
+ fi
|
|
|
+ if [ "${DBHOST}" != "localhost" ]; then
|
|
|
+ CONN_ARGS+=(--host "${DBHOST}")
|
|
|
+ fi
|
|
|
+ if [ "${DBPORT}" != "3306" ]; then
|
|
|
+ CONN_ARGS+=(--port "${DBPORT}")
|
|
|
+ fi
|
|
|
+ if [ -z "${USERNAME}" ]; then
|
|
|
+ USERNAME="root"
|
|
|
+ fi
|
|
|
+ CONN_ARGS+=(--user "${USERNAME}")
|
|
|
+ if [ -n "${PASSWORD}" ]; then
|
|
|
+ CONN_ARGS+=(--password "${PASSWORD}")
|
|
|
+ fi
|
|
|
+}
|
|
|
+# }}}
|
|
|
+
|
|
|
+# {{{ mysqldb_list()
|
|
|
+mysqldb_list () {
|
|
|
+ local cmd_prog cmd_args raw_dblist dblist dbexcl databases
|
|
|
+
|
|
|
+ cmd_prog="mysql"
|
|
|
+ cmd_args=(--batch --skip-column-names --execute 'SHOW DATABASES;')
|
|
|
+
|
|
|
+ if [ "${#CONN_ARGS[@]}" -gt 0 ]; then
|
|
|
+ cmd_args+=("${CONN_ARGS[@]}")
|
|
|
+ fi
|
|
|
+
|
|
|
+ log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
|
|
|
+ raw_dblist=$(
|
|
|
+ if ! "${cmd_prog}" "${cmd_args[@]}" 2> >(logger "err" "error"); then
|
|
|
+ log_error "Running command '${cmd_prog} ${cmd_args[*]}' has failed"
|
|
|
+ fi
|
|
|
+ )
|
|
|
+
|
|
|
+ read -r -a dblist <<< "$(
|
|
|
+ printf "%s" "${raw_dblist}" | \
|
|
|
+ arg_encode | \
|
|
|
+ tr '\n' ' '
|
|
|
+ )"
|
|
|
+ log_debug "Automatically found databases: ${dblist[*]}"
|
|
|
+
|
|
|
+ if [ -n "${DBEXCLUDE}" ]; then
|
|
|
+ IFS=" " read -r -a dbexcl <<< "${DBEXCLUDE}"
|
|
|
+ else
|
|
|
+ dbexcl=()
|
|
|
+ fi
|
|
|
+ dbexcl+=(information_schema performance_schema mysql)
|
|
|
+ log_debug "Excluded databases: ${dbexcl[*]}"
|
|
|
+
|
|
|
+ mapfile -t databases < <(
|
|
|
+ comm -23 \
|
|
|
+ <(IFS=$'\n'; echo "${dblist[*]}" | sort) \
|
|
|
+ <(IFS=$'\n'; echo "${dbexcl[*]}" | sort) \
|
|
|
+ )
|
|
|
+ log_debug "Database(s) to be backuped: ${databases[*]}"
|
|
|
+
|
|
|
+ printf "%s " "${databases[@]}"
|
|
|
+}
|
|
|
+# }}}
|
|
|
+
|
|
|
+# {{{ mysqldb_dump()
|
|
|
+mysqldb_dump () {
|
|
|
+ local db_name cmd_prog cmd_args my_args
|
|
|
+
|
|
|
+ db_name="${1}"
|
|
|
+
|
|
|
+ if [ -n "${MYDUMP_OPTS}" ]; then
|
|
|
+ IFS=" " read -r -a MYDUMP_ARGS <<< "${MYDUMP_OPTS}"
|
|
|
+ else
|
|
|
+ MYDUMP_ARGS=()
|
|
|
+ fi
|
|
|
+
|
|
|
+ cmd_prog="mysqldump"
|
|
|
+ cmd_args=("${db_name}")
|
|
|
+ my_args=("${MYDUMP_ARGS[@]}")
|
|
|
+ my_args+=(--quote-names --events --routines)
|
|
|
+ if [ "${CREATE_DATABASE}" = "no" ]; then
|
|
|
+ my_args+=(--databases)
|
|
|
+ else
|
|
|
+ my_args+=(--no-create-db)
|
|
|
+ fi
|
|
|
+
|
|
|
+ if [ "${#CONN_ARGS[@]}" -gt 0 ]; then
|
|
|
+ cmd_args+=("${CONN_ARGS[@]}")
|
|
|
+ fi
|
|
|
+ if [ "${#my_args[@]}" -gt 0 ]; then
|
|
|
+ cmd_args+=("${my_args[@]}")
|
|
|
+ fi
|
|
|
+
|
|
|
+ log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
|
|
|
+ if ! "${cmd_prog}" "${cmd_args[@]}" 2> >(logger "err" "error"); then
|
|
|
+ log_error "Running command '${cmd_prog} ${cmd_args[*]}' has failed"
|
|
|
+ fi
|
|
|
+}
|
|
|
+# }}}
|
|
|
+
|
|
|
+# {{{ db_init()
|
|
|
+db_init () {
|
|
|
+ case ${DBENGINE} in
|
|
|
+ postgresql)
|
|
|
+ postgresqldb_init
|
|
|
+ ;;
|
|
|
+ mysql)
|
|
|
+ mysqldb_init
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ log_error "Unsupported database engine ${DBENGINE}, check DBENGINE configuration parameter"
|
|
|
+ return 1
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+}
|
|
|
+# }}}
|
|
|
+
|
|
|
+# {{{ db_list()
|
|
|
+db_list () {
|
|
|
+ case ${DBENGINE} in
|
|
|
+ postgresql)
|
|
|
+ postgresqldb_list
|
|
|
+ ;;
|
|
|
+ mysql)
|
|
|
+ mysqldb_list
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ log_error "Unsupported database engine ${DBENGINE}, check DBENGINE configuration parameter"
|
|
|
+ return 1
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+}
|
|
|
+# }}}
|
|
|
+
|
|
|
+# {{{ db_dump()
|
|
|
+db_dump () {
|
|
|
+ case ${DBENGINE} in
|
|
|
+ postgresql|mysql)
|
|
|
+ ${DBENGINE}db_dump "${1}"
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ log_error "Unsupported database engine ${DBENGINE}, check DBENGINE configuration parameter"
|
|
|
+ return 1
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+}
|
|
|
+# }}}
|
|
|
+
|
|
|
# {{{ dump()
|
|
|
dump() {
|
|
|
local db_name dump_file comp_ext
|
|
@@ -410,16 +576,16 @@ dump() {
|
|
|
|
|
|
if [ -n "${COMP}" ] && [ "${ENCRYPTION}" = "yes" ]; then
|
|
|
log_debug "Dumping (${db_name}) +compress +encrypt to '${dump_file}'"
|
|
|
- pgdb_dump "${db_name}" | compression | encryption > "${dump_file}"
|
|
|
+ db_dump "${db_name}" | compression | encryption > "${dump_file}"
|
|
|
elif [ -n "${COMP}" ]; then
|
|
|
log_debug "Dumping (${db_name}) +compress to '${dump_file}'"
|
|
|
- pgdb_dump "${db_name}" | compression > "${dump_file}"
|
|
|
+ db_dump "${db_name}" | compression > "${dump_file}"
|
|
|
elif [ "${ENCRYPTION}" = "yes" ]; then
|
|
|
log_debug "Dumping (${db_name}) +encrypt to '${dump_file}'"
|
|
|
- pgdb_dump "${db_name}" | encryption > "${dump_file}"
|
|
|
+ db_dump "${db_name}" | encryption > "${dump_file}"
|
|
|
else
|
|
|
log_debug "Dumping (${db_name}) to '${dump_file}'"
|
|
|
- pgdb_dump "${db_name}" > "${dump_file}"
|
|
|
+ db_dump "${db_name}" > "${dump_file}"
|
|
|
fi
|
|
|
|
|
|
if [ -f "${dump_file}" ]; then
|
|
@@ -618,7 +784,7 @@ fi
|
|
|
# {{{ main()
|
|
|
log_info "${NAME} version ${VERSION}"
|
|
|
log_info "Homepage: ${HOMEPAGE}"
|
|
|
-log_info "Backup of Database Server - ${HOST}"
|
|
|
+log_info "Backup of Database Server (${DBENGINE}) - ${HOST}"
|
|
|
|
|
|
if [ -n "${COMP}" ]; then
|
|
|
if ! command -v "${COMP}" >/dev/null ; then
|
|
@@ -665,24 +831,25 @@ fi
|
|
|
|
|
|
# If backing up all DBs on the server
|
|
|
if [ "${DBNAMES}" = "all" ]; then
|
|
|
- DBNAMES="$(pgdb_list)"
|
|
|
+ DBNAMES="$(db_list)"
|
|
|
fi
|
|
|
|
|
|
-pgdb_init
|
|
|
+db_init
|
|
|
+
|
|
|
+for db_enc in ${DBNAMES} ; do
|
|
|
+ db="$(echo "${db_enc}" | arg_decode)"
|
|
|
|
|
|
-for db in ${DBNAMES} ; do
|
|
|
- db="${db//%/ / }"
|
|
|
log_info "Backup of Database (${period}) '${db}'"
|
|
|
|
|
|
- backupdbdir="${BACKUPDIR}/${period}/${db}"
|
|
|
+ backupdbdir="${BACKUPDIR}/${period}/${db_enc}"
|
|
|
if [ ! -e "${backupdbdir}" ]; then
|
|
|
log_debug "Creating Backup DB directory '${backupdbdir}'"
|
|
|
mkdir -p "${backupdbdir}"
|
|
|
fi
|
|
|
|
|
|
- cleanup "${BACKUPDIR}" "${db}" "${period}" "${rotate}"
|
|
|
+ cleanup "${BACKUPDIR}" "${db_enc}" "${period}" "${rotate}"
|
|
|
|
|
|
- backupfile="${backupdbdir}/${db}_${DATE}.${EXT}"
|
|
|
+ backupfile="${backupdbdir}/${db_enc}_${DATE}.${EXT}"
|
|
|
dump "${db}" "${backupfile}"
|
|
|
done
|
|
|
log_info "Backup End: $(date)"
|