From cb5c842979628c83b7735866030bd3b39ef0bba2 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Sun, 1 Jan 2023 23:04:44 +0100 Subject: [PATCH 001/105] Extract functions for each local task --- client/zzz_evobackup | 523 +++++++++++++++++++++++++++---------------- 1 file changed, 329 insertions(+), 194 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 99d87b7..4f289d4 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -127,230 +127,262 @@ lxc/*/rootfs/var/tmp ##### FUNCTIONS ####################################################### -local_tasks() { - log "START LOCAL_TASKS" - - # You can comment or uncomment sections below to customize the backup +mysql_list_databases() { + port=${1:-"3306"} + mysql --defaults-extra-file=/etc/mysql/debian.cnf -P ${port} -e 'show databases' -s --skip-column-names | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" +} +# shellcheck disable=SC2317 +dump_ldap() { ## OpenLDAP : example with slapcat - # slapcat -n 0 -l ${LOCAL_BACKUP_DIR}/config.ldap.bak - # slapcat -n 1 -l ${LOCAL_BACKUP_DIR}/data.ldap.bak - # slapcat -l ${LOCAL_BACKUP_DIR}/ldap.bak + dump_dir="${LOCAL_BACKUP_DIR}/ldap/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" - ## MySQL + slapcat -n 0 -l "${dump_dir}/config.bak" + slapcat -n 1 -l "${dump_dir}/data.bak" + slapcat -l "${dump_dir}/ldap.bak" +} +# shellcheck disable=SC2317 +dump_mysql_global() { + dump_dir="${LOCAL_BACKUP_DIR}/mysql-global/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" - ## Purge previous dumps - # rm -f ${LOCAL_BACKUP_DIR}/mysql.*.gz - # rm -rf ${LOCAL_BACKUP_DIR}/mysql - # rm -rf ${LOCAL_BACKUP_DIR}/mysqlhotcopy - # rm -rf /home/mysqldump - # find ${LOCAL_BACKUP_DIR}/ -type f -name '*.err' -delete + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${dump_dir}/mysql.bak.err" | gzip --best > "${dump_dir}/mysql.bak.gz" - ## example with global and compressed mysqldump - # mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 \ - # --opt --all-databases --force --events --hex-blob 2> ${LOCAL_BACKUP_DIR}/mysql.bak.err | gzip --best > ${LOCAL_BACKUP_DIR}/mysql.bak.gz - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "mysqldump (global compressed) returned an error ${last_rc}, check ${LOCAL_BACKUP_DIR}/mysql.bak.err" - # rc=101 - # fi + last_rc=$? + if [ ${last_rc} -ne 0 ]; then + error "mysqldump (global compressed) returned an error ${last_rc}, check ${dump_dir}/mysql.bak.err" + rc=101 + else + rm -f "${dump_dir}/mysql.bak.err" + fi +} +# shellcheck disable=SC2317 +dump_mysql_per_base() { + dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" - ## example with compressed SQL dump (with data) for each databases - # mkdir -p -m 700 ${LOCAL_BACKUP_DIR}/mysql/ - # for i in $(mysql --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 -e 'show databases' -s --skip-column-names \ - # | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)"); do - # mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob $i 2> ${LOCAL_BACKUP_DIR}/${i}.err | gzip --best > ${LOCAL_BACKUP_DIR}/mysql/${i}.sql.gz - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "mysqldump (${i} compressed) returned an error ${last_rc}, check ${LOCAL_BACKUP_DIR}/${i}.err" - # rc=102 - # fi - # done + for i in $(mysql_list_databases 3306); do + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob $i 2> "${dump_dir}/${i}.err" | gzip --best > "${dump_dir}/${i}.sql.gz" + + last_rc=$? + if [ ${last_rc} -ne 0 ]; then + error "mysqldump (${i} compressed) returned an error ${last_rc}, check ${dump_dir}/${i}.err" + rc=102 + else + rm -f "${dump_dir}/${i}.err" + fi + done +} +# shellcheck disable=SC2317 +dump_mysql_meta() { + dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" ## Dump all grants (requires 'percona-toolkit' package) - # mkdir -p -m 700 ${LOCAL_BACKUP_DIR}/mysql/ - # pt-show-grants --flush --no-header 2> ${LOCAL_BACKUP_DIR}/mysql/all_grants.err > ${LOCAL_BACKUP_DIR}/mysql/all_grants.sql - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "pt-show-grants returned an error ${last_rc}, check ${LOCAL_BACKUP_DIR}/mysql/all_grants.err" - # rc=103 - # fi + pt-show-grants --flush --no-header 2> "${dump_dir}/all_grants.err" > "${dump_dir}/all_grants.sql" - # Dump all variables - # mysql -A -e"SHOW GLOBAL VARIABLES;" 2> ${LOCAL_BACKUP_DIR}/MySQLCurrentSettings.err > ${LOCAL_BACKUP_DIR}/MySQLCurrentSettings.txt - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "mysql (variables) returned an error ${last_rc}, check ${LOCAL_BACKUP_DIR}/MySQLCurrentSettings.err" - # rc=104 - # fi + last_rc=$? + if [ ${last_rc} -ne 0 ]; then + error "pt-show-grants returned an error ${last_rc}, check ${dump_dir}/all_grants.err" + rc=103 + else + rm -f "${dump_dir}/all_grants.err" + fi - ## example with SQL dump (schema only, no data) for each databases - # mkdir -p -m 700 ${LOCAL_BACKUP_DIR}/mysql/ - # for i in $(mysql --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 -e 'show databases' -s --skip-column-names \ - # | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)"); do - # mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --no-data --databases $i 2> ${LOCAL_BACKUP_DIR}/${i}.schema.err > ${LOCAL_BACKUP_DIR}/mysql/${i}.schema.sql - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "mysqldump (${i} schema) returned an error ${last_rc}, check ${LOCAL_BACKUP_DIR}/${i}.schema.err" - # rc=105 - # fi - # done + ## Dump all variables + mysql -A -e"SHOW GLOBAL VARIABLES;" 2> "${dump_dir}/variables.err" > "${dump_dir}/variables.txt" - ## example with *one* uncompressed SQL dump for *one* database (MYBASE) - # mkdir -p -m 700 ${LOCAL_BACKUP_DIR}/mysql/MYBASE - # chown -RL mysql ${LOCAL_BACKUP_DIR}/mysql/ - # mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -Q \ - # --opt --events --hex-blob --skip-comments -T ${LOCAL_BACKUP_DIR}/mysql/MYBASE MYBASE 2> ${LOCAL_BACKUP_DIR}/mysql/MYBASE.err - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "mysqldump (MYBASE) returned an error ${last_rc}, check ${LOCAL_BACKUP_DIR}/mysql/MYBASE.err" - # rc=106 - # fi + last_rc=$? + if [ ${last_rc} -ne 0 ]; then + error "mysql (variables) returned an error ${last_rc}, check ${dump_dir}/variables.err" + rc=104 + else + rm -f "${dump_dir}/variables.err" + fi - ## example with two dumps for each table (.sql/.txt) for all databases - # for i in $(echo SHOW DATABASES | mysql --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 \ - # | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" ); do - # mkdir -p -m 700 /home/mysqldump/$i ; chown -RL mysql /home/mysqldump - # mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 -Q --opt --events --hex-blob --skip-comments \ - # --fields-enclosed-by='\"' --fields-terminated-by=',' -T /home/mysqldump/$i $i 2> /home/mysqldump/$i.err" - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "mysqldump (${i} files) returned an error ${last_rc}, check /home/mysqldump/$i.err" - # rc=107 - # fi - # done + ## Schema only (no data) for each databases + for i in $(mysql_list_databases 3306); do + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --no-data --databases $i 2> "${dump_dir}/${i}.schema.err" > "${dump_dir}/${i}.schema.sql" - ## example with mysqlhotcopy - # mkdir -p -m 700 ${LOCAL_BACKUP_DIR}/mysqlhotcopy/ - # mysqlhotcopy MYBASE ${LOCAL_BACKUP_DIR}/mysqlhotcopy/ 2> ${LOCAL_BACKUP_DIR}/mysqlhotcopy/MYBASE.err - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "mysqlhotcopy returned an error ${last_rc}, check ${LOCAL_BACKUP_DIR}/mysqlhotcopy/MYBASE.err" - # rc=108 - # fi + last_rc=$? + if [ ${last_rc} -ne 0 ]; then + error "mysqldump (${i} schema) returned an error ${last_rc}, check ${dump_dir}/${i}.schema.err" + rc=105 + else + rm -f "${dump_dir}/${i}.schema.err" + fi + done +} +# shellcheck disable=SC2317 +dump_mysql_tabs() { + for i in $(mysql_list_databases 3306); do + dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs/$i" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" + chown -RL mysql "${dump_dir}" - ## example for multiples MySQL instances - # mysqladminpasswd=$(grep -m1 'password = .*' /root/.my.cnf|cut -d" " -f3) - # grep --extended-regexp "^port\s*=\s*\d*" /etc/mysql/my.cnf | while read instance; do - # instance=$(echo "$instance"|awk '{ print $3 }') - # if [ "$instance" != "3306" ] - # then - # mysqldump -P $instance --opt --all-databases --hex-blob -u mysqladmin -p$mysqladminpasswd 2> ${LOCAL_BACKUP_DIR}/mysql.${instance}.err | gzip --best > ${LOCAL_BACKUP_DIR}/mysql.${instance}.bak.gz - # last_rc=$? - # if [ ${last_rc} -ne 0 ]; then - # error "mysqldump (instance ${instance}) returned an error ${last_rc}, check ${LOCAL_BACKUP_DIR}/mysql.${instance}.err" - # rc=107 - # fi - # fi - # done + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 -Q --opt --events --hex-blob --skip-comments --fields-enclosed-by='\"' --fields-terminated-by=',' -T "${dump_dir}" $i 2> "${dump_dir}.err" + + last_rc=$? + if [ ${last_rc} -ne 0 ]; then + error "mysqldump (${i} files) returned an error ${last_rc}, check ${dump_dir}.err" + rc=107 + else + rm -f "${dump_dir}.err" + fi + done +} +# shellcheck disable=SC2317 +dump_mysql_hotcopy() { + dump_dir="${LOCAL_BACKUP_DIR}/mysql-hotcopy/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" - ## PostgreSQL + mysqlhotcopy MYBASE "${dump_dir}/" 2> "${dump_dir}/MYBASE.err" - ## Purge previous dumps - # rm -rf ${LOCAL_BACKUP_DIR}/pg.*.gz - # rm -rf ${LOCAL_BACKUP_DIR}/pg-backup.tar - # rm -rf ${LOCAL_BACKUP_DIR}/postgresql/* + last_rc=$? + if [ ${last_rc} -ne 0 ]; then + error "mysqlhotcopy returned an error ${last_rc}, check ${dump_dir}/MYBASE.err" + rc=108 + else + rm -f "${dump_dir}/MYBASE.err" + fi +} +# shellcheck disable=SC2317 +dump_mysql_instances() { + dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" - ## example with pg_dumpall (warning: you need space in ~postgres) - # su - postgres -c "pg_dumpall > ~/pg.dump.bak" - # mv ~postgres/pg.dump.bak ${LOCAL_BACKUP_DIR}/ + mysqladminpasswd=$(grep -m1 'password = .*' /root/.my.cnf|cut -d" " -f3) + + grep --extended-regexp "^port\s*=\s*\d*" /etc/mysql/my.cnf | while read instance; do + instance=$(echo "$instance"|awk '{ print $3 }') + if [ "$instance" != "3306" ]; then + mysqldump -P ${instance} --opt --all-databases --hex-blob -u mysqladmin -p${mysqladminpasswd} 2> "${dump_dir}/${instance}.err" | gzip --best > "${dump_dir}/${instance}.bak.gz" + + last_rc=$? + if [ ${last_rc} -ne 0 ]; then + error "mysqldump (instance ${instance}) returned an error ${last_rc}, check ${dump_dir}/${instance}.err" + rc=107 + else + rm -f "${dump_dir}/${instance}.err" + fi + fi + done +} +# shellcheck disable=SC2317 +dump_postgresql_global() { + dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" + + ## example with pg_dumpall + # WARNING: you need space in ~postgres + su - postgres -c "pg_dumpall > ~/pg.dump.bak" + mv ~postgres/pg.dump.bak "${dump_dir}/" ## another method with gzip directly piped # ( # cd /var/lib/postgresql; - # sudo -u postgres pg_dumpall | gzip > ${LOCAL_BACKUP_DIR}/pg.dump.bak.gz + # sudo -u postgres pg_dumpall | gzip > ${dump_dir}/pg.dump.bak.gz # ) +} +# shellcheck disable=SC2317 +dump_postgresql_per_base() { + dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" + + ( + cd /var/lib/postgresql + databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v template*) + for database in ${databases} ; do + sudo -u postgres /usr/bin/pg_dump --create -s -U postgres -d ${database} | gzip --best -c > ${dump_dir}/$databases.sql.gz + done + ) +} +# shellcheck disable=SC2317 +dump_postgresql_filtered() { + dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" + ## example with all tables from MYBASE excepts TABLE1 and TABLE2 - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f ${LOCAL_BACKUP_DIR}/pg-backup.tar -t 'TABLE1' -t 'TABLE2' MYBASE + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_dir}/pg-backup.tar" -t 'TABLE1' -t 'TABLE2' MYBASE ## example with only TABLE1 and TABLE2 from MYBASE - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f ${LOCAL_BACKUP_DIR}/pg-backup.tar -T 'TABLE1' -T 'TABLE2' MYBASE - - ## example with compressed PostgreSQL dump for each databases - # mkdir -p -m 700 ${LOCAL_BACKUP_DIR}/postgresql - # chown postgres:postgres ${LOCAL_BACKUP_DIR}/postgresql - # ( - # cd /var/lib/postgresql - # dbs=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' |grep -v template*) - # for databases in $dbs ; do sudo -u postgres /usr/bin/pg_dump --create -s -U postgres -d $databases | gzip --best -c > ${LOCAL_BACKUP_DIR}/postgresql/$databases.sql.gz ; done - # ) - - ## MongoDB + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_dir}/pg-backup.tar" -T 'TABLE1' -T 'TABLE2' MYBASE +} +# shellcheck disable=SC2317 +dump_redis() { + for instance in $(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*'); do + name=$(basename "${instance}") + dump_dir="${LOCAL_BACKUP_DIR}/${name}/" + rm -rf "${dump_dir}" + if [ -f "${instance}/dump.rdb" ]; then + mkdir -p -m 700 "${dump_dir}" + cp -a "${instance}/dump.rdb" "${dump_dir}/" + fi + done +} +# shellcheck disable=SC2317 +dump_mongodb() { ## don't forget to create use with read-only access ## > use admin ## > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) - ## Purge previous dumps - # rm -rf ${LOCAL_BACKUP_DIR}/mongodump/ - # mkdir -p -m 700 ${LOCAL_BACKUP_DIR}/mongodump/ - # mongodump --quiet -u mongobackup -pPASS -o ${LOCAL_BACKUP_DIR}/mongodump/ - # if [ $? -ne 0 ]; then - # echo "Error with mongodump!" - # fi - ## Redis + dump_dir="${LOCAL_BACKUP_DIR}/mongodump/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" + mongo_user="" + mongo_password="" - ## Purge previous dumps - # rm -rf ${LOCAL_BACKUP_DIR}/redis/ - # rm -rf ${LOCAL_BACKUP_DIR}/redis-* - ## Copy dump.rdb file for each found instance - # for instance in $(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*'); do - # if [ -f "${instance}/dump.rdb" ]; then - # name=$(basename $instance) - # mkdir -p ${LOCAL_BACKUP_DIR}/${name} - # cp -a "${instance}/dump.rdb" "${LOCAL_BACKUP_DIR}/${name}" - # fi - # done + mongodump --quiet -u ${mongo_user} -p${mongo_password} -o "${dump_dir}/" - ## ElasticSearch + if [ $? -ne 0 ]; then + echo "Error with mongodump!" + fi +} +# shellcheck disable=SC2317 +dump_megacli_config() { + megacli -CfgSave -f "${LOCAL_BACKUP_DIR}/megacli_conf.dump" -a0 >/dev/null +} +# shellcheck disable=SC2317 +dump_traceroute() { + dump_dir="${LOCAL_BACKUP_DIR}/traceroute/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" - ## Take a snapshot as a backup. - ## Warning: You need to have a path.repo configured. - ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes - # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" - # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" - ## Clustered version here - ## It basically the same thing except that you need to check that NFS is mounted - # if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2' - # then - # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" - # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" - # else - # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' - # fi - ## If you need to keep older snapshot, for example the last 10 daily snapshots, replace the XDELETE and XPUT lines by : - # for snapshot in $(curl -s -XGET "localhost:9200/_snapshot/snaprepo/_all?pretty=true" | grep -Eo 'snapshot_[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -n -10); do - # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/${snapshot}" | grep -v -Fx '{"acknowledged":true}' - # done - # date=$(/bin/date +%F) - # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot_${date}?wait_for_completion=true" >> "${LOGFILE}" - - ## RabbitMQ - - ## export config - # rabbitmqadmin export ${LOCAL_BACKUP_DIR}/rabbitmq.config >> "${LOGFILE}" - - ## MegaCli config - - # megacli -CfgSave -f ${LOCAL_BACKUP_DIR}/megacli_conf.dump -a0 >/dev/null - - ## Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) network_targets="8.8.8.8 www.evolix.fr travaux.evolix.net" + mtr_bin=$(command -v mtr) - if [ -n "${mtr_bin}" ]; then + if [ -n "${network_targets}" ] && [ -n "${mtr_bin}" ]; then for addr in ${network_targets}; do - ${mtr_bin} -r "${addr}" > "${LOCAL_BACKUP_DIR}/mtr-${addr}" - done - fi - traceroute_bin=$(command -v traceroute) - if [ -n "${traceroute_bin}" ]; then - for addr in ${network_targets}; do - ${traceroute_bin} -n "${addr}" > "${LOCAL_BACKUP_DIR}/traceroute-${addr}" 2>&1 + ${mtr_bin} -r "${addr}" > "${dump_dir}/mtr-${addr}" done fi - server_state_dir="${LOCAL_BACKUP_DIR}/server-state" + traceroute_bin=$(command -v traceroute) + if [ -n "${network_targets}" ] && [ -n "${traceroute_bin}" ]; then + for addr in ${network_targets}; do + ${traceroute_bin} -n "${addr}" > "${dump_dir}/traceroute-${addr}" 2>&1 + done + fi +} +# shellcheck disable=SC2317 +dump_server_state() { + dump_dir="${LOCAL_BACKUP_DIR}/server-state" + rm -rf "${dump_dir}" + # Do not create the directory + # mkdir -p -m 700 "${dump_dir}" dump_server_state_bin=$(command -v dump-server-state) if [ -z "${dump_server_state_bin}" ]; then @@ -358,27 +390,126 @@ local_tasks() { rc=1 else if [ "${SYSTEM}" = "linux" ]; then - ${dump_server_state_bin} --all --force --dump-dir "${server_state_dir}" + ${dump_server_state_bin} --all --dump-dir "${dump_dir}" last_rc=$? if [ ${last_rc} -ne 0 ]; then - error "dump-server-state returned an error ${last_rc}, check ${server_state_dir}" + error "dump-server-state returned an error ${last_rc}, check ${dump_dir}" rc=1 fi else - ${dump_server_state_bin} --all --force --dump-dir "${server_state_dir}" + ${dump_server_state_bin} --all --dump-dir "${dump_dir}" last_rc=$? if [ ${last_rc} -ne 0 ]; then - error "dump-server-state returned an error ${last_rc}, check ${server_state_dir}" + error "dump-server-state returned an error ${last_rc}, check ${dump_dir}" rc=1 fi fi fi +} +# shellcheck disable=SC2317 +dump_rabbitmq() { + dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" - ## Dump rights - # getfacl -R /var > ${server_state_dir}/rights-var.txt - # getfacl -R /etc > ${server_state_dir}/rights-etc.txt - # getfacl -R /usr > ${server_state_dir}/rights-usr.txt - # getfacl -R /home > ${server_state_dir}/rights-home.txt + rabbitmqadmin export "${dump_dir}/config" >> "${LOGFILE}" +} +# shellcheck disable=SC2317 +dump_facl() { + dump_dir="${LOCAL_BACKUP_DIR}/facl/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" + + getfacl -R /etc > "${dump_dir}/etc.txt" + getfacl -R /home > "${dump_dir}/home.txt" + getfacl -R /usr > "${dump_dir}/usr.txt" + getfacl -R /var > "${dump_dir}/var.txt" +} +# shellcheck disable=SC2317 +dump_elasticsearch_snapshot() { + ## Take a snapshot as a backup. + ## Warning: You need to have a path.repo configured. + ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes + + curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" + curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" + + # Clustered version here + # It basically the same thing except that you need to check that NFS is mounted + # if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2' + # then + # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" + # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" + # else + # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' + # fi + + ## If you need to keep older snapshot, for example the last 10 daily snapshots, replace the XDELETE and XPUT lines by : + # for snapshot in $(curl -s -XGET "localhost:9200/_snapshot/snaprepo/_all?pretty=true" | grep -Eo 'snapshot_[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -n -10); do + # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/${snapshot}" | grep -v -Fx '{"acknowledged":true}' + # done + # date=$(/bin/date +%F) + # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot_${date}?wait_for_completion=true" >> "${LOGFILE}" +} + +local_tasks() { + log "START LOCAL_TASKS" + + # Remove previous error files + find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -delete + + # You can comment or uncomment sections below to customize the backup + + ## OpenLDAP + # dump_ldap + + ## MySQL + + ### example with global and compressed mysqldump + # dump_mysql_global + ### example with compressed SQL dump (with data) for each databases + # dump_mysql_per_base + ### meta-data (grants, variables, schema…) + # dump_mysql_meta + ### example with two dumps for each table (.sql/.txt) for all databases + # dump_mysql_tabs + ### example with mysqlhotcopy + # dump_mysql_hotcopy + ### example for multiples MySQL instances + # dump_mysql_instances + + ## PostgreSQL + + ### example with global dump + # dump_postgresql_global + ### example with filtered tables ("only" or "except") + # dump_postgresql_filtered + ### example with compressed PostgreSQL dump for each databases + # dump_postgresql_per_base + + ## MongoDB + # dump_mongodb + + ## Redis + # dump_redis + + ## ElasticSearch + # dump_elasticsearch_snapshot + + ## RabbitMQ config + # dump_rabbitmq + + ## MegaCli config + # dump_megacli_config + + ## Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) + dump_traceroute + + ## Dump various information about server state + dump_server_state + + ## Dump file access control lists + # dump_facl log "STOP LOCAL_TASKS" } @@ -658,8 +789,12 @@ main() { # set all programs to C language (english) export LC_ALL=C -# Error on unassigned variable +# If expansion is attempted on an unset variable or parameter, the shell prints an +# error message, and, if not interactive, exits with a non-zero status. set -u +# The pipeline's return status is the value of the last (rightmost) command +# to exit with a non-zero status, or zero if all commands exit successfully. +set -o pipefail # Default return-code (0 == succes) rc=0 From 4496ea883a4ae0f0ff1c29aafb994192bdeb0384 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 3 Jan 2023 09:59:13 +0100 Subject: [PATCH 002/105] explicit canary file --- client/zzz_evobackup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 4f289d4..3464cfa 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -760,7 +760,7 @@ main() { trap "rm -f ${temp_files}" EXIT # Update canary to keep track of each run - update-evobackup-canary --who "${PROGNAME}" + update-evobackup-canary --who "${PROGNAME}" --file "${CANARY_FILE}" if [ "${LOCAL_TASKS}" = "1" ]; then local_tasks From e3c7da32a9a4b8730842aab166e15c4c50c0eb1f Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 3 Jan 2023 23:30:50 +0100 Subject: [PATCH 003/105] Add logs and error control --- client/zzz_evobackup | 336 +++++++++++++++++++++++++++++++++---------- 1 file changed, 260 insertions(+), 76 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 3464cfa..9e133a4 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -127,10 +127,11 @@ lxc/*/rootfs/var/tmp ##### FUNCTIONS ####################################################### +# shellcheck disable=SC2317 mysql_list_databases() { port=${1:-"3306"} - mysql --defaults-extra-file=/etc/mysql/debian.cnf -P ${port} -e 'show databases' -s --skip-column-names | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" + mysql --defaults-extra-file=/etc/mysql/debian.cnf -P "${port}" -e 'show databases' -s --skip-column-names | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" } # shellcheck disable=SC2317 dump_ldap() { @@ -139,9 +140,13 @@ dump_ldap() { rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" + log "LOCAL_TASKS - start dump_ldap to ${dump_dir}" + slapcat -n 0 -l "${dump_dir}/config.bak" slapcat -n 1 -l "${dump_dir}/data.bak" slapcat -l "${dump_dir}/ldap.bak" + + log "LOCAL_TASKS - stop dump_ldap" } # shellcheck disable=SC2317 dump_mysql_global() { @@ -149,15 +154,21 @@ dump_mysql_global() { rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${dump_dir}/mysql.bak.err" | gzip --best > "${dump_dir}/mysql.bak.gz" + error_file="${dump_dir}/mysql.bak.err" + dump_file="${dump_dir}/mysql.bak.err" + log "LOCAL_TASKS - start ${dump_file}" + + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" | gzip --best > "${dump_file}" last_rc=$? + # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump (global compressed) returned an error ${last_rc}, check ${dump_dir}/mysql.bak.err" - rc=101 + error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 else - rm -f "${dump_dir}/mysql.bak.err" + rm -f "${error_file}" fi + log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_mysql_per_base() { @@ -165,16 +176,23 @@ dump_mysql_per_base() { rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" - for i in $(mysql_list_databases 3306); do - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob $i 2> "${dump_dir}/${i}.err" | gzip --best > "${dump_dir}/${i}.sql.gz" + databases=$(mysql_list_databases 3306) + for database in ${databases}; do + error_file="${dump_dir}/${database}.err" + dump_file="${dump_dir}/${database}.sql.gz" + log "LOCAL_TASKS - start ${dump_file}" + + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" last_rc=$? + # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump (${i} compressed) returned an error ${last_rc}, check ${dump_dir}/${i}.err" - rc=102 + error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 else - rm -f "${dump_dir}/${i}.err" + rm -f "${error_file}" fi + log "LOCAL_TASKS - stop ${dump_file}" done } # shellcheck disable=SC2317 @@ -184,74 +202,108 @@ dump_mysql_meta() { mkdir -p -m 700 "${dump_dir}" ## Dump all grants (requires 'percona-toolkit' package) - pt-show-grants --flush --no-header 2> "${dump_dir}/all_grants.err" > "${dump_dir}/all_grants.sql" + error_file="${dump_dir}/all_grants.err" + dump_file="${dump_dir}/all_grants.sql" + log "LOCAL_TASKS - start ${dump_file}" + + pt-show-grants --flush --no-header 2> "${error_file}" > "${dump_file}" last_rc=$? + # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "pt-show-grants returned an error ${last_rc}, check ${dump_dir}/all_grants.err" - rc=103 + error "pt-show-grants to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 else - rm -f "${dump_dir}/all_grants.err" + rm -f "${error_file}" fi + log "LOCAL_TASKS - stop ${dump_file}" ## Dump all variables - mysql -A -e"SHOW GLOBAL VARIABLES;" 2> "${dump_dir}/variables.err" > "${dump_dir}/variables.txt" + error_file="${dump_dir}/variables.err" + dump_file="${dump_dir}/variables.txt" + log "LOCAL_TASKS - start ${dump_file}" + + mysql -A -e "SHOW GLOBAL VARIABLES;" 2> "${error_file}" > "${dump_file}" last_rc=$? + # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysql (variables) returned an error ${last_rc}, check ${dump_dir}/variables.err" - rc=104 + error "mysql 'show variables' returned an error ${last_rc}, check ${error_file}" + rc=100 else - rm -f "${dump_dir}/variables.err" + rm -f "${error_file}" fi + log "LOCAL_TASKS - stop ${dump_file}" ## Schema only (no data) for each databases - for i in $(mysql_list_databases 3306); do - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --no-data --databases $i 2> "${dump_dir}/${i}.schema.err" > "${dump_dir}/${i}.schema.sql" + databases=$(mysql_list_databases 3306) + for database in ${databases}; do + error_file="${dump_dir}/${database}.schema.err" + dump_file="${dump_dir}/${database}.schema.sql" + log "LOCAL_TASKS - start ${dump_file}" + + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --no-data --databases "${database}" 2> "${error_file}" > "${dump_file}" last_rc=$? + # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump (${i} schema) returned an error ${last_rc}, check ${dump_dir}/${i}.schema.err" - rc=105 + error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 else - rm -f "${dump_dir}/${i}.schema.err" + rm -f "${error_file}" fi + log "LOCAL_TASKS - stop ${dump_file}" done } # shellcheck disable=SC2317 dump_mysql_tabs() { - for i in $(mysql_list_databases 3306); do - dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs/$i" + databases=$(mysql_list_databases 3306) + for database in ${databases}; do + dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs/${database}" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" chown -RL mysql "${dump_dir}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 -Q --opt --events --hex-blob --skip-comments --fields-enclosed-by='\"' --fields-terminated-by=',' -T "${dump_dir}" $i 2> "${dump_dir}.err" + error_file="${dump_dir}.err" + log "LOCAL_TASKS - start ${dump_dir}" + + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 -Q --opt --events --hex-blob --skip-comments --fields-enclosed-by='\"' --fields-terminated-by=',' -T "${dump_dir}" "${database}" 2> "${error_file}" last_rc=$? + # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump (${i} files) returned an error ${last_rc}, check ${dump_dir}.err" - rc=107 + error "mysqldump to ${dump_dir} returned an error ${last_rc}, check ${error_file}" + rc=100 else - rm -f "${dump_dir}.err" + rm -f "${error_file}" fi + log "LOCAL_TASKS - stop ${dump_dir}" done } # shellcheck disable=SC2317 dump_mysql_hotcopy() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-hotcopy/" - rm -rf "${dump_dir}" - mkdir -p -m 700 "${dump_dir}" + # customize the list of databases to hot-copy + databases="" + for database in ${databases}; do + dump_dir="${LOCAL_BACKUP_DIR}/mysql-hotcopy/${database}" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" - mysqlhotcopy MYBASE "${dump_dir}/" 2> "${dump_dir}/MYBASE.err" + error_file="${dump_dir}.err" + log "LOCAL_TASKS - start ${dump_dir}" - last_rc=$? - if [ ${last_rc} -ne 0 ]; then - error "mysqlhotcopy returned an error ${last_rc}, check ${dump_dir}/MYBASE.err" - rc=108 - else - rm -f "${dump_dir}/MYBASE.err" - fi + mysqlhotcopy "${database}" "${dump_dir}/" 2> "${error_file}" + + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "mysqlhotcopy to ${dump_dir} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_dir}" + done } # shellcheck disable=SC2317 dump_mysql_instances() { @@ -259,21 +311,26 @@ dump_mysql_instances() { rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" - mysqladminpasswd=$(grep -m1 'password = .*' /root/.my.cnf|cut -d" " -f3) + mysqladminpasswd=$(grep -m1 'password = .*' /root/.my.cnf | cut -d " " -f 3) - grep --extended-regexp "^port\s*=\s*\d*" /etc/mysql/my.cnf | while read instance; do - instance=$(echo "$instance"|awk '{ print $3 }') - if [ "$instance" != "3306" ]; then - mysqldump -P ${instance} --opt --all-databases --hex-blob -u mysqladmin -p${mysqladminpasswd} 2> "${dump_dir}/${instance}.err" | gzip --best > "${dump_dir}/${instance}.bak.gz" + # customize list of instances + instances="" + for instance in ${instances}; do + error_file="${dump_dir}/${instance}.err" + dump_file="${dump_dir}/${instance}.bak.gz" + log "LOCAL_TASKS - start ${dump_file}" - last_rc=$? - if [ ${last_rc} -ne 0 ]; then - error "mysqldump (instance ${instance}) returned an error ${last_rc}, check ${dump_dir}/${instance}.err" - rc=107 - else - rm -f "${dump_dir}/${instance}.err" - fi + mysqldump -P "${instance}" --opt --all-databases --hex-blob -u mysqladmin -p"${mysqladminpasswd}" 2> "${error_file}" | gzip --best > "${dump_file}" + + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" fi + log "LOCAL_TASKS - stop ${dump_file}" done } # shellcheck disable=SC2317 @@ -282,17 +339,32 @@ dump_postgresql_global() { rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" - ## example with pg_dumpall - # WARNING: you need space in ~postgres - su - postgres -c "pg_dumpall > ~/pg.dump.bak" - mv ~postgres/pg.dump.bak "${dump_dir}/" + ## example with pg_dumpall and with compression + dump_file="${dump_dir}/pg.dump.bak.gz" + log "LOCAL_TASKS - start ${dump_file}" - ## another method with gzip directly piped - # ( - # cd /var/lib/postgresql; - # sudo -u postgres pg_dumpall | gzip > ${dump_dir}/pg.dump.bak.gz - # ) + (sudo -u postgres pg_dumpall) 2> "${error_file}" | gzip --best > "${dump_file}" + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "pg_dumpall to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" + fi + + log "LOCAL_TASKS - stop ${dump_file}" + + ## example with pg_dumpall and without compression + ## WARNING: you need space in ~postgres + # dump_file="${dump_dir}/pg.dump.bak" + # log "LOCAL_TASKS - start ${dump_file}" + # + # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" + # mv ~postgres/pg.dump.bak "${dump_file}" + # + # log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_postgresql_per_base() { @@ -302,9 +374,23 @@ dump_postgresql_per_base() { ( cd /var/lib/postgresql - databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v template*) + databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") for database in ${databases} ; do - sudo -u postgres /usr/bin/pg_dump --create -s -U postgres -d ${database} | gzip --best -c > ${dump_dir}/$databases.sql.gz + error_file="${dump_dir}/${database}.err" + dump_file="${dump_dir}/${database}.sql.gz" + log "LOCAL_TASKS - start ${dump_file}" + + (sudo -u postgres /usr/bin/pg_dump --create -s -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" + + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" done ) } @@ -314,11 +400,25 @@ dump_postgresql_filtered() { rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" + error_file="${dump_dir}/pg-backup.err" + dump_file="${dump_dir}/pg-backup.tar" + log "LOCAL_TASKS - start ${dump_file}" + ## example with all tables from MYBASE excepts TABLE1 and TABLE2 - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_dir}/pg-backup.tar" -t 'TABLE1' -t 'TABLE2' MYBASE + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -t 'TABLE1' -t 'TABLE2' MYBASE 2> "${error_file}" ## example with only TABLE1 and TABLE2 from MYBASE - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_dir}/pg-backup.tar" -T 'TABLE1' -T 'TABLE2' MYBASE + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -T 'TABLE1' -T 'TABLE2' MYBASE 2> "${error_file}" + + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_redis() { @@ -329,7 +429,19 @@ dump_redis() { if [ -f "${instance}/dump.rdb" ]; then mkdir -p -m 700 "${dump_dir}" - cp -a "${instance}/dump.rdb" "${dump_dir}/" + log "LOCAL_TASKS - start ${dump_dir}" + + cp -a "${instance}/dump.rdb" "${dump_dir}/" 2> "${error_file}" + + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "cp ${instance}/dump.rdb to ${dump_dir} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_dir}" fi done } @@ -339,21 +451,49 @@ dump_mongodb() { ## > use admin ## > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) - dump_dir="${LOCAL_BACKUP_DIR}/mongodump/" + dump_dir="${LOCAL_BACKUP_DIR}/mongodump" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" + + error_file="${dump_dir}.err" + log "LOCAL_TASKS - start ${dump_dir}" + mongo_user="" mongo_password="" - mongodump --quiet -u ${mongo_user} -p${mongo_password} -o "${dump_dir}/" + mongodump -u "${mongo_user}" -p"${mongo_password}" -o "${dump_dir}/" 2> "${error_file}" > /dev/null - if [ $? -ne 0 ]; then - echo "Error with mongodump!" + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "mongodump to ${dump_dir} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" fi + log "LOCAL_TASKS - stop ${dump_dir}" } # shellcheck disable=SC2317 dump_megacli_config() { - megacli -CfgSave -f "${LOCAL_BACKUP_DIR}/megacli_conf.dump" -a0 >/dev/null + dump_dir="${LOCAL_BACKUP_DIR}/megacli/" + rm -rf "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" + + dump_file="${dump_dir}/megacli.cfg" + error_file="${dump_dir}/megacli.err" + log "LOCAL_TASKS - start ${dump_file}" + + megacli -CfgSave -f "${dump_file}" -a0 2> "${error_file}" > /dev/null + + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "megacli to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_traceroute() { @@ -366,14 +506,24 @@ dump_traceroute() { mtr_bin=$(command -v mtr) if [ -n "${network_targets}" ] && [ -n "${mtr_bin}" ]; then for addr in ${network_targets}; do - ${mtr_bin} -r "${addr}" > "${dump_dir}/mtr-${addr}" + dump_file="${dump_dir}/mtr-${addr}" + log "LOCAL_TASKS - start ${dump_file}" + + ${mtr_bin} -r "${addr}" > "${dump_file}" + + log "LOCAL_TASKS - stop ${dump_file}" done fi traceroute_bin=$(command -v traceroute) if [ -n "${network_targets}" ] && [ -n "${traceroute_bin}" ]; then for addr in ${network_targets}; do - ${traceroute_bin} -n "${addr}" > "${dump_dir}/traceroute-${addr}" 2>&1 + dump_file="${dump_dir}/traceroute-${addr}" + log "LOCAL_TASKS - start ${dump_file}" + + ${traceroute_bin} -n "${addr}" > "${dump_file}" 2>&1 + + log "LOCAL_TASKS - stop ${dump_file}" done fi } @@ -384,6 +534,8 @@ dump_server_state() { # Do not create the directory # mkdir -p -m 700 "${dump_dir}" + log "LOCAL_TASKS - start ${dump_dir}" + dump_server_state_bin=$(command -v dump-server-state) if [ -z "${dump_server_state_bin}" ]; then error "dump-server-state is missing" @@ -392,19 +544,22 @@ dump_server_state() { if [ "${SYSTEM}" = "linux" ]; then ${dump_server_state_bin} --all --dump-dir "${dump_dir}" last_rc=$? + # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "dump-server-state returned an error ${last_rc}, check ${dump_dir}" - rc=1 + rc=100 fi else ${dump_server_state_bin} --all --dump-dir "${dump_dir}" last_rc=$? + # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "dump-server-state returned an error ${last_rc}, check ${dump_dir}" - rc=1 + rc=100 fi fi fi + log "LOCAL_TASKS - stop ${dump_dir}" } # shellcheck disable=SC2317 dump_rabbitmq() { @@ -412,7 +567,21 @@ dump_rabbitmq() { rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" - rabbitmqadmin export "${dump_dir}/config" >> "${LOGFILE}" + error_file="${dump_dir}.err" + dump_file="${dump_dir}/config" + log "LOCAL_TASKS - start ${dump_file}" + + rabbitmqadmin export "${dump_file}" 2> "${error_file}" >> "${LOGFILE}" + + last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + rc=100 + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_facl() { @@ -420,13 +589,19 @@ dump_facl() { rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" + log "LOCAL_TASKS - start ${dump_dir}" + getfacl -R /etc > "${dump_dir}/etc.txt" getfacl -R /home > "${dump_dir}/home.txt" getfacl -R /usr > "${dump_dir}/usr.txt" getfacl -R /var > "${dump_dir}/var.txt" + + log "LOCAL_TASKS - stop ${dump_dir}" } # shellcheck disable=SC2317 dump_elasticsearch_snapshot() { + log "LOCAL_TASKS - start dump_elasticsearch_snapshot" + ## Take a snapshot as a backup. ## Warning: You need to have a path.repo configured. ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes @@ -450,6 +625,8 @@ dump_elasticsearch_snapshot() { # done # date=$(/bin/date +%F) # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot_${date}?wait_for_completion=true" >> "${LOGFILE}" + + log "LOCAL_TASKS - stop dump_elasticsearch_snapshot" } local_tasks() { @@ -511,6 +688,13 @@ local_tasks() { ## Dump file access control lists # dump_facl + # Remove previous error files + for error_file in $(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -print); do + printf "### Content of %s ###\n" "${error_file}" >&2 + cat "${error_file}" >&2 + printf "########################################\n" >&2 + done + log "STOP LOCAL_TASKS" } build_rsync_main_cmd() { From 910a7398fbe2de8c5146fab81b7dd355490c0a13 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 3 Jan 2023 23:50:23 +0100 Subject: [PATCH 004/105] error codes --- client/zzz_evobackup | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 9e133a4..65fa5c1 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -124,7 +124,6 @@ lxc/*/rootfs/var/tmp /home/mysqltmp " - ##### FUNCTIONS ####################################################### # shellcheck disable=SC2317 @@ -164,7 +163,7 @@ dump_mysql_global() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -188,7 +187,7 @@ dump_mysql_per_base() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -212,7 +211,7 @@ dump_mysql_meta() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "pt-show-grants to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -229,7 +228,7 @@ dump_mysql_meta() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "mysql 'show variables' returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -248,7 +247,7 @@ dump_mysql_meta() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -273,7 +272,7 @@ dump_mysql_tabs() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "mysqldump to ${dump_dir} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -298,7 +297,7 @@ dump_mysql_hotcopy() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "mysqlhotcopy to ${dump_dir} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -326,7 +325,7 @@ dump_mysql_instances() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -349,7 +348,7 @@ dump_postgresql_global() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "pg_dumpall to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -386,7 +385,7 @@ dump_postgresql_per_base() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -414,7 +413,7 @@ dump_postgresql_filtered() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -437,7 +436,7 @@ dump_redis() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "cp ${instance}/dump.rdb to ${dump_dir} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -467,7 +466,7 @@ dump_mongodb() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "mongodump to ${dump_dir} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -489,7 +488,7 @@ dump_megacli_config() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "megacli to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -547,7 +546,7 @@ dump_server_state() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "dump-server-state returned an error ${last_rc}, check ${dump_dir}" - rc=100 + rc=${E_DUMPFAILED} fi else ${dump_server_state_bin} --all --dump-dir "${dump_dir}" @@ -555,7 +554,7 @@ dump_server_state() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "dump-server-state returned an error ${last_rc}, check ${dump_dir}" - rc=100 + rc=${E_DUMPFAILED} fi fi fi @@ -577,7 +576,7 @@ dump_rabbitmq() { # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" - rc=100 + rc=${E_DUMPFAILED} else rm -f "${error_file}" fi @@ -773,7 +772,7 @@ sync_tasks() { # We try to find a suitable server while :; do server=$(pick_server "${n}") - test $? = 0 || exit 2 + test $? = 0 || exit ${E_NOSRVAVAIL} if test_server "${server}"; then break @@ -822,7 +821,7 @@ sync_tasks() { if [ ${rsync_main_rc} -ne 0 ]; then error "rsync returned an error ${rsync_main_rc}, check ${LOGFILE}" - rc=201 + rc=${E_SYNCFAILED} else # Build the canary Rsync command rsync_canary_cmd=$(build_rsync_canary_cmd) @@ -983,6 +982,11 @@ set -o pipefail # Default return-code (0 == succes) rc=0 +### Possible error codes +E_NOSRVAVAIL=21 # No server is available +E_SYNCFAILED=20 # Faild sync task +E_DUMPFAILED=10 # Faild dump task + # execute main funciton main From c368c9b11a8a873d57226a69fb79ab409963012c Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 3 Jan 2023 23:50:34 +0100 Subject: [PATCH 005/105] typo --- client/zzz_evobackup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 65fa5c1..d2de288 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -987,7 +987,7 @@ E_NOSRVAVAIL=21 # No server is available E_SYNCFAILED=20 # Faild sync task E_DUMPFAILED=10 # Faild dump task -# execute main funciton +# execute main function main exit ${rc} From c6a89cbc325db3925172d40b736434a53ef45197 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 07:30:54 +0100 Subject: [PATCH 006/105] Reorder functions --- client/zzz_evobackup | 247 ++++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 121 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index d2de288..97a741f 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -126,6 +126,75 @@ lxc/*/rootfs/var/tmp ##### FUNCTIONS ####################################################### +local_tasks() { + log "START LOCAL_TASKS" + + # Remove previous error files + find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -delete + + # You can comment or uncomment sections below to customize the backup + + ## OpenLDAP + # dump_ldap + + ## MySQL + + ### example with global and compressed mysqldump + # dump_mysql_global + ### example with compressed SQL dump (with data) for each databases + # dump_mysql_per_base + ### meta-data (grants, variables, schema…) + # dump_mysql_meta + ### example with two dumps for each table (.sql/.txt) for all databases + # dump_mysql_tabs + ### example with mysqlhotcopy + # dump_mysql_hotcopy + ### example for multiples MySQL instances + # dump_mysql_instances + + ## PostgreSQL + + ### example with global dump + # dump_postgresql_global + ### example with filtered tables ("only" or "except") + # dump_postgresql_filtered + ### example with compressed PostgreSQL dump for each databases + # dump_postgresql_per_base + + ## MongoDB + # dump_mongodb + + ## Redis + # dump_redis + + ## ElasticSearch + # dump_elasticsearch_snapshot + + ## RabbitMQ config + # dump_rabbitmq + + ## MegaCli config + # dump_megacli_config + + ## Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) + dump_traceroute + + ## Dump various information about server state + dump_server_state + + ## Dump file access control lists + # dump_facl + + # Remove previous error files + for error_file in $(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -print); do + printf "### Content of %s ###\n" "${error_file}" >&2 + cat "${error_file}" >&2 + printf "########################################\n" >&2 + done + + log "STOP LOCAL_TASKS" +} + # shellcheck disable=SC2317 mysql_list_databases() { port=${1:-"3306"} @@ -628,74 +697,6 @@ dump_elasticsearch_snapshot() { log "LOCAL_TASKS - stop dump_elasticsearch_snapshot" } -local_tasks() { - log "START LOCAL_TASKS" - - # Remove previous error files - find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -delete - - # You can comment or uncomment sections below to customize the backup - - ## OpenLDAP - # dump_ldap - - ## MySQL - - ### example with global and compressed mysqldump - # dump_mysql_global - ### example with compressed SQL dump (with data) for each databases - # dump_mysql_per_base - ### meta-data (grants, variables, schema…) - # dump_mysql_meta - ### example with two dumps for each table (.sql/.txt) for all databases - # dump_mysql_tabs - ### example with mysqlhotcopy - # dump_mysql_hotcopy - ### example for multiples MySQL instances - # dump_mysql_instances - - ## PostgreSQL - - ### example with global dump - # dump_postgresql_global - ### example with filtered tables ("only" or "except") - # dump_postgresql_filtered - ### example with compressed PostgreSQL dump for each databases - # dump_postgresql_per_base - - ## MongoDB - # dump_mongodb - - ## Redis - # dump_redis - - ## ElasticSearch - # dump_elasticsearch_snapshot - - ## RabbitMQ config - # dump_rabbitmq - - ## MegaCli config - # dump_megacli_config - - ## Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) - dump_traceroute - - ## Dump various information about server state - dump_server_state - - ## Dump file access control lists - # dump_facl - - # Remove previous error files - for error_file in $(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -print); do - printf "### Content of %s ###\n" "${error_file}" >&2 - cat "${error_file}" >&2 - printf "########################################\n" >&2 - done - - log "STOP LOCAL_TASKS" -} build_rsync_main_cmd() { ################################################################### # /!\ WARNING /!\ WARNING /!\ WARNING /!\ WARNING /!\ WARNING /!\ # @@ -765,6 +766,61 @@ build_rsync_canary_cmd() { # output final command echo "${cmd}" } + +# Call test_server with "HOST:PORT" string +# It will return with 0 if the server is reachable. +# It will return with 1 and a message on stderr if not. +test_server() { + item=$1 + # split HOST and PORT from the input string + host=$(echo "${item}" | cut -d':' -f1) + port=$(echo "${item}" | cut -d':' -f2) + + # Test if the server is accepting connections + ssh -q -o "ConnectTimeout ${SSH_CONNECT_TIMEOUT}" "${host}" -p "${port}" -t "exit" + # shellcheck disable=SC2181 + if [ $? = 0 ]; then + # SSH connection is OK + return 0 + else + # SSH connection failed + new_error=$(printf "Failed to connect to \`%s' within %s seconds" "${item}" "${SSH_CONNECT_TIMEOUT}") + log "${new_error}" + SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d') + + return 1 + fi +} +# Call pick_server with an optional positive integer to get the nth server in the list. +pick_server() { + increment=${1:-0} + list_length=$(echo "${SERVERS}" | wc -w) + + if [ "${increment}" -ge "${list_length}" ]; then + # We've reached the end of the list + new_error="No more server available" + log "${new_error}" + SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d') + + # Log errors to stderr + printf "%s\\n" "${SERVERS_SSH_ERRORS}" >&2 + return 1 + fi + + # Extract the day of month, without leading 0 (which would give an octal based number) + today=$(/bin/date +%e) + # A salt is useful to randomize the starting point in the list + # but stay identical each time it's called for a server (based on hostname). + salt=$(hostname | cksum | cut -d' ' -f1) + # Pick an integer between 0 and the length of the SERVERS list + # It changes each day + item=$(( (today + salt + increment) % list_length )) + # cut starts counting fields at 1, not 0. + field=$(( item + 1 )) + + echo "${SERVERS}" | cut -d ' ' -f ${field} +} + sync_tasks() { n=0 server="" @@ -836,59 +892,7 @@ sync_tasks() { log "STOP SYNC_TASKS - server=${server}" } -# Call test_server with "HOST:PORT" string -# It will return with 0 if the server is reachable. -# It will return with 1 and a message on stderr if not. -test_server() { - item=$1 - # split HOST and PORT from the input string - host=$(echo "${item}" | cut -d':' -f1) - port=$(echo "${item}" | cut -d':' -f2) - - # Test if the server is accepting connections - ssh -q -o "ConnectTimeout ${SSH_CONNECT_TIMEOUT}" "${host}" -p "${port}" -t "exit" - # shellcheck disable=SC2181 - if [ $? = 0 ]; then - # SSH connection is OK - return 0 - else - # SSH connection failed - new_error=$(printf "Failed to connect to \`%s' within %s seconds" "${item}" "${SSH_CONNECT_TIMEOUT}") - log "${new_error}" - SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d') - - return 1 - fi -} -# Call pick_server with an optional positive integer to get the nth server in the list. -pick_server() { - increment=${1:-0} - list_length=$(echo "${SERVERS}" | wc -w) - - if [ "${increment}" -ge "${list_length}" ]; then - # We've reached the end of the list - new_error="No more server available" - log "${new_error}" - SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d') - - # Log errors to stderr - printf "%s\\n" "${SERVERS_SSH_ERRORS}" >&2 - return 1 - fi - - # Extract the day of month, without leading 0 (which would give an octal based number) - today=$(/bin/date +%e) - # A salt is useful to randomize the starting point in the list - # but stay identical each time it's called for a server (based on hostname). - salt=$(hostname | cksum | cut -d' ' -f1) - # Pick an integer between 0 and the length of the SERVERS list - # It changes each day - item=$(( (today + salt + increment) % list_length )) - # cut starts counting fields at 1, not 0. - field=$(( item + 1 )) - - echo "${SERVERS}" | cut -d' ' -f${field} -} +# Output a message to the log file log() { msg="${1:-$(cat /dev/stdin)}" pid=$$ @@ -896,6 +900,7 @@ log() { "$(/bin/date +"${DATE_FORMAT}")" "${PROGNAME}" "${pid}" "${msg}" \ >> "${LOGFILE}" } +# Output a message to stderr error() { msg="${1:-$(cat /dev/stdin)}" pid=$$ From 65ba8695ad8ebce9fc9d37f155ab862ee6102898 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 07:45:47 +0100 Subject: [PATCH 007/105] Add documentation comments --- client/zzz_evobackup | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 97a741f..f57cbab 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -13,8 +13,19 @@ # and others. # # Licence: AGPLv3 + +###################################################################### +# To configure the script: # -# /!\ DON'T FORGET TO SET "MAIL" and "SERVERS" VARIABLES +# 1. Set the following MAIL and SERVERS variables. +# 2. Customize the RSYNC_INCLUDES and RSYNC_EXCLUDES variables. +# 3. Enable or disable local tasks inside the local_tasks() function. +# +# Some local tasks are configurable. +# If you enable them, have a look at their implementation. +# +####################################################################### + ##### Configuration ################################################### From b1c5b693eeaf95394e4d0abf7005f6a34d755fb5 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 09:13:22 +0100 Subject: [PATCH 008/105] Output error file if size is not null --- client/zzz_evobackup | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index f57cbab..005cdae 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -198,9 +198,11 @@ local_tasks() { # Remove previous error files for error_file in $(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -print); do - printf "### Content of %s ###\n" "${error_file}" >&2 - cat "${error_file}" >&2 - printf "########################################\n" >&2 + if [ $(stat -c "%s" "${error_file}") -gt 0 ]; then + printf "### Content of %s ###\n" "${error_file}" >&2 + cat "${error_file}" >&2 + printf "########################################\n" >&2 + fi done log "STOP LOCAL_TASKS" From 02359065460d228773b008114db2f268ab743cf1 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 09:13:46 +0100 Subject: [PATCH 009/105] fix dump_file --- client/zzz_evobackup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 005cdae..67848df 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -236,7 +236,7 @@ dump_mysql_global() { mkdir -p -m 700 "${dump_dir}" error_file="${dump_dir}/mysql.bak.err" - dump_file="${dump_dir}/mysql.bak.err" + dump_file="${dump_dir}/mysql.bak.gz" log "LOCAL_TASKS - start ${dump_file}" mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" | gzip --best > "${dump_file}" From b6d50cc921d24f02d48b78dfccb8ba4b5119722f Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 09:15:26 +0100 Subject: [PATCH 010/105] remove trailing slash in dump_dir --- client/zzz_evobackup | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 67848df..5a4804e 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -217,7 +217,7 @@ mysql_list_databases() { # shellcheck disable=SC2317 dump_ldap() { ## OpenLDAP : example with slapcat - dump_dir="${LOCAL_BACKUP_DIR}/ldap/" + dump_dir="${LOCAL_BACKUP_DIR}/ldap" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -231,7 +231,7 @@ dump_ldap() { } # shellcheck disable=SC2317 dump_mysql_global() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-global/" + dump_dir="${LOCAL_BACKUP_DIR}/mysql-global" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -253,7 +253,7 @@ dump_mysql_global() { } # shellcheck disable=SC2317 dump_mysql_per_base() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base/" + dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -278,7 +278,7 @@ dump_mysql_per_base() { } # shellcheck disable=SC2317 dump_mysql_meta() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta/" + dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -388,7 +388,7 @@ dump_mysql_hotcopy() { } # shellcheck disable=SC2317 dump_mysql_instances() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances/" + dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -416,7 +416,7 @@ dump_mysql_instances() { } # shellcheck disable=SC2317 dump_postgresql_global() { - dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global/" + dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -449,7 +449,7 @@ dump_postgresql_global() { } # shellcheck disable=SC2317 dump_postgresql_per_base() { - dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base/" + dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -477,7 +477,7 @@ dump_postgresql_per_base() { } # shellcheck disable=SC2317 dump_postgresql_filtered() { - dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered/" + dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -505,7 +505,7 @@ dump_postgresql_filtered() { dump_redis() { for instance in $(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*'); do name=$(basename "${instance}") - dump_dir="${LOCAL_BACKUP_DIR}/${name}/" + dump_dir="${LOCAL_BACKUP_DIR}/${name}" rm -rf "${dump_dir}" if [ -f "${instance}/dump.rdb" ]; then @@ -556,7 +556,7 @@ dump_mongodb() { } # shellcheck disable=SC2317 dump_megacli_config() { - dump_dir="${LOCAL_BACKUP_DIR}/megacli/" + dump_dir="${LOCAL_BACKUP_DIR}/megacli" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -578,7 +578,7 @@ dump_megacli_config() { } # shellcheck disable=SC2317 dump_traceroute() { - dump_dir="${LOCAL_BACKUP_DIR}/traceroute/" + dump_dir="${LOCAL_BACKUP_DIR}/traceroute" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -644,7 +644,7 @@ dump_server_state() { } # shellcheck disable=SC2317 dump_rabbitmq() { - dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq/" + dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" @@ -666,7 +666,7 @@ dump_rabbitmq() { } # shellcheck disable=SC2317 dump_facl() { - dump_dir="${LOCAL_BACKUP_DIR}/facl/" + dump_dir="${LOCAL_BACKUP_DIR}/facl" rm -rf "${dump_dir}" mkdir -p -m 700 "${dump_dir}" From 9ee784509db08643af81f45e3490992c79472408 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 09:16:00 +0100 Subject: [PATCH 011/105] add whitespace to align log outputs with start/stop --- client/zzz_evobackup | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 5a4804e..44a3833 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -227,7 +227,7 @@ dump_ldap() { slapcat -n 1 -l "${dump_dir}/data.bak" slapcat -l "${dump_dir}/ldap.bak" - log "LOCAL_TASKS - stop dump_ldap" + log "LOCAL_TASKS - stop dump_ldap" } # shellcheck disable=SC2317 dump_mysql_global() { @@ -249,7 +249,7 @@ dump_mysql_global() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_mysql_per_base() { @@ -273,7 +273,7 @@ dump_mysql_per_base() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" done } # shellcheck disable=SC2317 @@ -297,7 +297,7 @@ dump_mysql_meta() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" ## Dump all variables error_file="${dump_dir}/variables.err" @@ -314,7 +314,7 @@ dump_mysql_meta() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" ## Schema only (no data) for each databases databases=$(mysql_list_databases 3306) @@ -333,7 +333,7 @@ dump_mysql_meta() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" done } # shellcheck disable=SC2317 @@ -358,7 +358,7 @@ dump_mysql_tabs() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_dir}" + log "LOCAL_TASKS - stop ${dump_dir}" done } # shellcheck disable=SC2317 @@ -383,7 +383,7 @@ dump_mysql_hotcopy() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_dir}" + log "LOCAL_TASKS - stop ${dump_dir}" done } # shellcheck disable=SC2317 @@ -411,7 +411,7 @@ dump_mysql_instances() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" done } # shellcheck disable=SC2317 @@ -435,7 +435,7 @@ dump_postgresql_global() { rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" ## example with pg_dumpall and without compression ## WARNING: you need space in ~postgres @@ -445,7 +445,7 @@ dump_postgresql_global() { # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" # mv ~postgres/pg.dump.bak "${dump_file}" # - # log "LOCAL_TASKS - stop ${dump_file}" + # log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_postgresql_per_base() { @@ -471,7 +471,7 @@ dump_postgresql_per_base() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" done ) } @@ -499,7 +499,7 @@ dump_postgresql_filtered() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_redis() { @@ -522,7 +522,7 @@ dump_redis() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_dir}" + log "LOCAL_TASKS - stop ${dump_dir}" fi done } @@ -552,7 +552,7 @@ dump_mongodb() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_dir}" + log "LOCAL_TASKS - stop ${dump_dir}" } # shellcheck disable=SC2317 dump_megacli_config() { @@ -574,7 +574,7 @@ dump_megacli_config() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_traceroute() { @@ -592,7 +592,7 @@ dump_traceroute() { ${mtr_bin} -r "${addr}" > "${dump_file}" - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" done fi @@ -604,7 +604,7 @@ dump_traceroute() { ${traceroute_bin} -n "${addr}" > "${dump_file}" 2>&1 - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" done fi } @@ -640,7 +640,7 @@ dump_server_state() { fi fi fi - log "LOCAL_TASKS - stop ${dump_dir}" + log "LOCAL_TASKS - stop ${dump_dir}" } # shellcheck disable=SC2317 dump_rabbitmq() { @@ -662,7 +662,7 @@ dump_rabbitmq() { else rm -f "${error_file}" fi - log "LOCAL_TASKS - stop ${dump_file}" + log "LOCAL_TASKS - stop ${dump_file}" } # shellcheck disable=SC2317 dump_facl() { @@ -677,7 +677,7 @@ dump_facl() { getfacl -R /usr > "${dump_dir}/usr.txt" getfacl -R /var > "${dump_dir}/var.txt" - log "LOCAL_TASKS - stop ${dump_dir}" + log "LOCAL_TASKS - stop ${dump_dir}" } # shellcheck disable=SC2317 dump_elasticsearch_snapshot() { @@ -707,7 +707,7 @@ dump_elasticsearch_snapshot() { # date=$(/bin/date +%F) # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot_${date}?wait_for_completion=true" >> "${LOGFILE}" - log "LOCAL_TASKS - stop dump_elasticsearch_snapshot" + log "LOCAL_TASKS - stop dump_elasticsearch_snapshot" } build_rsync_main_cmd() { From c3f65a1722b0fb9b2c5bfd2e0b0128902b332d60 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 09:19:47 +0100 Subject: [PATCH 012/105] extract variables --- client/zzz_evobackup | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 44a3833..c602571 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -196,9 +196,11 @@ local_tasks() { ## Dump file access control lists # dump_facl - # Remove previous error files - for error_file in $(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -print); do - if [ $(stat -c "%s" "${error_file}") -gt 0 ]; then + # Output error files content, if any + error_files=$(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err') + for error_file in ${error_files}; do + error_file_size=$(stat -c "%s" "${error_file}") + if [ ${error_file_size} -gt 0 ]; then printf "### Content of %s ###\n" "${error_file}" >&2 cat "${error_file}" >&2 printf "########################################\n" >&2 From 17c2868fee85d925c00ff6468b0e7ac4753b67d1 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 09:20:41 +0100 Subject: [PATCH 013/105] shellcheck fixes --- client/zzz_evobackup | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index c602571..6b1e60b 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -200,6 +200,7 @@ local_tasks() { error_files=$(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err') for error_file in ${error_files}; do error_file_size=$(stat -c "%s" "${error_file}") + # shellcheck disable=SC2086 if [ ${error_file_size} -gt 0 ]; then printf "### Content of %s ###\n" "${error_file}" >&2 cat "${error_file}" >&2 @@ -752,7 +753,7 @@ build_rsync_main_cmd() { cmd="${cmd} --rsh='ssh -p ${SSH_PORT} -o \"ConnectTimeout ${SSH_CONNECT_TIMEOUT}\"'" # Rsync excludes - while read line ; do + while read -r line ; do cmd="${cmd} --exclude ${line}" done < "${excludes_file}" From a957498b6fb1b785617e638c2e403557ede9a30c Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 09:40:26 +0100 Subject: [PATCH 014/105] push rsync log file with the canary file --- client/zzz_evobackup | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 6b1e60b..5cba0d3 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -769,13 +769,14 @@ build_rsync_main_cmd() { # output final command echo "${cmd}" } -build_rsync_canary_cmd() { +build_rsync_report_cmd() { # Rsync command cmd="$(command -v rsync)" # Rsync options cmd="${cmd} --rsh='ssh -p ${SSH_PORT} -o \"ConnectTimeout ${SSH_CONNECT_TIMEOUT}\"'" # Rsync local source cmd="${cmd} ${CANARY_FILE}" + cmd="${cmd} ${RSYNC_LOGFILE}" # Rsync remote destination cmd="${cmd} root@${SSH_SERVER}:/var/backup/" @@ -896,13 +897,13 @@ sync_tasks() { rc=${E_SYNCFAILED} else # Build the canary Rsync command - rsync_canary_cmd=$(build_rsync_canary_cmd) + rsync_report_cmd=$(build_rsync_report_cmd) # … log it - log "SYNC_TASKS - Rsync canary command : ${rsync_canary_cmd}" + log "SYNC_TASKS - Rsync report command : ${rsync_report_cmd}" # … execute it - eval "${rsync_canary_cmd}" + eval "${rsync_report_cmd}" fi log "STOP SYNC_TASKS - server=${server}" From 8d4105cf313a738c3264f5dd6e2cca2d6594b02b Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 11:34:42 +0100 Subject: [PATCH 015/105] sync only the Rsync stats alongside the canary file --- client/zzz_evobackup | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 5cba0d3..fcd2132 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -61,6 +61,7 @@ LOGFILE="/var/log/evobackup.log" # Full Rsync log file, reset each time RSYNC_LOGFILE="/var/log/${PROGNAME}.rsync.log" +RSYNC_STATSFILE="/var/log/${PROGNAME}.rsync-stats.log" HOSTNAME=$(hostname) @@ -776,7 +777,7 @@ build_rsync_report_cmd() { cmd="${cmd} --rsh='ssh -p ${SSH_PORT} -o \"ConnectTimeout ${SSH_CONNECT_TIMEOUT}\"'" # Rsync local source cmd="${cmd} ${CANARY_FILE}" - cmd="${cmd} ${RSYNC_LOGFILE}" + cmd="${cmd} ${RSYNC_STATSFILE}" # Rsync remote destination cmd="${cmd} root@${SSH_SERVER}:/var/backup/" @@ -891,6 +892,8 @@ sync_tasks() { # Copy last lines of rsync log to the main log tail -n 30 "${RSYNC_LOGFILE}" >> "${LOGFILE}" + # Copy Rsync stats to special file + tail -n 30 "${RSYNC_LOGFILE}" | grep --invert-match --extended-regexp " [\<\>ch\.\*]\S{10} " > "${RSYNC_STATSFILE}" if [ ${rsync_main_rc} -ne 0 ]; then error "rsync returned an error ${rsync_main_rc}, check ${LOGFILE}" From f5660b1e467e7cf1188288366cb4cc32943c21b1 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 12:34:17 +0100 Subject: [PATCH 016/105] doc --- client/zzz_evobackup | 1 + 1 file changed, 1 insertion(+) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index fcd2132..ce37400 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -61,6 +61,7 @@ LOGFILE="/var/log/evobackup.log" # Full Rsync log file, reset each time RSYNC_LOGFILE="/var/log/${PROGNAME}.rsync.log" +# Rsync stats only, reset each time RSYNC_STATSFILE="/var/log/${PROGNAME}.rsync-stats.log" HOSTNAME=$(hostname) From 82df2b38e9a1e35188e61bafa8a6efac999b96c1 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 14:19:48 +0100 Subject: [PATCH 017/105] move variables around to simplify common usage --- client/zzz_evobackup | 109 +++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 50 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index ce37400..4e8407e 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -14,8 +14,9 @@ # # Licence: AGPLv3 -###################################################################### -# To configure the script: +VERSION="22.12" + +##### CONFIGURATION ################################################### # # 1. Set the following MAIL and SERVERS variables. # 2. Customize the RSYNC_INCLUDES and RSYNC_EXCLUDES variables. @@ -24,57 +25,25 @@ # Some local tasks are configurable. # If you enable them, have a look at their implementation. # +# Some additional configuration variable can be customized +# at the end of the script, before invoking the main() function. +# ####################################################################### - -##### Configuration ################################################### - -VERSION="22.12" - # email adress for notifications MAIL=jdoe@example.com # list of hosts (hostname or IP) and SSH port for Rsync SERVERS="node0.backup.example.com:2XXX node1.backup.example.com:2XXX" -# explicit PATH -PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin - -# Should we fallback on other servers when the first one is unreachable? -SERVERS_FALLBACK=${SERVERS_FALLBACK:-1} - -# timeout (in seconds) for SSH connections -SSH_CONNECT_TIMEOUT=${SSH_CONNECT_TIMEOUT:-90} - # We use /home/backup : feel free to use your own dir LOCAL_BACKUP_DIR="/home/backup" -# You can set "linux" or "bsd" manually or let it choose automatically -SYSTEM=$(uname | tr '[:upper:]' '[:lower:]') - -# Store pid in a file named after this program's name -PROGNAME=$(basename "$0") -PIDFILE="/var/run/${PROGNAME}.pid" - -# Customize the log path if you have multiple scripts and with separate logs -LOGFILE="/var/log/evobackup.log" - -# Full Rsync log file, reset each time -RSYNC_LOGFILE="/var/log/${PROGNAME}.rsync.log" -# Rsync stats only, reset each time -RSYNC_STATSFILE="/var/log/${PROGNAME}.rsync-stats.log" - -HOSTNAME=$(hostname) - -DATE_FORMAT="%Y-%m-%d %H:%M:%S" - # Enable/disable local tasks (default: enabled) : "${LOCAL_TASKS:=1}" # Enable/disable sync tasks (default: enabled) : "${SYNC_TASKS:=1}" -CANARY_FILE="/zzz_evobackup_canary" - # Source paths can be customized # Empty lines, and lines containing # or ; are ignored RSYNC_INCLUDES=" @@ -139,13 +108,19 @@ lxc/*/rootfs/var/tmp ##### FUNCTIONS ####################################################### +# Execute all local tasks: database dumps, system state dump… local_tasks() { log "START LOCAL_TASKS" # Remove previous error files find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -delete - # You can comment or uncomment sections below to customize the backup + ################################################################### + # You can enable/disable local tasks + # by (un)commenting calls to "dump_XXX" functions. + # + # You can also add your own functions and call them from here. + ################################################################### ## OpenLDAP # dump_ldap @@ -198,21 +173,26 @@ local_tasks() { ## Dump file access control lists # dump_facl - # Output error files content, if any - error_files=$(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err') - for error_file in ${error_files}; do - error_file_size=$(stat -c "%s" "${error_file}") - # shellcheck disable=SC2086 - if [ ${error_file_size} -gt 0 ]; then - printf "### Content of %s ###\n" "${error_file}" >&2 - cat "${error_file}" >&2 - printf "########################################\n" >&2 - fi - done + ################################################################### + + print_error_files_content log "STOP LOCAL_TASKS" } +# Output error files content, if any +print_error_files_content() { + error_files=$(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err') + for error_file in ${error_files}; do + error_file_size=$(stat -c "%s" "${error_file}") + + # shellcheck disable=SC2086 + if [ ${error_file_size} -gt 0 ]; then + printf "### cat %s ###\n" "${error_file}" >&2 + cat "${error_file}" >&2 + fi + done +} # shellcheck disable=SC2317 mysql_list_databases() { port=${1:-"3306"} @@ -1008,11 +988,40 @@ set -o pipefail # Default return-code (0 == succes) rc=0 -### Possible error codes +# Possible error codes E_NOSRVAVAIL=21 # No server is available E_SYNCFAILED=20 # Faild sync task E_DUMPFAILED=10 # Faild dump task +# explicit PATH +PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin + +# You can set "linux" or "bsd" manually or let it choose automatically +SYSTEM=$(uname | tr '[:upper:]' '[:lower:]') + +# Hostname reported in the mail subject +HOSTNAME=$(hostname) + +# Store pid in a file named after this program's name +PROGNAME=$(basename "$0") +PIDFILE="/var/run/${PROGNAME}.pid" + +# Customize the log path if you want multiple scripts to have separate log files +LOGFILE="/var/log/evobackup.log" +# Rsync complete log file for the current run +RSYNC_LOGFILE="/var/log/${PROGNAME}.rsync.log" +# Rsync stats for the current run +RSYNC_STATSFILE="/var/log/${PROGNAME}.rsync-stats.log" +# Canary file to update before executing tasks +CANARY_FILE="/zzz_evobackup_canary" + +DATE_FORMAT="%Y-%m-%d %H:%M:%S" + +# Should we fallback on other servers when the first one is unreachable? +SERVERS_FALLBACK=${SERVERS_FALLBACK:-1} +# timeout (in seconds) for SSH connections +SSH_CONNECT_TIMEOUT=${SSH_CONNECT_TIMEOUT:-90} + # execute main function main From f6c8d966d76252d941a070fff131093a01518712 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 14:20:12 +0100 Subject: [PATCH 018/105] shellcheck --- client/zzz_evobackup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 4e8407e..79a1ecf 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -741,7 +741,7 @@ build_rsync_main_cmd() { # Rsync local sources cmd="${cmd} ${default_includes}" - while read line ; do + while read -r line ; do cmd="${cmd} ${line}" done < "${includes_file}" From 58f41963a79dd531628e7a23ffe326842c947a1d Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 14:51:10 +0100 Subject: [PATCH 019/105] store temp_files in TMPDIR instead of current directory --- client/zzz_evobackup | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 79a1ecf..f28faeb 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -706,8 +706,8 @@ build_rsync_main_cmd() { ################################################################### # Create a temp file for excludes and includes - includes_file="$(mktemp "${PROGNAME}.includes.XXXXXX")" - excludes_file="$(mktemp "${PROGNAME}.excludes.XXXXXX")" + includes_file="$(mktemp --tmpdir "${PROGNAME}.includes.XXXXXX")" + excludes_file="$(mktemp --tmpdir "${PROGNAME}.excludes.XXXXXX")" # … and add them to the list of files to delete at exit temp_files="${temp_files} ${includes_file} ${excludes_file}" From d75d75cd4c0419afae31584903d1970a3591b6ca Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 4 Jan 2023 23:32:12 +0100 Subject: [PATCH 020/105] Use an array to build the rsync commands, instead of eval --- client/zzz_evobackup | 141 +++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 80 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index f28faeb..43fccad 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -695,77 +695,6 @@ dump_elasticsearch_snapshot() { log "LOCAL_TASKS - stop dump_elasticsearch_snapshot" } -build_rsync_main_cmd() { - ################################################################### - # /!\ WARNING /!\ WARNING /!\ WARNING /!\ WARNING /!\ WARNING /!\ # - ################################################################### - # DO NOT USE COMMENTS in rsync lines # - # DO NOT ADD WHITESPACES AFTER \ in rsync lines # - # It breaks the command and destroys data # - # You should not modify this, unless you are really REALLY sure # - ################################################################### - - # Create a temp file for excludes and includes - includes_file="$(mktemp --tmpdir "${PROGNAME}.includes.XXXXXX")" - excludes_file="$(mktemp --tmpdir "${PROGNAME}.excludes.XXXXXX")" - # … and add them to the list of files to delete at exit - temp_files="${temp_files} ${includes_file} ${excludes_file}" - - # Store includes/excludes in files - # without blank lines of comments (# or ;) - echo "${RSYNC_INCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${includes_file}" - echo "${RSYNC_EXCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${excludes_file}" - - # Rsync command - cmd="$(command -v rsync)" - - # Rsync main options - cmd="${cmd} --archive" - cmd="${cmd} --itemize-changes" - cmd="${cmd} --quiet" - cmd="${cmd} --stats" - cmd="${cmd} --human-readable" - cmd="${cmd} --relative" - cmd="${cmd} --partial" - cmd="${cmd} --delete" - cmd="${cmd} --delete-excluded" - cmd="${cmd} --force" - cmd="${cmd} --ignore-errors" - cmd="${cmd} --log-file=${RSYNC_LOGFILE}" - cmd="${cmd} --rsh='ssh -p ${SSH_PORT} -o \"ConnectTimeout ${SSH_CONNECT_TIMEOUT}\"'" - - # Rsync excludes - while read -r line ; do - cmd="${cmd} --exclude ${line}" - done < "${excludes_file}" - - # Rsync local sources - cmd="${cmd} ${default_includes}" - while read -r line ; do - cmd="${cmd} ${line}" - done < "${includes_file}" - - # Rsync remote destination - cmd="${cmd} root@${SSH_SERVER}:/var/backup/" - - # output final command - echo "${cmd}" -} -build_rsync_report_cmd() { - # Rsync command - cmd="$(command -v rsync)" - # Rsync options - cmd="${cmd} --rsh='ssh -p ${SSH_PORT} -o \"ConnectTimeout ${SSH_CONNECT_TIMEOUT}\"'" - # Rsync local source - cmd="${cmd} ${CANARY_FILE}" - cmd="${cmd} ${RSYNC_STATSFILE}" - # Rsync remote destination - cmd="${cmd} root@${SSH_SERVER}:/var/backup/" - - # output final command - echo "${cmd}" -} - # Call test_server with "HOST:PORT" string # It will return with 0 if the server is reachable. # It will return with 1 and a message on stderr if not. @@ -860,14 +789,55 @@ sync_tasks() { printf "" > "${RSYNC_LOGFILE}" fi + # Create a temp file for excludes and includes + includes_file="$(mktemp --tmpdir "${PROGNAME}.includes.XXXXXX")" + excludes_file="$(mktemp --tmpdir "${PROGNAME}.excludes.XXXXXX")" + # … and add them to the list of files to delete at exit + temp_files="${temp_files} ${includes_file} ${excludes_file}" + + # Store includes/excludes in files + # without blank lines of comments (# or ;) + echo "${RSYNC_INCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${includes_file}" + echo "${RSYNC_EXCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${excludes_file}" + + rsync_bin=$(command -v rsync) # Build the final Rsync command - rsync_main_cmd=$(build_rsync_main_cmd) + + # Rsync main options + rsync_main_args=() + rsync_main_args+=(--archive) + rsync_main_args+=(--itemize-changes) + rsync_main_args+=(--quiet) + rsync_main_args+=(--stats) + rsync_main_args+=(--human-readable) + rsync_main_args+=(--relative) + rsync_main_args+=(--partial) + rsync_main_args+=(--delete) + rsync_main_args+=(--delete-excluded) + rsync_main_args+=(--force) + rsync_main_args+=(--ignore-errors) + rsync_main_args+=(--log-file "${RSYNC_LOGFILE}") + rsync_main_args+=(--rsh "ssh -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") + + # Rsync excludes + while read -r line ; do + rsync_main_args+=(--exclude "${line}") + done < "${excludes_file}" + + # Rsync local sources + rsync_main_args+=(${default_includes}) + while read -r line ; do + rsync_main_args+=("${line}") + done < "${includes_file}" + + # Rsync remote destination + rsync_main_args+=("root@${SSH_SERVER}:/var/backup/") # … log it - log "SYNC_TASKS - Rsync main command : ${rsync_main_cmd}" + log "SYNC_TASKS - Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" # … execute it - eval "${rsync_main_cmd}" + ${rsync_bin} "${rsync_main_args[@]}" rsync_main_rc=$? @@ -881,13 +851,20 @@ sync_tasks() { rc=${E_SYNCFAILED} else # Build the canary Rsync command - rsync_report_cmd=$(build_rsync_report_cmd) + rsync_report_args=() + # Rsync options + rsync_report_args+=(--rsh "ssh -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") + # Rsync local source + rsync_report_args+=("${CANARY_FILE}") + rsync_report_args+=("${RSYNC_STATSFILE}") + # Rsync remote destination + rsync_report_args+=("root@${SSH_SERVER}:/var/backup/") # … log it - log "SYNC_TASKS - Rsync report command : ${rsync_report_cmd}" + log "SYNC_TASKS - Rsync report command : ${rsync_bin} ${rsync_report_args[*]}" # … execute it - eval "${rsync_report_cmd}" + ${rsync_bin} "${rsync_report_args[@]}" fi log "STOP SYNC_TASKS - server=${server}" @@ -909,7 +886,12 @@ error() { "$(/bin/date +"${DATE_FORMAT}")" "${PROGNAME}" "${pid}" "${msg}" \ >&2 } - +# Remove all temporary file created during the execution +# shellcheck disable=SC2317 +clean_temp_files() { + # shellcheck disable=SC2086 + rm -f ${temp_files} +} main() { START_EPOCH=$(/bin/date +%s) log "START GLOBAL - VERSION=${VERSION} LOCAL_TASKS=${LOCAL_TASKS} SYNC_TASKS=${SYNC_TASKS}" @@ -945,8 +927,7 @@ main() { # Any file added to the list will also be deleted at exit temp_files="${PIDFILE}" - # shellcheck disable=SC2064 - trap "rm -f ${temp_files}" EXIT + trap clean_temp_files EXIT # Update canary to keep track of each run update-evobackup-canary --who "${PROGNAME}" --file "${CANARY_FILE}" From 053c339e8f3b30a818af6dbf651a4f6fe62bc863 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 5 Jan 2023 13:45:17 +0100 Subject: [PATCH 021/105] better comments --- client/zzz_evobackup | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 43fccad..af895ac 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -127,26 +127,26 @@ local_tasks() { ## MySQL - ### example with global and compressed mysqldump - # dump_mysql_global - ### example with compressed SQL dump (with data) for each databases - # dump_mysql_per_base ### meta-data (grants, variables, schema…) # dump_mysql_meta - ### example with two dumps for each table (.sql/.txt) for all databases - # dump_mysql_tabs - ### example with mysqlhotcopy - # dump_mysql_hotcopy - ### example for multiples MySQL instances + ### global and compressed mysqldump + # dump_mysql_global + ### compressed SQL dump (with data) for each databases separately + # dump_mysql_per_base + ### global dump for multiple MySQL instances # dump_mysql_instances + ### two dumps for each table (.sql/.txt) for all databases + # dump_mysql_tabs + ### mysqlhotcopy + # dump_mysql_hotcopy ## PostgreSQL - ### example with global dump + ### global dump # dump_postgresql_global - ### example with filtered tables ("only" or "except") + ### dump with filtered tables ("only" or "except") # dump_postgresql_filtered - ### example with compressed PostgreSQL dump for each databases + ### compressed dump for each databases separately # dump_postgresql_per_base ## MongoDB @@ -197,7 +197,8 @@ print_error_files_content() { mysql_list_databases() { port=${1:-"3306"} - mysql --defaults-extra-file=/etc/mysql/debian.cnf -P "${port}" -e 'show databases' -s --skip-column-names | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" + mysql --defaults-extra-file=/etc/mysql/debian.cnf -P "${port}" -e 'show databases' -s --skip-column-names \ + | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" } # shellcheck disable=SC2317 dump_ldap() { @@ -224,7 +225,8 @@ dump_mysql_global() { dump_file="${dump_dir}/mysql.bak.gz" log "LOCAL_TASKS - start ${dump_file}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" | gzip --best > "${dump_file}" + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" \ + | gzip --best > "${dump_file}" last_rc=$? # shellcheck disable=SC2086 @@ -248,7 +250,8 @@ dump_mysql_per_base() { dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob "${database}" 2> "${error_file}" \ + | gzip --best > "${dump_file}" last_rc=$? # shellcheck disable=SC2086 From c3c98b64f2af26fbc5aeb5fd8e80f86133b59531 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 6 Jan 2023 14:33:20 +0100 Subject: [PATCH 022/105] Use bash array for list of paths to include --- client/zzz_evobackup | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index af895ac..2108077 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -780,9 +780,12 @@ sync_tasks() { # default paths, depending on system if [ "${SYSTEM}" = "linux" ]; then - default_includes="/bin /boot /lib /opt /sbin /usr" + rsync_default_includes=(/bin /boot /lib /opt /sbin /usr) else - default_includes="/bsd /bin /sbin /usr" + rsync_default_includes=(/bsd /bin /sbin /usr) + fi + if [ -f "${CANARY_FILE}" ]; then + rsync_default_includes+=("${CANARY_FILE}") fi # reset Rsync log file @@ -793,13 +796,15 @@ sync_tasks() { fi # Create a temp file for excludes and includes - includes_file="$(mktemp --tmpdir "${PROGNAME}.includes.XXXXXX")" - excludes_file="$(mktemp --tmpdir "${PROGNAME}.excludes.XXXXXX")" + rsync_includes_file="$(mktemp --tmpdir "${PROGNAME}.rsync-includes.XXXXXX")" + rsync_excludes_file="$(mktemp --tmpdir "${PROGNAME}.rsync-excludes.XXXXXX")" # … and add them to the list of files to delete at exit - temp_files="${temp_files} ${includes_file} ${excludes_file}" + temp_files="${temp_files} ${rsync_includes_file} ${rsync_excludes_file}" # Store includes/excludes in files # without blank lines of comments (# or ;) + echo "${RSYNC_INCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${rsync_includes_file}" + echo "${RSYNC_EXCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${rsync_excludes_file}" echo "${RSYNC_INCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${includes_file}" echo "${RSYNC_EXCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${excludes_file}" @@ -825,13 +830,16 @@ sync_tasks() { # Rsync excludes while read -r line ; do rsync_main_args+=(--exclude "${line}") - done < "${excludes_file}" + done < "${rsync_excludes_file}" # Rsync local sources - rsync_main_args+=(${default_includes}) + # Start with default includes + # shellcheck disable=SC2206 + rsync_main_args+=(${rsync_default_includes[@]}) + # … and add custom includes while read -r line ; do rsync_main_args+=("${line}") - done < "${includes_file}" + done < "${rsync_includes_file}" # Rsync remote destination rsync_main_args+=("root@${SSH_SERVER}:/var/backup/") @@ -849,19 +857,19 @@ sync_tasks() { # Copy Rsync stats to special file tail -n 30 "${RSYNC_LOGFILE}" | grep --invert-match --extended-regexp " [\<\>ch\.\*]\S{10} " > "${RSYNC_STATSFILE}" - if [ ${rsync_main_rc} -ne 0 ]; then + # We ignore rc=24 (vanished files) + if [ ${rsync_main_rc} -ne 0 ] && [ ${rsync_main_rc} -ne 24 ]; then error "rsync returned an error ${rsync_main_rc}, check ${LOGFILE}" rc=${E_SYNCFAILED} else - # Build the canary Rsync command + # Build the report Rsync command rsync_report_args=() # Rsync options rsync_report_args+=(--rsh "ssh -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") # Rsync local source - rsync_report_args+=("${CANARY_FILE}") rsync_report_args+=("${RSYNC_STATSFILE}") # Rsync remote destination - rsync_report_args+=("root@${SSH_SERVER}:/var/backup/") + rsync_report_args+=("root@${SSH_SERVER}:/var/log/") # … log it log "SYNC_TASKS - Rsync report command : ${rsync_bin} ${rsync_report_args[*]}" From c2d08ed80eaac50d9297c384441dee4fdc4e1658 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 6 Jan 2023 14:34:51 +0100 Subject: [PATCH 023/105] create and sync mtree files --- client/zzz_evobackup | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 2108077..daf9266 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -805,8 +805,32 @@ sync_tasks() { # without blank lines of comments (# or ;) echo "${RSYNC_INCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${rsync_includes_file}" echo "${RSYNC_EXCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${rsync_excludes_file}" - echo "${RSYNC_INCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${includes_file}" - echo "${RSYNC_EXCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${excludes_file}" + + # Dump filesystem stats with mtree + log "SYNC_TASKS - start mtree" + + mtree_files=() + + # Loop over Rsync includes + while read -r line ; do + # … but exclude for mtree what will be excluded by Rsync + mtree_excludes_file="$(mktemp --tmpdir "${PROGNAME}.mtree-excludes.XXXXXX")" + temp_files="${temp_files} ${mtree_excludes_file}" + grep -E "^([^/]|${line})" "${rsync_excludes_file}" | sed -e "s|^${line}|.|" > "${mtree_excludes_file}" + + mtree_file="/var/log/evobackup.$(basename "${line}").mtree" + temp_files="${temp_files} ${mtree_file}" + + mtree -x -c -p "${line}" -X "${mtree_excludes_file}" > "${mtree_file}" + mtree_files+=("${mtree_file}") + done < "${rsync_includes_file}" + + if [ "${#mtree_files[@]}" -le 0 ]; then + error "ERROR: mtree didn't produce any file" + fi + + log "SYNC_TASKS - stop mtree (files: ${mtree_files[*]})" + rsync_bin=$(command -v rsync) # Build the final Rsync command @@ -868,6 +892,9 @@ sync_tasks() { rsync_report_args+=(--rsh "ssh -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") # Rsync local source rsync_report_args+=("${RSYNC_STATSFILE}") + if [ "${#mtree_files[@]}" -gt 0 ]; then + rsync_report_args+=("${mtree_files[@]}") + fi # Rsync remote destination rsync_report_args+=("root@${SSH_SERVER}:/var/log/") From aeebb815c88aa6fbb313ff662f4c100fa593b633 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 6 Jan 2023 14:45:02 +0100 Subject: [PATCH 024/105] Use bash array for temp_files --- client/zzz_evobackup | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index daf9266..349a4ea 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -799,7 +799,8 @@ sync_tasks() { rsync_includes_file="$(mktemp --tmpdir "${PROGNAME}.rsync-includes.XXXXXX")" rsync_excludes_file="$(mktemp --tmpdir "${PROGNAME}.rsync-excludes.XXXXXX")" # … and add them to the list of files to delete at exit - temp_files="${temp_files} ${rsync_includes_file} ${rsync_excludes_file}" + temp_files+=("${rsync_includes_file}") + temp_files+=("${rsync_excludes_file}") # Store includes/excludes in files # without blank lines of comments (# or ;) @@ -815,11 +816,11 @@ sync_tasks() { while read -r line ; do # … but exclude for mtree what will be excluded by Rsync mtree_excludes_file="$(mktemp --tmpdir "${PROGNAME}.mtree-excludes.XXXXXX")" - temp_files="${temp_files} ${mtree_excludes_file}" + temp_files+=("${mtree_excludes_file}") grep -E "^([^/]|${line})" "${rsync_excludes_file}" | sed -e "s|^${line}|.|" > "${mtree_excludes_file}" mtree_file="/var/log/evobackup.$(basename "${line}").mtree" - temp_files="${temp_files} ${mtree_file}" + temp_files+=("${mtree_file}") mtree -x -c -p "${line}" -X "${mtree_excludes_file}" > "${mtree_file}" mtree_files+=("${mtree_file}") @@ -928,7 +929,7 @@ error() { # shellcheck disable=SC2317 clean_temp_files() { # shellcheck disable=SC2086 - rm -f ${temp_files} + rm -f "${temp_files[@]}" } main() { START_EPOCH=$(/bin/date +%s) @@ -963,7 +964,7 @@ main() { # Initialize a list of files to delete at exit # Any file added to the list will also be deleted at exit - temp_files="${PIDFILE}" + temp_files=("${PIDFILE}") trap clean_temp_files EXIT From 4ff1bc59763bea361a51e43985056bdcacd65320 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 6 Jan 2023 16:59:12 +0100 Subject: [PATCH 025/105] better comments --- client/zzz_evobackup | 90 +++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 349a4ea..ab591fa 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -122,56 +122,76 @@ local_tasks() { # You can also add your own functions and call them from here. ################################################################### - ## OpenLDAP - # dump_ldap + ########## OpenLDAP ############### - ## MySQL + ### dump_ldap - ### meta-data (grants, variables, schema…) - # dump_mysql_meta - ### global and compressed mysqldump - # dump_mysql_global - ### compressed SQL dump (with data) for each databases separately - # dump_mysql_per_base - ### global dump for multiple MySQL instances - # dump_mysql_instances - ### two dumps for each table (.sql/.txt) for all databases - # dump_mysql_tabs - ### mysqlhotcopy + ########## MySQL ################## + + # Dump all grants (permissions), config variables and schema of databases + ### dump_mysql_meta + + # Dump all databases in a single compressed file + ### dump_mysql_global + + # Dump each database separately, in a compressed file + ### dump_mysql_per_base + + # Dump multiples instances, each in a single compressed file + ### dump_mysql_instances + + # Dump each table in schema/data files, for all databases + ### dump_mysql_tabs + + # Run mysqlhotcopy for a specific database (must be configured) # dump_mysql_hotcopy - ## PostgreSQL + ########## PostgreSQL ############# - ### global dump - # dump_postgresql_global - ### dump with filtered tables ("only" or "except") - # dump_postgresql_filtered - ### compressed dump for each databases separately - # dump_postgresql_per_base + # Dump all databases in a single file (compressed or not) + ### dump_postgresql_global - ## MongoDB - # dump_mongodb + # Dump a specific databse with only some tables, or all but some tables (must be configured) + ### dump_postgresql_filtered - ## Redis - # dump_redis + # Dump each database separately, in a compressed file + ### dump_postgresql_per_base - ## ElasticSearch - # dump_elasticsearch_snapshot + ########## MongoDB ################ + + ### dump_mongodb - ## RabbitMQ config - # dump_rabbitmq + ########## Redis ################## - ## MegaCli config - # dump_megacli_config + # Copy data file for all instances + ### dump_redis - ## Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) + ########## ElasticSearch ########## + + # Trigger snapshots (must be configured) + ### dump_elasticsearch_snapshot + + ########## RabbitMQ ############### + + ### dump_rabbitmq + + ########## MegaCli ################ + + # Copy RAID config + ### dump_megacli_config + + ########## Network ################ + + # Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) dump_traceroute - ## Dump various information about server state + ########## Server state ########### + + # Run dump-server-state to extract system information dump_server_state - ## Dump file access control lists - # dump_facl + # Dump file access control lists + ### dump_facl ################################################################### From 7199ffc64f5a4726b65ead512a5459d8faf791ba Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 9 Jan 2023 11:45:39 +0100 Subject: [PATCH 026/105] Add PING_BEFORE_SSH (enabled by default) --- client/zzz_evobackup | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index ab591fa..c9cb1f2 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -727,6 +727,10 @@ test_server() { host=$(echo "${item}" | cut -d':' -f1) port=$(echo "${item}" | cut -d':' -f2) + if [ "${PING_BEFORE_SSH}" = "1" ]; then + ping -w 2 "${host}" > /dev/null + fi + # Test if the server is accepting connections ssh -q -o "ConnectTimeout ${SSH_CONNECT_TIMEOUT}" "${host}" -p "${port}" -t "exit" # shellcheck disable=SC2181 @@ -1062,6 +1066,11 @@ SERVERS_FALLBACK=${SERVERS_FALLBACK:-1} # timeout (in seconds) for SSH connections SSH_CONNECT_TIMEOUT=${SSH_CONNECT_TIMEOUT:-90} +# Send a couple of ping packets before SSH. +# It comes from a weird bug in our network; +# the connection could not be established without a ping in the preceding 5 minutes. +PING_BEFORE_SSH=${PING_BEFORE_SSH:-1} + # execute main function main From 7f4cb788264073856d314fcb1b2017119d56dce3 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 11:17:20 +0100 Subject: [PATCH 027/105] shellcheck --- client/zzz_evobackup | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index c9cb1f2..1d49d8a 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -225,6 +225,7 @@ dump_ldap() { ## OpenLDAP : example with slapcat dump_dir="${LOCAL_BACKUP_DIR}/ldap" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" log "LOCAL_TASKS - start dump_ldap to ${dump_dir}" @@ -239,6 +240,7 @@ dump_ldap() { dump_mysql_global() { dump_dir="${LOCAL_BACKUP_DIR}/mysql-global" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" error_file="${dump_dir}/mysql.bak.err" @@ -262,6 +264,7 @@ dump_mysql_global() { dump_mysql_per_base() { dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" databases=$(mysql_list_databases 3306) @@ -288,6 +291,7 @@ dump_mysql_per_base() { dump_mysql_meta() { dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" ## Dump all grants (requires 'percona-toolkit' package) @@ -350,6 +354,7 @@ dump_mysql_tabs() { for database in ${databases}; do dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs/${database}" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" chown -RL mysql "${dump_dir}" @@ -376,6 +381,7 @@ dump_mysql_hotcopy() { for database in ${databases}; do dump_dir="${LOCAL_BACKUP_DIR}/mysql-hotcopy/${database}" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" error_file="${dump_dir}.err" @@ -398,6 +404,7 @@ dump_mysql_hotcopy() { dump_mysql_instances() { dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" mysqladminpasswd=$(grep -m1 'password = .*' /root/.my.cnf | cut -d " " -f 3) @@ -426,6 +433,7 @@ dump_mysql_instances() { dump_postgresql_global() { dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" ## example with pg_dumpall and with compression @@ -459,18 +467,20 @@ dump_postgresql_global() { dump_postgresql_per_base() { dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" ( - cd /var/lib/postgresql - databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") - for database in ${databases} ; do + # shellcheck disable=SC2164 + cd /var/lib/postgresql + databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") + for database in ${databases} ; do error_file="${dump_dir}/${database}.err" dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" (sudo -u postgres /usr/bin/pg_dump --create -s -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" - + last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then @@ -480,13 +490,14 @@ dump_postgresql_per_base() { rm -f "${error_file}" fi log "LOCAL_TASKS - stop ${dump_file}" - done + done ) } # shellcheck disable=SC2317 dump_postgresql_filtered() { dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" error_file="${dump_dir}/pg-backup.err" @@ -511,12 +522,14 @@ dump_postgresql_filtered() { } # shellcheck disable=SC2317 dump_redis() { - for instance in $(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*'); do + instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*') + for instance in ${instances}; do name=$(basename "${instance}") dump_dir="${LOCAL_BACKUP_DIR}/${name}" rm -rf "${dump_dir}" if [ -f "${instance}/dump.rdb" ]; then + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" log "LOCAL_TASKS - start ${dump_dir}" @@ -542,6 +555,7 @@ dump_mongodb() { dump_dir="${LOCAL_BACKUP_DIR}/mongodump" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" error_file="${dump_dir}.err" @@ -566,6 +580,7 @@ dump_mongodb() { dump_megacli_config() { dump_dir="${LOCAL_BACKUP_DIR}/megacli" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" dump_file="${dump_dir}/megacli.cfg" @@ -588,6 +603,7 @@ dump_megacli_config() { dump_traceroute() { dump_dir="${LOCAL_BACKUP_DIR}/traceroute" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" network_targets="8.8.8.8 www.evolix.fr travaux.evolix.net" @@ -621,6 +637,7 @@ dump_server_state() { dump_dir="${LOCAL_BACKUP_DIR}/server-state" rm -rf "${dump_dir}" # Do not create the directory + # shellcheck disable=SC2174 # mkdir -p -m 700 "${dump_dir}" log "LOCAL_TASKS - start ${dump_dir}" @@ -654,6 +671,7 @@ dump_server_state() { dump_rabbitmq() { dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" error_file="${dump_dir}.err" @@ -676,6 +694,7 @@ dump_rabbitmq() { dump_facl() { dump_dir="${LOCAL_BACKUP_DIR}/facl" rm -rf "${dump_dir}" + # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" log "LOCAL_TASKS - start ${dump_dir}" From 22ba5ed823418379b34e7033a58837407fbfc4e9 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 11:26:19 +0100 Subject: [PATCH 028/105] declare bash arrays --- client/zzz_evobackup | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 1d49d8a..22b6b68 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -823,9 +823,9 @@ sync_tasks() { # default paths, depending on system if [ "${SYSTEM}" = "linux" ]; then - rsync_default_includes=(/bin /boot /lib /opt /sbin /usr) + declare -a rsync_default_includes=(/bin /boot /lib /opt /sbin /usr) else - rsync_default_includes=(/bsd /bin /sbin /usr) + declare -a rsync_default_includes=(/bsd /bin /sbin /usr) fi if [ -f "${CANARY_FILE}" ]; then rsync_default_includes+=("${CANARY_FILE}") @@ -853,7 +853,7 @@ sync_tasks() { # Dump filesystem stats with mtree log "SYNC_TASKS - start mtree" - mtree_files=() + declare -a mtree_files=() # Loop over Rsync includes while read -r line ; do @@ -1007,7 +1007,7 @@ main() { # Initialize a list of files to delete at exit # Any file added to the list will also be deleted at exit - temp_files=("${PIDFILE}") + declare -a temp_files=("${PIDFILE}") trap clean_temp_files EXIT From e9cf39ad40042e1c51a058c63c7053813ffa8c5d Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 11:26:41 +0100 Subject: [PATCH 029/105] remove PING_BEFORE_SSH --- client/zzz_evobackup | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 22b6b68..9f1b17a 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -746,10 +746,6 @@ test_server() { host=$(echo "${item}" | cut -d':' -f1) port=$(echo "${item}" | cut -d':' -f2) - if [ "${PING_BEFORE_SSH}" = "1" ]; then - ping -w 2 "${host}" > /dev/null - fi - # Test if the server is accepting connections ssh -q -o "ConnectTimeout ${SSH_CONNECT_TIMEOUT}" "${host}" -p "${port}" -t "exit" # shellcheck disable=SC2181 @@ -1085,11 +1081,6 @@ SERVERS_FALLBACK=${SERVERS_FALLBACK:-1} # timeout (in seconds) for SSH connections SSH_CONNECT_TIMEOUT=${SSH_CONNECT_TIMEOUT:-90} -# Send a couple of ping packets before SSH. -# It comes from a weird bug in our network; -# the connection could not be established without a ping in the preceding 5 minutes. -PING_BEFORE_SSH=${PING_BEFORE_SSH:-1} - # execute main function main From 46c012f5fc92c94f1288c6f46669065114f02abc Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 13:30:57 +0100 Subject: [PATCH 030/105] skip mtree if disabled or missing --- client/zzz_evobackup | 49 +++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 9f1b17a..6bae192 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -43,6 +43,8 @@ LOCAL_BACKUP_DIR="/home/backup" : "${LOCAL_TASKS:=1}" # Enable/disable sync tasks (default: enabled) : "${SYNC_TASKS:=1}" +# Enable/disable mtree (default: enabled) +: "${MTREE_ENABLED:=1}" # Source paths can be customized # Empty lines, and lines containing # or ; are ignored @@ -846,32 +848,41 @@ sync_tasks() { echo "${RSYNC_INCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${rsync_includes_file}" echo "${RSYNC_EXCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${rsync_excludes_file}" - # Dump filesystem stats with mtree - log "SYNC_TASKS - start mtree" + if [ "${MTREE_ENABLED}" = "1" ]; then + mtree_bin=$(command -v mtree) - declare -a mtree_files=() + if [ -n "${mtree_bin}" ]; then + # Dump filesystem stats with mtree + log "SYNC_TASKS - start mtree" - # Loop over Rsync includes - while read -r line ; do - # … but exclude for mtree what will be excluded by Rsync - mtree_excludes_file="$(mktemp --tmpdir "${PROGNAME}.mtree-excludes.XXXXXX")" - temp_files+=("${mtree_excludes_file}") - grep -E "^([^/]|${line})" "${rsync_excludes_file}" | sed -e "s|^${line}|.|" > "${mtree_excludes_file}" + declare -a mtree_files=() - mtree_file="/var/log/evobackup.$(basename "${line}").mtree" - temp_files+=("${mtree_file}") + # Loop over Rsync includes + while read -r line ; do + # … but exclude for mtree what will be excluded by Rsync + mtree_excludes_file="$(mktemp --tmpdir "${PROGNAME}.mtree-excludes.XXXXXX")" + temp_files+=("${mtree_excludes_file}") + grep -E "^([^/]|${line})" "${rsync_excludes_file}" | sed -e "s|^${line}|.|" > "${mtree_excludes_file}" - mtree -x -c -p "${line}" -X "${mtree_excludes_file}" > "${mtree_file}" - mtree_files+=("${mtree_file}") - done < "${rsync_includes_file}" + mtree_file="/var/log/evobackup.$(basename "${line}").mtree" + temp_files+=("${mtree_file}") - if [ "${#mtree_files[@]}" -le 0 ]; then - error "ERROR: mtree didn't produce any file" + ${mtree_bin} -x -c -p "${line}" -X "${mtree_excludes_file}" > "${mtree_file}" + mtree_files+=("${mtree_file}") + done < "${rsync_includes_file}" + + if [ "${#mtree_files[@]}" -le 0 ]; then + error "ERROR: mtree didn't produce any file" + fi + + log "SYNC_TASKS - stop mtree (files: ${mtree_files[*]})" + else + log "SYNC_TASKS - skip mtree (missing)" + fi + else + log "SYNC_TASKS - skip mtree (disabled)" fi - log "SYNC_TASKS - stop mtree (files: ${mtree_files[*]})" - - rsync_bin=$(command -v rsync) # Build the final Rsync command From 9665a4ef001ee9f1f40664efd6163a921604c906 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 16:58:24 +0100 Subject: [PATCH 031/105] commands arguments (long options and whitespaces --- client/zzz_evobackup | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 6bae192..aa8f31c 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -219,7 +219,7 @@ print_error_files_content() { mysql_list_databases() { port=${1:-"3306"} - mysql --defaults-extra-file=/etc/mysql/debian.cnf -P "${port}" -e 'show databases' -s --skip-column-names \ + mysql --defaults-extra-file=/etc/mysql/debian.cnf --port="${port}" --execute="show databases" --silent --skip-column-names \ | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" } # shellcheck disable=SC2317 @@ -275,8 +275,7 @@ dump_mysql_per_base() { dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob "${database}" 2> "${error_file}" \ - | gzip --best > "${dump_file}" + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" last_rc=$? # shellcheck disable=SC2086 @@ -409,7 +408,8 @@ dump_mysql_instances() { # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" - mysqladminpasswd=$(grep -m1 'password = .*' /root/.my.cnf | cut -d " " -f 3) + mysql_user="mysqladmin" + mysql_passwd=$(grep -m1 'password = .*' /root/.my.cnf | cut -d " " -f 3) # customize list of instances instances="" @@ -418,7 +418,7 @@ dump_mysql_instances() { dump_file="${dump_dir}/${instance}.bak.gz" log "LOCAL_TASKS - start ${dump_file}" - mysqldump -P "${instance}" --opt --all-databases --hex-blob -u mysqladmin -p"${mysqladminpasswd}" 2> "${error_file}" | gzip --best > "${dump_file}" + mysqldump --port="${instance}" --opt --all-databases --hex-blob --user="${mysql_user}" --password="${mysql_passwd}" 2> "${error_file}" | gzip --best > "${dump_file}" last_rc=$? # shellcheck disable=SC2086 From 22814bc5d74e75446bfefbbe5c21ba697a16f9ec Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 17:13:06 +0100 Subject: [PATCH 032/105] Ldap dump file name --- client/zzz_evobackup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index aa8f31c..e665eda 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -234,7 +234,7 @@ dump_ldap() { slapcat -n 0 -l "${dump_dir}/config.bak" slapcat -n 1 -l "${dump_dir}/data.bak" - slapcat -l "${dump_dir}/ldap.bak" + slapcat -l "${dump_dir}/all.bak" log "LOCAL_TASKS - stop dump_ldap" } From 27568820bf4d2c2c0e896ca73dfb0cf2bdf8aece Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 17:15:33 +0100 Subject: [PATCH 033/105] revert "declare -a" on array variables --- client/zzz_evobackup | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index e665eda..1156ae8 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -821,9 +821,9 @@ sync_tasks() { # default paths, depending on system if [ "${SYSTEM}" = "linux" ]; then - declare -a rsync_default_includes=(/bin /boot /lib /opt /sbin /usr) + rsync_default_includes=(/bin /boot /lib /opt /sbin /usr) else - declare -a rsync_default_includes=(/bsd /bin /sbin /usr) + rsync_default_includes=(/bsd /bin /sbin /usr) fi if [ -f "${CANARY_FILE}" ]; then rsync_default_includes+=("${CANARY_FILE}") @@ -855,7 +855,7 @@ sync_tasks() { # Dump filesystem stats with mtree log "SYNC_TASKS - start mtree" - declare -a mtree_files=() + mtree_files=() # Loop over Rsync includes while read -r line ; do @@ -1014,7 +1014,7 @@ main() { # Initialize a list of files to delete at exit # Any file added to the list will also be deleted at exit - declare -a temp_files=("${PIDFILE}") + temp_files=("${PIDFILE}") trap clean_temp_files EXIT From 518fa9d1e7605f38fee34e802d8f8f92fc7c60d2 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 17:17:00 +0100 Subject: [PATCH 034/105] Store errors in dedicated and persistent directories --- client/zzz_evobackup | 138 +++++++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 50 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 1156ae8..18d04be 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -114,8 +114,8 @@ lxc/*/rootfs/var/tmp local_tasks() { log "START LOCAL_TASKS" - # Remove previous error files - find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err' -delete + # Remove old log directories + find "${LOCAL_BACKUP_DIR}/" -type d -name "${PROGNAME}.errors-*" -ctime +30 -delete ################################################################### # You can enable/disable local tasks @@ -204,16 +204,28 @@ local_tasks() { # Output error files content, if any print_error_files_content() { - error_files=$(find "${LOCAL_BACKUP_DIR}/" -type f -name '*.err') + # Search for error files + error_files=$(find "${ERRORS_DIR}" -type f) for error_file in ${error_files}; do error_file_size=$(stat -c "%s" "${error_file}") # shellcheck disable=SC2086 if [ ${error_file_size} -gt 0 ]; then + # if the file is not empty, display its content printf "### cat %s ###\n" "${error_file}" >&2 cat "${error_file}" >&2 + else + # if the file is empty, remove it + rm "${error_file}" fi done + + # Search for remaining error_files + error_files_count=$(find "${ERRORS_DIR}" -type f | wc -l) + # If there is no error file, clean the parent directories + if [ "${error_files_count}" -eq 0 ]; then + rm -rf "${ERRORS_DIR}" + fi } # shellcheck disable=SC2317 mysql_list_databases() { @@ -222,6 +234,11 @@ mysql_list_databases() { mysql --defaults-extra-file=/etc/mysql/debian.cnf --port="${port}" --execute="show databases" --silent --skip-column-names \ | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" } + +# shellcheck disable=SC2317 +errors_dir_from_dump_dir() { + realpath --canonicalize-missing "${ERRORS_DIR}/$(realpath --relative-to="${LOCAL_BACKUP_DIR}" "${1}")" +} # shellcheck disable=SC2317 dump_ldap() { ## OpenLDAP : example with slapcat @@ -241,16 +258,16 @@ dump_ldap() { # shellcheck disable=SC2317 dump_mysql_global() { dump_dir="${LOCAL_BACKUP_DIR}/mysql-global" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - error_file="${dump_dir}/mysql.bak.err" + error_file="${errors_dir}/mysql.bak.err" dump_file="${dump_dir}/mysql.bak.gz" log "LOCAL_TASKS - start ${dump_file}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" \ - | gzip --best > "${dump_file}" + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" | gzip --best > "${dump_file}" last_rc=$? # shellcheck disable=SC2086 @@ -265,13 +282,14 @@ dump_mysql_global() { # shellcheck disable=SC2317 dump_mysql_per_base() { dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" databases=$(mysql_list_databases 3306) for database in ${databases}; do - error_file="${dump_dir}/${database}.err" + error_file="${errors_dir}/${database}.err" dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" @@ -291,12 +309,13 @@ dump_mysql_per_base() { # shellcheck disable=SC2317 dump_mysql_meta() { dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" ## Dump all grants (requires 'percona-toolkit' package) - error_file="${dump_dir}/all_grants.err" + error_file="${errors_dir}/all_grants.err" dump_file="${dump_dir}/all_grants.sql" log "LOCAL_TASKS - start ${dump_file}" @@ -313,7 +332,7 @@ dump_mysql_meta() { log "LOCAL_TASKS - stop ${dump_file}" ## Dump all variables - error_file="${dump_dir}/variables.err" + error_file="${errors_dir}/variables.err" dump_file="${dump_dir}/variables.txt" log "LOCAL_TASKS - start ${dump_file}" @@ -332,7 +351,7 @@ dump_mysql_meta() { ## Schema only (no data) for each databases databases=$(mysql_list_databases 3306) for database in ${databases}; do - error_file="${dump_dir}/${database}.schema.err" + error_file="${errors_dir}/${database}.schema.err" dump_file="${dump_dir}/${database}.schema.sql" log "LOCAL_TASKS - start ${dump_file}" @@ -354,12 +373,13 @@ dump_mysql_tabs() { databases=$(mysql_list_databases 3306) for database in ${databases}; do dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs/${database}" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" chown -RL mysql "${dump_dir}" - error_file="${dump_dir}.err" + error_file="${errors_dir}.err" log "LOCAL_TASKS - start ${dump_dir}" mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 -Q --opt --events --hex-blob --skip-comments --fields-enclosed-by='\"' --fields-terminated-by=',' -T "${dump_dir}" "${database}" 2> "${error_file}" @@ -381,11 +401,12 @@ dump_mysql_hotcopy() { databases="" for database in ${databases}; do dump_dir="${LOCAL_BACKUP_DIR}/mysql-hotcopy/${database}" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - error_file="${dump_dir}.err" + error_file="${errors_dir}.err" log "LOCAL_TASKS - start ${dump_dir}" mysqlhotcopy "${database}" "${dump_dir}/" 2> "${error_file}" @@ -404,9 +425,10 @@ dump_mysql_hotcopy() { # shellcheck disable=SC2317 dump_mysql_instances() { dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" mysql_user="mysqladmin" mysql_passwd=$(grep -m1 'password = .*' /root/.my.cnf | cut -d " " -f 3) @@ -414,7 +436,7 @@ dump_mysql_instances() { # customize list of instances instances="" for instance in ${instances}; do - error_file="${dump_dir}/${instance}.err" + error_file="${errors_dir}/${instance}.err" dump_file="${dump_dir}/${instance}.bak.gz" log "LOCAL_TASKS - start ${dump_file}" @@ -434,9 +456,10 @@ dump_mysql_instances() { # shellcheck disable=SC2317 dump_postgresql_global() { dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" ## example with pg_dumpall and with compression dump_file="${dump_dir}/pg.dump.bak.gz" @@ -468,16 +491,17 @@ dump_postgresql_global() { # shellcheck disable=SC2317 dump_postgresql_per_base() { dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" ( # shellcheck disable=SC2164 cd /var/lib/postgresql databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") for database in ${databases} ; do - error_file="${dump_dir}/${database}.err" + error_file="${errors_dir}/${database}.err" dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" @@ -498,11 +522,12 @@ dump_postgresql_per_base() { # shellcheck disable=SC2317 dump_postgresql_filtered() { dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - error_file="${dump_dir}/pg-backup.err" + error_file="${errors_dir}/pg-backup.err" dump_file="${dump_dir}/pg-backup.tar" log "LOCAL_TASKS - start ${dump_file}" @@ -528,11 +553,13 @@ dump_redis() { for instance in ${instances}; do name=$(basename "${instance}") dump_dir="${LOCAL_BACKUP_DIR}/${name}" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" if [ -f "${instance}/dump.rdb" ]; then - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + error_file="${errors_dir}/${instance}.err" log "LOCAL_TASKS - start ${dump_dir}" cp -a "${instance}/dump.rdb" "${dump_dir}/" 2> "${error_file}" @@ -556,11 +583,12 @@ dump_mongodb() { ## > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) dump_dir="${LOCAL_BACKUP_DIR}/mongodump" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - error_file="${dump_dir}.err" + error_file="${errors_dir}.err" log "LOCAL_TASKS - start ${dump_dir}" mongo_user="" @@ -581,12 +609,13 @@ dump_mongodb() { # shellcheck disable=SC2317 dump_megacli_config() { dump_dir="${LOCAL_BACKUP_DIR}/megacli" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" dump_file="${dump_dir}/megacli.cfg" - error_file="${dump_dir}/megacli.err" + error_file="${errors_dir}/megacli.err" log "LOCAL_TASKS - start ${dump_file}" megacli -CfgSave -f "${dump_file}" -a0 2> "${error_file}" > /dev/null @@ -604,9 +633,10 @@ dump_megacli_config() { # shellcheck disable=SC2317 dump_traceroute() { dump_dir="${LOCAL_BACKUP_DIR}/traceroute" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" network_targets="8.8.8.8 www.evolix.fr travaux.evolix.net" @@ -672,11 +702,12 @@ dump_server_state() { # shellcheck disable=SC2317 dump_rabbitmq() { dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - error_file="${dump_dir}.err" + error_file="${errors_dir}.err" dump_file="${dump_dir}/config" log "LOCAL_TASKS - start ${dump_file}" @@ -695,9 +726,10 @@ dump_rabbitmq() { # shellcheck disable=SC2317 dump_facl() { dump_dir="${LOCAL_BACKUP_DIR}/facl" - rm -rf "${dump_dir}" + errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" log "LOCAL_TASKS - start ${dump_dir}" @@ -983,10 +1015,16 @@ clean_temp_files() { } main() { START_EPOCH=$(/bin/date +%s) + START_TIME=$(/bin/date +"%Y%m%d%H%M%S") + log "START GLOBAL - VERSION=${VERSION} LOCAL_TASKS=${LOCAL_TASKS} SYNC_TASKS=${SYNC_TASKS}" # shellcheck disable=SC2174 - mkdir -p -m 700 ${LOCAL_BACKUP_DIR} + mkdir -p -m 700 "${LOCAL_BACKUP_DIR}" + + ERRORS_DIR="${LOCAL_BACKUP_DIR}/${PROGNAME}.errors-${START_TIME}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${ERRORS_DIR}" ## Force umask umask 077 From 86f00467977fea63954596adefac244ef078944d Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 13 Jan 2023 18:17:54 +0100 Subject: [PATCH 035/105] send rsync full log file if it exists --- client/zzz_evobackup | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 18d04be..ba46469 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -974,10 +974,18 @@ sync_tasks() { # Rsync options rsync_report_args+=(--rsh "ssh -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") # Rsync local source - rsync_report_args+=("${RSYNC_STATSFILE}") if [ "${#mtree_files[@]}" -gt 0 ]; then + # send mtree files if there is any rsync_report_args+=("${mtree_files[@]}") fi + if [ -f "${RSYNC_LOGFILE}" ]; then + # send rsync full log file if it exists + rsync_report_args+=("${RSYNC_LOGFILE}") + fi + if [ -f "${RSYNC_STATSFILE}" ]; then + # send rsync stats log file if it exists + rsync_report_args+=("${RSYNC_STATSFILE}") + fi # Rsync remote destination rsync_report_args+=("root@${SSH_SERVER}:/var/log/") From f9aa722ac974f273e8950750bf2a4524c873ba93 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Sat, 14 Jan 2023 18:51:37 +0100 Subject: [PATCH 036/105] log errors as they happen --- client/zzz_evobackup | 70 +++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index ba46469..0419cae 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -272,7 +272,7 @@ dump_mysql_global() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -298,7 +298,7 @@ dump_mysql_per_base() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -324,7 +324,7 @@ dump_mysql_meta() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "pt-show-grants to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -341,7 +341,7 @@ dump_mysql_meta() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysql 'show variables' returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - mysql 'show variables' returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -360,7 +360,7 @@ dump_mysql_meta() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -387,7 +387,7 @@ dump_mysql_tabs() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump to ${dump_dir} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - mysqldump to ${dump_dir} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -414,7 +414,7 @@ dump_mysql_hotcopy() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqlhotcopy to ${dump_dir} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - mysqlhotcopy to ${dump_dir} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -445,7 +445,7 @@ dump_mysql_instances() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mysqldump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -470,7 +470,7 @@ dump_postgresql_global() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "pg_dumpall to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - pg_dumpall to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -510,7 +510,7 @@ dump_postgresql_per_base() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -540,7 +540,7 @@ dump_postgresql_filtered() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -567,7 +567,7 @@ dump_redis() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "cp ${instance}/dump.rdb to ${dump_dir} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - cp ${instance}/dump.rdb to ${dump_dir} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -599,7 +599,7 @@ dump_mongodb() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "mongodump to ${dump_dir} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - mongodump to ${dump_dir} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -623,7 +623,7 @@ dump_megacli_config() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "megacli to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - megacli to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -676,7 +676,7 @@ dump_server_state() { dump_server_state_bin=$(command -v dump-server-state) if [ -z "${dump_server_state_bin}" ]; then - error "dump-server-state is missing" + log_error "LOCAL_TASKS - dump-server-state is missing" rc=1 else if [ "${SYSTEM}" = "linux" ]; then @@ -684,7 +684,7 @@ dump_server_state() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "dump-server-state returned an error ${last_rc}, check ${dump_dir}" + log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" rc=${E_DUMPFAILED} fi else @@ -692,7 +692,7 @@ dump_server_state() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "dump-server-state returned an error ${last_rc}, check ${dump_dir}" + log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" rc=${E_DUMPFAILED} fi fi @@ -716,7 +716,7 @@ dump_rabbitmq() { last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - error "pg_dump to ${dump_file} returned an error ${last_rc}, check ${error_file}" + log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" rc=${E_DUMPFAILED} else rm -f "${error_file}" @@ -904,7 +904,7 @@ sync_tasks() { done < "${rsync_includes_file}" if [ "${#mtree_files[@]}" -le 0 ]; then - error "ERROR: mtree didn't produce any file" + log_error "SYNC_TASKS - ERROR: mtree didn't produce any file" fi log "SYNC_TASKS - stop mtree (files: ${mtree_files[*]})" @@ -966,7 +966,7 @@ sync_tasks() { # We ignore rc=24 (vanished files) if [ ${rsync_main_rc} -ne 0 ] && [ ${rsync_main_rc} -ne 24 ]; then - error "rsync returned an error ${rsync_main_rc}, check ${LOGFILE}" + log_error "SYNC_TASKS - rsync returned an error ${rsync_main_rc}" "${LOGFILE}" rc=${E_SYNCFAILED} else # Build the report Rsync command @@ -1007,13 +1007,29 @@ log() { "$(/bin/date +"${DATE_FORMAT}")" "${PROGNAME}" "${pid}" "${msg}" \ >> "${LOGFILE}" } -# Output a message to stderr -error() { - msg="${1:-$(cat /dev/stdin)}" - pid=$$ - printf "[%s] %s[%s]: %s\\n" \ - "$(/bin/date +"${DATE_FORMAT}")" "${PROGNAME}" "${pid}" "${msg}" \ - >&2 +log_error() { + error_msg=${1} + error_file=${2:""} + + if [ -n "${error_file}" ] && [ -f "${error_file}" ]; then + printf "\n### %s\n" "${error_msg}" >&2 + # shellcheck disable=SC2046 + if [ $(wc -l "${error_file}") -gt 30 ]; then + printf "~~~{%s (tail -30)}\n" "${error_file}" >&2 + tail -n 30 "${error_file}" >&2 + else + printf "~~~{%s}\n" "${error_file}" >&2 + cat "${error_file}" >&2 + fi + printf "~~~\n" >&2 + + log "${error_msg}, check ${error_file}" + else + printf "\n### %s\n" "${error_msg}" >&2 + + log "${error_msg}" + fi + } # Remove all temporary file created during the execution # shellcheck disable=SC2317 From 2ea9614e3c47a110093ad2cc22465475d4ed6c1e Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Sun, 15 Jan 2023 22:56:03 +0100 Subject: [PATCH 037/105] WIP: separate lib and custom code --- client/lib/dump.sh | 529 ++++++++++++++++++++++++++++++++++++++++ client/lib/main.sh | 430 ++++++++++++++++++++++++++++++++ client/lib/utilities.sh | 136 +++++++++++ client/zzz_evobackup.sh | 256 +++++++++++++++++++ 4 files changed, 1351 insertions(+) create mode 100644 client/lib/dump.sh create mode 100644 client/lib/main.sh create mode 100644 client/lib/utilities.sh create mode 100644 client/zzz_evobackup.sh diff --git a/client/lib/dump.sh b/client/lib/dump.sh new file mode 100644 index 0000000..47b8a51 --- /dev/null +++ b/client/lib/dump.sh @@ -0,0 +1,529 @@ +#!/bin/bash +# shellcheck disable=SC2034,SC2317 + +mysql_list_databases() { + port=${1:-"3306"} + + mysql --defaults-extra-file=/etc/mysql/debian.cnf --port="${port}" --execute="show databases" --silent --skip-column-names \ + | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" +} + +### BEGIN Dump functions #### + +dump_from_lib() { + echo "Dump from lib" +} + +dump_ldap() { + ## OpenLDAP : example with slapcat + local dump_dir="${LOCAL_BACKUP_DIR}/ldap" + rm -rf "${dump_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" + + log "LOCAL_TASKS - start dump_ldap to ${dump_dir}" + + slapcat -n 0 -l "${dump_dir}/config.bak" + slapcat -n 1 -l "${dump_dir}/data.bak" + slapcat -l "${dump_dir}/all.bak" + + log "LOCAL_TASKS - stop dump_ldap" +} +dump_mysql_global() { + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-global" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}/mysql.bak.err" + local dump_file="${dump_dir}/mysql.bak.gz" + log "LOCAL_TASKS - start ${dump_file}" + + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" +} +dump_mysql_per_base() { + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + databases=$(mysql_list_databases 3306) + for database in ${databases}; do + local error_file="${errors_dir}/${database}.err" + local dump_file="${dump_dir}/${database}.sql.gz" + log "LOCAL_TASKS - start ${dump_file}" + + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + done +} +dump_mysql_meta() { + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + ## Dump all grants (requires 'percona-toolkit' package) + local error_file="${errors_dir}/all_grants.err" + local dump_file="${dump_dir}/all_grants.sql" + log "LOCAL_TASKS - start ${dump_file}" + + pt-show-grants --flush --no-header 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + + ## Dump all variables + local error_file="${errors_dir}/variables.err" + local dump_file="${dump_dir}/variables.txt" + log "LOCAL_TASKS - start ${dump_file}" + + mysql -A -e "SHOW GLOBAL VARIABLES;" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysql 'show variables' returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + + ## Schema only (no data) for each databases + databases=$(mysql_list_databases 3306) + for database in ${databases}; do + local error_file="${errors_dir}/${database}.schema.err" + local dump_file="${dump_dir}/${database}.schema.sql" + log "LOCAL_TASKS - start ${dump_file}" + + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --no-data --databases "${database}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + done +} +dump_mysql_tabs() { + databases=$(mysql_list_databases 3306) + for database in ${databases}; do + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs/${database}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + chown -RL mysql "${dump_dir}" + + local error_file="${errors_dir}.err" + log "LOCAL_TASKS - start ${dump_dir}" + + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 -Q --opt --events --hex-blob --skip-comments --fields-enclosed-by='\"' --fields-terminated-by=',' -T "${dump_dir}" "${database}" 2> "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_dir} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_dir}" + done +} +dump_mysql_hotcopy() { + # customize the list of databases to hot-copy + databases="" + for database in ${databases}; do + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-hotcopy/${database}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}.err" + log "LOCAL_TASKS - start ${dump_dir}" + + mysqlhotcopy "${database}" "${dump_dir}/" 2> "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqlhotcopy to ${dump_dir} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_dir}" + done +} +dump_mysql_instances() { + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + mysql_user="mysqladmin" + mysql_passwd=$(grep -m1 'password = .*' /root/.my.cnf | cut -d " " -f 3) + + # customize list of instances + instances="" + for instance in ${instances}; do + local error_file="${errors_dir}/${instance}.err" + local dump_file="${dump_dir}/${instance}.bak.gz" + log "LOCAL_TASKS - start ${dump_file}" + + mysqldump --port="${instance}" --opt --all-databases --hex-blob --user="${mysql_user}" --password="${mysql_passwd}" 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + done +} +dump_postgresql_global() { + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + ## example with pg_dumpall and with compression + local dump_file="${dump_dir}/pg.dump.bak.gz" + log "LOCAL_TASKS - start ${dump_file}" + + (sudo -u postgres pg_dumpall) 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - pg_dumpall to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + + log "LOCAL_TASKS - stop ${dump_file}" + + ## example with pg_dumpall and without compression + ## WARNING: you need space in ~postgres + # local dump_file="${dump_dir}/pg.dump.bak" + # log "LOCAL_TASKS - start ${dump_file}" + # + # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" + # mv ~postgres/pg.dump.bak "${dump_file}" + # + # log "LOCAL_TASKS - stop ${dump_file}" +} +dump_postgresql_per_base() { + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + ( + # shellcheck disable=SC2164 + cd /var/lib/postgresql + databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") + for database in ${databases} ; do + local error_file="${errors_dir}/${database}.err" + local dump_file="${dump_dir}/${database}.sql.gz" + log "LOCAL_TASKS - start ${dump_file}" + + (sudo -u postgres /usr/bin/pg_dump --create -s -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + done + ) +} +dump_postgresql_filtered() { + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}/pg-backup.err" + local dump_file="${dump_dir}/pg-backup.tar" + log "LOCAL_TASKS - start ${dump_file}" + + ## example with all tables from MYBASE excepts TABLE1 and TABLE2 + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -t 'TABLE1' -t 'TABLE2' MYBASE 2> "${error_file}" + + ## example with only TABLE1 and TABLE2 from MYBASE + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -T 'TABLE1' -T 'TABLE2' MYBASE 2> "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" +} +dump_redis() { + instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*') + for instance in ${instances}; do + name=$(basename "${instance}") + local dump_dir="${LOCAL_BACKUP_DIR}/${name}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + if [ -f "${instance}/dump.rdb" ]; then + local error_file="${errors_dir}/${instance}.err" + log "LOCAL_TASKS - start ${dump_dir}" + + cp -a "${instance}/dump.rdb" "${dump_dir}/" 2> "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - cp ${instance}/dump.rdb to ${dump_dir} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_dir}" + fi + done +} +dump_mongodb() { + ## don't forget to create use with read-only access + ## > use admin + ## > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) + + local dump_dir="${LOCAL_BACKUP_DIR}/mongodump" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}.err" + log "LOCAL_TASKS - start ${dump_dir}" + + mongo_user="" + mongo_password="" + + mongodump -u "${mongo_user}" -p"${mongo_password}" -o "${dump_dir}/" 2> "${error_file}" > /dev/null + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mongodump to ${dump_dir} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_dir}" +} +dump_megacli_config() { + local dump_dir="${LOCAL_BACKUP_DIR}/megacli" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + local dump_file="${dump_dir}/megacli.cfg" + local error_file="${errors_dir}/megacli.err" + log "LOCAL_TASKS - start ${dump_file}" + + megacli -CfgSave -f "${dump_file}" -a0 2> "${error_file}" > /dev/null + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - megacli to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" +} +dump_traceroute() { + local dump_dir="${LOCAL_BACKUP_DIR}/traceroute" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + network_targets="8.8.8.8 www.evolix.fr travaux.evolix.net" + + mtr_bin=$(command -v mtr) + if [ -n "${network_targets}" ] && [ -n "${mtr_bin}" ]; then + for addr in ${network_targets}; do + local dump_file="${dump_dir}/mtr-${addr}" + log "LOCAL_TASKS - start ${dump_file}" + + ${mtr_bin} -r "${addr}" > "${dump_file}" + + log "LOCAL_TASKS - stop ${dump_file}" + done + fi + + traceroute_bin=$(command -v traceroute) + if [ -n "${network_targets}" ] && [ -n "${traceroute_bin}" ]; then + for addr in ${network_targets}; do + local dump_file="${dump_dir}/traceroute-${addr}" + log "LOCAL_TASKS - start ${dump_file}" + + ${traceroute_bin} -n "${addr}" > "${dump_file}" 2>&1 + + log "LOCAL_TASKS - stop ${dump_file}" + done + fi +} +dump_server_state() { + local dump_dir="${LOCAL_BACKUP_DIR}/server-state" + rm -rf "${dump_dir}" + # Do not create the directory + # shellcheck disable=SC2174 + # mkdir -p -m 700 "${dump_dir}" + + log "LOCAL_TASKS - start ${dump_dir}" + + dump_server_state_bin=$(command -v dump-server-state) + if [ -z "${dump_server_state_bin}" ]; then + log_error "LOCAL_TASKS - dump-server-state is missing" + rc=1 + else + if [ "${SYSTEM}" = "linux" ]; then + ${dump_server_state_bin} --all --dump-dir "${dump_dir}" + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" + GLOBAL_RC=${E_DUMPFAILED} + fi + else + ${dump_server_state_bin} --all --dump-dir "${dump_dir}" + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" + GLOBAL_RC=${E_DUMPFAILED} + fi + fi + fi + log "LOCAL_TASKS - stop ${dump_dir}" +} +dump_rabbitmq() { + local dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}.err" + local dump_file="${dump_dir}/config" + log "LOCAL_TASKS - start ${dump_file}" + + rabbitmqadmin export "${dump_file}" 2> "${error_file}" >> "${LOGFILE}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" +} +dump_facl() { + local dump_dir="${LOCAL_BACKUP_DIR}/facl" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + log "LOCAL_TASKS - start ${dump_dir}" + + getfacl -R /etc > "${dump_dir}/etc.txt" + getfacl -R /home > "${dump_dir}/home.txt" + getfacl -R /usr > "${dump_dir}/usr.txt" + getfacl -R /var > "${dump_dir}/var.txt" + + log "LOCAL_TASKS - stop ${dump_dir}" +} +dump_elasticsearch_snapshot() { + log "LOCAL_TASKS - start dump_elasticsearch_snapshot" + + ## Take a snapshot as a backup. + ## Warning: You need to have a path.repo configured. + ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes + + curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" + curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" + + # Clustered version here + # It basically the same thing except that you need to check that NFS is mounted + # if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2' + # then + # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" + # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" + # else + # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' + # fi + + ## If you need to keep older snapshot, for example the last 10 daily snapshots, replace the XDELETE and XPUT lines by : + # for snapshot in $(curl -s -XGET "localhost:9200/_snapshot/snaprepo/_all?pretty=true" | grep -Eo 'snapshot_[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -n -10); do + # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/${snapshot}" | grep -v -Fx '{"acknowledged":true}' + # done + # date=$(/bin/date +%F) + # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot_${date}?wait_for_completion=true" >> "${LOGFILE}" + + log "LOCAL_TASKS - stop dump_elasticsearch_snapshot" +} \ No newline at end of file diff --git a/client/lib/main.sh b/client/lib/main.sh new file mode 100644 index 0000000..ddc8070 --- /dev/null +++ b/client/lib/main.sh @@ -0,0 +1,430 @@ +#!/bin/bash +# shellcheck disable=SC2034,SC2317 + +readonly VERSION="23.1-pre" + +# set all programs to C language (english) +export LC_ALL=C + +# If expansion is attempted on an unset variable or parameter, the shell prints an +# error message, and, if not interactive, exits with a non-zero status. +set -u +# The pipeline's return status is the value of the last (rightmost) command +# to exit with a non-zero status, or zero if all commands exit successfully. +set -o pipefail + +local_tasks() { + log_error "The 'local_tasks' function hasn't been customized" +} +# Called from main, it is wrapping the local_tasks function defined in the real script +local_tasks_wrapper() { + log "START LOCAL_TASKS" + + # Remove old log directories + find "${LOCAL_BACKUP_DIR}/" -type d -name "${PROGNAME}.errors-*" -ctime +30 -delete + + # This function must be defined in the calling script + local_tasks + + # TODO: check if this is still needed + # print_error_files_content + + log "STOP LOCAL_TASKS" +} +sync_tasks() { + log_error "The 'sync_tasks' function hasn't been customized" +} +# Called from main, it is wrapping the sync_tasks function defined in the real script +sync_tasks_wrapper() { + declare -a SERVERS # Indexed array for server/port values + declare -a RSYNC_INCLUDES # Indexed array for includes + declare -a RSYNC_EXCLUDES # Indexed array for excludes + + case "${SYSTEM}" in + linux) + declare -a rsync_default_includes=( + /bin + /boot + /lib + /opt + /sbin + /usr + ) + ;; + *bsd) + declare -a rsync_default_includes=( + /bin + /bsd + /sbin + /usr + ) + ;; + *) + echo "Unknown system '${SYSTEM}'" >&2 + exit 1 + ;; + esac + if [ -f "${CANARY_FILE}" ]; then + rsync_default_includes+=("${CANARY_FILE}") + fi + readonly rsync_default_includes + + declare -a rsync_default_excludes=( + /dev + /proc + /run + /sys + /tmp + /usr/doc + /usr/obj + /usr/share/doc + /usr/src + /var/apt + /var/cache + /var/db/munin/*.tmp + /var/lib/amavis/amavisd.sock + /var/lib/amavis/tmp + /var/lib/clamav/*.tmp + /var/lib/elasticsearch + /var/lib/metche + /var/lib/mongodb + /var/lib/munin/*tmp* + /var/lib/mysql + /var/lib/php/sessions + /var/lib/php5 + /var/lib/postgres + /var/lib/postgresql + /var/lib/sympa + /var/lock + /var/run + /var/spool/postfix + /var/spool/smtpd + /var/spool/squid + /var/state + /var/tmp + lost+found + .nfs.* + lxc/*/rootfs/tmp + lxc/*/rootfs/usr/doc + lxc/*/rootfs/usr/obj + lxc/*/rootfs/usr/share/doc + lxc/*/rootfs/usr/src + lxc/*/rootfs/var/apt + lxc/*/rootfs/var/cache + lxc/*/rootfs/var/lib/php5 + lxc/*/rootfs/var/lib/php/sessions + lxc/*/rootfs/var/lock + lxc/*/rootfs/var/run + lxc/*/rootfs/var/state + lxc/*/rootfs/var/tmp + /home/mysqltmp + ) + readonly rsync_default_excludes + + # This function must be defined in the calling script + sync_tasks +} + +sync() { + local sync_name=${1} + local -a rsync_servers=("${!2}") + local -a rsync_includes=("${!3}") + local -a rsync_excludes=("${!4}") + + ## Initialize variable to store SSH connection errors + declare -a SSH_ERRORS=() + + # echo "### sync ###" + + # for server in "${rsync_servers[@]}"; do + # echo "server: ${server}" + # done + + # for include in "${rsync_includes[@]}"; do + # echo "include: ${include}" + # done + + # for exclude in "${rsync_excludes[@]}"; do + # echo "exclude: ${exclude}" + # done + + local -i n=0 + local server="" + if [ "${SERVERS_FALLBACK}" = "1" ]; then + # We try to find a suitable server + while :; do + server=$(pick_server ${n}) + test $? = 0 || exit ${E_NOSRVAVAIL} + + if test_server "${server}"; then + break + else + server="" + n=$(( n + 1 )) + fi + done + else + # we force the server + server=$(pick_server "${n}") + fi + + rsync_server=$(echo "${server}" | cut -d':' -f1) + rsync_port=$(echo "${server}" | cut -d':' -f2) + + log "START SYNC_TASKS - ${sync_name} : server=${server}" + + # Rsync complete log file for the current run + RSYNC_LOGFILE="/var/log/${PROGNAME}.${sync_name}.rsync.log" + # Rsync stats for the current run + RSYNC_STATSFILE="/var/log/${PROGNAME}.${sync_name}.rsync-stats.log" + + # reset Rsync log file + if [ -n "$(command -v truncate)" ]; then + truncate -s 0 "${RSYNC_LOGFILE}" + truncate -s 0 "${RSYNC_STATSFILE}" + else + printf "" > "${RSYNC_LOGFILE}" + printf "" > "${RSYNC_STATSFILE}" + fi + + if [ "${MTREE_ENABLED}" = "1" ]; then + mtree_bin=$(command -v mtree) + + if [ -n "${mtree_bin}" ]; then + # Dump filesystem stats with mtree + log "SYNC_TASKS - start mtree" + + local -a mtree_files=() + + # Loop over Rsync includes + + for i in "${!rsync_includes[@]}"; do + include="${rsync_includes[i]}" + + # … but exclude for mtree what will be excluded by Rsync + mtree_excludes_file="$(mktemp --tmpdir "${PROGNAME}.${sync_name}.mtree-excludes.XXXXXX")" + add_to_temp_files "${mtree_excludes_file}" + + for j in "${!rsync_excludes[@]}"; do + echo "${rsync_excludes[j]}" | grep -E "^([^/]|${include})" | sed -e "s|^${include}|.|" >> "${mtree_excludes_file}" + done + + mtree_file="/var/log/evobackup.$(basename "${include}").mtree" + add_to_temp_files "${mtree_file}" + + ${mtree_bin} -x -c -p "${include}" -X "${mtree_excludes_file}" > "${mtree_file}" + mtree_files+=("${mtree_file}") + done + + if [ "${#mtree_files[@]}" -le 0 ]; then + log_error "SYNC_TASKS - ERROR: mtree didn't produce any file" + fi + + log "SYNC_TASKS - stop mtree (files: ${mtree_files[*]})" + else + log "SYNC_TASKS - skip mtree (missing)" + fi + else + log "SYNC_TASKS - skip mtree (disabled)" + fi + + rsync_bin=$(command -v rsync) + # Build the final Rsync command + + # Rsync main options + rsync_main_args=() + rsync_main_args+=(--archive) + rsync_main_args+=(--itemize-changes) + rsync_main_args+=(--quiet) + rsync_main_args+=(--stats) + rsync_main_args+=(--human-readable) + rsync_main_args+=(--relative) + rsync_main_args+=(--partial) + rsync_main_args+=(--delete) + rsync_main_args+=(--delete-excluded) + rsync_main_args+=(--force) + rsync_main_args+=(--ignore-errors) + rsync_main_args+=(--log-file "${RSYNC_LOGFILE}") + rsync_main_args+=(--rsh "ssh -p ${rsync_port} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") + + # Rsync excludes + for i in "${!rsync_excludes[@]}"; do + rsync_main_args+=(--exclude "${rsync_excludes[i]}") + done + + # Rsync local sources + rsync_main_args+=("${rsync_includes[@]}") + + # Rsync remote destination + rsync_main_args+=("root@${rsync_server}:${REMOTE_BACKUP_DIR}/") + + # … log it + log "SYNC_TASKS - ${sync_name} Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" + + # … execute it + ${rsync_bin} "${rsync_main_args[@]}" + + rsync_main_rc=$? + + # Copy last lines of rsync log to the main log + tail -n 30 "${RSYNC_LOGFILE}" >> "${LOGFILE}" + # Copy Rsync stats to special file + tail -n 30 "${RSYNC_LOGFILE}" | grep --invert-match --extended-regexp " [\<\>ch\.\*]\S{10} " > "${RSYNC_STATSFILE}" + + # We ignore rc=24 (vanished files) + if [ ${rsync_main_rc} -ne 0 ] && [ ${rsync_main_rc} -ne 24 ]; then + log_error "SYNC_TASKS - ${sync_name} Rsync main command returned an error ${rsync_main_rc}" "${LOGFILE}" + GLOBAL_RC=${E_SYNCFAILED} + else + # Build the report Rsync command + local -a rsync_report_args + + rsync_report_args=() + + # Rsync options + rsync_report_args+=(--rsh "ssh -p ${rsync_port} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") + + # Rsync local sources + if [ "${#mtree_files[@]}" -gt 0 ]; then + # send mtree files if there is any + rsync_report_args+=("${mtree_files[@]}") + fi + if [ -f "${RSYNC_LOGFILE}" ]; then + # send rsync full log file if it exists + rsync_report_args+=("${RSYNC_LOGFILE}") + fi + if [ -f "${RSYNC_STATSFILE}" ]; then + # send rsync stats log file if it exists + rsync_report_args+=("${RSYNC_STATSFILE}") + fi + + # Rsync remote destination + rsync_report_args+=("root@${rsync_server}:${REMOTE_LOG_DIR}/") + + # … log it + log "SYNC_TASKS - ${sync_name} Rsync report command : ${rsync_bin} ${rsync_report_args[*]}" + + # … execute it + ${rsync_bin} "${rsync_report_args[@]}" + fi + + log "STOP SYNC_TASKS - ${sync_name} server=${server}" +} + +setup() { + # Default return-code (0 == succes) + declare -i GLOBAL_RC=0 + + # Possible error codes + readonly E_NOSRVAVAIL=21 # No server is available + readonly E_SYNCFAILED=20 # Failed sync task + readonly E_DUMPFAILED=10 # Failed dump task + + # explicit PATH + PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin + + # System name (linux, openbsd…) + : "${SYSTEM:=$(uname | tr '[:upper:]' '[:lower:]')}" + + # Hostname (for logs and notifications) + : "${HOSTNAME:=$(hostname)}" + + # Store pid in a file named after this program's name + : "${PROGNAME:=$(basename "$0")}" + : "${PIDFILE:="/var/run/${PROGNAME}.pid"}" + + # Customize the log path if you want multiple scripts to have separate log files + : "${LOGFILE:="/var/log/evobackup.log"}" + + # Canary file to update before executing tasks + : "${CANARY_FILE:="/zzz_evobackup_canary"}" + + # Date format for log messages + : "${DATE_FORMAT:="%Y-%m-%d %H:%M:%S"}" + + # Should we fallback on other servers when the first one is unreachable? + : "${SERVERS_FALLBACK:=1}" + # timeout (in seconds) for SSH connections + : "${SSH_CONNECT_TIMEOUT:=90}" + + : "${LOCAL_BACKUP_DIR:="/home/backup"}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${LOCAL_BACKUP_DIR}" + + : "${ERRORS_DIR:="${LOCAL_BACKUP_DIR}/${PROGNAME}.errors-${START_TIME}"}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${ERRORS_DIR}" + + # Backup directory on remote server + : "${REMOTE_BACKUP_DIR:="/var/backup"}" + # Log directory in remote server + : "${REMOTE_LOG_DIR:="/var/log"}" + + # Email address for notifications + : "${MAIL:="root"}" + + # Email subject for notifications + : "${MAIL_SUBJECT:="[info] EvoBackup - Client ${HOSTNAME}"}" + + # Enable/disable local tasks (default: enabled) + : "${LOCAL_TASKS:=1}" + # Enable/disable sync tasks (default: enabled) + : "${SYNC_TASKS:=1}" + + # Enable/disable mtree (default: enabled) + : "${MTREE_ENABLED:=1}" + + ## Force umask + umask 077 + + # Initialize a list of temporary files + declare -a TEMP_FILES=() + # Any file in this list will be deleted when the program exits + trap "clean_temp_files" EXIT +} + +main() { + # Start timer + START_EPOCH=$(/bin/date +%s) + START_TIME=$(/bin/date +"%Y%m%d%H%M%S") + + # Configure variables and environment + setup + + log "START GLOBAL - VERSION=${VERSION} LOCAL_TASKS=${LOCAL_TASKS} SYNC_TASKS=${SYNC_TASKS}" + + # /!\ Only one backup processus can run at the sametime /!\ + # Based on PID file, kill any running process before continuing + enforce_single_process "${PIDFILE}" + + # Update canary to keep track of each run + update-evobackup-canary --who "${PROGNAME}" --file "${CANARY_FILE}" + + if [ "${LOCAL_TASKS}" = "1" ]; then + local_tasks_wrapper + fi + + if [ "${SYNC_TASKS}" = "1" ]; then + sync_tasks_wrapper + fi + + STOP_EPOCH=$(/bin/date +%s) + + case "${SYSTEM}" in + *bsd) + start_time=$(/bin/date -f "%s" -j "${START_EPOCH}" +"${DATE_FORMAT}") + stop_time=$(/bin/date -f "%s" -j "${STOP_EPOCH}" +"${DATE_FORMAT}") + ;; + *) + start_time=$(/bin/date --date="@${START_EPOCH}" +"${DATE_FORMAT}") + stop_time=$(/bin/date --date="@${STOP_EPOCH}" +"${DATE_FORMAT}") + ;; + esac + duration=$(( STOP_EPOCH - START_EPOCH )) + + log "STOP GLOBAL - start='${start_time}' stop='${stop_time}' duration=${duration}s" + + send_mail + + exit ${GLOBAL_RC} +} \ No newline at end of file diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh new file mode 100644 index 0000000..d17aa3c --- /dev/null +++ b/client/lib/utilities.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +# Output a message to the log file +log() { + local msg="${1:-$(cat /dev/stdin)}" + local pid=$$ + + printf "[%s] %s[%s]: %s\\n" \ + "$(/bin/date +"${DATE_FORMAT}")" "${PROGNAME}" "${pid}" "${msg}" \ + >> "${LOGFILE}" +} +log_error() { + local error_msg=${1} + local error_file=${2:""} + + if [ -n "${error_file}" ] && [ -f "${error_file}" ]; then + printf "\n### %s\n" "${error_msg}" >&2 + # shellcheck disable=SC2046 + if [ $(wc -l "${error_file}") -gt 30 ]; then + printf "~~~{%s (tail -30)}\n" "${error_file}" >&2 + tail -n 30 "${error_file}" >&2 + else + printf "~~~{%s}\n" "${error_file}" >&2 + cat "${error_file}" >&2 + fi + printf "~~~\n" >&2 + + log "${error_msg}, check ${error_file}" + else + printf "\n### %s\n" "${error_msg}" >&2 + + log "${error_msg}" + fi + +} +add_to_temp_files() { + TEMP_FILES+=("${1}") +} +# Remove all temporary file created during the execution +clean_temp_files() { + # shellcheck disable=SC2086 + rm -f "${TEMP_FILES[@]}" +} +enforce_single_process() { + local pidfile=$1 + + if [ -e "${pidfile}" ]; then + pid=$(cat "${pidfile}") + # Does process still exist? + if kill -0 "${pid}" 2> /dev/null; then + # Killing the childs of evobackup. + for ppid in $(pgrep -P "${pid}"); do + kill -9 "${ppid}"; + done + # Then kill the main PID. + kill -9 "${pid}" + printf "%s is still running (PID %s). Process has been killed" "$0" "${pid}\\n" >&2 + else + rm -f "${pidfile}" + fi + fi + add_to_temp_files "${pidfile}" + + echo "$$" > "${pidfile}" +} + +# Build the error directory (inside ERRORS_DIR) based on the dump directory path +errors_dir_from_dump_dir() { + local dump_dir=$1 + local relative_path=$(realpath --relative-to="${LOCAL_BACKUP_DIR}" "${dump_dir}") + + # return absolute path + realpath --canonicalize-missing "${ERRORS_DIR}/${relative_path}" +} + +# Call test_server with "HOST:PORT" string +# It will return with 0 if the server is reachable. +# It will return with 1 and a message on stderr if not. +test_server() { + local item=$1 + # split HOST and PORT from the input string + local host=$(echo "${item}" | cut -d':' -f1) + local port=$(echo "${item}" | cut -d':' -f2) + + local new_error + + # Test if the server is accepting connections + ssh -q -o "ConnectTimeout ${SSH_CONNECT_TIMEOUT}" "${host}" -p "${port}" -t "exit" + # shellcheck disable=SC2181 + if [ $? = 0 ]; then + # SSH connection is OK + return 0 + else + # SSH connection failed + new_error=$(printf "Failed to connect to \`%s' within %s seconds" "${item}" "${SSH_CONNECT_TIMEOUT}") + log "${new_error}" + SSH_ERRORS+=("${new_error}") + + return 1 + fi +} + +# Call pick_server with an optional positive integer to get the nth server in the list. +pick_server() { + local -i increment=${1:-0} + local -i list_length=${#SERVERS[@]} + + if (( increment >= list_length )); then + # We've reached the end of the list + new_error="No more server available" + log "${new_error}" + SSH_ERRORS+=("${new_error}") + + # Log errors to stderr + for i in "${!SSH_ERRORS[@]}"; do + printf "%s\n" "${SSH_ERRORS[i]}" >&2 + done + + return 1 + fi + + # Extract the day of month, without leading 0 (which would give an octal based number) + today=$(/bin/date +%e) + # A salt is useful to randomize the starting point in the list + # but stay identical each time it's called for a server (based on hostname). + salt=$(hostname | cksum | cut -d' ' -f1) + # Pick an integer between 0 and the length of the SERVERS list + # It changes each day + n=$(( (today + salt + increment) % list_length )) + + echo "${SERVERS[n]}" +} + +send_mail() { + tail -20 "${LOGFILE}" | mail -s "${MAIL_SUBJECT}" "${MAIL}" +} \ No newline at end of file diff --git a/client/zzz_evobackup.sh b/client/zzz_evobackup.sh new file mode 100644 index 0000000..537d53c --- /dev/null +++ b/client/zzz_evobackup.sh @@ -0,0 +1,256 @@ +#!/bin/bash +# +# Script Evobackup client +# See https://gitea.evolix.org/evolix/evobackup +# +# Authors: Evolix , +# Gregory Colpart , +# Romain Dessort , +# Benoit Série , +# Tristan Pilat , +# Victor Laborie , +# Jérémy Lecour +# and others. +# +# Licence: AGPLv3 + +source ./lib/utilities.sh +source ./lib/dump.sh +source ./lib/main.sh + +####################################################################### +# +# You must configure the MAIL variable to receive notifications. +# +# There is some optional configuration that you can do +# at the end of this script. +# +# The library (usually installed at /usr/local/lib/evobackup/main.sh) +# also has many variables that you can override for fine-tuning. +# +####################################################################### + +# Email adress for notifications +MAIL=jdoe@example.com + +####################################################################### +# +# The "sync_tasks" function will be called by the main function. +# +# You can customize the variables: +# * "sync_name" (String) +# * "SERVERS" (Array of HOST:PORT) +# * "RSYNC_INCLUDES" (Array of paths to include) +# * "RSYNC_EXCLUDES" (Array of paths to exclude) +# +# The "sync" function can be called multiple times +# with a different set of variables. +# That way you can to sync to various destinations. +# +####################################################################### + +sync_tasks() { + + ########## System-only backup (to Evolix servers) ################# + + # Name your sync task, for logs + sync_name="evolix-system" + + # List of host/port for your sync task + SERVERS=( + node0.backup.evolix.net:2234 + node1.backup.evolix.net:2234 + ) + + # What to include in your sync task + # shellcheck disable=SC2034 + RSYNC_INCLUDES=( + "${rsync_default_includes[@]}" + /etc + /root + /var + ) + + # What to exclude from your sync task + # shellcheck disable=SC2034 + RSYNC_EXCLUDES=( + "${rsync_default_excludes[@]}" + ) + + # Call the sync task + sync "${sync_name}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" + + + ########## Full backup (to client servers) ######################## + + # Name your sync task, for logs + sync_name="client-full" + + # List of host/port for your sync task + SERVERS=( + client-backup00.evolix.net:2221 + client-backup01.evolix.net:2221 + ) + + # What to include in your sync task + # shellcheck disable=SC2034 + RSYNC_INCLUDES=( + "${rsync_default_includes[@]}" + /etc + /root + /var + /home + /srv + ) + + # What to exclude from your sync task + # shellcheck disable=SC2034 + RSYNC_EXCLUDES=( + "${rsync_default_excludes[@]}" + /home/foo + ) + + # Call the sync task + sync "${sync_name}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" + +} + +####################################################################### +# +# The "local_tasks" function will be called by the main function. +# +# You can call any available "dump_xxx" function +# (usually installed at /usr/local/lib/evobackup/dump.sh) +# +# You can also write some custom functions and call them. +# A "dump_custom" example is available further down. +# +####################################################################### + +local_tasks() { + + ########## OpenLDAP ############### + + ### dump_ldap + + ########## MySQL ################## + + # Dump all grants (permissions), config variables and schema of databases + ### dump_mysql_meta + + # Dump all databases in a single compressed file + ### dump_mysql_global + + # Dump each database separately, in a compressed file + ### dump_mysql_per_base + + # Dump multiples instances, each in a single compressed file + ### dump_mysql_instances + + # Dump each table in schema/data files, for all databases + ### dump_mysql_tabs + + # Run mysqlhotcopy for a specific database (must be configured) + # dump_mysql_hotcopy + + ########## PostgreSQL ############# + + # Dump all databases in a single file (compressed or not) + ### dump_postgresql_global + + # Dump a specific databse with only some tables, or all but some tables (must be configured) + ### dump_postgresql_filtered + + # Dump each database separately, in a compressed file + ### dump_postgresql_per_base + + ########## MongoDB ################ + + ### dump_mongodb + + ########## Redis ################## + + # Copy data file for all instances + ### dump_redis + + ########## ElasticSearch ########## + + # Trigger snapshots (must be configured) + ### dump_elasticsearch_snapshot + + ########## RabbitMQ ############### + + ### dump_rabbitmq + + ########## MegaCli ################ + + # Copy RAID config + ### dump_megacli_config + + ########## Network ################ + + # Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) + ### dump_traceroute + + ########## Server state ########### + + # Run dump-server-state to extract system information + ### dump_server_state + + # Dump file access control lists + ### dump_facl + + # No-op, in case nothing is enabled + : +} + +# This is an example for a custom dump function +# Uncomment, customize and call it from the "local_tasks" function +### dump_custom() { +### # Set dump and errors directories and files +### local dump_dir="${LOCAL_BACKUP_DIR}/custom" +### local dump_file="${dump_dir}/dump.gz" +### local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") +### local error_file="${errors_dir}/dump.err" +### +### # Reset dump and errors directories +### rm -rf "${dump_dir}" "${errors_dir}" +### # shellcheck disable=SC2174 +### mkdir -p -m 700 "${dump_dir}" "${errors_dir}" +### +### # Log the start of the command +### log "LOCAL_TASKS - start ${dump_file}" +### +### # Execute your dump command +### # Send errors to the error file and the data to the dump file +### my-dump-command 2> "${error_file}" > "${dump_file}" +### +### # Check result and deal with potential errors +### local last_rc=$? +### # shellcheck disable=SC2086 +### if [ ${last_rc} -ne 0 ]; then +### log_error "LOCAL_TASKS - my-dump-command to ${dump_file} returned an error ${last_rc}" "${error_file}" +### GLOBAL_RC=${E_DUMPFAILED} +### else +### rm -f "${error_file}" +### fi +### +### # Log the end of the command +### log "LOCAL_TASKS - stop ${dump_file}" +### } + +########## Optional configuration ##################################### + +# If you set a value (like "linux", "openbsd"…) it will be used, +# Default: uname(1) in lowercase. +### SYSTEM="linux" + +# If you set a value it will be used, +# Default: hostname(1). +### HOSTNAME="example-host" + +# Email subect for notifications +### MAIL_SUBJECT="[info] EvoBackup - Client ${HOSTNAME}"}" + +# Call main function +main \ No newline at end of file From 7784ba55481f05b2d6e8b45c347c1e9b77532b41 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 16 Jan 2023 09:58:17 +0100 Subject: [PATCH 038/105] load libraries just before calling main --- client/zzz_evobackup.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/zzz_evobackup.sh b/client/zzz_evobackup.sh index 537d53c..83d4944 100644 --- a/client/zzz_evobackup.sh +++ b/client/zzz_evobackup.sh @@ -14,10 +14,6 @@ # # Licence: AGPLv3 -source ./lib/utilities.sh -source ./lib/dump.sh -source ./lib/main.sh - ####################################################################### # # You must configure the MAIL variable to receive notifications. @@ -239,6 +235,12 @@ local_tasks() { ### log "LOCAL_TASKS - stop ${dump_file}" ### } +########## Libraries ################################################## + +source ./lib/utilities.sh +source ./lib/dump.sh +source ./lib/main.sh + ########## Optional configuration ##################################### # If you set a value (like "linux", "openbsd"…) it will be used, From ed7f9e79ae488ff945a2a38d43a4860ca74dea85 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 16 Jan 2023 13:16:19 +0100 Subject: [PATCH 039/105] default value --- client/lib/utilities.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh index d17aa3c..0f48077 100644 --- a/client/lib/utilities.sh +++ b/client/lib/utilities.sh @@ -11,7 +11,7 @@ log() { } log_error() { local error_msg=${1} - local error_file=${2:""} + local error_file=${2:-""} if [ -n "${error_file}" ] && [ -f "${error_file}" ]; then printf "\n### %s\n" "${error_msg}" >&2 From 2bf4d0dd0f41609f6e1157d6e4a84e9504fdeb36 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 16 Jan 2023 14:25:31 +0100 Subject: [PATCH 040/105] mtree includes must be directories --- client/lib/main.sh | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index ddc8070..090af60 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -201,19 +201,21 @@ sync() { for i in "${!rsync_includes[@]}"; do include="${rsync_includes[i]}" - # … but exclude for mtree what will be excluded by Rsync - mtree_excludes_file="$(mktemp --tmpdir "${PROGNAME}.${sync_name}.mtree-excludes.XXXXXX")" - add_to_temp_files "${mtree_excludes_file}" + if [ -d "${include}" ]; then + # … but exclude for mtree what will be excluded by Rsync + mtree_excludes_file="$(mktemp --tmpdir "${PROGNAME}.${sync_name}.mtree-excludes.XXXXXX")" + add_to_temp_files "${mtree_excludes_file}" - for j in "${!rsync_excludes[@]}"; do - echo "${rsync_excludes[j]}" | grep -E "^([^/]|${include})" | sed -e "s|^${include}|.|" >> "${mtree_excludes_file}" - done + for j in "${!rsync_excludes[@]}"; do + echo "${rsync_excludes[j]}" | grep -E "^([^/]|${include})" | sed -e "s|^${include}|.|" >> "${mtree_excludes_file}" + done - mtree_file="/var/log/evobackup.$(basename "${include}").mtree" - add_to_temp_files "${mtree_file}" + mtree_file="/var/log/evobackup.$(basename "${include}").mtree" + add_to_temp_files "${mtree_file}" - ${mtree_bin} -x -c -p "${include}" -X "${mtree_excludes_file}" > "${mtree_file}" - mtree_files+=("${mtree_file}") + ${mtree_bin} -x -c -p "${include}" -X "${mtree_excludes_file}" > "${mtree_file}" + mtree_files+=("${mtree_file}") + fi done if [ "${#mtree_files[@]}" -le 0 ]; then From 0491598c1fe8a5d8d76c7271e2c6045093dd43e8 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 16 Jan 2023 14:26:04 +0100 Subject: [PATCH 041/105] hook functions --- client/lib/main.sh | 39 ++++++++++++++++++++++++++------------- client/zzz_evobackup.sh | 41 ++++++++++++++++++++++++----------------- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index 090af60..deb8275 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -13,9 +13,9 @@ set -u # to exit with a non-zero status, or zero if all commands exit successfully. set -o pipefail -local_tasks() { - log_error "The 'local_tasks' function hasn't been customized" -} +source "${LIBDIR}/utilities.sh" +source "${LIBDIR}/dump.sh" + # Called from main, it is wrapping the local_tasks function defined in the real script local_tasks_wrapper() { log "START LOCAL_TASKS" @@ -23,17 +23,19 @@ local_tasks_wrapper() { # Remove old log directories find "${LOCAL_BACKUP_DIR}/" -type d -name "${PROGNAME}.errors-*" -ctime +30 -delete - # This function must be defined in the calling script - local_tasks + local_tasks_type="$(type -t local_tasks)" + if [ "${local_tasks_type}" = "function" ]; then + local_tasks + else + log_error "There is no 'local_tasks' function to execute" + fi # TODO: check if this is still needed # print_error_files_content log "STOP LOCAL_TASKS" } -sync_tasks() { - log_error "The 'sync_tasks' function hasn't been customized" -} + # Called from main, it is wrapping the sync_tasks function defined in the real script sync_tasks_wrapper() { declare -a SERVERS # Indexed array for server/port values @@ -121,8 +123,12 @@ sync_tasks_wrapper() { ) readonly rsync_default_excludes - # This function must be defined in the calling script - sync_tasks + sync_tasks_type="$(type -t sync_tasks)" + if [ "${sync_tasks_type}" = "function" ]; then + sync_tasks + else + log_error "There is no 'sync_tasks' function to execute" + fi } sync() { @@ -171,7 +177,7 @@ sync() { rsync_server=$(echo "${server}" | cut -d':' -f1) rsync_port=$(echo "${server}" | cut -d':' -f2) - log "START SYNC_TASKS - ${sync_name} : server=${server}" + log "START SYNC_TASKS - \"${sync_name}\" : server=${server}" # Rsync complete log file for the current run RSYNC_LOGFILE="/var/log/${PROGNAME}.${sync_name}.rsync.log" @@ -261,7 +267,7 @@ sync() { rsync_main_args+=("root@${rsync_server}:${REMOTE_BACKUP_DIR}/") # … log it - log "SYNC_TASKS - ${sync_name} Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" + log "SYNC_TASKS - \"${sync_name}\" Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" # … execute it ${rsync_bin} "${rsync_main_args[@]}" @@ -315,7 +321,7 @@ sync() { setup() { # Default return-code (0 == succes) - declare -i GLOBAL_RC=0 + GLOBAL_RC=0 # Possible error codes readonly E_NOSRVAVAIL=21 # No server is available @@ -376,6 +382,12 @@ setup() { # Enable/disable mtree (default: enabled) : "${MTREE_ENABLED:=1}" + # If "setup_custom" exists and is a function, let's call it + setup_custom_type="$(type -t setup_custom)" + if [ "${setup_custom_type}" = "function" ]; then + setup_custom + fi + ## Force umask umask 077 @@ -385,6 +397,7 @@ setup() { trap "clean_temp_files" EXIT } + main() { # Start timer START_EPOCH=$(/bin/date +%s) diff --git a/client/zzz_evobackup.sh b/client/zzz_evobackup.sh index 83d4944..ba08fa6 100644 --- a/client/zzz_evobackup.sh +++ b/client/zzz_evobackup.sh @@ -186,12 +186,12 @@ local_tasks() { ########## Network ################ # Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) - ### dump_traceroute + dump_traceroute ########## Server state ########### # Run dump-server-state to extract system information - ### dump_server_state + dump_server_state # Dump file access control lists ### dump_facl @@ -235,24 +235,31 @@ local_tasks() { ### log "LOCAL_TASKS - stop ${dump_file}" ### } -########## Libraries ################################################## - -source ./lib/utilities.sh -source ./lib/dump.sh -source ./lib/main.sh - ########## Optional configuration ##################################### -# If you set a value (like "linux", "openbsd"…) it will be used, -# Default: uname(1) in lowercase. -### SYSTEM="linux" +setup_custom() { + # If you set a value (like "linux", "openbsd"…) it will be used, + # Default: uname(1) in lowercase. + ### SYSTEM="linux" -# If you set a value it will be used, -# Default: hostname(1). -### HOSTNAME="example-host" + # If you set a value it will be used, + # Default: hostname(1). + ### HOSTNAME="example-host" -# Email subect for notifications -### MAIL_SUBJECT="[info] EvoBackup - Client ${HOSTNAME}"}" + # Email subect for notifications + ### MAIL_SUBJECT="[info] EvoBackup - Client ${HOSTNAME}" + + # No-op in case nothing is executed + : +} + +########## Libraries ################################################## + +# Change this to wherever you install the libraries +LIBDIR="./lib" + +source "${LIBDIR}/main.sh" + +########## Let's go! ################################################## -# Call main function main \ No newline at end of file From c5d82eda68136f45d03294590a9ec1ab5b592cd0 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 16 Jan 2023 14:26:15 +0100 Subject: [PATCH 042/105] deployment playbook --- client/deploy-evobackup-beta.yml | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 client/deploy-evobackup-beta.yml diff --git a/client/deploy-evobackup-beta.yml b/client/deploy-evobackup-beta.yml new file mode 100644 index 0000000..897a190 --- /dev/null +++ b/client/deploy-evobackup-beta.yml @@ -0,0 +1,33 @@ +--- + +- hosts: lecour-www00 + gather_facts: yes + become: yes + + tasks: + + - name: LIBDIR is present + file: + path: /usr/local/lib/evobackup + state: directory + + - name: libraries are installed + copy: + src: "{{ item }}" + dest: /usr/local/lib/evobackup/ + remote_src: False + owner: root + group: root + mode: "0640" + force: yes + loop: "{{ lookup('fileglob', 'lib/*.sh', wantlist=True) }}" + + - name: script is present + copy: + src: zzz_evobackup.sh + dest: /root/evobackup-beta.sh + remote_src: False + owner: root + group: root + mode: "0750" + force: no \ No newline at end of file From 70fbab9bb04d2e2319cb085d9b9bd0e77418f877 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Sat, 28 Jan 2023 16:07:39 +0100 Subject: [PATCH 043/105] Test presence of old config file before trying to delete it --- server/CHANGELOG.md | 2 ++ server/lib/bkctld-remove | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/server/CHANGELOG.md b/server/CHANGELOG.md index 3012189..a3bfad3 100644 --- a/server/CHANGELOG.md +++ b/server/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* Test presence of old config file before trying to delete it + ### Security ## [22.11] - 2022-11-28 diff --git a/server/lib/bkctld-remove b/server/lib/bkctld-remove index 382bac7..87a0ec0 100755 --- a/server/lib/bkctld-remove +++ b/server/lib/bkctld-remove @@ -49,7 +49,7 @@ fi "${LIBDIR}/bkctld-is-on" "${jail_name}" && "${LIBDIR}/bkctld-stop" "${jail_name}" -rm -f "${CONFDIR}/${jail_name}" +test -f "${CONFDIR}/${jail_name}" && rm -f "${CONFDIR}/${jail_name}" rm -rf "$(jail_config_dir "${jail_name}")" btrfs_bin=$(command -v btrfs) From 767d509390c395ac18c99971b8b05aba2862a60f Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Sat, 28 Jan 2023 16:20:36 +0100 Subject: [PATCH 044/105] deploy evobackup beta with configured MAIL and LIBDIR --- client/deploy-evobackup-beta.yml | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/client/deploy-evobackup-beta.yml b/client/deploy-evobackup-beta.yml index 897a190..f17fd47 100644 --- a/client/deploy-evobackup-beta.yml +++ b/client/deploy-evobackup-beta.yml @@ -1,20 +1,23 @@ --- -- hosts: lecour-www00 +- hosts: all gather_facts: yes become: yes - tasks: + vars: + evobackup_mail: alert4@evolix.net + evobackup_libdir: "/usr/local/lib/evobackup" + tasks: - name: LIBDIR is present file: - path: /usr/local/lib/evobackup + path: "{{ evobackup_libdir }}" state: directory - name: libraries are installed copy: src: "{{ item }}" - dest: /usr/local/lib/evobackup/ + dest: "{{ evobackup_libdir }}/" remote_src: False owner: root group: root @@ -25,9 +28,21 @@ - name: script is present copy: src: zzz_evobackup.sh - dest: /root/evobackup-beta.sh + dest: /etc/cron.daily/zzz_evobackup remote_src: False owner: root group: root mode: "0750" - force: no \ No newline at end of file + force: no + + - name: Email is customized + replace: + dest: /etc/cron.daily/zzz_evobackup + regexp: "^MAIL=.*" + replace: "MAIL={{ evobackup_mail }}" + + - name: LIBDIR is customized + replace: + dest: /etc/cron.daily/zzz_evobackup + regexp: "^LIBDIR=.*" + replace: "LIBDIR=\"{{ evobackup_libdir }}\"" \ No newline at end of file From d532ac83da708daf55b1318f821f33aebd21eae4 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Sat, 28 Jan 2023 16:20:51 +0100 Subject: [PATCH 045/105] client: declare variable earlier --- client/lib/main.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index deb8275..b8723d0 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -193,6 +193,9 @@ sync() { printf "" > "${RSYNC_STATSFILE}" fi + # Initialize variable here, we need it later + local -a mtree_files=() + if [ "${MTREE_ENABLED}" = "1" ]; then mtree_bin=$(command -v mtree) @@ -200,10 +203,7 @@ sync() { # Dump filesystem stats with mtree log "SYNC_TASKS - start mtree" - local -a mtree_files=() - # Loop over Rsync includes - for i in "${!rsync_includes[@]}"; do include="${rsync_includes[i]}" From 149b5d0e8d62cad6e8040857ee4219a02a38cd2b Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Sat, 28 Jan 2023 21:14:22 +0100 Subject: [PATCH 046/105] comments --- client/zzz_evobackup.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/zzz_evobackup.sh b/client/zzz_evobackup.sh index ba08fa6..3653d18 100644 --- a/client/zzz_evobackup.sh +++ b/client/zzz_evobackup.sh @@ -53,12 +53,14 @@ sync_tasks() { sync_name="evolix-system" # List of host/port for your sync task + # shellcheck disable=SC2034 SERVERS=( node0.backup.evolix.net:2234 node1.backup.evolix.net:2234 ) # What to include in your sync task + # Add or remove paths if you need # shellcheck disable=SC2034 RSYNC_INCLUDES=( "${rsync_default_includes[@]}" @@ -68,6 +70,7 @@ sync_tasks() { ) # What to exclude from your sync task + # Add or remove paths if you need # shellcheck disable=SC2034 RSYNC_EXCLUDES=( "${rsync_default_excludes[@]}" @@ -83,12 +86,14 @@ sync_tasks() { sync_name="client-full" # List of host/port for your sync task + # shellcheck disable=SC2034 SERVERS=( client-backup00.evolix.net:2221 client-backup01.evolix.net:2221 ) # What to include in your sync task + # Add or remove paths if you need # shellcheck disable=SC2034 RSYNC_INCLUDES=( "${rsync_default_includes[@]}" @@ -100,10 +105,10 @@ sync_tasks() { ) # What to exclude from your sync task + # Add or remove paths if you need # shellcheck disable=SC2034 RSYNC_EXCLUDES=( "${rsync_default_excludes[@]}" - /home/foo ) # Call the sync task From 2e9eb4a946aa883b7624fb2b8d0e510426dd97a7 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 8 Feb 2023 22:51:38 +0100 Subject: [PATCH 047/105] variable for script path --- client/deploy-evobackup-beta.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/deploy-evobackup-beta.yml b/client/deploy-evobackup-beta.yml index f17fd47..a00df7c 100644 --- a/client/deploy-evobackup-beta.yml +++ b/client/deploy-evobackup-beta.yml @@ -5,6 +5,7 @@ become: yes vars: + evobackup_script_path: /etc/cron.daily/zzz_evobackup_beta evobackup_mail: alert4@evolix.net evobackup_libdir: "/usr/local/lib/evobackup" @@ -28,7 +29,7 @@ - name: script is present copy: src: zzz_evobackup.sh - dest: /etc/cron.daily/zzz_evobackup + dest: "{{ evobackup_script_path }}" remote_src: False owner: root group: root From 50f81f2716094149d68c477d4d791a2e3b7934e1 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 8 Feb 2023 22:53:28 +0100 Subject: [PATCH 048/105] Add options for dump functions --- client/lib/dump.sh | 1109 ++++++++++++++++++++++++++++++++++++--- client/zzz_evobackup.sh | 28 +- 2 files changed, 1038 insertions(+), 99 deletions(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 47b8a51..9f1f3c2 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -10,10 +10,11 @@ mysql_list_databases() { ### BEGIN Dump functions #### -dump_from_lib() { - echo "Dump from lib" -} - +####################################################################### +# Dump LDAP files (config, data, all) +# +# Arguments: +####################################################################### dump_ldap() { ## OpenLDAP : example with slapcat local dump_dir="${LOCAL_BACKUP_DIR}/ldap" @@ -29,6 +30,14 @@ dump_ldap() { log "LOCAL_TASKS - stop dump_ldap" } + +####################################################################### +# Dump a single compressed file of all databases of an instance +# +# Arguments: +# --masterdata (default: ) +# --port=[Integer] (default: 3306) +####################################################################### dump_mysql_global() { local dump_dir="${LOCAL_BACKUP_DIR}/mysql-global" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -40,7 +49,62 @@ dump_mysql_global() { local dump_file="${dump_dir}/mysql.bak.gz" log "LOCAL_TASKS - start ${dump_file}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" | gzip --best > "${dump_file}" + local option_masterdata="" + local option_port="3306" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --masterdata) + option_masterdata=1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + declare -a options + options+=("${option_masterdata}") + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--port="${option_port}") + options+=(--opt) + options+=(--all-databases) + options+=(--force) + options+=(--events) + options+=(--hex-blob) + + mysqldump "${options[@]}" 2> "${error_file}" | gzip --best > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -52,6 +116,13 @@ dump_mysql_global() { fi log "LOCAL_TASKS - stop ${dump_file}" } + +####################################################################### +# Dump a compressed file per database of an instance +# +# Arguments: +# --port=[Integer] (default: 3306) +####################################################################### dump_mysql_per_base() { local dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -59,13 +130,61 @@ dump_mysql_per_base() { # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - databases=$(mysql_list_databases 3306) + local option_port="3306" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + declare -a options + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--port="${option_port}") + options+=(--force) + options+=(--events) + options+=(--hex-blob) + + databases=$(mysql_list_databases ${option_port}) for database in ${databases}; do local error_file="${errors_dir}/${database}.err" local dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" + mysqldump "${options[@]}" "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -78,6 +197,13 @@ dump_mysql_per_base() { log "LOCAL_TASKS - stop ${dump_file}" done } + +####################################################################### +# Dump grants, variables and databases schemas for an instance +# +# Arguments: +# --port=[Integer] (default: 3306) +####################################################################### dump_mysql_meta() { local dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -85,12 +211,58 @@ dump_mysql_meta() { # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + local option_port="3306" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + ## Dump all grants (requires 'percona-toolkit' package) local error_file="${errors_dir}/all_grants.err" local dump_file="${dump_dir}/all_grants.sql" log "LOCAL_TASKS - start ${dump_file}" - pt-show-grants --flush --no-header 2> "${error_file}" > "${dump_file}" + declare -a options + options+=(--port "${option_port}") + options+=(--flush) + options+=(--no-header) + + pt-show-grants "${options[@]}" 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -107,7 +279,12 @@ dump_mysql_meta() { local dump_file="${dump_dir}/variables.txt" log "LOCAL_TASKS - start ${dump_file}" - mysql -A -e "SHOW GLOBAL VARIABLES;" 2> "${error_file}" > "${dump_file}" + declare -a options + options+=(--port="${option_port}") + options+=(-A) + options+=(-e "SHOW GLOBAL VARIABLES;") + + mysql "${options[@]}" 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -120,13 +297,20 @@ dump_mysql_meta() { log "LOCAL_TASKS - stop ${dump_file}" ## Schema only (no data) for each databases - databases=$(mysql_list_databases 3306) + databases=$(mysql_list_databases "${option_port}") for database in ${databases}; do local error_file="${errors_dir}/${database}.schema.err" local dump_file="${dump_dir}/${database}.schema.sql" log "LOCAL_TASKS - start ${dump_file}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --no-data --databases "${database}" 2> "${error_file}" > "${dump_file}" + declare -a options + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--port="${option_port}") + options+=(--force) + options+=(--no-data) + options+=(--databases "${database}") + + mysqldump "${options[@]}" 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -139,6 +323,13 @@ dump_mysql_meta() { log "LOCAL_TASKS - stop ${dump_file}" done } + +####################################################################### +# Dump "tabs style" separate schema/data for each database of an instance +# +# Arguments: +# --port=[Integer] (default: 3306) +####################################################################### dump_mysql_tabs() { databases=$(mysql_list_databases 3306) for database in ${databases}; do @@ -152,7 +343,63 @@ dump_mysql_tabs() { local error_file="${errors_dir}.err" log "LOCAL_TASKS - start ${dump_dir}" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 -Q --opt --events --hex-blob --skip-comments --fields-enclosed-by='\"' --fields-terminated-by=',' -T "${dump_dir}" "${database}" 2> "${error_file}" + local option_port="3306" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + declare -a options + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--port="${option_port}") + options+=(--force) + options+=(--quote-names) + options+=(--opt) + options+=(--events) + options+=(--hex-blob) + options+=(--skip-comments) + options+=(--fields-enclosed-by='\"') + options+=(--fields-terminated-by=',') + options+=(--tab="${dump_dir}") + options+=("${database}") + + mysqldump "${options[@]}" 2> "${error_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -165,62 +412,136 @@ dump_mysql_tabs() { log "LOCAL_TASKS - stop ${dump_dir}" done } -dump_mysql_hotcopy() { - # customize the list of databases to hot-copy - databases="" - for database in ${databases}; do - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-hotcopy/${database}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - local error_file="${errors_dir}.err" - log "LOCAL_TASKS - start ${dump_dir}" - - mysqlhotcopy "${database}" "${dump_dir}/" 2> "${error_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqlhotcopy to ${dump_dir} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_dir}" - done -} -dump_mysql_instances() { +####################################################################### +# Dump a single file for all databases of an instance +# using a custom authentication, instead of /etc/mysql/debian.cnf +# +# Arguments: +# --port=[Integer] (default: 3306) +# --user=[String] (default: ) +# --password=[String] (default: ) +####################################################################### +dump_mysql_instance() { local dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - mysql_user="mysqladmin" - mysql_passwd=$(grep -m1 'password = .*' /root/.my.cnf | cut -d " " -f 3) + local option_port="" + local option_user="" + local option_password="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac - # customize list of instances - instances="" - for instance in ${instances}; do - local error_file="${errors_dir}/${instance}.err" - local dump_file="${dump_dir}/${instance}.bak.gz" - log "LOCAL_TASKS - start ${dump_file}" - - mysqldump --port="${instance}" --opt --all-databases --hex-blob --user="${mysql_user}" --password="${mysql_passwd}" 2> "${error_file}" | gzip --best > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" + shift done + + declare -a options + options+=(--port="${option_port}") + options+=(--user="${option_user}") + options+=(--password="${option_password}") + options+=(--force) + options+=(--opt) + options+=(--all-databases) + options+=(--events) + options+=(--hex-blob) + + local error_file="${errors_dir}/${option_port}.err" + local dump_file="${dump_dir}/${option_port}.bak.gz" + log "LOCAL_TASKS - start ${dump_file}" + + mysqldump "${options[@]}" 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" } + +####################################################################### +# Dump a single file of all PostgreSQL databases +# +# Arguments: +####################################################################### dump_postgresql_global() { local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -255,6 +576,12 @@ dump_postgresql_global() { # # log "LOCAL_TASKS - stop ${dump_file}" } + +####################################################################### +# Dump a compressed file per database +# +# Arguments: +####################################################################### dump_postgresql_per_base() { local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -285,6 +612,14 @@ dump_postgresql_per_base() { done ) } + +####################################################################### +# Dump a compressed file per database +# +# Arguments: +# +# TODO: add arguments to include/exclude tables +####################################################################### dump_postgresql_filtered() { local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -312,9 +647,66 @@ dump_postgresql_filtered() { fi log "LOCAL_TASKS - stop ${dump_file}" } + +####################################################################### +# Copy dump file of Redis instances +# +# Arguments: +# --instances=[Integer] (default: all) +####################################################################### dump_redis() { - instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*') - for instance in ${instances}; do + all_instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*') + + local option_instances="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --instances) + # instances options, with key and value separated by space + if [ -n "$2" ]; then + if [ "${2}" == "all" ]; then + read -a option_instances <<< "${all_instances}" + else + IFS="," read -a option_instances <<< "${2}" + fi + shift + else + log_error "LOCAL_TASKS - '--instances' requires a non-empty option argument." + exit 1 + fi + ;; + --instances=?*) + # instances options, with key and value separated by = + if [ "${1#*=}" == "all" ]; then + read -a option_instances <<< "${all_instances}" + else + IFS="," read -a option_instances <<< "${1#*=}" + fi + ;; + --instances=) + # instances options, without value + log_error "LOCAL_TASKS - '--instances' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + for instance in "${option_instances[@]}"; do name=$(basename "${instance}") local dump_dir="${LOCAL_BACKUP_DIR}/${name}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -337,9 +729,20 @@ dump_redis() { rm -f "${error_file}" fi log "LOCAL_TASKS - stop ${dump_dir}" + else + log_error "LOCAL_TASKS - '${instance}/dump.rdb' not found." fi done } + +####################################################################### +# Dump all collections of a MongoDB database +# using a custom authentication, instead of /etc/mysql/debian.cnf +# +# Arguments: +# --user=[String] (default: ) +# --password=[String] (default: ) +####################################################################### dump_mongodb() { ## don't forget to create use with read-only access ## > use admin @@ -354,10 +757,73 @@ dump_mongodb() { local error_file="${errors_dir}.err" log "LOCAL_TASKS - start ${dump_dir}" - mongo_user="" - mongo_password="" + local option_user="" + local option_password="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac - mongodump -u "${mongo_user}" -p"${mongo_password}" -o "${dump_dir}/" 2> "${error_file}" > /dev/null + shift + done + + declare -a options + options+=(--username="${option_user}") + options+=(--password="${option_password}") + options+=(--out="${dump_dir}/") + + mongodump "${options[@]}" 2> "${error_file}" > /dev/null local last_rc=$? # shellcheck disable=SC2086 @@ -369,6 +835,12 @@ dump_mongodb() { fi log "LOCAL_TASKS - stop ${dump_dir}" } + +####################################################################### +# Dump MegaCLI configuration +# +# Arguments: +####################################################################### dump_megacli_config() { local dump_dir="${LOCAL_BACKUP_DIR}/megacli" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -392,6 +864,13 @@ dump_megacli_config() { fi log "LOCAL_TASKS - stop ${dump_file}" } + +####################################################################### +# Save some traceroute/mtr results +# +# Arguments: +# --targets=[IP,HOST] (default: ) +####################################################################### dump_traceroute() { local dump_dir="${LOCAL_BACKUP_DIR}/traceroute" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -399,32 +878,80 @@ dump_traceroute() { # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - network_targets="8.8.8.8 www.evolix.fr travaux.evolix.net" + local option_targets="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --targets) + # targets options, with key and value separated by space + if [ -n "$2" ]; then + IFS="," read -a option_targets <<< "${2}" + shift + else + log_error "LOCAL_TASKS - '--targets' requires a non-empty option argument." + exit 1 + fi + ;; + --targets=?*) + # targets options, with key and value separated by = + IFS="," read -a option_targets <<< "${1#*=}" + ;; + --targets=) + # targets options, without value + log_error "LOCAL_TASKS - '--targets' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done mtr_bin=$(command -v mtr) - if [ -n "${network_targets}" ] && [ -n "${mtr_bin}" ]; then - for addr in ${network_targets}; do - local dump_file="${dump_dir}/mtr-${addr}" + if [ -n "${mtr_bin}" ]; then + for target in "${option_targets[@]}"; do + local dump_file="${dump_dir}/mtr-${target}" log "LOCAL_TASKS - start ${dump_file}" - ${mtr_bin} -r "${addr}" > "${dump_file}" + ${mtr_bin} -r "${target}" > "${dump_file}" log "LOCAL_TASKS - stop ${dump_file}" done fi traceroute_bin=$(command -v traceroute) - if [ -n "${network_targets}" ] && [ -n "${traceroute_bin}" ]; then - for addr in ${network_targets}; do - local dump_file="${dump_dir}/traceroute-${addr}" + if [ -n "${traceroute_bin}" ]; then + for target in "${option_targets[@]}"; do + local dump_file="${dump_dir}/traceroute-${target}" log "LOCAL_TASKS - start ${dump_file}" - ${traceroute_bin} -n "${addr}" > "${dump_file}" 2>&1 + ${traceroute_bin} -n "${target}" > "${dump_file}" 2>&1 log "LOCAL_TASKS - stop ${dump_file}" done fi } + +####################################################################### +# Save many system information, using dump_server_state +# +# Arguments: +# +# TODO: pass arguments to the dump_server_state command +# with defaults and overrides (incuding "dump-dir") +####################################################################### dump_server_state() { local dump_dir="${LOCAL_BACKUP_DIR}/server-state" rm -rf "${dump_dir}" @@ -459,6 +986,14 @@ dump_server_state() { fi log "LOCAL_TASKS - stop ${dump_dir}" } + +####################################################################### +# Save RabbitMQ data +# +# Arguments: +# +# Warning: This has been poorly tested +####################################################################### dump_rabbitmq() { local dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -482,6 +1017,12 @@ dump_rabbitmq() { fi log "LOCAL_TASKS - stop ${dump_file}" } + +####################################################################### +# Save Files ACL on various partitions. +# +# Arguments: +####################################################################### dump_facl() { local dump_dir="${LOCAL_BACKUP_DIR}/facl" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") @@ -498,32 +1039,430 @@ dump_facl() { log "LOCAL_TASKS - stop ${dump_dir}" } -dump_elasticsearch_snapshot() { - log "LOCAL_TASKS - start dump_elasticsearch_snapshot" + +####################################################################### +# Snapshot Elasticsearch data (single-node cluster) +# +# Arguments: +# --protocol=[String] (default: http) +# --host=[String] (default: localhost) +# --port=[Integer] (default: 9200) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --repository=[String] (default: snaprepo) +# --snapshot=[String] (default: snapshot.daily) +####################################################################### +dump_elasticsearch_snapshot_singlenode() { + log "LOCAL_TASKS - start dump_elasticsearch_snapshot_singlenode" + + local option_protocol="http" + local option_host="localhost" + local option_port="9200" + local option_user="" + local option_password="" + local option_repository="snaprepo" + local option_snapshot="snapshot.daily" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --protocol) + # protocol options, with value separated by space + if [ -n "$2" ]; then + option_protocol="${2}" + shift + else + log_error "LOCAL_TASKS - '--protocol' requires a non-empty option argument." + exit 1 + fi + ;; + --protocol=?*) + # protocol options, with value separated by = + option_protocol="${1#*=}" + ;; + --protocol=) + # protocol options, without value + log_error "LOCAL_TASKS - '--protocol' requires a non-empty option argument." + exit 1 + ;; + --host) + # host options, with value separated by space + if [ -n "$2" ]; then + option_host="${2}" + shift + else + log_error "LOCAL_TASKS - '--host' requires a non-empty option argument." + exit 1 + fi + ;; + --host=?*) + # host options, with value separated by = + option_host="${1#*=}" + ;; + --host=) + # host options, without value + log_error "LOCAL_TASKS - '--host' requires a non-empty option argument." + exit 1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." + exit 1 + ;; + --repository) + # repository options, with value separated by space + if [ -n "$2" ]; then + option_repository="${2}" + shift + else + log_error "LOCAL_TASKS - '--repository' requires a non-empty option argument." + exit 1 + fi + ;; + --repository=?*) + # repository options, with value separated by = + option_repository="${1#*=}" + ;; + --repository=) + # repository options, without value + log_error "LOCAL_TASKS - '--repository' requires a non-empty option argument." + exit 1 + ;; + --snapshot) + # snapshot options, with value separated by space + if [ -n "$2" ]; then + option_snapshot="${2}" + shift + else + log_error "LOCAL_TASKS - '--snapshot' requires a non-empty option argument." + exit 1 + fi + ;; + --snapshot=?*) + # snapshot options, with value separated by = + option_snapshot="${1#*=}" + ;; + --snapshot=) + # snapshot options, without value + log_error "LOCAL_TASKS - '--snapshot' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done ## Take a snapshot as a backup. ## Warning: You need to have a path.repo configured. ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes - curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" - curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" + local base_url="${option_protocol}://${option_host}:${option_port}" + local snapshot_url="${base_url}/_snapshot/${option_repository}/${option_snapshot}" + + if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then + local option_auth="--user ${option_user}:${option_password}" + else + local option_auth="" + fi + + curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" + curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" # Clustered version here # It basically the same thing except that you need to check that NFS is mounted # if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2' # then - # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" - # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" + # curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" + # curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" # else # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' # fi - ## If you need to keep older snapshot, for example the last 10 daily snapshots, replace the XDELETE and XPUT lines by : - # for snapshot in $(curl -s -XGET "localhost:9200/_snapshot/snaprepo/_all?pretty=true" | grep -Eo 'snapshot_[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -n -10); do - # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/${snapshot}" | grep -v -Fx '{"acknowledged":true}' - # done - # date=$(/bin/date +%F) - # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot_${date}?wait_for_completion=true" >> "${LOGFILE}" + log "LOCAL_TASKS - stop dump_elasticsearch_snapshot_singlenode" +} - log "LOCAL_TASKS - stop dump_elasticsearch_snapshot" +####################################################################### +# Snapshot Elasticsearch data (multi-node cluster) +# +# Arguments: +# --protocol=[String] (default: http) +# --host=[String] (default: localhost) +# --port=[Integer] (default: 9200) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --repository=[String] (default: snaprepo) +# --snapshot=[String] (default: snapshot.daily) +# --nfs-server=[IP|HOST] (default: ) +####################################################################### +dump_elasticsearch_snapshot_multinode() { + log "LOCAL_TASKS - start dump_elasticsearch_snapshot_multinode" + + local option_protocol="http" + local option_host="localhost" + local option_port="9200" + local option_user="" + local option_password="" + local option_repository="snaprepo" + local option_snapshot="snapshot.daily" + local option_nfs_server="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case $1 in + --protocol) + # protocol options, with value separated by space + if [ -n "$2" ]; then + option_protocol="${2}" + shift + else + log_error "LOCAL_TASKS - '--protocol' requires a non-empty option argument." + exit 1 + fi + ;; + --protocol=?*) + # protocol options, with value separated by = + option_protocol="${1#*=}" + ;; + --protocol=) + # protocol options, without value + log_error "LOCAL_TASKS - '--protocol' requires a non-empty option argument." + exit 1 + ;; + --host) + # host options, with value separated by space + if [ -n "$2" ]; then + option_host="${2}" + shift + else + log_error "LOCAL_TASKS - '--host' requires a non-empty option argument." + exit 1 + fi + ;; + --host=?*) + # host options, with value separated by = + option_host="${1#*=}" + ;; + --host=) + # host options, without value + log_error "LOCAL_TASKS - '--host' requires a non-empty option argument." + exit 1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." + exit 1 + ;; + --repository) + # repository options, with value separated by space + if [ -n "$2" ]; then + option_repository="${2}" + shift + else + log_error "LOCAL_TASKS - '--repository' requires a non-empty option argument." + exit 1 + fi + ;; + --repository=?*) + # repository options, with value separated by = + option_repository="${1#*=}" + ;; + --repository=) + # repository options, without value + log_error "LOCAL_TASKS - '--repository' requires a non-empty option argument." + exit 1 + ;; + --snapshot) + # snapshot options, with value separated by space + if [ -n "$2" ]; then + option_snapshot="${2}" + shift + else + log_error "LOCAL_TASKS - '--snapshot' requires a non-empty option argument." + exit 1 + fi + ;; + --snapshot=?*) + # snapshot options, with value separated by = + option_snapshot="${1#*=}" + ;; + --snapshot=) + # snapshot options, without value + log_error "LOCAL_TASKS - '--snapshot' requires a non-empty option argument." + exit 1 + ;; + --nfs-server) + # nfs-server options, with value separated by space + if [ -n "$2" ]; then + option_nfs_server="${2}" + shift + else + log_error "LOCAL_TASKS - '--nfs-server' requires a non-empty option argument." + exit 1 + fi + ;; + --nfs-server=?*) + # nfs-server options, with value separated by = + option_nfs_server="${1#*=}" + ;; + --nfs-server=) + # nfs-server options, without value + log_error "LOCAL_TASKS - '--nfs-server' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + ## Take a snapshot as a backup. + ## Warning: You need to have a path.repo configured. + ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes + + local base_url="${option_protocol}://${option_host}:${option_port}" + local snapshot_url="${base_url}/_snapshot/${option_repository}/${option_snapshot}" + + if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then + local option_auth="--user ${option_user}:${option_password}" + else + local option_auth="" + fi + + # Clustered version here + # It basically the same thing except that you need to check that NFS is mounted + if ss | grep ':nfs' | grep -q -F "${option_nfs_server}"; then + curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" + curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" + else + echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' + fi + + log "LOCAL_TASKS - stop dump_elasticsearch_snapshot_multinode" } \ No newline at end of file diff --git a/client/zzz_evobackup.sh b/client/zzz_evobackup.sh index 3653d18..6b2dcba 100644 --- a/client/zzz_evobackup.sh +++ b/client/zzz_evobackup.sh @@ -137,22 +137,19 @@ local_tasks() { ########## MySQL ################## # Dump all grants (permissions), config variables and schema of databases - ### dump_mysql_meta + ### dump_mysql_meta [--port=3306] # Dump all databases in a single compressed file - ### dump_mysql_global + ### dump_mysql_global [--port=3306] [--masterdata] # Dump each database separately, in a compressed file - ### dump_mysql_per_base + ### dump_mysql_per_base [--port=3306] # Dump multiples instances, each in a single compressed file - ### dump_mysql_instances + ### dump_mysql_instance [--port=3306] # Dump each table in schema/data files, for all databases - ### dump_mysql_tabs - - # Run mysqlhotcopy for a specific database (must be configured) - # dump_mysql_hotcopy + ### dump_mysql_tabs [--port=3306] [--user=foo] [--password=123456789] ########## PostgreSQL ############# @@ -167,17 +164,20 @@ local_tasks() { ########## MongoDB ################ - ### dump_mongodb + ### dump_mongodb [--user=foo] [--password=123456789] ########## Redis ################## # Copy data file for all instances - ### dump_redis + ### dump_redis [--instances=] - ########## ElasticSearch ########## + ########## Elasticsearch ########## - # Trigger snapshots (must be configured) - ### dump_elasticsearch_snapshot + # Snapshot data for a single-node cluster + ### dump_elasticsearch_snapshot_singlenode [--protocol=http] [--host=localhost] [--port=9200] [--user=foo] [--password=123456789] [--repository=snaprepo] [--snapshot=snapshot.daily] + + # Snapshot data for a multi-node cluster + ### dump_elasticsearch_snapshot_multinode [--protocol=http] [--host=localhost] [--port=9200] [--user=foo] [--password=123456789] [--repository=snaprepo] [--snapshot=snapshot.daily] [--nfs-server=192.168.2.1] ########## RabbitMQ ############### @@ -191,7 +191,7 @@ local_tasks() { ########## Network ################ # Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) - dump_traceroute + dump_traceroute --targets=8.8.8.8,www.evolix.fr,travaux.evolix.net ########## Server state ########### From 1fa1eb77935b447362b85b7ed7f9db22f1264649 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 27 Feb 2023 14:56:45 +0100 Subject: [PATCH 049/105] Delete README containing dead links --- client/README.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 client/README.md diff --git a/client/README.md b/client/README.md deleted file mode 100644 index d1142f6..0000000 --- a/client/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Pour l'installation de `zzz_evobackup`, voir - -Pour `update-evobackup-canary`, voir \ No newline at end of file From feafe016924d1f12be043e1c6eb75a6afadc2949 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 8 Mar 2023 09:22:28 +0100 Subject: [PATCH 050/105] Delete error directories recursively --- client/lib/main.sh | 4 ++-- client/zzz_evobackup | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index b8723d0..52611a4 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -20,8 +20,8 @@ source "${LIBDIR}/dump.sh" local_tasks_wrapper() { log "START LOCAL_TASKS" - # Remove old log directories - find "${LOCAL_BACKUP_DIR}/" -type d -name "${PROGNAME}.errors-*" -ctime +30 -delete + # Remove old log directories (recursively) + find "${LOCAL_BACKUP_DIR}/" -type d -name "${PROGNAME}.errors-*" -ctime +30 -exec rm -rf \; local_tasks_type="$(type -t local_tasks)" if [ "${local_tasks_type}" = "function" ]; then diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 0419cae..41c5ff6 100755 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -114,7 +114,7 @@ lxc/*/rootfs/var/tmp local_tasks() { log "START LOCAL_TASKS" - # Remove old log directories + # Remove old log directories (recursively) find "${LOCAL_BACKUP_DIR}/" -type d -name "${PROGNAME}.errors-*" -ctime +30 -delete ################################################################### From 4475ee9af8a0aaab1d3f6f5353ce090149ad0b26 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 22 Mar 2023 14:10:11 +0100 Subject: [PATCH 051/105] dump.sh: improve options handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * default values, * reset variable each time * option masterdata seulement si présente --- client/lib/dump.sh | 76 +++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 9f1f3c2..6db0f9a 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -53,9 +53,9 @@ dump_mysql_global() { local option_port="3306" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --masterdata) - option_masterdata=1 + option_masterdata="--masterdata" ;; --port) # port options, with value separated by space @@ -95,14 +95,17 @@ dump_mysql_global() { done declare -a options - options+=("${option_masterdata}") + options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) options+=(--port="${option_port}") options+=(--opt) - options+=(--all-databases) options+=(--force) options+=(--events) options+=(--hex-blob) + options+=(--all-databases) + if [ -n "${option_masterdata}" ]; then + options+=("${option_masterdata}") + fi mysqldump "${options[@]}" 2> "${error_file}" | gzip --best > "${dump_file}" @@ -133,7 +136,7 @@ dump_mysql_per_base() { local option_port="3306" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --port) # port options, with value separated by space if [ -n "$2" ]; then @@ -172,6 +175,7 @@ dump_mysql_per_base() { done declare -a options + options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) options+=(--port="${option_port}") options+=(--force) @@ -214,7 +218,7 @@ dump_mysql_meta() { local option_port="3306" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --port) # port options, with value separated by space if [ -n "$2" ]; then @@ -258,6 +262,7 @@ dump_mysql_meta() { log "LOCAL_TASKS - start ${dump_file}" declare -a options + options=() options+=(--port "${option_port}") options+=(--flush) options+=(--no-header) @@ -280,8 +285,9 @@ dump_mysql_meta() { log "LOCAL_TASKS - start ${dump_file}" declare -a options + options=() options+=(--port="${option_port}") - options+=(-A) + options+=(--no-auto-rehash) options+=(-e "SHOW GLOBAL VARIABLES;") mysql "${options[@]}" 2> "${error_file}" > "${dump_file}" @@ -304,6 +310,7 @@ dump_mysql_meta() { log "LOCAL_TASKS - start ${dump_file}" declare -a options + options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) options+=(--port="${option_port}") options+=(--force) @@ -346,7 +353,7 @@ dump_mysql_tabs() { local option_port="3306" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --port) # port options, with value separated by space if [ -n "$2" ]; then @@ -386,6 +393,7 @@ dump_mysql_tabs() { done declare -a options + options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) options+=(--port="${option_port}") options+=(--force) @@ -434,7 +442,7 @@ dump_mysql_instance() { local option_password="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --port) # port options, with value separated by space if [ -n "$2" ]; then @@ -511,6 +519,7 @@ dump_mysql_instance() { done declare -a options + options=() options+=(--port="${option_port}") options+=(--user="${option_user}") options+=(--password="${option_password}") @@ -660,7 +669,7 @@ dump_redis() { local option_instances="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --instances) # instances options, with key and value separated by space if [ -n "$2" ]; then @@ -761,7 +770,7 @@ dump_mongodb() { local option_password="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --user) # user options, with value separated by space if [ -n "$2" ]; then @@ -819,6 +828,7 @@ dump_mongodb() { done declare -a options + options=() options+=(--username="${option_user}") options+=(--password="${option_password}") options+=(--out="${dump_dir}/") @@ -881,7 +891,7 @@ dump_traceroute() { local option_targets="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --targets) # targets options, with key and value separated by space if [ -n "$2" ]; then @@ -947,10 +957,9 @@ dump_traceroute() { ####################################################################### # Save many system information, using dump_server_state # -# Arguments: -# -# TODO: pass arguments to the dump_server_state command -# with defaults and overrides (incuding "dump-dir") +# Arguments: +# any option for dump-server-state (except --dump-dir) is usable +# (default: --all) ####################################################################### dump_server_state() { local dump_dir="${LOCAL_BACKUP_DIR}/server-state" @@ -961,27 +970,26 @@ dump_server_state() { log "LOCAL_TASKS - start ${dump_dir}" + # pass all options + read -a options <<< "${@}" + # if no option is given, use "--all" as fallback + if [ ${#options[@]} -le 0 ]; then + options=(--all) + fi + # add "--dump-dir" in case it is missing (as it should) + options+=(--dump-dir "${dump_dir}") + dump_server_state_bin=$(command -v dump-server-state) if [ -z "${dump_server_state_bin}" ]; then log_error "LOCAL_TASKS - dump-server-state is missing" rc=1 else - if [ "${SYSTEM}" = "linux" ]; then - ${dump_server_state_bin} --all --dump-dir "${dump_dir}" - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" - GLOBAL_RC=${E_DUMPFAILED} - fi - else - ${dump_server_state_bin} --all --dump-dir "${dump_dir}" - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" - GLOBAL_RC=${E_DUMPFAILED} - fi + ${dump_server_state_bin} "${options[@]}" + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" + GLOBAL_RC=${E_DUMPFAILED} fi fi log "LOCAL_TASKS - stop ${dump_dir}" @@ -1064,7 +1072,7 @@ dump_elasticsearch_snapshot_singlenode() { local option_snapshot="snapshot.daily" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --protocol) # protocol options, with value separated by space if [ -n "$2" ]; then @@ -1271,7 +1279,7 @@ dump_elasticsearch_snapshot_multinode() { local option_nfs_server="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do - case $1 in + case ${1:-''} in --protocol) # protocol options, with value separated by space if [ -n "$2" ]; then From 5aeba28d5c6cd4982a08a1c72190f2b3ab63fdae Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 22 Mar 2023 14:10:27 +0100 Subject: [PATCH 052/105] utilities.sh: fix line count --- client/lib/utilities.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh index 0f48077..4b07c86 100644 --- a/client/lib/utilities.sh +++ b/client/lib/utilities.sh @@ -16,7 +16,7 @@ log_error() { if [ -n "${error_file}" ] && [ -f "${error_file}" ]; then printf "\n### %s\n" "${error_msg}" >&2 # shellcheck disable=SC2046 - if [ $(wc -l "${error_file}") -gt 30 ]; then + if [ $(wc -l "${error_file}" | cut -d " " -f 1) -gt 30 ]; then printf "~~~{%s (tail -30)}\n" "${error_file}" >&2 tail -n 30 "${error_file}" >&2 else From 70e541dd6d23c430fe5bd775fffc2041a0a1df1f Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 22 Mar 2023 14:11:03 +0100 Subject: [PATCH 053/105] zzz_evobackup.sh: LIBDIR="/usr/local/lib/evobackup" --- client/zzz_evobackup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/zzz_evobackup.sh b/client/zzz_evobackup.sh index 6b2dcba..f6bf335 100644 --- a/client/zzz_evobackup.sh +++ b/client/zzz_evobackup.sh @@ -261,7 +261,7 @@ setup_custom() { ########## Libraries ################################################## # Change this to wherever you install the libraries -LIBDIR="./lib" +LIBDIR="/usr/local/lib/evobackup" source "${LIBDIR}/main.sh" From ea054f314c42cdaefba13661dd841b5fac232765 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 22 Mar 2023 14:17:10 +0100 Subject: [PATCH 054/105] Add some comments --- client/zzz_evobackup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/zzz_evobackup.sh b/client/zzz_evobackup.sh index f6bf335..9afe3d2 100644 --- a/client/zzz_evobackup.sh +++ b/client/zzz_evobackup.sh @@ -191,11 +191,13 @@ local_tasks() { ########## Network ################ # Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) + ### dump_traceroute --targets=host_or_ip[,host_or_ip] dump_traceroute --targets=8.8.8.8,www.evolix.fr,travaux.evolix.net ########## Server state ########### # Run dump-server-state to extract system information + ### dump-server-state [any dump-server-state option] dump_server_state # Dump file access control lists From a6573c6db324f0c699cf60e3cdc64b091ba9b7de Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 22 Mar 2023 14:17:42 +0100 Subject: [PATCH 055/105] changelog --- client/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index 06b5faa..d898560 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* split functions into libraries + ### Deprecated ### Removed From 5ac990473ebc26c5ed6de796771019ac73aafb4d Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 22 Mar 2023 14:19:29 +0100 Subject: [PATCH 056/105] remove monolithic script --- client/zzz_evobackup | 1232 ++++++--------------------------------- client/zzz_evobackup.sh | 272 --------- 2 files changed, 172 insertions(+), 1332 deletions(-) mode change 100755 => 100644 client/zzz_evobackup delete mode 100644 client/zzz_evobackup.sh diff --git a/client/zzz_evobackup b/client/zzz_evobackup old mode 100755 new mode 100644 index 41c5ff6..9afe3d2 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -14,115 +14,121 @@ # # Licence: AGPLv3 -VERSION="22.12" - -##### CONFIGURATION ################################################### +####################################################################### # -# 1. Set the following MAIL and SERVERS variables. -# 2. Customize the RSYNC_INCLUDES and RSYNC_EXCLUDES variables. -# 3. Enable or disable local tasks inside the local_tasks() function. +# You must configure the MAIL variable to receive notifications. # -# Some local tasks are configurable. -# If you enable them, have a look at their implementation. +# There is some optional configuration that you can do +# at the end of this script. # -# Some additional configuration variable can be customized -# at the end of the script, before invoking the main() function. +# The library (usually installed at /usr/local/lib/evobackup/main.sh) +# also has many variables that you can override for fine-tuning. # ####################################################################### -# email adress for notifications +# Email adress for notifications MAIL=jdoe@example.com -# list of hosts (hostname or IP) and SSH port for Rsync -SERVERS="node0.backup.example.com:2XXX node1.backup.example.com:2XXX" +####################################################################### +# +# The "sync_tasks" function will be called by the main function. +# +# You can customize the variables: +# * "sync_name" (String) +# * "SERVERS" (Array of HOST:PORT) +# * "RSYNC_INCLUDES" (Array of paths to include) +# * "RSYNC_EXCLUDES" (Array of paths to exclude) +# +# The "sync" function can be called multiple times +# with a different set of variables. +# That way you can to sync to various destinations. +# +####################################################################### -# We use /home/backup : feel free to use your own dir -LOCAL_BACKUP_DIR="/home/backup" +sync_tasks() { -# Enable/disable local tasks (default: enabled) -: "${LOCAL_TASKS:=1}" -# Enable/disable sync tasks (default: enabled) -: "${SYNC_TASKS:=1}" -# Enable/disable mtree (default: enabled) -: "${MTREE_ENABLED:=1}" + ########## System-only backup (to Evolix servers) ################# -# Source paths can be customized -# Empty lines, and lines containing # or ; are ignored -RSYNC_INCLUDES=" -/etc -/root -/var -/home -" + # Name your sync task, for logs + sync_name="evolix-system" -# Excluded paths can be customized -# Empty lines, and lines beginning with # or ; are ignored -RSYNC_EXCLUDES=" -/dev -/proc -/run -/sys -/tmp -/usr/doc -/usr/obj -/usr/share/doc -/usr/src -/var/apt -/var/cache -/var/db/munin/*.tmp -/var/lib/amavis/amavisd.sock -/var/lib/amavis/tmp -/var/lib/clamav/*.tmp -/var/lib/elasticsearch -/var/lib/metche -/var/lib/mongodb -/var/lib/munin/*tmp* -/var/lib/mysql -/var/lib/php/sessions -/var/lib/php5 -/var/lib/postgres -/var/lib/postgresql -/var/lib/sympa -/var/lock -/var/run -/var/spool/postfix -/var/spool/smtpd -/var/spool/squid -/var/state -/var/tmp -lost+found -.nfs.* -lxc/*/rootfs/tmp -lxc/*/rootfs/usr/doc -lxc/*/rootfs/usr/obj -lxc/*/rootfs/usr/share/doc -lxc/*/rootfs/usr/src -lxc/*/rootfs/var/apt -lxc/*/rootfs/var/cache -lxc/*/rootfs/var/lib/php5 -lxc/*/rootfs/var/lib/php/sessions -lxc/*/rootfs/var/lock -lxc/*/rootfs/var/run -lxc/*/rootfs/var/state -lxc/*/rootfs/var/tmp -/home/mysqltmp -" + # List of host/port for your sync task + # shellcheck disable=SC2034 + SERVERS=( + node0.backup.evolix.net:2234 + node1.backup.evolix.net:2234 + ) -##### FUNCTIONS ####################################################### + # What to include in your sync task + # Add or remove paths if you need + # shellcheck disable=SC2034 + RSYNC_INCLUDES=( + "${rsync_default_includes[@]}" + /etc + /root + /var + ) + + # What to exclude from your sync task + # Add or remove paths if you need + # shellcheck disable=SC2034 + RSYNC_EXCLUDES=( + "${rsync_default_excludes[@]}" + ) + + # Call the sync task + sync "${sync_name}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" + + + ########## Full backup (to client servers) ######################## + + # Name your sync task, for logs + sync_name="client-full" + + # List of host/port for your sync task + # shellcheck disable=SC2034 + SERVERS=( + client-backup00.evolix.net:2221 + client-backup01.evolix.net:2221 + ) + + # What to include in your sync task + # Add or remove paths if you need + # shellcheck disable=SC2034 + RSYNC_INCLUDES=( + "${rsync_default_includes[@]}" + /etc + /root + /var + /home + /srv + ) + + # What to exclude from your sync task + # Add or remove paths if you need + # shellcheck disable=SC2034 + RSYNC_EXCLUDES=( + "${rsync_default_excludes[@]}" + ) + + # Call the sync task + sync "${sync_name}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" + +} + +####################################################################### +# +# The "local_tasks" function will be called by the main function. +# +# You can call any available "dump_xxx" function +# (usually installed at /usr/local/lib/evobackup/dump.sh) +# +# You can also write some custom functions and call them. +# A "dump_custom" example is available further down. +# +####################################################################### -# Execute all local tasks: database dumps, system state dump… local_tasks() { - log "START LOCAL_TASKS" - - # Remove old log directories (recursively) - find "${LOCAL_BACKUP_DIR}/" -type d -name "${PROGNAME}.errors-*" -ctime +30 -delete - - ################################################################### - # You can enable/disable local tasks - # by (un)commenting calls to "dump_XXX" functions. - # - # You can also add your own functions and call them from here. - ################################################################### ########## OpenLDAP ############### @@ -131,22 +137,19 @@ local_tasks() { ########## MySQL ################## # Dump all grants (permissions), config variables and schema of databases - ### dump_mysql_meta + ### dump_mysql_meta [--port=3306] # Dump all databases in a single compressed file - ### dump_mysql_global + ### dump_mysql_global [--port=3306] [--masterdata] # Dump each database separately, in a compressed file - ### dump_mysql_per_base + ### dump_mysql_per_base [--port=3306] # Dump multiples instances, each in a single compressed file - ### dump_mysql_instances + ### dump_mysql_instance [--port=3306] # Dump each table in schema/data files, for all databases - ### dump_mysql_tabs - - # Run mysqlhotcopy for a specific database (must be configured) - # dump_mysql_hotcopy + ### dump_mysql_tabs [--port=3306] [--user=foo] [--password=123456789] ########## PostgreSQL ############# @@ -161,17 +164,20 @@ local_tasks() { ########## MongoDB ################ - ### dump_mongodb + ### dump_mongodb [--user=foo] [--password=123456789] ########## Redis ################## # Copy data file for all instances - ### dump_redis + ### dump_redis [--instances=] - ########## ElasticSearch ########## + ########## Elasticsearch ########## - # Trigger snapshots (must be configured) - ### dump_elasticsearch_snapshot + # Snapshot data for a single-node cluster + ### dump_elasticsearch_snapshot_singlenode [--protocol=http] [--host=localhost] [--port=9200] [--user=foo] [--password=123456789] [--repository=snaprepo] [--snapshot=snapshot.daily] + + # Snapshot data for a multi-node cluster + ### dump_elasticsearch_snapshot_multinode [--protocol=http] [--host=localhost] [--port=9200] [--user=foo] [--password=123456789] [--repository=snaprepo] [--snapshot=snapshot.daily] [--nfs-server=192.168.2.1] ########## RabbitMQ ############### @@ -185,976 +191,82 @@ local_tasks() { ########## Network ################ # Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) - dump_traceroute + ### dump_traceroute --targets=host_or_ip[,host_or_ip] + dump_traceroute --targets=8.8.8.8,www.evolix.fr,travaux.evolix.net ########## Server state ########### # Run dump-server-state to extract system information + ### dump-server-state [any dump-server-state option] dump_server_state # Dump file access control lists ### dump_facl - ################################################################### - - print_error_files_content - - log "STOP LOCAL_TASKS" + # No-op, in case nothing is enabled + : } -# Output error files content, if any -print_error_files_content() { - # Search for error files - error_files=$(find "${ERRORS_DIR}" -type f) - for error_file in ${error_files}; do - error_file_size=$(stat -c "%s" "${error_file}") +# This is an example for a custom dump function +# Uncomment, customize and call it from the "local_tasks" function +### dump_custom() { +### # Set dump and errors directories and files +### local dump_dir="${LOCAL_BACKUP_DIR}/custom" +### local dump_file="${dump_dir}/dump.gz" +### local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") +### local error_file="${errors_dir}/dump.err" +### +### # Reset dump and errors directories +### rm -rf "${dump_dir}" "${errors_dir}" +### # shellcheck disable=SC2174 +### mkdir -p -m 700 "${dump_dir}" "${errors_dir}" +### +### # Log the start of the command +### log "LOCAL_TASKS - start ${dump_file}" +### +### # Execute your dump command +### # Send errors to the error file and the data to the dump file +### my-dump-command 2> "${error_file}" > "${dump_file}" +### +### # Check result and deal with potential errors +### local last_rc=$? +### # shellcheck disable=SC2086 +### if [ ${last_rc} -ne 0 ]; then +### log_error "LOCAL_TASKS - my-dump-command to ${dump_file} returned an error ${last_rc}" "${error_file}" +### GLOBAL_RC=${E_DUMPFAILED} +### else +### rm -f "${error_file}" +### fi +### +### # Log the end of the command +### log "LOCAL_TASKS - stop ${dump_file}" +### } - # shellcheck disable=SC2086 - if [ ${error_file_size} -gt 0 ]; then - # if the file is not empty, display its content - printf "### cat %s ###\n" "${error_file}" >&2 - cat "${error_file}" >&2 - else - # if the file is empty, remove it - rm "${error_file}" - fi - done +########## Optional configuration ##################################### - # Search for remaining error_files - error_files_count=$(find "${ERRORS_DIR}" -type f | wc -l) - # If there is no error file, clean the parent directories - if [ "${error_files_count}" -eq 0 ]; then - rm -rf "${ERRORS_DIR}" - fi -} -# shellcheck disable=SC2317 -mysql_list_databases() { - port=${1:-"3306"} +setup_custom() { + # If you set a value (like "linux", "openbsd"…) it will be used, + # Default: uname(1) in lowercase. + ### SYSTEM="linux" - mysql --defaults-extra-file=/etc/mysql/debian.cnf --port="${port}" --execute="show databases" --silent --skip-column-names \ - | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" + # If you set a value it will be used, + # Default: hostname(1). + ### HOSTNAME="example-host" + + # Email subect for notifications + ### MAIL_SUBJECT="[info] EvoBackup - Client ${HOSTNAME}" + + # No-op in case nothing is executed + : } -# shellcheck disable=SC2317 -errors_dir_from_dump_dir() { - realpath --canonicalize-missing "${ERRORS_DIR}/$(realpath --relative-to="${LOCAL_BACKUP_DIR}" "${1}")" -} -# shellcheck disable=SC2317 -dump_ldap() { - ## OpenLDAP : example with slapcat - dump_dir="${LOCAL_BACKUP_DIR}/ldap" - rm -rf "${dump_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" +########## Libraries ################################################## - log "LOCAL_TASKS - start dump_ldap to ${dump_dir}" +# Change this to wherever you install the libraries +LIBDIR="/usr/local/lib/evobackup" - slapcat -n 0 -l "${dump_dir}/config.bak" - slapcat -n 1 -l "${dump_dir}/data.bak" - slapcat -l "${dump_dir}/all.bak" +source "${LIBDIR}/main.sh" - log "LOCAL_TASKS - stop dump_ldap" -} -# shellcheck disable=SC2317 -dump_mysql_global() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-global" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" +########## Let's go! ################################################## - error_file="${errors_dir}/mysql.bak.err" - dump_file="${dump_dir}/mysql.bak.gz" - log "LOCAL_TASKS - start ${dump_file}" - - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf -P 3306 --opt --all-databases --force --events --hex-blob 2> "${error_file}" | gzip --best > "${dump_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} -# shellcheck disable=SC2317 -dump_mysql_per_base() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - databases=$(mysql_list_databases 3306) - for database in ${databases}; do - error_file="${errors_dir}/${database}.err" - dump_file="${dump_dir}/${database}.sql.gz" - log "LOCAL_TASKS - start ${dump_file}" - - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --events --hex-blob "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done -} -# shellcheck disable=SC2317 -dump_mysql_meta() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - ## Dump all grants (requires 'percona-toolkit' package) - error_file="${errors_dir}/all_grants.err" - dump_file="${dump_dir}/all_grants.sql" - log "LOCAL_TASKS - start ${dump_file}" - - pt-show-grants --flush --no-header 2> "${error_file}" > "${dump_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - - ## Dump all variables - error_file="${errors_dir}/variables.err" - dump_file="${dump_dir}/variables.txt" - log "LOCAL_TASKS - start ${dump_file}" - - mysql -A -e "SHOW GLOBAL VARIABLES;" 2> "${error_file}" > "${dump_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysql 'show variables' returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - - ## Schema only (no data) for each databases - databases=$(mysql_list_databases 3306) - for database in ${databases}; do - error_file="${errors_dir}/${database}.schema.err" - dump_file="${dump_dir}/${database}.schema.sql" - log "LOCAL_TASKS - start ${dump_file}" - - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 --no-data --databases "${database}" 2> "${error_file}" > "${dump_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done -} -# shellcheck disable=SC2317 -dump_mysql_tabs() { - databases=$(mysql_list_databases 3306) - for database in ${databases}; do - dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs/${database}" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - chown -RL mysql "${dump_dir}" - - error_file="${errors_dir}.err" - log "LOCAL_TASKS - start ${dump_dir}" - - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --force -P 3306 -Q --opt --events --hex-blob --skip-comments --fields-enclosed-by='\"' --fields-terminated-by=',' -T "${dump_dir}" "${database}" 2> "${error_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_dir} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_dir}" - done -} -# shellcheck disable=SC2317 -dump_mysql_hotcopy() { - # customize the list of databases to hot-copy - databases="" - for database in ${databases}; do - dump_dir="${LOCAL_BACKUP_DIR}/mysql-hotcopy/${database}" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - error_file="${errors_dir}.err" - log "LOCAL_TASKS - start ${dump_dir}" - - mysqlhotcopy "${database}" "${dump_dir}/" 2> "${error_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqlhotcopy to ${dump_dir} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_dir}" - done -} -# shellcheck disable=SC2317 -dump_mysql_instances() { - dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - mysql_user="mysqladmin" - mysql_passwd=$(grep -m1 'password = .*' /root/.my.cnf | cut -d " " -f 3) - - # customize list of instances - instances="" - for instance in ${instances}; do - error_file="${errors_dir}/${instance}.err" - dump_file="${dump_dir}/${instance}.bak.gz" - log "LOCAL_TASKS - start ${dump_file}" - - mysqldump --port="${instance}" --opt --all-databases --hex-blob --user="${mysql_user}" --password="${mysql_passwd}" 2> "${error_file}" | gzip --best > "${dump_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done -} -# shellcheck disable=SC2317 -dump_postgresql_global() { - dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - ## example with pg_dumpall and with compression - dump_file="${dump_dir}/pg.dump.bak.gz" - log "LOCAL_TASKS - start ${dump_file}" - - (sudo -u postgres pg_dumpall) 2> "${error_file}" | gzip --best > "${dump_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pg_dumpall to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - - log "LOCAL_TASKS - stop ${dump_file}" - - ## example with pg_dumpall and without compression - ## WARNING: you need space in ~postgres - # dump_file="${dump_dir}/pg.dump.bak" - # log "LOCAL_TASKS - start ${dump_file}" - # - # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" - # mv ~postgres/pg.dump.bak "${dump_file}" - # - # log "LOCAL_TASKS - stop ${dump_file}" -} -# shellcheck disable=SC2317 -dump_postgresql_per_base() { - dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - ( - # shellcheck disable=SC2164 - cd /var/lib/postgresql - databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") - for database in ${databases} ; do - error_file="${errors_dir}/${database}.err" - dump_file="${dump_dir}/${database}.sql.gz" - log "LOCAL_TASKS - start ${dump_file}" - - (sudo -u postgres /usr/bin/pg_dump --create -s -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done - ) -} -# shellcheck disable=SC2317 -dump_postgresql_filtered() { - dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - error_file="${errors_dir}/pg-backup.err" - dump_file="${dump_dir}/pg-backup.tar" - log "LOCAL_TASKS - start ${dump_file}" - - ## example with all tables from MYBASE excepts TABLE1 and TABLE2 - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -t 'TABLE1' -t 'TABLE2' MYBASE 2> "${error_file}" - - ## example with only TABLE1 and TABLE2 from MYBASE - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -T 'TABLE1' -T 'TABLE2' MYBASE 2> "${error_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} -# shellcheck disable=SC2317 -dump_redis() { - instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*') - for instance in ${instances}; do - name=$(basename "${instance}") - dump_dir="${LOCAL_BACKUP_DIR}/${name}" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - if [ -f "${instance}/dump.rdb" ]; then - error_file="${errors_dir}/${instance}.err" - log "LOCAL_TASKS - start ${dump_dir}" - - cp -a "${instance}/dump.rdb" "${dump_dir}/" 2> "${error_file}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - cp ${instance}/dump.rdb to ${dump_dir} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_dir}" - fi - done -} -# shellcheck disable=SC2317 -dump_mongodb() { - ## don't forget to create use with read-only access - ## > use admin - ## > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) - - dump_dir="${LOCAL_BACKUP_DIR}/mongodump" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - error_file="${errors_dir}.err" - log "LOCAL_TASKS - start ${dump_dir}" - - mongo_user="" - mongo_password="" - - mongodump -u "${mongo_user}" -p"${mongo_password}" -o "${dump_dir}/" 2> "${error_file}" > /dev/null - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mongodump to ${dump_dir} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_dir}" -} -# shellcheck disable=SC2317 -dump_megacli_config() { - dump_dir="${LOCAL_BACKUP_DIR}/megacli" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - dump_file="${dump_dir}/megacli.cfg" - error_file="${errors_dir}/megacli.err" - log "LOCAL_TASKS - start ${dump_file}" - - megacli -CfgSave -f "${dump_file}" -a0 2> "${error_file}" > /dev/null - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - megacli to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} -# shellcheck disable=SC2317 -dump_traceroute() { - dump_dir="${LOCAL_BACKUP_DIR}/traceroute" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - network_targets="8.8.8.8 www.evolix.fr travaux.evolix.net" - - mtr_bin=$(command -v mtr) - if [ -n "${network_targets}" ] && [ -n "${mtr_bin}" ]; then - for addr in ${network_targets}; do - dump_file="${dump_dir}/mtr-${addr}" - log "LOCAL_TASKS - start ${dump_file}" - - ${mtr_bin} -r "${addr}" > "${dump_file}" - - log "LOCAL_TASKS - stop ${dump_file}" - done - fi - - traceroute_bin=$(command -v traceroute) - if [ -n "${network_targets}" ] && [ -n "${traceroute_bin}" ]; then - for addr in ${network_targets}; do - dump_file="${dump_dir}/traceroute-${addr}" - log "LOCAL_TASKS - start ${dump_file}" - - ${traceroute_bin} -n "${addr}" > "${dump_file}" 2>&1 - - log "LOCAL_TASKS - stop ${dump_file}" - done - fi -} -# shellcheck disable=SC2317 -dump_server_state() { - dump_dir="${LOCAL_BACKUP_DIR}/server-state" - rm -rf "${dump_dir}" - # Do not create the directory - # shellcheck disable=SC2174 - # mkdir -p -m 700 "${dump_dir}" - - log "LOCAL_TASKS - start ${dump_dir}" - - dump_server_state_bin=$(command -v dump-server-state) - if [ -z "${dump_server_state_bin}" ]; then - log_error "LOCAL_TASKS - dump-server-state is missing" - rc=1 - else - if [ "${SYSTEM}" = "linux" ]; then - ${dump_server_state_bin} --all --dump-dir "${dump_dir}" - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" - rc=${E_DUMPFAILED} - fi - else - ${dump_server_state_bin} --all --dump-dir "${dump_dir}" - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" - rc=${E_DUMPFAILED} - fi - fi - fi - log "LOCAL_TASKS - stop ${dump_dir}" -} -# shellcheck disable=SC2317 -dump_rabbitmq() { - dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - error_file="${errors_dir}.err" - dump_file="${dump_dir}/config" - log "LOCAL_TASKS - start ${dump_file}" - - rabbitmqadmin export "${dump_file}" 2> "${error_file}" >> "${LOGFILE}" - - last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" - rc=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} -# shellcheck disable=SC2317 -dump_facl() { - dump_dir="${LOCAL_BACKUP_DIR}/facl" - errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - log "LOCAL_TASKS - start ${dump_dir}" - - getfacl -R /etc > "${dump_dir}/etc.txt" - getfacl -R /home > "${dump_dir}/home.txt" - getfacl -R /usr > "${dump_dir}/usr.txt" - getfacl -R /var > "${dump_dir}/var.txt" - - log "LOCAL_TASKS - stop ${dump_dir}" -} -# shellcheck disable=SC2317 -dump_elasticsearch_snapshot() { - log "LOCAL_TASKS - start dump_elasticsearch_snapshot" - - ## Take a snapshot as a backup. - ## Warning: You need to have a path.repo configured. - ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes - - curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" - curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" - - # Clustered version here - # It basically the same thing except that you need to check that NFS is mounted - # if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2' - # then - # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" >> "${LOGFILE}" - # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" >> "${LOGFILE}" - # else - # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' - # fi - - ## If you need to keep older snapshot, for example the last 10 daily snapshots, replace the XDELETE and XPUT lines by : - # for snapshot in $(curl -s -XGET "localhost:9200/_snapshot/snaprepo/_all?pretty=true" | grep -Eo 'snapshot_[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -n -10); do - # curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/${snapshot}" | grep -v -Fx '{"acknowledged":true}' - # done - # date=$(/bin/date +%F) - # curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot_${date}?wait_for_completion=true" >> "${LOGFILE}" - - log "LOCAL_TASKS - stop dump_elasticsearch_snapshot" -} - -# Call test_server with "HOST:PORT" string -# It will return with 0 if the server is reachable. -# It will return with 1 and a message on stderr if not. -test_server() { - item=$1 - # split HOST and PORT from the input string - host=$(echo "${item}" | cut -d':' -f1) - port=$(echo "${item}" | cut -d':' -f2) - - # Test if the server is accepting connections - ssh -q -o "ConnectTimeout ${SSH_CONNECT_TIMEOUT}" "${host}" -p "${port}" -t "exit" - # shellcheck disable=SC2181 - if [ $? = 0 ]; then - # SSH connection is OK - return 0 - else - # SSH connection failed - new_error=$(printf "Failed to connect to \`%s' within %s seconds" "${item}" "${SSH_CONNECT_TIMEOUT}") - log "${new_error}" - SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d') - - return 1 - fi -} -# Call pick_server with an optional positive integer to get the nth server in the list. -pick_server() { - increment=${1:-0} - list_length=$(echo "${SERVERS}" | wc -w) - - if [ "${increment}" -ge "${list_length}" ]; then - # We've reached the end of the list - new_error="No more server available" - log "${new_error}" - SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d') - - # Log errors to stderr - printf "%s\\n" "${SERVERS_SSH_ERRORS}" >&2 - return 1 - fi - - # Extract the day of month, without leading 0 (which would give an octal based number) - today=$(/bin/date +%e) - # A salt is useful to randomize the starting point in the list - # but stay identical each time it's called for a server (based on hostname). - salt=$(hostname | cksum | cut -d' ' -f1) - # Pick an integer between 0 and the length of the SERVERS list - # It changes each day - item=$(( (today + salt + increment) % list_length )) - # cut starts counting fields at 1, not 0. - field=$(( item + 1 )) - - echo "${SERVERS}" | cut -d ' ' -f ${field} -} - -sync_tasks() { - n=0 - server="" - if [ "${SERVERS_FALLBACK}" = "1" ]; then - # We try to find a suitable server - while :; do - server=$(pick_server "${n}") - test $? = 0 || exit ${E_NOSRVAVAIL} - - if test_server "${server}"; then - break - else - server="" - n=$(( n + 1 )) - fi - done - else - # we force the server - server=$(pick_server "${n}") - fi - - SSH_SERVER=$(echo "${server}" | cut -d':' -f1) - SSH_PORT=$(echo "${server}" | cut -d':' -f2) - - log "START SYNC_TASKS - server=${server}" - - # default paths, depending on system - if [ "${SYSTEM}" = "linux" ]; then - rsync_default_includes=(/bin /boot /lib /opt /sbin /usr) - else - rsync_default_includes=(/bsd /bin /sbin /usr) - fi - if [ -f "${CANARY_FILE}" ]; then - rsync_default_includes+=("${CANARY_FILE}") - fi - - # reset Rsync log file - if [ -n "$(command -v truncate)" ]; then - truncate -s 0 "${RSYNC_LOGFILE}" - else - printf "" > "${RSYNC_LOGFILE}" - fi - - # Create a temp file for excludes and includes - rsync_includes_file="$(mktemp --tmpdir "${PROGNAME}.rsync-includes.XXXXXX")" - rsync_excludes_file="$(mktemp --tmpdir "${PROGNAME}.rsync-excludes.XXXXXX")" - # … and add them to the list of files to delete at exit - temp_files+=("${rsync_includes_file}") - temp_files+=("${rsync_excludes_file}") - - # Store includes/excludes in files - # without blank lines of comments (# or ;) - echo "${RSYNC_INCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${rsync_includes_file}" - echo "${RSYNC_EXCLUDES}" | sed -e 's/\s*\(#\|;\).*//; /^\s*$/d' > "${rsync_excludes_file}" - - if [ "${MTREE_ENABLED}" = "1" ]; then - mtree_bin=$(command -v mtree) - - if [ -n "${mtree_bin}" ]; then - # Dump filesystem stats with mtree - log "SYNC_TASKS - start mtree" - - mtree_files=() - - # Loop over Rsync includes - while read -r line ; do - # … but exclude for mtree what will be excluded by Rsync - mtree_excludes_file="$(mktemp --tmpdir "${PROGNAME}.mtree-excludes.XXXXXX")" - temp_files+=("${mtree_excludes_file}") - grep -E "^([^/]|${line})" "${rsync_excludes_file}" | sed -e "s|^${line}|.|" > "${mtree_excludes_file}" - - mtree_file="/var/log/evobackup.$(basename "${line}").mtree" - temp_files+=("${mtree_file}") - - ${mtree_bin} -x -c -p "${line}" -X "${mtree_excludes_file}" > "${mtree_file}" - mtree_files+=("${mtree_file}") - done < "${rsync_includes_file}" - - if [ "${#mtree_files[@]}" -le 0 ]; then - log_error "SYNC_TASKS - ERROR: mtree didn't produce any file" - fi - - log "SYNC_TASKS - stop mtree (files: ${mtree_files[*]})" - else - log "SYNC_TASKS - skip mtree (missing)" - fi - else - log "SYNC_TASKS - skip mtree (disabled)" - fi - - rsync_bin=$(command -v rsync) - # Build the final Rsync command - - # Rsync main options - rsync_main_args=() - rsync_main_args+=(--archive) - rsync_main_args+=(--itemize-changes) - rsync_main_args+=(--quiet) - rsync_main_args+=(--stats) - rsync_main_args+=(--human-readable) - rsync_main_args+=(--relative) - rsync_main_args+=(--partial) - rsync_main_args+=(--delete) - rsync_main_args+=(--delete-excluded) - rsync_main_args+=(--force) - rsync_main_args+=(--ignore-errors) - rsync_main_args+=(--log-file "${RSYNC_LOGFILE}") - rsync_main_args+=(--rsh "ssh -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") - - # Rsync excludes - while read -r line ; do - rsync_main_args+=(--exclude "${line}") - done < "${rsync_excludes_file}" - - # Rsync local sources - # Start with default includes - # shellcheck disable=SC2206 - rsync_main_args+=(${rsync_default_includes[@]}) - # … and add custom includes - while read -r line ; do - rsync_main_args+=("${line}") - done < "${rsync_includes_file}" - - # Rsync remote destination - rsync_main_args+=("root@${SSH_SERVER}:/var/backup/") - - # … log it - log "SYNC_TASKS - Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" - - # … execute it - ${rsync_bin} "${rsync_main_args[@]}" - - rsync_main_rc=$? - - # Copy last lines of rsync log to the main log - tail -n 30 "${RSYNC_LOGFILE}" >> "${LOGFILE}" - # Copy Rsync stats to special file - tail -n 30 "${RSYNC_LOGFILE}" | grep --invert-match --extended-regexp " [\<\>ch\.\*]\S{10} " > "${RSYNC_STATSFILE}" - - # We ignore rc=24 (vanished files) - if [ ${rsync_main_rc} -ne 0 ] && [ ${rsync_main_rc} -ne 24 ]; then - log_error "SYNC_TASKS - rsync returned an error ${rsync_main_rc}" "${LOGFILE}" - rc=${E_SYNCFAILED} - else - # Build the report Rsync command - rsync_report_args=() - # Rsync options - rsync_report_args+=(--rsh "ssh -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'") - # Rsync local source - if [ "${#mtree_files[@]}" -gt 0 ]; then - # send mtree files if there is any - rsync_report_args+=("${mtree_files[@]}") - fi - if [ -f "${RSYNC_LOGFILE}" ]; then - # send rsync full log file if it exists - rsync_report_args+=("${RSYNC_LOGFILE}") - fi - if [ -f "${RSYNC_STATSFILE}" ]; then - # send rsync stats log file if it exists - rsync_report_args+=("${RSYNC_STATSFILE}") - fi - # Rsync remote destination - rsync_report_args+=("root@${SSH_SERVER}:/var/log/") - - # … log it - log "SYNC_TASKS - Rsync report command : ${rsync_bin} ${rsync_report_args[*]}" - - # … execute it - ${rsync_bin} "${rsync_report_args[@]}" - fi - - log "STOP SYNC_TASKS - server=${server}" -} - -# Output a message to the log file -log() { - msg="${1:-$(cat /dev/stdin)}" - pid=$$ - printf "[%s] %s[%s]: %s\\n" \ - "$(/bin/date +"${DATE_FORMAT}")" "${PROGNAME}" "${pid}" "${msg}" \ - >> "${LOGFILE}" -} -log_error() { - error_msg=${1} - error_file=${2:""} - - if [ -n "${error_file}" ] && [ -f "${error_file}" ]; then - printf "\n### %s\n" "${error_msg}" >&2 - # shellcheck disable=SC2046 - if [ $(wc -l "${error_file}") -gt 30 ]; then - printf "~~~{%s (tail -30)}\n" "${error_file}" >&2 - tail -n 30 "${error_file}" >&2 - else - printf "~~~{%s}\n" "${error_file}" >&2 - cat "${error_file}" >&2 - fi - printf "~~~\n" >&2 - - log "${error_msg}, check ${error_file}" - else - printf "\n### %s\n" "${error_msg}" >&2 - - log "${error_msg}" - fi - -} -# Remove all temporary file created during the execution -# shellcheck disable=SC2317 -clean_temp_files() { - # shellcheck disable=SC2086 - rm -f "${temp_files[@]}" -} -main() { - START_EPOCH=$(/bin/date +%s) - START_TIME=$(/bin/date +"%Y%m%d%H%M%S") - - log "START GLOBAL - VERSION=${VERSION} LOCAL_TASKS=${LOCAL_TASKS} SYNC_TASKS=${SYNC_TASKS}" - - # shellcheck disable=SC2174 - mkdir -p -m 700 "${LOCAL_BACKUP_DIR}" - - ERRORS_DIR="${LOCAL_BACKUP_DIR}/${PROGNAME}.errors-${START_TIME}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${ERRORS_DIR}" - - ## Force umask - umask 077 - - ## Initialize variable to store SSH connection errors - SERVERS_SSH_ERRORS="" - - ## Verify other evobackup process and kill if needed - if [ -e "${PIDFILE}" ]; then - pid=$(cat "${PIDFILE}") - # Does process still exist ? - if kill -0 "${pid}" 2> /dev/null; then - # Killing the childs of evobackup. - for ppid in $(pgrep -P "${pid}"); do - kill -9 "${ppid}"; - done - # Then kill the main PID. - kill -9 "${pid}" - printf "%s is still running (PID %s). Process has been killed" "$0" "${pid}\\n" >&2 - else - rm -f "${PIDFILE}" - fi - fi - echo "$$" > "${PIDFILE}" - - # Initialize a list of files to delete at exit - # Any file added to the list will also be deleted at exit - temp_files=("${PIDFILE}") - - trap clean_temp_files EXIT - - # Update canary to keep track of each run - update-evobackup-canary --who "${PROGNAME}" --file "${CANARY_FILE}" - - if [ "${LOCAL_TASKS}" = "1" ]; then - local_tasks - fi - - if [ "${SYNC_TASKS}" = "1" ]; then - sync_tasks - fi - - STOP_EPOCH=$(/bin/date +%s) - - if [ "${SYSTEM}" = "openbsd" ]; then - start_time=$(/bin/date -f "%s" -j "${START_EPOCH}" +"${DATE_FORMAT}") - stop_time=$(/bin/date -f "%s" -j "${STOP_EPOCH}" +"${DATE_FORMAT}") - else - start_time=$(/bin/date --date="@${START_EPOCH}" +"${DATE_FORMAT}") - stop_time=$(/bin/date --date="@${STOP_EPOCH}" +"${DATE_FORMAT}") - fi - duration=$(( STOP_EPOCH - START_EPOCH )) - - log "STOP GLOBAL - start='${start_time}' stop='${stop_time}' duration=${duration}s" - - tail -20 "${LOGFILE}" | mail -s "[info] EvoBackup - Client ${HOSTNAME}" ${MAIL} -} - -# set all programs to C language (english) -export LC_ALL=C - -# If expansion is attempted on an unset variable or parameter, the shell prints an -# error message, and, if not interactive, exits with a non-zero status. -set -u -# The pipeline's return status is the value of the last (rightmost) command -# to exit with a non-zero status, or zero if all commands exit successfully. -set -o pipefail - -# Default return-code (0 == succes) -rc=0 - -# Possible error codes -E_NOSRVAVAIL=21 # No server is available -E_SYNCFAILED=20 # Faild sync task -E_DUMPFAILED=10 # Faild dump task - -# explicit PATH -PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin - -# You can set "linux" or "bsd" manually or let it choose automatically -SYSTEM=$(uname | tr '[:upper:]' '[:lower:]') - -# Hostname reported in the mail subject -HOSTNAME=$(hostname) - -# Store pid in a file named after this program's name -PROGNAME=$(basename "$0") -PIDFILE="/var/run/${PROGNAME}.pid" - -# Customize the log path if you want multiple scripts to have separate log files -LOGFILE="/var/log/evobackup.log" -# Rsync complete log file for the current run -RSYNC_LOGFILE="/var/log/${PROGNAME}.rsync.log" -# Rsync stats for the current run -RSYNC_STATSFILE="/var/log/${PROGNAME}.rsync-stats.log" -# Canary file to update before executing tasks -CANARY_FILE="/zzz_evobackup_canary" - -DATE_FORMAT="%Y-%m-%d %H:%M:%S" - -# Should we fallback on other servers when the first one is unreachable? -SERVERS_FALLBACK=${SERVERS_FALLBACK:-1} -# timeout (in seconds) for SSH connections -SSH_CONNECT_TIMEOUT=${SSH_CONNECT_TIMEOUT:-90} - -# execute main function -main - -exit ${rc} +main \ No newline at end of file diff --git a/client/zzz_evobackup.sh b/client/zzz_evobackup.sh deleted file mode 100644 index 9afe3d2..0000000 --- a/client/zzz_evobackup.sh +++ /dev/null @@ -1,272 +0,0 @@ -#!/bin/bash -# -# Script Evobackup client -# See https://gitea.evolix.org/evolix/evobackup -# -# Authors: Evolix , -# Gregory Colpart , -# Romain Dessort , -# Benoit Série , -# Tristan Pilat , -# Victor Laborie , -# Jérémy Lecour -# and others. -# -# Licence: AGPLv3 - -####################################################################### -# -# You must configure the MAIL variable to receive notifications. -# -# There is some optional configuration that you can do -# at the end of this script. -# -# The library (usually installed at /usr/local/lib/evobackup/main.sh) -# also has many variables that you can override for fine-tuning. -# -####################################################################### - -# Email adress for notifications -MAIL=jdoe@example.com - -####################################################################### -# -# The "sync_tasks" function will be called by the main function. -# -# You can customize the variables: -# * "sync_name" (String) -# * "SERVERS" (Array of HOST:PORT) -# * "RSYNC_INCLUDES" (Array of paths to include) -# * "RSYNC_EXCLUDES" (Array of paths to exclude) -# -# The "sync" function can be called multiple times -# with a different set of variables. -# That way you can to sync to various destinations. -# -####################################################################### - -sync_tasks() { - - ########## System-only backup (to Evolix servers) ################# - - # Name your sync task, for logs - sync_name="evolix-system" - - # List of host/port for your sync task - # shellcheck disable=SC2034 - SERVERS=( - node0.backup.evolix.net:2234 - node1.backup.evolix.net:2234 - ) - - # What to include in your sync task - # Add or remove paths if you need - # shellcheck disable=SC2034 - RSYNC_INCLUDES=( - "${rsync_default_includes[@]}" - /etc - /root - /var - ) - - # What to exclude from your sync task - # Add or remove paths if you need - # shellcheck disable=SC2034 - RSYNC_EXCLUDES=( - "${rsync_default_excludes[@]}" - ) - - # Call the sync task - sync "${sync_name}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" - - - ########## Full backup (to client servers) ######################## - - # Name your sync task, for logs - sync_name="client-full" - - # List of host/port for your sync task - # shellcheck disable=SC2034 - SERVERS=( - client-backup00.evolix.net:2221 - client-backup01.evolix.net:2221 - ) - - # What to include in your sync task - # Add or remove paths if you need - # shellcheck disable=SC2034 - RSYNC_INCLUDES=( - "${rsync_default_includes[@]}" - /etc - /root - /var - /home - /srv - ) - - # What to exclude from your sync task - # Add or remove paths if you need - # shellcheck disable=SC2034 - RSYNC_EXCLUDES=( - "${rsync_default_excludes[@]}" - ) - - # Call the sync task - sync "${sync_name}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" - -} - -####################################################################### -# -# The "local_tasks" function will be called by the main function. -# -# You can call any available "dump_xxx" function -# (usually installed at /usr/local/lib/evobackup/dump.sh) -# -# You can also write some custom functions and call them. -# A "dump_custom" example is available further down. -# -####################################################################### - -local_tasks() { - - ########## OpenLDAP ############### - - ### dump_ldap - - ########## MySQL ################## - - # Dump all grants (permissions), config variables and schema of databases - ### dump_mysql_meta [--port=3306] - - # Dump all databases in a single compressed file - ### dump_mysql_global [--port=3306] [--masterdata] - - # Dump each database separately, in a compressed file - ### dump_mysql_per_base [--port=3306] - - # Dump multiples instances, each in a single compressed file - ### dump_mysql_instance [--port=3306] - - # Dump each table in schema/data files, for all databases - ### dump_mysql_tabs [--port=3306] [--user=foo] [--password=123456789] - - ########## PostgreSQL ############# - - # Dump all databases in a single file (compressed or not) - ### dump_postgresql_global - - # Dump a specific databse with only some tables, or all but some tables (must be configured) - ### dump_postgresql_filtered - - # Dump each database separately, in a compressed file - ### dump_postgresql_per_base - - ########## MongoDB ################ - - ### dump_mongodb [--user=foo] [--password=123456789] - - ########## Redis ################## - - # Copy data file for all instances - ### dump_redis [--instances=] - - ########## Elasticsearch ########## - - # Snapshot data for a single-node cluster - ### dump_elasticsearch_snapshot_singlenode [--protocol=http] [--host=localhost] [--port=9200] [--user=foo] [--password=123456789] [--repository=snaprepo] [--snapshot=snapshot.daily] - - # Snapshot data for a multi-node cluster - ### dump_elasticsearch_snapshot_multinode [--protocol=http] [--host=localhost] [--port=9200] [--user=foo] [--password=123456789] [--repository=snaprepo] [--snapshot=snapshot.daily] [--nfs-server=192.168.2.1] - - ########## RabbitMQ ############### - - ### dump_rabbitmq - - ########## MegaCli ################ - - # Copy RAID config - ### dump_megacli_config - - ########## Network ################ - - # Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) - ### dump_traceroute --targets=host_or_ip[,host_or_ip] - dump_traceroute --targets=8.8.8.8,www.evolix.fr,travaux.evolix.net - - ########## Server state ########### - - # Run dump-server-state to extract system information - ### dump-server-state [any dump-server-state option] - dump_server_state - - # Dump file access control lists - ### dump_facl - - # No-op, in case nothing is enabled - : -} - -# This is an example for a custom dump function -# Uncomment, customize and call it from the "local_tasks" function -### dump_custom() { -### # Set dump and errors directories and files -### local dump_dir="${LOCAL_BACKUP_DIR}/custom" -### local dump_file="${dump_dir}/dump.gz" -### local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") -### local error_file="${errors_dir}/dump.err" -### -### # Reset dump and errors directories -### rm -rf "${dump_dir}" "${errors_dir}" -### # shellcheck disable=SC2174 -### mkdir -p -m 700 "${dump_dir}" "${errors_dir}" -### -### # Log the start of the command -### log "LOCAL_TASKS - start ${dump_file}" -### -### # Execute your dump command -### # Send errors to the error file and the data to the dump file -### my-dump-command 2> "${error_file}" > "${dump_file}" -### -### # Check result and deal with potential errors -### local last_rc=$? -### # shellcheck disable=SC2086 -### if [ ${last_rc} -ne 0 ]; then -### log_error "LOCAL_TASKS - my-dump-command to ${dump_file} returned an error ${last_rc}" "${error_file}" -### GLOBAL_RC=${E_DUMPFAILED} -### else -### rm -f "${error_file}" -### fi -### -### # Log the end of the command -### log "LOCAL_TASKS - stop ${dump_file}" -### } - -########## Optional configuration ##################################### - -setup_custom() { - # If you set a value (like "linux", "openbsd"…) it will be used, - # Default: uname(1) in lowercase. - ### SYSTEM="linux" - - # If you set a value it will be used, - # Default: hostname(1). - ### HOSTNAME="example-host" - - # Email subect for notifications - ### MAIL_SUBJECT="[info] EvoBackup - Client ${HOSTNAME}" - - # No-op in case nothing is executed - : -} - -########## Libraries ################################################## - -# Change this to wherever you install the libraries -LIBDIR="/usr/local/lib/evobackup" - -source "${LIBDIR}/main.sh" - -########## Let's go! ################################################## - -main \ No newline at end of file From 6f755b94ba6be02733d20ae5ba00d00c6ae5d0e5 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 22 Mar 2023 22:06:24 +0100 Subject: [PATCH 057/105] dump.sh reorganize backup directories --- client/lib/dump.sh | 152 ++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 6db0f9a..48df63e 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -39,15 +39,6 @@ dump_ldap() { # --port=[Integer] (default: 3306) ####################################################################### dump_mysql_global() { - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-global" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - local error_file="${errors_dir}/mysql.bak.err" - local dump_file="${dump_dir}/mysql.bak.gz" - log "LOCAL_TASKS - start ${dump_file}" local option_masterdata="" local option_port="3306" @@ -94,6 +85,16 @@ dump_mysql_global() { shift done + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}/mysql-global.err" + local dump_file="${dump_dir}/mysql-global.sql.gz" + log "LOCAL_TASKS - start ${dump_file}" + declare -a options options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) @@ -127,12 +128,6 @@ dump_mysql_global() { # --port=[Integer] (default: 3306) ####################################################################### dump_mysql_per_base() { - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - local option_port="3306" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do @@ -174,6 +169,12 @@ dump_mysql_per_base() { shift done + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + declare -a options options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) @@ -209,12 +210,6 @@ dump_mysql_per_base() { # --port=[Integer] (default: 3306) ####################################################################### dump_mysql_meta() { - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-meta" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - local option_port="3306" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do @@ -256,6 +251,12 @@ dump_mysql_meta() { shift done + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + ## Dump all grants (requires 'percona-toolkit' package) local error_file="${errors_dir}/all_grants.err" local dump_file="${dump_dir}/all_grants.sql" @@ -338,9 +339,50 @@ dump_mysql_meta() { # --port=[Integer] (default: 3306) ####################################################################### dump_mysql_tabs() { - databases=$(mysql_list_databases 3306) + local option_port="3306" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + databases=$(mysql_list_databases ${option_port}) for database in ${databases}; do - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs/${database}" + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}/${database}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 @@ -350,48 +392,6 @@ dump_mysql_tabs() { local error_file="${errors_dir}.err" log "LOCAL_TASKS - start ${dump_dir}" - local option_port="3306" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - declare -a options options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) @@ -431,12 +431,6 @@ dump_mysql_tabs() { # --password=[String] (default: ) ####################################################################### dump_mysql_instance() { - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-instances" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - local option_port="" local option_user="" local option_password="" @@ -518,6 +512,12 @@ dump_mysql_instance() { shift done + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + declare -a options options=() options+=(--port="${option_port}") @@ -529,8 +529,8 @@ dump_mysql_instance() { options+=(--events) options+=(--hex-blob) - local error_file="${errors_dir}/${option_port}.err" - local dump_file="${dump_dir}/${option_port}.bak.gz" + local error_file="${errors_dir}/mysql-global.err" + local dump_file="${dump_dir}/mysql-global.sql.gz" log "LOCAL_TASKS - start ${dump_file}" mysqldump "${options[@]}" 2> "${error_file}" | gzip --best > "${dump_file}" @@ -552,7 +552,7 @@ dump_mysql_instance() { # Arguments: ####################################################################### dump_postgresql_global() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 @@ -592,7 +592,7 @@ dump_postgresql_global() { # Arguments: ####################################################################### dump_postgresql_per_base() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 @@ -630,7 +630,7 @@ dump_postgresql_per_base() { # TODO: add arguments to include/exclude tables ####################################################################### dump_postgresql_filtered() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 From 0a485b30541267c10773f2b363fe4b35b30a8f83 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 23 Mar 2023 07:48:41 +0100 Subject: [PATCH 058/105] dump.sh reorganize backup directories, again --- client/lib/dump.sh | 247 ++++++++++++++++++--------------------------- 1 file changed, 98 insertions(+), 149 deletions(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 48df63e..16e8b95 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -1,5 +1,5 @@ #!/bin/bash -# shellcheck disable=SC2034,SC2317 +# shellcheck disable=SC2034,SC2317,SC2155 mysql_list_databases() { port=${1:-"3306"} @@ -39,7 +39,6 @@ dump_ldap() { # --port=[Integer] (default: 3306) ####################################################################### dump_mysql_global() { - local option_masterdata="" local option_port="3306" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a @@ -85,16 +84,17 @@ dump_mysql_global() { shift done - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}" + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-global-${option_port}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - local error_file="${errors_dir}/mysql-global.err" - local dump_file="${dump_dir}/mysql-global.sql.gz" + local error_file="${errors_dir}/mysqldump.err" + local dump_file="${dump_dir}/mysqldump.sql.gz" log "LOCAL_TASKS - start ${dump_file}" + ## Global all databases in one file declare -a options options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) @@ -119,6 +119,82 @@ dump_mysql_global() { rm -f "${error_file}" fi log "LOCAL_TASKS - stop ${dump_file}" + + ## Dump all grants (requires 'percona-toolkit' package) + if command -v pt-show-grants > /dev/null; then + local error_file="${errors_dir}/all_grants.err" + local dump_file="${dump_dir}/all_grants.sql" + log "LOCAL_TASKS - start ${dump_file}" + + declare -a options + options=() + options+=(--port "${option_port}") + options+=(--flush) + options+=(--no-header) + + pt-show-grants "${options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + fi + + ## Dump all variables + local error_file="${errors_dir}/variables.err" + local dump_file="${dump_dir}/variables.txt" + log "LOCAL_TASKS - start ${dump_file}" + + declare -a options + options=() + options+=(--port="${option_port}") + options+=(--no-auto-rehash) + options+=(-e "SHOW GLOBAL VARIABLES;") + + mysql "${options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysql 'show variables' returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + + ## Schema only (no data) for each databases + databases=$(mysql_list_databases "${option_port}") + for database in ${databases}; do + local error_file="${errors_dir}/${database}.schema.err" + local dump_file="${dump_dir}/${database}.schema.sql" + log "LOCAL_TASKS - start ${dump_file}" + + declare -a options + options=() + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--port="${option_port}") + options+=(--force) + options+=(--no-data) + options+=(--databases "${database}") + + mysqldump "${options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + done } ####################################################################### @@ -169,12 +245,6 @@ dump_mysql_per_base() { shift done - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - declare -a options options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) @@ -183,8 +253,14 @@ dump_mysql_per_base() { options+=(--events) options+=(--hex-blob) - databases=$(mysql_list_databases ${option_port}) + databases=$(mysql_list_databases "${option_port}") for database in ${databases}; do + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base-${option_port}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + local error_file="${errors_dir}/${database}.err" local dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" @@ -203,135 +279,6 @@ dump_mysql_per_base() { done } -####################################################################### -# Dump grants, variables and databases schemas for an instance -# -# Arguments: -# --port=[Integer] (default: 3306) -####################################################################### -dump_mysql_meta() { - local option_port="3306" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - - ## Dump all grants (requires 'percona-toolkit' package) - local error_file="${errors_dir}/all_grants.err" - local dump_file="${dump_dir}/all_grants.sql" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--port "${option_port}") - options+=(--flush) - options+=(--no-header) - - pt-show-grants "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - - ## Dump all variables - local error_file="${errors_dir}/variables.err" - local dump_file="${dump_dir}/variables.txt" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--port="${option_port}") - options+=(--no-auto-rehash) - options+=(-e "SHOW GLOBAL VARIABLES;") - - mysql "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysql 'show variables' returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - - ## Schema only (no data) for each databases - databases=$(mysql_list_databases "${option_port}") - for database in ${databases}; do - local error_file="${errors_dir}/${database}.schema.err" - local dump_file="${dump_dir}/${database}.schema.sql" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--port="${option_port}") - options+=(--force) - options+=(--no-data) - options+=(--databases "${database}") - - mysqldump "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done -} - ####################################################################### # Dump "tabs style" separate schema/data for each database of an instance # @@ -380,9 +327,9 @@ dump_mysql_tabs() { shift done - databases=$(mysql_list_databases ${option_port}) + databases=$(mysql_list_databases "${option_port}") for database in ${databases}; do - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}/${database}" + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs-${option_port}/${database}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 @@ -512,7 +459,7 @@ dump_mysql_instance() { shift done - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_port}" + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-instance-${option_port}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 @@ -552,14 +499,15 @@ dump_mysql_instance() { # Arguments: ####################################################################### dump_postgresql_global() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql" + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 mkdir -p -m 700 "${dump_dir}" "${errors_dir}" ## example with pg_dumpall and with compression - local dump_file="${dump_dir}/pg.dump.bak.gz" + local error_file="${errors_dir}/pg_dumpall.err" + local dump_file="${dump_dir}/pg_dumpall.sql.gz" log "LOCAL_TASKS - start ${dump_file}" (sudo -u postgres pg_dumpall) 2> "${error_file}" | gzip --best > "${dump_file}" @@ -577,7 +525,8 @@ dump_postgresql_global() { ## example with pg_dumpall and without compression ## WARNING: you need space in ~postgres - # local dump_file="${dump_dir}/pg.dump.bak" + # local error_file="${errors_dir}/pg_dumpall.err" + # local dump_file="${dump_dir}/pg_dumpall.sql" # log "LOCAL_TASKS - start ${dump_file}" # # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" @@ -592,7 +541,7 @@ dump_postgresql_global() { # Arguments: ####################################################################### dump_postgresql_per_base() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql" + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 @@ -630,7 +579,7 @@ dump_postgresql_per_base() { # TODO: add arguments to include/exclude tables ####################################################################### dump_postgresql_filtered() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql" + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" # shellcheck disable=SC2174 From 81177d63f5a2ec1ccfbae4f4f4151edc0b0151e2 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 23 Mar 2023 07:52:00 +0100 Subject: [PATCH 059/105] remov dump_mysql_meta --- client/zzz_evobackup | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 9afe3d2..824b630 100644 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -136,16 +136,14 @@ local_tasks() { ########## MySQL ################## - # Dump all grants (permissions), config variables and schema of databases - ### dump_mysql_meta [--port=3306] - - # Dump all databases in a single compressed file + # Dump all databases in a single compressed file, + # all grants (permissions), config variables and schema of databases ### dump_mysql_global [--port=3306] [--masterdata] # Dump each database separately, in a compressed file ### dump_mysql_per_base [--port=3306] - # Dump multiples instances, each in a single compressed file + # Dump specific instances, each in a single compressed file ### dump_mysql_instance [--port=3306] # Dump each table in schema/data files, for all databases From a5b553ab99460f349ad5793c1d3877e6796c1ea4 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 23 Mar 2023 08:47:37 +0100 Subject: [PATCH 060/105] dump: global/per-base schema along with data --- client/lib/dump.sh | 83 ++++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 16e8b95..667576c 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -169,32 +169,29 @@ dump_mysql_global() { log "LOCAL_TASKS - stop ${dump_file}" ## Schema only (no data) for each databases - databases=$(mysql_list_databases "${option_port}") - for database in ${databases}; do - local error_file="${errors_dir}/${database}.schema.err" - local dump_file="${dump_dir}/${database}.schema.sql" - log "LOCAL_TASKS - start ${dump_file}" + local error_file="${errors_dir}/mysqldump.schema.err" + local dump_file="${dump_dir}/mysqldump.schema.sql" + log "LOCAL_TASKS - start ${dump_file}" - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--port="${option_port}") - options+=(--force) - options+=(--no-data) - options+=(--databases "${database}") + declare -a options + options=() + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--port="${option_port}") + options+=(--force) + options+=(--no-data) + options+=(--all-databases) - mysqldump "${options[@]}" 2> "${error_file}" > "${dump_file}" + mysqldump "${options[@]}" 2> "${error_file}" > "${dump_file}" - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" } ####################################################################### @@ -245,6 +242,12 @@ dump_mysql_per_base() { shift done + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base-${option_port}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + # shellcheck disable=SC2174 + mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + declare -a options options=() options+=(--defaults-extra-file=/etc/mysql/debian.cnf) @@ -255,12 +258,6 @@ dump_mysql_per_base() { databases=$(mysql_list_databases "${option_port}") for database in ${databases}; do - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base-${option_port}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" - local error_file="${errors_dir}/${database}.err" local dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" @@ -277,6 +274,34 @@ dump_mysql_per_base() { fi log "LOCAL_TASKS - stop ${dump_file}" done + + ## Schema only (no data) for each databases + databases=$(mysql_list_databases "${option_port}") + for database in ${databases}; do + local error_file="${errors_dir}/${database}.schema.err" + local dump_file="${dump_dir}/${database}.schema.sql" + log "LOCAL_TASKS - start ${dump_file}" + + declare -a options + options=() + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--port="${option_port}") + options+=(--force) + options+=(--no-data) + options+=(--databases "${database}") + + mysqldump "${options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + done } ####################################################################### From 7379427051e05d425cdb74d7a9147c832c9c6405 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 31 Mar 2023 17:27:20 +0200 Subject: [PATCH 061/105] fix PG dump per base : dump everything and not only the schema --- client/lib/dump.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 667576c..4df00c5 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -581,7 +581,7 @@ dump_postgresql_per_base() { local dump_file="${dump_dir}/${database}.sql.gz" log "LOCAL_TASKS - start ${dump_file}" - (sudo -u postgres /usr/bin/pg_dump --create -s -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" + (sudo -u postgres /usr/bin/pg_dump --create -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 From d71fd5343f00ad1b18122fe75091188c4fe79c03 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 6 Jul 2023 09:06:21 +0200 Subject: [PATCH 062/105] Compress Redis dump --- client/lib/dump.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 4df00c5..a0298b7 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -701,7 +701,7 @@ dump_redis() { local error_file="${errors_dir}/${instance}.err" log "LOCAL_TASKS - start ${dump_dir}" - cp -a "${instance}/dump.rdb" "${dump_dir}/" 2> "${error_file}" + cp -a "${instance}/dump.rdb" "${dump_dir}/dump.rdb" 2> "${error_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -711,6 +711,18 @@ dump_redis() { else rm -f "${error_file}" fi + + gzip "${dump_dir}/dump.rdb" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - gzip ${dump_dir}/dump.rdb returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_dir}" else log_error "LOCAL_TASKS - '${instance}/dump.rdb' not found." From b4ab681061a5109464cef53b0b01720af764ff1e Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 6 Jul 2023 09:08:23 +0200 Subject: [PATCH 063/105] #71538 : fix Redis dump list in case directory is a symlink Cf. #fd9bb57f8bd0d2537d82e7e3cf4362e3da0538ea --- client/lib/dump.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index a0298b7..39a8d1f 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -638,7 +638,7 @@ dump_postgresql_filtered() { # --instances=[Integer] (default: all) ####################################################################### dump_redis() { - all_instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 -type d -name 'redis*') + all_instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 '(' -type d -o -type l ')' -name 'redis*') local option_instances="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a From 54a52d7b8f2ff52ae87433517c6d941178f54d11 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 6 Jul 2023 09:10:30 +0200 Subject: [PATCH 064/105] Exclude amavis virusmails directory from backup Cf. #981f5118cec40f560c1a743be8d9ef0509305c94 --- client/lib/main.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/client/lib/main.sh b/client/lib/main.sh index 52611a4..cd8afca 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -86,6 +86,7 @@ sync_tasks_wrapper() { /var/db/munin/*.tmp /var/lib/amavis/amavisd.sock /var/lib/amavis/tmp + /var/lib/amavis/virusmails /var/lib/clamav/*.tmp /var/lib/elasticsearch /var/lib/metche From c8f0781e75615736f8c4a9e2d43d77404c79a9c8 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 6 Jul 2023 09:12:33 +0200 Subject: [PATCH 065/105] whitespace --- client/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index d898560..e87f7e4 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security ## [22.12] + ### Changed * Use --dump-dir instead of --backup-dir to suppress dump-server-state warning From 03961156a8e747ba9af60b49ac1127dda6fc0cdd Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 6 Jul 2023 09:42:38 +0200 Subject: [PATCH 066/105] split dump_mysql_grants and dump_mysql_variables --- client/lib/dump.sh | 208 +++++++++++++++++++++++++++++++++---------- client/zzz_evobackup | 18 ++-- 2 files changed, 173 insertions(+), 53 deletions(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 39a8d1f..2261878 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -31,6 +31,158 @@ dump_ldap() { log "LOCAL_TASKS - stop dump_ldap" } + + +####################################################################### +# Dump config variables of an instance +# +# Arguments: +# --port=[Integer] (default: 3306) +####################################################################### +dump_mysql_variables() { + local option_port="3306" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + ## Dump all variables + local error_file="${errors_dir}/variables.err" + local dump_file="${dump_dir}/variables.txt" + log "LOCAL_TASKS - start ${dump_file}" + + declare -a options + options=() + options+=(--port="${option_port}") + options+=(--no-auto-rehash) + options+=(-e "SHOW GLOBAL VARIABLES;") + + mysql "${options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - mysql 'show variables' returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" +} + +####################################################################### +# Dump grants of an instance +# +# Arguments: +# --port=[Integer] (default: 3306) +####################################################################### +dump_mysql_grants() { + local option_port="3306" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + ## Dump all grants (requires 'percona-toolkit' package) + if command -v pt-show-grants > /dev/null; then + local error_file="${errors_dir}/all_grants.err" + local dump_file="${dump_dir}/all_grants.sql" + log "LOCAL_TASKS - start ${dump_file}" + + declare -a options + options=() + options+=(--port "${option_port}") + options+=(--flush) + options+=(--no-header) + + pt-show-grants "${options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${dump_file}" + else + log "LOCAL_TASKS - pt-show-grants not found, unable to dump grants" + fi +} + + + ####################################################################### # Dump a single compressed file of all databases of an instance # @@ -120,54 +272,6 @@ dump_mysql_global() { fi log "LOCAL_TASKS - stop ${dump_file}" - ## Dump all grants (requires 'percona-toolkit' package) - if command -v pt-show-grants > /dev/null; then - local error_file="${errors_dir}/all_grants.err" - local dump_file="${dump_dir}/all_grants.sql" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--port "${option_port}") - options+=(--flush) - options+=(--no-header) - - pt-show-grants "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - fi - - ## Dump all variables - local error_file="${errors_dir}/variables.err" - local dump_file="${dump_dir}/variables.txt" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--port="${option_port}") - options+=(--no-auto-rehash) - options+=(-e "SHOW GLOBAL VARIABLES;") - - mysql "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysql 'show variables' returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - ## Schema only (no data) for each databases local error_file="${errors_dir}/mysqldump.schema.err" local dump_file="${dump_dir}/mysqldump.schema.sql" @@ -192,6 +296,10 @@ dump_mysql_global() { rm -f "${error_file}" fi log "LOCAL_TASKS - stop ${dump_file}" + + dump_mysql_grants --port="${option_port}" + + dump_mysql_variables --port="${option_port}" } ####################################################################### @@ -302,6 +410,10 @@ dump_mysql_per_base() { fi log "LOCAL_TASKS - stop ${dump_file}" done + + dump_mysql_grants --port="${option_port}" + + dump_mysql_variables --port="${option_port}" } ####################################################################### diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 824b630..ce3d22c 100644 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -136,17 +136,25 @@ local_tasks() { ########## MySQL ################## - # Dump all databases in a single compressed file, - # all grants (permissions), config variables and schema of databases + # Dump all databases in a single compressed file + # and all grants (permissions), config variables and schema of databases ### dump_mysql_global [--port=3306] [--masterdata] # Dump each database separately, in a compressed file + # and all grants (permissions), config variables and schema of databases ### dump_mysql_per_base [--port=3306] - # Dump specific instances, each in a single compressed file - ### dump_mysql_instance [--port=3306] + # Dump a specific instance, in a single compressed file + # This is similar to "dump_mysql_global", but will custom authentication + ### dump_mysql_instance [--port=3306] [--user=foo] [--password=123456789] - # Dump each table in schema/data files, for all databases + # Dump only grants of an instance + ### dump_mysql_grants [--port=3306] + + # Dump only variables of an instance + ### dump_mysql_variables [--port=3306] + + # Dump each table in schema/data files ### dump_mysql_tabs [--port=3306] [--user=foo] [--password=123456789] ########## PostgreSQL ############# From f0f177bae95bf7308f839b912cd268d6947db40f Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 6 Jul 2023 09:47:39 +0200 Subject: [PATCH 067/105] wording --- client/zzz_evobackup | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index ce3d22c..85e610f 100644 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -251,15 +251,13 @@ local_tasks() { ########## Optional configuration ##################################### setup_custom() { - # If you set a value (like "linux", "openbsd"…) it will be used, - # Default: uname(1) in lowercase. - ### SYSTEM="linux" + # System name ("linux" and "openbsd" currently supported) + ### SYSTEM="$(uname)" - # If you set a value it will be used, - # Default: hostname(1). - ### HOSTNAME="example-host" + # Host name for logs and notifications + ### HOSTNAME="$(hostname)" - # Email subect for notifications + # Email subject for notifications ### MAIL_SUBJECT="[info] EvoBackup - Client ${HOSTNAME}" # No-op in case nothing is executed From c3f238b3c6c0866b7ebf5fe6ba7c14e59dbf4bb8 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 6 Jul 2023 10:25:15 +0200 Subject: [PATCH 068/105] VERSION="23.07-pre" --- client/lib/main.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index cd8afca..bcb2218 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -1,7 +1,7 @@ #!/bin/bash # shellcheck disable=SC2034,SC2317 -readonly VERSION="23.1-pre" +readonly VERSION="23.07-pre" # set all programs to C language (english) export LC_ALL=C From 68daa1ebf2918f48b00a06d87cc1801a23bd29e9 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 6 Jul 2023 10:26:01 +0200 Subject: [PATCH 069/105] deploy-evobackup-beta : fix various issues * bad permissions * hard-coded path for cron job * Ansible syntax for booleans --- client/deploy-evobackup-beta.yml | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/client/deploy-evobackup-beta.yml b/client/deploy-evobackup-beta.yml index a00df7c..d716164 100644 --- a/client/deploy-evobackup-beta.yml +++ b/client/deploy-evobackup-beta.yml @@ -1,8 +1,12 @@ +### +# ansible-playbook deploy-evobackup-beta.yml -K --diff -l HOSTNAME --check +# --- -- hosts: all - gather_facts: yes - become: yes +- name: Deploy a beta version of evobackup + hosts: all + gather_facts: true + become: true vars: evobackup_script_path: /etc/cron.daily/zzz_evobackup_beta @@ -13,37 +17,40 @@ - name: LIBDIR is present file: path: "{{ evobackup_libdir }}" + owner: root + group: root + mode: "0755" state: directory - name: libraries are installed copy: src: "{{ item }}" dest: "{{ evobackup_libdir }}/" - remote_src: False + remote_src: false owner: root group: root - mode: "0640" - force: yes + mode: "0644" + force: true loop: "{{ lookup('fileglob', 'lib/*.sh', wantlist=True) }}" - name: script is present copy: - src: zzz_evobackup.sh + src: zzz_evobackup dest: "{{ evobackup_script_path }}" - remote_src: False + remote_src: false owner: root group: root mode: "0750" - force: no + force: false - name: Email is customized replace: - dest: /etc/cron.daily/zzz_evobackup + dest: "{{ evobackup_script_path }}" regexp: "^MAIL=.*" replace: "MAIL={{ evobackup_mail }}" - name: LIBDIR is customized replace: - dest: /etc/cron.daily/zzz_evobackup + dest: "{{ evobackup_script_path }}" regexp: "^LIBDIR=.*" replace: "LIBDIR=\"{{ evobackup_libdir }}\"" \ No newline at end of file From 32d41a4564b5c386a664722ae7dfecb28430c79b Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 23 Nov 2023 09:03:05 +0100 Subject: [PATCH 070/105] dump_redis: fix error_dir --- client/lib/dump.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 2261878..39884d9 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -810,7 +810,7 @@ dump_redis() { mkdir -p -m 700 "${dump_dir}" "${errors_dir}" if [ -f "${instance}/dump.rdb" ]; then - local error_file="${errors_dir}/${instance}.err" + local error_file="${errors_dir}/${name}.err" log "LOCAL_TASKS - start ${dump_dir}" cp -a "${instance}/dump.rdb" "${dump_dir}/dump.rdb" 2> "${error_file}" From d4ad3b735fda7714da7983eda9b4c8ef8717f0d3 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 23 Nov 2023 09:03:25 +0100 Subject: [PATCH 071/105] split mkdir and chmod --- client/lib/dump.sh | 57 +++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 39884d9..34d2d9f 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -19,8 +19,8 @@ dump_ldap() { ## OpenLDAP : example with slapcat local dump_dir="${LOCAL_BACKUP_DIR}/ldap" rm -rf "${dump_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" + mkdir -p "${dump_dir}" + chmod -R 700 "${dump_dir}" log "LOCAL_TASKS - start dump_ldap to ${dump_dir}" @@ -239,8 +239,8 @@ dump_mysql_global() { local dump_dir="${LOCAL_BACKUP_DIR}/mysql-global-${option_port}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" local error_file="${errors_dir}/mysqldump.err" local dump_file="${dump_dir}/mysqldump.sql.gz" @@ -353,8 +353,8 @@ dump_mysql_per_base() { local dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base-${option_port}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" declare -a options options=() @@ -469,8 +469,8 @@ dump_mysql_tabs() { local dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs-${option_port}/${database}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" chown -RL mysql "${dump_dir}" local error_file="${errors_dir}.err" @@ -599,8 +599,8 @@ dump_mysql_instance() { local dump_dir="${LOCAL_BACKUP_DIR}/mysql-instance-${option_port}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" declare -a options options=() @@ -639,8 +639,8 @@ dump_postgresql_global() { local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" ## example with pg_dumpall and with compression local error_file="${errors_dir}/pg_dumpall.err" @@ -681,8 +681,8 @@ dump_postgresql_per_base() { local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" ( # shellcheck disable=SC2164 @@ -719,8 +719,8 @@ dump_postgresql_filtered() { local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" local error_file="${errors_dir}/pg-backup.err" local dump_file="${dump_dir}/pg-backup.tar" @@ -806,8 +806,8 @@ dump_redis() { local dump_dir="${LOCAL_BACKUP_DIR}/${name}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" if [ -f "${instance}/dump.rdb" ]; then local error_file="${errors_dir}/${name}.err" @@ -858,8 +858,8 @@ dump_mongodb() { local dump_dir="${LOCAL_BACKUP_DIR}/mongodump" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" local error_file="${errors_dir}.err" log "LOCAL_TASKS - start ${dump_dir}" @@ -953,8 +953,8 @@ dump_megacli_config() { local dump_dir="${LOCAL_BACKUP_DIR}/megacli" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" local dump_file="${dump_dir}/megacli.cfg" local error_file="${errors_dir}/megacli.err" @@ -983,8 +983,8 @@ dump_traceroute() { local dump_dir="${LOCAL_BACKUP_DIR}/traceroute" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" local option_targets="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a @@ -1063,7 +1063,6 @@ dump_server_state() { local dump_dir="${LOCAL_BACKUP_DIR}/server-state" rm -rf "${dump_dir}" # Do not create the directory - # shellcheck disable=SC2174 # mkdir -p -m 700 "${dump_dir}" log "LOCAL_TASKS - start ${dump_dir}" @@ -1104,8 +1103,8 @@ dump_rabbitmq() { local dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" local error_file="${errors_dir}.err" local dump_file="${dump_dir}/config" @@ -1133,8 +1132,8 @@ dump_facl() { local dump_dir="${LOCAL_BACKUP_DIR}/facl" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" - # shellcheck disable=SC2174 - mkdir -p -m 700 "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + chmod -R 700 "${dump_dir}" "${errors_dir}" log "LOCAL_TASKS - start ${dump_dir}" From 3268ff08586a0e7ad3e0f56bb0d4e9a0b4540e6b Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 28 Dec 2023 15:23:52 +0100 Subject: [PATCH 072/105] Add note for includes/excludes brace expansion --- client/lib/main.sh | 42 ++++++++++++++++++++++++------------------ client/zzz_evobackup | 6 +++++- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index bcb2218..6027505 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -44,6 +44,8 @@ sync_tasks_wrapper() { case "${SYSTEM}" in linux) + # NOTE: remember to single-quote paths if they contain globs (*) + # and you want to defer expansion declare -a rsync_default_includes=( /bin /boot @@ -54,6 +56,8 @@ sync_tasks_wrapper() { ) ;; *bsd) + # NOTE: remember to single-quote paths if they contain globs (*) + # and you want to defer expansion declare -a rsync_default_includes=( /bin /bsd @@ -71,6 +75,8 @@ sync_tasks_wrapper() { fi readonly rsync_default_includes + # NOTE: remember to single-quote paths if they contain globs (*) + # and you want to defer expansion declare -a rsync_default_excludes=( /dev /proc @@ -83,15 +89,15 @@ sync_tasks_wrapper() { /usr/src /var/apt /var/cache - /var/db/munin/*.tmp + '/var/db/munin/*.tmp' /var/lib/amavis/amavisd.sock /var/lib/amavis/tmp /var/lib/amavis/virusmails - /var/lib/clamav/*.tmp + '/var/lib/clamav/*.tmp' /var/lib/elasticsearch /var/lib/metche /var/lib/mongodb - /var/lib/munin/*tmp* + '/var/lib/munin/*tmp*' /var/lib/mysql /var/lib/php/sessions /var/lib/php5 @@ -106,20 +112,20 @@ sync_tasks_wrapper() { /var/state /var/tmp lost+found - .nfs.* - lxc/*/rootfs/tmp - lxc/*/rootfs/usr/doc - lxc/*/rootfs/usr/obj - lxc/*/rootfs/usr/share/doc - lxc/*/rootfs/usr/src - lxc/*/rootfs/var/apt - lxc/*/rootfs/var/cache - lxc/*/rootfs/var/lib/php5 - lxc/*/rootfs/var/lib/php/sessions - lxc/*/rootfs/var/lock - lxc/*/rootfs/var/run - lxc/*/rootfs/var/state - lxc/*/rootfs/var/tmp + '.nfs.*' + 'lxc/*/rootfs/tmp' + 'lxc/*/rootfs/usr/doc' + 'lxc/*/rootfs/usr/obj' + 'lxc/*/rootfs/usr/share/doc' + 'lxc/*/rootfs/usr/src' + 'lxc/*/rootfs/var/apt' + 'lxc/*/rootfs/var/cache' + 'lxc/*/rootfs/var/lib/php5' + 'lxc/*/rootfs/var/lib/php/sessions' + 'lxc/*/rootfs/var/lock' + 'lxc/*/rootfs/var/run' + 'lxc/*/rootfs/var/state' + 'lxc/*/rootfs/var/tmp' /home/mysqltmp ) readonly rsync_default_excludes @@ -443,4 +449,4 @@ main() { send_mail exit ${GLOBAL_RC} -} \ No newline at end of file +} diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 85e610f..ae0e4ef 100644 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -94,6 +94,8 @@ sync_tasks() { # What to include in your sync task # Add or remove paths if you need + # NOTE: remember to single-quote paths if they contain globs (*) + # and you want to defer expansion # shellcheck disable=SC2034 RSYNC_INCLUDES=( "${rsync_default_includes[@]}" @@ -106,6 +108,8 @@ sync_tasks() { # What to exclude from your sync task # Add or remove paths if you need + # NOTE: remember to single-quote paths if they contain globs (*) + # and you want to defer expansion # shellcheck disable=SC2034 RSYNC_EXCLUDES=( "${rsync_default_excludes[@]}" @@ -273,4 +277,4 @@ source "${LIBDIR}/main.sh" ########## Let's go! ################################################## -main \ No newline at end of file +main From 430e4f52ac8ba484dd99a9c64005f15a34d97162 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 11:32:23 +0100 Subject: [PATCH 073/105] Remove empty error directories at exit --- client/lib/dump.sh | 14 +++++++++++++- client/lib/main.sh | 2 +- client/lib/utilities.sh | 3 ++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client/lib/dump.sh b/client/lib/dump.sh index 34d2d9f..b099fe2 100644 --- a/client/lib/dump.sh +++ b/client/lib/dump.sh @@ -101,6 +101,7 @@ dump_mysql_variables() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_file}" } @@ -174,6 +175,7 @@ dump_mysql_grants() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_file}" else @@ -294,6 +296,7 @@ dump_mysql_global() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_file}" @@ -500,6 +503,7 @@ dump_mysql_tabs() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_dir}" done @@ -626,6 +630,7 @@ dump_mysql_instance() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_file}" } @@ -702,6 +707,7 @@ dump_postgresql_per_base() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_file}" done @@ -739,6 +745,7 @@ dump_postgresql_filtered() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_file}" } @@ -822,6 +829,7 @@ dump_redis() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi gzip "${dump_dir}/dump.rdb" @@ -833,6 +841,7 @@ dump_redis() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_dir}" @@ -940,6 +949,7 @@ dump_mongodb() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_dir}" } @@ -969,6 +979,7 @@ dump_megacli_config() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_file}" } @@ -1119,6 +1130,7 @@ dump_rabbitmq() { GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" + rmdir --ignore-fail-on-non-empty "${errors_dir}" fi log "LOCAL_TASKS - stop ${dump_file}" } @@ -1570,4 +1582,4 @@ dump_elasticsearch_snapshot_multinode() { fi log "LOCAL_TASKS - stop dump_elasticsearch_snapshot_multinode" -} \ No newline at end of file +} diff --git a/client/lib/main.sh b/client/lib/main.sh index 6027505..be0ff1e 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -401,7 +401,7 @@ setup() { # Initialize a list of temporary files declare -a TEMP_FILES=() # Any file in this list will be deleted when the program exits - trap "clean_temp_files" EXIT + trap "cleanup" EXIT } diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh index 4b07c86..af0a7f4 100644 --- a/client/lib/utilities.sh +++ b/client/lib/utilities.sh @@ -37,9 +37,10 @@ add_to_temp_files() { TEMP_FILES+=("${1}") } # Remove all temporary file created during the execution -clean_temp_files() { +cleanup() { # shellcheck disable=SC2086 rm -f "${TEMP_FILES[@]}" + find "${ERRORS_DIR}" -type d -empty -delete } enforce_single_process() { local pidfile=$1 From 5840e8efeee23d6b9f82b3cfad86d510e31cc53b Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 13:49:08 +0100 Subject: [PATCH 074/105] whitespaces --- client/lib/utilities.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh index af0a7f4..476ffb9 100644 --- a/client/lib/utilities.sh +++ b/client/lib/utilities.sh @@ -134,4 +134,4 @@ pick_server() { send_mail() { tail -20 "${LOGFILE}" | mail -s "${MAIL_SUBJECT}" "${MAIL}" -} \ No newline at end of file +} From b7ce6e1cff5037324841366dde2621e0f9bc06bc Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 13:50:51 +0100 Subject: [PATCH 075/105] Rewrite mysql dump functions and reorganize code --- client/lib/dump-misc.sh | 866 ++++++++++++++++++ client/lib/dump-mysql.sh | 1076 ++++++++++++++++++++++ client/lib/dump-postgresql.sh | 118 +++ client/lib/dump.sh | 1585 --------------------------------- client/lib/main.sh | 24 +- client/zzz_evobackup | 131 ++- 6 files changed, 2175 insertions(+), 1625 deletions(-) create mode 100644 client/lib/dump-misc.sh create mode 100644 client/lib/dump-mysql.sh create mode 100644 client/lib/dump-postgresql.sh delete mode 100644 client/lib/dump.sh diff --git a/client/lib/dump-misc.sh b/client/lib/dump-misc.sh new file mode 100644 index 0000000..e90a1b6 --- /dev/null +++ b/client/lib/dump-misc.sh @@ -0,0 +1,866 @@ +#!/bin/bash +# shellcheck disable=SC2034,SC2317,SC2155 + +####################################################################### +# Dump LDAP files (config, data, all) +# +# Arguments: +####################################################################### +dump_ldap() { + ## OpenLDAP : example with slapcat + local dump_dir="${LOCAL_BACKUP_DIR}/ldap" + rm -rf "${dump_dir}" + mkdir -p "${dump_dir}" + chmod 700 "${dump_dir}" + + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${FUNCNAME[0]} to ${dump_dir}" + + slapcat -n 0 -l "${dump_dir}/config.bak" + slapcat -n 1 -l "${dump_dir}/data.bak" + slapcat -l "${dump_dir}/all.bak" + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${FUNCNAME[0]}" +} + +####################################################################### +# Copy dump file of Redis instances +# +# Arguments: +# --instances=[Integer] (default: all) +####################################################################### +dump_redis() { + all_instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 '(' -type d -o -type l ')' -name 'redis*') + + local option_instances="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --instances) + # instances options, with key and value separated by space + if [ -n "$2" ]; then + if [ "${2}" == "all" ]; then + read -a option_instances <<< "${all_instances}" + else + IFS="," read -a option_instances <<< "${2}" + fi + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--instances' requires a non-empty option argument." + exit 1 + fi + ;; + --instances=?*) + # instances options, with key and value separated by = + if [ "${1#*=}" == "all" ]; then + read -a option_instances <<< "${all_instances}" + else + IFS="," read -a option_instances <<< "${1#*=}" + fi + ;; + --instances=) + # instances options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--instances' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + for instance in "${option_instances[@]}"; do + name=$(basename "${instance}") + local dump_dir="${LOCAL_BACKUP_DIR}/${name}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + if [ -f "${instance}/dump.rdb" ]; then + local error_file="${errors_dir}/${name}.err" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" + + cp -a "${instance}/dump.rdb" "${dump_dir}/dump.rdb" 2> "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: cp ${instance}/dump.rdb to ${dump_dir} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + + gzip "${dump_dir}/dump.rdb" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: gzip ${dump_dir}/dump.rdb returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_dir}" + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '${instance}/dump.rdb' not found." + fi + done +} + +####################################################################### +# Dump all collections of a MongoDB database +# using a custom authentication, instead of /etc/mysql/debian.cnf +# +# Arguments: +# --user=[String] (default: ) +# --password=[String] (default: ) +####################################################################### +dump_mongodb() { + ## don't forget to create use with read-only access + ## > use admin + ## > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) + + local dump_dir="${LOCAL_BACKUP_DIR}/mongodump" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}.err" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" + + local option_user="" + local option_password="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + declare -a options + options=() + options+=(--username="${option_user}") + options+=(--password="${option_password}") + options+=(--out="${dump_dir}/") + + mongodump "${options[@]}" 2> "${error_file}" > /dev/null + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mongodump to ${dump_dir} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - stop ${FUNCNAME[0]}: ${dump_dir}" +} + +####################################################################### +# Dump MegaCLI configuration +# +# Arguments: +####################################################################### +dump_megacli_config() { + local dump_dir="${LOCAL_BACKUP_DIR}/megacli" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + if command -v megacli > /dev/null; then + local error_file="${errors_dir}/megacli.cfg" + local dump_file="${dump_dir}/megacli.err" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + megacli -CfgSave -f "${dump_file}" -a0 2> "${error_file}" > /dev/null + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: megacli to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + else + log "LOCAL_TASKS - ${FUNCNAME[0]}: 'megacli' not found, unable to dump RAID configuration" + fi +} + + + + + +####################################################################### +# Save some traceroute/mtr results +# +# Arguments: +# --targets=[IP,HOST] (default: ) +####################################################################### +dump_traceroute() { + local dump_dir="${LOCAL_BACKUP_DIR}/traceroute" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + local option_targets="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --targets) + # targets options, with key and value separated by space + if [ -n "$2" ]; then + IFS="," read -a option_targets <<< "${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--targets' requires a non-empty option argument." + exit 1 + fi + ;; + --targets=?*) + # targets options, with key and value separated by = + IFS="," read -a option_targets <<< "${1#*=}" + ;; + --targets=) + # targets options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--targets' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + mtr_bin=$(command -v mtr) + if [ -n "${mtr_bin}" ]; then + for target in "${option_targets[@]}"; do + local dump_file="${dump_dir}/mtr-${target}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + ${mtr_bin} -r "${target}" > "${dump_file}" + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + done + fi + + traceroute_bin=$(command -v traceroute) + if [ -n "${traceroute_bin}" ]; then + for target in "${option_targets[@]}"; do + local dump_file="${dump_dir}/traceroute-${target}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + ${traceroute_bin} -n "${target}" > "${dump_file}" 2>&1 + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + done + fi +} + +####################################################################### +# Save many system information, using dump_server_state +# +# Arguments: +# any option for dump-server-state (except --dump-dir) is usable +# (default: --all) +####################################################################### +dump_server_state() { + local dump_dir="${LOCAL_BACKUP_DIR}/server-state" + rm -rf "${dump_dir}" + # Do not create the directory + # mkdir -p -m 700 "${dump_dir}" + + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" + + # pass all options + read -a options <<< "${@}" + # if no option is given, use "--all" as fallback + if [ ${#options[@]} -le 0 ]; then + options=(--all) + fi + # add "--dump-dir" in case it is missing (as it should) + options+=(--dump-dir "${dump_dir}") + + dump_server_state_bin=$(command -v dump-server-state) + if [ -z "${dump_server_state_bin}" ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: dump-server-state is missing" + rc=1 + else + ${dump_server_state_bin} "${options[@]}" + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: dump-server-state returned an error ${last_rc}, check ${dump_dir}" + GLOBAL_RC=${E_DUMPFAILED} + fi + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_dir}" +} + +####################################################################### +# Save RabbitMQ data +# +# Arguments: +# +# Warning: This has been poorly tested +####################################################################### +dump_rabbitmq() { + local dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}.err" + local dump_file="${dump_dir}/config" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + rabbitmqadmin export "${dump_file}" 2> "${error_file}" >> "${LOGFILE}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" +} + +####################################################################### +# Save Files ACL on various partitions. +# +# Arguments: +####################################################################### +dump_facl() { + local dump_dir="${LOCAL_BACKUP_DIR}/facl" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" + + getfacl -R /etc > "${dump_dir}/etc.txt" + getfacl -R /home > "${dump_dir}/home.txt" + getfacl -R /usr > "${dump_dir}/usr.txt" + getfacl -R /var > "${dump_dir}/var.txt" + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_dir}" +} + +####################################################################### +# Snapshot Elasticsearch data (single-node cluster) +# +# Arguments: +# --protocol=[String] (default: http) +# --host=[String] (default: localhost) +# --port=[Integer] (default: 9200) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --repository=[String] (default: snaprepo) +# --snapshot=[String] (default: snapshot.daily) +####################################################################### +dump_elasticsearch_snapshot_singlenode() { + log "LOCAL_TASKS - ${FUNCNAME[0]}: start dump_elasticsearch_snapshot_singlenode" + + local option_protocol="http" + local option_host="localhost" + local option_port="9200" + local option_user="" + local option_password="" + local option_repository="snaprepo" + local option_snapshot="snapshot.daily" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --protocol) + # protocol options, with value separated by space + if [ -n "$2" ]; then + option_protocol="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." + exit 1 + fi + ;; + --protocol=?*) + # protocol options, with value separated by = + option_protocol="${1#*=}" + ;; + --protocol=) + # protocol options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." + exit 1 + ;; + --host) + # host options, with value separated by space + if [ -n "$2" ]; then + option_host="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." + exit 1 + fi + ;; + --host=?*) + # host options, with value separated by = + option_host="${1#*=}" + ;; + --host=) + # host options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." + exit 1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --repository) + # repository options, with value separated by space + if [ -n "$2" ]; then + option_repository="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." + exit 1 + fi + ;; + --repository=?*) + # repository options, with value separated by = + option_repository="${1#*=}" + ;; + --repository=) + # repository options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." + exit 1 + ;; + --snapshot) + # snapshot options, with value separated by space + if [ -n "$2" ]; then + option_snapshot="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." + exit 1 + fi + ;; + --snapshot=?*) + # snapshot options, with value separated by = + option_snapshot="${1#*=}" + ;; + --snapshot=) + # snapshot options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + ## Take a snapshot as a backup. + ## Warning: You need to have a path.repo configured. + ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes + + local base_url="${option_protocol}://${option_host}:${option_port}" + local snapshot_url="${base_url}/_snapshot/${option_repository}/${option_snapshot}" + + if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then + local option_auth="--user ${option_user}:${option_password}" + else + local option_auth="" + fi + + curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" + curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" + + # Clustered version here + # It basically the same thing except that you need to check that NFS is mounted + # if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2' + # then + # curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" + # curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" + # else + # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' + # fi + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop dump_elasticsearch_snapshot_singlenode" +} + +####################################################################### +# Snapshot Elasticsearch data (multi-node cluster) +# +# Arguments: +# --protocol=[String] (default: http) +# --host=[String] (default: localhost) +# --port=[Integer] (default: 9200) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --repository=[String] (default: snaprepo) +# --snapshot=[String] (default: snapshot.daily) +# --nfs-server=[IP|HOST] (default: ) +####################################################################### +dump_elasticsearch_snapshot_multinode() { + log "LOCAL_TASKS - ${FUNCNAME[0]}: start dump_elasticsearch_snapshot_multinode" + + local option_protocol="http" + local option_host="localhost" + local option_port="9200" + local option_user="" + local option_password="" + local option_repository="snaprepo" + local option_snapshot="snapshot.daily" + local option_nfs_server="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --protocol) + # protocol options, with value separated by space + if [ -n "$2" ]; then + option_protocol="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." + exit 1 + fi + ;; + --protocol=?*) + # protocol options, with value separated by = + option_protocol="${1#*=}" + ;; + --protocol=) + # protocol options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." + exit 1 + ;; + --host) + # host options, with value separated by space + if [ -n "$2" ]; then + option_host="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." + exit 1 + fi + ;; + --host=?*) + # host options, with value separated by = + option_host="${1#*=}" + ;; + --host=) + # host options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." + exit 1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --repository) + # repository options, with value separated by space + if [ -n "$2" ]; then + option_repository="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." + exit 1 + fi + ;; + --repository=?*) + # repository options, with value separated by = + option_repository="${1#*=}" + ;; + --repository=) + # repository options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." + exit 1 + ;; + --snapshot) + # snapshot options, with value separated by space + if [ -n "$2" ]; then + option_snapshot="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." + exit 1 + fi + ;; + --snapshot=?*) + # snapshot options, with value separated by = + option_snapshot="${1#*=}" + ;; + --snapshot=) + # snapshot options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." + exit 1 + ;; + --nfs-server) + # nfs-server options, with value separated by space + if [ -n "$2" ]; then + option_nfs_server="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--nfs-server' requires a non-empty option argument." + exit 1 + fi + ;; + --nfs-server=?*) + # nfs-server options, with value separated by = + option_nfs_server="${1#*=}" + ;; + --nfs-server=) + # nfs-server options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--nfs-server' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + ## Take a snapshot as a backup. + ## Warning: You need to have a path.repo configured. + ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes + + local base_url="${option_protocol}://${option_host}:${option_port}" + local snapshot_url="${base_url}/_snapshot/${option_repository}/${option_snapshot}" + + if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then + local option_auth="--user ${option_user}:${option_password}" + else + local option_auth="" + fi + + # Clustered version here + # It basically the same thing except that you need to check that NFS is mounted + if ss | grep ':nfs' | grep -q -F "${option_nfs_server}"; then + curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" + curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" + else + echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' + fi + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop dump_elasticsearch_snapshot_multinode" +} diff --git a/client/lib/dump-mysql.sh b/client/lib/dump-mysql.sh new file mode 100644 index 0000000..c9803d8 --- /dev/null +++ b/client/lib/dump-mysql.sh @@ -0,0 +1,1076 @@ +#!/bin/bash +# shellcheck disable=SC2034,SC2317,SC2155 + +mysql_list_databases() { + port=${1:-"3306"} + + mysql --defaults-extra-file=/etc/mysql/debian.cnf --port="${port}" --execute="show databases" --silent --skip-column-names \ + | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" +} + +####################################################################### +# Dump complete summary of an instance (using pt-mysql-summary) +# +# Arguments: +# --port=[Integer] (default: ) +# --socket=[String] (default: ) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --defaults-file=[String] (default: ) +# --defaults-extra-file=[String] (default: ) +# --defaults-group-suffix=[String] (default: ) +# --dump-label=[String] (default: "default") +# used as suffix of the dump dir to differenciate multiple instances +####################################################################### +dump_mysql_summary() { + local option_port="" + local option_socket="" + local option_defaults_file="" + local option_defaults_extra_file="" + local option_defaults_group_suffix="" + local option_user="" + local option_password="" + local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --defaults-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-file=?*) + # defaults-file options, with value separated by = + option_defaults_file="${1#*=}" + ;; + --defaults-file=) + # defaults-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + ;; + --defaults-extra-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_extra_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-extra-file=?*) + # defaults-extra-file options, with value separated by = + option_defaults_extra_file="${1#*=}" + ;; + --defaults-extra-file=) + # defaults-extra-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-extra-file' requires a non-empty option argument." + exit 1 + ;; + --defaults-group-suffix) + # defaults-group-suffix options, with value separated by space + if [ -n "$2" ]; then + option_defaults_group_suffix="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-group-suffix' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-group-suffix=?*) + # defaults-group-suffix options, with value separated by = + option_defaults_group_suffix="${1#*=}" + ;; + --defaults-group-suffix=) + # defaults-group-suffix options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-group-suffix' requires a non-empty option argument." + exit 1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; + --socket) + # socket options, with value separated by space + if [ -n "$2" ]; then + option_socket="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + fi + ;; + --socket=?*) + # socket options, with value separated by = + option_socket="${1#*=}" + ;; + --socket=) + # socket options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --dump-label) + # dump-label options, with value separated by space + if [ -n "$2" ]; then + option_dump_label="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + fi + ;; + --dump-label=?*) + # dump-label options, with value separated by = + option_dump_label="${1#*=}" + ;; + --dump-label=) + # dump-label options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unkwnown option (ignored): '${1}'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + if [ -z "${option_dump_label}" ]; then + if [ -n "${option_defaults_group_suffix}" ]; then + option_dump_label="${option_defaults_group_suffix}" + elif [ -n "${option_port}" ]; then + option_dump_label="${option_port}" + else + option_dump_label="default" + fi + fi + + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + # DO NOT REMOVE EXISTING DIRECTORIES + # rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + ## Dump all grants (requires 'percona-toolkit' package) + if command -v pt-mysql-summary > /dev/null; then + local error_file="${errors_dir}/mysql-summary.err" + local dump_file="${dump_dir}/mysql-summary.out" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + ## Connection options + declare -a connect_options + connect_options=() + if [ -n "${option_defaults_file}" ]; then + connect_options+=(--defaults-file="${option_defaults_file}") + fi + if [ -n "${option_defaults_extra_file}" ]; then + connect_options+=(--defaults-extra-file="${option_defaults_extra_file}") + fi + if [ -n "${option_defaults_group_suffix}" ]; then + connect_options+=(--defaults-group-suffix="${option_defaults_group_suffix}") + fi + if [ -n "${option_port}" ]; then + connect_options+=(--protocol=tcp) + connect_options+=(--port="${option_port}") + fi + if [ -n "${option_socket}" ]; then + connect_options+=(--protocol=socket) + connect_options+=(--socket="${option_socket}") + fi + if [ -n "${option_user}" ]; then + connect_options+=(--user="${option_user}") + fi + if [ -n "${option_password}" ]; then + connect_options+=(--password="${option_password}") + fi + + declare -a options + options=() + options+=(--sleep=0) + + pt-mysql-summary "${options[@]}" -- "${connect_options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pt-mysql-summary to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + else + log "LOCAL_TASKS - ${FUNCNAME[0]}: 'pt-mysql-summary' not found, unable to dump summary" + fi +} + +####################################################################### +# Dump grants of an instance +# +# Arguments: +# --port=[Integer] (default: ) +# --socket=[String] (default: ) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --defaults-file=[String] (default: ) +# --dump-label=[String] (default: "default") +# used as suffix of the dump dir to differenciate multiple instances +####################################################################### +dump_mysql_grants() { + local option_port="" + local option_socket="" + local option_defaults_file="" + local option_user="" + local option_password="" + local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --defaults-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-file=?*) + # defaults-file options, with value separated by = + option_defaults_file="${1#*=}" + ;; + --defaults-file=) + # defaults-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; + --socket) + # socket options, with value separated by space + if [ -n "$2" ]; then + option_socket="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + fi + ;; + --socket=?*) + # socket options, with value separated by = + option_socket="${1#*=}" + ;; + --socket=) + # socket options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --dump-label) + # dump-label options, with value separated by space + if [ -n "$2" ]; then + option_dump_label="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + fi + ;; + --dump-label=?*) + # dump-label options, with value separated by = + option_dump_label="${1#*=}" + ;; + --dump-label=) + # dump-label options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + if [ -z "${option_dump_label}" ]; then + if [ -n "${option_port}" ]; then + option_dump_label="${option_port}" + else + option_dump_label="default" + fi + fi + + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + # DO NOT REMOVE EXISTING DIRECTORIES + # rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + ## Dump all grants (requires 'percona-toolkit' package) + if command -v pt-show-grants > /dev/null; then + local error_file="${errors_dir}/all_grants.err" + local dump_file="${dump_dir}/all_grants.sql" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + declare -a options + options=() + if [ -n "${option_defaults_file}" ]; then + options+=(--defaults-file="${option_defaults_file}") + fi + if [ -n "${option_port}" ]; then + options+=(--port="${option_port}") + fi + if [ -n "${option_socket}" ]; then + options+=(--socket="${option_socket}") + fi + if [ -n "${option_user}" ]; then + options+=(--user="${option_user}") + fi + if [ -n "${option_password}" ]; then + options+=(--password="${option_password}") + fi + options+=(--flush) + options+=(--no-header) + + pt-show-grants "${options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + else + log "LOCAL_TASKS - ${FUNCNAME[0]}: 'pt-show-grants' not found, unable to dump grants" + fi +} + +####################################################################### +# Dump a single compressed file of all databases of an instance +# +# Arguments: +# --masterdata (default: ) +# --port=[Integer] (default: ) +# --socket=[String] (default: ) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --defaults-file=[String] (default: ) +# --defaults-extra-file=[String] (default: ) +# --defaults-group-suffix=[String] (default: ) +# --dump-label=[String] (default: "default") +# used as suffix of the dump dir to differenciate multiple instances +####################################################################### +dump_mysql_global() { + local option_masterdata="" + local option_port="" + local option_socket="" + local option_defaults_file="" + local option_defaults_extra_file="" + local option_defaults_group_suffix="" + local option_user="" + local option_password="" + local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --masterdata) + option_masterdata="--masterdata" + ;; + --defaults-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-file=?*) + # defaults-file options, with value separated by = + option_defaults_file="${1#*=}" + ;; + --defaults-file=) + # defaults-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + ;; + --defaults-extra-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_extra_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-extra-file=?*) + # defaults-extra-file options, with value separated by = + option_defaults_extra_file="${1#*=}" + ;; + --defaults-extra-file=) + # defaults-extra-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-extra-file' requires a non-empty option argument." + exit 1 + ;; + --defaults-group-suffix) + # defaults-group-suffix options, with value separated by space + if [ -n "$2" ]; then + option_defaults_group_suffix="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-group-suffix' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-group-suffix=?*) + # defaults-group-suffix options, with value separated by = + option_defaults_group_suffix="${1#*=}" + ;; + --defaults-group-suffix=) + # defaults-group-suffix options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-group-suffix' requires a non-empty option argument." + exit 1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; + --socket) + # socket options, with value separated by space + if [ -n "$2" ]; then + option_socket="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + fi + ;; + --socket=?*) + # socket options, with value separated by = + option_socket="${1#*=}" + ;; + --socket=) + # socket options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --dump-label) + # dump-label options, with value separated by space + if [ -n "$2" ]; then + option_dump_label="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + fi + ;; + --dump-label=?*) + # dump-label options, with value separated by = + option_dump_label="${1#*=}" + ;; + --dump-label=) + # dump-label options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + if [ -z "${option_dump_label}" ]; then + if [ -n "${option_defaults_group_suffix}" ]; then + option_dump_label="${option_defaults_group_suffix}" + elif [ -n "${option_port}" ]; then + option_dump_label="${option_port}" + else + option_dump_label="default" + fi + fi + + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}/mysqldump.err" + local dump_file="${dump_dir}/mysqldump.sql.gz" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + ## Connection options + declare -a connect_options + connect_options=() + if [ -n "${option_defaults_file}" ]; then + connect_options+=(--defaults-file="${option_defaults_file}") + fi + if [ -n "${option_defaults_extra_file}" ]; then + connect_options+=(--defaults-extra-file="${option_defaults_extra_file}") + fi + if [ -n "${option_defaults_group_suffix}" ]; then + connect_options+=(--defaults-group-suffix="${option_defaults_group_suffix}") + fi + if [ -n "${option_port}" ]; then + connect_options+=(--protocol=tcp) + connect_options+=(--port="${option_port}") + fi + if [ -n "${option_socket}" ]; then + connect_options+=(--protocol=socket) + connect_options+=(--socket="${option_socket}") + fi + if [ -n "${option_user}" ]; then + connect_options+=(--user="${option_user}") + fi + if [ -n "${option_password}" ]; then + connect_options+=(--password="${option_password}") + fi + + ## Global all databases in one file + declare -a dump_options + dump_options=() + dump_options+=(--opt) + dump_options+=(--force) + dump_options+=(--events) + dump_options+=(--hex-blob) + dump_options+=(--all-databases) + if [ -n "${option_masterdata}" ]; then + dump_options+=("${option_masterdata}") + fi + + mysqldump "${connect_options[@]}" "${dump_options[@]}" 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + + ## Schema only (no data) for each databases + local error_file="${errors_dir}/mysqldump.schema.err" + local dump_file="${dump_dir}/mysqldump.schema.sql" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + declare -a dump_options + dump_options=() + dump_options+=(--force) + dump_options+=(--no-data) + dump_options+=(--all-databases) + + mysqldump "${connect_options[@]}" "${dump_options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" +} + +####################################################################### +# Dump a compressed file per database of an instance +# +# Arguments: +# --port=[Integer] (default: ) +# --socket=[String] (default: ) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --defaults-file=[String] (default: ) +# --defaults-extra-file=[String] (default: ) +# --defaults-group-suffix=[String] (default: ) +# --dump-label=[String] (default: "default") +# used as suffix of the dump dir to differenciate multiple instances +####################################################################### +dump_mysql_per_base() { + local option_port="" + local option_socket="" + local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; + --socket) + # socket options, with value separated by space + if [ -n "$2" ]; then + option_socket="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + fi + ;; + --socket=?*) + # socket options, with value separated by = + option_socket="${1#*=}" + ;; + --socket=) + # socket options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + option_dump_label="${option_dump_label:-default}" + + declare -a connect_options + connect_options=() + if [ -n "${option_port}" ]; then + connect_options+=(--port="${option_port}") + fi + if [ -n "${option_socket}" ]; then + connect_options+=(--protocol=socket) + connect_options+=(--socket="${option_socket}") + fi + + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}/databases" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + declare -a options + options=() + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--force) + options+=(--events) + options+=(--hex-blob) + + databases=$(mysql_list_databases "${connect_options[@]}") + + for database in ${databases}; do + local error_file="${errors_dir}/${database}.err" + local dump_file="${dump_dir}/${database}.sql.gz" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + mysqldump "${connect_options[@]}" "${options[@]}" "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + done + + ## Schema only (no data) for each databases + for database in ${databases}; do + local error_file="${errors_dir}/${database}.schema.err" + local dump_file="${dump_dir}/${database}.schema.sql" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + declare -a options + options=() + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + options+=(--force) + options+=(--no-data) + options+=(--databases "${database}") + + mysqldump "${connect_options[@]}" "${options[@]}" 2> "${error_file}" > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + done + + ## Grants and summary + if [ -n "${option_port}" ]; then + dump_mysql_grants --port="${option_port}" + dump_mysql_summary --port="${option_port}" + elif [ -n "${option_socket}" ]; then + dump_mysql_grants --socket="${option_socket}" + dump_mysql_summary --socket="${option_socket}" + else + dump_mysql_grants + dump_mysql_summary + fi +} + +####################################################################### +# Dump "tabs style" separate schema/data for each database of an instance +# +# Arguments: +# --masterdata (default: ) +# --port=[Integer] (default: ) +# --socket=[String] (default: ) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --defaults-file=[String] (default: ) +# --defaults-extra-file=[String] (default: ) +# --defaults-group-suffix=[String] (default: ) +# --dump-label=[String] (default: "default") +# used as suffix of the dump dir to differenciate multiple instances +####################################################################### +dump_mysql_tabs() { + local option_port="" + local option_socket="" + local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; + --socket) + # socket options, with value separated by space + if [ -n "$2" ]; then + option_socket="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + fi + ;; + --socket=?*) + # socket options, with value separated by = + option_socket="${1#*=}" + ;; + --socket=) + # socket options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + option_dump_label="${option_dump_label:-default}" + + databases=$(mysql_list_databases "${option_port}") + + for database in ${databases}; do + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}/tabs/${database}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + chown -RL mysql "${dump_dir}" + + local error_file="${errors_dir}.err" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" + + declare -a options + options=() + options+=(--defaults-extra-file=/etc/mysql/debian.cnf) + if [ -n "${option_port}" ]; then + options+=(--port="${option_port}") + fi + if [ -n "${option_socket}" ]; then + options+=(--protocol=socket) + options+=(--socket="${option_socket}") + fi + options+=(--force) + options+=(--quote-names) + options+=(--opt) + options+=(--events) + options+=(--hex-blob) + options+=(--skip-comments) + options+=(--fields-enclosed-by='\"') + options+=(--fields-terminated-by=',') + options+=(--tab="${dump_dir}") + options+=("${database}") + + mysqldump "${options[@]}" 2> "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_dir} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_dir}" + done +} diff --git a/client/lib/dump-postgresql.sh b/client/lib/dump-postgresql.sh new file mode 100644 index 0000000..ebbe7d6 --- /dev/null +++ b/client/lib/dump-postgresql.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# shellcheck disable=SC2034,SC2317,SC2155 + +####################################################################### +# Dump a single file of all PostgreSQL databases +# +# Arguments: +####################################################################### +dump_postgresql_global() { + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + ## example with pg_dumpall and with compression + local error_file="${errors_dir}/pg_dumpall.err" + local dump_file="${dump_dir}/pg_dumpall.sql.gz" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + (sudo -u postgres pg_dumpall) 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dumpall to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + + ## example with pg_dumpall and without compression + ## WARNING: you need space in ~postgres + # local error_file="${errors_dir}/pg_dumpall.err" + # local dump_file="${dump_dir}/pg_dumpall.sql" + # log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + # + # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" + # mv ~postgres/pg.dump.bak "${dump_file}" + # + # log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" +} + +####################################################################### +# Dump a compressed file per database +# +# Arguments: +####################################################################### +dump_postgresql_per_base() { + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + ( + # shellcheck disable=SC2164 + cd /var/lib/postgresql + databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") + for database in ${databases} ; do + local error_file="${errors_dir}/${database}.err" + local dump_file="${dump_dir}/${database}.sql.gz" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + (sudo -u postgres /usr/bin/pg_dump --create -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + done + ) +} + +####################################################################### +# Dump a compressed file per database +# +# Arguments: +# +# TODO: add arguments to include/exclude tables +####################################################################### +dump_postgresql_filtered() { + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}/pg-backup.err" + local dump_file="${dump_dir}/pg-backup.tar" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + ## example with all tables from MYBASE excepts TABLE1 and TABLE2 + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -t 'TABLE1' -t 'TABLE2' MYBASE 2> "${error_file}" + + ## example with only TABLE1 and TABLE2 from MYBASE + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -T 'TABLE1' -T 'TABLE2' MYBASE 2> "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" +} diff --git a/client/lib/dump.sh b/client/lib/dump.sh deleted file mode 100644 index b099fe2..0000000 --- a/client/lib/dump.sh +++ /dev/null @@ -1,1585 +0,0 @@ -#!/bin/bash -# shellcheck disable=SC2034,SC2317,SC2155 - -mysql_list_databases() { - port=${1:-"3306"} - - mysql --defaults-extra-file=/etc/mysql/debian.cnf --port="${port}" --execute="show databases" --silent --skip-column-names \ - | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" -} - -### BEGIN Dump functions #### - -####################################################################### -# Dump LDAP files (config, data, all) -# -# Arguments: -####################################################################### -dump_ldap() { - ## OpenLDAP : example with slapcat - local dump_dir="${LOCAL_BACKUP_DIR}/ldap" - rm -rf "${dump_dir}" - mkdir -p "${dump_dir}" - chmod -R 700 "${dump_dir}" - - log "LOCAL_TASKS - start dump_ldap to ${dump_dir}" - - slapcat -n 0 -l "${dump_dir}/config.bak" - slapcat -n 1 -l "${dump_dir}/data.bak" - slapcat -l "${dump_dir}/all.bak" - - log "LOCAL_TASKS - stop dump_ldap" -} - - - -####################################################################### -# Dump config variables of an instance -# -# Arguments: -# --port=[Integer] (default: 3306) -####################################################################### -dump_mysql_variables() { - local option_port="3306" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - ## Dump all variables - local error_file="${errors_dir}/variables.err" - local dump_file="${dump_dir}/variables.txt" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--port="${option_port}") - options+=(--no-auto-rehash) - options+=(-e "SHOW GLOBAL VARIABLES;") - - mysql "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysql 'show variables' returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} - -####################################################################### -# Dump grants of an instance -# -# Arguments: -# --port=[Integer] (default: 3306) -####################################################################### -dump_mysql_grants() { - local option_port="3306" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - ## Dump all grants (requires 'percona-toolkit' package) - if command -v pt-show-grants > /dev/null; then - local error_file="${errors_dir}/all_grants.err" - local dump_file="${dump_dir}/all_grants.sql" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--port "${option_port}") - options+=(--flush) - options+=(--no-header) - - pt-show-grants "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pt-show-grants to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - else - log "LOCAL_TASKS - pt-show-grants not found, unable to dump grants" - fi -} - - - -####################################################################### -# Dump a single compressed file of all databases of an instance -# -# Arguments: -# --masterdata (default: ) -# --port=[Integer] (default: 3306) -####################################################################### -dump_mysql_global() { - local option_masterdata="" - local option_port="3306" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --masterdata) - option_masterdata="--masterdata" - ;; - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-global-${option_port}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - local error_file="${errors_dir}/mysqldump.err" - local dump_file="${dump_dir}/mysqldump.sql.gz" - log "LOCAL_TASKS - start ${dump_file}" - - ## Global all databases in one file - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--port="${option_port}") - options+=(--opt) - options+=(--force) - options+=(--events) - options+=(--hex-blob) - options+=(--all-databases) - if [ -n "${option_masterdata}" ]; then - options+=("${option_masterdata}") - fi - - mysqldump "${options[@]}" 2> "${error_file}" | gzip --best > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - - ## Schema only (no data) for each databases - local error_file="${errors_dir}/mysqldump.schema.err" - local dump_file="${dump_dir}/mysqldump.schema.sql" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--port="${option_port}") - options+=(--force) - options+=(--no-data) - options+=(--all-databases) - - mysqldump "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - - dump_mysql_grants --port="${option_port}" - - dump_mysql_variables --port="${option_port}" -} - -####################################################################### -# Dump a compressed file per database of an instance -# -# Arguments: -# --port=[Integer] (default: 3306) -####################################################################### -dump_mysql_per_base() { - local option_port="3306" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-per-base-${option_port}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--port="${option_port}") - options+=(--force) - options+=(--events) - options+=(--hex-blob) - - databases=$(mysql_list_databases "${option_port}") - for database in ${databases}; do - local error_file="${errors_dir}/${database}.err" - local dump_file="${dump_dir}/${database}.sql.gz" - log "LOCAL_TASKS - start ${dump_file}" - - mysqldump "${options[@]}" "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done - - ## Schema only (no data) for each databases - databases=$(mysql_list_databases "${option_port}") - for database in ${databases}; do - local error_file="${errors_dir}/${database}.schema.err" - local dump_file="${dump_dir}/${database}.schema.sql" - log "LOCAL_TASKS - start ${dump_file}" - - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--port="${option_port}") - options+=(--force) - options+=(--no-data) - options+=(--databases "${database}") - - mysqldump "${options[@]}" 2> "${error_file}" > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done - - dump_mysql_grants --port="${option_port}" - - dump_mysql_variables --port="${option_port}" -} - -####################################################################### -# Dump "tabs style" separate schema/data for each database of an instance -# -# Arguments: -# --port=[Integer] (default: 3306) -####################################################################### -dump_mysql_tabs() { - local option_port="3306" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - databases=$(mysql_list_databases "${option_port}") - for database in ${databases}; do - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-tabs-${option_port}/${database}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - chown -RL mysql "${dump_dir}" - - local error_file="${errors_dir}.err" - log "LOCAL_TASKS - start ${dump_dir}" - - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--port="${option_port}") - options+=(--force) - options+=(--quote-names) - options+=(--opt) - options+=(--events) - options+=(--hex-blob) - options+=(--skip-comments) - options+=(--fields-enclosed-by='\"') - options+=(--fields-terminated-by=',') - options+=(--tab="${dump_dir}") - options+=("${database}") - - mysqldump "${options[@]}" 2> "${error_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_dir} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_dir}" - done -} - -####################################################################### -# Dump a single file for all databases of an instance -# using a custom authentication, instead of /etc/mysql/debian.cnf -# -# Arguments: -# --port=[Integer] (default: 3306) -# --user=[String] (default: ) -# --password=[String] (default: ) -####################################################################### -dump_mysql_instance() { - local option_port="" - local option_user="" - local option_password="" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --user) - # user options, with value separated by space - if [ -n "$2" ]; then - option_user="${2}" - shift - else - log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." - exit 1 - fi - ;; - --user=?*) - # user options, with value separated by = - option_user="${1#*=}" - ;; - --user=) - # user options, without value - log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." - exit 1 - ;; - --password) - # password options, with value separated by space - if [ -n "$2" ]; then - option_password="${2}" - shift - else - log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." - exit 1 - fi - ;; - --password=?*) - # password options, with value separated by = - option_password="${1#*=}" - ;; - --password=) - # password options, without value - log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-instance-${option_port}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - declare -a options - options=() - options+=(--port="${option_port}") - options+=(--user="${option_user}") - options+=(--password="${option_password}") - options+=(--force) - options+=(--opt) - options+=(--all-databases) - options+=(--events) - options+=(--hex-blob) - - local error_file="${errors_dir}/mysql-global.err" - local dump_file="${dump_dir}/mysql-global.sql.gz" - log "LOCAL_TASKS - start ${dump_file}" - - mysqldump "${options[@]}" 2> "${error_file}" | gzip --best > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} - -####################################################################### -# Dump a single file of all PostgreSQL databases -# -# Arguments: -####################################################################### -dump_postgresql_global() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - ## example with pg_dumpall and with compression - local error_file="${errors_dir}/pg_dumpall.err" - local dump_file="${dump_dir}/pg_dumpall.sql.gz" - log "LOCAL_TASKS - start ${dump_file}" - - (sudo -u postgres pg_dumpall) 2> "${error_file}" | gzip --best > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pg_dumpall to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - - log "LOCAL_TASKS - stop ${dump_file}" - - ## example with pg_dumpall and without compression - ## WARNING: you need space in ~postgres - # local error_file="${errors_dir}/pg_dumpall.err" - # local dump_file="${dump_dir}/pg_dumpall.sql" - # log "LOCAL_TASKS - start ${dump_file}" - # - # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" - # mv ~postgres/pg.dump.bak "${dump_file}" - # - # log "LOCAL_TASKS - stop ${dump_file}" -} - -####################################################################### -# Dump a compressed file per database -# -# Arguments: -####################################################################### -dump_postgresql_per_base() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - ( - # shellcheck disable=SC2164 - cd /var/lib/postgresql - databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") - for database in ${databases} ; do - local error_file="${errors_dir}/${database}.err" - local dump_file="${dump_dir}/${database}.sql.gz" - log "LOCAL_TASKS - start ${dump_file}" - - (sudo -u postgres /usr/bin/pg_dump --create -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_file}" - done - ) -} - -####################################################################### -# Dump a compressed file per database -# -# Arguments: -# -# TODO: add arguments to include/exclude tables -####################################################################### -dump_postgresql_filtered() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - local error_file="${errors_dir}/pg-backup.err" - local dump_file="${dump_dir}/pg-backup.tar" - log "LOCAL_TASKS - start ${dump_file}" - - ## example with all tables from MYBASE excepts TABLE1 and TABLE2 - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -t 'TABLE1' -t 'TABLE2' MYBASE 2> "${error_file}" - - ## example with only TABLE1 and TABLE2 from MYBASE - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -T 'TABLE1' -T 'TABLE2' MYBASE 2> "${error_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} - -####################################################################### -# Copy dump file of Redis instances -# -# Arguments: -# --instances=[Integer] (default: all) -####################################################################### -dump_redis() { - all_instances=$(find /var/lib/ -mindepth 1 -maxdepth 1 '(' -type d -o -type l ')' -name 'redis*') - - local option_instances="" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --instances) - # instances options, with key and value separated by space - if [ -n "$2" ]; then - if [ "${2}" == "all" ]; then - read -a option_instances <<< "${all_instances}" - else - IFS="," read -a option_instances <<< "${2}" - fi - shift - else - log_error "LOCAL_TASKS - '--instances' requires a non-empty option argument." - exit 1 - fi - ;; - --instances=?*) - # instances options, with key and value separated by = - if [ "${1#*=}" == "all" ]; then - read -a option_instances <<< "${all_instances}" - else - IFS="," read -a option_instances <<< "${1#*=}" - fi - ;; - --instances=) - # instances options, without value - log_error "LOCAL_TASKS - '--instances' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - for instance in "${option_instances[@]}"; do - name=$(basename "${instance}") - local dump_dir="${LOCAL_BACKUP_DIR}/${name}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - if [ -f "${instance}/dump.rdb" ]; then - local error_file="${errors_dir}/${name}.err" - log "LOCAL_TASKS - start ${dump_dir}" - - cp -a "${instance}/dump.rdb" "${dump_dir}/dump.rdb" 2> "${error_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - cp ${instance}/dump.rdb to ${dump_dir} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - - gzip "${dump_dir}/dump.rdb" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - gzip ${dump_dir}/dump.rdb returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - - log "LOCAL_TASKS - stop ${dump_dir}" - else - log_error "LOCAL_TASKS - '${instance}/dump.rdb' not found." - fi - done -} - -####################################################################### -# Dump all collections of a MongoDB database -# using a custom authentication, instead of /etc/mysql/debian.cnf -# -# Arguments: -# --user=[String] (default: ) -# --password=[String] (default: ) -####################################################################### -dump_mongodb() { - ## don't forget to create use with read-only access - ## > use admin - ## > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) - - local dump_dir="${LOCAL_BACKUP_DIR}/mongodump" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - local error_file="${errors_dir}.err" - log "LOCAL_TASKS - start ${dump_dir}" - - local option_user="" - local option_password="" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --user) - # user options, with value separated by space - if [ -n "$2" ]; then - option_user="${2}" - shift - else - log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." - exit 1 - fi - ;; - --user=?*) - # user options, with value separated by = - option_user="${1#*=}" - ;; - --user=) - # user options, without value - log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." - exit 1 - ;; - --password) - # password options, with value separated by space - if [ -n "$2" ]; then - option_password="${2}" - shift - else - log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." - exit 1 - fi - ;; - --password=?*) - # password options, with value separated by = - option_password="${1#*=}" - ;; - --password=) - # password options, without value - log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - declare -a options - options=() - options+=(--username="${option_user}") - options+=(--password="${option_password}") - options+=(--out="${dump_dir}/") - - mongodump "${options[@]}" 2> "${error_file}" > /dev/null - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - mongodump to ${dump_dir} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_dir}" -} - -####################################################################### -# Dump MegaCLI configuration -# -# Arguments: -####################################################################### -dump_megacli_config() { - local dump_dir="${LOCAL_BACKUP_DIR}/megacli" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - local dump_file="${dump_dir}/megacli.cfg" - local error_file="${errors_dir}/megacli.err" - log "LOCAL_TASKS - start ${dump_file}" - - megacli -CfgSave -f "${dump_file}" -a0 2> "${error_file}" > /dev/null - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - megacli to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} - -####################################################################### -# Save some traceroute/mtr results -# -# Arguments: -# --targets=[IP,HOST] (default: ) -####################################################################### -dump_traceroute() { - local dump_dir="${LOCAL_BACKUP_DIR}/traceroute" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - local option_targets="" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --targets) - # targets options, with key and value separated by space - if [ -n "$2" ]; then - IFS="," read -a option_targets <<< "${2}" - shift - else - log_error "LOCAL_TASKS - '--targets' requires a non-empty option argument." - exit 1 - fi - ;; - --targets=?*) - # targets options, with key and value separated by = - IFS="," read -a option_targets <<< "${1#*=}" - ;; - --targets=) - # targets options, without value - log_error "LOCAL_TASKS - '--targets' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - mtr_bin=$(command -v mtr) - if [ -n "${mtr_bin}" ]; then - for target in "${option_targets[@]}"; do - local dump_file="${dump_dir}/mtr-${target}" - log "LOCAL_TASKS - start ${dump_file}" - - ${mtr_bin} -r "${target}" > "${dump_file}" - - log "LOCAL_TASKS - stop ${dump_file}" - done - fi - - traceroute_bin=$(command -v traceroute) - if [ -n "${traceroute_bin}" ]; then - for target in "${option_targets[@]}"; do - local dump_file="${dump_dir}/traceroute-${target}" - log "LOCAL_TASKS - start ${dump_file}" - - ${traceroute_bin} -n "${target}" > "${dump_file}" 2>&1 - - log "LOCAL_TASKS - stop ${dump_file}" - done - fi -} - -####################################################################### -# Save many system information, using dump_server_state -# -# Arguments: -# any option for dump-server-state (except --dump-dir) is usable -# (default: --all) -####################################################################### -dump_server_state() { - local dump_dir="${LOCAL_BACKUP_DIR}/server-state" - rm -rf "${dump_dir}" - # Do not create the directory - # mkdir -p -m 700 "${dump_dir}" - - log "LOCAL_TASKS - start ${dump_dir}" - - # pass all options - read -a options <<< "${@}" - # if no option is given, use "--all" as fallback - if [ ${#options[@]} -le 0 ]; then - options=(--all) - fi - # add "--dump-dir" in case it is missing (as it should) - options+=(--dump-dir "${dump_dir}") - - dump_server_state_bin=$(command -v dump-server-state) - if [ -z "${dump_server_state_bin}" ]; then - log_error "LOCAL_TASKS - dump-server-state is missing" - rc=1 - else - ${dump_server_state_bin} "${options[@]}" - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - dump-server-state returned an error ${last_rc}, check ${dump_dir}" - GLOBAL_RC=${E_DUMPFAILED} - fi - fi - log "LOCAL_TASKS - stop ${dump_dir}" -} - -####################################################################### -# Save RabbitMQ data -# -# Arguments: -# -# Warning: This has been poorly tested -####################################################################### -dump_rabbitmq() { - local dump_dir="${LOCAL_BACKUP_DIR}/rabbitmq" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - local error_file="${errors_dir}.err" - local dump_file="${dump_dir}/config" - log "LOCAL_TASKS - start ${dump_file}" - - rabbitmqadmin export "${dump_file}" 2> "${error_file}" >> "${LOGFILE}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - rmdir --ignore-fail-on-non-empty "${errors_dir}" - fi - log "LOCAL_TASKS - stop ${dump_file}" -} - -####################################################################### -# Save Files ACL on various partitions. -# -# Arguments: -####################################################################### -dump_facl() { - local dump_dir="${LOCAL_BACKUP_DIR}/facl" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - chmod -R 700 "${dump_dir}" "${errors_dir}" - - log "LOCAL_TASKS - start ${dump_dir}" - - getfacl -R /etc > "${dump_dir}/etc.txt" - getfacl -R /home > "${dump_dir}/home.txt" - getfacl -R /usr > "${dump_dir}/usr.txt" - getfacl -R /var > "${dump_dir}/var.txt" - - log "LOCAL_TASKS - stop ${dump_dir}" -} - -####################################################################### -# Snapshot Elasticsearch data (single-node cluster) -# -# Arguments: -# --protocol=[String] (default: http) -# --host=[String] (default: localhost) -# --port=[Integer] (default: 9200) -# --user=[String] (default: ) -# --password=[String] (default: ) -# --repository=[String] (default: snaprepo) -# --snapshot=[String] (default: snapshot.daily) -####################################################################### -dump_elasticsearch_snapshot_singlenode() { - log "LOCAL_TASKS - start dump_elasticsearch_snapshot_singlenode" - - local option_protocol="http" - local option_host="localhost" - local option_port="9200" - local option_user="" - local option_password="" - local option_repository="snaprepo" - local option_snapshot="snapshot.daily" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --protocol) - # protocol options, with value separated by space - if [ -n "$2" ]; then - option_protocol="${2}" - shift - else - log_error "LOCAL_TASKS - '--protocol' requires a non-empty option argument." - exit 1 - fi - ;; - --protocol=?*) - # protocol options, with value separated by = - option_protocol="${1#*=}" - ;; - --protocol=) - # protocol options, without value - log_error "LOCAL_TASKS - '--protocol' requires a non-empty option argument." - exit 1 - ;; - --host) - # host options, with value separated by space - if [ -n "$2" ]; then - option_host="${2}" - shift - else - log_error "LOCAL_TASKS - '--host' requires a non-empty option argument." - exit 1 - fi - ;; - --host=?*) - # host options, with value separated by = - option_host="${1#*=}" - ;; - --host=) - # host options, without value - log_error "LOCAL_TASKS - '--host' requires a non-empty option argument." - exit 1 - ;; - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --user) - # user options, with value separated by space - if [ -n "$2" ]; then - option_user="${2}" - shift - else - log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." - exit 1 - fi - ;; - --user=?*) - # user options, with value separated by = - option_user="${1#*=}" - ;; - --user=) - # user options, without value - log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." - exit 1 - ;; - --password) - # password options, with value separated by space - if [ -n "$2" ]; then - option_password="${2}" - shift - else - log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." - exit 1 - fi - ;; - --password=?*) - # password options, with value separated by = - option_password="${1#*=}" - ;; - --password=) - # password options, without value - log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." - exit 1 - ;; - --repository) - # repository options, with value separated by space - if [ -n "$2" ]; then - option_repository="${2}" - shift - else - log_error "LOCAL_TASKS - '--repository' requires a non-empty option argument." - exit 1 - fi - ;; - --repository=?*) - # repository options, with value separated by = - option_repository="${1#*=}" - ;; - --repository=) - # repository options, without value - log_error "LOCAL_TASKS - '--repository' requires a non-empty option argument." - exit 1 - ;; - --snapshot) - # snapshot options, with value separated by space - if [ -n "$2" ]; then - option_snapshot="${2}" - shift - else - log_error "LOCAL_TASKS - '--snapshot' requires a non-empty option argument." - exit 1 - fi - ;; - --snapshot=?*) - # snapshot options, with value separated by = - option_snapshot="${1#*=}" - ;; - --snapshot=) - # snapshot options, without value - log_error "LOCAL_TASKS - '--snapshot' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - ## Take a snapshot as a backup. - ## Warning: You need to have a path.repo configured. - ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes - - local base_url="${option_protocol}://${option_host}:${option_port}" - local snapshot_url="${base_url}/_snapshot/${option_repository}/${option_snapshot}" - - if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then - local option_auth="--user ${option_user}:${option_password}" - else - local option_auth="" - fi - - curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" - curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" - - # Clustered version here - # It basically the same thing except that you need to check that NFS is mounted - # if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2' - # then - # curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" - # curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" - # else - # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' - # fi - - log "LOCAL_TASKS - stop dump_elasticsearch_snapshot_singlenode" -} - -####################################################################### -# Snapshot Elasticsearch data (multi-node cluster) -# -# Arguments: -# --protocol=[String] (default: http) -# --host=[String] (default: localhost) -# --port=[Integer] (default: 9200) -# --user=[String] (default: ) -# --password=[String] (default: ) -# --repository=[String] (default: snaprepo) -# --snapshot=[String] (default: snapshot.daily) -# --nfs-server=[IP|HOST] (default: ) -####################################################################### -dump_elasticsearch_snapshot_multinode() { - log "LOCAL_TASKS - start dump_elasticsearch_snapshot_multinode" - - local option_protocol="http" - local option_host="localhost" - local option_port="9200" - local option_user="" - local option_password="" - local option_repository="snaprepo" - local option_snapshot="snapshot.daily" - local option_nfs_server="" - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --protocol) - # protocol options, with value separated by space - if [ -n "$2" ]; then - option_protocol="${2}" - shift - else - log_error "LOCAL_TASKS - '--protocol' requires a non-empty option argument." - exit 1 - fi - ;; - --protocol=?*) - # protocol options, with value separated by = - option_protocol="${1#*=}" - ;; - --protocol=) - # protocol options, without value - log_error "LOCAL_TASKS - '--protocol' requires a non-empty option argument." - exit 1 - ;; - --host) - # host options, with value separated by space - if [ -n "$2" ]; then - option_host="${2}" - shift - else - log_error "LOCAL_TASKS - '--host' requires a non-empty option argument." - exit 1 - fi - ;; - --host=?*) - # host options, with value separated by = - option_host="${1#*=}" - ;; - --host=) - # host options, without value - log_error "LOCAL_TASKS - '--host' requires a non-empty option argument." - exit 1 - ;; - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - '--port' requires a non-empty option argument." - exit 1 - ;; - --user) - # user options, with value separated by space - if [ -n "$2" ]; then - option_user="${2}" - shift - else - log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." - exit 1 - fi - ;; - --user=?*) - # user options, with value separated by = - option_user="${1#*=}" - ;; - --user=) - # user options, without value - log_error "LOCAL_TASKS - '--user' requires a non-empty option argument." - exit 1 - ;; - --password) - # password options, with value separated by space - if [ -n "$2" ]; then - option_password="${2}" - shift - else - log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." - exit 1 - fi - ;; - --password=?*) - # password options, with value separated by = - option_password="${1#*=}" - ;; - --password=) - # password options, without value - log_error "LOCAL_TASKS - '--password' requires a non-empty option argument." - exit 1 - ;; - --repository) - # repository options, with value separated by space - if [ -n "$2" ]; then - option_repository="${2}" - shift - else - log_error "LOCAL_TASKS - '--repository' requires a non-empty option argument." - exit 1 - fi - ;; - --repository=?*) - # repository options, with value separated by = - option_repository="${1#*=}" - ;; - --repository=) - # repository options, without value - log_error "LOCAL_TASKS - '--repository' requires a non-empty option argument." - exit 1 - ;; - --snapshot) - # snapshot options, with value separated by space - if [ -n "$2" ]; then - option_snapshot="${2}" - shift - else - log_error "LOCAL_TASKS - '--snapshot' requires a non-empty option argument." - exit 1 - fi - ;; - --snapshot=?*) - # snapshot options, with value separated by = - option_snapshot="${1#*=}" - ;; - --snapshot=) - # snapshot options, without value - log_error "LOCAL_TASKS - '--snapshot' requires a non-empty option argument." - exit 1 - ;; - --nfs-server) - # nfs-server options, with value separated by space - if [ -n "$2" ]; then - option_nfs_server="${2}" - shift - else - log_error "LOCAL_TASKS - '--nfs-server' requires a non-empty option argument." - exit 1 - fi - ;; - --nfs-server=?*) - # nfs-server options, with value separated by = - option_nfs_server="${1#*=}" - ;; - --nfs-server=) - # nfs-server options, without value - log_error "LOCAL_TASKS - '--nfs-server' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - unkwnown option (ignored): '${1}'" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - ## Take a snapshot as a backup. - ## Warning: You need to have a path.repo configured. - ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes - - local base_url="${option_protocol}://${option_host}:${option_port}" - local snapshot_url="${base_url}/_snapshot/${option_repository}/${option_snapshot}" - - if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then - local option_auth="--user ${option_user}:${option_password}" - else - local option_auth="" - fi - - # Clustered version here - # It basically the same thing except that you need to check that NFS is mounted - if ss | grep ':nfs' | grep -q -F "${option_nfs_server}"; then - curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" - curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" - else - echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' - fi - - log "LOCAL_TASKS - stop dump_elasticsearch_snapshot_multinode" -} diff --git a/client/lib/main.sh b/client/lib/main.sh index be0ff1e..4d61839 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -14,7 +14,9 @@ set -u set -o pipefail source "${LIBDIR}/utilities.sh" -source "${LIBDIR}/dump.sh" +source "${LIBDIR}/dump-mysql.sh" +source "${LIBDIR}/dump-postgresql.sh" +source "${LIBDIR}/dump-misc.sh" # Called from main, it is wrapping the local_tasks function defined in the real script local_tasks_wrapper() { @@ -184,7 +186,7 @@ sync() { rsync_server=$(echo "${server}" | cut -d':' -f1) rsync_port=$(echo "${server}" | cut -d':' -f2) - log "START SYNC_TASKS - \"${sync_name}\" : server=${server}" + log "START SYNC_TASKS - ${sync_name}: server=${server}" # Rsync complete log file for the current run RSYNC_LOGFILE="/var/log/${PROGNAME}.${sync_name}.rsync.log" @@ -208,7 +210,7 @@ sync() { if [ -n "${mtree_bin}" ]; then # Dump filesystem stats with mtree - log "SYNC_TASKS - start mtree" + log "SYNC_TASKS - ${sync_name}: start mtree" # Loop over Rsync includes for i in "${!rsync_includes[@]}"; do @@ -232,15 +234,15 @@ sync() { done if [ "${#mtree_files[@]}" -le 0 ]; then - log_error "SYNC_TASKS - ERROR: mtree didn't produce any file" + log_error "SYNC_TASKS - ${sync_name}: ERROR: mtree didn't produce any file" fi - log "SYNC_TASKS - stop mtree (files: ${mtree_files[*]})" + log "SYNC_TASKS - ${sync_name}: stop mtree (files: ${mtree_files[*]})" else - log "SYNC_TASKS - skip mtree (missing)" + log "SYNC_TASKS - ${sync_name}: skip mtree (missing)" fi else - log "SYNC_TASKS - skip mtree (disabled)" + log "SYNC_TASKS - ${sync_name}: skip mtree (disabled)" fi rsync_bin=$(command -v rsync) @@ -274,7 +276,7 @@ sync() { rsync_main_args+=("root@${rsync_server}:${REMOTE_BACKUP_DIR}/") # … log it - log "SYNC_TASKS - \"${sync_name}\" Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" + log "SYNC_TASKS - ${sync_name}: Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" # … execute it ${rsync_bin} "${rsync_main_args[@]}" @@ -288,7 +290,7 @@ sync() { # We ignore rc=24 (vanished files) if [ ${rsync_main_rc} -ne 0 ] && [ ${rsync_main_rc} -ne 24 ]; then - log_error "SYNC_TASKS - ${sync_name} Rsync main command returned an error ${rsync_main_rc}" "${LOGFILE}" + log_error "SYNC_TASKS - ${sync_name}: Rsync main command returned an error ${rsync_main_rc}" "${LOGFILE}" GLOBAL_RC=${E_SYNCFAILED} else # Build the report Rsync command @@ -317,13 +319,13 @@ sync() { rsync_report_args+=("root@${rsync_server}:${REMOTE_LOG_DIR}/") # … log it - log "SYNC_TASKS - ${sync_name} Rsync report command : ${rsync_bin} ${rsync_report_args[*]}" + log "SYNC_TASKS - ${sync_name}: Rsync report command : ${rsync_bin} ${rsync_report_args[*]}" # … execute it ${rsync_bin} "${rsync_report_args[@]}" fi - log "STOP SYNC_TASKS - ${sync_name} server=${server}" + log "STOP SYNC_TASKS - ${sync_name}: server=${server}" } setup() { diff --git a/client/zzz_evobackup b/client/zzz_evobackup index ae0e4ef..ea85983 100644 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -125,7 +125,7 @@ sync_tasks() { # The "local_tasks" function will be called by the main function. # # You can call any available "dump_xxx" function -# (usually installed at /usr/local/lib/evobackup/dump.sh) +# (usually installed at /usr/local/lib/evobackup/dump-*.sh) # # You can also write some custom functions and call them. # A "dump_custom" example is available further down. @@ -134,42 +134,111 @@ sync_tasks() { local_tasks() { - ########## OpenLDAP ############### + ########## Server state ########### - ### dump_ldap + # Run dump-server-state to extract system information + # + # Options : any dump-server-state option (see 'dump-server-state -h') + # + dump_server_state ########## MySQL ################## + # Very common strategy for a single instance server with default configuration : + # + ### dump_mysql_global; dump_mysql_grants; dump_mysql_summary + # Dump all databases in a single compressed file - # and all grants (permissions), config variables and schema of databases - ### dump_mysql_global [--port=3306] [--masterdata] + # + # Options : + # --masterdata (default: ) + # --port=[Integer] (default: ) + # --socket=[String] (default: ) + # --user=[String] (default: ) + # --password=[String] (default: ) + # --defaults-file=[String] (default: ) + # --defaults-extra-file=[String] (default: ) + # --defaults-group-suffix=[String] (default: ) + # --dump-label=[String] (default: "default") + # used as suffix of the dump dir to differenciate multiple instances + # + ### dump_mysql_global # Dump each database separately, in a compressed file - # and all grants (permissions), config variables and schema of databases - ### dump_mysql_per_base [--port=3306] - - # Dump a specific instance, in a single compressed file - # This is similar to "dump_mysql_global", but will custom authentication - ### dump_mysql_instance [--port=3306] [--user=foo] [--password=123456789] + # + # Options : + # --port=[Integer] (default: ) + # --socket=[String] (default: ) + # --user=[String] (default: ) + # --password=[String] (default: ) + # --defaults-file=[String] (default: ) + # --defaults-extra-file=[String] (default: ) + # --defaults-group-suffix=[String] (default: ) + # --dump-label=[String] (default: "default") + # used as suffix of the dump dir to differenciate multiple instances + # + ### dump_mysql_per_base - # Dump only grants of an instance - ### dump_mysql_grants [--port=3306] + # Dump permissions of an instance (using pt-show-grants) + # + # Options : + # --port=[Integer] (default: ) + # --socket=[String] (default: ) + # --user=[String] (default: ) + # --password=[String] (default: ) + # --defaults-file=[String] (default: ) + # --dump-label=[String] (default: "default") + # used as suffix of the dump dir to differenciate multiple instances + # + # WARNING - unsupported options : + # --defaults-extra-file + # --defaults-group-suffix + # You have to provide credentials manually + # + ### dump_mysql_grants - # Dump only variables of an instance - ### dump_mysql_variables [--port=3306] + # Dump complete summary of an instance (using pt-mysql-summary) + # + # Options : + # --port=[Integer] (default: ) + # --socket=[String] (default: ) + # --user=[String] (default: ) + # --password=[String] (default: ) + # --defaults-file=[String] (default: ) + # --defaults-extra-file=[String] (default: ) + # --defaults-group-suffix=[String] (default: ) + # --dump-label=[String] (default: "default") + # used as suffix of the dump dir to differenciate multiple instances + # + ### dump_mysql_summary - # Dump each table in schema/data files - ### dump_mysql_tabs [--port=3306] [--user=foo] [--password=123456789] + # Dump each table in separate schema/data files + # + # Options : + # --port=[Integer] (default: ) + # --socket=[String] (default: ) + # --user=[String] (default: ) + # --password=[String] (default: ) + # --defaults-file=[String] (default: ) + # --defaults-extra-file=[String] (default: ) + # --defaults-group-suffix=[String] (default: ) + # --dump-label=[String] (default: "default") + # used as suffix of the dump dir to differenciate multiple instances + # + ### dump_mysql_tabs ########## PostgreSQL ############# # Dump all databases in a single file (compressed or not) + # ### dump_postgresql_global # Dump a specific databse with only some tables, or all but some tables (must be configured) + # ### dump_postgresql_filtered # Dump each database separately, in a compressed file + # ### dump_postgresql_per_base ########## MongoDB ################ @@ -179,14 +248,17 @@ local_tasks() { ########## Redis ################## # Copy data file for all instances + # ### dump_redis [--instances=] ########## Elasticsearch ########## # Snapshot data for a single-node cluster + # ### dump_elasticsearch_snapshot_singlenode [--protocol=http] [--host=localhost] [--port=9200] [--user=foo] [--password=123456789] [--repository=snaprepo] [--snapshot=snapshot.daily] # Snapshot data for a multi-node cluster + # ### dump_elasticsearch_snapshot_multinode [--protocol=http] [--host=localhost] [--port=9200] [--user=foo] [--password=123456789] [--repository=snaprepo] [--snapshot=snapshot.daily] [--nfs-server=192.168.2.1] ########## RabbitMQ ############### @@ -196,23 +268,24 @@ local_tasks() { ########## MegaCli ################ # Copy RAID config + # ### dump_megacli_config + # Dump file access control lists + # + ### dump_facl + + ########## OpenLDAP ############### + + ### dump_ldap + ########## Network ################ # Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) + # ### dump_traceroute --targets=host_or_ip[,host_or_ip] dump_traceroute --targets=8.8.8.8,www.evolix.fr,travaux.evolix.net - ########## Server state ########### - - # Run dump-server-state to extract system information - ### dump-server-state [any dump-server-state option] - dump_server_state - - # Dump file access control lists - ### dump_facl - # No-op, in case nothing is enabled : } @@ -232,7 +305,7 @@ local_tasks() { ### mkdir -p -m 700 "${dump_dir}" "${errors_dir}" ### ### # Log the start of the command -### log "LOCAL_TASKS - start ${dump_file}" +### log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" ### ### # Execute your dump command ### # Send errors to the error file and the data to the dump file @@ -242,14 +315,14 @@ local_tasks() { ### local last_rc=$? ### # shellcheck disable=SC2086 ### if [ ${last_rc} -ne 0 ]; then -### log_error "LOCAL_TASKS - my-dump-command to ${dump_file} returned an error ${last_rc}" "${error_file}" +### log_error "LOCAL_TASKS - ${FUNCNAME[0]}: my-dump-command to ${dump_file} returned an error ${last_rc}" "${error_file}" ### GLOBAL_RC=${E_DUMPFAILED} ### else ### rm -f "${error_file}" ### fi ### ### # Log the end of the command -### log "LOCAL_TASKS - stop ${dump_file}" +### log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" ### } ########## Optional configuration ##################################### From a0f5c3117571e838ca9688acfb97ac3d6b5c90cc Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 14:05:01 +0100 Subject: [PATCH 076/105] whitespaces and comments --- client/lib/dump-misc.sh | 60 ++++++++++++++++++++++------------------ client/lib/dump-mysql.sh | 5 ++++ client/zzz_evobackup | 4 ++- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/client/lib/dump-misc.sh b/client/lib/dump-misc.sh index e90a1b6..92f6e2e 100644 --- a/client/lib/dump-misc.sh +++ b/client/lib/dump-misc.sh @@ -129,24 +129,15 @@ dump_redis() { # Arguments: # --user=[String] (default: ) # --password=[String] (default: ) +# +# don't forget to create use with read-only access +# > use admin +# > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) ####################################################################### dump_mongodb() { - ## don't forget to create use with read-only access - ## > use admin - ## > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) - - local dump_dir="${LOCAL_BACKUP_DIR}/mongodump" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - # No need to change recursively, the top directory is enough - chmod 700 "${dump_dir}" "${errors_dir}" - - local error_file="${errors_dir}.err" - log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" - local option_user="" local option_password="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in @@ -206,6 +197,16 @@ dump_mongodb() { shift done + local dump_dir="${LOCAL_BACKUP_DIR}/mongodump" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}.err" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" + declare -a options options=() options+=(--username="${option_user}") @@ -270,14 +271,8 @@ dump_megacli_config() { # --targets=[IP,HOST] (default: ) ####################################################################### dump_traceroute() { - local dump_dir="${LOCAL_BACKUP_DIR}/traceroute" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - # No need to change recursively, the top directory is enough - chmod 700 "${dump_dir}" "${errors_dir}" - local option_targets="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in @@ -318,6 +313,14 @@ dump_traceroute() { shift done + local dump_dir="${LOCAL_BACKUP_DIR}/traceroute" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + mtr_bin=$(command -v mtr) if [ -n "${mtr_bin}" ]; then for target in "${option_targets[@]}"; do @@ -400,6 +403,7 @@ dump_rabbitmq() { local error_file="${errors_dir}.err" local dump_file="${dump_dir}/config" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" rabbitmqadmin export "${dump_file}" 2> "${error_file}" >> "${LOGFILE}" @@ -451,8 +455,6 @@ dump_facl() { # --snapshot=[String] (default: snapshot.daily) ####################################################################### dump_elasticsearch_snapshot_singlenode() { - log "LOCAL_TASKS - ${FUNCNAME[0]}: start dump_elasticsearch_snapshot_singlenode" - local option_protocol="http" local option_host="localhost" local option_port="9200" @@ -460,6 +462,7 @@ dump_elasticsearch_snapshot_singlenode() { local option_password="" local option_repository="snaprepo" local option_snapshot="snapshot.daily" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in @@ -614,6 +617,8 @@ dump_elasticsearch_snapshot_singlenode() { shift done + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${option_snapshot}" + ## Take a snapshot as a backup. ## Warning: You need to have a path.repo configured. ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes @@ -640,7 +645,7 @@ dump_elasticsearch_snapshot_singlenode() { # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' # fi - log "LOCAL_TASKS - ${FUNCNAME[0]}: stop dump_elasticsearch_snapshot_singlenode" + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${option_snapshot}" } ####################################################################### @@ -657,8 +662,6 @@ dump_elasticsearch_snapshot_singlenode() { # --nfs-server=[IP|HOST] (default: ) ####################################################################### dump_elasticsearch_snapshot_multinode() { - log "LOCAL_TASKS - ${FUNCNAME[0]}: start dump_elasticsearch_snapshot_multinode" - local option_protocol="http" local option_host="localhost" local option_port="9200" @@ -667,6 +670,7 @@ dump_elasticsearch_snapshot_multinode() { local option_repository="snaprepo" local option_snapshot="snapshot.daily" local option_nfs_server="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in @@ -840,6 +844,8 @@ dump_elasticsearch_snapshot_multinode() { shift done + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${option_snapshot}" + ## Take a snapshot as a backup. ## Warning: You need to have a path.repo configured. ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes @@ -862,5 +868,5 @@ dump_elasticsearch_snapshot_multinode() { echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' fi - log "LOCAL_TASKS - ${FUNCNAME[0]}: stop dump_elasticsearch_snapshot_multinode" + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${option_snapshot}" } diff --git a/client/lib/dump-mysql.sh b/client/lib/dump-mysql.sh index c9803d8..df329fa 100644 --- a/client/lib/dump-mysql.sh +++ b/client/lib/dump-mysql.sh @@ -31,6 +31,7 @@ dump_mysql_summary() { local option_user="" local option_password="" local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in @@ -294,6 +295,7 @@ dump_mysql_grants() { local option_user="" local option_password="" local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in @@ -512,6 +514,7 @@ dump_mysql_global() { local option_user="" local option_password="" local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in @@ -802,6 +805,7 @@ dump_mysql_per_base() { local option_port="" local option_socket="" local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in @@ -965,6 +969,7 @@ dump_mysql_tabs() { local option_port="" local option_socket="" local option_dump_label="" + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in diff --git a/client/zzz_evobackup b/client/zzz_evobackup index ea85983..9111b01 100644 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -138,7 +138,9 @@ local_tasks() { # Run dump-server-state to extract system information # - # Options : any dump-server-state option (see 'dump-server-state -h') + # Options : any dump-server-state supported option + # (except --dump-dir that will be overwritten) + # See 'dump-server-state -h' for details. # dump_server_state From 8c7a9632660d0a63207d837d8de925330dfc8c15 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 14:06:28 +0100 Subject: [PATCH 077/105] more comments --- client/zzz_evobackup | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/zzz_evobackup b/client/zzz_evobackup index 9111b01..dc24372 100644 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -149,7 +149,9 @@ local_tasks() { # Very common strategy for a single instance server with default configuration : # ### dump_mysql_global; dump_mysql_grants; dump_mysql_summary - + # + # See below for details regarding dump functions for MySQL/MariaDB + # Dump all databases in a single compressed file # # Options : From 4b71218ae13700b19bb0a172ac45da2c60d7e3ca Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 15:17:08 +0100 Subject: [PATCH 078/105] simplify cron script --- client/lib/main.sh | 2 +- client/zzz_evobackup | 101 +++++++++++++------------------------------ 2 files changed, 32 insertions(+), 71 deletions(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index 4d61839..7a1c831 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -407,7 +407,7 @@ setup() { } -main() { +run_evobackup() { # Start timer START_EPOCH=$(/bin/date +%s) START_TIME=$(/bin/date +"%Y%m%d%H%M%S") diff --git a/client/zzz_evobackup b/client/zzz_evobackup index dc24372..c2622b8 100644 --- a/client/zzz_evobackup +++ b/client/zzz_evobackup @@ -1,18 +1,7 @@ #!/bin/bash # -# Script Evobackup client +# Evobackup client # See https://gitea.evolix.org/evolix/evobackup -# -# Authors: Evolix , -# Gregory Colpart , -# Romain Dessort , -# Benoit Série , -# Tristan Pilat , -# Victor Laborie , -# Jérémy Lecour -# and others. -# -# Licence: AGPLv3 ####################################################################### # @@ -21,9 +10,6 @@ # There is some optional configuration that you can do # at the end of this script. # -# The library (usually installed at /usr/local/lib/evobackup/main.sh) -# also has many variables that you can override for fine-tuning. -# ####################################################################### # Email adress for notifications @@ -31,98 +17,73 @@ MAIL=jdoe@example.com ####################################################################### # -# The "sync_tasks" function will be called by the main function. +# The "sync_tasks" function will be called by the "main" function. # # You can customize the variables: -# * "sync_name" (String) +# * "SYNC_NAME" (String) # * "SERVERS" (Array of HOST:PORT) # * "RSYNC_INCLUDES" (Array of paths to include) # * "RSYNC_EXCLUDES" (Array of paths to exclude) # +# WARNING: remember to single-quote paths if they contain globs (*) +# and you want to pass them as-is to Rsync. +# # The "sync" function can be called multiple times # with a different set of variables. # That way you can to sync to various destinations. # +# Default includes/excludes are defined in the "main" library, +# referenced at this end of this file. +# ####################################################################### +# shellcheck disable=SC2034 sync_tasks() { ########## System-only backup (to Evolix servers) ################# - # Name your sync task, for logs - sync_name="evolix-system" - - # List of host/port for your sync task - # shellcheck disable=SC2034 + SYNC_NAME="evolix-system" SERVERS=( node0.backup.evolix.net:2234 node1.backup.evolix.net:2234 ) - - # What to include in your sync task - # Add or remove paths if you need - # shellcheck disable=SC2034 RSYNC_INCLUDES=( "${rsync_default_includes[@]}" /etc /root /var ) - - # What to exclude from your sync task - # Add or remove paths if you need - # shellcheck disable=SC2034 RSYNC_EXCLUDES=( "${rsync_default_excludes[@]}" ) - - # Call the sync task - sync "${sync_name}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" + sync "${SYNC_NAME}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" ########## Full backup (to client servers) ######################## - # Name your sync task, for logs - sync_name="client-full" - - # List of host/port for your sync task - # shellcheck disable=SC2034 - SERVERS=( - client-backup00.evolix.net:2221 - client-backup01.evolix.net:2221 - ) - - # What to include in your sync task - # Add or remove paths if you need - # NOTE: remember to single-quote paths if they contain globs (*) - # and you want to defer expansion - # shellcheck disable=SC2034 - RSYNC_INCLUDES=( - "${rsync_default_includes[@]}" - /etc - /root - /var - /home - /srv - ) - - # What to exclude from your sync task - # Add or remove paths if you need - # NOTE: remember to single-quote paths if they contain globs (*) - # and you want to defer expansion - # shellcheck disable=SC2034 - RSYNC_EXCLUDES=( - "${rsync_default_excludes[@]}" - ) - - # Call the sync task - sync "${sync_name}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" + ### SYNC_NAME="client-full" + ### SERVERS=( + ### client-backup00.evolix.net:2221 + ### client-backup01.evolix.net:2221 + ### ) + ### RSYNC_INCLUDES=( + ### "${rsync_default_includes[@]}" + ### /etc + ### /root + ### /var + ### /home + ### /srv + ### ) + ### RSYNC_EXCLUDES=( + ### "${rsync_default_excludes[@]}" + ### ) + ### sync "${SYNC_NAME}" "SERVERS[@]" "RSYNC_INCLUDES[@]" "RSYNC_EXCLUDES[@]" } ####################################################################### # -# The "local_tasks" function will be called by the main function. +# The "local_tasks" function will be called by the "main" function. # # You can call any available "dump_xxx" function # (usually installed at /usr/local/lib/evobackup/dump-*.sh) @@ -354,4 +315,4 @@ source "${LIBDIR}/main.sh" ########## Let's go! ################################################## -main +run_evobackup From ceb12254be301bcc30c6346be72d886ed3f9aceb Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 15:17:39 +0100 Subject: [PATCH 079/105] =?UTF-8?q?add=20simple=20bin=20script=20to=20show?= =?UTF-8?q?=20version=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/bin/evobackup | 31 +++++++++++++++++++++++++++++++ client/lib/main.sh | 19 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 client/bin/evobackup diff --git a/client/bin/evobackup b/client/bin/evobackup new file mode 100644 index 0000000..d9a1317 --- /dev/null +++ b/client/bin/evobackup @@ -0,0 +1,31 @@ +#!/bin/sh + +# Change this to wherever you install the libraries +LIBDIR="/usr/local/lib/evobackup" + +source "${LIBDIR}/main.sh" + +# Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a +while :; do + case ${1:-''} in + -V|--version) + show_version + exit 0 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift +done diff --git a/client/lib/main.sh b/client/lib/main.sh index 7a1c831..b49a40b 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -18,6 +18,25 @@ source "${LIBDIR}/dump-mysql.sh" source "${LIBDIR}/dump-postgresql.sh" source "${LIBDIR}/dump-misc.sh" +show_version() { + cat <, + Gregory Colpart , + Romain Dessort , + Benoit Série , + Tristan Pilat , + Victor Laborie , + Jérémy Lecour + and others. + +evobackup comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU General Public License v3.0 for details. +END +} + # Called from main, it is wrapping the local_tasks function defined in the real script local_tasks_wrapper() { log "START LOCAL_TASKS" From 98bfc5d84083b180f211153d9fe9c71655618b65 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 18:15:39 +0100 Subject: [PATCH 080/105] Introducing evobackupctl --- client/bin/evobackup | 31 ----- client/bin/evobackupctl | 120 ++++++++++++++++++ client/deploy-evobackup-beta.yml | 56 -------- client/lib/main.sh | 21 --- .../{zzz_evobackup => lib/zzz_evobackup.sh} | 5 + 5 files changed, 125 insertions(+), 108 deletions(-) delete mode 100644 client/bin/evobackup create mode 100644 client/bin/evobackupctl delete mode 100644 client/deploy-evobackup-beta.yml rename client/{zzz_evobackup => lib/zzz_evobackup.sh} (98%) diff --git a/client/bin/evobackup b/client/bin/evobackup deleted file mode 100644 index d9a1317..0000000 --- a/client/bin/evobackup +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -# Change this to wherever you install the libraries -LIBDIR="/usr/local/lib/evobackup" - -source "${LIBDIR}/main.sh" - -# Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a -while :; do - case ${1:-''} in - -V|--version) - show_version - exit 0 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "unknown option '${1}' (ignored)" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift -done diff --git a/client/bin/evobackupctl b/client/bin/evobackupctl new file mode 100644 index 0000000..4942ba3 --- /dev/null +++ b/client/bin/evobackupctl @@ -0,0 +1,120 @@ +#!/bin/bash + +readonly VERSION="23.12-pre" + +# shellcheck disable=SC2155 +readonly PROGNAME=$(basename "${0}") +# shellcheck disable=SC2155 +readonly PROGDIR=$(readlink -m "$(dirname "${0}")") +# shellcheck disable=SC2124 +readonly ARGS=$@ + +# Change this to wherever you install the libraries +readonly LIBDIR="/usr/local/lib/evobackup" + +show_version() { + cat <, + Gregory Colpart , + Romain Dessort , + Benoit Série , + Tristan Pilat , + Victor Laborie , + Jérémy Lecour + and others. + +${PROGNAME} comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU General Public License v3.0 for details. +END +} +show_help() { + cat <&2 + exit 1 + elif [ ! -e "${dest_dir}" ]; then + printf "Parent directory '%s' doesn't exist. Create it first.\n" "${dest_dir}" >&2 + exit 1 + else + if cp "${LIBDIR}/zzz_evobackup.sh" "${dest_path}"; then + chmod 750 "${dest_path}" + + sed -i "s|@COMMAND@|${PROGDIR}/${PROGNAME} ${ARGS}|" "${dest_path}" + sed -i "s|@DATE@|$(date --iso-8601=seconds)|" "${dest_path}" + sed -i "s|@VERSION@|${VERSION}|" "${dest_path}" + + printf "New evobackup script has been saved to '%s'.\n" "${dest_path}" + printf "Remember to customize it (mail notifications, backup servers…).\n" + exit 0 + fi + fi +} + +main() { + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + -V|--version) + show_version + exit 0 + ;; + -h|--help) + show_help + exit 0 + ;; + --copy-template) + # copy-template option, with value separated by space + if [ -n "$2" ]; then + copy_template "${2}" + shift + else + printf "'%s' requires a non-empty option argument.\n" "--copy-template" >&2 + exit 1 + fi + ;; + --copy-template=?*) + # copy-template option, with value separated by = + copy_template "${1#*=}" + ;; + --copy-template=) + # copy-template option, without value + printf "'%s' requires a non-empty option argument.\n" "--copy-template" >&2 + exit 1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + printf "unknown option '%s'.\n" "${1}" >&2 + exit 1 + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done +} + +main ${ARGS} diff --git a/client/deploy-evobackup-beta.yml b/client/deploy-evobackup-beta.yml deleted file mode 100644 index d716164..0000000 --- a/client/deploy-evobackup-beta.yml +++ /dev/null @@ -1,56 +0,0 @@ -### -# ansible-playbook deploy-evobackup-beta.yml -K --diff -l HOSTNAME --check -# ---- - -- name: Deploy a beta version of evobackup - hosts: all - gather_facts: true - become: true - - vars: - evobackup_script_path: /etc/cron.daily/zzz_evobackup_beta - evobackup_mail: alert4@evolix.net - evobackup_libdir: "/usr/local/lib/evobackup" - - tasks: - - name: LIBDIR is present - file: - path: "{{ evobackup_libdir }}" - owner: root - group: root - mode: "0755" - state: directory - - - name: libraries are installed - copy: - src: "{{ item }}" - dest: "{{ evobackup_libdir }}/" - remote_src: false - owner: root - group: root - mode: "0644" - force: true - loop: "{{ lookup('fileglob', 'lib/*.sh', wantlist=True) }}" - - - name: script is present - copy: - src: zzz_evobackup - dest: "{{ evobackup_script_path }}" - remote_src: false - owner: root - group: root - mode: "0750" - force: false - - - name: Email is customized - replace: - dest: "{{ evobackup_script_path }}" - regexp: "^MAIL=.*" - replace: "MAIL={{ evobackup_mail }}" - - - name: LIBDIR is customized - replace: - dest: "{{ evobackup_script_path }}" - regexp: "^LIBDIR=.*" - replace: "LIBDIR=\"{{ evobackup_libdir }}\"" \ No newline at end of file diff --git a/client/lib/main.sh b/client/lib/main.sh index b49a40b..12bd4ef 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -1,8 +1,6 @@ #!/bin/bash # shellcheck disable=SC2034,SC2317 -readonly VERSION="23.07-pre" - # set all programs to C language (english) export LC_ALL=C @@ -18,25 +16,6 @@ source "${LIBDIR}/dump-mysql.sh" source "${LIBDIR}/dump-postgresql.sh" source "${LIBDIR}/dump-misc.sh" -show_version() { - cat <, - Gregory Colpart , - Romain Dessort , - Benoit Série , - Tristan Pilat , - Victor Laborie , - Jérémy Lecour - and others. - -evobackup comes with ABSOLUTELY NO WARRANTY. This is free software, -and you are welcome to redistribute it under certain conditions. -See the GNU General Public License v3.0 for details. -END -} - # Called from main, it is wrapping the local_tasks function defined in the real script local_tasks_wrapper() { log "START LOCAL_TASKS" diff --git a/client/zzz_evobackup b/client/lib/zzz_evobackup.sh similarity index 98% rename from client/zzz_evobackup rename to client/lib/zzz_evobackup.sh index c2622b8..5fea710 100644 --- a/client/zzz_evobackup +++ b/client/lib/zzz_evobackup.sh @@ -2,6 +2,11 @@ # # Evobackup client # See https://gitea.evolix.org/evolix/evobackup +# +# This is a generated backup script made by: +# command: @COMMAND@ +# version: @VERSION@ +# date: @DATE@ ####################################################################### # From ebc20cf4e8ee17b092ac03f0d233e1a9374c6d86 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 18:24:27 +0100 Subject: [PATCH 081/105] cleanup + changelog --- client/CHANGELOG.md | 5 ++ client/update-evobackup-canary | 129 --------------------------------- 2 files changed, 5 insertions(+), 129 deletions(-) delete mode 100644 client/update-evobackup-canary diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index e87f7e4..d743123 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -11,11 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed * split functions into libraries +* add evobackupctl script +* change the "zzz_evobackup" script to a template, easy to copy with evobackupctl ### Deprecated ### Removed +* update-evobackup-canary is managed by ansible-roles.git +* deployment by Ansible is managed elsewhere (now in evolix-private.git, later in ansible-roles.git) + ### Fixed ### Security diff --git a/client/update-evobackup-canary b/client/update-evobackup-canary deleted file mode 100644 index 868c3be..0000000 --- a/client/update-evobackup-canary +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/sh - -PROGNAME="update-evobackup-canary" -REPOSITORY="https://gitea.evolix.org/evolix/evobackup" - -VERSION="22.06" -readonly VERSION - -# base functions - -show_version() { - cat <, - Jérémy Lecour , - and others. - -${REPOSITORY} - -${PROGNAME} comes with ABSOLUTELY NO WARRANTY. This is free software, -and you are welcome to redistribute it under certain conditions. -See the GNU General Public License v3.0 for details. -END -} -show_help() { - cat <> "${canary_file}" -} - -# parse options -# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a -while :; do - case $1 in - -h|-\?|--help) - show_help - exit 0 - ;; - -V|--version) - show_version - exit 0 - ;; - - -w|--who) - # with value separated by space - if [ -n "$2" ]; then - who=$2 - shift - else - printf 'ERROR: "-w|--who" requires a non-empty option argument.\n' >&2 - exit 1 - fi - ;; - --who=?*) - # with value speparated by = - who=${1#*=} - ;; - --who=) - # without value - printf 'ERROR: "--who" requires a non-empty option argument.\n' >&2 - exit 1 - ;; - - -f|--file) - # with value separated by space - if [ -n "$2" ]; then - canary_file=$2 - shift - else - printf 'ERROR: "-f|--file" requires a non-empty option argument.\n' >&2 - exit 1 - fi - ;; - --file=?*) - # with value speparated by = - canary_file=${1#*=} - ;; - --file=) - # without value - printf 'ERROR: "--file" requires a non-empty option argument.\n' >&2 - exit 1 - ;; - - --) - # End of all options. - shift - break - ;; - -?*) - # ignore unknown options - printf 'WARN: Unknown option : %s\n' "$1" >&2 - exit 1 - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift -done - -export LC_ALL=C - -set -u - -main From 28a0f7a17a74af5b2bb8d9008415a8101904f70c Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 29 Dec 2023 18:26:07 +0100 Subject: [PATCH 082/105] CHANGELOG update (no semver) --- client/CHANGELOG.md | 9 +++++++-- server/CHANGELOG.md | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index d743123..43cd327 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -1,8 +1,13 @@ # Changelog + All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). + +This project does not follow semantic versioning. +The **major** part of the version is the year +The **minor** part changes is the month +The **patch** part changes is incremented if multiple releases happen the same month ## [Unreleased] diff --git a/server/CHANGELOG.md b/server/CHANGELOG.md index a3bfad3..c62e455 100644 --- a/server/CHANGELOG.md +++ b/server/CHANGELOG.md @@ -1,8 +1,13 @@ # Changelog + All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). + +This project does not follow semantic versioning. +The **major** part of the version is the year +The **minor** part changes is the month +The **patch** part changes is incremented if multiple releases happen the same month ## [Unreleased] From 0f0b328f27a5f23e3b3b90fdddd2f6686414799f Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 2 Jan 2024 12:11:55 +0100 Subject: [PATCH 083/105] include main.sh in evobackupctl --- client/bin/evobackupctl | 4 ++-- client/lib/main.sh | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/bin/evobackupctl b/client/bin/evobackupctl index 4942ba3..f8d68c3 100644 --- a/client/bin/evobackupctl +++ b/client/bin/evobackupctl @@ -1,7 +1,5 @@ #!/bin/bash -readonly VERSION="23.12-pre" - # shellcheck disable=SC2155 readonly PROGNAME=$(basename "${0}") # shellcheck disable=SC2155 @@ -12,6 +10,8 @@ readonly ARGS=$@ # Change this to wherever you install the libraries readonly LIBDIR="/usr/local/lib/evobackup" +source "${LIBDIR}/main.sh" + show_version() { cat < Date: Mon, 8 Jan 2024 23:01:43 +0100 Subject: [PATCH 084/105] Vagrant definition for manual tests --- client/CHANGELOG.md | 2 ++ client/Vagrantfile | 49 +++++++++++++++++++++++++++++ client/vagrant.yml | 76 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 client/Vagrantfile create mode 100644 client/vagrant.yml diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index 43cd327..1e112a7 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -13,6 +13,8 @@ The **patch** part changes is incremented if multiple releases happen the same m ### Added +* Vagrant definition for manual tests + ### Changed * split functions into libraries diff --git a/client/Vagrantfile b/client/Vagrantfile new file mode 100644 index 0000000..de3337a --- /dev/null +++ b/client/Vagrantfile @@ -0,0 +1,49 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Load ~/.VagrantFile if exist, permit local config provider +vagrantfile = File.join(Dir.home, ".VagrantFile") +load File.expand_path(vagrantfile) if File.exist?(vagrantfile) + +Vagrant.configure("2") do |config| + # Run "vagrant rsync-auto" to sync after each change + config.vm.synced_folder ".", "/vagrant", type: "rsync", disabled: true + config.vm.synced_folder "bin", "/usr/local/bin", type: "rsync" + config.vm.synced_folder "lib", "/usr/local/lib/evobackup", type: "rsync" + + config.ssh.shell = "/bin/sh" + + config.vm.provider :libvirt do |libvirt| + # libvirt.storage :file, :size => '10G', :device => 'vdb' + libvirt.memory = 1024 + libvirt.cpus = 1 + end + + config_script = <<~SCRIPT + set -e + sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/; s/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \ + echo 'LANG="fr_FR.UTF-8"'>/etc/default/locale && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + update-locale LANG=fr_FR.UTF-8 + exit 0 + SCRIPT + + [ + {version: "buster"}, + {version: "bullseye"}, + {version: "bookworm"} + ].each do |i| + config.vm.define(i[:version].to_s) do |node| + node.vm.hostname = "evobackup-#{i[:version]}" + node.vm.box = "debian/#{i[:version]}64" + + node.vm.provision "config", type: "shell", inline: config_script + + node.vm.provision :ansible do |ansible| + ansible.playbook = "vagrant.yml" + end + + end + end + +end diff --git a/client/vagrant.yml b/client/vagrant.yml new file mode 100644 index 0000000..39647a5 --- /dev/null +++ b/client/vagrant.yml @@ -0,0 +1,76 @@ +# To be used through "vagrant up" or "vagrant provision". +--- +- hosts: bookworm,bullseye,buster + gather_facts: yes + become: yes + + vars_files: + - '~/GIT/evolix-private/vars/evolinux-secrets.yml' + + vars: + evolinux_hostname: "localhost" + evolinux_domain: "localdomain.tld" + evomaintenance_alert_email: "evomaintenance-{{ evolinux_hostname }}@evolix.fr" + evomaintenance_install_vendor: True + client_number: "XXX" + monitoring_mode: "everytime" + evocheck_force_install: "local" + evoadmin_host: "evoadmin.{{ evolinux_hostname }}.evolix.eu" + evoadmin_contact_email: root@localhost + postfix_slow_transport_include: True + + evolinux_ssh_allow_current_user: True + + minifirewall_additional_trusted_ips: ["192.168.0.0/16", "10.0.0.0/8"] + minifirewall_http_sites: ["0.0.0.0/0"] + + packweb_enable_evoadmin_vhost: True + packweb_phpmyadmin_suffix: "uE34swx9" + + evolinux_apt_include: True + evolinux_etcgit_include: True + evolinux_hostname_include: True + evolinux_kernel_include: True + evolinux_fstab_include: True + evolinux_packages_include: True + evolinux_system_include: True + evolinux_evomaintenance_include: True + evolinux_ssh_include: True + evolinux_users_include: False + evolinux_root_include: True + evolinux_postfix_include: True + evolinux_logs_include: True + evolinux_default_www_include: True + evolinux_hardware_include: True + evolinux_provider_online_include: False + evolinux_provider_orange_fce_include: False + evolinux_log2mail_include: True + evolinux_minifirewall_include: True + evolinux_munin_include: True + evolinux_nagios_nrpe_include: True + evolinux_fail2ban_include: False + mysql_custom_datadir: '/home/mysql' + mysql_custom_tmpdir: '/home/tmpmysql' + mysql_custom_logdir: '/home/mysql-logs' + # evolinux_apt_public_sources: False + apt_upgrade: True + + # TODO Try to to make it work without the following line + # packweb_multiphp_versions: + # - php74 + # - php82 + + # autosysadmin_config: + # repair_http: "on" + # repair_mysql: off + # repair_all: 'off' + + roles: + - mysql + # - evolinux-base + # # - evolinux-users + # - ./ansible/roles/autosysadmin-agent + # - packweb-apache + # # - redis + # - { role: redis, redis_instance_name: foo, redis_port: 6380 } + # - { role: redis, redis_instance_name: bar, redis_port: 6381 } From 257679364d461e0e003dad27a51f01edc84c263d Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 9 Jan 2024 08:45:24 +0100 Subject: [PATCH 085/105] rewrite (again) mysql functions --- client/lib/dump-mysql.sh | 392 ++++++++++++++++++++++++++++++++------- client/lib/utilities.sh | 7 + 2 files changed, 331 insertions(+), 68 deletions(-) diff --git a/client/lib/dump-mysql.sh b/client/lib/dump-mysql.sh index df329fa..7689446 100644 --- a/client/lib/dump-mysql.sh +++ b/client/lib/dump-mysql.sh @@ -1,13 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash # shellcheck disable=SC2034,SC2317,SC2155 -mysql_list_databases() { - port=${1:-"3306"} - - mysql --defaults-extra-file=/etc/mysql/debian.cnf --port="${port}" --execute="show databases" --silent --skip-column-names \ - | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)" -} - ####################################################################### # Dump complete summary of an instance (using pt-mysql-summary) # @@ -210,15 +203,16 @@ dump_mysql_summary() { option_dump_label="${option_defaults_group_suffix}" elif [ -n "${option_port}" ]; then option_dump_label="${option_port}" + elif [ -n "${option_socket}" ]; then + option_dump_label=$(path_to_str "${option_socket}") else option_dump_label="default" fi fi - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}" + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}-summary" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - # DO NOT REMOVE EXISTING DIRECTORIES - # rm -rf "${dump_dir}" "${errors_dir}" + rm -rf "${dump_dir}" "${errors_dir}" mkdir -p "${dump_dir}" "${errors_dir}" # No need to change recursively, the top directory is enough chmod 700 "${dump_dir}" "${errors_dir}" @@ -434,15 +428,16 @@ dump_mysql_grants() { if [ -z "${option_dump_label}" ]; then if [ -n "${option_port}" ]; then option_dump_label="${option_port}" + elif [ -n "${option_socket}" ]; then + option_dump_label=$(path_to_str "${option_socket}") else option_dump_label="default" fi fi - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}" + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}-grants" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - # DO NOT REMOVE EXISTING DIRECTORIES - # rm -rf "${dump_dir}" "${errors_dir}" + rm -rf "${dump_dir}" "${errors_dir}" mkdir -p "${dump_dir}" "${errors_dir}" # No need to change recursively, the top directory is enough chmod 700 "${dump_dir}" "${errors_dir}" @@ -491,6 +486,7 @@ dump_mysql_grants() { ####################################################################### # Dump a single compressed file of all databases of an instance +# and a file containing only the schema. # # Arguments: # --masterdata (default: ) @@ -503,6 +499,8 @@ dump_mysql_grants() { # --defaults-group-suffix=[String] (default: ) # --dump-label=[String] (default: "default") # used as suffix of the dump dir to differenciate multiple instances +# --compress= (default: "gzip") +# Other options after -- are passed as-is to mysqldump ####################################################################### dump_mysql_global() { local option_masterdata="" @@ -514,6 +512,8 @@ dump_mysql_global() { local option_user="" local option_password="" local option_dump_label="" + local option_compress="" + local option_others="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do @@ -673,9 +673,29 @@ dump_mysql_global() { log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." exit 1 ;; + --compress) + # compress options, with value separated by space + if [ -n "$2" ]; then + option_compress="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + fi + ;; + --compress=?*) + # compress options, with value separated by = + option_compress="${1#*=}" + ;; + --compress=) + # compress options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + ;; --) # End of all options. shift + option_others=${*} break ;; -?*|[[:alnum:]]*) @@ -691,27 +711,37 @@ dump_mysql_global() { shift done + case "${option_compress}" in + none) + compress_cmd="cat" + dump_ext="" + ;; + bzip2|bz|bz2) + compress_cmd="bzip2 --best" + dump_ext=".bz" + ;; + xz) + compress_cmd="xz --best" + dump_ext=".xz" + ;; + gz|gzip|*) + compress_cmd="gzip --best" + dump_ext=".gz" + ;; + esac + if [ -z "${option_dump_label}" ]; then if [ -n "${option_defaults_group_suffix}" ]; then option_dump_label="${option_defaults_group_suffix}" elif [ -n "${option_port}" ]; then option_dump_label="${option_port}" + elif [ -n "${option_socket}" ]; then + option_dump_label=$(path_to_str "${option_socket}") else option_dump_label="default" fi fi - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - # No need to change recursively, the top directory is enough - chmod 700 "${dump_dir}" "${errors_dir}" - - local error_file="${errors_dir}/mysqldump.err" - local dump_file="${dump_dir}/mysqldump.sql.gz" - log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - ## Connection options declare -a connect_options connect_options=() @@ -740,6 +770,18 @@ dump_mysql_global() { fi ## Global all databases in one file + + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}/mysqldump.err" + local dump_file="${dump_dir}/mysqldump.sql${dump_ext}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + declare -a dump_options dump_options=() dump_options+=(--opt) @@ -750,20 +792,29 @@ dump_mysql_global() { if [ -n "${option_masterdata}" ]; then dump_options+=("${option_masterdata}") fi + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + dump_options+=(${option_others}) + fi - mysqldump "${connect_options[@]}" "${dump_options[@]}" 2> "${error_file}" | gzip --best > "${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} | ${compress_cmd} > ${dump_file}" + ${dump_cmd} 2> "${error_file}" | ${compress_cmd} > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump returned an error ${last_rc}" "${error_file}" GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" fi log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + ## Schema only (no data) for each databases + local error_file="${errors_dir}/mysqldump.schema.err" local dump_file="${dump_dir}/mysqldump.schema.sql" log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" @@ -773,13 +824,20 @@ dump_mysql_global() { dump_options+=(--force) dump_options+=(--no-data) dump_options+=(--all-databases) + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + dump_options+=(${option_others}) + fi - mysqldump "${connect_options[@]}" "${dump_options[@]}" 2> "${error_file}" > "${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} > ${dump_file}" + ${dump_cmd} 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump returned an error ${last_rc}" "${error_file}" GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" @@ -788,7 +846,8 @@ dump_mysql_global() { } ####################################################################### -# Dump a compressed file per database of an instance +# Dump a file of each databases of an instance +# and a file containing only the schema. # # Arguments: # --port=[Integer] (default: ) @@ -800,15 +859,81 @@ dump_mysql_global() { # --defaults-group-suffix=[String] (default: ) # --dump-label=[String] (default: "default") # used as suffix of the dump dir to differenciate multiple instances +# --compress= (default: "gzip") +# Other options after -- are passed as-is to mysqldump ####################################################################### dump_mysql_per_base() { local option_port="" local option_socket="" + local option_defaults_file="" + local option_defaults_extra_file="" + local option_defaults_group_suffix="" + local option_user="" + local option_password="" local option_dump_label="" + local option_compress="" + local option_others="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in + --defaults-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-file=?*) + # defaults-file options, with value separated by = + option_defaults_file="${1#*=}" + ;; + --defaults-file=) + # defaults-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + ;; + --defaults-extra-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_extra_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-extra-file=?*) + # defaults-extra-file options, with value separated by = + option_defaults_extra_file="${1#*=}" + ;; + --defaults-extra-file=) + # defaults-extra-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-extra-file' requires a non-empty option argument." + exit 1 + ;; + --defaults-group-suffix) + # defaults-group-suffix options, with value separated by space + if [ -n "$2" ]; then + option_defaults_group_suffix="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-group-suffix' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-group-suffix=?*) + # defaults-group-suffix options, with value separated by = + option_defaults_group_suffix="${1#*=}" + ;; + --defaults-group-suffix=) + # defaults-group-suffix options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-group-suffix' requires a non-empty option argument." + exit 1 + ;; --port) # port options, with value separated by space if [ -n "$2" ]; then @@ -847,9 +972,86 @@ dump_mysql_per_base() { log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." exit 1 ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --dump-label) + # dump-label options, with value separated by space + if [ -n "$2" ]; then + option_dump_label="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + fi + ;; + --dump-label=?*) + # dump-label options, with value separated by = + option_dump_label="${1#*=}" + ;; + --dump-label=) + # dump-label options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + ;; + --compress) + # compress options, with value separated by space + if [ -n "$2" ]; then + option_compress="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + fi + ;; + --compress=?*) + # compress options, with value separated by = + option_compress="${1#*=}" + ;; + --compress=) + # compress options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + ;; --) # End of all options. shift + option_others=${*} break ;; -?*|[[:alnum:]]*) @@ -865,89 +1067,138 @@ dump_mysql_per_base() { shift done - option_dump_label="${option_dump_label:-default}" + case "${option_compress}" in + none) + compress_cmd="cat" + dump_ext="" + ;; + bzip2|bz|bz2) + compress_cmd="bzip2 --best" + dump_ext=".bz" + ;; + xz) + compress_cmd="xz --best" + dump_ext=".xz" + ;; + gz|gzip|*) + compress_cmd="gzip --best" + dump_ext=".gz" + ;; + esac + if [ -z "${option_dump_label}" ]; then + if [ -n "${option_defaults_group_suffix}" ]; then + option_dump_label="${option_defaults_group_suffix}" + elif [ -n "${option_port}" ]; then + option_dump_label="${option_port}" + elif [ -n "${option_socket}" ]; then + option_dump_label=$(path_to_str "${option_socket}") + else + option_dump_label="default" + fi + fi + + ## Connection options declare -a connect_options connect_options=() + if [ -n "${option_defaults_file}" ]; then + connect_options+=(--defaults-file="${option_defaults_file}") + fi + if [ -n "${option_defaults_extra_file}" ]; then + connect_options+=(--defaults-extra-file="${option_defaults_extra_file}") + fi + if [ -n "${option_defaults_group_suffix}" ]; then + connect_options+=(--defaults-group-suffix="${option_defaults_group_suffix}") + fi if [ -n "${option_port}" ]; then + connect_options+=(--protocol=tcp) connect_options+=(--port="${option_port}") fi if [ -n "${option_socket}" ]; then connect_options+=(--protocol=socket) connect_options+=(--socket="${option_socket}") fi + if [ -n "${option_user}" ]; then + connect_options+=(--user="${option_user}") + fi + if [ -n "${option_password}" ]; then + connect_options+=(--password="${option_password}") + fi - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}/databases" + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}-per-base" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" mkdir -p "${dump_dir}" "${errors_dir}" # No need to change recursively, the top directory is enough chmod 700 "${dump_dir}" "${errors_dir}" - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--force) - options+=(--events) - options+=(--hex-blob) - - databases=$(mysql_list_databases "${connect_options[@]}") + databases=$(mysql "${connect_options[@]}" --execute="show databases" --silent --skip-column-names \ + | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)") for database in ${databases}; do local error_file="${errors_dir}/${database}.err" - local dump_file="${dump_dir}/${database}.sql.gz" + local dump_file="${dump_dir}/${database}.sql${dump_ext}" log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - mysqldump "${connect_options[@]}" "${options[@]}" "${database}" 2> "${error_file}" | gzip --best > "${dump_file}" + declare -a dump_options + dump_options=() + dump_options+=(--opt) + dump_options+=(--force) + dump_options+=(--events) + dump_options+=(--hex-blob) + dump_options+=(--databases "${database}") + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + dump_options+=(${option_others}) + fi + + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} | ${compress_cmd} > ${dump_file}" + ${dump_cmd} 2> "${error_file}" | ${compress_cmd} > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump returned an error ${last_rc}" "${error_file}" GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" fi log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" - done - ## Schema only (no data) for each databases - for database in ${databases}; do + + ## Schema only (no data) for each databases + local error_file="${errors_dir}/${database}.schema.err" local dump_file="${dump_dir}/${database}.schema.sql" log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - options+=(--force) - options+=(--no-data) - options+=(--databases "${database}") + declare -a dump_options + dump_options=() + dump_options+=(--force) + dump_options+=(--no-data) + dump_options+=(--databases "${database}") + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + dump_options+=(${option_others}) + fi - mysqldump "${connect_options[@]}" "${options[@]}" 2> "${error_file}" > "${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} > ${dump_file}" + ${dump_cmd} 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump to ${dump_file} returned an error ${last_rc}" "${error_file}" + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump returned an error ${last_rc}" "${error_file}" GLOBAL_RC=${E_DUMPFAILED} else rm -f "${error_file}" fi log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" done - - ## Grants and summary - if [ -n "${option_port}" ]; then - dump_mysql_grants --port="${option_port}" - dump_mysql_summary --port="${option_port}" - elif [ -n "${option_socket}" ]; then - dump_mysql_grants --socket="${option_socket}" - dump_mysql_summary --socket="${option_socket}" - else - dump_mysql_grants - dump_mysql_summary - fi } ####################################################################### @@ -1031,10 +1282,11 @@ dump_mysql_tabs() { option_dump_label="${option_dump_label:-default}" - databases=$(mysql_list_databases "${option_port}") + databases=$(mysql "${connect_options[@]}" --execute="show databases" --silent --skip-column-names \ + | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)") for database in ${databases}; do - local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}/tabs/${database}" + local dump_dir="${LOCAL_BACKUP_DIR}/mysql-${option_dump_label}-tabs/${database}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" mkdir -p "${dump_dir}" "${errors_dir}" @@ -1067,6 +1319,10 @@ dump_mysql_tabs() { options+=("${database}") mysqldump "${options[@]}" 2> "${error_file}" + + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} > ${dump_file}" + ${dump_cmd} 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh index 476ffb9..f451f13 100644 --- a/client/lib/utilities.sh +++ b/client/lib/utilities.sh @@ -135,3 +135,10 @@ pick_server() { send_mail() { tail -20 "${LOGFILE}" | mail -s "${MAIL_SUBJECT}" "${MAIL}" } + +path_to_str() { + local path=$1 + local str="${path}" + + echo "${path}" | sed -e 's|^/||; s|/$||; s|/|:|g' +} From a5ff9199b3309444f284cbe1d1c64719e3a5cc45 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 9 Jan 2024 08:46:58 +0100 Subject: [PATCH 086/105] long option name --- client/lib/main.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index 22db492..d84c159 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -8,7 +8,7 @@ export LC_ALL=C # If expansion is attempted on an unset variable or parameter, the shell prints an # error message, and, if not interactive, exits with a non-zero status. -set -u +set -o nounset # The pipeline's return status is the value of the last (rightmost) command # to exit with a non-zero status, or zero if all commands exit successfully. set -o pipefail From 7a9663781ddbd463af99b2ebdba71aab23a7abed Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 9 Jan 2024 08:47:18 +0100 Subject: [PATCH 087/105] add automatic trace mode if TRACE=1 --- client/lib/main.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/lib/main.sh b/client/lib/main.sh index d84c159..2fcae8f 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -12,6 +12,10 @@ set -o nounset # The pipeline's return status is the value of the last (rightmost) command # to exit with a non-zero status, or zero if all commands exit successfully. set -o pipefail +# Enable trace mode if called with environment variable TRACE=1 +if [[ "${TRACE-0}" == "1" ]]; then + set -o xtrace +fi source "${LIBDIR}/utilities.sh" source "${LIBDIR}/dump-mysql.sh" From f3e25a7beb509daf63d0d1cad1cfeda88535a48f Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 9 Jan 2024 08:48:42 +0100 Subject: [PATCH 088/105] Use env-based shebang for shell scripts --- client/CHANGELOG.md | 1 + client/bin/evobackupctl | 2 +- client/lib/dump-misc.sh | 2 +- client/lib/dump-postgresql.sh | 2 +- client/lib/main.sh | 2 +- client/lib/utilities.sh | 2 +- client/lib/zzz_evobackup.sh | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index 1e112a7..67a571c 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -20,6 +20,7 @@ The **patch** part changes is incremented if multiple releases happen the same m * split functions into libraries * add evobackupctl script * change the "zzz_evobackup" script to a template, easy to copy with evobackupctl +* use env-based shebang for shell scripts ### Deprecated diff --git a/client/bin/evobackupctl b/client/bin/evobackupctl index f8d68c3..5be5efe 100644 --- a/client/bin/evobackupctl +++ b/client/bin/evobackupctl @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # shellcheck disable=SC2155 readonly PROGNAME=$(basename "${0}") diff --git a/client/lib/dump-misc.sh b/client/lib/dump-misc.sh index 92f6e2e..7fbdafe 100644 --- a/client/lib/dump-misc.sh +++ b/client/lib/dump-misc.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # shellcheck disable=SC2034,SC2317,SC2155 ####################################################################### diff --git a/client/lib/dump-postgresql.sh b/client/lib/dump-postgresql.sh index ebbe7d6..cfe4a80 100644 --- a/client/lib/dump-postgresql.sh +++ b/client/lib/dump-postgresql.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # shellcheck disable=SC2034,SC2317,SC2155 ####################################################################### diff --git a/client/lib/main.sh b/client/lib/main.sh index 2fcae8f..e1d1ecc 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # shellcheck disable=SC2034,SC2317 readonly VERSION="23.12-pre" diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh index f451f13..d286195 100644 --- a/client/lib/utilities.sh +++ b/client/lib/utilities.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Output a message to the log file log() { diff --git a/client/lib/zzz_evobackup.sh b/client/lib/zzz_evobackup.sh index 5fea710..db922b0 100644 --- a/client/lib/zzz_evobackup.sh +++ b/client/lib/zzz_evobackup.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Evobackup client # See https://gitea.evolix.org/evolix/evobackup From 2b4333bded52951cc5c04be39250e3a6da8ee4e5 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 9 Jan 2024 11:22:18 +0100 Subject: [PATCH 089/105] path_to_str: simplify code --- client/lib/utilities.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh index d286195..df23c55 100644 --- a/client/lib/utilities.sh +++ b/client/lib/utilities.sh @@ -137,8 +137,5 @@ send_mail() { } path_to_str() { - local path=$1 - local str="${path}" - - echo "${path}" | sed -e 's|^/||; s|/$||; s|/|:|g' + echo "${1}" | sed -e 's|^/||; s|/$||; s|/|:|g' } From 8d9c60b543efb113c48cee2a87bc601b76df64d3 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 9 Jan 2024 11:24:03 +0100 Subject: [PATCH 090/105] Add command logging --- client/lib/dump-misc.sh | 60 +++++++++++++++++++++++++++++-------- client/lib/dump-mysql.sh | 38 ++++++++++++----------- client/lib/zzz_evobackup.sh | 4 ++- 3 files changed, 71 insertions(+), 31 deletions(-) diff --git a/client/lib/dump-misc.sh b/client/lib/dump-misc.sh index 7fbdafe..f060b73 100644 --- a/client/lib/dump-misc.sh +++ b/client/lib/dump-misc.sh @@ -15,9 +15,17 @@ dump_ldap() { log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${FUNCNAME[0]} to ${dump_dir}" - slapcat -n 0 -l "${dump_dir}/config.bak" - slapcat -n 1 -l "${dump_dir}/data.bak" - slapcat -l "${dump_dir}/all.bak" + dump_cmd="slapcat -n 0 -l ${dump_dir}/config.bak" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} + + dump_cmd="slapcat -n 1 -l ${dump_dir}/data.bak" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} + + dump_cmd="slapcat -l ${dump_dir}/all.bak" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${FUNCNAME[0]}" } @@ -93,7 +101,10 @@ dump_redis() { local error_file="${errors_dir}/${name}.err" log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" - cp -a "${instance}/dump.rdb" "${dump_dir}/dump.rdb" 2> "${error_file}" + # Copy the Redis database + dump_cmd="cp -a ${instance}/dump.rdb ${dump_dir}/dump.rdb 2> ${error_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -104,7 +115,10 @@ dump_redis() { rm -f "${error_file}" fi - gzip "${dump_dir}/dump.rdb" + # Compress the Redis database + dump_cmd="gzip ${dump_dir}/dump.rdb" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -213,7 +227,9 @@ dump_mongodb() { options+=(--password="${option_password}") options+=(--out="${dump_dir}/") - mongodump "${options[@]}" 2> "${error_file}" > /dev/null + dump_cmd=" mongodump ${options[*]} 2> ${error_file} > /dev/null" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -244,7 +260,9 @@ dump_megacli_config() { local dump_file="${dump_dir}/megacli.err" log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - megacli -CfgSave -f "${dump_file}" -a0 2> "${error_file}" > /dev/null + dump_cmd="megacli -CfgSave -f ${dump_file} -a0 2> ${error_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -375,7 +393,10 @@ dump_server_state() { log_error "LOCAL_TASKS - ${FUNCNAME[0]}: dump-server-state is missing" rc=1 else - ${dump_server_state_bin} "${options[@]}" + dump_cmd="${dump_server_state_bin} ${options[*]}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} + local last_rc=$? # shellcheck disable=SC2086 if [ ${last_rc} -ne 0 ]; then @@ -406,7 +427,9 @@ dump_rabbitmq() { log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - rabbitmqadmin export "${dump_file}" 2> "${error_file}" >> "${LOGFILE}" + dump_cmd="rabbitmqadmin export ${dump_file} 2> ${error_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -434,10 +457,21 @@ dump_facl() { log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" - getfacl -R /etc > "${dump_dir}/etc.txt" - getfacl -R /home > "${dump_dir}/home.txt" - getfacl -R /usr > "${dump_dir}/usr.txt" - getfacl -R /var > "${dump_dir}/var.txt" + dump_cmd="getfacl -R /etc > ${dump_dir}/etc.txt" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} + + dump_cmd="getfacl -R /home > ${dump_dir}/home.txt" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} + + dump_cmd="getfacl -R /usr > ${dump_dir}/usr.txt" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} + + dump_cmd="getfacl -R /var > ${dump_dir}/var.txt" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_dir}" } diff --git a/client/lib/dump-mysql.sh b/client/lib/dump-mysql.sh index 7689446..bdcfffd 100644 --- a/client/lib/dump-mysql.sh +++ b/client/lib/dump-mysql.sh @@ -254,7 +254,9 @@ dump_mysql_summary() { options=() options+=(--sleep=0) - pt-mysql-summary "${options[@]}" -- "${connect_options[@]}" 2> "${error_file}" > "${dump_file}" + dump_cmd="pt-mysql-summary ${options[*]} -- ${connect_options[*]} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} 2> "${error_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -468,7 +470,9 @@ dump_mysql_grants() { options+=(--flush) options+=(--no-header) - pt-show-grants "${options[@]}" 2> "${error_file}" > "${dump_file}" + dump_cmd="pt-show-grants ${options[*]} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} 2> "${error_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -798,9 +802,9 @@ dump_mysql_global() { dump_options+=(${option_others}) fi - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" - log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} | ${compress_cmd} > ${dump_file}" - ${dump_cmd} 2> "${error_file}" | ${compress_cmd} > "${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file}| ${compress_cmd} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -830,9 +834,9 @@ dump_mysql_global() { dump_options+=(${option_others}) fi - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" - log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} > ${dump_file}" - ${dump_cmd} 2> "${error_file}" > "${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -1153,9 +1157,9 @@ dump_mysql_per_base() { dump_options+=(${option_others}) fi - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" - log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} | ${compress_cmd} > ${dump_file}" - ${dump_cmd} 2> "${error_file}" | ${compress_cmd} > "${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} | ${compress_cmd} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -1185,9 +1189,9 @@ dump_mysql_per_base() { dump_options+=(${option_others}) fi - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" - log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} > ${dump_file}" - ${dump_cmd} 2> "${error_file}" > "${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 @@ -1320,9 +1324,9 @@ dump_mysql_tabs() { mysqldump "${options[@]}" 2> "${error_file}" - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" - log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} > ${dump_file}" - ${dump_cmd} 2> "${error_file}" > "${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} local last_rc=$? # shellcheck disable=SC2086 diff --git a/client/lib/zzz_evobackup.sh b/client/lib/zzz_evobackup.sh index db922b0..61fba30 100644 --- a/client/lib/zzz_evobackup.sh +++ b/client/lib/zzz_evobackup.sh @@ -279,7 +279,9 @@ local_tasks() { ### ### # Execute your dump command ### # Send errors to the error file and the data to the dump file -### my-dump-command 2> "${error_file}" > "${dump_file}" +### dump_cmd="my-dump-command 2> ${error_file} > ${dump_file}" +### log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" +### ${dump_cmd} ### ### # Check result and deal with potential errors ### local last_rc=$? From f71dd862bbc454a61e7c384e9cc7c14e1f71ae79 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 9 Jan 2024 13:30:55 +0100 Subject: [PATCH 091/105] Add options to dump_mysql_tabs --- client/lib/dump-mysql.sh | 241 +++++++++++++++++++++++++++++++++++---- 1 file changed, 217 insertions(+), 24 deletions(-) diff --git a/client/lib/dump-mysql.sh b/client/lib/dump-mysql.sh index bdcfffd..f7a6a99 100644 --- a/client/lib/dump-mysql.sh +++ b/client/lib/dump-mysql.sh @@ -1209,7 +1209,6 @@ dump_mysql_per_base() { # Dump "tabs style" separate schema/data for each database of an instance # # Arguments: -# --masterdata (default: ) # --port=[Integer] (default: ) # --socket=[String] (default: ) # --user=[String] (default: ) @@ -1219,15 +1218,81 @@ dump_mysql_per_base() { # --defaults-group-suffix=[String] (default: ) # --dump-label=[String] (default: "default") # used as suffix of the dump dir to differenciate multiple instances +# --compress= (default: "gzip") +# Other options after -- are passed as-is to mysqldump ####################################################################### dump_mysql_tabs() { local option_port="" local option_socket="" + local option_defaults_file="" + local option_defaults_extra_file="" + local option_defaults_group_suffix="" + local option_user="" + local option_password="" local option_dump_label="" + local option_compress="" + local option_others="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in + --defaults-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-file=?*) + # defaults-file options, with value separated by = + option_defaults_file="${1#*=}" + ;; + --defaults-file=) + # defaults-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + ;; + --defaults-extra-file) + # defaults-file options, with value separated by space + if [ -n "$2" ]; then + option_defaults_extra_file="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-file' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-extra-file=?*) + # defaults-extra-file options, with value separated by = + option_defaults_extra_file="${1#*=}" + ;; + --defaults-extra-file=) + # defaults-extra-file options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-extra-file' requires a non-empty option argument." + exit 1 + ;; + --defaults-group-suffix) + # defaults-group-suffix options, with value separated by space + if [ -n "$2" ]; then + option_defaults_group_suffix="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-group-suffix' requires a non-empty option argument." + exit 1 + fi + ;; + --defaults-group-suffix=?*) + # defaults-group-suffix options, with value separated by = + option_defaults_group_suffix="${1#*=}" + ;; + --defaults-group-suffix=) + # defaults-group-suffix options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--defaults-group-suffix' requires a non-empty option argument." + exit 1 + ;; --port) # port options, with value separated by space if [ -n "$2" ]; then @@ -1266,9 +1331,86 @@ dump_mysql_tabs() { log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--socket' requires a non-empty option argument." exit 1 ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --dump-label) + # dump-label options, with value separated by space + if [ -n "$2" ]; then + option_dump_label="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + fi + ;; + --dump-label=?*) + # dump-label options, with value separated by = + option_dump_label="${1#*=}" + ;; + --dump-label=) + # dump-label options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + ;; + --compress) + # compress options, with value separated by space + if [ -n "$2" ]; then + option_compress="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + fi + ;; + --compress=?*) + # compress options, with value separated by = + option_compress="${1#*=}" + ;; + --compress=) + # compress options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + ;; --) # End of all options. shift + option_others=${*} break ;; -?*|[[:alnum:]]*) @@ -1284,7 +1426,63 @@ dump_mysql_tabs() { shift done - option_dump_label="${option_dump_label:-default}" + case "${option_compress}" in + none) + compress_cmd="cat" + dump_ext="" + ;; + bzip2|bz|bz2) + compress_cmd="bzip2 --best" + dump_ext=".bz" + ;; + xz) + compress_cmd="xz --best" + dump_ext=".xz" + ;; + gz|gzip|*) + compress_cmd="gzip --best" + dump_ext=".gz" + ;; + esac + + if [ -z "${option_dump_label}" ]; then + if [ -n "${option_defaults_group_suffix}" ]; then + option_dump_label="${option_defaults_group_suffix}" + elif [ -n "${option_port}" ]; then + option_dump_label="${option_port}" + elif [ -n "${option_socket}" ]; then + option_dump_label=$(path_to_str "${option_socket}") + else + option_dump_label="default" + fi + fi + + ## Connection options + declare -a connect_options + connect_options=() + if [ -n "${option_defaults_file}" ]; then + connect_options+=(--defaults-file="${option_defaults_file}") + fi + if [ -n "${option_defaults_extra_file}" ]; then + connect_options+=(--defaults-extra-file="${option_defaults_extra_file}") + fi + if [ -n "${option_defaults_group_suffix}" ]; then + connect_options+=(--defaults-group-suffix="${option_defaults_group_suffix}") + fi + if [ -n "${option_port}" ]; then + connect_options+=(--protocol=tcp) + connect_options+=(--port="${option_port}") + fi + if [ -n "${option_socket}" ]; then + connect_options+=(--protocol=socket) + connect_options+=(--socket="${option_socket}") + fi + if [ -n "${option_user}" ]; then + connect_options+=(--user="${option_user}") + fi + if [ -n "${option_password}" ]; then + connect_options+=(--password="${option_password}") + fi databases=$(mysql "${connect_options[@]}" --execute="show databases" --silent --skip-column-names \ | grep --extended-regexp --invert-match "^(Database|information_schema|performance_schema|sys)") @@ -1301,30 +1499,25 @@ dump_mysql_tabs() { local error_file="${errors_dir}.err" log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" - declare -a options - options=() - options+=(--defaults-extra-file=/etc/mysql/debian.cnf) - if [ -n "${option_port}" ]; then - options+=(--port="${option_port}") + declare -a dump_options + dump_options=() + dump_options+=(--force) + dump_options+=(--quote-names) + dump_options+=(--opt) + dump_options+=(--events) + dump_options+=(--hex-blob) + dump_options+=(--skip-comments) + dump_options+=(--fields-enclosed-by='\"') + dump_options+=(--fields-terminated-by=',') + dump_options+=(--tab="${dump_dir}") + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + dump_options+=(${option_others}) fi - if [ -n "${option_socket}" ]; then - options+=(--protocol=socket) - options+=(--socket="${option_socket}") - fi - options+=(--force) - options+=(--quote-names) - options+=(--opt) - options+=(--events) - options+=(--hex-blob) - options+=(--skip-comments) - options+=(--fields-enclosed-by='\"') - options+=(--fields-terminated-by=',') - options+=(--tab="${dump_dir}") - options+=("${database}") + dump_options+=("${database}") - mysqldump "${options[@]}" 2> "${error_file}" - - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} > ${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file}" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" ${dump_cmd} From 45d0205cc10263eabc56f5768dffc96c3f393ac2 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Tue, 9 Jan 2024 18:02:22 +0100 Subject: [PATCH 092/105] add pigz compression method --- client/lib/dump-mysql.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/client/lib/dump-mysql.sh b/client/lib/dump-mysql.sh index f7a6a99..1f7a153 100644 --- a/client/lib/dump-mysql.sh +++ b/client/lib/dump-mysql.sh @@ -503,7 +503,7 @@ dump_mysql_grants() { # --defaults-group-suffix=[String] (default: ) # --dump-label=[String] (default: "default") # used as suffix of the dump dir to differenciate multiple instances -# --compress= (default: "gzip") +# --compress= (default: "gzip") # Other options after -- are passed as-is to mysqldump ####################################################################### dump_mysql_global() { @@ -728,6 +728,10 @@ dump_mysql_global() { compress_cmd="xz --best" dump_ext=".xz" ;; + pigz) + compress_cmd="pigz --best" + dump_ext=".gz" + ;; gz|gzip|*) compress_cmd="gzip --best" dump_ext=".gz" @@ -863,7 +867,7 @@ dump_mysql_global() { # --defaults-group-suffix=[String] (default: ) # --dump-label=[String] (default: "default") # used as suffix of the dump dir to differenciate multiple instances -# --compress= (default: "gzip") +# --compress= (default: "gzip") # Other options after -- are passed as-is to mysqldump ####################################################################### dump_mysql_per_base() { @@ -1084,6 +1088,10 @@ dump_mysql_per_base() { compress_cmd="xz --best" dump_ext=".xz" ;; + pigz) + compress_cmd="pigz --best" + dump_ext=".gz" + ;; gz|gzip|*) compress_cmd="gzip --best" dump_ext=".gz" @@ -1218,7 +1226,7 @@ dump_mysql_per_base() { # --defaults-group-suffix=[String] (default: ) # --dump-label=[String] (default: "default") # used as suffix of the dump dir to differenciate multiple instances -# --compress= (default: "gzip") +# --compress= (default: "gzip") # Other options after -- are passed as-is to mysqldump ####################################################################### dump_mysql_tabs() { @@ -1439,6 +1447,10 @@ dump_mysql_tabs() { compress_cmd="xz --best" dump_ext=".xz" ;; + pigz) + compress_cmd="pigz --best" + dump_ext=".gz" + ;; gz|gzip|*) compress_cmd="gzip --best" dump_ext=".gz" From 282bb6f1bc6f01abb5d1b5b1d9e57d53735ca09a Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Wed, 10 Jan 2024 15:06:17 +0100 Subject: [PATCH 093/105] dynamically include dump files --- client/lib/dump-postgresql.sh | 118 ----- client/lib/dump/elasticsearch.sh | 301 +++++++++++ client/lib/{dump-misc.sh => dump/misc.sh} | 557 ++++---------------- client/lib/{dump-mysql.sh => dump/mysql.sh} | 35 +- client/lib/dump/postgresql.sh | 343 ++++++++++++ client/lib/main.sh | 4 +- 6 files changed, 770 insertions(+), 588 deletions(-) delete mode 100644 client/lib/dump-postgresql.sh create mode 100644 client/lib/dump/elasticsearch.sh rename client/lib/{dump-misc.sh => dump/misc.sh} (50%) rename client/lib/{dump-mysql.sh => dump/mysql.sh} (98%) create mode 100644 client/lib/dump/postgresql.sh diff --git a/client/lib/dump-postgresql.sh b/client/lib/dump-postgresql.sh deleted file mode 100644 index cfe4a80..0000000 --- a/client/lib/dump-postgresql.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env bash -# shellcheck disable=SC2034,SC2317,SC2155 - -####################################################################### -# Dump a single file of all PostgreSQL databases -# -# Arguments: -####################################################################### -dump_postgresql_global() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-global" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - # No need to change recursively, the top directory is enough - chmod 700 "${dump_dir}" "${errors_dir}" - - ## example with pg_dumpall and with compression - local error_file="${errors_dir}/pg_dumpall.err" - local dump_file="${dump_dir}/pg_dumpall.sql.gz" - log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - - (sudo -u postgres pg_dumpall) 2> "${error_file}" | gzip --best > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dumpall to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - - log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" - - ## example with pg_dumpall and without compression - ## WARNING: you need space in ~postgres - # local error_file="${errors_dir}/pg_dumpall.err" - # local dump_file="${dump_dir}/pg_dumpall.sql" - # log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - # - # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" - # mv ~postgres/pg.dump.bak "${dump_file}" - # - # log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" -} - -####################################################################### -# Dump a compressed file per database -# -# Arguments: -####################################################################### -dump_postgresql_per_base() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-per-base" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - # No need to change recursively, the top directory is enough - chmod 700 "${dump_dir}" "${errors_dir}" - - ( - # shellcheck disable=SC2164 - cd /var/lib/postgresql - databases=$(sudo -u postgres psql -U postgres -lt | awk -F\| '{print $1}' | grep -v "template.*") - for database in ${databases} ; do - local error_file="${errors_dir}/${database}.err" - local dump_file="${dump_dir}/${database}.sql.gz" - log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - - (sudo -u postgres /usr/bin/pg_dump --create -U postgres -d "${database}") 2> "${error_file}" | gzip --best > "${dump_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" - done - ) -} - -####################################################################### -# Dump a compressed file per database -# -# Arguments: -# -# TODO: add arguments to include/exclude tables -####################################################################### -dump_postgresql_filtered() { - local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" - local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") - rm -rf "${dump_dir}" "${errors_dir}" - mkdir -p "${dump_dir}" "${errors_dir}" - # No need to change recursively, the top directory is enough - chmod 700 "${dump_dir}" "${errors_dir}" - - local error_file="${errors_dir}/pg-backup.err" - local dump_file="${dump_dir}/pg-backup.tar" - log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - - ## example with all tables from MYBASE excepts TABLE1 and TABLE2 - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -t 'TABLE1' -t 'TABLE2' MYBASE 2> "${error_file}" - - ## example with only TABLE1 and TABLE2 from MYBASE - # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -T 'TABLE1' -T 'TABLE2' MYBASE 2> "${error_file}" - - local last_rc=$? - # shellcheck disable=SC2086 - if [ ${last_rc} -ne 0 ]; then - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" - GLOBAL_RC=${E_DUMPFAILED} - else - rm -f "${error_file}" - fi - log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" -} diff --git a/client/lib/dump/elasticsearch.sh b/client/lib/dump/elasticsearch.sh new file mode 100644 index 0000000..1e5475b --- /dev/null +++ b/client/lib/dump/elasticsearch.sh @@ -0,0 +1,301 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034,SC2317,SC2155 + +####################################################################### +# Snapshot Elasticsearch data +# +# Arguments: +# --protocol= (default: http) +# --cacert=[String] (default: ) +# path to the CA certificate to use when using https +# --host=[String] (default: localhost) +# --port=[Integer] (default: 9200) +# --user=[String] (default: ) +# --password=[String] (default: ) +# --repository=[String] (default: snaprepo) +# --snapshot=[String] (default: snapshot.daily) +####################################################################### +dump_elasticsearch() { + local option_protocol="http" + local option_cacert="" + local option_host="localhost" + local option_port="9200" + local option_user="" + local option_password="" + local option_repository="snaprepo" + local option_snapshot="snapshot.daily" + local option_others="" + + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --protocol) + # protocol options, with value separated by space + if [ -n "$2" ]; then + option_protocol="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." + exit 1 + fi + ;; + --protocol=?*) + # protocol options, with value separated by = + option_protocol="${1#*=}" + ;; + --protocol=) + # protocol options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." + exit 1 + ;; + --cacert) + # cacert options, with value separated by space + if [ -n "$2" ]; then + option_cacert="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--cacert' requires a non-empty option argument." + exit 1 + fi + ;; + --cacert=?*) + # cacert options, with value separated by = + option_cacert="${1#*=}" + ;; + --cacert=) + # cacert options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--cacert' requires a non-empty option argument." + exit 1 + ;; + --host) + # host options, with value separated by space + if [ -n "$2" ]; then + option_host="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." + exit 1 + fi + ;; + --host=?*) + # host options, with value separated by = + option_host="${1#*=}" + ;; + --host=) + # host options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." + exit 1 + ;; + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; + --user) + # user options, with value separated by space + if [ -n "$2" ]; then + option_user="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + fi + ;; + --user=?*) + # user options, with value separated by = + option_user="${1#*=}" + ;; + --user=) + # user options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." + exit 1 + ;; + --password) + # password options, with value separated by space + if [ -n "$2" ]; then + option_password="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + fi + ;; + --password=?*) + # password options, with value separated by = + option_password="${1#*=}" + ;; + --password=) + # password options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." + exit 1 + ;; + --repository) + # repository options, with value separated by space + if [ -n "$2" ]; then + option_repository="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." + exit 1 + fi + ;; + --repository=?*) + # repository options, with value separated by = + option_repository="${1#*=}" + ;; + --repository=) + # repository options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." + exit 1 + ;; + --snapshot) + # snapshot options, with value separated by space + if [ -n "$2" ]; then + option_snapshot="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." + exit 1 + fi + ;; + --snapshot=?*) + # snapshot options, with value separated by = + option_snapshot="${1#*=}" + ;; + --snapshot=) + # snapshot options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + option_others=${*} + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + # Use the default Elasticsearch CA certificate when using HTTPS, if not specified directly + local default_cacert="/etc/elasticsearch/certs/http_ca.crt" + if [ "${option_protocol}" = "https" ] && [ -z "${option_cacert}" ] && [ -f "${default_cacert}" ]; then + option_cacert="${default_cacert}" + fi + + local errors_dir="${ERRORS_DIR}/elasticsearch-${option_repository}-${option_snapshot}" + rm -rf "${errors_dir}" + mkdir -p "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${errors_dir}" + + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${option_snapshot}" + + ## Take a snapshot as a backup. + ## Warning: You need to have a path.repo configured. + ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes + + local base_url="${option_protocol}://${option_host}:${option_port}" + local repository_url="${base_url}/_snapshot/${option_repository}" + local snapshot_url="${repository_url}/${option_snapshot}" + + # Verify snapshot repository + + local error_file="${errors_dir}/verify.err" + + declare -a connect_options + connect_options=() + if [ -n "${option_cacert}" ]; then + connect_options+=(--cacert "${option_cacert}") + fi + if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then + local connect_options+=("--user ${option_user}:${option_password}") + fi + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + connect_options+=(${option_others}) + fi + # Add the http return code at the end of the output + connect_options+=(--write-out '%{http_code}\n') + connect_options+=(--silent) + + declare -a dump_options + dump_options=() + dump_options+=(--request POST) + + dump_cmd="curl ${connect_options[*]} ${dump_options[*]} ${repository_url}/_verify?pretty" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} > "${error_file}" + + # test if the last line of the log file is "200" + tail -n 1 "${error_file}" | grep --quiet "^200$" "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: repository verification returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + + # Delete snapshot + + declare -a dump_options + dump_options=() + dump_options+=(--request DELETE) + + dump_cmd="curl ${connect_options[*]} ${dump_options[*]} ${snapshot_url}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} > /dev/null + + # Create snapshot + + local error_file="${errors_dir}/create.err" + + declare -a dump_options + dump_options=() + dump_options+=(--request PUT) + + dump_cmd="curl ${connect_options[*]} ${dump_options[*]} ${snapshot_url}?wait_for_completion=true" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} > "${error_file}" + + # test if the last line of the log file is "200" + tail -n 1 "${error_file}" | grep --quiet "^200$" "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: curl returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + fi + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${option_snapshot}" +} diff --git a/client/lib/dump-misc.sh b/client/lib/dump/misc.sh similarity index 50% rename from client/lib/dump-misc.sh rename to client/lib/dump/misc.sh index f060b73..c7ea1fa 100644 --- a/client/lib/dump-misc.sh +++ b/client/lib/dump/misc.sh @@ -102,9 +102,9 @@ dump_redis() { log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" # Copy the Redis database - dump_cmd="cp -a ${instance}/dump.rdb ${dump_dir}/dump.rdb 2> ${error_file}" + dump_cmd="cp -a ${instance}/dump.rdb ${dump_dir}/dump.rdb" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + ${dump_cmd} 2> "${error_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -141,20 +141,46 @@ dump_redis() { # using a custom authentication, instead of /etc/mysql/debian.cnf # # Arguments: +# --port=[String] (default: ) # --user=[String] (default: ) # --password=[String] (default: ) +# --dump-label=[String] (default: "default") +# used as suffix of the dump dir to differenciate multiple instances +# Other options after -- are passed as-is to mongodump # # don't forget to create use with read-only access # > use admin # > db.createUser( { user: "mongobackup", pwd: "PASS", roles: [ "backup", ] } ) ####################################################################### dump_mongodb() { + local option_port="" local option_user="" local option_password="" + local option_dump_label="" + local option_others="" # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in + --port) + # port options, with value separated by space + if [ -n "$2" ]; then + option_port="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + fi + ;; + --port=?*) + # port options, with value separated by = + option_port="${1#*=}" + ;; + --port=) + # port options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." + exit 1 + ;; --user) # user options, with value separated by space if [ -n "$2" ]; then @@ -193,9 +219,29 @@ dump_mongodb() { log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." exit 1 ;; + --dump-label) + # dump-label options, with value separated by space + if [ -n "$2" ]; then + option_dump_label="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + fi + ;; + --dump-label=?*) + # dump-label options, with value separated by = + option_dump_label="${1#*=}" + ;; + --dump-label=) + # dump-label options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + ;; --) # End of all options. shift + option_others=${*} break ;; -?*|[[:alnum:]]*) @@ -211,7 +257,15 @@ dump_mongodb() { shift done - local dump_dir="${LOCAL_BACKUP_DIR}/mongodump" + if [ -z "${option_dump_label}" ]; then + if [ -n "${option_port}" ]; then + option_dump_label="${option_port}" + else + option_dump_label="default" + fi + fi + + local dump_dir="${LOCAL_BACKUP_DIR}/mongodb-${option_dump_label}" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" mkdir -p "${dump_dir}" "${errors_dir}" @@ -221,15 +275,27 @@ dump_mongodb() { local error_file="${errors_dir}.err" log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_dir}" - declare -a options - options=() - options+=(--username="${option_user}") - options+=(--password="${option_password}") - options+=(--out="${dump_dir}/") + declare -a dump_options + dump_options=() + if [ -n "${option_port}" ]; then + dump_options+=(--port="${option_port}") + fi + if [ -n "${option_user}" ]; then + dump_options+=(--username="${option_user}") + fi + if [ -n "${option_password}" ]; then + dump_options+=(--password="${option_password}") + fi + dump_options+=(--out="${dump_dir}/") + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + dump_options+=(${option_others}) + fi - dump_cmd=" mongodump ${options[*]} 2> ${error_file} > /dev/null" - log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + dump_cmd="mongodump ${dump_options[*]}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd} > /dev/null" + ${dump_cmd} 2> "${error_file}" > /dev/null local last_rc=$? # shellcheck disable=SC2086 @@ -243,12 +309,12 @@ dump_mongodb() { } ####################################################################### -# Dump MegaCLI configuration +# Dump RAID configuration # # Arguments: ####################################################################### -dump_megacli_config() { - local dump_dir="${LOCAL_BACKUP_DIR}/megacli" +dump_raid_config() { + local dump_dir="${LOCAL_BACKUP_DIR}/raid" local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") rm -rf "${dump_dir}" "${errors_dir}" mkdir -p "${dump_dir}" "${errors_dir}" @@ -260,9 +326,9 @@ dump_megacli_config() { local dump_file="${dump_dir}/megacli.err" log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - dump_cmd="megacli -CfgSave -f ${dump_file} -a0 2> ${error_file}" + dump_cmd="megacli -CfgSave -f ${dump_file} -a0" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + ${dump_cmd} 2> "${error_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -273,15 +339,31 @@ dump_megacli_config() { rm -f "${error_file}" fi log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + elif command -v perccli > /dev/null; then + local error_file="${errors_dir}/perccli.cfg" + local dump_file="${dump_dir}/perccli.err" + # log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + # TODO: find out what the correct command is + + # dump_cmd="perccli XXXX" + # log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + # ${dump_cmd} 2> ${error_file} + + # local last_rc=$? + # # shellcheck disable=SC2086 + # if [ ${last_rc} -ne 0 ]; then + # log_error "LOCAL_TASKS - ${FUNCNAME[0]}: perccli to ${dump_file} returned an error ${last_rc}" "${error_file}" + # GLOBAL_RC=${E_DUMPFAILED} + # else + # rm -f "${error_file}" + # fi + # log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" else - log "LOCAL_TASKS - ${FUNCNAME[0]}: 'megacli' not found, unable to dump RAID configuration" + log "LOCAL_TASKS - ${FUNCNAME[0]}: 'megacli' and 'perccli' not found, unable to dump RAID configuration" fi } - - - - ####################################################################### # Save some traceroute/mtr results # @@ -427,9 +509,9 @@ dump_rabbitmq() { log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" - dump_cmd="rabbitmqadmin export ${dump_file} 2> ${error_file}" + dump_cmd="rabbitmqadmin export ${dump_file}" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + ${dump_cmd} 2> "${error_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -475,432 +557,3 @@ dump_facl() { log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_dir}" } - -####################################################################### -# Snapshot Elasticsearch data (single-node cluster) -# -# Arguments: -# --protocol=[String] (default: http) -# --host=[String] (default: localhost) -# --port=[Integer] (default: 9200) -# --user=[String] (default: ) -# --password=[String] (default: ) -# --repository=[String] (default: snaprepo) -# --snapshot=[String] (default: snapshot.daily) -####################################################################### -dump_elasticsearch_snapshot_singlenode() { - local option_protocol="http" - local option_host="localhost" - local option_port="9200" - local option_user="" - local option_password="" - local option_repository="snaprepo" - local option_snapshot="snapshot.daily" - - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --protocol) - # protocol options, with value separated by space - if [ -n "$2" ]; then - option_protocol="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." - exit 1 - fi - ;; - --protocol=?*) - # protocol options, with value separated by = - option_protocol="${1#*=}" - ;; - --protocol=) - # protocol options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." - exit 1 - ;; - --host) - # host options, with value separated by space - if [ -n "$2" ]; then - option_host="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." - exit 1 - fi - ;; - --host=?*) - # host options, with value separated by = - option_host="${1#*=}" - ;; - --host=) - # host options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." - exit 1 - ;; - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." - exit 1 - ;; - --user) - # user options, with value separated by space - if [ -n "$2" ]; then - option_user="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." - exit 1 - fi - ;; - --user=?*) - # user options, with value separated by = - option_user="${1#*=}" - ;; - --user=) - # user options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." - exit 1 - ;; - --password) - # password options, with value separated by space - if [ -n "$2" ]; then - option_password="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." - exit 1 - fi - ;; - --password=?*) - # password options, with value separated by = - option_password="${1#*=}" - ;; - --password=) - # password options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." - exit 1 - ;; - --repository) - # repository options, with value separated by space - if [ -n "$2" ]; then - option_repository="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." - exit 1 - fi - ;; - --repository=?*) - # repository options, with value separated by = - option_repository="${1#*=}" - ;; - --repository=) - # repository options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." - exit 1 - ;; - --snapshot) - # snapshot options, with value separated by space - if [ -n "$2" ]; then - option_snapshot="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." - exit 1 - fi - ;; - --snapshot=?*) - # snapshot options, with value separated by = - option_snapshot="${1#*=}" - ;; - --snapshot=) - # snapshot options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${option_snapshot}" - - ## Take a snapshot as a backup. - ## Warning: You need to have a path.repo configured. - ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes - - local base_url="${option_protocol}://${option_host}:${option_port}" - local snapshot_url="${base_url}/_snapshot/${option_repository}/${option_snapshot}" - - if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then - local option_auth="--user ${option_user}:${option_password}" - else - local option_auth="" - fi - - curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" - curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" - - # Clustered version here - # It basically the same thing except that you need to check that NFS is mounted - # if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2' - # then - # curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" - # curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" - # else - # echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' - # fi - - log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${option_snapshot}" -} - -####################################################################### -# Snapshot Elasticsearch data (multi-node cluster) -# -# Arguments: -# --protocol=[String] (default: http) -# --host=[String] (default: localhost) -# --port=[Integer] (default: 9200) -# --user=[String] (default: ) -# --password=[String] (default: ) -# --repository=[String] (default: snaprepo) -# --snapshot=[String] (default: snapshot.daily) -# --nfs-server=[IP|HOST] (default: ) -####################################################################### -dump_elasticsearch_snapshot_multinode() { - local option_protocol="http" - local option_host="localhost" - local option_port="9200" - local option_user="" - local option_password="" - local option_repository="snaprepo" - local option_snapshot="snapshot.daily" - local option_nfs_server="" - - # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a - while :; do - case ${1:-''} in - --protocol) - # protocol options, with value separated by space - if [ -n "$2" ]; then - option_protocol="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." - exit 1 - fi - ;; - --protocol=?*) - # protocol options, with value separated by = - option_protocol="${1#*=}" - ;; - --protocol=) - # protocol options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--protocol' requires a non-empty option argument." - exit 1 - ;; - --host) - # host options, with value separated by space - if [ -n "$2" ]; then - option_host="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." - exit 1 - fi - ;; - --host=?*) - # host options, with value separated by = - option_host="${1#*=}" - ;; - --host=) - # host options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--host' requires a non-empty option argument." - exit 1 - ;; - --port) - # port options, with value separated by space - if [ -n "$2" ]; then - option_port="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." - exit 1 - fi - ;; - --port=?*) - # port options, with value separated by = - option_port="${1#*=}" - ;; - --port=) - # port options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--port' requires a non-empty option argument." - exit 1 - ;; - --user) - # user options, with value separated by space - if [ -n "$2" ]; then - option_user="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." - exit 1 - fi - ;; - --user=?*) - # user options, with value separated by = - option_user="${1#*=}" - ;; - --user=) - # user options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--user' requires a non-empty option argument." - exit 1 - ;; - --password) - # password options, with value separated by space - if [ -n "$2" ]; then - option_password="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." - exit 1 - fi - ;; - --password=?*) - # password options, with value separated by = - option_password="${1#*=}" - ;; - --password=) - # password options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--password' requires a non-empty option argument." - exit 1 - ;; - --repository) - # repository options, with value separated by space - if [ -n "$2" ]; then - option_repository="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." - exit 1 - fi - ;; - --repository=?*) - # repository options, with value separated by = - option_repository="${1#*=}" - ;; - --repository=) - # repository options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--repository' requires a non-empty option argument." - exit 1 - ;; - --snapshot) - # snapshot options, with value separated by space - if [ -n "$2" ]; then - option_snapshot="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." - exit 1 - fi - ;; - --snapshot=?*) - # snapshot options, with value separated by = - option_snapshot="${1#*=}" - ;; - --snapshot=) - # snapshot options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--snapshot' requires a non-empty option argument." - exit 1 - ;; - --nfs-server) - # nfs-server options, with value separated by space - if [ -n "$2" ]; then - option_nfs_server="${2}" - shift - else - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--nfs-server' requires a non-empty option argument." - exit 1 - fi - ;; - --nfs-server=?*) - # nfs-server options, with value separated by = - option_nfs_server="${1#*=}" - ;; - --nfs-server=) - # nfs-server options, without value - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--nfs-server' requires a non-empty option argument." - exit 1 - ;; - --) - # End of all options. - shift - break - ;; - -?*|[[:alnum:]]*) - # ignore unknown options - log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift - done - - log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${option_snapshot}" - - ## Take a snapshot as a backup. - ## Warning: You need to have a path.repo configured. - ## See: https://wiki.evolix.org/HowtoElasticsearch#snapshots-et-sauvegardes - - local base_url="${option_protocol}://${option_host}:${option_port}" - local snapshot_url="${base_url}/_snapshot/${option_repository}/${option_snapshot}" - - if [ -n "${option_user}" ] || [ -n "${option_password}" ]; then - local option_auth="--user ${option_user}:${option_password}" - else - local option_auth="" - fi - - # Clustered version here - # It basically the same thing except that you need to check that NFS is mounted - if ss | grep ':nfs' | grep -q -F "${option_nfs_server}"; then - curl -s -XDELETE "${option_auth}" "${snapshot_url}" >> "${LOGFILE}" - curl -s -XPUT "${option_auth}" "${snapshot_url}?wait_for_completion=true" >> "${LOGFILE}" - else - echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.' - fi - - log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${option_snapshot}" -} diff --git a/client/lib/dump-mysql.sh b/client/lib/dump/mysql.sh similarity index 98% rename from client/lib/dump-mysql.sh rename to client/lib/dump/mysql.sh index 1f7a153..0d288b4 100644 --- a/client/lib/dump-mysql.sh +++ b/client/lib/dump/mysql.sh @@ -254,9 +254,9 @@ dump_mysql_summary() { options=() options+=(--sleep=0) - dump_cmd="pt-mysql-summary ${options[*]} -- ${connect_options[*]} > ${dump_file}" + dump_cmd="pt-mysql-summary ${options[*]} -- ${connect_options[*]}" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} 2> "${error_file}" + ${dump_cmd} 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -470,9 +470,9 @@ dump_mysql_grants() { options+=(--flush) options+=(--no-header) - dump_cmd="pt-show-grants ${options[*]} > ${dump_file}" + dump_cmd="pt-show-grants ${options[*]}" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} 2> "${error_file}" + ${dump_cmd} 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -806,9 +806,12 @@ dump_mysql_global() { dump_options+=(${option_others}) fi - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file}| ${compress_cmd} > ${dump_file}" + ## WARNING : logging and executing the command must be separate + ## because otherwise Bash would interpret | and > as strings and not syntax. + + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} | ${compress_cmd} > ${dump_file}" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + mysqldump "${connect_options[@]}" "${dump_options[@]}" 2> "${error_file}" | ${compress_cmd} > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -838,9 +841,9 @@ dump_mysql_global() { dump_options+=(${option_others}) fi - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} > ${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + ${dump_cmd} 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -1165,9 +1168,11 @@ dump_mysql_per_base() { dump_options+=(${option_others}) fi - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} | ${compress_cmd} > ${dump_file}" - log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + ## WARNING : logging and executing the command must be separate + ## because otherwise Bash would interpret | and > as strings and not syntax. + + log "LOCAL_TASKS - ${FUNCNAME[0]}: mysqldump ${connect_options[*]} ${dump_options[*]} | ${compress_cmd} > ${dump_file}" + mysqldump "${connect_options[@]}" "${dump_options[@]}" 2> "${error_file}" | ${compress_cmd} > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -1197,9 +1202,9 @@ dump_mysql_per_base() { dump_options+=(${option_others}) fi - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file} > ${dump_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + ${dump_cmd} 2> "${error_file}" > "${dump_file}" local last_rc=$? # shellcheck disable=SC2086 @@ -1529,9 +1534,9 @@ dump_mysql_tabs() { fi dump_options+=("${database}") - dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]} 2> ${error_file}" + dump_cmd="mysqldump ${connect_options[*]} ${dump_options[*]}" log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" - ${dump_cmd} + ${dump_cmd} 2> "${error_file}" local last_rc=$? # shellcheck disable=SC2086 diff --git a/client/lib/dump/postgresql.sh b/client/lib/dump/postgresql.sh new file mode 100644 index 0000000..302942c --- /dev/null +++ b/client/lib/dump/postgresql.sh @@ -0,0 +1,343 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034,SC2317,SC2155 + +####################################################################### +# Dump a single file of all PostgreSQL databases +# +# Arguments: +# --dump-label=[String] (default: "default") +# used as suffix of the dump dir to differenciate multiple instances +# --compress= (default: "gzip") +# Other options after -- are passed as-is to pg_dump +####################################################################### +dump_postgresql_global() { + local option_dump_label="" + local option_compress="" + local option_others="" + + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --dump-label) + # dump-label options, with value separated by space + if [ -n "$2" ]; then + option_dump_label="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + fi + ;; + --dump-label=?*) + # dump-label options, with value separated by = + option_dump_label="${1#*=}" + ;; + --dump-label=) + # dump-label options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + ;; + --compress) + # compress options, with value separated by space + if [ -n "$2" ]; then + option_compress="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + fi + ;; + --compress=?*) + # compress options, with value separated by = + option_compress="${1#*=}" + ;; + --compress=) + # compress options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + option_others=${*} + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + case "${option_compress}" in + none) + compress_cmd="cat" + dump_ext="" + ;; + bzip2|bz|bz2) + compress_cmd="bzip2 --best" + dump_ext=".bz" + ;; + xz) + compress_cmd="xz --best" + dump_ext=".xz" + ;; + pigz) + compress_cmd="pigz --best" + dump_ext=".gz" + ;; + gz|gzip|*) + compress_cmd="gzip --best" + dump_ext=".gz" + ;; + esac + + if [ -z "${option_dump_label}" ]; then + if [ -n "${option_defaults_group_suffix}" ]; then + option_dump_label="${option_defaults_group_suffix}" + elif [ -n "${option_port}" ]; then + option_dump_label="${option_port}" + elif [ -n "${option_socket}" ]; then + option_dump_label=$(path_to_str "${option_socket}") + else + option_dump_label="default" + fi + fi + + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-${option_dump_label}-global" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + ## example with pg_dumpall and with compression + local error_file="${errors_dir}/pg_dumpall.err" + local dump_file="${dump_dir}/pg_dumpall.sql${dump_ext}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + declare -a dump_options + dump_options=() + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + dump_options+=(${option_others}) + fi + + dump_cmd="(sudo -u postgres pg_dumpall ${dump_options[*]}) 2> ${error_file} | ${compress_cmd} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dumpall to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + + ## example with pg_dumpall and without compression + ## WARNING: you need space in ~postgres + # local error_file="${errors_dir}/pg_dumpall.err" + # local dump_file="${dump_dir}/pg_dumpall.sql" + # log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + # + # (su - postgres -c "pg_dumpall > ~/pg.dump.bak") 2> "${error_file}" + # mv ~postgres/pg.dump.bak "${dump_file}" + # + # log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" +} + +####################################################################### +# Dump a compressed file per database +# +# Arguments: +####################################################################### +dump_postgresql_per_base() { + local option_dump_label="" + local option_compress="" + local option_others="" + + # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-''} in + --dump-label) + # dump-label options, with value separated by space + if [ -n "$2" ]; then + option_dump_label="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + fi + ;; + --dump-label=?*) + # dump-label options, with value separated by = + option_dump_label="${1#*=}" + ;; + --dump-label=) + # dump-label options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--dump-label' requires a non-empty option argument." + exit 1 + ;; + --compress) + # compress options, with value separated by space + if [ -n "$2" ]; then + option_compress="${2}" + shift + else + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + fi + ;; + --compress=?*) + # compress options, with value separated by = + option_compress="${1#*=}" + ;; + --compress=) + # compress options, without value + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: '--compress' requires a non-empty option argument." + exit 1 + ;; + --) + # End of all options. + shift + option_others=${*} + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: unknown option '${1}' (ignored)" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + case "${option_compress}" in + none) + compress_cmd="cat" + dump_ext="" + ;; + bzip2|bz|bz2) + compress_cmd="bzip2 --best" + dump_ext=".bz" + ;; + xz) + compress_cmd="xz --best" + dump_ext=".xz" + ;; + pigz) + compress_cmd="pigz --best" + dump_ext=".gz" + ;; + gz|gzip|*) + compress_cmd="gzip --best" + dump_ext=".gz" + ;; + esac + + if [ -z "${option_dump_label}" ]; then + if [ -n "${option_defaults_group_suffix}" ]; then + option_dump_label="${option_defaults_group_suffix}" + elif [ -n "${option_port}" ]; then + option_dump_label="${option_port}" + elif [ -n "${option_socket}" ]; then + option_dump_label=$(path_to_str "${option_socket}") + else + option_dump_label="default" + fi + fi + + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-${option_dump_label}-per-base" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + ( + # shellcheck disable=SC2164 + cd /var/lib/postgresql + databases=$(sudo -u postgres psql -U postgres -lt | awk -F \| '{print $1}' | grep -v "template.*") + for database in ${databases} ; do + local error_file="${errors_dir}/${database}.err" + local dump_file="${dump_dir}/${database}.sql${dump_ext}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + declare -a dump_options + dump_options=() + dump_options+=(--create) + dump_options+=(-U postgres) + dump_options+=(-d "${database}") + if [ -n "${option_others}" ]; then + # word splitting is deliberate here + # shellcheck disable=SC2206 + dump_options+=(${option_others}) + fi + + dump_cmd="(sudo -u postgres /usr/bin/pg_dump ${dump_options[*]}) 2> ${error_file} | ${compress_cmd} > ${dump_file}" + log "LOCAL_TASKS - ${FUNCNAME[0]}: ${dump_cmd}" + ${dump_cmd} + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" + done + ) +} + +####################################################################### +# Dump a compressed file per database +# +# Arguments: +# +# TODO: add arguments to include/exclude tables +####################################################################### +dump_postgresql_filtered() { + local dump_dir="${LOCAL_BACKUP_DIR}/postgresql-filtered" + local errors_dir=$(errors_dir_from_dump_dir "${dump_dir}") + rm -rf "${dump_dir}" "${errors_dir}" + mkdir -p "${dump_dir}" "${errors_dir}" + # No need to change recursively, the top directory is enough + chmod 700 "${dump_dir}" "${errors_dir}" + + local error_file="${errors_dir}/pg-backup.err" + local dump_file="${dump_dir}/pg-backup.tar" + log "LOCAL_TASKS - ${FUNCNAME[0]}: start ${dump_file}" + + ## example with all tables from MYBASE excepts TABLE1 and TABLE2 + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -t 'TABLE1' -t 'TABLE2' MYBASE 2> "${error_file}" + + ## example with only TABLE1 and TABLE2 from MYBASE + # pg_dump -p 5432 -h 127.0.0.1 -U USER --clean -F t --inserts -f "${dump_file}" -T 'TABLE1' -T 'TABLE2' MYBASE 2> "${error_file}" + + local last_rc=$? + # shellcheck disable=SC2086 + if [ ${last_rc} -ne 0 ]; then + log_error "LOCAL_TASKS - ${FUNCNAME[0]}: pg_dump to ${dump_file} returned an error ${last_rc}" "${error_file}" + GLOBAL_RC=${E_DUMPFAILED} + else + rm -f "${error_file}" + fi + log "LOCAL_TASKS - ${FUNCNAME[0]}: stop ${dump_file}" +} diff --git a/client/lib/main.sh b/client/lib/main.sh index e1d1ecc..021fa9d 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -18,9 +18,7 @@ if [[ "${TRACE-0}" == "1" ]]; then fi source "${LIBDIR}/utilities.sh" -source "${LIBDIR}/dump-mysql.sh" -source "${LIBDIR}/dump-postgresql.sh" -source "${LIBDIR}/dump-misc.sh" +source "${LIBDIR}"/dump/*.sh # Called from main, it is wrapping the local_tasks function defined in the real script local_tasks_wrapper() { From 230ad8ae7927ade738a891106907324be15f1177 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Thu, 11 Jan 2024 08:03:09 +0100 Subject: [PATCH 094/105] update comments for the new function name --- client/lib/zzz_evobackup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lib/zzz_evobackup.sh b/client/lib/zzz_evobackup.sh index 61fba30..50188a2 100644 --- a/client/lib/zzz_evobackup.sh +++ b/client/lib/zzz_evobackup.sh @@ -22,7 +22,7 @@ MAIL=jdoe@example.com ####################################################################### # -# The "sync_tasks" function will be called by the "main" function. +# The "sync_tasks" function will be called by the "run_evobackup" function. # # You can customize the variables: # * "SYNC_NAME" (String) @@ -88,7 +88,7 @@ sync_tasks() { ####################################################################### # -# The "local_tasks" function will be called by the "main" function. +# The "local_tasks" function will be called by the "run_evobackup" function. # # You can call any available "dump_xxx" function # (usually installed at /usr/local/lib/evobackup/dump-*.sh) From 86ee6406a56cb327805a6d5c6bba5fbd6a16edbe Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 15 Jan 2024 07:35:27 +0100 Subject: [PATCH 095/105] manually source dump libraries --- client/lib/main.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index 021fa9d..b1aa6c0 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -18,7 +18,10 @@ if [[ "${TRACE-0}" == "1" ]]; then fi source "${LIBDIR}/utilities.sh" -source "${LIBDIR}"/dump/*.sh +source "${LIBDIR}/dump/elasticsearch.sh" +source "${LIBDIR}/dump/mysql.sh" +source "${LIBDIR}/dump/postgresql.sh" +source "${LIBDIR}/dump/misc.sh" # Called from main, it is wrapping the local_tasks function defined in the real script local_tasks_wrapper() { From da1d4356dd02187e30277ead250c4bbe59072b01 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 26 Jan 2024 16:02:17 +0100 Subject: [PATCH 096/105] don't exit the whole program if a sync task can't be done --- client/lib/main.sh | 33 ++++++++++++++++++++------------- client/lib/utilities.sh | 2 ++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index b1aa6c0..9c6a35a 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # shellcheck disable=SC2034,SC2317 -readonly VERSION="23.12-pre" +readonly VERSION="24.01-pre" # set all programs to C language (english) export LC_ALL=C @@ -154,6 +154,8 @@ sync() { ## Initialize variable to store SSH connection errors declare -a SSH_ERRORS=() + log "START SYNC_TASKS - sync=${sync_name}" + # echo "### sync ###" # for server in "${rsync_servers[@]}"; do @@ -173,8 +175,13 @@ sync() { if [ "${SERVERS_FALLBACK}" = "1" ]; then # We try to find a suitable server while :; do - server=$(pick_server ${n}) - test $? = 0 || exit ${E_NOSRVAVAIL} + server=$(pick_server ${n} "${sync_name}") + rc=$? + if [ ${rc} != 0 ]; then + GLOBAL_RC=${E_NOSRVAVAIL} + log "STOP SYNC_TASKS - sync=${sync_name}'" + return + fi if test_server "${server}"; then break @@ -185,13 +192,13 @@ sync() { done else # we force the server - server=$(pick_server "${n}") + server=$(pick_server "${n}" "${sync_name}") fi rsync_server=$(echo "${server}" | cut -d':' -f1) rsync_port=$(echo "${server}" | cut -d':' -f2) - log "START SYNC_TASKS - ${sync_name}: server=${server}" + log "SYNC_TASKS - sync=${sync_name}: use ${server}" # Rsync complete log file for the current run RSYNC_LOGFILE="/var/log/${PROGNAME}.${sync_name}.rsync.log" @@ -215,7 +222,7 @@ sync() { if [ -n "${mtree_bin}" ]; then # Dump filesystem stats with mtree - log "SYNC_TASKS - ${sync_name}: start mtree" + log "SYNC_TASKS - sync=${sync_name}: start mtree" # Loop over Rsync includes for i in "${!rsync_includes[@]}"; do @@ -242,12 +249,12 @@ sync() { log_error "SYNC_TASKS - ${sync_name}: ERROR: mtree didn't produce any file" fi - log "SYNC_TASKS - ${sync_name}: stop mtree (files: ${mtree_files[*]})" + log "SYNC_TASKS - sync=${sync_name}: stop mtree (files: ${mtree_files[*]})" else - log "SYNC_TASKS - ${sync_name}: skip mtree (missing)" + log "SYNC_TASKS - sync=${sync_name}: skip mtree (missing)" fi else - log "SYNC_TASKS - ${sync_name}: skip mtree (disabled)" + log "SYNC_TASKS - sync=${sync_name}: skip mtree (disabled)" fi rsync_bin=$(command -v rsync) @@ -281,7 +288,7 @@ sync() { rsync_main_args+=("root@${rsync_server}:${REMOTE_BACKUP_DIR}/") # … log it - log "SYNC_TASKS - ${sync_name}: Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" + log "SYNC_TASKS - sync=${sync_name}: Rsync main command : ${rsync_bin} ${rsync_main_args[*]}" # … execute it ${rsync_bin} "${rsync_main_args[@]}" @@ -295,7 +302,7 @@ sync() { # We ignore rc=24 (vanished files) if [ ${rsync_main_rc} -ne 0 ] && [ ${rsync_main_rc} -ne 24 ]; then - log_error "SYNC_TASKS - ${sync_name}: Rsync main command returned an error ${rsync_main_rc}" "${LOGFILE}" + log_error "SYNC_TASKS - sync=${sync_name}: Rsync main command returned an error ${rsync_main_rc}" "${LOGFILE}" GLOBAL_RC=${E_SYNCFAILED} else # Build the report Rsync command @@ -324,13 +331,13 @@ sync() { rsync_report_args+=("root@${rsync_server}:${REMOTE_LOG_DIR}/") # … log it - log "SYNC_TASKS - ${sync_name}: Rsync report command : ${rsync_bin} ${rsync_report_args[*]}" + log "SYNC_TASKS - sync=${sync_name}: Rsync report command : ${rsync_bin} ${rsync_report_args[*]}" # … execute it ${rsync_bin} "${rsync_report_args[@]}" fi - log "STOP SYNC_TASKS - ${sync_name}: server=${server}" + log "STOP SYNC_TASKS - sync=${sync_name}" } setup() { diff --git a/client/lib/utilities.sh b/client/lib/utilities.sh index df23c55..e14a434 100644 --- a/client/lib/utilities.sh +++ b/client/lib/utilities.sh @@ -105,10 +105,12 @@ test_server() { pick_server() { local -i increment=${1:-0} local -i list_length=${#SERVERS[@]} + local sync_name=${2:""} if (( increment >= list_length )); then # We've reached the end of the list new_error="No more server available" + new_error="${new_error} for sync '${sync_name}'" log "${new_error}" SSH_ERRORS+=("${new_error}") From 699ed5a781ec270d9afe6432df873299a9b6c358 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Fri, 22 Mar 2024 11:32:18 +0100 Subject: [PATCH 097/105] use $TMPDIR if available --- client/CHANGELOG.md | 1 + server/lib/includes | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index 67a571c..f29b223 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -21,6 +21,7 @@ The **patch** part changes is incremented if multiple releases happen the same m * add evobackupctl script * change the "zzz_evobackup" script to a template, easy to copy with evobackupctl * use env-based shebang for shell scripts +* use $TMPDIR if available ### Deprecated diff --git a/server/lib/includes b/server/lib/includes index 59c31c8..995b908 100755 --- a/server/lib/includes +++ b/server/lib/includes @@ -252,15 +252,15 @@ relative_date() { new_tmp_file() { name=${1:-} - mktemp --tmpdir=/tmp "bkctld.${$}.${name}.XXXXX" + mktemp --tmpdir "bkctld.${$}.${name}.XXXXX" } new_tmp_dir() { name=${1:-} - mktemp --directory --tmpdir=/tmp "bkctld.${$}.${name}.XXXXX" + mktemp --directory --tmpdir "bkctld.${$}.${name}.XXXXX" } cleanup_tmp() { - find /tmp -name "bkctld.${$}.*" -delete + find "${TMPDIR:-/tmp}" -name "bkctld.${$}.*" -delete } new_lock_file() { lock_file=${1:-} From 9c8dca7b70303923a4ee0537528dea7e03b3e891 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 8 Apr 2024 13:15:22 +0200 Subject: [PATCH 098/105] update CHANGELOG --- client/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index f29b223..ee335f4 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -32,6 +32,8 @@ The **patch** part changes is incremented if multiple releases happen the same m ### Fixed +* don't exit the whole program if a sync task can't be done + ### Security ## [22.12] From fd856b74239610286d7c55c251cb807d5db0b07d Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 29 Apr 2024 10:48:46 +0200 Subject: [PATCH 099/105] evobackupctl: update verison command output --- client/bin/evobackupctl | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/client/bin/evobackupctl b/client/bin/evobackupctl index 5be5efe..8a58c9a 100644 --- a/client/bin/evobackupctl +++ b/client/bin/evobackupctl @@ -16,14 +16,8 @@ show_version() { cat <, - Gregory Colpart , - Romain Dessort , - Benoit Série , - Tristan Pilat , - Victor Laborie , - Jérémy Lecour - and others. +Copyright 2024 Evolix , + Jérémy Lecour . ${PROGNAME} comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. From 0f1d9148fd21651c69625c48bc745cd354d0f7a0 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 29 Apr 2024 10:59:09 +0200 Subject: [PATCH 100/105] evobackupctl: If no argument is provided, print help and exit --- client/bin/evobackupctl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/bin/evobackupctl b/client/bin/evobackupctl index 8a58c9a..e82fe5c 100644 --- a/client/bin/evobackupctl +++ b/client/bin/evobackupctl @@ -61,6 +61,12 @@ copy_template() { } main() { + # If no argument is provided, print help and exit + # shellcheck disable=SC2086 + if [ -z ${1:-''} ]; then + show_help + exit 0 + fi # Parse options, based on https://gist.github.com/deshion/10d3cb5f88a21671e17a while :; do case ${1:-''} in From ee111b8c5c39a498c7e49138e06155d7f91a09bc Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 29 Apr 2024 10:59:30 +0200 Subject: [PATCH 101/105] client: update VERSION --- client/lib/main.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/main.sh b/client/lib/main.sh index 9c6a35a..910101e 100644 --- a/client/lib/main.sh +++ b/client/lib/main.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # shellcheck disable=SC2034,SC2317 -readonly VERSION="24.01-pre" +readonly VERSION="24.04-pre1" # set all programs to C language (english) export LC_ALL=C From 00e5384cd1b2830c4c0f5efce74e85479c607387 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 29 Apr 2024 14:49:41 +0200 Subject: [PATCH 102/105] Use placeholder values for script template --- client/lib/zzz_evobackup.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/lib/zzz_evobackup.sh b/client/lib/zzz_evobackup.sh index 50188a2..c8bbe22 100644 --- a/client/lib/zzz_evobackup.sh +++ b/client/lib/zzz_evobackup.sh @@ -18,7 +18,7 @@ ####################################################################### # Email adress for notifications -MAIL=jdoe@example.com +MAIL=__NOTIFICATION_MAIL__ ####################################################################### # @@ -49,8 +49,8 @@ sync_tasks() { SYNC_NAME="evolix-system" SERVERS=( - node0.backup.evolix.net:2234 - node1.backup.evolix.net:2234 + __SRV0_HOST__:__SRV0_PORT__ + __SRV1_HOST__:__SRV1_PORT__ ) RSYNC_INCLUDES=( "${rsync_default_includes[@]}" From 73bd007f6e9e6c22ca45db11512321e14e8f8588 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 29 Apr 2024 16:27:22 +0200 Subject: [PATCH 103/105] evobackupctl: use $ARGS --- client/bin/evobackupctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/bin/evobackupctl b/client/bin/evobackupctl index e82fe5c..b0b4e0c 100644 --- a/client/bin/evobackupctl +++ b/client/bin/evobackupctl @@ -63,7 +63,7 @@ copy_template() { main() { # If no argument is provided, print help and exit # shellcheck disable=SC2086 - if [ -z ${1:-''} ]; then + if [ -z ${ARGS} ]; then show_help exit 0 fi From d9674acff0e99fcd06f1d0619a0401bf95a6c36e Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 29 Apr 2024 16:29:44 +0200 Subject: [PATCH 104/105] evobackupctl: add "--init-jail-commands" --- client/bin/evobackupctl | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/client/bin/evobackupctl b/client/bin/evobackupctl index b0b4e0c..3cd55ea 100644 --- a/client/bin/evobackupctl +++ b/client/bin/evobackupctl @@ -31,10 +31,39 @@ ${PROGNAME} helps managing evobackup scripts Options -h, --help print this message and exit -V, --version print version and exit + --init-jail-commands print jail init commands --copy-template=PATH copy the backup template to PATH END } +init_jail_commands() { + if [ ! -f /root/.ssh/id_ed25519.pub ]; then + ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N '' + echo "" + fi + + SSH_KEY=$(cat /root/.ssh/id_ed25519.pub) + SERVER_NAME=$(hostname -s) + if [ "$(uname -s)" = "OpenBSD" ]; then + SERVER_IP=$(ifconfig egress | grep "inet " | head -1 | awk '{ print $2}') + else + SERVER_IP=$(curl -4 https://ifconfig.me 2> /dev/null || hostname -I | awk '{ print $1}') + fi + + echo "Copy-paste those lines on backup server(s) :" + echo "----------" + echo "SERVER_NAME=${SERVER_NAME}" + echo "SERVER_IP=${SERVER_IP}" + echo "echo '${SSH_KEY}' > /root/\${SERVER_NAME}.pub" + echo "bkctld init \${SERVER_NAME}" + echo "bkctld key \${SERVER_NAME} /root/\${SERVER_NAME}.pub" + echo "bkctld ip \${SERVER_NAME} \${SERVER_IP}" + echo "bkctld start \${SERVER_NAME}" + echo "bkctld status \${SERVER_NAME}" + echo "grep --quiet --extended-regexp \"^\\s?NODE=\" /etc/default/bkctld && bkctld sync \${SERVER_NAME}" + echo "----------" +} + copy_template() { dest_path=${1} dest_dir="$(dirname "${dest_path}")" @@ -78,6 +107,10 @@ main() { show_help exit 0 ;; + --init-jail-commands) + init_jail_commands + exit 0 + ;; --copy-template) # copy-template option, with value separated by space if [ -n "$2" ]; then From 2cf82641273998f856791960e3d25f40e601f8de Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 29 Apr 2024 17:52:35 +0200 Subject: [PATCH 105/105] evobackupctl: change option name --- client/bin/evobackupctl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/bin/evobackupctl b/client/bin/evobackupctl index 3cd55ea..286eb94 100644 --- a/client/bin/evobackupctl +++ b/client/bin/evobackupctl @@ -31,12 +31,12 @@ ${PROGNAME} helps managing evobackup scripts Options -h, --help print this message and exit -V, --version print version and exit - --init-jail-commands print jail init commands + --jail-init-commands print jail init commands --copy-template=PATH copy the backup template to PATH END } -init_jail_commands() { +jail_init_commands() { if [ ! -f /root/.ssh/id_ed25519.pub ]; then ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N '' echo "" @@ -107,8 +107,8 @@ main() { show_help exit 0 ;; - --init-jail-commands) - init_jail_commands + --jail-init-commands) + jail_init_commands exit 0 ;; --copy-template)