30 Commits ef88f5bbe9 ... e382a03c7a

Auteur SHA1 Bericht Datum
  Emmanuel Bouthenot e382a03c7a Merge pull request #47 from stweil/master 4 maanden geleden
  Stefan Weil c92d3ae253 Fix license statement with old FSF address 4 maanden geleden
  Stefan Weil b407a01363 Fix some typos (found by codespell and typos) 4 maanden geleden
  Emmanuel Bouthenot 0b44a5e4e2 chore: Bump version to 2.5 5 maanden geleden
  Emmanuel Bouthenot a72ededd0d Update changelog 5 maanden geleden
  Emmanuel Bouthenot 35e5a73412 fix: fix runtime environment by creating backup directories separately 5 maanden geleden
  Emmanuel Bouthenot 43c6fdfb95 chore: Bump version to 2.4 5 maanden geleden
  Emmanuel Bouthenot ec6fe2b55e chore: update changelog 5 maanden geleden
  Emmanuel Bouthenot 2531821fba fix: fix runtime environment (backup directories created too early) 5 maanden geleden
  Emmanuel Bouthenot d14e440d4f chore: Bump version to 2.3 9 maanden geleden
  Emmanuel Bouthenot 789e871010 chore: update changelog 9 maanden geleden
  Emmanuel Bouthenot 478ed3732a fix: make minor wording changes 9 maanden geleden
  Emmanuel Bouthenot b356d2962f chore: enhance reporting and add REPORT_ERRORS_ONLY configuration variable 7 maanden geleden
  Emmanuel Bouthenot 0cf5254eba chore: add cron and systemd examples 7 maanden geleden
  Emmanuel Bouthenot b44cbd8ad9 fix: build manpage from Documentation.md with pandoc 9 maanden geleden
  Emmanuel Bouthenot 5a0a002124 chore: Bump version to 2.2 10 maanden geleden
  Emmanuel Bouthenot cd5d247c1d chore: update manpage 10 maanden geleden
  Emmanuel Bouthenot 224488b705 chore: Bump version to 2.1 10 maanden geleden
  Emmanuel Bouthenot 6b870d49ca feat: Default configuration file /etc/default/autopostgresqlbackup is now deprecated in favor of /etc/autodbbackup.d 10 maanden geleden
  Emmanuel Bouthenot 7f4bbcf2b6 fix: update MariaDB example configuration file 10 maanden geleden
  Emmanuel Bouthenot 33d99f37e9 Make possible to override mysql{,dump} paths in config file 1 jaar geleden
  Emmanuel Bouthenot 1300124889 Make possible to override pg_dump{,all} paths in config file 1 jaar geleden
  Emmanuel Bouthenot 87d0f2445b Fix database purge, cleanup function was defined twice (Thanks to kaptontape) 1 jaar geleden
  Emmanuel Bouthenot b9ad2b0434 Fix database list retrieval (last one was missing) 1 jaar geleden
  Emmanuel Bouthenot 524f922e3b Update Changelog 1 jaar geleden
  Emmanuel Bouthenot 078a351499 Update Changelog 1 jaar geleden
  Emmanuel Bouthenot aac231140c Add configuration examples for PostgreSQL and MySQL 1 jaar geleden
  Emmanuel Bouthenot f7e61b7b21 Add manpage 1 jaar geleden
  Emmanuel Bouthenot 5d9b2d44c7 Add MySQL support 1 jaar geleden
  Emmanuel Bouthenot 81614646bc Add option -c to load a custom configuration file or folder 1 jaar geleden

+ 26 - 2
Changelog.md

@@ -1,10 +1,34 @@
 # Changelog
 
+## Version 2.5
+
+* fix runtime environment by creating backup directories separately (Closes: [#43](https://github.com/k0lter/autopostgresqlbackup/issues/43)
+
+## Version 2.4
+
+* Fix runtime environment (backup directories created too early) (Closes: [#43](https://github.com/k0lter/autopostgresqlbackup/issues/43)
+
+## Version 2.3
+
+* Add `REPORT_ERRORS_ONLY` (default: `yes`) configuration variable. Set it to `no` permits to receive a backup report even if there are no errors. (Closes: [#35](https://github.com/k0lter/autopostgresqlbackup/issues/35))
+* Add cron and systemd examples
+* Manpage is by now built from Documentation.md using pandoc
+
+## Version 2.2
+
+* Fix manpage issue
+
 ## Version 2.1
 
+* Default configuration file /etc/default/autopostgresqlbackup is now deprecated in favor of /etc/autodbbackup.d
+* Add support for MySQL/MariaDB (using the `DBENGINE` configuration parameter).
+* Add a command line option `-c` to specify an alternate config file or directory (see [Documentation](/Documentation.md)).
+* Add manpage
+* Fix hypothetical shell injection by crafting special database names
 * Variable `OPT` (used with pg_dump) is renamed to `PGDUMP_OPTS` and a new variable `PGDUMPALL_OPTS` is available (used for dump globals with pg_dumpall) (Closes: [#12](https://github.com/k0lter/autopostgresqlbackup/issues/12))
 * Fix stderr capture and check return code while running pg_dump/pg_dumpall commands
 * Add `MIN_DUMP_SIZE` configuration variable to raise a warning when a backup file size is below this limit (Closes: [#15](https://github.com/k0lter/autopostgresqlbackup/issues/15))
+* Various bug fixes
 
 ## Version 2.0
 
@@ -22,6 +46,6 @@
 ### Removed features
 
 * It's no longer possible to dump all databases in a single file
-* Copying the lastest dump in the latest/ directory is no longer supported
-* Specifying the databases names to dump during the montly backup is no longer supported
+* Copying the latest dump in the latest/ directory is no longer supported
+* Specifying the databases names to dump during the monthly backup is no longer supported
 * It is no longer supported to send backup files by email (MAILCONTENT=files). It was anyway probably not a good idea to send backup files by email (for various reasons: file size, privacy, data leaks, etc.) but I guess that it could easily be implemented in a POSTBACKUP script if needed.

+ 255 - 73
Documentation.md

@@ -1,208 +1,390 @@
-# Documentation
+## NAME
 
-## Configuration settings
+autopostgresqlbackup -- Automated tool to make periodic backups of databases
 
-### `MAILADDR`
+## SYNOPSIS
+
+`autopostgresqlbackup [OPTIONS]`
+
+## DESCRIPTION
+
+AutoPostgreSQLBackup is a shell script (usually executed from a cron job or a
+systemd timer) designed to provide a fully automated tool to make periodic
+backups of databases.
+
+AutoPostgreSQLBackup extract databases into flat files (compressed or not,
+encrypted or not) in a daily and/or weekly and/or monthly basis.
+
+AutoPostgreSQLBackup supports multiple databases engines (PostgreSQL and MySQL
+by now).
+
+## OPTIONS
+
+`-h` displays command line help
+
+`-d` Run in debug mode (no mail sent)
+
+`-c` Configuration file or directory (default: `/etc/autodbbackup.d/`)
+
+  When a directory is used, the `*.conf` files will be processed sequentially.
+  It allows one to backup multiple databases servers with distinct settings :
+
+   - database servers with distinct engines
+   - PostgreSQL cluster with instances running on multiple ports
+
+  Note: if no configuration file or directory is passed as argument but
+  `/etc/default/autopostgresqlbackup` exists, it will be used for backward
+  compatibility.
+
+## ENCRYPTION
+
+Encryption (asymmetric) is now done with GnuPG, you just need to add the public
+key (armored or not) you want to encrypt the data to in the file pointed by the
+`ENCRYPTION_PUBLIC_KEY` configuration setting.
+
+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` configuration setting and set the `ENCRYPTION` setting
+to yes.
+
+## DECRYPTION
+
+In order to decrypt a previously encrypted database dump:
+
+`gpg --decrypt --output backup.sql.gz backup.sql.gz.enc`
+
+## OPENSSL ENCRYPTION
+
+Starting from version 2.0 encryption with OpenSSL is no longer supported as it
+was discovered[1] (but also known for quite some time[2]) that encrypting large
+files with OpenSSL silently fail[3] and that decrypting these files is close to
+be impossible[4].
+
+  * [1] https://github.com/k0lter/autopostgresqlbackup/issues/10
+  * [2] https://github.com/cytopia/mysqldump-secure/issues/21
+  * [3] https://github.com/openssl/openssl/issues/2515
+  * [4] https://github.com/imreFitos/large_smime_decrypt
+
+## CONFIGURATION
+
+### MAILADDR
 
 Email Address to send errors to. If empty errors are displayed on stdout.
 
 **default**: `root`
 
-### `SU_USERNAME`
+### REPORT_ERRORS_ONLY
+
+Send email to `MAILADDR` only if there are errors
+
+**default**: `yes`
+
+### DBENGINE
+
+Database engine
 
-By default, on Debian systems (and maybe others), only `postgres` user is allowed to access PostgreSQL databases without password.
+**default**: `postgresql`
 
-In order to dump databases we need to run pg_dump/psql commands as `postgres` with su.
+*supported values*: `postgresql` or `mysql`
 
-This setting makes possible to run backups with a substitute user using `su`. If empty, `su` usage will be disabled)
+### USERNAME
+
+Username to access the database server
+
+**default**: `""` (empty, the username to use is automatically defined depending on `DBENGINE`: `postgres` for PostgreSQL and `root` for MySQL)
+
+### SU_USERNAME
+
+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)
 
 **default**: `""` (empty, not used)
 
-### `USERNAME`
+*Only while using PostgreSQL database engine*
 
-Username to access the PostgreSQL server
+### PASSWORD
 
-**default**: `postgres`
+Password to access then Database server
 
-### `DBHOST`
+While using PostgreSQL database engine, in order to use a password to connect
+to database create a file `~/.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.
+
+While using MySQL database engine, if password is not set mysqldump will try
+to read credentials from `~/.my.cnf` if file exists.
+
+**default**: `""` (empty)
 
-Host name (or IP address) of PostgreSQL server. Use `localhost` for socket connection or `127.0.0.1` to force TCP connection.
+### DBHOST
+
+Host name (or IP address) of database server. Use `localhost` for socket
+connection or `127.0.0.1` to force TCP connection.
 
 **default**: `localhost`
 
-### `DBPORT`
+### DBPORT
+
+Port of database server.
 
-Port of PostgreSQL server. It is also used if `${DBHOST}` is `localhost` (socket connection) as socket name contains port.
+While using PostgreSQL database engine, it is also used if `DBHOST` is
+`localhost` (socket connection) as socket name contains port.
 
-**default**: `5432`
+**default**: `""` (empty, the port to use is automatically defined depending on `DBENGINE`: `5432` for PostgreSQL and `3306` for MySQL)
 
-### `DBNAMES`
+### DBNAMES
 
-List of database(s) names(s) to backup
+Explicit 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 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"`).
+If the database you want to backup has a space in the name replace the space by a `%20` (`"data base"` will become `"data%20base"`).
 
 **default**: `all`
 
-**example**: `"users pages user%data"`
+**example**: `"users pages user%20data"`
 
-### `DBEXCLUDE`
+### DBEXCLUDE
 
 List of databases to exclude if `DBNAMES` is not set to `all`.
 
 **default** : `""` (empty)
 
-**example**: `"pages user%data"`
+**example**: `"pages user%20data"`
 
-### `GLOBALS_OBJECTS` 
+### GLOBALS_OBJECTS
 
-Pseudo database name used to dump global objects (users, roles, tablespaces)
+Virtual database name used to backup global objects (users, roles, tablespaces).
 
 **default**: `postgres_globals`
 
-### `BACKUPDIR` 
+*Only while using PostgreSQL database engine*
+
+### BACKUPDIR
 
 Backup directory
 
 **default**: `/var/backups`
 
-### `CREATE_DATABASE`
+### CREATE_DATABASE
 
-Include CREATE DATABASE in backups?
+Include or not `CREATE DATABASE` statements in dabatbases backups.
 
 **default**: `yes`
 
-### `DOWEEKLY`
+*supported values*: `yes` or `no`
 
-Which day do you want weekly backups? (1 to 7 where 1 is Monday)
+### DOWEEKLY
 
-When set to 0, weekly backups are disabled
+Which day do you want weekly backups? (1 to 7 where 1 is Monday).
+
+When set to 0, weekly backups are disabled.
 
 **default**: `7` (Sunday)
 
-### `DOMONTHLY`
+### DOMONTHLY
 
 Which day do you want monthly backups?
 
-When set to 0, monthly backups are disabled
+When set to 0, monthly backups are disabled.
 
 **default**: `1` (first day of the month)
 
-### `BRDAILY`
+### BRDAILY
 
 Backup retention count for daily backups, older backups are removed.
 
 **default**: `14` (14 days)
 
-### `BRWEEKLY`
+### BRWEEKLY
 
 Backup retention count for weekly backups, older backups are removed.
 
 **default**: `5` (5 weeks)
 
-### `BRMONTHLY`
+### BRMONTHLY
 
 Backup retention count for monthly backups, older backups are removed.
 
 **default**: `12` (12 months)
 
-### `COMP` 
+### COMP
 
-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).
+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.
 
 **default**: `gzip`
 
-### `COMP_OPTS`
+### COMP_OPTS
 
 Compression tools options to be used with `COMP`
 
 **default**: `""` (empty)
 
-**example**:
-```
-COMP="zstd"
-COMP_OPTS="-f -c"
-```
+**example**: `COMP="zstd" COMP_OPTS="-f -c"`
+
+### PGDUMP
+
+pg_dump path (relative if present in `${PATH}` or absolute)
+
+**default**: `""` (if empty `pg_dump` will be used)
+
+*Only while using PostgreSQL database engine*
+
+### PGDUMPALL
+
+pg_dumpall path (relative if present in `${PATH}` or absolute)
+
+**default**: `""` (if empty `pg_dumpall` will be used)
 
-### `PGDUMP_OPTS`
+*Only while using PostgreSQL database engine*
 
-Options string for use with pg_dump (see [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) manual page).
+### PGDUMP_OPTS
+
+Options string for use with pg_dump (see
+[pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) manual
+page).
 
 **default**: `""` (empty)
 
-### `PGDUMPALL_OPTS`
+*Only while using PostgreSQL database engine*
+
+### PGDUMPALL_OPTS
 
-Options string for use with pg_dumpall (see [pg_dumpall](https://www.postgresql.org/docs/current/app-pg-dumpall.html) manual page).
+Options string for use with pg_dumpall (see
+[pg_dumpall](https://www.postgresql.org/docs/current/app-pg-dumpall.html)
+manual page).
 
 **default**: `""` (empty)
 
-### `EXT`
+*Only while using PostgreSQL database engine*
+
+### MY
+
+mysql path (relative if present in `${PATH}` or absolute)
+
+**default**: `""` (if empty `mysql` will be used)
+
+*Only while using MySQL database engine*
+
+### MYDUMP
+
+mysqldump path (relative if present in `${PATH}` or absolute)
+
+**default**: `""` (if empty `mysqldump` will be used)
+
+*Only while using MySQL database engine*
+
+### MYDUMP_OPTS
+
+Options string for use with mysqldump (see
+[mysqldump](https://dev.mysql.com/doc/refman/8.3/en/mysqldump.html) manual
+page).
+
+**default**: `""` (empty)
+
+*Only while using MySQL database engine*
+
+### EXT
 
 Backup files extension
 
 **default**: `sql`
 
-### `PERM`
+### PERM
 
 Backup files permission
 
 **default**: `600`
 
-### `MIN_DUMP_SIZE`
+### MIN_DUMP_SIZE
 
-Minimum size (in bytes) for a dump/file (compressed or not). File size below this limit will raise an warning.
+Minimum size (in bytes) for a dump/file (compressed or not). File size below
+this limit will raise a warning.
 
 **default**: `256`
 
-### `ENCRYPTION`
+### ENCRYPTION
 
 Enable encryption (asymmetric) with GnuPG.
 
 **default**: `no`
 
-### `ENCRYPTION_PUBLIC_KEY`
+*supported values*: `yes` or `no`
+
+### ENCRYPTION_PUBLIC_KEY
 
 Encryption public key (path to the key)
 
 **default**: `""` (empty)
 
-#### 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_SUFFIX
 
-### `ENCRYPTION_SUFFIX`
-
-Suffix for encyrpted files
+Suffix for encrypted files
 
 **default**: `.enc`
 
-### `PREBACKUP`
+### PREBACKUP
 
 Command or script to execute before backups
 
 **default**: `""` (empty, not used)
 
-### `POSTBACKUP`
+### POSTBACKUP
 
 Command or script to execute after backups
 
 **default**: `""` (empty, not used)
 
-## Password settings
+## AUTHORS
+
+Originally developed by Aaron Axelsen with Friedrich Lobenstock contributions.
+
+Almost fully rewritten by Emmanuel Bouthenot (version 2.0 and higher).
+
+## LICENSE AND COPYRIGHT
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+details.
+
+## CONTRIBUTIONS
+
+Contributions are welcome on the project page:
+https://github.com/k0lter/autopostgresqlbackup/pulls
+
+## BUGS
+
+Bug reports are welcome on the project page:
+https://github.com/k0lter/autopostgresqlbackup/issues
 
-In order to use a password to connect to database create a file `${HOME}/.pgpass` containing a line like this
+## SEE ALSO
 
-```
-hostname:*:*:dbuser:dbpass
-```
-replace `hostname` with the value of `${DBHOST}`, `dbuser` with the value of `${USERNAME}` and `dbpass` with the password.
+`pg_dump`(1), `pg_dumpall`(1), `mysqldump`(1) and the project page https://github.com/k0lter/autopostgresqlbackup/

+ 16 - 0
Makefile

@@ -0,0 +1,16 @@
+NAME=autopostgresqlbackup
+SECTION=8
+
+all: man
+
+man: ${NAME}.${SECTION}
+
+${NAME}.${SECTION}: Documentation.md
+	pandoc \
+		--standalone \
+		--to man \
+		Documentation.md \
+		-o ${NAME}.${SECTION}
+
+clean:
+	rm -f ${NAME}.${SECTION}

+ 10 - 23
Readme.md

@@ -1,9 +1,11 @@
 # AutoPostgreSQLBackup
 
-AutoPostgreSQLBackup is a shell script (usually executed from a cron job) designed to provide a fully automated tool to make periodic backups of PostgreSQL databases.
+AutoPostgreSQLBackup is a shell script (usually executed from a cron job) designed to provide a fully automated tool to make periodic backups of databases (supports PostgreSQL and MySQL/MariaDB).
 
 AutoPostgreSQLBackup extract databases into flat files in a daily, weekly or monthly basis.
 
+Version 2.2 adds support for MySQL/MariaDB.
+
 Version 2.0 is a full rewrite.
 
 It supports:
@@ -21,42 +23,27 @@ On Debian (or derived):
 
 Install: `apt install autopostgresqlbackup`
 
-If the default options are not suitable for you, change them: `${EDITOR} /etc/default/autopostgresqlbackup`
-
 That's it!
 
 ## Documentation
 
 See [the documentation](/Documentation.md).
 
+## Manual page
+
+Man page is build from [the documentation](/Documentation.md) using pandoc using the Makefile.
+
+`make man`
+
 ## History
 
  * 2023: Almost full rewrite with better error handling and new features (see Changelog.md for details)
- * 2019: Creation of a fork/standelone project on Github (https://github.com/k0lter/autopostgresqlbackup)
+ * 2019: Creation of a fork/standalone project on GitHub (https://github.com/k0lter/autopostgresqlbackup)
  * Since 2011: Various patches (fixes and new features) were added in the Debian package
  * 2011: AutoPostgreSQLBackup was included in Debian
  * 2005: AutoPostgreSQLBackup was written by Aaron Axelsen (with some contributions of Friedrich Lobenstock)
    * Project webpage was http://autopgsqlbackup.frozenpc.net/ (offline)
 
-## Encryption
-
-Encryption (asymmetric) is now done with GnuPG, you just need to add the
-public key (armored or not) you want to encrypt the data to in the file pointed by the `${ENCRYPTION_PUBLIC_KEY}` configuration setting.
-
-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}` configuration setting and set the `${ENCRYPTION}` setting to `yes`.
-
-## OpenSSL Encryption
-
-Starting from version 2.0 encryption with OpenSSL is no longer supported as [it was discovered](https://github.com/k0lter/autopostgresqlbackup/issues/10) (but also [known for quite some time](https://github.com/cytopia/mysqldump-secure/issues/21)) that encrypting large files with [OpenSSL silently fail](https://github.com/openssl/openssl/issues/2515) and that decrypting these files is [close to be impossible](https://github.com/imreFitos/large_smime_decrypt).
-
 ## Authors
 
  * Emmanuel Bouthenot (Current maintainer)

+ 499 - 209
autopostgresqlbackup

@@ -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>
@@ -18,100 +17,109 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+# along with this program; if not, see
+# <https://www.gnu.org/licenses/>.
+# }}}
+
+# {{{ Constants
+PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/postgres/bin:/usr/local/pgsql/bin
+HOMEPAGE="https://github.com/k0lter/autopostgresqlbackup"
+NAME="AutoPostgreSQLBackup"         # Script name
+VERSION="2.5"                       # Version Number
+DATE="$(date '+%Y-%m-%d_%Hh%Mm')"   # Datestamp e.g 2002-09-21
+DNOW="$(date '+%u')"                # Day number of the week 1 to 7 where 1 represents Monday
+DNOM="$(date '+%d')"                # Date of the Month e.g. 27
 # }}}
 
 # {{{ Variables
 
+# Configuration file or directory
+CONFIG="/etc/autodbbackup.d"
+
+# Legacy configuration file path (for backward compatibility)
+CONFIG_COMPAT="/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=
+# Send email only if there are errors
+REPORT_ERRORS_ONLY="yes"
 
-# Username to access the PostgreSQL server
-USERNAME="postgres"
+# Database engines supported: postgresql, mysql
+DBENGINE="postgresql"
 
-# 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.
+# Only while using PostgreSQL DB Engine
+SU_USERNAME=""
+
+# 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).
+# pg_dump path (pg_dump will be used if empty)
+PGDUMP=
+
+# pg_dumpall path (pg_dumpall will be used if empty)
+PGDUMPALL=
+
+# Options string for use with all_dump (see pg_dump manual page).
 PGDUMP_OPTS=
 
 # Options string for use with pg_dumpall (see pg_dumpall manual page).
 PGDUMPALL_OPTS=
 
+# mysql path (mysql will be used if empty)
+MY=
+
+# mysqldump path (mysqldump will be used if empty)
+MYDUMP=
+
+# Options string for use with mysqldump (see myqldump manual page).
+MYDUMP_OPTS=
+
 # Backup files extension
 EXT="sql"
 
@@ -119,25 +127,15 @@ 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
+# Suffix for encrypted files
 ENCRYPTION_SUFFIX=".enc"
 
 # Command or script to execute before backups
@@ -145,36 +143,6 @@ PREBACKUP=
 
 # Command or script to execute after backups
 POSTBACKUP=
-# }}}
-
-# {{{ OS Specific
-if [ -f /etc/default/autopostgresqlbackup ]; then
-    # shellcheck source=/dev/null
-    . /etc/default/autopostgresqlbackup
-fi
-# }}}
-
-# {{{ Defaults
-PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/postgres/bin:/usr/local/pgsql/bin
-HOMEPAGE="https://github.com/k0lter/autopostgresqlbackup"
-NAME="AutoPostgreSQLBackup"         # Script name
-VERSION="2.0"                       # Version Number
-DATE="$(date '+%Y-%m-%d_%Hh%Mm')"   # Datestamp e.g 2002-09-21
-DNOW="$(date '+%u')"                # Day number of the week 1 to 7 where 1 represents Monday
-DNOM="$(date '+%d')"                # Date of the Month e.g. 27
-LOG_DIR="${BACKUPDIR}"              # Directory where the main log is saved
-# Fix day of month (left padding with 0)
-DOMONTHLY="$(printf '%.2i' "${DOMONTHLY}")"
-
-# Using a shared memory filesystem (if available) to avoid
-# issues when there is no left space on backup storage
-if [ -w "/dev/shm" ]; then
-    LOG_DIR="/dev/shm"
-fi
-
-LOG_PREFIX="${LOG_DIR}/${NAME}_${DBHOST//\//_}-$(date '+%Y-%m-%d_%Hh%Mm')"
-LOG_FILE="${LOG_PREFIX}.log"
-LOG_REPORT="${LOG_PREFIX}.report"
 
 # Debug mode
 DEBUG="no"
@@ -182,22 +150,8 @@ DEBUG="no"
 # Encryption prerequisites
 GPG_HOMEDIR=
 
-# Create required directories
-if [ ! -e "${BACKUPDIR}" ]; then         # Check Backup Directory exists.
-    mkdir -p "${BACKUPDIR}"
-fi
-
-if [ ! -e "${BACKUPDIR}/daily" ]; then   # Check Daily Directory exists.
-    mkdir -p "${BACKUPDIR}/daily"
-fi
-
-if [ ! -e "${BACKUPDIR}/weekly" ]; then  # Check Weekly Directory exists.
-    mkdir -p "${BACKUPDIR}/weekly"
-fi
-
-if [ ! -e "${BACKUPDIR}/monthly" ]; then # Check Monthly Directory exists.
-    mkdir -p "${BACKUPDIR}/monthly"
-fi
+# Database connection arguments
+CONN_ARGS=()
 
 # Hostname
 HOSTNAME="$(uname -n)"
@@ -205,12 +159,8 @@ if [[ "${HOSTNAME}" != *.* ]]; then
     HOSTNAME="$(hostname --fqdn)"
 fi
 
-HOST="${DBHOST}:${DBPORT}"
-if [ "${DBHOST}" = "localhost" ]; then
-    HOST="${HOSTNAME}:${DBPORT} (socket)"
-fi
-
-CONN_ARGS=()
+# Return Code
+RC=0
 # }}}
 
 # {{{ log{,ger,_info,_debug,_warn,_error}()
@@ -273,6 +223,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")"
@@ -302,20 +292,30 @@ 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}")
+    if [ -z "${PGDUMP}" ]; then
+        PGDUMP="pg_dump"
+    fi
+    if [ -z "${PGDUMPALL}" ]; then
+        PGDUMPALL="pg_dumpall"
     fi
 }
 # }}}
 
-# {{{ pgdb_list()
-pgdb_list () {
+# {{{ postgresqldb_list()
+postgresqldb_list () {
     local cmd_prog cmd_args raw_dblist dblist dbexcl databases
 
     cmd_prog="psql"
@@ -337,8 +337,9 @@ pgdb_list () {
     )
 
     read -r -a dblist <<< "$(
-        printf "%s" "${raw_dblist}" | \
+        printf "%s\n" "${raw_dblist}" | \
             sed -E -n 's/^([^:]+):.+$/\1/p' | \
+            arg_encode | \
             tr '\n' ' '
     )"
     log_debug "Automatically found databases: ${dblist[*]}"
@@ -363,8 +364,8 @@ pgdb_list () {
 }
 # }}}
 
-# {{{ pgdb_dump()
-pgdb_dump () {
+# {{{ postgresqldb_dump()
+postgresqldb_dump () {
     local db_name cmd_prog cmd_args pg_args
 
     db_name="${1}"
@@ -383,12 +384,16 @@ pgdb_dump () {
     fi
 
     if [ "${db_name}" = "${GLOBALS_OBJECTS}" ]; then
-        cmd_prog="pg_dumpall"
+        cmd_prog="${PGDUMPALL}"
         cmd_args=(--globals-only)
         pg_args=("${PGDUMPALL_ARGS[@]}")
     else
-        cmd_prog="pg_dump"
-        cmd_args=("${db_name}")
+        cmd_prog="${PGDUMP}"
+        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)
@@ -413,6 +418,189 @@ 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
+    if [ -z "${MY}" ]; then
+        MY="mysql"
+    fi
+    if [ -z "${MYDUMP}" ]; then
+        MYDUMP="mysqldump"
+    fi
+}
+# }}}
+
+# {{{ mysqldb_list()
+mysqldb_list () {
+    local cmd_prog cmd_args raw_dblist dblist dbexcl databases
+
+    cmd_prog="${MY}"
+    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\n" "${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="${MYDUMP}"
+    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
+}
+# }}}
+
+# {{{ db_purge()
+db_purge() {
+    local dumpdir db when count line
+
+    dumpdir="${1}"
+    db="${2}"
+    when="${3}"
+    count="${4}"
+
+    # Since version >= 2.0 the dump filename no longer contains the week number
+    # or the abbreviated month name so in order to be sure to remove the older
+    # dumps we need to sort the filename on the datetime part (YYYY-MM-DD_HHhMMm)
+
+    log_info "Rotating ${count} ${when} backups..."
+    log_debug "Looking for '${db}_*' in '${dumpdir}/${when}/${db}'"
+    find "${dumpdir}/${when}/${db}/" -name "${db}_*" | \
+        sed -E 's/(^.+([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}h[0-9]{2}m).*$)/\2 \1/' | \
+        sort -r | \
+        sed -E -n 's/\S+ //p' | \
+        tail -n "+${count}" | \
+        xargs -L1 rm -fv | \
+        while IFS= read -r line ; do
+            log_info "${line}"
+        done
+}
+# }}}
+
 # {{{ dump()
 dump() {
     local db_name dump_file comp_ext
@@ -445,16 +633,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
@@ -472,30 +660,118 @@ dump() {
 }
 # }}}
 
-# {{{ cleanup()
+# {{{ setup()
+setup() {
+    # Using a shared memory filesystem (if available) to avoid
+    # issues when there is no left space on backup storage
+    if [ -w "/dev/shm" ]; then
+        LOG_DIR="/dev/shm"
+    fi
+
+    LOG_PREFIX="${LOG_DIR}/${NAME}_${DBHOST//\//_}-$(date '+%Y-%m-%d_%Hh%Mm')"
+    LOG_FILE="${LOG_PREFIX}.log"
+    LOG_REPORT="${LOG_PREFIX}.report"
+
+    HOST="${DBHOST}:${DBPORT}"
+    if [ "${DBHOST}" = "localhost" ]; then
+        HOST="${HOSTNAME}:${DBPORT} (socket)"
+    fi
+}
+# }}}
+
+# {{{ prepare_backupdir()
+prepare_backupdir() {
+    # Create required directories
+    if [ ! -e "${BACKUPDIR}" ]; then         # Check Backup Directory exists.
+        mkdir -p "${BACKUPDIR}"
+    fi
+
+    if [ ! -e "${BACKUPDIR}/daily" ]; then   # Check Daily Directory exists.
+        mkdir -p "${BACKUPDIR}/daily"
+    fi
+
+    if [ ! -e "${BACKUPDIR}/weekly" ]; then  # Check Weekly Directory exists.
+        mkdir -p "${BACKUPDIR}/weekly"
+    fi
+
+    if [ ! -e "${BACKUPDIR}/monthly" ]; then # Check Monthly Directory exists.
+        mkdir -p "${BACKUPDIR}/monthly"
+    fi
+}
+# }}}
+
+#  {{{ cleanup()
 cleanup() {
-    local dumpdir db when count line
+    # Cleanup GnuPG home dir
+    if [ -d "${GPG_HOMEDIR}" ]; then
+        rm -rf "${GPG_HOMEDIR}"
+    fi
 
-    dumpdir="${1}"
-    db="${2}"
-    when="${3}"
-    count="${4}"
+    # Clean up log files
+    rm -f "${LOG_FILE}" "${LOG_REPORT}"
+}
+# }}}
 
-    # Since version >= 2.0 the dump filename no longer contains the week number
-    # or the abbreviated month name so in order to be sure to remove the older
-    # dumps we need to sort the filename on the datetime part (YYYY-MM-DD_HHhMMm)
+# {{{ setup_io()
+setup_io() {
+    exec 6>&1           # Link file descriptor #6 with stdout.
+                        # Saves stdout.
+    exec 7>&2           # Link file descriptor #7 with stderr.
+                        # Saves stderr.
+    exec >  >( logger "out")
+    exec 2> >( logger "err")
+}
+# }}}
 
-    log_info "Rotating ${count} ${when} backups..."
-    log_debug "Looking for '${db}_*' in '${dumpdir}/${when}/${db}'"
-    find "${dumpdir}/${when}/${db}/" -name "${db}_*" | \
-        sed -E 's/(^.+([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}h[0-9]{2}m).*$)/\2 \1/' | \
-        sort -r | \
-        sed -E -n 's/\S+ //p' | \
-        tail -n "+${count}" | \
-        xargs -L1 rm -fv | \
-        while IFS= read -r line ; do
-            log_info "${line}"
-        done
+#  {{{ cleanup_io()
+cleanup_io() {
+    exec 1>&6 6>&-      # Restore stdout and close file descriptor #6.
+    exec 2>&7 7>&-      # Restore stdout and close file descriptor #7.
+}
+# }}}
+
+# {{{ reporting()
+reporting() {
+    local exitcode subject
+
+    exitcode=0
+    if grep -q '^err|' "${LOG_FILE}"; then
+        exitcode=1
+    fi
+
+    if [[ ( "${DEBUG}" = "no" ) && ( ${exitcode} = 1 || "${REPORT_ERRORS_ONLY}" = "no" ) ]]; then
+        (
+            if [ ${exitcode} = 1 ]; then
+                printf "*Errors/Warnings* (below) reported during backup on *%s*:\n\n" "${HOST}"
+                grep '^err|' "${LOG_FILE}" | cut -d '|' -f 3- | \
+                while IFS= read -r line ; do
+                    printf "  | %s\n" "${line}"
+                done
+            fi
+            printf "\n\nFull backup log follows:\n\n"
+            grep -v '^...|debug|' "${LOG_FILE}" | \
+            while IFS="|" read -r fd level line ; do
+                if [ -n "${level}" ]; then
+                    printf "%8s| %s\n" "*${level}*" "${line}"
+                else
+                    printf "%8s| %s\n" "" "${line}"
+                fi
+            done
+            printf "\nFor more information, try to run %s in debug mode, see \`%s -h\`\n" "${NAME}" "$(basename "$0")"
+        ) > "${LOG_REPORT}"
+
+        if [ -n "${MAILADDR}" ]; then
+            subject="report"
+            if [ ${exitcode} = 1 ]; then
+                subject="issues"
+            fi
+            mail -s "${NAME} ${subject} on ${HOSTNAME}" "${MAILADDR}" < "${LOG_REPORT}"
+        else
+            cat "${LOG_REPORT}"
+        fi
+    fi
+
+    return ${exitcode}
 }
 # }}}
 
@@ -506,18 +782,21 @@ USAGE: $(basename "$0") [OPTIONS]
 
 ${NAME} ${VERSION}
 
-A fully automated tool to make periodic backups of PostgreSQL databases.
+A fully automated tool to make periodic backups databases (supports PostgreSQL and MySQL/MariaDB).
 
 Options:
     -h  Shows this help
     -d  Run in debug mode (no mail sent)
+    -c  Configuration file or directory (default: ${CONFIG})
+        Note: if ${CONFIG} file or directory does not exists
+        but ${CONFIG_COMPAT} exists, it will be used
+        for backward compatibility.
 EOH
 }
 # }}}
 
 # {{{ Process command line arguments
-
-while getopts "hd" OPTION ; do
+while getopts "hdc:" OPTION ; do
     case "${OPTION}" in
         h)
             usage
@@ -526,20 +805,66 @@ while getopts "hd" OPTION ; do
         d)
             DEBUG="yes"
             ;;
+        c)
+            CONFIG="${OPTARG}"
+            CONFIG_COMPAT=
+            ;;
         *)
-            printf "Try \`%s -h\` to check the command line arguments\n" "$(basename "$0")" >&2
+            printf "Try \`%s -h\` to check the command line arguments\n" "${NAME}" >&2
             exit 1
     esac
 done
 # }}}
 
 # {{{ I/O redirection(s) for logging
-exec 6>&1           # Link file descriptor #6 with stdout.
-                    # Saves stdout.
-exec 7>&2           # Link file descriptor #7 with stderr.
-                    # Saves stderr.
-exec >  >( logger "out")
-exec 2> >( logger "err")
+setup_io
+# }}}
+
+# {{{ Setup runtime settings
+setup
+# }}}
+
+# {{{ Config file loading
+CONFIG_N=0
+if [ -d "${CONFIG}" ]; then
+    CONFIG_N=$(find "${CONFIG}" -type f -iname '*.conf' | wc -l)
+fi
+
+if [ -f "${CONFIG_COMPAT}" ]; then
+    log_debug "Loading config '${CONFIG_COMPAT}' (for backward compatibility)"
+    # shellcheck source=/dev/null
+    . "${CONFIG_COMPAT}"
+    setup
+elif [ "${CONFIG_N}" -gt 0 ]; then
+    CMD="$(readlink -f "${0}")"
+    CMD_ARGS=()
+
+    if [ "${DEBUG}" = "yes" ]; then
+        CMD_ARGS+=(-d)
+    fi
+
+    cleanup_io
+    cleanup
+
+    find "${CONFIG}" -type f -iname '*.conf' -print0 | \
+        xargs -0 -L1 "${CMD}" "${CMD_ARGS[@]}" -c
+    exit $?
+elif [ -f "${CONFIG}" ]; then
+    log_debug "Loading config '${CONFIG}'"
+    # shellcheck source=/dev/null
+    . "${CONFIG}"
+    setup
+else
+    log_error "${NAME}: config file or directory '${CONFIG}' does not exists or directory '${CONFIG}' does not contains any configuration files."
+    reporting
+    cleanup_io
+    cleanup
+    exit 1
+fi
+# }}}
+
+# {{{ Create backup directories
+prepare_backupdir
 # }}}
 
 # {{{ PreBackup
@@ -556,7 +881,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
@@ -601,26 +926,27 @@ else
     rotate="${BRDAILY}"
 fi
 
+db_init
+
 # If backing up all DBs on the server
 if [ "${DBNAMES}" = "all" ]; then
-    DBNAMES="$(pgdb_list)"
+    DBNAMES="$(db_list)"
 fi
 
-pgdb_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}"
+    db_purge "${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)"
@@ -640,54 +966,18 @@ fi
 # }}}
 
 # {{{ cleanup I/O redirections
-exec 1>&6 6>&-      # Restore stdout and close file descriptor #6.
-exec 2>&7 7>&-      # Restore stdout and close file descriptor #7.
+cleanup_io
 # }}}
 
 # {{{ Reporting
-if grep -q '^err|' "${LOG_FILE}"; then
-    rc=1
-else
-    rc=0
-fi
-
-if [ "${DEBUG}" = "no" ] && [ ${rc} = 1 ]; then
-    (
-        printf "*Errors/Warnings* (below) reported during backup on *%s*:\n\n" "${HOST}"
-        grep '^err|' "${LOG_FILE}" | cut -d '|' -f 3- | \
-        while IFS= read -r line ; do
-            printf "  | %s\n" "${line}"
-        done
-        printf "\n\nFull backup log follows:\n\n"
-        grep -v '^...|debug|' "${LOG_FILE}" | \
-        while IFS="|" read -r fd level line ; do
-            if [ -n "${level}" ]; then
-                printf "%8s| %s\n" "*${level}*" "${line}"
-            else
-                printf "%8s| %s\n" "" "${line}"
-            fi
-        done
-        printf "\nFor more information, try to run %s in debug mode, see \`%s -h\`\n" "${NAME}" "$(basename "$0")"
-    ) > "${LOG_REPORT}"
-
-    if [ -n "${MAILADDR}" ]; then
-        mail -s "${NAME} issues on ${HOSTNAME}" "${MAILADDR}" < "${LOG_REPORT}"
-    else
-        cat "${LOG_REPORT}"
-    fi
-fi
+reporting
+RC=${?}
 # }}}
 
 # {{{ Cleanup and exit()
-# Cleanup GnuPG home dir
-if [ -d "${GPG_HOMEDIR}" ]; then
-    rm -rf "${GPG_HOMEDIR}"
-fi
-
-# Clean up log files
-rm -f "${LOG_FILE}" "${LOG_REPORT}"
+cleanup
 
-exit ${rc}
+exit ${RC}
 # }}}
 
 # vim: foldmethod=marker foldlevel=0 foldenable

+ 29 - 0
examples/mariadb.conf

@@ -0,0 +1,29 @@
+
+# DB engine
+DBENGINE="mysql"
+
+# Backup directory
+BACKUPDIR="/var/backups/autodbbackup/mariadb"
+
+# Include CREATE DATABASE in backups?
+CREATE_DATABASE="no"
+
+# Which day do you want weekly backups? (1 to 7 where 1 is Monday)
+# When set to 0, weekly backups are disabled
+DOWEEKLY=0
+
+# Which day do you want monthly backups?
+# When set to 0, monthly backups are disabled
+DOMONTHLY=0
+
+# Backup retention count for daily backups, older backups are removed.
+BRDAILY=7
+
+# Compression tool
+COMP="zstd"
+
+# mariadb path (like "mysql" for MySQL)
+MY="mariadb"
+
+# mariadb-dump path (like "mysqldump" for MySQL)
+MYDUMP="mariadb-dump"

+ 24 - 0
examples/mysql.conf

@@ -0,0 +1,24 @@
+
+# DB engine
+DBENGINE="mysql"
+
+# Backup directory
+BACKUPDIR="/var/backups/autodbbackup/${DBENGINE}"
+
+# Include CREATE DATABASE in backups?
+CREATE_DATABASE="no"
+
+# Which day do you want weekly backups? (1 to 7 where 1 is Monday)
+# When set to 0, weekly backups are disabled
+DOWEEKLY=0
+
+# Which day do you want monthly backups?
+# When set to 0, monthly backups are disabled
+DOMONTHLY=0
+
+# Backup retention count for daily backups, older backups are removed.
+BRDAILY=7
+
+# Compression tool
+COMP="zstd"
+

+ 26 - 0
examples/postgresql.conf

@@ -0,0 +1,26 @@
+
+# DB engine
+DBENGINE="postgresql"
+
+# Substitute user to run dump commands
+SU_USERNAME="postgres"
+
+# Backup directory
+BACKUPDIR="/var/backups/autodbbackup/${DBENGINE}"
+
+# Include CREATE DATABASE in backups?
+CREATE_DATABASE="no"
+
+# Which day do you want weekly backups? (1 to 7 where 1 is Monday)
+# When set to 0, weekly backups are disabled
+DOWEEKLY=0
+
+# Which day do you want monthly backups?
+# When set to 0, monthly backups are disabled
+DOMONTHLY=0
+
+# Backup retention count for daily backups, older backups are removed.
+BRDAILY=7
+
+# Compression tool
+COMP="zstd"

+ 15 - 0
services/cron/autopostgresqlbackup

@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# autopostgresqlbackup daily cron task
+#
+
+if [ -d /run/systemd/system ]; then
+    # Skip in favour of systemd timer.
+    exit 0
+fi
+
+if [ -x /usr/sbin/autopostgresqlbackup ]; then
+	/usr/sbin/autopostgresqlbackup
+fi
+
+exit 0

+ 11 - 0
services/systemd/autopostgresqlbackup.service

@@ -0,0 +1,11 @@
+[Unit]
+Description=autopostgresqlbackup daily service
+Documentation=man:autopostgresqlbackup(8)
+After=postgresql.target mysql.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/sbin/autopostgresqlbackup
+ProtectHome=true
+ProtectSystem=full
+PrivateTmp=true

+ 9 - 0
services/systemd/autopostgresqlbackup.timer

@@ -0,0 +1,9 @@
+[Unit]
+Description=autopostgresqlbackup daily timer
+
+[Timer]
+OnCalendar=daily
+Persistent=true
+
+[Install]
+WantedBy=timers.target