#!/bin/bash set -Eeuo pipefail ERROR_MSG="" BACKUP_OK=0 DA_USER="" DB_NAME="" JOB_ID="" LOG_FILE="" JOB_FILE="${1:-}" if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then echo "mysql: missing job file" >&2 exit 1 fi PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)" CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf" LOG_DIR="${PLUGIN_DIR}/data/logs" COMMON_FILE="${PLUGIN_DIR}/scripts/setup/mysql_common.sh" mkdir -p "$LOG_DIR" urlencode() { local s="$1" local out="" local i c for ((i=0; i<${#s}; i++)); do c="${s:i:1}" case "$c" in [a-zA-Z0-9.~_-]) out+="$c" ;; ' ') out+='%20' ;; *) printf -v c '%%%02X' "'$c"; out+="$c" ;; esac done printf '%s' "$out" } notify_user() { local code="$1" if [ -z "${DA_USER:-}" ]; then return 0 fi local subject message if [ "$code" -eq 0 ] && [ "$BACKUP_OK" -eq 1 ]; then subject="Backup bazy MySQL zakończony pomyślnie" message="Backup bazy ${DB_NAME} został wykonany. Plik: ${TARGET_FILE:-nieznany}." else subject="Błąd wykonywania backupu bazy MySQL" if [ -n "$ERROR_MSG" ]; then message="Backup bazy ${DB_NAME} nie powiódł się: ${ERROR_MSG}" else message="Backup bazy ${DB_NAME} nie powiódł się. Sprawdź log zadania." fi fi local task_queue="/usr/local/directadmin/data/task.queue" local users="select1%3D$(urlencode "$DA_USER")" local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}" printf "%s\n" "$line" >> "$task_queue" } fail() { local msg="$1" ERROR_MSG="$msg" if [ -n "${LOG_FILE:-}" ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${msg}" >> "$LOG_FILE" fi } log_unexpected_error() { local line_no="$1" local command="$2" local code="$3" if [ -z "${LOG_FILE:-}" ]; then return 0 fi if [ -n "${ERROR_MSG:-}" ]; then return 0 fi ERROR_MSG="Nieoczekiwany błąd wykonywania backupu (kod ${code}, linia ${line_no})." echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG} CMD: ${command}" >> "$LOG_FILE" } load_plugin_settings() { if [ ! -f "$CONFIG_FILE" ]; then fail "Brak pliku konfiguracyjnego pluginu: $CONFIG_FILE" exit 1 fi # shellcheck disable=SC1090 source "$CONFIG_FILE" } quote_sh_value() { local value="$1" value="${value//\'/\'\\\'\'}" printf "'%s'" "$value" } set_job_var() { local key="$1" local value="$2" local tmp local owner_group="" if [ -f "$JOB_FILE" ]; then owner_group="$(stat -c '%u:%g' "$JOB_FILE" 2>/dev/null || stat -f '%u:%g' "$JOB_FILE" 2>/dev/null || true)" fi tmp="$(mktemp)" grep -v "^${key}=" "$JOB_FILE" > "$tmp" 2>/dev/null || true printf "%s=%s\n" "$key" "$(quote_sh_value "$value")" >> "$tmp" mv "$tmp" "$JOB_FILE" chmod 600 "$JOB_FILE" 2>/dev/null || true if [ "$(id -u)" -eq 0 ] && [ -n "$owner_group" ]; then chown "$owner_group" "$JOB_FILE" 2>/dev/null || true fi } on_exit() { local code="$1" if [ "$code" -ne 0 ] && [ -z "${ERROR_MSG:-}" ] && [ -n "${LOG_FILE:-}" ]; then ERROR_MSG="Zadanie backupu zakończyło się błędem (kod ${code}) bez zarejestrowanego szczegółu." echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG}" >> "$LOG_FILE" fi notify_user "$code" } trap 'log_unexpected_error "$LINENO" "$BASH_COMMAND" "$?"' ERR trap 'on_exit $?' EXIT [ -f "$COMMON_FILE" ] || { echo "Brak helpera MySQL: $COMMON_FILE" >&2 exit 1 } # shellcheck disable=SC1090 source "$COMMON_FILE" # shellcheck disable=SC1090 source "$JOB_FILE" JOB_ID="${JOB_ID:-$(basename "$JOB_FILE" .env)}" DA_USER="${DA_USER:-}" DB_NAME="${DB_NAME:-}" DATE_DIR="${DATE_DIR:-$(date +%Y-%m-%d)}" COMPRESS_BACKUP="${COMPRESS_BACKUP:-1}" USER_BASE_BACKUP_DIR="${USER_BASE_BACKUP_DIR:-/home/${DA_USER}/mysql_backup}" JOB_COMPRESS_BACKUP="${COMPRESS_BACKUP}" JOB_USER_BASE_BACKUP_DIR="${USER_BASE_BACKUP_DIR}" LOG_FILE="${LOG_DIR}/${JOB_ID}.log" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Start backup job ${JOB_ID}" > "$LOG_FILE" if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ]; then fail "Brak DA_USER lub DB_NAME w pliku zadania." exit 1 fi if [[ "$DB_NAME" != "${DA_USER}_"* ]]; then fail "Baza ${DB_NAME} nie należy do użytkownika ${DA_USER}." exit 1 fi load_plugin_settings COMPRESS_BACKUP="${JOB_COMPRESS_BACKUP:-$COMPRESS_BACKUP}" USER_BASE_BACKUP_DIR="${JOB_USER_BASE_BACKUP_DIR:-$USER_BASE_BACKUP_DIR}" mysql_load_alt_credentials MYSQL_BIN="$(mysql_alt_binary mysql)" || { fail "Brak binarium mysql." exit 1 } MYSQLDUMP_BIN="$(mysql_alt_binary mysqldump)" || { fail "Brak binarium mysqldump." exit 1 } mapfile -t MYSQL_ARGS < <(mysql_base_args) if [[ "$COMPRESS_BACKUP" =~ ^(true|yes|on)$ ]]; then COMPRESS_BACKUP="1" fi if [[ "$COMPRESS_BACKUP" =~ ^(false|no|off)$ ]]; then COMPRESS_BACKUP="0" fi TARGET_DIR="${USER_BASE_BACKUP_DIR%/}/${DATE_DIR}" mkdir -p "$TARGET_DIR" chmod 700 "$TARGET_DIR" 2>/dev/null || true if [ "$COMPRESS_BACKUP" = "1" ]; then TARGET_FILE="${TARGET_DIR}/${DB_NAME}.sql.gz" else TARGET_FILE="${TARGET_DIR}/${DB_NAME}.sql" fi TMP_FILE="${TARGET_FILE}.tmp.$$" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup source DB: ${DB_NAME}" >> "$LOG_FILE" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup target file: ${TARGET_FILE}" >> "$LOG_FILE" set +e if [ "$COMPRESS_BACKUP" = "1" ]; then MYSQL_PWD="$MYSQL_PASSWORD" "$MYSQLDUMP_BIN" \ "${MYSQL_ARGS[@]}" \ --single-transaction \ --quick \ --skip-lock-tables \ --routines \ --triggers \ --events \ --default-character-set=utf8mb4 \ "$DB_NAME" 2>>"$LOG_FILE" | gzip -c > "$TMP_FILE" RC=${PIPESTATUS[0]} else MYSQL_PWD="$MYSQL_PASSWORD" "$MYSQLDUMP_BIN" \ "${MYSQL_ARGS[@]}" \ --single-transaction \ --quick \ --skip-lock-tables \ --routines \ --triggers \ --events \ --default-character-set=utf8mb4 \ "$DB_NAME" > "$TMP_FILE" 2>>"$LOG_FILE" RC=$? fi set -e if [ "$RC" -ne 0 ]; then rm -f "$TMP_FILE" fail "mysqldump zakończył się błędem dla bazy ${DB_NAME}." exit 1 fi mv "$TMP_FILE" "$TARGET_FILE" chmod 600 "$TARGET_FILE" 2>/dev/null || true set_job_var "TARGET_FILE" "$TARGET_FILE" set_job_var "END_TS" "$(date +%s)" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup completed successfully." >> "$LOG_FILE" BACKUP_OK=1 exit 0