Initial commit
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..14a7044
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,6 @@
+mysql formula
+=============
+
+0.0.1 (2015-08-03)
+
+- Initial formula setup
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8e80b12
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2014-2015 tcp cloud a. s.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..472d0db
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,252 @@
+
+=====
+MySQL
+=====
+
+MySQL is the world's second most widely used open-source relational database management system (RDBMS).
+
+Sample pillars
+==============
+
+Standalone servers
+------------------
+
+Standalone MySQL server
+
+.. code-block:: yaml
+
+ mysql:
+ server:
+ enabled: true
+ version: '5.5'
+ admin:
+ user: root
+ password: pass
+ bind:
+ address: '127.0.0.1'
+ port: 3306
+ database:
+ name:
+ encoding: 'utf8'
+ users:
+ - name: 'username'
+ password: 'password'
+ host: 'localhost'
+ rights: 'all privileges'
+
+MySQL replication master with SSL
+
+.. code-block:: yaml
+
+ mysql:
+ server:
+ enabled: true
+ version: 5.5
+ replication:
+ role: master
+ ssl:
+ enabled: true
+ authority: Org_CA
+ certificate: name_of_service
+ admin:
+ user: root
+ password: pass
+ bind:
+ address: '127.0.0.1'
+ port: 3306
+
+MySQL replication slave with SSL
+
+.. code-block:: yaml
+
+ mysql:
+ server:
+ enabled: true
+ version: '5.5'
+ replication:
+ role: slave
+ master: master.salt.id
+ ssl:
+ enabled: true
+ authority: Org_CA
+ certificate: name_of_service
+ client_certificate: name_of_client_cert
+ admin:
+ user: root
+ password: pass
+ bind:
+ address: '127.0.0.1'
+ port: 3306
+
+Tuned up MySQL server
+
+.. code-block:: yaml
+
+ mysql:
+ server:
+ enabled: true
+ version: '5.5'
+ admin:
+ user: root
+ password: pass
+ bind:
+ address: '127.0.0.1'
+ port: 3306
+ key_buffer: 250M
+ max_allowed_packet: 32M
+ max_connections: 1000
+ thread_stack: 512K
+ thread_cache_size: 64
+ query_cache_limit: 16M
+ query_cache_size: 96M
+ force_encoding: utf8
+ database:
+ name:
+ encoding: 'utf8'
+ users:
+ - name: 'username'
+ password: 'password'
+ host: 'localhost'
+ rights: 'all privileges'
+
+MySQL galera cluster
+--------------------
+
+MySQL Galera cluster is configured for ring connection between 3 nodes. Each node should have just one member.
+
+Galera initial server (master)
+
+.. code-block:: yaml
+
+ mysql:
+ cluster:
+ enabled: true
+ name: openstack
+ role:master
+ bind:
+ address: 192.168.0.1
+ members:
+ - host: 192.168.0.1
+ port: 4567
+ user:
+ name: wsrep_sst
+ password: password
+ server:
+ enabled: true
+ version: 5.5
+ admin:
+ user: root
+ password: pass
+ bind:
+ address: 192.168.0.1
+ database:
+ name:
+ encoding: 'utf8'
+ users:
+ - name: 'username'
+ password: 'password'
+ host: 'localhost'
+ rights: 'all privileges'
+
+Usage
+=====
+
+MySQL Galera check sripts
+
+.. code-block:: bash
+
+ mysql> SHOW STATUS LIKE 'wsrep%';
+
+ mysql> SHOW STATUS LIKE 'wsrep_cluster_size' ;"
+
+Galera monitoring command, performed from extra server
+
+.. code-block:: bash
+
+ garbd -a gcomm://ipaddrofone:4567 -g my_wsrep_cluster -l /tmp/1.out -d
+
+1. salt-call state.sls mysql
+2. Comment everything starting wsrep* (wsrep_provider, wsrep_cluster, wsrep_sst)
+3. service mysql start
+4. run on each node mysql_secure_install and filling root password.
+
+.. code-block:: bash
+
+ Enter current password for root (enter for none):
+ OK, successfully used password, moving on...
+
+ Setting the root password ensures that nobody can log into the MySQL
+ root user without the proper authorisation.
+
+ Set root password? [Y/n] y
+ New password:
+ Re-enter new password:
+ Password updated successfully!
+ Reloading privilege tables..
+ ... Success!
+
+ By default, a MySQL installation has an anonymous user, allowing anyone
+ to log into MySQL without having to have a user account created for
+ them. This is intended only for testing, and to make the installation
+ go a bit smoother. You should remove them before moving into a
+ production environment.
+
+ Remove anonymous users? [Y/n] y
+ ... Success!
+
+ Normally, root should only be allowed to connect from 'localhost'. This
+ ensures that someone cannot guess at the root password from the network.
+
+ Disallow root login remotely? [Y/n] n
+ ... skipping.
+
+ By default, MySQL comes with a database named 'test' that anyone can
+ access. This is also intended only for testing, and should be removed
+ before moving into a production environment.
+
+ Remove test database and access to it? [Y/n] y
+ - Dropping test database...
+ ... Success!
+ - Removing privileges on test database...
+ ... Success!
+
+ Reloading the privilege tables will ensure that all changes made so far
+ will take effect immediately.
+
+ Reload privilege tables now? [Y/n] y
+ ... Success!
+
+ Cleaning up...
+
+5. service mysql stop
+6. uncomment all wsrep* lines except first server, where leave only in my.cnf wsrep_cluster_address='gcomm://';
+7. start first node
+8. Start third node which is connected to first one
+9. Start second node which is connected to third one
+10. After starting cluster, it must be change cluster address at first starting node without restart database and change config my.cnf.
+
+.. code-block:: bash
+
+ mysql> SET GLOBAL wsrep_cluster_address='gcomm://10.0.0.2';
+
+Read more
+=========
+
+* http://dev.mysql.com/doc/
+* http://www.slideshare.net/osscube/mysql-performance-tuning-top-10-tips
+
+Galera replication
+------------------
+
+* https://github.com/CaptTofu/ansible-galera
+* http://www.sebastien-han.fr/blog/2012/04/15/active-passive-failover-cluster-on-a-mysql-galera-cluster-with-haproxy-lsb-agent/
+* http://opentodo.net/2012/12/mysql-multi-master-replication-with-galera/
+* http://www.codership.com/wiki/doku.php
+* Best one: - http://www.sebastien-han.fr/blog/2012/04/01/mysql-multi-master-replication-with-galera/
+
+Mysql Backup
+------------
+
+* http://sourceforge.net/projects/automysqlbackup/
+* https://labs.riseup.net/code/projects/backupninja/wiki
+* http://wiki.zmanda.com/index.php/Mysql-zrm
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..3b04cfb
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.2
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..4697504
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,11 @@
+salt-formula-mysql (0.2) trusty; urgency=medium
+
+ * First public release
+
+ -- Filip Pytloun <filip.pytloun@tcpcloud.eu> Tue, 06 Oct 2015 16:38:48 +0200
+
+salt-formula-mysql (0.1) trusty; urgency=medium
+
+ * Initial release
+
+ -- Jakub Pavlik <jakub.pavlik@tcpcloud.eu> Thu, 13 Aug 2015 23:23:41 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..31daa13
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,15 @@
+Source: salt-formula-mysql
+Maintainer: Jakub Pavlik <jakub.pavlik@tcpcloud.eu>
+Section: admin
+Priority: optional
+Build-Depends: debhelper (>= 9)
+Standards-Version: 3.9.6
+Homepage: http://www.tcpcloud.eu
+Vcs-Browser: https://github.com/tcpcloud/salt-formula-mysql
+Vcs-Git: https://github.com/tcpcloud/salt-formula-mysql.git
+
+Package: salt-formula-mysql
+Architecture: all
+Depends: ${misc:Depends}, salt-master, reclass
+Description: MySQL Salt formula
+ Install and configure MySQL server and client.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..6b0c433
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,15 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: salt-formula-mysql
+Upstream-Contact: Jakub Pavlik <jakub.pavlik@tcpcloud.eu>
+Source: https://github.com/tcpcloud/salt-formula-mysql
+
+Files: *
+Copyright: 2014-2015 tcp cloud
+License: Apache-2.0
+ Copyright (C) 2014-2015 tcp cloud
+ .
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ .
+ On a Debian system you can find a copy of this license in
+ /usr/share/common-licenses/Apache-2.0.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..d585829
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,3 @@
+README.rst
+CHANGELOG.rst
+VERSION
diff --git a/debian/install b/debian/install
new file mode 100644
index 0000000..62a8f8a
--- /dev/null
+++ b/debian/install
@@ -0,0 +1,2 @@
+mysql/* /usr/share/salt-formulas/env/mysql/
+metadata/service/* /usr/share/salt-formulas/reclass/service/mysql/
diff --git a/debian/lintian-overrides b/debian/lintian-overrides
new file mode 100644
index 0000000..b7b3b12
--- /dev/null
+++ b/debian/lintian-overrides
@@ -0,0 +1 @@
+salt-formula-mysql binary: shell-script-fails-syntax-check usr/share/salt-formulas/env/mysql/conf/restore.sh
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..abde6ef
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,5 @@
+#!/usr/bin/make -f
+
+%:
+ dh $@
+
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/metadata/service/server/cluster.yml b/metadata/service/server/cluster.yml
new file mode 100644
index 0000000..81f5e42
--- /dev/null
+++ b/metadata/service/server/cluster.yml
@@ -0,0 +1,57 @@
+applications:
+- mysql
+parameters:
+ mysql:
+ cluster:
+ enabled: true
+ name: openstack
+ role: ${_param:mysql_cluster_role}
+ bind:
+ address: ${_param:cluster_local_address}
+ port: 4567
+ members:
+ - host: ${_param:cluster_node01_address}
+ port: 4567
+ - host: ${_param:cluster_node02_address}
+ port: 4567
+ - host: ${_param:cluster_node03_address}
+ port: 4567
+ gcache_size: 4000
+ server:
+ enabled: true
+ force_encoding: utf8
+ version: '5.5'
+ admin:
+ user: root
+ password: ${_param:mysql_admin_password}
+ bind:
+ address: ${_param:cluster_local_address}
+ port: 3306
+ users:
+ - name: haproxy
+ host: localhost
+ - name: haproxy
+ host: '%'
+ - name: haproxy
+ host: ${_param:cluster_local_address}
+ haproxy:
+ proxy:
+ listen:
+ mysql_cluster:
+ type: mysql
+ binds:
+ - address: ${_param:cluster_vip_address}
+ port: 3306
+ servers:
+ - name: ctl01
+ host: ${_param:cluster_node01_address}
+ port: 3306
+ params: check
+ - name: ctl02
+ host: ${_param:cluster_node02_address}
+ port: 3306
+ params: backup check
+ - name: ctl03
+ host: ${_param:cluster_node03_address}
+ port: 3306
+ params: backup check
diff --git a/metadata/service/server/local.yml b/metadata/service/server/local.yml
new file mode 100644
index 0000000..e61bbfa
--- /dev/null
+++ b/metadata/service/server/local.yml
@@ -0,0 +1,15 @@
+applications:
+- mysql
+parameters:
+ mysql:
+ server:
+ enabled: true
+ version: '5.5'
+ force_encoding: utf8
+ admin:
+ user: ${_param:mysql_admin_user}
+ password: ${_param:mysql_admin_password}
+ bind:
+ address: 0.0.0.0
+ port: 3306
+ protocol: tcp
diff --git a/metadata/service/server/replication_both.yml b/metadata/service/server/replication_both.yml
new file mode 100644
index 0000000..ffc137c
--- /dev/null
+++ b/metadata/service/server/replication_both.yml
@@ -0,0 +1,32 @@
+applications:
+- mysql
+parameters:
+ mysql:
+ server:
+ enabled: true
+ version: '5.5'
+ force_encoding: utf8
+ replication:
+ role: both
+ master: ${_param:mysql_replication_master}
+ master_address: ${_param:mysql_replication_master_address}
+ user: ${_param:mysql_replication_user}
+ password: ${_param:mysql_replication_password}
+ server_id: ${_param:mysql_server_id}
+ ssl:
+ enabled: true
+ authority: ${_param:mysql_ssl_authority}
+ certificate: ${_param:mysql_ssl_certificate}
+ client_certificate: ${_param:mysql_ssl_client_certificate}
+ admin:
+ user: ${_param:mysql_admin_user}
+ password: ${_param:mysql_admin_password}
+ bind:
+ address: 0.0.0.0
+ port: 3306
+ protocol: tcp
+ salt:
+ minion:
+ mine:
+ module:
+ mysql.get_master_status: []
diff --git a/metadata/service/server/replication_master.yml b/metadata/service/server/replication_master.yml
new file mode 100644
index 0000000..5ebca0c
--- /dev/null
+++ b/metadata/service/server/replication_master.yml
@@ -0,0 +1,29 @@
+applications:
+- mysql
+parameters:
+ mysql:
+ server:
+ enabled: true
+ version: '5.5'
+ force_encoding: utf8
+ replication:
+ role: master
+ user: ${_param:mysql_replication_user}
+ password: ${_param:mysql_replication_password}
+ server_id: 1
+ ssl:
+ enabled: true
+ authority: ${_param:mysql_ssl_authority}
+ certificate: ${_param:mysql_ssl_certificate}
+ admin:
+ user: ${_param:mysql_admin_user}
+ password: ${_param:mysql_admin_password}
+ bind:
+ address: 0.0.0.0
+ port: 3306
+ protocol: tcp
+ salt:
+ minion:
+ mine:
+ module:
+ mysql.get_master_status: []
\ No newline at end of file
diff --git a/metadata/service/server/replication_slave.yml b/metadata/service/server/replication_slave.yml
new file mode 100644
index 0000000..adac12c
--- /dev/null
+++ b/metadata/service/server/replication_slave.yml
@@ -0,0 +1,27 @@
+applications:
+- mysql
+parameters:
+ mysql:
+ server:
+ enabled: true
+ version: '5.5'
+ force_encoding: utf8
+ replication:
+ role: slave
+ master: ${_param:mysql_replication_master}
+ master_address: ${_param:mysql_replication_master_address}
+ user: ${_param:mysql_replication_user}
+ password: ${_param:mysql_replication_password}
+ server_id: 2
+ ssl:
+ enabled: true
+ authority: ${_param:mysql_ssl_authority}
+ certificate: ${_param:mysql_ssl_certificate}
+ client_certificate: ${_param:mysql_ssl_client_certificate}
+ admin:
+ user: ${_param:mysql_admin_user}
+ password: ${_param:mysql_admin_password}
+ bind:
+ address: 0.0.0.0
+ port: 3306
+ protocol: tcp
diff --git a/metadata/service/server/single.yml b/metadata/service/server/single.yml
new file mode 100644
index 0000000..e61bbfa
--- /dev/null
+++ b/metadata/service/server/single.yml
@@ -0,0 +1,15 @@
+applications:
+- mysql
+parameters:
+ mysql:
+ server:
+ enabled: true
+ version: '5.5'
+ force_encoding: utf8
+ admin:
+ user: ${_param:mysql_admin_user}
+ password: ${_param:mysql_admin_password}
+ bind:
+ address: 0.0.0.0
+ port: 3306
+ protocol: tcp
diff --git a/mysql/common.sls b/mysql/common.sls
new file mode 100644
index 0000000..a58e163
--- /dev/null
+++ b/mysql/common.sls
@@ -0,0 +1,152 @@
+{%- from "mysql/map.jinja" import server with context %}
+
+{%- if server.admin is defined %}
+
+mariadb_debconf:
+ debconf.set:
+ - name: mariadb-server
+ - data:
+ 'mysql-server/root_password': {'type':'string','value':'{{ server.admin.password }}'}
+ 'mysql-server/root_password_again': {'type':'string','value':'{{ server.admin.password }}'}
+ - require_in:
+ - pkg: mysql_packages
+
+{%- endif %}
+
+mysql_packages:
+ pkg.installed:
+ - names: {{ server.pkgs }}
+
+{%- if server.version != '5.6' %}
+
+mysql_config:
+ file.managed:
+ - name: {{ server.config }}
+ - source: salt://mysql/conf/my.cnf.{{ grains.os_family }}
+ - mode: 644
+ - template: jinja
+ - require:
+ - pkg: mysql_packages
+ - watch_in:
+ - service: mysql_service
+
+{%- endif %}
+
+mysql_service:
+ service.running:
+ - name: {{ server.service }}
+ - enable: true
+
+mysql_config_dir:
+ file.directory:
+ - name: /etc/mysql/conf.d
+ - makedirs: true
+ - mode: 755
+
+mysql_dirs:
+ file.directory:
+ - names:
+ - /root/mysql/scripts
+ - /root/mysql/flags
+ - /root/mysql/data
+ - mode: 700
+ - user: root
+ - group: root
+ - makedirs: true
+ - require:
+ - pkg: mysql_packages
+
+/root/mysql/flags:
+ file.directory:
+ - mode: 700
+ - user: root
+ - group: root
+ - makedirs: true
+ - require:
+ - pkg: mysql_packages
+
+
+{#
+# Backup part
+#}
+
+{#
+# Backup part - automysqlbackup
+#}
+
+{% for backup_engine in pillar.mysql.server.get("backup_engine", []) %}
+
+{%- if backup_engine.get("name", []) == "automysqlbackup" %}
+
+{%- if grains.osfullname in ['CentOS'] %}
+
+mysql_backup1_pkgs:
+ pkg.installed:
+ - names:
+ - pigz
+ - pbzip2
+ - cronie
+ - mailx
+
+{%- elif grains.osfullname in ['Ubuntu'] %}
+
+mysql_backup2_pkgs:
+ pkg.installed:
+ - names:
+ - mailutils
+ - pigz
+ - pbzip2
+
+{%- endif %}
+
+{%- if grains.osfullname in ['CentOS'] or grains.osfullname in ['Ubuntu'] %}
+
+mysql_backup_dirs:
+ file.directory:
+ - names:
+ - /root/mysql/scripts
+ - /root/mysql/data
+ - /etc/automysqlbackup
+ - mode: 700
+ - user: root
+ - group: root
+ - makedirs: true
+ - require:
+{%- if grains.osfullname in ['CentOS'] %}
+ - pkg: mysql_backup1_pkgs
+{%- elif grains.osfullname in ['Ubuntu'] %}
+ - pkg: mysql_backup2_pkgs
+{%- endif %}
+
+mysql_automysqlbackup_conf:
+ file.managed:
+ - name: /etc/automysqlbackup/automysqlbackup.conf
+ - source: salt://mysql/conf/automysqlbackup.conf
+ - mode: 644
+ - template: jinja
+ - require:
+ - file: mysql_backup_dirs
+
+mysql_automysqlbackup_script:
+ file.managed:
+ - name: /root/mysql/scripts/automysqlbackup
+ - source: salt://mysql/conf/automysqlbackup
+ - mode: 755
+ - require:
+ - file: mysql_backup_dirs
+
+mysql_automysqlbackup_cron:
+ file.managed:
+ - name: /etc/cron.daily/automysqlbackup
+ - source: salt://mysql/conf/automysqlbackup.cron
+ - mode: 755
+ - template: jinja
+ - require:
+ - file: mysql_backup_dirs
+
+{%- endif %}
+
+{%- endif %}
+
+{%- endfor %}
+
diff --git a/mysql/conf/automysqlbackup b/mysql/conf/automysqlbackup
new file mode 100755
index 0000000..516b4f1
--- /dev/null
+++ b/mysql/conf/automysqlbackup
@@ -0,0 +1,2255 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+# 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
+#
+shopt -s extglob
+
+# BEGIN _flags
+let "filename_flag_encrypted=0x01"
+let "filename_flag_gz=0x02"
+let "filename_flag_bz2=0x04"
+let "filename_flag_diff=0x08"
+# END _flags
+
+# BEGIN _errors_notifications
+let "E=0x00" # no errors
+let "N=0x00" # no notifications
+
+let "E_dbdump_failed=0x01"
+let "E_backup_local_failed=0x02"
+let "E_mkdir_basedir_failed=0x04"
+let "E_mkdir_subdirs_failed=0x08"
+let "E_perm_basedir=0x10"
+let "E_enc_cleartext_delfailed=0x20"
+let "E_enc_failed=0x40"
+let "E_db_empty=0x80"
+let "E_create_pipe_failed=0x100"
+let "E_missing_deps=0x200"
+let "E_no_basedir=0x400"
+let "E_config_backupdir_not_writable=0x800"
+let "E_dump_status_failed=0x1000"
+let "E_dump_fullschema_failed=0x2000"
+
+let "N_config_file_missing=0x01"
+let "N_arg_conffile_parsed=0x02"
+let "N_arg_conffile_unreadable=0x04"
+let "N_too_many_args=0x08"
+let "N_latest_cleanup_failed=0x10"
+let "N_backup_local_nofiles=0x20"
+# END _errors_notifications
+
+# BEGIN _functions
+
+# @info: Default configuration options.
+# @deps: (none)
+load_default_config() {
+ CONFIG_configfile="/etc/automysqlbackup/automysqlbackup.conf"
+ CONFIG_backup_dir='/var/backup/db'
+ CONFIG_multicore='yes'
+ CONFIG_multicore_threads=2
+ CONFIG_do_monthly="01"
+ CONFIG_do_weekly="5"
+ CONFIG_rotation_daily=6
+ CONFIG_rotation_weekly=35
+ CONFIG_rotation_monthly=150
+ CONFIG_mysql_dump_port=3306
+ CONFIG_mysql_dump_usessl='yes'
+ CONFIG_mysql_dump_username='root'
+ CONFIG_mysql_dump_password=''
+ CONFIG_mysql_dump_host='localhost'
+ CONFIG_mysql_dump_host_friendly=''
+ CONFIG_mysql_dump_socket=''
+ CONFIG_mysql_dump_create_database='no'
+ CONFIG_mysql_dump_use_separate_dirs='yes'
+ CONFIG_mysql_dump_compression='gzip'
+ CONFIG_mysql_dump_commcomp='no'
+ CONFIG_mysql_dump_latest='no'
+ CONFIG_mysql_dump_latest_clean_filenames='no'
+ CONFIG_mysql_dump_max_allowed_packet=''
+ CONFIG_mysql_dump_single_transaction='no'
+ CONFIG_mysql_dump_master_data=
+ CONFIG_mysql_dump_full_schema='yes'
+ CONFIG_mysql_dump_dbstatus='yes'
+ CONFIG_mysql_dump_differential='no'
+ CONFIG_backup_local_files=()
+ CONFIG_db_names=()
+ CONFIG_db_month_names=()
+ CONFIG_db_exclude=( 'information_schema' )
+ CONFIG_table_exclude=()
+ CONFIG_mailcontent='stdout'
+ CONFIG_mail_maxattsize=4000
+ CONFIG_mail_splitandtar='yes'
+ CONFIG_mail_use_uuencoded_attachments='no'
+ CONFIG_mail_address='root'
+ CONFIG_encrypt='no'
+ CONFIG_encrypt_password='password0123'
+}
+
+# @return: true, if variable is set; else false
+isSet() {
+ if [[ ! ${!1} && ${!1-_} ]]; then return 1; else return 0; fi
+}
+
+# @return: true, if variable is empty; else false
+isEmpty() {
+ if [[ ${!1} ]]; then return 1; else return 0; fi
+}
+
+# @info: Called when one of the signals EXIT, SIGHUP, SIGINT, SIGQUIT or SIGTERM is emitted.
+# It removes the IO redirection, mails any log file information and cleans up any temporary files.
+# @args: (none)
+# @return: (none)
+mail_cleanup () {
+ removeIO
+ # if the variables $log_file and $log_errfile aren't set or are empty and both associated files don't exist, skip output methods.
+ # this might happen if 'exit' occurs before they are set.
+ if [[ ! -e "$log_file" && ! -e "$log_errfile" ]];then
+ echo "Skipping normal output methods, since the program exited before any log files could be created."
+ else
+ case "${CONFIG_mailcontent}" in
+ 'files')
+ # Include error log if larger than zero.
+ if [[ -s "$log_errfile" ]]; then
+ backupfiles=( "${backupfiles[@]}" "$log_errfile" )
+ errornote="WARNING: Error Reported - "
+ fi
+ temp="$(mktemp "$CONFIG_backup_dir"/tmp/mail_content.XXXXXX)"
+ # Get backup size
+ attsize=`du -c "${backupfiles[@]}" | awk 'END {print $1}'`
+ if (( ${CONFIG_mail_maxattsize} >= ${attsize} )); then
+ if [[ "x$CONFIG_mail_use_uuencoded_attachments" = "xyes" ]]; then
+ cat "$log_file" > "$temp"
+ for j in "${backupfiles[@]}"; do
+ uuencode "$j" "$j" >> "$temp"
+ done
+ mail -s "${errornote} MySQL Backup Log and SQL Files for ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}" ${CONFIG_mail_address} < "$temp"
+ else
+ mutt -s "${errornote} MySQL Backup Log and SQL Files for ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}" -a "${backupfiles[@]}" -- ${CONFIG_mail_address} < "$log_file"
+ fi
+ elif (( ${CONFIG_mail_maxattsize} <= ${attsize} )) && [[ "x$CONFIG_mail_splitandtar" = "xyes" ]]; then
+ if sPWD="$PWD"; cd "$CONFIG_backup_dir"/tmp && pax -wv "${backupfiles[@]}" | bzip2_compression | split -b $((CONFIG_mail_maxattsize*1000)) - mail_attachment_${datetimestamp}_ && cd "$sPWD"; then
+ files=("$CONFIG_backup_dir"/tmp/mail_attachment_${datetimestamp}_*)
+ echo -e "\n\nThe attachments have been split into multiple files.\nUse 'cat mail_attachment_2011-08-13_13h15m_* > mail_attachment_2011-08-13_13h15m.tar.bz2' to combine them and \
+ 'bunzip2 <mail_attachment_2011-08-13_13h15m.tar.bz2 | pax -rv' to extract the content."
+ for ((j=0;j<"${#files[@]}";j++)); do
+ if [[ "x$CONFIG_mail_use_uuencoded_attachments" = "xyes" ]]; then
+ if (( $j = 0 )); then
+ cat "$log_file" > "$temp"
+ uuencode "$j" "$j" >> "$temp"
+ else
+ uuencode "$j" "$j" > "$temp"
+ fi
+ mail -s "${errornote} MySQL Backup Log and SQL Files for ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}" ${CONFIG_mail_address} < "$temp"
+ else
+ mutt -s "${errornote} MySQL Backup Log and SQL Files for ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}; Part $((j+1))/${#files[@]}" -a "${files[j]}" -- ${CONFIG_mail_address} < "$log_file"
+ fi
+ done
+ else
+ cat "$log_file" | mail -s "WARNING! - MySQL Backup exceeds set maximum attachment size on ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}" ${CONFIG_mail_address}
+ fi
+ else
+ cat "$log_file" | mail -s "WARNING! - MySQL Backup exceeds set maximum attachment size on ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}" ${CONFIG_mail_address}
+ fi
+ rm "$temp"
+ ;;
+ 'log')
+ cat "$log_file" | mail -s "MySQL Backup Log for ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}" ${CONFIG_mail_address}
+ [[ -s "$log_errfile" ]] && cat "$log_errfile" | mail -s "ERRORS REPORTED: MySQL Backup error Log for ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}" ${CONFIG_mail_address}
+ ;;
+ 'quiet')
+ [[ -s "$log_errfile" ]] && cat "$log_errfile" | mail -s "ERRORS REPORTED: MySQL Backup error Log for ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host} - ${datetimestamp}" ${CONFIG_mail_address}
+ ;;
+ *)
+ if [[ -s "$log_errfile" ]]; then
+ cat "$log_file"
+ echo
+ echo "###### WARNING ######"
+ echo "Errors reported during AutoMySQLBackup execution.. Backup failed"
+ echo "Error log below.."
+ cat "$log_errfile"
+ else
+ cat "$log_file"
+ fi
+ ;;
+ esac
+ ###################################################################################
+ # Clean up and finish
+ [[ -e "$log_file" ]] && rm -f "$log_file"
+ [[ -e "$log_errfile" ]] && rm -f "$log_errfile"
+ fi
+}
+
+# @params: #month #year
+# @deps: (none)
+days_of_month() {
+ m="$1"; y="$2"; a=$(( 30+(m+m/8)%2 ))
+ (( m==2 )) && a=$((a-2))
+ (( m==2 && y%4==0 && ( y<100 || y%100>0 || y%400==0) )) && a=$((a+1))
+ printf '%d' $a
+}
+
+# @info: Checks if a folder is writable by creating a temporary file in it and removing it afterwards.
+# @args: folder to test
+# @return: returns false if creation of temporary file failed or it can't be removed afterwards; else true
+# @deps: (none)
+chk_folder_writable () {
+ local temp; temp="$(mktemp "$1"/tmp.XXXXXX)"
+ if (( $? == 0 )); then
+ rm "${temp}" || return 1
+ return 0
+ else
+ return 1
+ fi
+}
+
+# @info: bzip2 compression
+bzip2_compression() {
+ var=("$@")
+ re='^[0-9]*$'
+ if [[ "x$CONFIG_multicore" = 'xyes' ]]; then
+ if [[ "x$CONFIG_multicore_threads" != 'xauto' ]] && [[ "x$CONFIG_multicore_threads" =~ $re ]]; then
+ var=( "-p${CONFIG_multicore_threads}" "${var[@]}" )
+ fi
+ pbzip2 "${var[@]}"
+ else
+ bzip2 "${var[@]}"
+ fi
+}
+
+# @info: gzip compression
+gzip_compression() {
+ var=("$@")
+ re='^[0-9]*$'
+ if [[ "x$CONFIG_multicore" = 'xyes' ]]; then
+ if [[ "x$CONFIG_multicore_threads" != 'xauto' ]] && [[ "x$CONFIG_multicore_threads" =~ $re ]]; then
+ var=( "-p${CONFIG_multicore_threads}" "${var[@]}" )
+ fi
+ pigz "${var[@]}"
+ else
+ gzip "${var[@]}"
+ fi
+}
+
+# @info: Remove date and time information from filename by renaming it.
+# @args: filename
+# @return: (none)
+# @deps: (none)
+remove_datetimeinfo () {
+ mv "$1" "$(echo "$1" | sed -re 's/_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}h[0-9]{2}m_(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|January|February|March|April|May|June|July|August|September|October|November|December|[0-9]{1,2})//g')"
+}
+export -f remove_datetimeinfo
+
+# @info: Set time and date variables.
+# @args: (none)
+# @deps: days_of_month
+set_datetime_vars() {
+ datetimestamp=`date +%Y-%m-%d_%Hh%Mm` # Datestamp e.g 2002-09-21_18h12m
+ date_stamp=`date +%Y-%m-%d` # Datestamp e.g 2002-09-21
+ date_day_of_week=`date +%A` # Day of the week e.g. Monday
+ date_dayno_of_week=`date +%u` # Day number of the week 1 to 7 where 1 represents Monday
+ date_day_of_month=`date +%e | sed -e 's/^ //'` # Date of the Month e.g. 27
+ date_month=`date +%B` # Month e.g January
+ date_weekno=`date +%V | sed -e 's/^0//'` # Week Number e.g 37
+ year=`date +%Y`
+ month=`date +%m | sed -e 's/^0//'`
+ date_lastday_of_last_month=$(days_of_month $(( $month==1 ? 12 : $month-1 )) $(( $month==1 ? ($year-1):$year )) )
+ date_lastday_of_this_month=$(days_of_month $month $year)
+}
+
+# @info: This function is called after data has already been saved. It performs encryption and
+# hardlink-copying of files to a latest folder.
+# @return: flags
+# @deps: load_default_config
+files_postprocessing () {
+ local flags
+ let "flags=0x00"
+ let "flags_files_postprocessing_success_encrypt=0x01"
+
+ # -> CONFIG_encrypt
+ [[ "${CONFIG_encrypt}" = "yes" && "${CONFIG_encrypt_password}" ]] && {
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running: openssl enc -aes-256-cbc -e -in %s -out %s.enc -pass pass:%s\n' ${1} ${1} "${CONFIG_encrypt_password}"
+ else
+ openssl enc -aes-256-cbc -e -in ${1} -out ${1}.enc -pass pass:"${CONFIG_encrypt_password}"
+ if (( $? == 0 )); then
+ if rm ${1} 2>&1; then
+ echo "Successfully encrypted archive as ${1}.enc"
+ let "flags |= $flags_files_postprocessing_success_encrypt"
+ else
+ echo "Successfully encrypted archive as ${1}.enc, but could not remove cleartext file ${1}."
+ let "E |= $E_enc_cleartext_delfailed"
+ fi
+ else
+ let "E |= $E_enc_failed"
+ fi
+ fi
+ }
+ # <- CONFIG_encrypt
+
+ # -> CONFIG_mysql_dump_latest
+ [[ "${CONFIG_mysql_dump_latest}" = "yes" ]] && {
+ if (( $flags & $flags_files_postprocessing_success_encrypt )); then
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running: cp -al %s.enc %s/latest/\n' "${1}" "${CONFIG_backup_dir}"
+ else
+ cp -al "${1}${suffix}.enc" "${CONFIG_backup_dir}"/latest/
+ fi
+ else
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running: cp -al %s %s/latest/\n' "${1}" "${CONFIG_backup_dir}"
+ else
+ cp -al "${1}" "${CONFIG_backup_dir}"/latest/
+ fi
+ fi
+ }
+ # <- CONFIG_mysql_dump_latest
+
+ return $flags
+}
+
+# @info: When called, sets error and notify strings matching their flags. It then goes through all
+# collected error and notify messages and displays them.
+# @args: (none)
+# @return: true if no errors were set, otherwise false
+# @deps: log_base2, load_default_config
+error_handler () {
+
+ errors=(
+ [0x01]='dbdump() failed.'
+ [0x02]='Backup of local files failed. This is not this scripts primary objective. Continuing anyway.'
+ [0x04]="Could not create the backup_dir ${CONFIG_backup_dir}. Please check permissions of the higher directory."
+ [0x08]='At least one of the subdirectories (daily, weekly, monthly, latest) failed to create.'
+ [0x10]="The backup_dir ${CONFIG_backup_dir} is not writable AND/OR executable."
+ [0x20]='Could not remove the cleartext file after encryption. This error did not cause an abort. Remove it manually and check permissions.'
+ [0x40]='Encryption failed. Continuing without encryption.'
+ [0x80]='The mysql server is empty, i.e. no databases found. Check if something is wrong. Exiting.'
+ [0x100]='Failed to create the named pipe (fifo) for reading in all databases. Exiting.'
+ [0x200]='Dependency programs are missing. Perhaps they are not in $PATH. Exiting.'
+ [0x400]='No basedir found, i.e. '
+ [0x800]="${CONFIG_backup_dir} is not writable. Exiting."
+ [0x1000]='Running of mysqlstatus failed.'
+ [0x2000]='Running of mysqldump full schema failed.'
+ )
+
+ notify=(
+ [0x01]="${CONFIG_configfile} was not found - no global config file."
+ [0x02]="Parsed config file ${opt_config_file}."
+ [0x04]="Unreadable config file \"${opt_config_file}\""
+ [0x08]='Supplied more than one argument, ignoring ALL arguments - using default and global config file only.'
+ [0x10]='Could not remove the files in the latest directory. Please check this.'
+ [0x20]='No local backup files were set.'
+ [0x40]=''
+ [0x80]=''
+ [0x100]=''
+ [0x200]=''
+ [0x400]=''
+ [0x800]=''
+ [0x1000]=''
+ [0x2000]=''
+ )
+
+ local n
+ local e
+
+ n=$((${#notify[@]}-1))
+ while (( N > 0 )); do
+ e=$((2**n))
+ if (( N&e )); then
+ echo "Note:" ${notify[e]}
+ let "N-=e"
+ fi
+ ((n--))
+ done
+ unset n;
+
+ n=$((${#errors[@]}-1))
+ if (( E > 0 )); then
+ while (( E > 0 )); do
+ e=$((2**n))
+ if (( E&e )); then
+ echo "Error:" ${errors[e]}
+ let "E-=e"
+ fi
+ ((n--))
+ done
+ exit 1
+ else
+ exit 0
+ fi
+}
+
+# @info: Packs files in array ${#CONFIG_backup_local_files[@]} into tar file with optional compression.
+# @args: archive file without compression suffix, i.e. ending on .tar
+# @return: true in case of dry-run, otherwise the return value of tar -cvf
+# @deps: load_default_config
+backup_local_files () {
+ if ((! ${#CONFIG_backup_local_files[@]})) ; then
+ if (( $CONFIG_dryrun )); then
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ echo "tar -czvf ${1}${suffix} ${CONFIG_backup_local_files[@]}";
+ ;;
+ 'bzip2')
+ echo "tar -cjvf ${1}${suffix} ${CONFIG_backup_local_files[@]}";
+ ;;
+ *)
+ echo "tar -cvf ${1}${suffix} ${CONFIG_backup_local_files[@]}";
+ ;;
+ esac
+ echo "dry-running: tar -cv ${1} ${CONFIG_backup_local_files[@]}"
+ return 0;
+ else
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ tar -czvf "${1}${suffix}" "${CONFIG_backup_local_files[@]}";
+ return $?
+ ;;
+ 'bzip2')
+ tar -cjvf "${1}${suffix}" "${CONFIG_backup_local_files[@]}";
+ return $?
+ ;;
+ *)
+ tar -cvf "${1}${suffix}" "${CONFIG_backup_local_files[@]}";
+ return $?
+ ;;
+ esac
+ fi
+ else
+ let "N |= $N_backup_local_nofiles"
+ echo "No local backup files specified."
+ fi
+}
+
+# @info: Parses the configuration options and sets the variables appropriately.
+# @args: (none)
+# @deps: load_default_config
+parse_configuration () {
+ # OPT string for use with mysqldump ( see man mysqldump )
+ opt=( '--quote-names' '--opt' )
+
+ # OPT string for use with mysql (see man mysql )
+ mysql_opt=()
+
+ # OPT string for use with mysqldump fullschema
+ opt_fullschema=( '--all-databases' '--routines' '--no-data' )
+
+ # OPT string for use with mysqlstatus
+ opt_dbstatus=( '--status' )
+
+ [[ "${CONFIG_mysql_dump_usessl}" = "yes" ]] && {
+ opt=( "${opt[@]}" '--ssl' )
+ mysql_opt=( "${mysql_opt[@]}" '--ssl' )
+ opt_fullschema=( "${opt_fullschema[@]}" '--ssl' )
+ opt_dbstatus=( "${opt_dbstatus[@]}" '--ssl' )
+ }
+ [[ "${CONFIG_mysql_dump_master_data}" ]] && (( ${CONFIG_mysql_dump_master_data} == 1 || ${CONFIG_mysql_dump_master_data} == 2 )) && { opt=( "${opt[@]}" "--master-data=${CONFIG_mysql_dump_master_data}" );}
+ [[ "${CONFIG_mysql_dump_single_transaction}" = "yes" ]] && {
+ opt=( "${opt[@]}" '--single-transaction' )
+ opt_fullschema=( "${opt_fullschema[@]}" '--single-transaction' )
+ }
+ [[ "${CONFIG_mysql_dump_commcomp}" = "yes" ]] && {
+ opt=( "${opt[@]}" '--compress' )
+ opt_fullschema=( "${opt_fullschema[@]}" '--compress' )
+ opt_dbstatus=( "${opt_dbstatus[@]}" '--compress' )
+ }
+ [[ "${CONFIG_mysql_dump_max_allowed_packet}" ]] && {
+ opt=( "${opt[@]}" "--max_allowed_packet=${CONFIG_mysql_dump_max_allowed_packet}" )
+ opt_fullschema=( "${opt_fullschema[@]}" "--max_allowed_packet=${CONFIG_mysql_dump_max_allowed_packet}" )
+ }
+ [[ "${CONFIG_mysql_dump_socket}" ]] && {
+ opt=( "${opt[@]}" "--socket=${CONFIG_mysql_dump_socket}" )
+ mysql_opt=( "${mysql_opt[@]}" "--socket=${CONFIG_mysql_dump_socket}" )
+ opt_fullschema=( "${opt_fullschema[@]}" "--socket=${CONFIG_mysql_dump_socket}" )
+ opt_dbstatus=( "${opt_dbstatus[@]}" "--socket=${CONFIG_mysql_dump_socket}" )
+ }
+ [[ "${CONFIG_mysql_dump_port}" ]] && {
+ opt=( "${opt[@]}" "--port=${CONFIG_mysql_dump_port}" )
+ mysql_opt=( "${mysql_opt[@]}" "--port=${CONFIG_mysql_dump_port}" )
+ opt_fullschema=( "${opt_fullschema[@]}" "--port=${CONFIG_mysql_dump_port}" )
+ opt_dbstatus=( "${opt_dbstatus[@]}" "--port=${CONFIG_mysql_dump_port}" )
+ }
+
+ # Check if CREATE DATABASE should be included in Dump
+ if [[ "${CONFIG_mysql_dump_use_separate_dirs}" = "yes" ]]; then
+ if [[ "${CONFIG_mysql_dump_create_database}" = "no" ]]; then
+ opt=( "${opt[@]}" '--no-create-db' )
+ else
+ opt=( "${opt[@]}" '--databases' )
+ fi
+ else
+ opt=( "${opt[@]}" '--databases' )
+ fi
+
+ # if differential backup is active and the specified rotation is smaller than 21 days, set it to 21 days to ensure, that
+ # master backups aren't deleted.
+ if [[ "x$CONFIG_mysql_dump_differential" = "xyes" ]] && (( ${CONFIG_rotation_daily} < 21 )); then
+ CONFIG_rotation_daily=21
+ fi
+
+ # -> determine suffix
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip') suffix='.gz';;
+ 'bzip2') suffix='.bz2';;
+ *) suffix='';;
+ esac
+ # <- determine suffix
+
+ # -> check exclude tables for wildcards
+ local tmp;tmp=()
+ local z;z=0
+ for i in "${CONFIG_table_exclude[@]}"; do
+ r='^[^*.]+\.[^.]+$'; [[ "$i" =~ $r ]] || { printf 'The entry %s in CONFIG_table_exclude has a wrong format. Ignoring the entry.' "$i"; continue; }
+ db=${i%.*}
+ table=${i#"$db".}
+ r='\*'; [[ "$i" =~ $r ]] || { tmp[z++]="$i"; continue; }
+ while read -r; do tmp[z++]="${db}.${REPLY}"; done < <(mysql --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${mysql_opt[@]}" --batch --skip-column-names -e "select table_name from information_schema.tables where table_schema='${db}' and table_name like '${table//\*/%}';")
+ done
+ for l in "${tmp[@]}"; do echo "exclude $l";done
+ CONFIG_table_exclude=("${tmp[@]}")
+ # <-
+
+ if ((${#CONFIG_table_exclude[@]})); then
+ for i in "${CONFIG_table_exclude[@]}"; do
+ opt=( "${opt[@]}" "--ignore-table=$i" )
+ done
+ fi
+}
+
+# @info: Backup database status
+# @args: archive file without compression suffix, i.e. ending on .txt
+# @return: true in case of dry-run, otherwise the return value of mysqlshow
+# @deps: load_default_config, parse_configuration
+dbstatus() {
+ if (( $CONFIG_dryrun )); then
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ echo "dry-running: mysqlshow --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt_dbstatus[@]} | gzip_compression > ${1}${suffix}";
+ ;;
+ 'bzip2')
+ echo "dry-running: mysqlshow --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt_dbstatus[@]} | bzip2_compression > ${1}${suffix}";
+ ;;
+ *)
+ echo "dry-running: mysqlshow --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt_dbstatus[@]} > ${1}${suffix}";
+ ;;
+ esac
+ return 0;
+ else
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ mysqlshow --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt_dbstatus[@]}" | gzip_compression > "${1}${suffix}";
+ return $?
+ ;;
+ 'bzip2')
+ mysqlshow --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt_dbstatus[@]}" | bzip2_compression > "${1}${suffix}";
+ return $?
+ ;;
+ *)
+ mysqlshow --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt_dbstatus[@]}" > "${1}${suffix}";
+ return $?
+ ;;
+ esac
+ fi
+}
+
+# @info: Backup of the database schema.
+# @args: filename to save data to
+# @return: true in case of dry-run, otherwise the return value of mysqldump
+# @deps: load_default_config, parse_configuration
+fullschema () {
+ if (( $CONFIG_dryrun )); then
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ echo "dry-running: mysqldump --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt_fullschema[@]} | gzip_compression > ${1}${suffix}";
+ ;;
+ 'bzip2')
+ echo "dry-running: mysqldump --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt_fullschema[@]} | bzip2_compression > ${1}${suffix}";
+ ;;
+ *)
+ echo "dry-running: mysqldump --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt_fullschema[@]} > ${1}${suffix}";
+ ;;
+ esac
+ return 0;
+ else
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt_fullschema[@]}" | gzip_compression > "${1}${suffix}";
+ return $?
+ ;;
+ 'bzip2')
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt_fullschema[@]}" | bzip2_compression > "${1}${suffix}";
+ return $?
+ ;;
+ *)
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt_fullschema[@]}" > "${1}${suffix}";
+ return $?
+ ;;
+ esac
+ fi
+}
+
+# @info: Process a single db.
+# @args: subfolder, prefix, midfix, extension, rotation, rotation_divisor, rotation_string, 0/1 (db/dbs), db[, db ...]
+process_dbs() {
+ local subfolder="$1"
+ local prefix="$2"
+ local midfix="$3"
+ local extension="$4"
+ local rotation="$5"
+ local rotation_divisor="$6"
+ local rotation_string="$7"
+ local multipledbs="$8"
+ shift 8
+
+ local name
+ local subsubfolder
+
+ # only activate differential backup for daily backups
+ [[ "x$subfolder" != "xdaily" ]] && activate_differential_backup=0 || activate_differential_backup=1
+
+ if (( $multipledbs )); then
+ # multiple dbs
+ subsubfolder=""
+ name="all-databases"
+ else
+ # single db
+ subsubfolder="/$1"
+ name="$@"
+ fi
+
+ [[ -d "${CONFIG_backup_dir}/${subfolder}${subsubfolder}" ]] || {
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running: mkdir -p %s/${subfolder}%s\n' "${CONFIG_backup_dir}" "${subsubfolder}"
+ else
+ mkdir -p "${CONFIG_backup_dir}/${subfolder}${subsubfolder}"
+ fi
+ }
+
+ manifest_file="${CONFIG_backup_dir}/${subfolder}${subsubfolder}/Manifest"
+ fname="${CONFIG_backup_dir}/${subfolder}${subsubfolder}/${prefix}${name}_${datetimestamp}${midfix}${extension}"
+
+ (( $CONFIG_debug )) && echo "DEBUG: process_dbs >> Setting manifest file to: ${manifest_file}"
+
+ if (( $multipledbs )); then
+ # multiple databases
+ db="all-databases"
+ else
+ # single db
+ db="$1"
+ fi
+
+ if [[ "x$CONFIG_mysql_dump_differential" = "xyes" ]] && [[ "x${CONFIG_encrypt}" != "xyes" ]] && (( $activate_differential_backup )); then
+
+
+ unset manifest_entry manifest_entry_to_check
+
+ echo "## Reading in Manifest file"
+ parse_manifest "$manifest_file"
+ echo
+ echo "Number of manifest entries: $(num_manifest_entries)"
+ echo
+
+
+ # -> generate diff file
+ let "filename_flags=0x00"
+
+# ## -> get latest differential manifest entry for specified db
+# if get_latest_manifest_entry_for_db "$db" 1; then
+# pid="${manifest_entry[2]}"
+# # filename format: prefix_db_YYYY-MM-DD_HHhMMm_[A-Za-z0-9]{8}(.sql|.diff)(.gz|.bz2)(.enc)
+# FileStub=${manifest_entry[0]%.@(sql|diff)*}
+# FileExt=${manifest_entry[0]#"$FileStub"}
+# re=".*\.enc.*"; [[ "$FileExt" =~ $re ]] && let "filename_flags|=$filename_flag_encrypted"
+# re=".*\.gz.*"; [[ "$FileExt" =~ $re ]] && let "filename_flags|=$filename_flag_gz"
+# re=".*\.bz2.*"; [[ "$FileExt" =~ $re ]] && let "filename_flags|=$filename_flag_bz2"
+# re=".*\.diff.*"; [[ "$FileExt" =~ $re ]] && let "filename_flags|=$filename_flag_diff"
+# manifest_latest_diff_entry=("${manifest_entry[@]}")
+# else # no entries in manifest
+# pid=0
+# fi
+# ## <- get latest differential manifest entry for specified db
+
+ ## -> get latest master manifest entry for specified db
+ # Create a differential backup if a master entry in the manifest exists, it isn't the day we do weekly master backups or the master file we fetched is already from today.
+ if get_latest_manifest_entry_for_db "$db" 0 && ( (( ${date_dayno_of_week} != ${CONFIG_do_weekly} )) || [[ "${manifest_entry[0]}" = *_$(date +%Y-%m-%d)_* ]] ); then
+ pid="${manifest_entry[2]}"
+ # filename format: prefix_db_YYYY-MM-DD_HHhMMm_[A-Za-z0-9]{8}(.sql|.diff)(.gz|.bz2)(.enc)
+ FileStub="${manifest_entry[0]%.@(sql|diff)*}"
+ FileExt="${manifest_entry[0]#"$FileStub"}"
+ re=".*\.enc.*"; [[ "$FileExt" =~ $re ]] && let "filename_flags|=$filename_flag_encrypted"
+ re=".*\.gz.*"; [[ "$FileExt" =~ $re ]] && let "filename_flags|=$filename_flag_gz"
+ re=".*\.bz2.*"; [[ "$FileExt" =~ $re ]] && let "filename_flags|=$filename_flag_bz2"
+ re=".*\.diff.*"; [[ "$FileExt" =~ $re ]] && let "filename_flags|=$filename_flag_diff"
+ manifest_latest_master_entry=("${manifest_entry[@]}")
+ else # no entries in manifest
+ pid=0
+ fi
+ ## <- get latest master manifest entry for specified db
+
+ fi
+
+ if [[ "x$CONFIG_mysql_dump_differential" = "xyes" ]] && [[ "x${CONFIG_encrypt}" != "xyes" ]] && (( $activate_differential_backup )) && ((! ($filename_flags & $filename_flag_encrypted) )); then
+
+ # the master file is encrypted ... well this just shouldn't happen ^^ not going to decrypt or stuff like that ...at least not today :)
+
+ if [[ "x$pid" = "x0" ]]; then
+ # -> create master backup
+ cfname="$(mktemp "${fname%.sql}_"XXXXXXXX".sql${suffix}")"
+ uid="${cfname%.@(diff|sql)*}"
+ uid="${uid:-8:8}"
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@" | gzip_compression > "$cfname";
+ ;;
+ 'bzip2')
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@" | bzip2_compression > "$cfname";
+ ;;
+ *)
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@" > "$cfname";
+ ;;
+ esac
+ add_manifest_entry "$manifest_file" "$cfname" "$pid" "$db" && parse_manifest "$manifest_file" && cp -al "$cfname" "${CONFIG_backup_dir}"/latest/ && echo "Generated master backup $cfname" && return 0 || return 1
+ # <- create master backup
+ else
+ cfname="$(mktemp "${fname%.sql}_"XXXXXXXX".diff${suffix}")"
+ uid="${cfname%.@(diff|sql)*}"
+ uid=${uid:-8:8}
+ echo "Creating differential backup to ${manifest_entry[0]}:"
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ if (( $filename_flags & $filename_flag_gz )); then
+ diff <(gzip_compression -dc "${manifest_latest_master_entry[0]}") <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") | gzip_compression > "$cfname";
+ elif (( $filename_flags & $filename_flag_bz2 )); then
+ diff <(bzip2_compression -dc "${manifest_latest_master_entry[0]}") <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") | gzip_compression > "$cfname";
+ else
+ diff "${manifest_latest_master_entry[0]}" <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") | gzip_compression > "$cfname";
+ fi
+ ;;
+ 'bzip2')
+ if (( $filename_flags & $filename_flag_gz )); then
+ diff <(gzip_compression -dc "${manifest_latest_master_entry[0]}") <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") | bzip2_compression > "$cfname";
+ elif (( $filename_flags & $filename_flag_bz2 )); then
+ diff <(bzip2_compression -dc "${manifest_latest_master_entry[0]}") <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") | bzip2_compression > "$cfname";
+ else
+ diff "${manifest_latest_master_entry[0]}" <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") | bzip2_compression > "$cfname";
+ fi
+ ;;
+ *)
+ if (( $filename_flags & $filename_flag_gz )); then
+ diff <(gzip_compression -dc "${manifest_latest_master_entry[0]}") <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") > "$cfname";
+ elif (( $filename_flags & $filename_flag_bz2 )); then
+ diff <(bzip2_compression -dc "${manifest_latest_master_entry[0]}") <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") > "$cfname";
+ else
+ diff "${manifest_latest_master_entry[0]}" <(mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@") > "$cfname";
+ fi
+ ;;
+ esac
+ add_manifest_entry "$manifest_file" "$cfname" "$pid" "$db" && parse_manifest "$manifest_file" && cp -al "$cfname" "${manifest_latest_master_entry[0]}" "${CONFIG_backup_dir}"/latest/ && echo "generated $cfname" && return 0 || return 1
+
+ fi
+ # <- generate diff filename
+
+ else
+ cfname="${fname}${suffix}"
+ if (( $CONFIG_dryrun )); then
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ echo "dry-running: mysqldump --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt[@]} $@ | gzip_compression > ${cfname}"
+ ;;
+ 'bzip2')
+ echo "dry-running: mysqldump --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt[@]} $@ | bzip2_compression > ${cfname}"
+ ;;
+ *)
+ echo "dry-running: mysqldump --user=${CONFIG_mysql_dump_username} --password=${CONFIG_mysql_dump_password} --host=${CONFIG_mysql_dump_host} ${opt[@]} $@ > ${cfname}"
+ ;;
+ esac
+ return 0;
+ else
+ case "${CONFIG_mysql_dump_compression}" in
+ 'gzip')
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@" | gzip_compression > "${cfname}"
+ ret=$?
+ ;;
+ 'bzip2')
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@" | bzip2_compression > "${cfname}"
+ ret=$?
+ ;;
+ *)
+ mysqldump --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${opt[@]}" "$@" > "${cfname}"
+ ret=$?
+ ;;
+ esac
+ fi
+
+ fi
+
+
+ if (( $ret == 0 )); then
+ echo "Rotating $(( ${rotation}/${rotation_divisor} )) ${rotation_string} backups for ${name}"
+ if (( $CONFIG_dryrun )); then
+ find "${CONFIG_backup_dir}/${subfolder}${subsubfolder}" -mtime +"${rotation}" -type f -exec echo "dry-running: rm" {} \;
+ else
+ find "${CONFIG_backup_dir}/${subfolder}${subsubfolder}" -mtime +"${rotation}" -type f -exec rm {} \;
+ fi
+ files_postprocessing "$cfname"
+ tmp_flags=$?; var=; (( $tmp_flags & $flags_files_postprocessing_success_encrypt )) && var=.enc
+ backupfiles=( "${backupfiles[@]}" "${cfname}${var}" )
+ else
+ let "E |= $E_dbdump_failed"
+ echo "dbdump with parameters \"${CONFIG_db_names[@]}\" \"${cfname}\" failed!"
+ fi
+}
+
+# @info: Save stdout and stderr
+# @deps: (none)
+activateIO() {
+ ###################################################################################
+ # IO redirection for logging.
+ # $1 = $log_file, $2 = $log_errfile
+
+ #(( $CONFIG_debug )) || {
+ touch "$log_file"
+ exec 6>&1 # Link file descriptor #6 with stdout. Saves stdout.
+ exec > "$log_file" # stdout replaced with file $log_file.
+
+ touch "$log_errfile"
+ exec 7>&2 # Link file descriptor #7 with stderr. Saves stderr.
+ exec 2> "$log_errfile" # stderr replaced with file $log_errfile.
+ #}
+}
+
+# @info: Restore stdout and stderr redirections.
+# @deps: (none)
+removeIO() {
+ exec 1>&6 6>&- # Restore stdout and close file descriptor #6.
+ exec 2>&7 7>&- # Restore stdout and close file descriptor #7.
+}
+
+# @info: Checks directories and subdirectories for existence and activates logging to either
+# $CONFIG_backup_dir or /tmp depending on what exists.
+# @args: (none)
+# @deps: load_default_config, activateIO, chk_folder_writable, error_handler
+directory_checks_enable_logging () {
+ ###################################################################################
+ # Check directories and do cleanup work
+
+ checkdirs=( "${CONFIG_backup_dir}"/{daily,weekly,monthly,latest,tmp} )
+ [[ "${CONFIG_backup_local_files[@]}" ]] && { checkdirs=( "${checkdirs[@]}" "${CONFIG_backup_dir}/backup_local_files" ); }
+ [[ "${CONFIG_mysql_dump_full_schema}" = 'yes' ]] && { checkdirs=( "${checkdirs[@]}" "${CONFIG_backup_dir}/fullschema" ); }
+ [[ "${CONFIG_mysql_dump_dbstatus}" = 'yes' ]] && { checkdirs=( "${checkdirs[@]}" "${CONFIG_backup_dir}/status" ); }
+
+ tmp_permcheck=0
+ printf '# Checking for permissions to write to folders:\n'
+
+
+ # "dirname ${CONFIG_backup_dir}" exists?
+ # Y -> ${CONFIG_backup_dir} exists?
+ # Y -> Dry-run?
+ # Y -> log to /tmp, proceed to test subdirs
+ # N -> check writable ${CONFIG_backup_dir}?
+ # Y -> proceed to test subdirs
+ # N -> error: can't write to ${CONFIG_backup_dir}. Exit.
+ # N -> Dry-run?
+ # N -> proceed without testing subdirs
+ # Y -> create directory ${CONFIG_backup_dir}?
+ # Y -> check writable ${CONFIG_backup_dir}?
+ # Y -> proceed to test subdirs
+ # N -> error: can't write to ${CONFIG_backup_dir}. Exit.
+ # N -> error: ${CONFIG_backup_dir} is not writable. Exit.
+ # N -> Dry-run?
+ # Y -> log to /tmp, proceed without testing subdirs
+ # N -> error: no basedir. Exit.
+
+
+ # -> check base folder
+ printf 'base folder %s ... ' "$(dirname "${CONFIG_backup_dir}")"
+ if [[ -d "$(dirname "${CONFIG_backup_dir}")" ]]; then
+
+ printf 'exists ... ok.\n'
+ printf 'backup folder %s ... ' "${CONFIG_backup_dir}"
+
+ if [[ -d "${CONFIG_backup_dir}" ]]; then
+ printf 'exists ... writable? '
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running. Skipping. Logging to /tmp\n'
+ log_file="/tmp/${CONFIG_mysql_dump_host}-`date +%N`.log"
+ log_errfile="/tmp/ERRORS_${CONFIG_mysql_dump_host}-`date +%N`.log"
+ activateIO "$log_file" "$log_errfile"
+ tmp_permcheck=1
+ else
+ if chk_folder_writable "${CONFIG_backup_dir}"; then
+ printf 'yes. Proceeding.\n'
+ log_file="${CONFIG_backup_dir}/${CONFIG_mysql_dump_host}-`date +%N`.log"
+ log_errfile="${CONFIG_backup_dir}/ERRORS_${CONFIG_mysql_dump_host}-`date +%N`.log"
+ activateIO "$log_file" "$log_errfile"
+ tmp_permcheck=1
+ else
+ printf 'no. Exiting.\n'
+ let "E |= $E_config_backupdir_not_writable"
+ error_handler
+ fi
+ fi
+
+ else
+
+ printf 'creating ... '
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running. Skipping.\n'
+ else
+ if mkdir -p "${CONFIG_backup_dir}" >/dev/null 2>&1; then
+ printf 'success.\n'
+ log_file="${CONFIG_backup_dir}/${CONFIG_mysql_dump_host}-`date +%N`.log"
+ log_errfile="${CONFIG_backup_dir}/ERRORS_${CONFIG_mysql_dump_host}-`date +%N`.log"
+ activateIO "$log_file" "$log_errfile"
+ tmp_permcheck=1
+ else
+ printf 'failed. Exiting.\n'
+ let "E |= $E_mkdir_basedir_failed"
+ error_handler
+ fi
+ fi
+
+ fi
+
+ else
+
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running. Skipping. Logging to /tmp\n'
+ log_file="/tmp/${CONFIG_mysql_dump_host}-`date +%N`.log"
+ log_errfile="/tmp/ERRORS_${CONFIG_mysql_dump_host}-`date +%N`.log"
+ activateIO "$log_file" "$log_errfile"
+ else
+ printf 'does not exist. Exiting.\n'
+ let "E |= $E_no_basedir"
+ error_handler
+ fi
+
+ fi
+ # <- check base folder
+
+
+ # -> check subdirs
+ if (( $tmp_permcheck == 1 )); then
+
+ (( $CONFIG_dryrun )) || [[ -r "${CONFIG_backup_dir}" && -x "${CONFIG_backup_dir}" ]] || { let "E |= $E_perm_basedir"; error_handler; }
+
+ for i in "${checkdirs[@]}"; do
+ printf 'checking directory "%s" ... ' "$i"
+ if [[ -d "$i" ]]; then
+ printf 'exists.\n'
+ else
+ printf 'creating ... '
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running. Skipping.\n'
+ else
+ if mkdir -p "$i" >/dev/null 2>&1; then
+ printf 'success.\n'
+ else
+ printf 'failed. Exiting.\n'
+ let "E |= $E_mkdir_subdirs_failed"
+ error_handler
+ fi
+ fi
+ fi
+ done
+
+ fi
+ # <- check subdirs
+
+}
+
+# @info: If CONFIG_mysql_dump_latest is set to 'yes', the directory ${CONFIG_backup_dir}"/latest will
+# be cleaned.
+# @args: (none)
+# @deps: load_default_config
+cleanup_latest () {
+ # -> latest cleanup
+ if [[ "${CONFIG_mysql_dump_latest}" = "yes" ]]; then
+ printf 'Cleaning up latest directory ... '
+ if (( $CONFIG_dryrun )); then
+ printf 'dry-running. Skipping.\n'
+ else
+ if rm -f "${CONFIG_backup_dir}"/latest/* >/dev/null 2>&1; then
+ printf 'success.\n'
+ else
+ printf 'failed. Continuing anyway, activating Note-Flag.\n'
+ let "N |= $N_latest_cleanup_failed"
+ fi
+ fi
+ fi
+ # <- latest cleanup
+}
+
+# @info: Checks for dependencies in form of external programs, that need to be available when running
+# this program.
+# @args: (none)
+# @deps: load_default_config
+check_dependencies () {
+ echo
+ echo "# Testing for installed programs"
+ dependencies=( 'mysql' 'mysqldump' )
+
+ if [[ "x$CONFIG_multicore" = 'xyes' ]]; then
+
+ if [[ "x$CONFIG_mysql_dump_compression" = 'xbzip2' ]]; then
+ if type pbzip2 &>/dev/null; then
+ echo "pbzip2 ... found."
+ else
+ CONFIG_multicore='no' # turn off multicore support, since the program isn't there
+ echo "WARNING: Turning off multicore support, since pbzip2 isn't there."
+ fi
+ elif [[ "x$CONFIG_mysql_dump_compression" = 'xgzip' ]]; then
+ if type pigz &>/dev/null; then
+ echo "pigz ... found."
+ else
+ CONFIG_multicore='no' # turn off multicore support, since the program isn't there
+ echo "WARNING: Turning off multicore support, since pigz isn't there."
+ fi
+ fi
+
+ else
+ [[ "x$CONFIG_mysql_dump_compression" = 'xbzip2' ]] && dependencies=("${dependencies[@]}" 'bzip2' )
+ [[ "x$CONFIG_mysql_dump_compression" = 'xgzip' ]] && dependencies=("${dependencies[@]}" 'gzip' )
+ fi
+
+ if [[ "x$CONFIG_mailcontent" = 'xlog' || "x$CONFIG_mailcontent" = 'xquiet' ]]; then
+ dependencies=( "${dependencies[@]}" 'mail' )
+ elif [[ "x$CONFIG_mailcontent" = 'xfiles' ]]; then
+ dependencies=( "${dependencies[@]}" 'mail' )
+ if [[ "x$CONFIG_mail_use_uuencoded_attachments" != 'xyes' ]]; then
+ dependencies=( "${dependencies[@]}" 'mutt' )
+ fi
+ fi
+
+ for i in "${dependencies[@]}"; do
+ printf '%s ... ' "$i"
+ if type "$i" &>/dev/null; then
+ printf 'found.\n'
+ else
+ printf 'not found. Aborting.\n';
+ let "E |= $E_missing_deps"
+ error_handler
+ fi
+ done
+ echo
+}
+
+# @info: Get database list and remove excluded ones.
+# @args: (none)
+# @deps: load_default_config, error_handler
+#
+# alldbnames = array of all databases
+# empty? -> error
+# remove excludes from array alldbnames
+# CONFIG_db_names empty? -> set to alldbnames
+# CONFIG_db_month_names empty? -> set to alldbnames
+#
+parse_databases() {
+ # bash 4.x version
+ #mapfile -t alldbnames < <(mysql --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" --batch --skip-column-names -e "show databases")
+ alldbnames=()
+
+ printf "# Parsing databases ... "
+ # bash 3.0
+ local i;i=0;
+ while read -r; do alldbnames[i++]="$REPLY"; done < <(mysql --user="${CONFIG_mysql_dump_username}" --password="${CONFIG_mysql_dump_password}" --host="${CONFIG_mysql_dump_host}" "${mysql_opt[@]}" --batch --skip-column-names -e "show databases")
+ unset i
+
+ # mkfifo foo || exit; trap 'rm -f foo' EXIT
+
+ ((! "${#alldbnames[@]}" )) && { let "E |= $E_db_empty"; error_handler; }
+
+ # -> remove excluded dbs from list
+ for exclude in "${CONFIG_db_exclude[@]}"; do
+ for i in "${!alldbnames[@]}"; do if [[ "x${alldbnames[$i]}" = "x${exclude}" ]]; then unset 'alldbnames[i]'; fi; done
+ done
+ # <- remove excluded dbs from list
+
+ # check for empty array lists and copy all dbs
+ ((! ${#CONFIG_db_names[@]})) && CONFIG_db_names=( "${alldbnames[@]}" )
+ ((! ${#CONFIG_db_month_names[@]})) && CONFIG_db_month_names=( "${alldbnames[@]}" )
+ printf "done.\n"
+}
+
+# @return: true if locked, false otherwise
+# @param: manifest_file
+status_manifest() {
+ if [[ -e "$1".lock ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+# @return: true if successfully created lock file, else false
+# @param: manifest_file
+lock_manifest() {
+ if status_manifest "$1"; then
+ return 0
+ else
+ if touch "$1".lock &>/dev/null; then
+ return 0
+ else
+ return 1
+ fi
+ fi
+}
+# @return: true if successfully removed lock file, else false
+# @param: manifest_file
+unlock_manifest() {
+ if status_manifest "$1"; then
+ if rm "$1".lock &>/dev/null; then
+ return 0
+ else
+ return 1
+ fi
+ else
+ return 0
+ fi
+}
+# @return: true if unlock_manifest or lock_manifest, depending on status_manifest, return true, else false
+# @param: manifest_file
+toggle_manifest() {
+ if status_manifest "$1"; then
+ unlock_manifest "$1" && return 0 || return 1
+ else
+ lock_manifest "$1" && return 0 || return 1
+ fi
+}
+
+# expects manifest_entry_to_check to be an array with four entries
+# @param: manifest_file
+# return: 0, if all is okay
+# 1, file doesn't exist - removed entry from manifest
+# 2, file doesn't exist - tried to remove entry from manifest, but failed
+check_manifest_entry() {
+ local entry_md5sum
+ [[ ! -e "${manifest_entry_to_check[0]}" ]] && { rm_manifest_entry_by_filename "${manifest_entry_to_check[0]}" 1 && return 1 || return 2; }
+ entry_md5sum="$(md5sum "${manifest_entry_to_check[0]}" | awk '{print $1}')"
+ if [[ "${entry_md5sum}" != "${manifest_entry_to_check[1]}" ]]; then
+ printf 'g/%s/s//%s/g\nw\nq' "${manifest_entry_to_check[1]}" "${entry_md5sum}" | ed -s "$1"
+ else
+ return 0
+ fi
+}
+
+# parse manifest file and collect entries in manifest_array
+# @param: manifest_file
+#
+# sort manifest file after first field (filename) -> read this line by line
+# check if line matches regexp || add to array manifest_entries_corrupted && continue
+# split lines at tab character \t and put into array line_arr
+# check manifest entry
+# -> file does not exist -> remove entry from manifest; continue no matter if this succeeds or not
+# loop through previous entries in the manifest
+# filename already in there? remove all entries with the same filename but the one that is already in the array
+# md5sum has already occured?
+# if size = 0
+# then don't compare
+# else
+# request user action by adding entry to array manifest_entries_user_action_required with information, that identical files exist && continue 2
+# fi
+# add entry to manifest_array
+#
+parse_manifest() {
+ local i n re line line_arr check
+ unset manifest_array; manifest_array=()
+ local tmp_md5sum
+ # array ( filename_1, md5sum_1, id_1[, rel_id_1] ), ... )
+ # reserving 4 members for each entry, thus each filename entry in the array has array key 4(n-1)+1
+ (( $CONFIG_debug )) && echo ">>>>>>> Parsing manifest file: $1"
+ n=1
+ [[ -s "$1" ]] &&
+ while read line
+ do
+ # ANY CHANGES INSIDE HERE ON THE MANIFEST_FILE HAVE NO IMPACT ON THE LINES WE LOOP OVER; THE sort COMMAND READS THE FILE ENTIRELY AT THE BEGINNING AND PASSES THE OUTPUT TO THE LOOP
+ # check if line has expected format, i.e. check against regular expression
+ re=$'^[^\t]*\tmd5sum\t[^\t]*\tdiff_id\t[A-Za-z0-9]{8}\trel_id\t(0|[A-Za-z0-9]{8})\tdb\t[^\t]*$'
+ [[ $line =~ $re ]] || { echo "Corrupted line: $line"; manifest_entries_corrupted=( "${manifest_entries_corrupted[@]}" "$1" "$line" ); continue; }
+ IFS=$'\t' read -ra line_arr <<< "$line"
+
+ # prepare array of the current line
+ manifest_entry_to_check=()
+ for ((i=0;i<${#line_arr[@]};i=$i+2)); do
+ manifest_entry_to_check[i/2]="${line_arr[i]}"
+ done
+ # check manifest entry, which uses the array manifest_entry_to_check
+ check_manifest_entry "$1"
+ check=$?
+ case $check in
+ 1) (( $CONFIG_debug )) && echo "File for manifest entry $line does not exist. Entry removed."
+ continue # file doesn't exist - removed entry from manifest
+ break;;
+ 2) (( $CONFIG_debug )) && echo "File for manifest entry $line does not exist. Failed to remove the entry."
+ continue # file doesn't exist - tried to remove entry from manifest, but failed
+ break;;
+ esac
+
+ # loop through the manifest_array, as it has been filled by now and check if an entry already exists with the same values
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "x${manifest_array[i]}" = "x${line_arr[0]}" ]]; then # found entry with the same filename
+ (( $CONFIG_debug )) && echo "Found multiple entries with the same filename. Removing all but the first-found entry from manifest."
+ # remove all entries with this filename and add a new one based on the values of the item already in the array
+ rm_manifest_entry_by_filename "$1" "${manifest_array[i]}" 1 && add_manifest_entry "$1" "${manifest_array[i]}" "${manifest_array[i+3]}"
+ continue 2 # the original entry, to which we compared, is already in the manifest_array; no matter if this is resolved or not, we don't
+ # need to add this entry to the manifest_array
+ elif [[ "x${manifest_array[i+1]}" = "x${line_arr[2]}" ]]; then # found entry with different filename but same md5sum - file copied and renamed?!
+ if [[ ! -s "${line_arr[0]}" ]]; then # empty file - don't start to compare md5sums ...
+ (( $CONFIG_debug )) && echo "Found empty file ${line_arr[0]}."
+ else
+ (( $CONFIG_debug )) && echo "Found multiple entries with the same md5sum but different filename."
+ (( $CONFIG_debug )) && echo -e ">> fname_manifest:\t${manifest_array[i]}\t${manifest_array[i+1]}\n>> fname_line:\t\t${line_arr[0]}\t${line_arr[2]}"
+ if [[ "x${line_arr[6]}" != "x0" ]]; then
+ if [[ "x${manifest_array[i+3]}" = "x${line_arr[6]}" ]]; then # parent id is the same; TODO inform user of this predicament and suggest solution
+ manifest_entries_user_action_required=( "${manifest_entries_user_action_required[@]}" "$1" "${manifest_array[i]}" "The file has an identical copy with the same parent id. If you don't know why it exists, it is safe to remove it." )
+ continue 2
+ else
+ manifest_entries_user_action_required=( "${manifest_entries_user_action_required[@]}" "$1" "${manifest_array[i]}" "The file has an identical copy with different parent id. This should not happen. Remove the file, which is not the correct follow-up to the previous differential or master backup." )
+ continue 2
+ fi
+ fi
+ fi
+ fi
+ done
+
+ # add entry to manifest array
+ for ((i=0;i<${#line_arr[@]};i=$i+2)); do
+ manifest_array[(n-1)*${fields}+i/2]="${line_arr[i]}"
+ #echo "manifest array key $((($n-1)*4+$i/2)) with value ${line_arr[i]}"
+ done
+
+ ((n++))
+ done < <(sort -t $'\t' -k"1" "$1")
+ (( $CONFIG_debug )) && echo "<<<<<<< # manifest entries: $((${#manifest_array[@]}/$fields))"
+ (( $CONFIG_debug )) && echo "<<<<<<< FINISHED"
+ return 0
+}
+
+# get_manifest_entry_by_* PATTERN [regexp]
+# if second parameter 'regexp' (string!) is passed, PATTERN will be matched as regular expression
+get_manifest_entry_by_filename() {
+ local i
+ if [[ "x$2" = "xregexp" ]]; then
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "${manifest_array[i]}" =~ $1 ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ else
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "x${manifest_array[i]}" = "x$1" ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ fi
+ return 1
+}
+get_manifest_entry_by_md5sum() {
+ local i
+ if [[ "x$2" = "xregexp" ]]; then
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "${manifest_array[i+1]}" =~ $1 ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ else
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "x${manifest_array[i+1]}" = "x$1" ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ fi
+ return 1
+}
+get_manifest_entry_by_id() {
+ local i
+ if [[ "x$2" = "xregexp" ]]; then
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "${manifest_array[i+2]}" =~ $1 ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ else
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "x${manifest_array[i+2]}" = "x$1" ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ fi
+ return 1
+}
+get_manifest_entry_by_rel_id() {
+ local i
+ if [[ "x$2" = "xregexp" ]]; then
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "${manifest_array[i+3]}" =~ $1 ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ else
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "x${manifest_array[i+3]}" = "x$1" ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ fi
+ return 1
+}
+get_manifest_entry_by_db() {
+ local i
+ if [[ "x$2" = "xregexp" ]]; then
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "${manifest_array[i+4]}" =~ $1 ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ else
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "x${manifest_array[i+4]}" = "x$1" ]]; then
+ manifest_entry=( "${manifest_array[i]}" "${manifest_array[i+1]}" "${manifest_array[i+2]}" "${manifest_array[i+3]}" "${manifest_array[i+4]}" )
+ return 0
+ break;
+ fi
+ done
+ fi
+ return 1
+}
+
+# @params: db, master/diff (0,1)
+# @return: 2: no entries in manifest for the specified database 'db'
+# 1: could not get manifest element by filename
+# 0: all fine, match is in array 'manifest_entry'
+get_latest_manifest_entry_for_db() {
+ local db_array newarray i
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if (( $2 )); then # latest differential or master backup, i.e. just take the latest one!
+ if [[ "x${manifest_array[i+4]}" = "x$1" ]]; then
+ db_array=( "${db_array[@]}" "${manifest_array[i]}" )
+ fi
+ else # latest master backup, pid=0
+ if [[ "x${manifest_array[i+4]}" = "x$1" && "x${manifest_array[i+3]}" = "x0" ]]; then
+ db_array=( "${db_array[@]}" "${manifest_array[i]}")
+ fi
+ fi
+ done
+ if (( "${#db_array[@]}" == 0 )); then return 2;
+ else
+ #newarray=(); while IFS= read -r -d '' line; do newarray+=("$line"); done < <(printf '%s\0' "${db_array[@]}" | sort -z)
+ get_manifest_entry_by_filename "${db_array[@]:(-1)}" # last entry of db_array, has, due to the way sort works, to be the latest one
+ return $?
+ fi
+}
+
+# @params: manifest_file filename/md5sum/id/rel_id [1(=don't parse manifest after finished)]
+# if second parameters
+#
+# lock manifest -> use awk, print all lines that don't have second parameter at the appropriate field -> unlock manifest
+# param3=0 -> parse manifest
+#
+rm_manifest_entry_by_filename() {
+ lock_manifest "$1" && awk -F"\t" -v v="$2" '$1 != v' "$1" > "$1".tmp && mv "$1".tmp "$1" && unlock_manifest "$1" || return 1
+ (( "$3" )) || parse_manifest "$1"
+ return 0
+}
+rm_manifest_entry_by_md5sum() {
+ lock_manifest "$1" && awk -F"\t" -v v="$2" '$3 != v' "$1" > "$1".tmp && mv "$1".tmp "$1" && unlock_manifest "$1" || return 1
+ (( "$3" )) || parse_manifest "$1"
+ return 0
+}
+rm_manifest_entry_by_id() {
+ lock_manifest "$1" && awk -F"\t" -v v="$2" '$5 != v' "$1" > "$1".tmp && mv "$1".tmp "$1" && unlock_manifest "$1" || return 1
+ (( "$3" )) || parse_manifest "$1"
+ return 0
+}
+rm_manifest_entry_by_rel_id() {
+ lock_manifest "$1" && awk -F"\t" -v v="$2" '$7 != v' "$1" > "$1".tmp && mv "$1".tmp "$1" && unlock_manifest "$1" || return 1
+ (( "$3" )) || parse_manifest "$1"
+ return 0
+}
+rm_manifest_entry_by_db() {
+ lock_manifest "$1" && awk -F"\t" -v v="$2" '$9 != v' "$1" > "$1".tmp && mv "$1".tmp "$1" && unlock_manifest "$1" || return 1
+ (( "$3" )) || parse_manifest "$1"
+ return 0
+}
+
+# parameters: manifest_file, filename, parent_id, db
+add_manifest_entry() {
+ local md5sum
+ local id
+ local filename
+ local parent_id
+ local db
+ filename="$2"
+ parent_id="$3"
+ db="$4"
+ lock_manifest "$1" || return 1
+ id="${filename%.@(diff|sql)*}"
+ id="${id:(-8):8}"
+ #id="$(echo $filename | sed -re 's/.*_[0-9]{2}h[0-9]{2}m_([^\.]*)\..*/\1/')"
+ md5sum="$(md5sum "$filename" | awk '{print $1}')"
+ if [[ "x$parent_id" = 'x' ]]; then
+ echo -e "${filename}\tmd5sum\t${md5sum}\tdiff_id\t${id}\trel_id\t0\tdb\t${db}" >> "$1"
+ else
+ echo -e "${filename}\tmd5sum\t${md5sum}\tdiff_id\t${id}\trel_id\t${parent_id}\tdb\t${db}" >> "$1"
+ fi
+ unlock_manifest "$1" || return 1
+}
+
+# @info: Echos number of manifest entries.
+num_manifest_entries() {
+ echo "$((${#manifest_array[@]}/$fields))"
+}
+
+# @info: Test if a value is in the array testarray
+# @param: value
+# @var in_array_index: array index of the first match
+# @return 0 if a match was found, otherwise 1
+in_array() {
+ local j
+ for ((j=0;j<"${#testarray[@]}";j++)); do
+ if [[ "x${testarray[j]}" = "x$1" ]]; then
+ in_array_index=$j
+ return 0
+ fi
+ done
+ return 1
+}
+
+# @param: clear(0/1), meta_information, list_value1, list_value2, ...
+extended_select() {
+ local a c k m i r r_number meta_information choice selection do_clear
+ meta_information="$2"
+ do_clear="$1"
+ shift 2
+ declare -a list=("$@")
+ selection=()
+ # BEGIN _select_filenames
+ #tput sc
+ while true; do
+ if (( $do_clear )); then
+ clear
+ else
+ : #tput rc
+ fi
+ declare -a testarray=("${selection[@]}")
+ echo "Selection for <$meta_information>"
+ echo "Notation: 1,2-4,-5,-6-9 or * or -* ('-' will remove selections)."
+ # print options
+
+ for ((i=0;i<"${#list[@]}";i++)); do
+ if in_array $i; then
+ echo -e "$i) [+]\t${list[i]}"
+ else
+ echo -e "$i) [ ]\t${list[i]}"
+ fi
+ done
+
+ echo -e "$i)\tDONE"
+ done_id=$i
+
+ min=0
+ max=${#list[@]} # we have to account for the last possible number of DONE
+ # evaluate response
+ while true; do
+ printf '#? '
+ read choice
+ r='^((-?[0-9]+(-[0-9]+)?,)*-?[0-9]+(-[0-9]+)?|-?\*)$'
+ [[ $choice =~ $r ]] || continue
+ if [[ "x$choice" = 'x*' ]]; then
+ unset m
+ for ((m=0;m<"${#list[@]}";m++)); do
+ selection=("${selection[@]}" "$m")
+ done
+ continue 2
+ elif [[ "x$choice" = 'x-*' ]]; then
+ selection=()
+ continue 2
+ else
+ unset string num1 num2 op op_rm
+ r_number='^[0-9]$'
+
+ # BEGIN process_choice
+ for ((a=0;a<${#choice};a++))
+ do
+ c="${choice:a:1}"
+ declare -a testarray=("${selection[@]}")
+
+ if (( ${#string} == 0 )) && [[ "x$c" = "x-" ]] && ! (($op)); then
+ op_rm=1
+ continue
+ elif [[ $c =~ $r ]]; then
+ string=${string}"$c"
+ if (( $a == (${#choice}-1) )); then # last character
+ # we have a A-B case
+ if (($op)); then
+ num2="$string"
+ unset k
+ for ((k=$num1;k<=$num2;k++)); do
+ (( $k >= $min )) && (( $k <= $max )) || continue
+ if ! in_array $k; then
+ selection=("${selection[@]}" $k)
+ else
+ if (( $op_rm )); then
+ new_array=()
+ for ((m="$((${#selection[@]}-1))";m>=0;m--)); do
+ if [[ "x${selection[m]}" != "x$k" ]]; then
+ new_array=("${new_array[@]}" "${selection[m]}")
+ fi
+ done
+ declare -a selection=("${new_array[@]}")
+ fi
+ fi
+ done
+ unset op op_rm num1 num2 string
+ continue
+ else
+ (( $string >= $min )) && (( $string <= $max )) || continue
+ if ! in_array "$string"; then
+ selection=("${selection[@]}" "$string")
+ else
+ if (($op_rm)); then
+ unset m
+ new_array=()
+ for ((m=0;m<"${#selection[@]}";m++)); do
+ if [[ "x${selection[m]}" != "x$string" ]]; then
+ new_array=("${new_array[@]}" "${selection[m]}")
+ fi
+ done
+ declare -a selection=("${new_array[@]}")
+ fi
+ fi
+ fi
+ else
+ continue
+ fi
+ elif [[ "x$c" = "x-" ]]; then
+ num1="$string"
+ unset string
+ op=1
+ if (( $a == (${#choice}-1) )); then
+ break
+ else
+ continue
+ fi
+ elif [[ "x$c" = "x," ]]; then
+ # we have a A-B case
+ if (($op)); then
+ num2="$string"
+ unset k
+ for ((k=$num1;k<=$num2;k++)); do
+ (( $k >= $min )) && (( $k <= $max )) || continue
+ if ! in_array $k; then
+ selection=("${selection[@]}" $k)
+ else
+ if (( $op_rm )); then
+ unset m
+ new_array=()
+ for ((m=0;m<"${#selection[@]}";m++)); do
+ if [[ "x${selection[m]}" != "x$k" ]]; then
+ new_array=("${new_array[@]}" "${selection[m]}")
+ fi
+ done
+ declare -a selection=("${new_array[@]}")
+ fi
+ fi
+ done
+ unset op op_rm num1 num2 string
+ continue
+ else # it's just a single number
+ (( $string >= $min )) && (( $string <= $max )) || { unset op op_rm num1 num2 string; continue; }
+ if ! in_array "$string"; then
+ selection=("${selection[@]}" "$string")
+ else
+ if (($op_rm)); then
+ unset m
+ new_array=()
+ for ((m=0;m<"${#selection[@]}";m++)); do
+ if [[ "x${selection[m]}" != "x$string" ]]; then
+ new_array=("${new_array[@]}" "${selection[m]}")
+ fi
+ done
+ declare -a selection=("${new_array[@]}")
+ fi
+ fi
+ unset op op_rm num1 num2 string
+ continue
+ fi
+ else
+ continue 2; # this should not happen
+ fi
+ done
+ # END process_choice
+
+ declare -a testarray=("${selection[@]}")
+ if in_array "$done_id"; then
+ break 2
+ else
+ continue 2
+ fi
+ fi
+ done
+ done
+ extended_select_return=()
+ extended_select_return_id=()
+ for i in "${selection[@]}"; do
+ [[ "x$i" != "x$done_id" ]] && { extended_select_return=("${extended_select_return[@]}" "${list[i]}"); extended_select_return_id=("${extended_select_return_id[@]}" "$i"); }
+ done
+
+}
+
+# END _functions
+
+
+# BEGIN _methods
+
+# @info: Backup method
+method_backup () {
+ manifest_entries_corrupted=()
+ manifest_entries_user_action_required=()
+
+
+ # END __FUNCTIONS
+ ##############################################################################################################
+ # BEGIN __STARTUP
+
+ load_default_config
+
+ trap mail_cleanup EXIT SIGHUP SIGINT SIGQUIT SIGTERM
+ if [[ -r "${CONFIG_configfile}" ]]; then source "${CONFIG_configfile}"; echo "Parsed config file \"${CONFIG_configfile}\""; else let "N |= $N_config_file_missing"; fi; echo
+ if (( $opt_flag_config_file )); then if [[ -r "${opt_config_file}" ]]; then source "${opt_config_file}"; let "N |= $N_arg_conffile_parsed"; else let "N |= $N_arg_conffile_unreadable"; fi; else let "N |= $N_too_many_args"; fi
+
+ (( $CONFIG_dryrun )) && {
+ echo "NOTE: We are dry-running. That means, that the script just shows you what it would do, if it were operating normally."
+ echo "THE PRINTED COMMANDS CAN'T BE COPIED AND EXECUTED IF THERE ARE SPECIAL CHARACTERS, SPACES, ETC. IN THERE THAT WOULD NEED TO BE PROPERLY QUOTED IN ORDER TO WORK. THESE WERE CORRECTLY QUOTED FOR THE OUTPUT COMMAND, BUT CAN'T BE SEEN NOW."
+ echo
+ }
+
+ export LC_ALL=C
+ PROGNAME=`basename $0`
+ PATH=${PATH}:/usr/local/bin:/usr/bin:/bin:/usr/local/mysql/bin
+ version=3.0
+ fields=5 # manifest fields
+
+ directory_checks_enable_logging
+ cleanup_latest
+ set_datetime_vars
+ check_dependencies # check for required programs
+ parse_configuration # parse configuration and set variables appropriately
+
+
+ # END __STARTUP
+ #--------------------------------------------------------------------------------------------------------------------------------------
+ # BEGIN __PREPARE
+
+ backupfiles=()
+ parse_databases
+
+ # debug output of variables
+ (( $CONFIG_debug )) && { echo; echo "# DEBUG: printing all current variables"; declare -p | egrep -o '.* (CONFIG_[a-z_]*|opt|mysql_opt|opt_dbstatus|opt_fullschema)=.*'; echo; }
+ (( $CONFIG_debug )) && { echo "DEBUG: before pre-backup"; ( IFS=,; echo "DEBUG: CONFIG_db_names '${CONFIG_db_names[*]}'" ); ( IFS=,; echo "DEBUG: CONFIG_db_month_names '${CONFIG_db_month_names[*]}'" );}
+
+
+ # END __PREPARE
+ #--------------------------------------------------------------------------------------------------------------------------------------
+ # BEGIN __MAIN
+
+ ### filename formats
+ ##
+ ## example date values:
+ # 14'th of August (08) 2011
+ # week number: 32
+ # Sunday (date_dayno_of_week: 7)
+ ##
+ ## separate db's:
+ # monthly_DBNAME_2011-08-14_18h12m_August.sql(.enc).{gz,bzip2}
+ # weekly_DBNAME_2011-08-14_18h12m_32.sql(.enc).{gz,bzip2}
+ # daily_DBNAME_2011-08-14_18h12m_7.sql(.enc).{gz,bzip2}
+ ## all-databases:
+ # monthly_all-databases_DBNAME_2011-08-14_18h12m_August.sql(.enc).{gz,bzip2}
+ # weekly_all-databases_DBNAME_2011-08-14_18h12m_32.sql(.enc).{gz,bzip2}
+ # daily_all-databases_DBNAME_2011-08-14_18h12m_7.sql(.enc).{gz,bzip2}
+
+ echo "======================================================================"
+ echo "AutoMySQLBackup version ${version}"
+ echo "http://sourceforge.net/projects/automysqlbackup/"
+ echo
+ echo "Backup of Database Server - ${CONFIG_mysql_dump_host_friendly:-$CONFIG_mysql_dump_host}"
+ ( IFS=,; echo "Databases - ${CONFIG_db_names[*]}" )
+ ( IFS=,; echo "Databases (monthly) - ${CONFIG_db_month_names[*]}" )
+ echo "======================================================================"
+
+
+ # -> preback commands
+ if [[ "${CONFIG_prebackup}" ]]; then
+ echo "======================================================================"
+ echo "Prebackup command output."
+ echo
+ source ${CONFIG_prebackup}
+ echo
+ echo "======================================================================"
+ echo
+ fi
+ # <- preback commands
+
+ # -> backup local files
+ if [[ "${CONFIG_backup_local_files[@]}" ]] && [[ ${CONFIG_do_weekly} != 0 && ${date_dayno_of_week} = ${CONFIG_do_weekly} ]] && (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/backup_local_files/bcf_weekly_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m_${date_weekno}.tar${suffix}"); ((! ${#f[@]}))); then
+ echo "======================================================================"
+ echo "Backup local files. Doing this weekly on CONFIG_do_weekly."
+ echo
+ backup_local_files "${CONFIG_backup_dir}/backup_local_files/bcf_weekly_${datetimestamp}_${date_weekno}.tar"
+ tmp_flags=$?; var=;
+ if (( $? == 0 )); then
+ echo "success!"
+ backupfiles=( "${backupfiles[@]}" "${CONFIG_backup_dir}/backup_local_files/bcf_weekly_${datetimestamp}_${date_weekno}.tar" )
+ else
+ let "E |= $E_backup_local_failed"
+ echo "failed!"
+ fi
+ echo
+ echo "======================================================================"
+ echo
+ fi
+ # <- backup local files
+
+ # -> dump full schema
+ if [[ "${CONFIG_mysql_dump_full_schema}" = 'yes' ]]; then
+ echo "======================================================================"
+ echo "Dump full schema."
+ echo
+
+ # monthly
+ if (( ${CONFIG_do_monthly} != 0 && (${date_day_of_month} == ${CONFIG_do_monthly} || $date_day_of_month == $date_lastday_of_this_month && $date_lastday_of_this_month < ${CONFIG_do_monthly}) )) && (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/fullschema/fullschema_monthly_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m_${date_month}.sql${suffix}"); ((! ${#f[@]}))); then
+ fullschema "${CONFIG_backup_dir}/fullschema/fullschema_monthly_${datetimestamp}_${date_month}.sql"
+ if (( $? == 0 )); then
+ echo "Rotating $(( ${CONFIG_rotation_monthly}/31 )) month backups for ${mdb}"
+ if (( $CONFIG_dryrun )); then
+ find "${CONFIG_backup_dir}/fullschema" -mtime +"${CONFIG_rotation_monthly}" -type f -name 'fullschema_monthly*' -exec echo "dry-running: rm" {} \;
+ else
+ find "${CONFIG_backup_dir}/fullschema" -mtime +"${CONFIG_rotation_monthly}" -type f -name 'fullschema_monthly*' -exec rm {} \;
+ fi
+ files_postprocessing "${CONFIG_backup_dir}/fullschema/fullschema_monthly_${datetimestamp}_${date_month}.sql${suffix}"
+ tmp_flags=$?; var=; (( $tmp_flags & $flags_files_postprocessing_success_encrypt )) && var=.enc
+ backupfiles=( "${backupfiles[@]}" "${CONFIG_backup_dir}/fullschema/fullschema_monthly_${datetimestamp}_${date_month}.sql${suffix}${var}" )
+ else
+ let "E |= $E_dump_fullschema_failed"
+ fi
+ fi
+
+ # weekly
+ if [[ ${CONFIG_do_weekly} != 0 && ${date_dayno_of_week} = ${CONFIG_do_weekly} ]] && (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/fullschema/fullschema_weekly_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m_${date_weekno}.sql${suffix}"); ((! ${#f[@]}))); then
+ fullschema "${CONFIG_backup_dir}/fullschema/fullschema_weekly_${datetimestamp}_${date_weekno}.sql"
+ if (( $? == 0 )); then
+ echo "Rotating $(( ${CONFIG_rotation_monthly}/31 )) month backups for ${mdb}"
+ if (( $CONFIG_dryrun )); then
+ find "${CONFIG_backup_dir}/fullschema" -mtime +"${CONFIG_rotation_weekly}" -type f -name 'fullschema_weekly*' -exec echo "dry-running: rm" {} \;
+ else
+ find "${CONFIG_backup_dir}/fullschema" -mtime +"${CONFIG_rotation_weekly}" -type f -name 'fullschema_weekly*' -exec rm {} \;
+ fi
+ files_postprocessing "${CONFIG_backup_dir}/fullschema/fullschema_weekly_${datetimestamp}_${date_weekno}.sql${suffix}"
+ tmp_flags=$?; var=; (( $tmp_flags & $flags_files_postprocessing_success_encrypt )) && var=.enc
+ backupfiles=( "${backupfiles[@]}" "${CONFIG_backup_dir}/fullschema/fullschema_weekly_${datetimestamp}_${date_weekno}.sql${suffix}${var}" )
+ else
+ let "E |= $E_dump_fullschema_failed"
+ fi
+ fi
+
+ # daily
+ fullschema "${CONFIG_backup_dir}/fullschema/fullschema_daily_${datetimestamp}_${date_day_of_week}.sql"
+ if (( $? == 0 )); then
+ echo "Rotating $(( ${CONFIG_rotation_monthly}/31 )) month backups for ${mdb}"
+ if (( $CONFIG_dryrun )); then
+ find "${CONFIG_backup_dir}/fullschema" -mtime +"${CONFIG_rotation_daily}" -type f -name 'fullschema_daily*' -exec echo "dry-running: rm" {} \;
+ else
+ find "${CONFIG_backup_dir}/fullschema" -mtime +"${CONFIG_rotation_daily}" -type f -name 'fullschema_daily*' -exec rm {} \;
+ fi
+ files_postprocessing "${CONFIG_backup_dir}/fullschema/fullschema_daily_${datetimestamp}_${date_day_of_week}.sql${suffix}"
+ tmp_flags=$?; var=; (( $tmp_flags & $flags_files_postprocessing_success_encrypt )) && var=.enc
+ backupfiles=( "${backupfiles[@]}" "${CONFIG_backup_dir}/fullschema/fullschema_daily_${datetimestamp}_${date_day_of_week}.sql${suffix}${var}" )
+ else
+ let "E |= $E_dump_fullschema_failed"
+ fi
+ echo
+ echo "======================================================================"
+ echo
+
+ fi
+ # <- dump full schema
+
+ # -> dump status
+ if [[ "${CONFIG_mysql_dump_dbstatus}" = 'yes' ]]; then
+ echo "======================================================================"
+ echo "Dump status."
+ echo
+
+ # monthly
+ if (( ${CONFIG_do_monthly} != 0 && (${date_day_of_month} == ${CONFIG_do_monthly} || $date_day_of_month == $date_lastday_of_this_month && $date_lastday_of_this_month < ${CONFIG_do_monthly}) )) && (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/status/status_monthly_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m_${date_month}.txt${suffix}"); ((! ${#f[@]}))); then
+ dbstatus "${CONFIG_backup_dir}/status/status_monthly_${datetimestamp}_${date_month}.txt"
+ if (( $? == 0 )); then
+ echo "Rotating $(( ${CONFIG_rotation_monthly}/31 )) month backups for ${mdb}"
+ if (( $CONFIG_dryrun )); then
+ find "${CONFIG_backup_dir}/status" -mtime +"${CONFIG_rotation_monthly}" -type f -name 'status_monthly*' -exec echo "dry-running: rm" {} \;
+ else
+ find "${CONFIG_backup_dir}/status" -mtime +"${CONFIG_rotation_monthly}" -type f -name 'status_monthly*' -exec rm {} \;
+ fi
+ files_postprocessing "${CONFIG_backup_dir}/status/status_monthly_${datetimestamp}_${date_month}.txt${suffix}"
+ tmp_flags=$?; var=; (( $tmp_flags & $flags_files_postprocessing_success_encrypt )) && var=.enc
+ backupfiles=( "${backupfiles[@]}" "${CONFIG_backup_dir}/status/status_monthly_${datetimestamp}_${date_month}.txt${suffix}${var}" )
+ else
+ let "E |= $E_dump_status_failed"
+ fi
+ fi
+
+ # weekly
+ if [[ ${CONFIG_do_weekly} != 0 && ${date_dayno_of_week} = ${CONFIG_do_weekly} ]] && (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/status/status_weekly_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m_${date_weekno}.txt${suffix}"); ((! ${#f[@]}))); then
+ dbstatus "${CONFIG_backup_dir}/status/status_weekly_${datetimestamp}_${date_weekno}.txt"
+ if (( $? == 0 )); then
+ echo "Rotating $(( ${CONFIG_rotation_monthly}/31 )) month backups for ${mdb}"
+ if (( $CONFIG_dryrun )); then
+ find "${CONFIG_backup_dir}/status" -mtime +"${CONFIG_rotation_weekly}" -type f -name 'status_weekly*' -exec echo "dry-running: rm" {} \;
+ else
+ find "${CONFIG_backup_dir}/status" -mtime +"${CONFIG_rotation_weekly}" -type f -name 'status_weekly*' -exec rm {} \;
+ fi
+ files_postprocessing "${CONFIG_backup_dir}/status/status_weekly_${datetimestamp}_${date_weekno}.txt${suffix}"
+ tmp_flags=$?; var=; (( $tmp_flags & $flags_files_postprocessing_success_encrypt )) && var=.enc
+ backupfiles=( "${backupfiles[@]}" "${CONFIG_backup_dir}/status/status_weekly_${datetimestamp}_${date_weekno}.txt${suffix}${var}" )
+ else
+ let "E |= $E_dump_status_failed"
+ fi
+ fi
+
+ # daily
+ dbstatus "${CONFIG_backup_dir}/status/status_daily_${datetimestamp}_${date_day_of_week}.txt"
+ if (( $? == 0 )); then
+ echo "Rotating $(( ${CONFIG_rotation_monthly}/31 )) month backups for ${mdb}"
+ if (( $CONFIG_dryrun )); then
+ find "${CONFIG_backup_dir}/status" -mtime +"${CONFIG_rotation_daily}" -type f -name 'status_daily*' -exec echo "dry-running: rm" {} \;
+ else
+ find "${CONFIG_backup_dir}/status" -mtime +"${CONFIG_rotation_daily}" -type f -name 'status_daily*' -exec rm {} \;
+ fi
+ files_postprocessing "${CONFIG_backup_dir}/status/status_daily_${datetimestamp}_${date_day_of_week}.txt${suffix}"
+ tmp_flags=$?; var=; (( $tmp_flags & $flags_files_postprocessing_success_encrypt )) && var=.enc
+ backupfiles=( "${backupfiles[@]}" "${CONFIG_backup_dir}/status/status_daily_${datetimestamp}_${date_day_of_week}.txt${suffix}${var}" )
+ else
+ let "E |= $E_dump_status_failed"
+ fi
+ echo
+ echo "======================================================================"
+ echo
+
+ fi
+ # <- dump status
+
+
+ # -> BACKUP DATABASES
+ echo "Backup Start Time `date`"
+ echo "======================================================================"
+
+ ## <- monthly backup, unique per month
+ if (( ${CONFIG_do_monthly} != 0 && (${date_day_of_month} == ${CONFIG_do_monthly} || $date_day_of_month == $date_lastday_of_this_month && $date_lastday_of_this_month < ${CONFIG_do_monthly}) )); then
+ echo "Monthly Backup ..."
+ echo
+
+ subfolder="monthly"
+ prefix="monthly_"
+ midfix="_${date_month}"
+ extension=".sql"
+ rotation="${CONFIG_rotation_monthly}"
+ rotation_divisor="31"
+ rotation_string="month"
+
+ if [[ "${CONFIG_mysql_dump_use_separate_dirs}" = "yes" ]]; then
+ for db in "${CONFIG_db_month_names[@]}"; do
+ echo "Monthly Backup of Database ( ${db} )"
+ (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/${subfolder}/${db}/${prefix}${db}_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m${midfix}${extension}${suffix}"); ((${#f[@]}))) && continue
+ process_dbs "$subfolder" "$prefix" "$midfix" "$extension" "$rotation" "$rotation_divisor" "$rotation_string" 0 "$db"
+ echo ----------------------------------------------------------------------
+ done
+ else
+ echo "Monthly backup of databases ( ${CONFIG_db_month_names[@]} )."
+ (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/${subfolder}/${prefix}all-databases_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m${midfix}${extension}${suffix}"); ((${#f[@]}))) &&
+ process_dbs "$subfolder" "$prefix" "$midfix" "$extension" "$rotation" "$rotation_divisor" "$rotation_string" 1 "${CONFIG_db_month_names[@]}"
+ echo "----------------------------------------------------------------------"
+ fi
+ fi
+ ## <- monthly backup
+
+ ## <- weekly backup, unique per week
+ if (( ${CONFIG_do_weekly} != 0 && ${date_dayno_of_week} == ${CONFIG_do_weekly} )); then
+ echo "Weekly Backup ..."
+ echo
+
+ subfolder="weekly"
+ prefix="weekly_"
+ midfix="_${date_weekno}"
+ extension=".sql"
+ rotation="${CONFIG_rotation_weekly}"
+ rotation_divisor="7"
+ rotation_string="week"
+ if [[ "${CONFIG_mysql_dump_use_separate_dirs}" = "yes" ]]; then
+ for db in "${CONFIG_db_names[@]}"; do
+ echo "Weekly Backup of Database ( ${db} )"
+ (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/${subfolder}/${db}/${prefix}${db}_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m${midfix}${extension}${suffix}"); ((${#f[@]}))) && continue
+ process_dbs "$subfolder" "$prefix" "$midfix" "$extension" "$rotation" "$rotation_divisor" "$rotation_string" 0 "$db"
+ echo "----------------------------------------------------------------------"
+ done
+ else
+ echo "Weekly backup of databases ( ${CONFIG_db_names[@]} )."
+ (shopt -s nullglob dotglob; f=("${CONFIG_backup_dir}/${subfolder}/${prefix}all-databases_${date_stamp}_"[0-9][0-9]"h"[0-9][0-9]"m${midfix}${extension}${suffix}"); ((${#f[@]}))) &&
+ process_dbs "$subfolder" "$prefix" "$midfix" "$extension" "$rotation" "$rotation_divisor" "$rotation_string" 1 "${CONFIG_db_names[@]}"
+ echo "----------------------------------------------------------------------"
+ fi
+ fi
+ ## <- weekly backup
+
+ ## -> daily backup, test (( 1 )) is always true, just creates a grouping for Kate, which can be closed ^^
+ if (( 1 )); then
+ echo "Daily Backup ..."
+ echo
+
+ subfolder="daily"
+ prefix="daily_"
+ midfix="_${date_day_of_week}"
+ extension=".sql"
+ rotation="${CONFIG_rotation_daily}"
+ rotation_divisor="1"
+ rotation_string="day"
+
+ if [[ "${CONFIG_mysql_dump_use_separate_dirs}" = "yes" ]]; then
+ for db in "${CONFIG_db_names[@]}"; do
+ echo "Daily Backup of Database ( ${db} )"
+ process_dbs "$subfolder" "$prefix" "$midfix" "$extension" "$rotation" "$rotation_divisor" "$rotation_string" 0 "$db"
+ echo "----------------------------------------------------------------------"
+ done
+ else
+ echo "Daily backup of databases ( ${CONFIG_db_names[@]} )."
+ process_dbs "$subfolder" "$prefix" "$midfix" "$extension" "$rotation" "$rotation_divisor" "$rotation_string" 1 "${CONFIG_db_names[@]}"
+ echo "----------------------------------------------------------------------"
+ fi
+ fi
+ ## <- daily backup
+
+ echo
+ echo "Backup End Time `date`"
+ echo "======================================================================"
+ # <- BACKUP DATABASES
+
+
+ # -> clean latest filenames
+ [[ "${CONFIG_mysql_dump_latest_clean_filenames}" = 'yes' ]] && find "${CONFIG_backup_dir}"/latest/ -type f -exec bash -c 'remove_datetimeinfo "$@"' -- {} \;
+ # <- clean latest filenames
+
+ # -> finished information
+ echo "Total disk space used for backup storage..."
+ echo "Size - Location"
+ echo `du -hsH "${CONFIG_backup_dir}"`
+ echo
+ echo "======================================================================"
+ # <- finished information
+
+ # -> postbackup commands
+ if [[ "${CONFIG_postbackup}" ]];then
+ echo "======================================================================"
+ echo "Postbackup command output."
+ echo
+ source ${CONFIG_postbackup}
+ echo
+ echo "======================================================================"
+ fi
+ # <- postbackup commands
+
+ if [[ -s "$log_errfile" ]];then status=1; else status=0; fi
+
+ exit ${status}
+
+}
+
+# @return variable method_list_manifest_entries_array
+method_list_manifest_entries () {
+ local files files_master files_manifest file db manifest_files manifest_files_db selected_dbs i z l master_flags master to_rm actions
+ manifest_entries_corrupted=()
+ manifest_entries_user_action_required=()
+ files=()
+ files_master=()
+ files_manifest=()
+ master_flags=0
+ let "filename_flag_encrypted=0x01"
+ let "filename_flag_gz=0x02"
+ let "filename_flag_bz2=0x04"
+ let "filename_flag_diff=0x08"
+
+ ##############################################################################################################
+ # BEGIN __STARTUP
+
+ load_default_config
+
+ if [[ -r "${CONFIG_configfile}" ]]; then source "${CONFIG_configfile}"; echo "Parsed config file \"${CONFIG_configfile}\""; else let "N |= $N_config_file_missing"; fi; echo
+ if (( $opt_flag_config_file )); then if [[ -r "${opt_config_file}" ]]; then source "${opt_config_file}"; let "N |= $N_arg_conffile_parsed"; else let "N |= $N_arg_conffile_unreadable"; fi; else let "N |= $N_too_many_args"; fi
+
+ export LC_ALL=C
+ PROGNAME=`basename $0`
+ PATH=${PATH}:/usr/local/bin:/usr/bin:/bin:/usr/local/mysql/bin
+ version=3.0
+ fields=5 # manifest fields
+
+ set_datetime_vars
+ check_dependencies # check for required programs
+ parse_configuration # parse configuration and set variables appropriately
+
+ # BEGIN __MAIN
+ unset manifest_files manifest_files_db i db
+ while IFS= read -r -d '' file; do
+ db="${file#/var/backup/db/@(daily|monthly|weekly|latest)/}";
+ db="${db%/Manifest}";
+ manifest_files_db[i]="$db"
+ manifest_files[i++]="$file"
+ done < <(find "${CONFIG_backup_dir}"/ -type f -name 'Manifest' -print0)
+
+ extended_select 0 "Databases" "${manifest_files_db[@]}"
+ declare -a selected_dbs=("${extended_select_return[@]}")
+
+ for db in "${selected_dbs[@]}"; do
+ selected_available_files=()
+ for ((i=0;i<"${#manifest_files_db[@]}";i++)); do
+ if [[ "x${manifest_files_db[i]}" = "x$db" ]]; then
+ selected_available_files[j++]="${manifest_files[i]}"
+ fi
+ done
+ if (( "${#selected_available_files[@]}" > 0 )); then
+ extended_select 1 "$db" "${selected_available_files[@]}"
+ declare -a selected_entries=("${extended_select_return[@]}")
+ if (( "${#selected_entries[@]}" > 0 )); then
+ for z in "${selected_entries[@]}"; do
+ parse_manifest "$z"
+ list=()
+ list_id=()
+ for ((i=0;$i<"${#manifest_array[@]}";i=$i+$fields)); do
+ if [[ "${manifest_array[i+3]}" != 0 ]]; then # only add differential backups
+ list=("${list[@]}" "${manifest_array[i]}")
+ list_id=("${list_id[@]}" "${manifest_array[i+3]}") # save rel_id, so we can retrieve the master backup file
+ fi
+ done
+ if (( "${#list[@]}" > 0 )); then
+ extended_select 1 "$z" "${list[@]}"
+ if (( "${#extended_select_return[@]}" > 0 )); then
+ for ((i=0;$i<"${#extended_select_return[@]}";i++)); do
+ if get_manifest_entry_by_id "${list_id[${extended_select_return_id[i]}]}"; then
+ files=("${files[@]}" "${extended_select_return[i]}")
+ files_master=("${files_master[@]}" "${manifest_entry[0]}")
+ files_manifest=("${files_manifest[@]}" "$z")
+ else
+ echo "no found master for id ${list_id[${extended_select_return_id[i]}]}"
+ fi
+ done
+ fi
+ fi
+ done
+ fi
+ fi
+ done
+ # END _select_filenames
+ declare -a method_list_manifest_entries_array=("${files[@]}")
+ declare -a method_list_manifest_entries_array_master=("${files_master[@]}")
+ declare -a method_list_manifest_entries_array_manifest=("${files_manifest[@]}")
+
+ clear
+ echo "You have selected the following files:"
+ for i in "${files[@]}"; do printf '>>> %s\n' "$i"; done
+ echo
+ actions=('diff to full' 'remove files (also from Manifest)')
+ extended_select 0 "Actions" "${actions[@]}"
+ for action in "${extended_select_return[@]}"; do
+ case "$action" in
+ 'diff to full')
+ for ((l=0;$l<"${#files[@]}";l++)); do
+ # put the unpacking of the master file in here, so that in the case of multiple diffs with the same
+ # master file don't cause the script to unpack the same master file multiple times
+ master="${files_master[l]}"
+ diff="${files[l]}"
+ FileStub="${master%.@(sql|master)*}"
+ FileExt="${master#"$FileStub"}"
+ re=".*\.enc.*"; [[ "$FileExt" =~ $re ]] && let "master_flags|=$filename_flag_encrypted"
+ re=".*\.gz.*"; [[ "$FileExt" =~ $re ]] && let "master_flags|=$filename_flag_gz"
+ re=".*\.bz2.*"; [[ "$FileExt" =~ $re ]] && let "master_flags|=$filename_flag_bz2"
+ re=".*\.diff.*"; [[ "$FileExt" =~ $re ]] && let "master_flags|=$filename_flag_diff"
+ if (( $master_flags & $filename_flag_gz )); then
+ declare -a testarray=("${to_rm[@]}")
+ if ! in_array "${master%.gz}"; then
+ gzip_compression -dc "$master" > "${master%.gz}"
+ to_rm=("${to_rm[@]}" "${master%.gz}")
+ fi
+ master="${master%.gz}"
+ elif (( $master_flags & $filename_flag_bz2 )); then
+ declare -a testarray=("${to_rm[@]}")
+ if ! in_array "${master%.bz2}"; then
+ bzip2_compression -dc "$master" > "${master%.bz2}"
+ to_rm=("${to_rm[@]}" "${master%.bz2}")
+ fi
+ master="${master%.bz2}"
+ else
+ :
+ fi
+ method_diff_to_full "$master" "$diff"
+ #printf '%s\n>>> master: %s\n>>> manifest: %s\n' "${files[l]}" "${files_master[l]}" "${files_manifest[l]}"
+ done
+ # cleanup all unpacked master files ... the unpacked diff files are cleaned up by method_diff_to_full
+ for i in "${to_rm[@]}"; do rm "$i"; done
+ ;;
+ 'remove files (also from Manifest)')
+ for ((l=0;$l<"${#files[@]}";l++)); do
+ if rm_manifest_entry_by_filename "${files_manifest[l]}" "${files[l]}" 1; then
+ rm "${files[l]}"
+ fi
+ done
+ ;;
+ *)
+ echo "Unrecognized option. This Should not happen! Error!"
+ ;;
+ esac
+ done
+
+ # END __MAIN
+}
+
+# @info: Convert a differential backup file to a full one.
+# @param: master_backup_file diff_backup_file
+method_diff_to_full() {
+ local diff full diff_flags master_flags to_rm
+ master="$1"
+ diff="$2"
+ diff_flags=0
+ master_flags=0
+ to_rm=()
+
+ FileStub="${diff%.@(sql|diff)*}"
+ FileExt="${diff#"$FileStub"}"
+ re=".*\.enc.*"; [[ "$FileExt" =~ $re ]] && let "diff_flags|=$filename_flag_encrypted"
+ re=".*\.gz.*"; [[ "$FileExt" =~ $re ]] && let "diff_flags|=$filename_flag_gz"
+ re=".*\.bz2.*"; [[ "$FileExt" =~ $re ]] && let "diff_flags|=$filename_flag_bz2"
+ re=".*\.diff.*"; [[ "$FileExt" =~ $re ]] && let "diff_flags|=$filename_flag_diff"
+ FileStub="${master%.@(sql|master)*}"
+ FileExt="${master#"$FileStub"}"
+ re=".*\.enc.*"; [[ "$FileExt" =~ $re ]] && let "master_flags|=$filename_flag_encrypted"
+ re=".*\.gz.*"; [[ "$FileExt" =~ $re ]] && let "master_flags|=$filename_flag_gz"
+ re=".*\.bz2.*"; [[ "$FileExt" =~ $re ]] && let "master_flags|=$filename_flag_bz2"
+ re=".*\.diff.*"; [[ "$FileExt" =~ $re ]] && let "master_flags|=$filename_flag_diff"
+
+ # TODO: Differential backup with encryption is not yet implemented!
+ if (( $diff_flags & $filename_flag_encrypted )); then
+ : #decrypt it
+ fi
+
+ if (( $master_flags & $filename_flag_encrypted )); then
+ : #decrypt it
+ fi
+
+ if (( $diff_flags & $filename_flag_gz )); then
+ gzip_compression -dc "$diff" > "${diff%.gz}"
+ to_rm=("${to_rm[@]}" "${diff%.gz}")
+ diff="${diff%.gz}"
+ elif (( $diff_flags & $filename_flag_bz2 )); then
+ bzip2_compression -dc "$diff" > "${diff%.bz2}"
+ to_rm=("${to_rm[@]}" "${diff%.bz2}")
+ diff="${diff%.bz2}"
+ else
+ :
+ fi
+
+ if (( $master_flags & $filename_flag_gz )); then
+ gzip_compression -dc "$master" > "${master%.gz}"
+ to_rm=("${to_rm[@]}" "${master%.gz}")
+ master="${master%.gz}"
+ elif (( $master_flags & $filename_flag_bz2 )); then
+ bzip2_compression -dc "$master" > "${master%.bz2}"
+ to_rm=("${to_rm[@]}" "${master%.bz2}")
+ master="${master%.bz2}"
+ else
+ :
+ fi
+
+ patch "$master" "$diff" -o "${diff/diff/sql}"
+
+ # cleanup
+ for i in "${to_rm[@]}"; do rm "$i"; done
+}
+
+# END _methods
+
+
+# BEGIN __main
+
+NO_ARGS=0
+E_OPTERROR=85
+
+if (( $# == $NO_ARGS )); then # Script invoked with no command-line args?
+ echo "Invoking backup method."; echo; method_backup
+fi
+
+while getopts ":c:blh" Option
+do
+ case $Option in
+ c ) echo "Using \"$OPTARG\" as optional config file."; echo; opt_config_file="$OPTARG"; opt_flag_config_file=1;;
+ b ) echo "MySQL backup method invoked."; echo; opt_flag_method_backup=1;;
+ l ) echo "List manifest entries."; echo; opt_flag_list_manifest_entries=1;;
+ h ) echo "Usage `basename $0` options -cblh"
+ echo -e "-c CONFIG_FILE\tSpecify optional config file."
+ echo -e "-b\tUse backup method."
+ echo -e "-l\tList manifest entries."
+ echo -e "-h\tShow this help."
+ exit 0;;
+ #n | o ) echo "Scenario #2: option -$Option- [OPTIND=${OPTIND}]";;
+
+ #q ) echo "Scenario #4: option -q-\
+ # with argument \"$OPTARG\" [OPTIND=${OPTIND}]";;
+ # Note that option 'q' must have an associated argument,
+ #+ otherwise it falls through to the default.
+ #r | s ) echo "Scenario #5: option -$Option-";;
+ * ) echo "Unimplemented option chosen.";; # Default.
+ esac
+done
+
+(( $opt_flag_method_backup )) && method_backup
+(( $opt_flag_list_manifest_entries )) && method_list_manifest_entries
+
+shift $(($OPTIND - 1))
+# Decrements the argument pointer so it points to next argument.
+# $1 now references the first non-option item supplied on the command-line
+#+ if one exists.
+
+# For backward compatibility. If no option items are present and only one non-option item is there, we expect it
+# to be the optional config file and invoke the backup method.
+opt_flags=( "${!opt_flag_@}" ) # array of all set variables starting with opt_flag_
+if (( $# == 1 )) && (( ${#opt_flags[@]} == 0 )); then
+ opt_config_file="$1"; opt_flag_config_file=1; method_backup
+elif (( $# == 0 )) && (( ${#opt_flags[@]} == 0 )); then
+ method_backup
+fi
+
+# END __main
\ No newline at end of file
diff --git a/mysql/conf/automysqlbackup.conf b/mysql/conf/automysqlbackup.conf
new file mode 100644
index 0000000..bc44f21
--- /dev/null
+++ b/mysql/conf/automysqlbackup.conf
@@ -0,0 +1,306 @@
+#version=3.0_rc2
+# DONT'T REMOVE THE PREVIOUS VERSION LINE!
+#
+# Uncomment to change the default values (shown after =)
+# WARNING:
+# This is not true for UMASK, CONFIG_prebackup and CONFIG_postbackup!!!
+#
+# Default values are stored in the script itself. Declarations in
+# /etc/automysqlbackup/automysqlbackup.conf will overwrite them. The
+# declarations in here will supersede all other.
+
+# Edit $PATH if mysql and mysqldump are not located in /usr/local/bin:/usr/bin:/bin:/usr/local/mysql/bin
+#PATH=${PATH}:FULL_PATH_TO_YOUR_DIR_CONTAINING_MYSQL:FULL_PATH_TO_YOUR_DIR_CONTAINING_MYSQLDUMP
+
+# Basic Settings
+
+# Username to access the MySQL server e.g. dbuser
+{%- if pillar.mysql.server.admin is defined %}
+CONFIG_mysql_dump_username='{{ pillar.mysql.server.admin.user }}'
+{%- else %}
+CONFIG_mysql_dump_username='root'
+{%- endif %}
+
+# Password to access the MySQL server e.g. password
+{%- if pillar.mysql.server.admin is defined %}
+CONFIG_mysql_dump_password='{{ pillar.mysql.server.admin.password }}'
+{%- else %}
+CONFIG_mysql_dump_password=''
+{%- endif %}
+
+# Host name (or IP address) of MySQL server e.g localhost
+CONFIG_mysql_dump_host='localhost'
+
+# "Friendly" host name of MySQL server to be used in email log
+# if unset or empty (default) will use CONFIG_mysql_dump_host instead
+#CONFIG_mysql_dump_host_friendly=''
+
+# Backup directory location e.g /backups
+{%- for backup_engine in pillar.mysql.server.get("backup_engine", []) %}
+
+{%- if backup_engine.get("name", []) == "automysqlbackup" %}
+CONFIG_backup_dir='{{ backup_engine.backup_dir }}'
+{%- endif %}
+
+{%- endfor %}
+
+# This is practically a moot point, since there is a fallback to the compression
+# functions without multicore support in the case that the multicore versions aren't
+# present in the system. Of course, if you have the latter installed, but don't want
+# to use them, just choose no here.
+# pigz -> gzip
+# pbzip2 -> bzip2
+#CONFIG_multicore='yes'
+{%- for backup_engine in pillar.mysql.server.get("backup_engine", []) %}
+{%- if backup_engine.get("name", []) == "automysqlbackup" %}
+CONFIG_multicore='{{ backup_engine.get("multicore", "no") }}'
+{%- endif %}
+{%- endfor %}
+
+# Number of threads (= occupied cores) you want to use. You should - for the sake
+# of the stability of your system - not choose more than (#number of cores - 1).
+# Especially if the script is run in background by cron and the rest of your system
+# has already heavy load, setting this too high, might crash your system. Assuming
+# all systems have at least some sort of HyperThreading, the default is 2 threads.
+# If you wish to let pigz and pbzip2 autodetect or use their standards, set it to
+# 'auto'.
+#CONFIG_multicore_threads=2
+
+# Databases to backup
+
+# List of databases for Daily/Weekly Backup e.g. ( 'DB1' 'DB2' 'DB3' ... )
+# set to (), i.e. empty, if you want to backup all databases
+#CONFIG_db_names=()
+
+CONFIG_db_names=({%- for backup_engine in pillar.mysql.server.get("backup_engine", []) %}
+{%- if backup_engine.get("name", []) == "automysqlbackup" %}
+{%- for dbs in backup_engine.get("databases", []) %} '{{ dbs }}' {%- endfor %}
+{%- endif %}
+{%- endfor %})
+
+# You can use
+#declare -a MDBNAMES=( "${DBNAMES[@]}" 'added entry1' 'added entry2' ... )
+# INSTEAD to copy the contents of $DBNAMES and add further entries (optional).
+
+# List of databases for Monthly Backups.
+# set to (), i.e. empty, if you want to backup all databases
+#CONFIG_db_month_names=()
+CONFIG_db_month_names=({%- for backup_engine in pillar.mysql.server.get("backup_engine", []) %}
+{%- if backup_engine.get("name", []) == "automysqlbackup" %}
+{%- for dbs in backup_engine.get("databases", []) %} '{{ dbs }}' {%- endfor %}
+{%- endif %}
+{%- endfor %})
+
+# List of DBNAMES to EXLUCDE if DBNAMES is empty, i.e. ().
+#CONFIG_db_exclude=( 'information_schema' 'mysql' 'performance_schema')
+
+# List of tables to exclude, in the form db_name.table_name
+# You may use wildcards for the table names, i.e. 'mydb.a*' selects all tables starting with an 'a'.
+# However we only offer the wildcard '*', matching everything that could appear, which translates to the
+# '%' wildcard in mysql.
+#CONFIG_table_exclude=()
+CONFIG_table_excludes=({%- for backup_engine in pillar.mysql.server.get("backup_engine", []) %}
+{%- if backup_engine.get("name", []) == "automysqlbackup" %}
+{%- for dbs in backup_engine.get("table_excludes", []) %} '{{ dbs }}' {%- endfor %}
+{%- endif %}
+{%- endfor %})
+
+
+# Advanced Settings
+
+# Rotation Settings
+
+# Which day do you want monthly backups? (01 to 31)
+# If the chosen day is greater than the last day of the month, it will be done
+# on the last day of the month.
+# Set to 0 to disable monthly backups.
+#CONFIG_do_monthly="01"
+
+# Which day do you want weekly backups? (1 to 7 where 1 is Monday)
+# Set to 0 to disable weekly backups.
+CONFIG_do_weekly="6"
+
+# Set rotation of daily backups. VALUE*24hours
+# If you want to keep only today's backups, you could choose 1, i.e. everything older than 24hours will be removed.
+#CONFIG_rotation_daily=6
+
+# Set rotation for weekly backups. VALUE*24hours
+#CONFIG_rotation_weekly=35
+
+# Set rotation for monthly backups. VALUE*24hours
+#CONFIG_rotation_monthly=150
+
+
+# Server Connection Settings
+
+# Set the port for the mysql connection
+#CONFIG_mysql_dump_port=3306
+
+# Compress communications between backup server and MySQL server?
+#CONFIG_mysql_dump_commcomp='no'
+
+# Use ssl encryption with mysqldump?
+#CONFIG_mysql_dump_usessl='yes'
+
+# For connections to localhost. Sometimes the Unix socket file must be specified.
+#CONFIG_mysql_dump_socket=''
+
+# The maximum size of the buffer for client/server communication. e.g. 16MB (maximum is 1GB)
+#CONFIG_mysql_dump_max_allowed_packet=''
+
+# This option sends a START TRANSACTION SQL statement to the server before dumping data. It is useful only with
+# transactional tables such as InnoDB, because then it dumps the consistent state of the database at the time
+# when BEGIN was issued without blocking any applications.
+#
+# When using this option, you should keep in mind that only InnoDB tables are dumped in a consistent state. For
+# example, any MyISAM or MEMORY tables dumped while using this option may still change state.
+#
+# While a --single-transaction dump is in process, to ensure a valid dump file (correct table contents and
+# binary log coordinates), no other connection should use the following statements: ALTER TABLE, CREATE TABLE,
+# DROP TABLE, RENAME TABLE, TRUNCATE TABLE. A consistent read is not isolated from those statements, so use of
+# them on a table to be dumped can cause the SELECT that is performed by mysqldump to retrieve the table
+# contents to obtain incorrect contents or fail.
+#CONFIG_mysql_dump_single_transaction='no'
+
+# http://dev.mysql.com/doc/refman/5.0/en/mysqldump.html#option_mysqldump_master-data
+# --master-data[=value]
+# Use this option to dump a master replication server to produce a dump file that can be used to set up another
+# server as a slave of the master. It causes the dump output to include a CHANGE MASTER TO statement that indicates
+# the binary log coordinates (file name and position) of the dumped server. These are the master server coordinates
+# from which the slave should start replicating after you load the dump file into the slave.
+#
+# If the option value is 2, the CHANGE MASTER TO statement is written as an SQL comment, and thus is informative only;
+# it has no effect when the dump file is reloaded. If the option value is 1, the statement is not written as a comment
+# and takes effect when the dump file is reloaded. If no option value is specified, the default value is 1.
+#
+# This option requires the RELOAD privilege and the binary log must be enabled.
+#
+# The --master-data option automatically turns off --lock-tables. It also turns on --lock-all-tables, unless
+# --single-transaction also is specified, in which case, a global read lock is acquired only for a short time at the
+# beginning of the dump (see the description for --single-transaction). In all cases, any action on logs happens at
+# the exact moment of the dump.
+# ==================================================================================================================
+# possible values are 1 and 2, which correspond with the values from mysqldump
+# VARIABLE= , i.e. no value, turns it off (default)
+#
+#CONFIG_mysql_dump_master_data=
+
+# Included stored routines (procedures and functions) for the dumped databases in the output. Use of this option
+# requires the SELECT privilege for the mysql.proc table. The output generated by using --routines contains
+# CREATE PROCEDURE and CREATE FUNCTION statements to re-create the routines. However, these statements do not
+# include attributes such as the routine creation and modification timestamps. This means that when the routines
+# are reloaded, they will be created with the timestamps equal to the reload time.
+#
+# If you require routines to be re-created with their original timestamp attributes, do not use --routines. Instead,
+# dump and reload the contents of the mysql.proc table directly, using a MySQL account that has appropriate privileges
+# for the mysql database.
+#
+# This option was added in MySQL 5.0.13. Before that, stored routines are not dumped. Routine DEFINER values are not
+# dumped until MySQL 5.0.20. This means that before 5.0.20, when routines are reloaded, they will be created with the
+# definer set to the reloading user. If you require routines to be re-created with their original definer, dump and
+# load the contents of the mysql.proc table directly as described earlier.
+#
+#CONFIG_mysql_dump_full_schema='yes'
+
+# Backup status of table(s) in textfile. This is very helpful when restoring backups, since it gives an idea, what changed
+# in the meantime.
+#CONFIG_mysql_dump_dbstatus='yes'
+
+# Backup dump settings
+
+# Include CREATE DATABASE in backup?
+#CONFIG_mysql_dump_create_database='no'
+
+# Separate backup directory and file for each DB? (yes or no)
+#CONFIG_mysql_dump_use_separate_dirs='yes'
+
+# Choose Compression type. (gzip or bzip2)
+#CONFIG_mysql_dump_compression='gzip'
+
+# Store an additional copy of the latest backup to a standard
+# location so it can be downloaded by third party scripts.
+#CONFIG_mysql_dump_latest='no'
+
+# Remove all date and time information from the filenames in the latest folder.
+# Runs, if activated, once after the backups are completed. Practically it just finds all files in the latest folder
+# and removes the date and time information from the filenames (if present).
+#CONFIG_mysql_dump_latest_clean_filenames='no'
+
+# Create differential backups. Master backups are created weekly at #$CONFIG_do_weekly weekday. Between master backups,
+# diff is used to create differential backups relative to the latest master backup. In the Manifest file, you find the
+# following structure
+# $filename md5sum $md5sum diff_id $diff_id rel_id $rel_id
+# where each field is separated by the tabular character '\t'. The entries with $ at the beginning mean the actual values,
+# while the others are just for readability. The diff_id is the id of the differential or master backup which is also in
+# the filename after the last _ and before the suffixes begin, i.e. .diff, .sql and extensions. It is used to relate
+# differential backups to master backups. The master backups have 0 as $rel_id and are thereby identifiable. Differential
+# backups have the id of the corresponding master backup as $rel_id.
+#
+# To ensure that master backups are kept long enough, the value of $CONFIG_rotation_daily is set to a minimum of 21 days.
+#
+#CONFIG_mysql_dump_differential='no'
+
+
+# Notification setup
+
+# What would you like to be mailed to you?
+# - log : send only log file
+# - files : send log file and sql files as attachments (see docs)
+# - stdout : will simply output the log to the screen if run manually.
+# - quiet : Only send logs if an error occurs to the MAILADDR.
+#CONFIG_mailcontent='stdout'
+{%- for backup_engine in pillar.mysql.server.get("backup_engine", []) %}
+{%- if backup_engine.get("name", []) == "automysqlbackup" %}
+CONFIG_mailcontent='{{ backup_engine.get("mailcontent", "log") }}'
+{%- endif %}
+{%- endfor %}
+
+# Set the maximum allowed email size in k. (4000 = approx 5MB email [see docs])
+#CONFIG_mail_maxattsize=4000
+
+# Allow packing of files with tar and splitting it in pieces of CONFIG_mail_maxattsize.
+#CONFIG_mail_splitandtar='yes'
+
+# Use uuencode instead of mutt. WARNING: Not all email clients work well with uuencoded attachments.
+#CONFIG_mail_use_uuencoded_attachments='no'
+
+# Email Address to send mail to? (user@domain.com)
+#CONFIG_mail_address='root'
+{%- for backup_engine in pillar.mysql.server.get("backup_engine", []) %}
+{%- if backup_engine.get("name", []) == "automysqlbackup" %}
+CONFIG_mail_address='{{ backup_engine.get("mail_address", "root") }}'
+{%- endif %}
+{%- endfor %}
+
+# Encryption
+
+# Do you wish to encrypt your backups using openssl?
+#CONFIG_encrypt='no'
+
+# Choose a password to encrypt the backups.
+#CONFIG_encrypt_password='password0123'
+
+# Other
+
+# Backup local files, i.e. maybe you would like to backup your my.cnf (mysql server configuration), etc.
+# These files will be tar'ed, depending on your compression option CONFIG_mysql_dump_compression compressed and
+# depending on the option CONFIG_encrypt encrypted.
+#
+# Note: This could also have been accomplished with CONFIG_prebackup or CONFIG_postbackup.
+#CONFIG_backup_local_files=()
+
+# Command to run before backups (uncomment to use)
+#CONFIG_prebackup="/etc/mysql-backup-pre"
+
+# Command run after backups (uncomment to use)
+#CONFIG_postbackup="/etc/mysql-backup-post"
+
+# Uncomment to activate! This will give folders rwx------
+# and files rw------- permissions.
+#umask 0077
+
+# dry-run, i.e. show what you are gonna do without actually doing it
+# inactive: =0 or commented out
+# active: uncommented AND =1
+#CONFIG_dryrun=1
+
diff --git a/mysql/conf/automysqlbackup.cron b/mysql/conf/automysqlbackup.cron
new file mode 100755
index 0000000..5ffed92
--- /dev/null
+++ b/mysql/conf/automysqlbackup.cron
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+#/root/scripts/automysqlbackup/automysqlbackup-2.5.1-01.sh >/dev/null 2>&1
+/root/mysql/scripts/automysqlbackup >/dev/null 2>&1
+
+EXITVALUE=$?
+if [ $EXITVALUE != 0 ]; then
+ /usr/bin/logger -t automysqlbackup "ALERT exited abnormally with [$EXITVALUE]"
+fi
+exit 0
diff --git a/mysql/conf/backup.sh b/mysql/conf/backup.sh
new file mode 100755
index 0000000..705ee2b
--- /dev/null
+++ b/mysql/conf/backup.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+DATE=$(date +%Y%m%d)
+
+{%- if pillar.mysql.server.databases is defined %}
+{%- for database in pillar.mysql.server.databases %}
+mkdir -p /root/mysql/backup/{{ database.name }}
+mysqldump --defaults-file=/etc/mysql/debian.cnf {{ database.name }} > /root/mysql/backup/{{ database.name }}/${DATE}.sql
+cp /root/mysql/backup/{{ database.name }}/${DATE}.sql /root/mysql/backup/{{ database.name }}/last.sql
+{%- endfor %}
+{%- endif %}
+
+# purge old dumps
+find /root/mysql/backup/ -name "*.sql*" -mtime +8 -exec rm -vf {} \;
\ No newline at end of file
diff --git a/mysql/conf/debian.cnf b/mysql/conf/debian.cnf
new file mode 100644
index 0000000..c4f782c
--- /dev/null
+++ b/mysql/conf/debian.cnf
@@ -0,0 +1,14 @@
+# Generated by Salt.
+{%- from "mysql/map.jinja" import server with context %}
+# Automatically generated for Debian scripts. DO NOT TOUCH!
+[client]
+host = localhost
+user = debian-sys-maint
+password = {{ server.maintenance_password }}
+socket = /var/run/mysqld/mysqld.sock
+[mysql_upgrade]
+host = localhost
+user = debian-sys-maint
+password = {{ server.maintenance_password }}
+socket = /var/run/mysqld/mysqld.sock
+basedir = /usr
diff --git a/mysql/conf/my.cnf.Debian b/mysql/conf/my.cnf.Debian
new file mode 100644
index 0000000..af80c70
--- /dev/null
+++ b/mysql/conf/my.cnf.Debian
@@ -0,0 +1,152 @@
+{%- from "mysql/map.jinja" import server with context %}
+{%- set mysql = server %}
+#
+# The MySQL database server configuration file.
+#
+# You can copy this to one of:
+# - "/etc/mysql/my.cnf" to set global options,
+# - "~/.my.cnf" to set user-specific options.
+#
+# One can use all long options that the program supports.
+# Run program with --help to get a list of available options and with
+# --print-defaults to see which it would actually understand and use.
+#
+# For explanations see
+# http://dev.mysql.com/doc/mysql/en/server-system-variables.html
+
+# This will be passed to all mysql clients
+# It has been reported that passwords should be enclosed with ticks/quotes
+# escpecially if they contain "#" chars...
+# Remember to edit /etc/mysql/debian.cnf when changing the socket location.
+
+[client]
+port = {{ server.bind.get('port', '3306') }}
+socket = /var/run/mysqld/mysqld.sock
+
+{%- if server.force_encoding is defined %}
+default-character-set=utf8
+{%- endif %}
+
+{%- if server.ssl.enabled and server.replication.role in ['slave'] %}
+ssl-ca=/etc/mysql/cacert.pem
+ssl-cert=/etc/mysql/client-cert.pem
+ssl-key=/etc/mysql/client-key.pem
+{%- endif %}
+
+# Here is entries for some specific programs
+# The following values assume you have at least 32M ram
+
+# This was formally known as [safe_mysqld]. Both versions are currently parsed.
+[mysqld_safe]
+socket = /var/run/mysqld/mysqld.sock
+nice = 0
+
+[mysqld]
+#
+# * Basic Settings
+#
+user = mysql
+pid-file = /var/run/mysqld/mysqld.pid
+socket = /var/run/mysqld/mysqld.sock
+port = 3306
+basedir = /usr
+datadir = /var/lib/mysql
+tmpdir = /tmp
+lc-messages-dir = /usr/share/mysql
+skip-external-locking
+#
+# Instead of skip-networking the default is now to listen only on
+# localhost which is more compatible and is not less secure.
+bind-address = {{ server.bind.address }}
+
+{%- if server.force_encoding is defined %}
+character-set-server=utf8
+{%- endif %}
+
+#
+# * Fine Tuning
+#
+key_buffer = {{ mysql.get('key_buffer', '16') }}M
+max_allowed_packet = {{ mysql.get('max_allowed_packet', '16') }}M
+thread_stack = {{ mysql.get('thread_stack', '192') }}K
+thread_cache_size = {{ mysql.get('thread_cache_size', '8') }}
+# This replaces the startup script and checks MyISAM tables if needed
+# the first time they are touched
+myisam-recover = BACKUP
+max_connections = {{ mysql.get('max_connections', '100') }}
+#table_cache = 64
+#thread_concurrency = 10
+#
+# * Query Cache Configuration
+#
+query_cache_limit = {{ mysql.get('query_cache_limit', '1') }}M
+query_cache_size = {{ mysql.get('query_cache_size', '16') }}M
+#
+# * Logging and Replication
+#
+# Both location gets rotated by the cronjob.
+# Be aware that this log type is a performance killer.
+# As of 5.1 you can enable the log at runtime!
+#general_log_file = /var/log/mysql/mysql.log
+#general_log = 1
+#
+# Error logging goes to syslog due to /etc/mysql/conf.d/mysqld_safe_syslog.cnf.
+#
+# Here you can see queries with especially long duration
+log_slow_queries = /var/log/mysql/mysql-slow.log
+long_query_time = 2
+#log-queries-not-using-indexes
+#
+# The following can be used as easy to replay backup logs or for replication.
+# note: if you are setting up a replication slave, see README.Debian about
+# other settings you may need to change.
+{%- if server.replication.role in ['slave','master', 'both'] %}
+server-id = {{ server.server_id }}
+log_bin = /var/log/mysql/mysql-bin.log
+expire_logs_days = 10
+{%- if server.replication.role in ['slave'] %}
+relay-log = /var/log/mysql/mysql-relay-bin.log
+{%- for database_name, database in server.get('database', {}).iteritems() %}
+binlog_do_db = {{ database_name }}
+{%- endfor %}
+{%- endif %}
+{%- endif %}
+max_binlog_size = 100M
+#binlog_ignore_db = include_database_name
+#
+# * InnoDB
+#
+# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
+# Read the manual for more InnoDB related options. There are many!
+#
+# * Security Features
+#
+# Read the manual, too, if you want chroot!
+# chroot = /var/lib/mysql/
+#
+# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
+#
+
+{%- if server.ssl.enabled and server.replication.role in ['master', 'slave', 'both', 'none'] %}
+old_passwords = 1
+ssl-ca=/etc/mysql/cacert.pem
+ssl-cert=/etc/mysql/server-cert.pem
+ssl-key=/etc/mysql/server-key.pem
+{%- endif %}
+
+[mysqldump]
+quick
+quote-names
+max_allowed_packet = 16M
+
+[mysql]
+#no-auto-rehash # faster start of mysql but no tab completition
+
+[isamchk]
+key_buffer = 16M
+
+#
+# * IMPORTANT: Additional settings that can override those from this file!
+# The files must end with '.cnf', otherwise they'll be ignored.
+#
+!includedir /etc/mysql/conf.d/
diff --git a/mysql/conf/restore.sh b/mysql/conf/restore.sh
new file mode 100755
index 0000000..8cce564
--- /dev/null
+++ b/mysql/conf/restore.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+cd /root/mysql
+
+{%- for database in pillar.mysql.server.databases %}
+{%- if database.name == database_name %}
+
+{%- set age = database.initial_data.get("age", "0") %}
+{%- set host = database.initial_data.get("host", grains.id ) %}
+{%- set name = database.initial_data.get("database", database.name) %}
+{%- set source_name = name + ".sql.gz" %}
+{%- set dest_name = database.name + ".sql.gz" %}
+{%- set target = "/root/mysql/data/" %}
+
+scp backupninja@{{ database.initial_data.source }}:/srv/backupninja/{{ host }}/var/backups/mysql/mysql.{{ age }}/sqldump/{{ source_name }} {{ target }}{{ dest_name }}
+
+gunzip -c {{ target }}{{ dest_name }} | mysql -h 127.0.0.1 -u {{ database.users[0].name }} -p{{ database.users[0].password }} {{ database.name }}
+
+touch /root/mysql/flags/{{ database.name }}-installed
+
+{%- endif %}
+{%- endfor %}
\ No newline at end of file
diff --git a/mysql/files/backupninja.conf b/mysql/files/backupninja.conf
new file mode 100644
index 0000000..2c4e100
--- /dev/null
+++ b/mysql/files/backupninja.conf
@@ -0,0 +1,2 @@
+
+include = /var/backups/mysql
diff --git a/mysql/files/collectd.conf b/mysql/files/collectd.conf
new file mode 100644
index 0000000..575cee9
--- /dev/null
+++ b/mysql/files/collectd.conf
@@ -0,0 +1,14 @@
+{%- from "mysql/map.jinja" import server with context %}
+LoadPlugin mysql
+<Plugin mysql>
+ {%- for database_name, database in server.get('database', {}).iteritems() %}
+ <Database {{ database_name }}>
+ Host "{{ server.bind.address }}"
+ Port "{{ server.bind.port }}"
+ Database "{{ database_name }}"
+ User "{{ database.users[0].name }}"
+ Password "{{ database.users[0].password }}"
+ MasterStats true
+ </Database>
+ {%- endfor %}
+</Plugin>
diff --git a/mysql/files/sensu.conf b/mysql/files/sensu.conf
new file mode 100644
index 0000000..a143316
--- /dev/null
+++ b/mysql/files/sensu.conf
@@ -0,0 +1,12 @@
+local_mysql_server_proc:
+ command: "PATH=$PATH:/usr/lib64/nagios/plugins:/usr/lib/nagios/plugins check_procs -C mysqld -u mysql -c 1:1"
+ interval: 30
+ occurrences: 1
+ subscribers:
+ - local-mysql-server
+local_mysql_galera_status:
+ command: "PATH=$PATH:/etc/sensu/plugins check_galera_cluster -u :::mysql.user::: -p :::mysql.password::: -H localhost -P :::mysql.port::: -c 2"
+ interval: 60
+ occurrences: 1
+ subscribers:
+ - local-mysql-cluster
diff --git a/mysql/init.sls b/mysql/init.sls
new file mode 100755
index 0000000..6e7f5bf
--- /dev/null
+++ b/mysql/init.sls
@@ -0,0 +1,5 @@
+
+include:
+{%- if pillar.mysql.server is defined %}
+- mysql.server
+{%- endif %}
diff --git a/mysql/map.jinja b/mysql/map.jinja
new file mode 100644
index 0000000..1ca5f9e
--- /dev/null
+++ b/mysql/map.jinja
@@ -0,0 +1,28 @@
+
+{%- set mysql_version = pillar.mysql.server.get('version', '6.0') %}
+
+{%- set server = salt['grains.filter_by']({
+ 'Debian': {
+ 'pkgs': ['mysql-server-'+mysql_version, 'python-mysqldb'],
+ 'service': 'mysql',
+ 'config': '/etc/mysql/my.cnf',
+ 'maintenance_password': '5Pg91H1flC4HSVns',
+ 'replication': {
+ 'role': 'none',
+ },
+ 'ssl': {
+ 'enabled': False
+ }
+ },
+ 'RedHat': {
+ 'pkgs': ['mysql-server', 'MySQL-python'],
+ 'service': 'mysqld',
+ 'config': '/etc/my.cnf',
+ 'replication': {
+ 'role': 'none',
+ },
+ 'ssl': {
+ 'enabled': False
+ }
+ },
+}, merge=salt['pillar.get']('mysql:server')) %}
diff --git a/mysql/server.sls b/mysql/server.sls
new file mode 100644
index 0000000..6a8834a
--- /dev/null
+++ b/mysql/server.sls
@@ -0,0 +1,156 @@
+{%- from "mysql/map.jinja" import server with context %}
+{%- if server.enabled %}
+
+include:
+- mysql.common
+
+{%- if server.ssl.enabled %}
+
+/etc/mysql/server-cert.pem:
+ file.managed:
+ - source: salt://pki/{{ server.ssl.authority }}/certs/{{ server.ssl.certificate }}.cert.pem
+ - require:
+ - pkg: mysql_packages
+
+/etc/mysql/server-key.pem:
+ file.managed:
+ - source: salt://pki/{{ server.ssl.authority }}/certs/{{ server.ssl.certificate }}.key.pem
+ - require:
+ - pkg: mysql_packages
+
+{%- if server.replication.role in ['slave', 'both'] %}
+
+/etc/mysql/client-cert.pem:
+ file.managed:
+ - source: salt://pki/{{ server.ssl.authority }}/certs/{{ server.ssl.client_certificate }}.cert.pem
+ - require:
+ - pkg: mysql_packages
+
+/etc/mysql/client-key.pem:
+ file.managed:
+ - source: salt://pki/{{ server.ssl.authority }}/certs/{{ server.ssl.client_certificate }}.key.pem
+ - require:
+ - pkg: mysql_packages
+
+{%- endif %}
+
+/etc/mysql/cacert.pem:
+ file.managed:
+ - source: salt://pki/{{ server.ssl.authority }}/{{ server.ssl.authority }}-chain.cert.pem
+ - require:
+ - pkg: mysql_packages
+
+{%- endif %}
+
+
+{%- if server.replication.role in ['master', 'both'] %}
+
+{{ server.replication.user }}:
+ mysql_user.present:
+ - host: '%'
+ - password: {{ server.replication.password }}
+
+{{ server.replication.user }}_replication_grants:
+ mysql_grants.present:
+ - grant: replication slave
+ - database: '*.*'
+ - user: {{ server.replication.user }}
+ - host: '%'
+
+{%- endif %}
+
+{%- if server.replication.role in ['slave', 'both'] %}
+
+{%- if not salt['mysql.get_slave_status'] is defined %}
+
+{%- include "mysql/server/_connect_replication_slave.sls" %}
+
+{%- elif salt['mysql.get_slave_status']() == [] %}
+
+{%- include "mysql/server/_connect_replication_slave.sls" %}
+
+{%- else %}
+
+{%- if salt['mysql.get_slave_status']().get('Slave_SQL_Running', 'No') == 'Yes' and salt['mysql.get_slave_status']().get('Slave_IO_Running', 'No') == 'Yes' %}
+
+{%- else %}
+
+{%- include "mysql/server/_connect_replication_slave.sls" %}
+
+{%- endif %}
+
+{%- endif %}
+
+{%- endif %}
+
+
+{%- for database_name, database in server.get('database', {}).iteritems() %}
+
+mysql_database_{{ database_name }}:
+ mysql_database.present:
+ - name: {{ database_name }}
+ - require:
+ - service: mysql_service
+
+{%- for user in database.users %}
+
+mysql_user_{{ user.name }}_{{ database_name }}_{{ user.host }}:
+ mysql_user.present:
+ - host: '{{ user.host }}'
+ - name: '{{ user.name }}'
+ - password: {{ user.password }}
+ - require:
+ - service: mysql_service
+
+mysql_grants_{{ user.name }}_{{ database_name }}_{{ user.host }}:
+ mysql_grants.present:
+ - grant: {{ user.rights }}
+ - database: '{{ database_name }}.*'
+ - user: '{{ user.name }}'
+ - host: '{{ user.host }}'
+ - require:
+ - mysql_user: mysql_user_{{ user.name }}_{{ database_name }}_{{ user.host }}
+ - mysql_database: mysql_database_{{ database_name }}
+
+{%- endfor %}
+
+{%- if database.initial_data is defined %}
+
+/root/mysql/scripts/restore_{{ database_name }}.sh:
+ file.managed:
+ - source: salt://mysql/conf/restore.sh
+ - mode: 770
+ - template: jinja
+ - defaults:
+ database_name: {{ database_name }}
+ - require:
+ - file: mysql_dirs
+ - mysql_database: mysql_database_{{ database_name }}
+
+restore_mysql_database_{{ database_name }}:
+ cmd.run:
+ - name: /root/mysql/scripts/restore_{{ database_name }}.sh
+ - unless: "[ -f /root/mysql/flags/{{ database_name }}-installed ]"
+ - cwd: /root
+ - require:
+ - file: /root/mysql/scripts/restore_{{ database_name }}.sh
+
+{%- endif %}
+
+{%- endfor %}
+
+{%- for user in server.get('users', []) %}
+
+mysql_user_{{ user.name }}_{{ user.host }}:
+ mysql_user.present:
+ - host: '{{ user.host }}'
+ - name: '{{ user.name }}'
+ {%- if user.password is defined %}
+ - password: {{ user.password }}
+ {%- else %}
+ - allow_passwordless: True
+ {%- endif %}
+
+{%- endfor %}
+
+{%- endif %}
diff --git a/mysql/server/_connect_replication_slave.sls b/mysql/server/_connect_replication_slave.sls
new file mode 100644
index 0000000..a4f955c
--- /dev/null
+++ b/mysql/server/_connect_replication_slave.sls
@@ -0,0 +1,24 @@
+{%- from "mysql/map.jinja" import server with context %}
+{%- set master_status = salt['mine.get'](server.replication.master, 'mysql.get_master_status').get(server.replication.master, {}) %}
+
+{%- set master_host = salt['mine.get'](server.replication.master, 'network.interfaces')[server.replication.master] %}
+
+/tmp/get_slave_status:
+ file.managed:
+ - contents: {{ master_status }}
+
+{%- if pillar.mysql.server.ssl.client_certificate is defined %}
+
+{%- set setup_replication_query = "CHANGE MASTER TO MASTER_HOST='"+server.replication.master_address+"', MASTER_USER='"+server.replication.user+"', MASTER_PASSWORD='"+server.replication.password+"', MASTER_LOG_FILE='"+master_status.get('File', 'mysql-bin.000001')+"', MASTER_SSL=1, MASTER_LOG_POS="+master_status.get('Position', '1')|string+", MASTER_SSL_CA='/etc/mysql/cacert.pem', MASTER_SSL_CERT='/etc/mysql/client-cert.pem', MASTER_SSL_KEY='/etc/mysql/client-key.pem'; START SLAVE;" %}
+
+{%- else %}
+
+{%- set setup_replication_query = "CHANGE MASTER TO MASTER_HOST='"+server.replication.master_address+"', MASTER_USER='"+server.replication.user+"', MASTER_PASSWORD='"+server.replication.password+"', MASTER_LOG_FILE='"+master_status.get('File', 'mysql-bin.000001')+"', MASTER_LOG_POS="+master_status.get('Position', '1')|string+";" %}
+
+{%- endif %}
+
+{%- if salt['mysql.query'] is defined %}
+
+{%- set setup_replication_result = salt['mysql.query']('mysql', setup_replication_query) %}
+
+{%- endif %}
diff --git a/mysql/server/_start_replication_master.sls b/mysql/server/_start_replication_master.sls
new file mode 100644
index 0000000..664d5b4
--- /dev/null
+++ b/mysql/server/_start_replication_master.sls
@@ -0,0 +1,12 @@
+
+{%- set setup_replication_query = "START MASTER;" %}
+
+/tmp/setup_replication_query:
+ file.managed:
+ - contents: {{ setup_replication_query }}
+
+{%- set setup_replication_result = salt['mysql.query']('mysql', setup_replication_query) %}
+
+/tmp/setup_replication_result:
+ file.managed:
+ - contents: {{ setup_replication_result }}