From 039c740ef3b64665e96453be8cb43531ae670140 Mon Sep 17 00:00:00 2001 From: Jeremy Lecour Date: Mon, 1 Nov 2021 10:16:52 +0100 Subject: [PATCH] mysql: add evomariabackup 21.11 --- CHANGELOG.md | 1 + mysql/files/evomariabackup.sh | 554 ++++++++++++++++++++++++++++++++++ mysql/tasks/utils.yml | 9 + 3 files changed, 564 insertions(+) create mode 100644 mysql/files/evomariabackup.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index f7455b82..4549cdca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The **patch** part changes is incremented if multiple releases happen the same m ### Added * evolinux-base: add script backup-server-state +* mysql: add evomariabackup 21.11 * nagios-nrpe: new check influxdb ### Changed diff --git a/mysql/files/evomariabackup.sh b/mysql/files/evomariabackup.sh new file mode 100644 index 00000000..0e3de84b --- /dev/null +++ b/mysql/files/evomariabackup.sh @@ -0,0 +1,554 @@ +#!/bin/sh + +VERSION="21.11" + +show_version() { + cat <, + Éric Morino , + Jérémy Lecour + and others. + +evomariabackup comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU Affero General Public License v3.0 for details. +END +} +show_help() { + cat <> "${log_file}" + else + log_line "${level}" "${msg}" >&2 + fi + fi +} +log_info() { + level="INFO" + msg=$1 + if ! is_quiet; then + if is_log_file; then + log_line "${level}" "${msg}" >> "${log_file}" + else + log_line "${level}" "${msg}" >&2 + fi + fi +} +log_warning() { + level="WARNING" + msg=$1 + if ! is_quiet; then + if is_log_file; then + log_line "${level}" "${msg}" >> "${log_file}" + else + log_line "${level}" "${msg}" >&2 + fi + fi +} +log_error() { + level="ERROR" + msg=$1 + if ! is_quiet; then + if is_log_file; then + log_line "${level}" "${msg}" >> "${log_file}" + if tty -s; then + printf "%s\n" "${msg}" >&2 + fi + else + log_line "${level}" "${msg}" >&2 + fi + fi +} +log_fatal() { + level="FATAL" + msg=$1 + if is_log_file; then + log_line "${level}" "${msg}" >> "${log_file}" + if tty -s; then + printf "%s\n" "${msg}" >&2 + fi + else + log_line "${level}" "${msg}" >&2 + fi +} +duration_in_seconds() { + if echo "${1}" | grep -E -q '^([0-9]+[wdhms])+$'; then + duration=$(echo "${1}" | sed 's/w/ * 604800 + /g; s/d/ * 86400 + /g; s/h/ * 3600 + /g; s/m/ * 60 + /g; s/s/ + /g; s/+ $//' | xargs expr) + elif echo "${1}" | grep -E -q '^[0-9]+$'; then + duration=$(echo "${1} * 3600" | xargs expr) + else + return 1 + fi + log_debug "Duration \`${1}' translated to ${duration} seconds" + + echo "${duration}" +} +lock_file_created_at() { + created_at=$(stat -c %Z "${lock_file}") + log_debug "Lock file ${lock_file} created at ${created_at}" + + echo "${created_at}" +} +lock_file_age() { + last_change=$(lock_file_created_at) + now=$(date +"%s") + + age=$(( now - last_change )) + log_debug "Lock file ${lock_file} is ${age} seconds old" + + echo "${created_at}" +} +is_lock_file_too_old() { + test "$(lock_file_age)" -ge "${max_age}" +} +kill_or_clean_lockfile() { + lock_file=${1:-} + + if [ -f "${lock_file}" ]; then + # Get Process ID from the lock file + pid=$(cat "${lock_file}") + if [ -n "${pid}" ]; then + log_debug "Found pid ${pid} in ${lock_file}" + + if kill -0 "${pid}" 2> /dev/null; then + log_debug "Found process with pid ${pid}" + + lock_file_created_at_human=$(date --date "@$(lock_file_created_at)" +"%Y-%m-%d %H:%M:%S") + if is_lock_file_too_old ; then + # Kill the children + pkill -9 --parent "${pid}" + # Kill the parent + kill -9 "${pid}" + # Only one process can run in parallel + log_warning "Process \`${pid}' (started at ${lock_file_created_at_human}) has been killed by \`$$'" + else + log_info "Process \`${pid}' (started at ${lock_file_created_at_human}) has precedence. Let's leave it work." + # make sure that this exit doesn't remove the existing lockfile !! + exit 0 + fi + else + log_warning "Process not found at PID \`${pid}'. Ignoring lock file \`${lock_file}'." + fi + else + log_warning "Empty lockfile \`${lock_file}'. It should contain a PID." + fi + # Remove the lock file + rm -f "${lock_file}" + log_debug "Lock file ${lock_file} has been removed" + fi +} +new_lock_file() { + lock_file=${1:-} + lock_dir=$(dirname "${lock_file}") + + if mkdir --parents "${lock_dir}"; then + echo $$ > "${lock_file}" + log_debug "Lock file '${lock_file}' has been created" + else + log_fatal "Failed to acquire lock file '${lock_file}'. Abort." + exit 1 + fi +} +is_mariabackup_directory() { + directory=${1:-} + find "${directory}" -name 'ibdata*' -o -name 'ib_logfile*' -o -name 'xtrabackup_*' > /dev/null +} +check_backup_dir() { + if [ -d "${backup_dir:?}" ]; then + if [ "$(ls -A "${backup_dir:?}")" ]; then + if is_mariabackup_directory "${backup_dir:?}"; then + log_debug "The backup directory ${backup_dir:?} is not empty but looks like a mariabackup target. Let's clear it." + rm -rf "${backup_dir:?}" + else + log_fatal "The backup directory ${backup_dir:?} is not empty and doesn't look like a mariabackup target. Please verify and clear the directory if you are sure." + exit 1 + fi + else + log_debug "The backup directory ${backup_dir:?} exists but is empty. Let's proceed." + fi + else + log_debug "The backup directory ${backup_dir:?} doesn't exist. Let's proceed." + fi + mkdir -p "${backup_dir:?}" +} +check_compress_dir() { + if [ -d "${compress_dir:?}" ]; then + log_debug "The compress_dir directory ${compress_dir:?} exists. Let's proceed." + else + log_debug "The compress_dir directory ${compress_dir:?} doesn't exist. Let's proceed." + fi + mkdir -p "${compress_dir:?}" +} + +backup() { + if [ -z "${backup_dir}" ]; then + log_fatal "backup-dir option is empty" + else + check_backup_dir + fi + + mariabackup_bin=$(command -v mariabackup) + if [ -z "${mariabackup_bin}" ]; then + log_fatal "Couldn't find mariabackup.\nUse 'apt install mariadb-backup'." + exit 1 + fi + + backup_command="${mariabackup_bin} --backup --slave-info --target-dir=${backup_dir:?}" + + + if ! is_quiet; then + log_debug "${backup_command}" + log_info "BEGIN mariabackup backup phase" + fi + + if is_quiet || ! is_verbose ; then + ${backup_command} >/dev/null 2>&1 + backup_rc=$? + elif ! is_quiet; then + if is_log_file; then + ${backup_command} >>"${log_file}" 2>&1 + backup_rc=$? + else + ${backup_command} + backup_rc=$? + fi + fi + + if [ ${backup_rc} -ne 0 ]; then + log_fatal "Error executing mariabackup --backup" + exit 1 + elif ! is_quiet; then + log_info "END mariabackup backup phase" + fi + + prepare_command="${mariabackup_bin} --prepare --target-dir=${backup_dir:?}" + + if ! is_quiet; then + log_debug "${prepare_command}" + log_info "BEGIN mariabackup prepare phase" + fi + + if is_quiet || ! is_verbose ; then + ${prepare_command} >/dev/null 2>&1 + prepare_rc=$? + elif ! is_quiet; then + if is_log_file; then + ${prepare_command} >>"${log_file}" 2>&1 + prepare_rc=$? + else + ${prepare_command} + prepare_rc=$? + fi + fi + + if [ ${prepare_rc} -ne 0 ]; then + log_fatal "Error executing mariabackup --prepare" + exit 1 + elif ! is_quiet; then + log_info "END mariabackup prepare phase" + fi +} +compress() { + compress_dir=$(dirname "${compress_file}") + + if [ -z "${backup_dir}" ]; then + log_fatal "backup-dir option is empty" + exit 1 + elif [ -e "${backup_dir}" ] && [ ! -d "${backup_dir}" ]; then + log_fatal "backup directory '${backup_dir}' exists but is not a directory" + exit 1 + fi + if [ -z "${compress_file}" ]; then + log_fatal "compress-file option is empty" + exit 1 + fi + if [ -n "${compress_dir}" ]; then + check_compress_dir + fi + + pigz_bin=$(command -v pigz) + gzip_bin=$(command -v gzip) + + if [ -n "${pigz_bin}" ]; then + compress_program="${pigz_bin} --keep -6" + elif [ -n "${gzip_bin}" ]; then + compress_program="${gzip_bin} -6" + else + log_fatal "Couldn't find pigz nor gzip.\nUse 'apt install pigz' or 'apt install gzip'." + exit 1 + fi + + if ! is_quiet; then + log_debug "Compression of ${backup_dir} to ${compress_file} using \`${compress_program}'" + log_info "BEGIN compression phase" + fi + if is_quiet || ! is_verbose ; then + tar --use-compress-program="${compress_program}" -cf "${compress_file}" "${backup_dir}" >/dev/null 2>&1 + tar_rc=$? + elif ! is_quiet; then + if is_log_file; then + tar --use-compress-program="${compress_program}" -cf "${compress_file}" "${backup_dir}" >>"${log_file}" 2>&1 + tar_rc=$? + else + tar --use-compress-program="${compress_program}" -cf "${compress_file}" "${backup_dir}" + tar_rc=$? + fi + fi + + if [ ${tar_rc} -ne 0 ]; then + log_fatal "An error occured while compressing ${backup_dir} to ${compress_file}" + exit 1 + elif ! is_quiet; then + log_info "END compression phase" + fi +} +main() { + kill_or_clean_lockfile "${lock_file}" + # shellcheck disable=SC2064 + trap "rm -f ${lock_file};" 0 + new_lock_file "${lock_file}" + + if [ "${do_backup}" = "1" ] && [ -n "${backup_dir}" ]; then + backup "${backup_dir}" + fi + + if [ "${do_compress}" = "1" ] && [ -n "${compress_file}" ]; then + compress "${backup_dir}" "${compress_file}" + fi +} + +# Declare variables + +lock_file="" +log_file="" +verbose="" +quiet="" +max_age="" +max_age="" +do_backup="" +backup_dir="" +do_compress="" +compress_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 + ;; + + -m|--max-age) + # with value separated by space + if [ -n "$2" ]; then + max_age=$(duration_in_seconds "$2") + shift + else + log_fatal 'ERROR: "-m|--max-age" requires a non-empty option argument.' + fi + ;; + --max-age=?*) + # with value speparated by = + max_age=$(duration_in_seconds "${1#*=}") + ;; + --max-age=) + # without value + log_fatal 'ERROR: "--max-age" requires a non-empty option argument.' + ;; + + --backup) + do_backup=1 + ;; + + --no-backup) + do_backup=0 + ;; + + --backup-dir) + # with value separated by space + if [ -n "$2" ]; then + backup_dir="$2" + shift + else + log_fatal 'ERROR: "--backup-dir" requires a non-empty option argument.' + fi + ;; + --backup-dir=?*) + # with value speparated by = + backup_dir=${1#*=} + ;; + --backup-dir=) + # without value + log_fatal '"--backup-dir" requires a non-empty option argument.' + ;; + + --compress) + do_compress=1 + ;; + + --no-compress) + do_compress=0 + ;; + + --compress-file) + # with value separated by space + if [ -n "$2" ]; then + compress_file="$2" + if [ -z "${do_compress}" ]; then + do_compress=1 + fi + shift + else + log_fatal '"--compress-file" requires a non-empty option argument.' + fi + ;; + --compress-file=?*) + # with value speparated by = + compress_file=${1#*=} + if [ -z "${do_compress}" ]; then + do_compress=1 + fi + ;; + --compress-file=) + # without value + log_fatal '"--compress-file" requires a non-empty option argument.' + ;; + + --lock-file) + # with value separated by space + if [ -n "$2" ]; then + lock_file="$2" + shift + else + log_fatal '"--lock-file" requires a non-empty option argument.' + fi + ;; + --lock-file=?*) + # with value speparated by = + lock_file=${1#*=} + ;; + --lock-file=) + # without value + log_fatal '"--lock-file" requires a non-empty option argument.' + ;; + + --log-file) + # with value separated by space + if [ -n "$2" ]; then + log_file="$2" + shift + else + log_fatal '"--log-file" requires a non-empty option argument.' + fi + ;; + --log-file=?*) + # with value speparated by = + log_file=${1#*=} + ;; + --log-file=) + # without value + log_fatal '"--log-file" requires a non-empty option argument.' + ;; + + -v|--verbose) + verbose=1 + ;; + + --quiet) + quiet=1 + verbose=0 + ;; + + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + if tty -s; then + printf 'Unknown option : %s\n' "$1" >&2 + echo "" >&2 + show_usage >&2 + exit 1 + else + log_fatal 'Unknown option : %s\n' "$1" >&2 + fi + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift +done + +# Default values + +lock_file="${lock_file:-/run/lock/evomariabackup.lock}" +verbose=${verbose:-0} +quiet=${quiet:-0} +max_age="${max_age:-86400}" +do_backup="${do_backup:-1}" +do_compress="${do_compress:-0}" + +main \ No newline at end of file diff --git a/mysql/tasks/utils.yml b/mysql/tasks/utils.yml index b210ca3a..e55b6361 100644 --- a/mysql/tasks/utils.yml +++ b/mysql/tasks/utils.yml @@ -229,5 +229,14 @@ dest: "{{ _mysql_scripts_dir }}/mysql-queries-killer.sh" mode: "0755" force: no + tags: + - mysql + +- name: "Install evomariabackup" + copy: + src: evomariabackup.sh + dest: "{{ _mysql_scripts_dir }}/evomariabackup" + mode: "0755" + force: no tags: - mysql \ No newline at end of file